focalboard/server/services/store/sqlstore/notificationhints.go
Miguel de la Cruz 08c0b7a2fd
Refactor error usage from the store level up and add API helpers (#3792)
* Refactor error usage from the store level up and add API helpers

* Complete API tests

* Fix merge errorResponse calls

* Remove ensure helpers to allow for custom messages on permission errors

* Fix bad import and call

* Remove bad user check on auth that was added as part of the main merge

* Fix empty list test

* Replace deprecated proxy calls to ioutil.ReadAll with io.ReadAll

* Add information to the NotFound errors

* Add context to all remaining errors and address review comments

* Fix linter

* Adapt the new card API endpoints to the error refactor

* Remove almost all customErrorResponse calls

* Add request entity too large to errorResponse and remove customErrorResponse

* Fix linter
2022-09-13 12:18:40 +02:00

194 lines
5 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sqlstore
import (
"database/sql"
"fmt"
"time"
sq "github.com/Masterminds/squirrel"
"github.com/mattermost/focalboard/server/model"
"github.com/mattermost/focalboard/server/utils"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
)
var notificationHintFields = []string{
"block_type",
"block_id",
"modified_by_id",
"create_at",
"notify_at",
}
func valuesForNotificationHint(hint *model.NotificationHint) []interface{} {
return []interface{}{
hint.BlockType,
hint.BlockID,
hint.ModifiedByID,
hint.CreateAt,
hint.NotifyAt,
}
}
func (s *SQLStore) notificationHintFromRows(rows *sql.Rows) ([]*model.NotificationHint, error) {
hints := []*model.NotificationHint{}
for rows.Next() {
var hint model.NotificationHint
err := rows.Scan(
&hint.BlockType,
&hint.BlockID,
&hint.ModifiedByID,
&hint.CreateAt,
&hint.NotifyAt,
)
if err != nil {
return nil, err
}
hints = append(hints, &hint)
}
return hints, nil
}
// upsertNotificationHint creates or updates a notification hint. When updating the `notify_at` is set
// to the current time plus `notifyFreq`.
func (s *SQLStore) upsertNotificationHint(db sq.BaseRunner, hint *model.NotificationHint, notifyFreq time.Duration) (*model.NotificationHint, error) {
if err := hint.IsValid(); err != nil {
return nil, err
}
hint.CreateAt = utils.GetMillis()
notifyAt := utils.GetMillisForTime(time.Now().Add(notifyFreq))
hint.NotifyAt = notifyAt
query := s.getQueryBuilder(db).Insert(s.tablePrefix + "notification_hints").
Columns(notificationHintFields...).
Values(valuesForNotificationHint(hint)...)
if s.dbType == model.MysqlDBType {
query = query.Suffix("ON DUPLICATE KEY UPDATE notify_at = ?", notifyAt)
} else {
query = query.Suffix("ON CONFLICT (block_id) DO UPDATE SET notify_at = ?", notifyAt)
}
if _, err := query.Exec(); err != nil {
s.logger.Error("Cannot upsert notification hint",
mlog.String("block_id", hint.BlockID),
mlog.Err(err),
)
return nil, err
}
return hint, nil
}
// deleteNotificationHint deletes the notification hint for the specified block.
func (s *SQLStore) deleteNotificationHint(db sq.BaseRunner, blockID string) error {
query := s.getQueryBuilder(db).
Delete(s.tablePrefix + "notification_hints").
Where(sq.Eq{"block_id": blockID})
result, err := query.Exec()
if err != nil {
return err
}
count, err := result.RowsAffected()
if err != nil {
return err
}
if count == 0 {
return model.NewErrNotFound("notification hint BlockID=" + blockID)
}
return nil
}
// getNotificationHint fetches the notification hint for the specified block.
func (s *SQLStore) getNotificationHint(db sq.BaseRunner, blockID string) (*model.NotificationHint, error) {
query := s.getQueryBuilder(db).
Select(notificationHintFields...).
From(s.tablePrefix + "notification_hints").
Where(sq.Eq{"block_id": blockID})
rows, err := query.Query()
if err != nil {
s.logger.Error("Cannot fetch notification hint",
mlog.String("block_id", blockID),
mlog.Err(err),
)
return nil, err
}
defer s.CloseRows(rows)
hint, err := s.notificationHintFromRows(rows)
if err != nil {
s.logger.Error("Cannot get notification hint",
mlog.String("block_id", blockID),
mlog.Err(err),
)
return nil, err
}
if len(hint) == 0 {
return nil, model.NewErrNotFound("notification hint BlockID=" + blockID)
}
return hint[0], nil
}
// getNextNotificationHint fetches the next scheduled notification hint. If remove is true
// then the hint is removed from the database as well, as if popping from a stack.
func (s *SQLStore) getNextNotificationHint(db sq.BaseRunner, remove bool) (*model.NotificationHint, error) {
selectQuery := s.getQueryBuilder(db).
Select(notificationHintFields...).
From(s.tablePrefix + "notification_hints").
OrderBy("notify_at").
Limit(1)
rows, err := selectQuery.Query()
if err != nil {
s.logger.Error("Cannot fetch next notification hint",
mlog.Err(err),
)
return nil, err
}
defer s.CloseRows(rows)
hints, err := s.notificationHintFromRows(rows)
if err != nil {
s.logger.Error("Cannot get next notification hint",
mlog.Err(err),
)
return nil, err
}
if len(hints) == 0 {
return nil, model.NewErrNotFound("next notification hint")
}
hint := hints[0]
if remove {
deleteQuery := s.getQueryBuilder(db).
Delete(s.tablePrefix + "notification_hints").
Where(sq.Eq{"block_id": hint.BlockID})
result, err := deleteQuery.Exec()
if err != nil {
return nil, fmt.Errorf("cannot delete while getting next notification hint: %w", err)
}
rows, err := result.RowsAffected()
if err != nil {
return nil, fmt.Errorf("cannot verify delete while getting next notification hint: %w", err)
}
if rows == 0 {
// another node likely has grabbed this hint for processing concurrently; let that node handle it
// and we'll return an error here so we try again.
return nil, model.NewErrNotFound("notification hint")
}
}
return hint, nil
}