Channel Bookmarks (#25449)
* create ChannelBookmarks table * ChannelBookmark model * channel bookamrks Store layer * add GetBookmarksForAllChannelByIdSince * add channel bookmarks to test store * Add channel bookmarks to app layer * remove index for createAt in channel bookmarks migrations * remove createAt from select channel bookmark query and enable store delete bookmark test * update reponse of UpdateBookmark * rename db migration files * channel bookmarks store update sort order * channel bookmarks app layer update sort order * fix lint & tests * Fix lint and introduce util functions to insert / remove from slice * remove model etag * i18n * defer remove file info after test run * Fix tests passing the request context * fix migrations * fix TestRetry * Add bookmark permissions (#25560) * Adds channel bookmarks permissions * Fix linter * Remove unnecessary empty lines * Remove scss change as it's not necessary anymore * Fix mock store * Fix mock store and add role entry * Fix test * Adds cypress test and update permissions migration to update admin roles * Adds channel bookmarks roles to default admin roles * Adds bookmark permissions to default role permissions constant in webapp * Update mmctl test * Update permission test after normalising the roles * fix store tests * fix app layer tests * Add new bookmark endpoint (#25624) * Adds channel bookmarks api scaffold and create endpoint * Applies review comments to the API docs * Adds websocket test to create channel bookmark --------- Co-authored-by: Mattermost Build <build@mattermost.com> * MM-54426 exclude Channel Bookmarks files from data retention (#25656) * Augment channel APIs to include bookmarks (#25567) * update files docs for server 9.4 * Adds update channel bookmark endpoint (#25653) * Adds update channel bookmark sort order endpoint (#25686) * Adds update channel bookmark endpoint * Updates edit app method to return the right deleted bookmark and adds tests * Adds the update channel bookmark sort order endpoint * Fix repeated test after merge * Assign right permissions to each test * Update store and app layer to return specific errors and add tests * Adds delete channel bookmark endpoint (#25693) * Updates edit app method to return the right deleted bookmark and adds tests * Fix repeated test after merge * Updates edit app method to return the right deleted bookmark and adds tests * Adds delete channel bookmark endpoint * Adds list channel bookmarks endpoint (#25700) * Add channel moderation to bookmarks (#25716) * fix migrations index * fix getChannelsForTeamForUser * fix getChannelsForTeamForUser * fix bad merge client4 * fix file api with bookmark permission * add ChannelBookmarks feature flag * add missing translations * Set DB column for type as enum * use custom type for bookmark query using sqlx * use transaction when saving bookmark * return NewErrNotFound instead of Sql.ErrNoRows * use squirrel for IN query * add a limit of 1K for records in GetBookmarksForAllChannelByIdSince * UpdateSortOrder with one single query instead of multiple updates * fix shadow declaration * fix channel bookmarks permission string definition in admin console * fix another shadow declaration * Fix model conversion * add SplitSliceInChunks * remove include bookmarks in channels api * Cap amount of bookmarks per channel * add etag back to get channels * feedback review * update file info when replacing a bookmark file * return 501 not implemented when the license is not available * add detail message when getting channel member on bookmark api * start audit before permission check on create bookmark api * use require.Eventuallyf for testing WS events * remove unnecessary log in app layer * use require instead of assert to avoid panics * enforce limit when querying bookmarks since * prevent to create/update bookmark if file is already attached * fix lint * delete file when a bookmark is deleted * Dot allow to set a fileId and a url at the same time to a bookmark * fix query to delete a file that belongs to a bookmark * do not patch the bookmark type * Server side FeatureFlag check (#26145) * use ff in server, set ff to false * turn on FF for unit tests * defer unset FF for unit tests * turn ff on for testing * only allow attaching files that were uploaded for bookmark * Set feature flag off as default * fix lint * update email templates as PR failed * revert templates * force the assignment of ID when creating a bookmark * Fix unit tests --------- Co-authored-by: Miguel de la Cruz <miguel@mcrx.me> Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Caleb Roseland <caleb@calebroseland.com> Co-authored-by: Scott Bishel <scott.bishel@mattermost.com>
Этот коммит содержится в:
@@ -22,6 +22,7 @@ type OpenTracingLayer struct {
|
||||
AuditStore store.AuditStore
|
||||
BotStore store.BotStore
|
||||
ChannelStore store.ChannelStore
|
||||
ChannelBookmarkStore store.ChannelBookmarkStore
|
||||
ChannelMemberHistoryStore store.ChannelMemberHistoryStore
|
||||
ClusterDiscoveryStore store.ClusterDiscoveryStore
|
||||
CommandStore store.CommandStore
|
||||
@@ -78,6 +79,10 @@ func (s *OpenTracingLayer) Channel() store.ChannelStore {
|
||||
return s.ChannelStore
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayer) ChannelBookmark() store.ChannelBookmarkStore {
|
||||
return s.ChannelBookmarkStore
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayer) ChannelMemberHistory() store.ChannelMemberHistoryStore {
|
||||
return s.ChannelMemberHistoryStore
|
||||
}
|
||||
@@ -261,6 +266,11 @@ type OpenTracingLayerChannelStore struct {
|
||||
Root *OpenTracingLayer
|
||||
}
|
||||
|
||||
type OpenTracingLayerChannelBookmarkStore struct {
|
||||
store.ChannelBookmarkStore
|
||||
Root *OpenTracingLayer
|
||||
}
|
||||
|
||||
type OpenTracingLayerChannelMemberHistoryStore struct {
|
||||
store.ChannelMemberHistoryStore
|
||||
Root *OpenTracingLayer
|
||||
@@ -2689,6 +2699,132 @@ func (s *OpenTracingLayerChannelStore) UserBelongsToChannels(userID string, chan
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerChannelBookmarkStore) Delete(bookmarkId string, deleteFile bool) error {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelBookmarkStore.Delete")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
err := s.ChannelBookmarkStore.Delete(bookmarkId, deleteFile)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerChannelBookmarkStore) ErrorIfBookmarkFileInfoAlreadyAttached(fileId string) error {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelBookmarkStore.ErrorIfBookmarkFileInfoAlreadyAttached")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
err := s.ChannelBookmarkStore.ErrorIfBookmarkFileInfoAlreadyAttached(fileId)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerChannelBookmarkStore) Get(Id string, includeDeleted bool) (*model.ChannelBookmarkWithFileInfo, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelBookmarkStore.Get")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.ChannelBookmarkStore.Get(Id, includeDeleted)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerChannelBookmarkStore) GetBookmarksForChannelSince(channelId string, since int64) ([]*model.ChannelBookmarkWithFileInfo, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelBookmarkStore.GetBookmarksForChannelSince")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.ChannelBookmarkStore.GetBookmarksForChannelSince(channelId, since)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerChannelBookmarkStore) Save(bookmark *model.ChannelBookmark, increaseSortOrder bool) (*model.ChannelBookmarkWithFileInfo, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelBookmarkStore.Save")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.ChannelBookmarkStore.Save(bookmark, increaseSortOrder)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerChannelBookmarkStore) Update(bookmark *model.ChannelBookmark) error {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelBookmarkStore.Update")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
err := s.ChannelBookmarkStore.Update(bookmark)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerChannelBookmarkStore) UpdateSortOrder(bookmarkId string, channelId string, newIndex int64) ([]*model.ChannelBookmarkWithFileInfo, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelBookmarkStore.UpdateSortOrder")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.ChannelBookmarkStore.UpdateSortOrder(bookmarkId, channelId, newIndex)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerChannelMemberHistoryStore) DeleteOrphanedRows(limit int) (int64, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelMemberHistoryStore.DeleteOrphanedRows")
|
||||
@@ -13284,6 +13420,7 @@ func New(childStore store.Store, ctx context.Context) *OpenTracingLayer {
|
||||
newStore.AuditStore = &OpenTracingLayerAuditStore{AuditStore: childStore.Audit(), Root: &newStore}
|
||||
newStore.BotStore = &OpenTracingLayerBotStore{BotStore: childStore.Bot(), Root: &newStore}
|
||||
newStore.ChannelStore = &OpenTracingLayerChannelStore{ChannelStore: childStore.Channel(), Root: &newStore}
|
||||
newStore.ChannelBookmarkStore = &OpenTracingLayerChannelBookmarkStore{ChannelBookmarkStore: childStore.ChannelBookmark(), Root: &newStore}
|
||||
newStore.ChannelMemberHistoryStore = &OpenTracingLayerChannelMemberHistoryStore{ChannelMemberHistoryStore: childStore.ChannelMemberHistory(), Root: &newStore}
|
||||
newStore.ClusterDiscoveryStore = &OpenTracingLayerClusterDiscoveryStore{ClusterDiscoveryStore: childStore.ClusterDiscovery(), Root: &newStore}
|
||||
newStore.CommandStore = &OpenTracingLayerCommandStore{CommandStore: childStore.Command(), Root: &newStore}
|
||||
|
||||
@@ -26,6 +26,7 @@ type RetryLayer struct {
|
||||
AuditStore store.AuditStore
|
||||
BotStore store.BotStore
|
||||
ChannelStore store.ChannelStore
|
||||
ChannelBookmarkStore store.ChannelBookmarkStore
|
||||
ChannelMemberHistoryStore store.ChannelMemberHistoryStore
|
||||
ClusterDiscoveryStore store.ClusterDiscoveryStore
|
||||
CommandStore store.CommandStore
|
||||
@@ -82,6 +83,10 @@ func (s *RetryLayer) Channel() store.ChannelStore {
|
||||
return s.ChannelStore
|
||||
}
|
||||
|
||||
func (s *RetryLayer) ChannelBookmark() store.ChannelBookmarkStore {
|
||||
return s.ChannelBookmarkStore
|
||||
}
|
||||
|
||||
func (s *RetryLayer) ChannelMemberHistory() store.ChannelMemberHistoryStore {
|
||||
return s.ChannelMemberHistoryStore
|
||||
}
|
||||
@@ -265,6 +270,11 @@ type RetryLayerChannelStore struct {
|
||||
Root *RetryLayer
|
||||
}
|
||||
|
||||
type RetryLayerChannelBookmarkStore struct {
|
||||
store.ChannelBookmarkStore
|
||||
Root *RetryLayer
|
||||
}
|
||||
|
||||
type RetryLayerChannelMemberHistoryStore struct {
|
||||
store.ChannelMemberHistoryStore
|
||||
Root *RetryLayer
|
||||
@@ -2987,6 +2997,153 @@ func (s *RetryLayerChannelStore) UserBelongsToChannels(userID string, channelIds
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerChannelBookmarkStore) Delete(bookmarkId string, deleteFile bool) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.ChannelBookmarkStore.Delete(bookmarkId, deleteFile)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerChannelBookmarkStore) ErrorIfBookmarkFileInfoAlreadyAttached(fileId string) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.ChannelBookmarkStore.ErrorIfBookmarkFileInfoAlreadyAttached(fileId)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerChannelBookmarkStore) Get(Id string, includeDeleted bool) (*model.ChannelBookmarkWithFileInfo, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.ChannelBookmarkStore.Get(Id, includeDeleted)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerChannelBookmarkStore) GetBookmarksForChannelSince(channelId string, since int64) ([]*model.ChannelBookmarkWithFileInfo, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.ChannelBookmarkStore.GetBookmarksForChannelSince(channelId, since)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerChannelBookmarkStore) Save(bookmark *model.ChannelBookmark, increaseSortOrder bool) (*model.ChannelBookmarkWithFileInfo, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.ChannelBookmarkStore.Save(bookmark, increaseSortOrder)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerChannelBookmarkStore) Update(bookmark *model.ChannelBookmark) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.ChannelBookmarkStore.Update(bookmark)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerChannelBookmarkStore) UpdateSortOrder(bookmarkId string, channelId string, newIndex int64) ([]*model.ChannelBookmarkWithFileInfo, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.ChannelBookmarkStore.UpdateSortOrder(bookmarkId, channelId, newIndex)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerChannelMemberHistoryStore) DeleteOrphanedRows(limit int) (int64, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -15157,6 +15314,7 @@ func New(childStore store.Store) *RetryLayer {
|
||||
newStore.AuditStore = &RetryLayerAuditStore{AuditStore: childStore.Audit(), Root: &newStore}
|
||||
newStore.BotStore = &RetryLayerBotStore{BotStore: childStore.Bot(), Root: &newStore}
|
||||
newStore.ChannelStore = &RetryLayerChannelStore{ChannelStore: childStore.Channel(), Root: &newStore}
|
||||
newStore.ChannelBookmarkStore = &RetryLayerChannelBookmarkStore{ChannelBookmarkStore: childStore.ChannelBookmark(), Root: &newStore}
|
||||
newStore.ChannelMemberHistoryStore = &RetryLayerChannelMemberHistoryStore{ChannelMemberHistoryStore: childStore.ChannelMemberHistory(), Root: &newStore}
|
||||
newStore.ClusterDiscoveryStore = &RetryLayerClusterDiscoveryStore{ClusterDiscoveryStore: childStore.ClusterDiscovery(), Root: &newStore}
|
||||
newStore.CommandStore = &RetryLayerCommandStore{CommandStore: childStore.Command(), Root: &newStore}
|
||||
|
||||
@@ -20,6 +20,7 @@ func genStore() *mocks.Store {
|
||||
mock.On("Bot").Return(&mocks.BotStore{})
|
||||
mock.On("Channel").Return(&mocks.ChannelStore{})
|
||||
mock.On("ChannelMemberHistory").Return(&mocks.ChannelMemberHistoryStore{})
|
||||
mock.On("ChannelBookmark").Return(&mocks.ChannelBookmarkStore{})
|
||||
mock.On("ClusterDiscovery").Return(&mocks.ClusterDiscoveryStore{})
|
||||
mock.On("RemoteCluster").Return(&mocks.RemoteClusterStore{})
|
||||
mock.On("Command").Return(&mocks.CommandStore{})
|
||||
@@ -61,6 +62,7 @@ func genStore() *mocks.Store {
|
||||
mock.On("PostPersistentNotification").Return(&mocks.PostPersistentNotificationStore{})
|
||||
mock.On("TrueUpReview").Return(&mocks.TrueUpReviewStore{})
|
||||
mock.On("DesktopTokens").Return(&mocks.DesktopTokensStore{})
|
||||
mock.On("ChannelBookmark").Return(&mocks.ChannelBookmarkStore{})
|
||||
return mock
|
||||
}
|
||||
|
||||
|
||||
380
server/channels/store/sqlstore/channel_bookmark_store.go
Обычный файл
380
server/channels/store/sqlstore/channel_bookmark_store.go
Обычный файл
@@ -0,0 +1,380 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
sq "github.com/mattermost/squirrel"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/utils"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type SqlChannelBookmarkStore struct {
|
||||
*SqlStore
|
||||
}
|
||||
|
||||
func newSqlChannelBookmarkStore(sqlStore *SqlStore) store.ChannelBookmarkStore {
|
||||
return &SqlChannelBookmarkStore{sqlStore}
|
||||
}
|
||||
|
||||
func bookmarkWithFileInfoSliceColumns() []string {
|
||||
return []string{
|
||||
"cb.Id",
|
||||
"cb.OwnerId",
|
||||
"cb.ChannelId",
|
||||
"cb.FileInfoId",
|
||||
"cb.CreateAt",
|
||||
"cb.UpdateAt",
|
||||
"cb.DeleteAt",
|
||||
"cb.DisplayName",
|
||||
"cb.SortOrder",
|
||||
"cb.LinkUrl",
|
||||
"cb.ImageUrl",
|
||||
"cb.Emoji",
|
||||
"cb.Type",
|
||||
"COALESCE(cb.OriginalId, '') as OriginalId",
|
||||
"COALESCE(fi.Id, '') as FileId",
|
||||
"COALESCE(fi.Name, '') as FileName",
|
||||
"COALESCE(fi.Extension, '') as Extension",
|
||||
"COALESCE(fi.Size, 0) as Size",
|
||||
"COALESCE(fi.MimeType, '') as MimeType",
|
||||
"COALESCE(fi.Width, 0) as Width",
|
||||
"COALESCE(fi.Height, 0) as Height",
|
||||
"COALESCE(fi.HasPreviewImage, false) as HasPreviewImage",
|
||||
"COALESCE(fi.MiniPreview, '') as MiniPreview",
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SqlChannelBookmarkStore) ErrorIfBookmarkFileInfoAlreadyAttached(fileId string) error {
|
||||
existingQuery := s.getSubQueryBuilder().
|
||||
Select("FileInfoId").
|
||||
From("ChannelBookmarks").
|
||||
Where(sq.And{
|
||||
sq.Eq{"FileInfoId": fileId},
|
||||
sq.Eq{"DeleteAt": 0},
|
||||
})
|
||||
|
||||
alreadyAttachedQuery := s.getQueryBuilder().
|
||||
Select("COUNT(*)").
|
||||
From("FileInfo").
|
||||
Where(sq.Or{
|
||||
sq.Expr("Id IN (?)", existingQuery),
|
||||
sq.And{
|
||||
sq.Or{
|
||||
sq.NotEq{"PostId": ""},
|
||||
sq.NotEq{"CreatorId": model.BookmarkFileOwner},
|
||||
},
|
||||
sq.Eq{"Id": fileId},
|
||||
},
|
||||
})
|
||||
|
||||
var attached int64
|
||||
err := s.GetReplicaX().GetBuilder(&attached, alreadyAttachedQuery)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable_to_save_channel_bookmark")
|
||||
}
|
||||
|
||||
if attached > 0 {
|
||||
return store.NewErrInvalidInput("ChannelBookmarks", "FileInfoId", fileId)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SqlChannelBookmarkStore) Get(Id string, includeDeleted bool) (*model.ChannelBookmarkWithFileInfo, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(bookmarkWithFileInfoSliceColumns()...).
|
||||
From("ChannelBookmarks cb").
|
||||
LeftJoin("FileInfo fi ON cb.FileInfoId = fi.Id").
|
||||
Where(sq.Eq{"cb.Id": Id})
|
||||
|
||||
if !includeDeleted {
|
||||
query = query.Where(sq.Eq{"cb.DeleteAt": 0})
|
||||
}
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "channel_bookmark_getforchanneltsince_tosql")
|
||||
}
|
||||
|
||||
bookmark := model.ChannelBookmarkAndFileInfo{}
|
||||
|
||||
if err := s.GetReplicaX().Get(&bookmark, queryString, args...); err != nil {
|
||||
return nil, store.NewErrNotFound("ChannelBookmark", Id)
|
||||
}
|
||||
|
||||
return bookmark.ToChannelBookmarkWithFileInfo(), nil
|
||||
}
|
||||
|
||||
func (s *SqlChannelBookmarkStore) Save(bookmark *model.ChannelBookmark, increaseSortOrder bool) (b *model.ChannelBookmarkWithFileInfo, err error) {
|
||||
bookmark.PreSave()
|
||||
if err := bookmark.IsValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transaction, err := s.GetMasterX().Beginx()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer finalizeTransactionX(transaction, &err)
|
||||
|
||||
var currentBookmarksCount int64
|
||||
query := s.getQueryBuilder().
|
||||
Select("COUNT(*) as count").
|
||||
From("ChannelBookmarks").
|
||||
Where(sq.Eq{"ChannelId": bookmark.ChannelId, "DeleteAt": 0})
|
||||
err = transaction.GetBuilder(¤tBookmarksCount, query)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed while getting the count of ChannelBookmarks")
|
||||
}
|
||||
|
||||
if currentBookmarksCount >= model.MaxBookmarksPerChannel {
|
||||
return nil, store.NewErrLimitExceeded("bookmarks_per_channel", int(currentBookmarksCount), "channelId="+bookmark.ChannelId)
|
||||
}
|
||||
|
||||
if bookmark.FileId != "" {
|
||||
err = s.ErrorIfBookmarkFileInfoAlreadyAttached(bookmark.FileId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable_to_save_channel_bookmark")
|
||||
}
|
||||
}
|
||||
|
||||
if increaseSortOrder {
|
||||
var sortOrder int64
|
||||
query := s.getQueryBuilder().
|
||||
Select("COALESCE(MAX(SortOrder), -1) as SortOrder").
|
||||
From("ChannelBookmarks").
|
||||
Where(sq.Eq{"ChannelId": bookmark.ChannelId, "DeleteAt": 0})
|
||||
|
||||
err = transaction.GetBuilder(&sortOrder, query)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed while getting the sortOrder from ChannelBookmarks")
|
||||
}
|
||||
bookmark.SortOrder = sortOrder + 1
|
||||
}
|
||||
|
||||
sql, args, sqlErr := s.getQueryBuilder().
|
||||
Insert("ChannelBookmarks").
|
||||
Columns("Id", "CreateAt", "UpdateAt", "DeleteAt", "ChannelId", "OwnerId", "FileInfoId", "DisplayName", "SortOrder", "LinkUrl", "ImageUrl", "Emoji", "Type").
|
||||
Values(bookmark.Id, bookmark.CreateAt, bookmark.UpdateAt, bookmark.DeleteAt, bookmark.ChannelId, bookmark.OwnerId, bookmark.FileId, bookmark.DisplayName, bookmark.SortOrder, bookmark.LinkUrl, bookmark.ImageUrl, bookmark.Emoji, bookmark.Type).
|
||||
ToSql()
|
||||
|
||||
if sqlErr != nil {
|
||||
return nil, errors.Wrap(err, "insert_channel_bookmark_to_sql")
|
||||
}
|
||||
|
||||
if _, insertErr := transaction.Exec(sql, args...); insertErr != nil {
|
||||
return nil, errors.Wrap(insertErr, "unable_to_save_channel_bookmark")
|
||||
}
|
||||
|
||||
var fileInfo model.FileInfo
|
||||
if bookmark.FileId != "" {
|
||||
query, args, queryErr := s.getQueryBuilder().
|
||||
Select("Id, Name, Extension, Size, MimeType, Width, Height, HasPreviewImage, MiniPreview").
|
||||
From("FileInfo").
|
||||
Where(sq.Eq{"Id": bookmark.FileId}).
|
||||
ToSql()
|
||||
if queryErr != nil {
|
||||
return nil, errors.Wrap(queryErr, "channel_bookmark_get_file_info_to_sql")
|
||||
}
|
||||
if queryErr = transaction.Get(&fileInfo, query, args...); queryErr != nil {
|
||||
return nil, errors.Wrap(queryErr, "unable_to_get_channel_bookmark_file_info")
|
||||
}
|
||||
}
|
||||
|
||||
err = transaction.Commit()
|
||||
return bookmark.ToBookmarkWithFileInfo(&fileInfo), err
|
||||
}
|
||||
|
||||
func (s *SqlChannelBookmarkStore) Update(bookmark *model.ChannelBookmark) error {
|
||||
bookmark.PreUpdate()
|
||||
if err := bookmark.IsValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query, args, err := s.getQueryBuilder().
|
||||
Update("ChannelBookmarks").
|
||||
Set("DisplayName", bookmark.DisplayName).
|
||||
Set("SortOrder", bookmark.SortOrder).
|
||||
Set("LinkUrl", bookmark.LinkUrl).
|
||||
Set("ImageUrl", bookmark.ImageUrl).
|
||||
Set("Emoji", bookmark.Emoji).
|
||||
Set("FileInfoId", bookmark.FileId).
|
||||
Set("UpdateAt", bookmark.UpdateAt).
|
||||
Where(sq.Eq{
|
||||
"Id": bookmark.Id,
|
||||
"DeleteAt": 0,
|
||||
}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "channel_bookmark_update_tosql")
|
||||
}
|
||||
|
||||
res, err := s.GetMasterX().Exec(query, args...)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to update channel bookmark with id=%s", bookmark.Id)
|
||||
}
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get affected rows after updating bookmark with id=%s", bookmark.Id)
|
||||
}
|
||||
if rowsAffected == 0 {
|
||||
return store.NewErrNotFound("ChannelBookmark", bookmark.Id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SqlChannelBookmarkStore) UpdateSortOrder(bookmarkId, channelId string, newIndex int64) ([]*model.ChannelBookmarkWithFileInfo, error) {
|
||||
now := model.GetMillis()
|
||||
transaction, err := s.GetMasterX().Beginx()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer finalizeTransactionX(transaction, &err)
|
||||
|
||||
bookmarks, err := s.GetBookmarksForChannelSince(channelId, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if (int(newIndex) > len(bookmarks)-1) || newIndex < 0 {
|
||||
return nil, store.NewErrInvalidInput("ChannelBookmark", "SortOrder", newIndex)
|
||||
}
|
||||
|
||||
currentIndex := -1
|
||||
var current *model.ChannelBookmarkWithFileInfo
|
||||
for index, b := range bookmarks {
|
||||
if b.Id == bookmarkId {
|
||||
currentIndex = index
|
||||
current = b
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if currentIndex == -1 {
|
||||
return nil, store.NewErrNotFound("ChannelBookmark", bookmarkId)
|
||||
}
|
||||
|
||||
bookmarks = utils.RemoveElementFromSliceAtIndex(bookmarks, currentIndex)
|
||||
bookmarks = utils.InsertElementToSliceAtIndex(bookmarks, current, int(newIndex))
|
||||
caseStmt := sq.Case()
|
||||
query := s.getQueryBuilder().
|
||||
Update("ChannelBookmarks")
|
||||
|
||||
ids := []string{}
|
||||
for index, b := range bookmarks {
|
||||
b.SortOrder = int64(index)
|
||||
caseStmt = caseStmt.When(sq.Eq{"Id": b.Id}, strconv.FormatInt(int64(index), 10))
|
||||
ids = append(ids, b.Id)
|
||||
}
|
||||
query = query.Set("SortOrder", caseStmt)
|
||||
query = query.Set("UpdateAt", now)
|
||||
query = query.Where(sq.Eq{"Id": ids})
|
||||
queryStr, args, queryErr := query.ToSql()
|
||||
if queryErr != nil {
|
||||
return nil, queryErr
|
||||
}
|
||||
|
||||
if _, updateSortOrderErr := transaction.Exec(queryStr, args...); updateSortOrderErr != nil {
|
||||
return nil, updateSortOrderErr
|
||||
}
|
||||
|
||||
err = transaction.Commit()
|
||||
return bookmarks, err
|
||||
}
|
||||
|
||||
func (s *SqlChannelBookmarkStore) Delete(bookmarkId string, deleteFile bool) error {
|
||||
now := model.GetMillis()
|
||||
transaction, err := s.GetMasterX().Beginx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer finalizeTransactionX(transaction, &err)
|
||||
query, args, err := s.getQueryBuilder().
|
||||
Update("ChannelBookmarks").
|
||||
Set("DeleteAt", now).
|
||||
Set("UpdateAt", now).
|
||||
Where(sq.Eq{"Id": bookmarkId}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "channel_bookmark_delete_tosql")
|
||||
}
|
||||
|
||||
_, err = transaction.Exec(query, args...)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to delete channel bookmark with id=%s", bookmarkId)
|
||||
}
|
||||
|
||||
if deleteFile {
|
||||
fileIdQuery := s.getSubQueryBuilder().
|
||||
Select("FileInfoId").
|
||||
From("ChannelBookmarks").
|
||||
Where(sq.And{
|
||||
sq.Eq{"Id": bookmarkId},
|
||||
sq.Eq{"DeleteAt": 0},
|
||||
})
|
||||
|
||||
fileQuery, fileArgs, fileErr := s.getQueryBuilder().
|
||||
Update("FileInfo").
|
||||
Set("DeleteAt", now).
|
||||
Set("UpdateAt", now).
|
||||
Where(sq.Expr("Id IN (?)", fileIdQuery)).
|
||||
ToSql()
|
||||
|
||||
if fileErr != nil {
|
||||
return errors.Wrap(err, "channel_bookmark_delete_tosql")
|
||||
}
|
||||
|
||||
_, err = transaction.Exec(fileQuery, fileArgs...)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to delete channel bookmark with id=%s", bookmarkId)
|
||||
}
|
||||
}
|
||||
|
||||
return transaction.Commit()
|
||||
}
|
||||
|
||||
func (s *SqlChannelBookmarkStore) GetBookmarksForChannelSince(channelId string, since int64) ([]*model.ChannelBookmarkWithFileInfo, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(bookmarkWithFileInfoSliceColumns()...).
|
||||
From("ChannelBookmarks cb").
|
||||
LeftJoin("FileInfo fi ON cb.FileInfoId = fi.Id").
|
||||
Where(sq.Eq{"cb.ChannelId": channelId})
|
||||
|
||||
if since > 0 {
|
||||
query = query.Where(sq.Or{
|
||||
sq.GtOrEq{"cb.UpdateAt": since},
|
||||
sq.GtOrEq{"cb.DeleteAt": since},
|
||||
})
|
||||
} else {
|
||||
query = query.Where(sq.Eq{"cb.DeleteAt": 0})
|
||||
}
|
||||
|
||||
query = query.
|
||||
OrderBy("cb.SortOrder ASC").
|
||||
OrderBy("cb.DeleteAt ASC").
|
||||
Limit(model.MaxBookmarksPerChannel * 2) // limit to the double of the cap as an edge case
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "channel_bookmark_getforchanneltsince_tosql")
|
||||
}
|
||||
|
||||
bookmarkRows := []model.ChannelBookmarkAndFileInfo{}
|
||||
bookmarks := []*model.ChannelBookmarkWithFileInfo{}
|
||||
|
||||
if err := s.GetReplicaX().Select(&bookmarkRows, queryString, args...); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find bookmarks")
|
||||
}
|
||||
|
||||
for _, bookmark := range bookmarkRows {
|
||||
bookmarks = append(bookmarks, bookmark.ToChannelBookmarkWithFileInfo())
|
||||
}
|
||||
|
||||
return bookmarks, nil
|
||||
}
|
||||
14
server/channels/store/sqlstore/channel_bookmark_store_test.go
Обычный файл
14
server/channels/store/sqlstore/channel_bookmark_store_test.go
Обычный файл
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store/storetest"
|
||||
)
|
||||
|
||||
func TestChannelBookmarkStore(t *testing.T) {
|
||||
StoreTestWithSqlStore(t, storetest.TestChannelBookmarkStore)
|
||||
}
|
||||
@@ -465,12 +465,12 @@ func (fs SqlFileInfoStore) PermanentDelete(rctx request.CTX, fileId string) erro
|
||||
func (fs SqlFileInfoStore) PermanentDeleteBatch(rctx request.CTX, endTime int64, limit int64) (int64, error) {
|
||||
var query string
|
||||
if fs.DriverName() == "postgres" {
|
||||
query = "DELETE from FileInfo WHERE Id = any (array (SELECT Id FROM FileInfo WHERE CreateAt < ? LIMIT ?))"
|
||||
query = "DELETE from FileInfo WHERE Id = any (array (SELECT Id FROM FileInfo WHERE CreateAt < ? AND CreatorId != ? LIMIT ?))"
|
||||
} else {
|
||||
query = "DELETE from FileInfo WHERE CreateAt < ? LIMIT ?"
|
||||
query = "DELETE from FileInfo WHERE CreateAt < ? AND CreatorId != ? LIMIT ?"
|
||||
}
|
||||
|
||||
sqlResult, err := fs.GetMasterX().Exec(query, endTime, limit)
|
||||
sqlResult, err := fs.GetMasterX().Exec(query, endTime, model.BookmarkFileOwner, limit)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "failed to delete FileInfos in batch")
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ type SqlStoreStores struct {
|
||||
postPersistentNotification store.PostPersistentNotificationStore
|
||||
trueUpReview store.TrueUpReviewStore
|
||||
desktopTokens store.DesktopTokensStore
|
||||
channelBookmarks store.ChannelBookmarkStore
|
||||
}
|
||||
|
||||
type SqlStore struct {
|
||||
@@ -236,6 +237,7 @@ func New(settings model.SqlSettings, logger mlog.LoggerIFace, metrics einterface
|
||||
store.stores.postPersistentNotification = newSqlPostPersistentNotificationStore(store)
|
||||
store.stores.trueUpReview = newSqlTrueUpReviewStore(store)
|
||||
store.stores.desktopTokens = newSqlDesktopTokensStore(store, metrics)
|
||||
store.stores.channelBookmarks = newSqlChannelBookmarkStore(store)
|
||||
|
||||
store.stores.preference.(*SqlPreferenceStore).deleteUnusedFeatures()
|
||||
|
||||
@@ -1043,6 +1045,10 @@ func (ss *SqlStore) DesktopTokens() store.DesktopTokensStore {
|
||||
return ss.stores.desktopTokens
|
||||
}
|
||||
|
||||
func (ss *SqlStore) ChannelBookmark() store.ChannelBookmarkStore {
|
||||
return ss.stores.channelBookmarks
|
||||
}
|
||||
|
||||
func (ss *SqlStore) DropAllTables() {
|
||||
if ss.DriverName() == model.DatabaseDriverPostgres {
|
||||
ss.masterX.Exec(`DO
|
||||
|
||||
@@ -91,6 +91,7 @@ type Store interface {
|
||||
PostPersistentNotification() PostPersistentNotificationStore
|
||||
TrueUpReview() TrueUpReviewStore
|
||||
DesktopTokens() DesktopTokensStore
|
||||
ChannelBookmark() ChannelBookmarkStore
|
||||
}
|
||||
|
||||
type RetentionPolicyStore interface {
|
||||
@@ -1033,6 +1034,16 @@ type TrueUpReviewStore interface {
|
||||
Update(reviewStatus *model.TrueUpReviewStatus) (*model.TrueUpReviewStatus, error)
|
||||
}
|
||||
|
||||
type ChannelBookmarkStore interface {
|
||||
ErrorIfBookmarkFileInfoAlreadyAttached(fileId string) error
|
||||
Get(Id string, includeDeleted bool) (b *model.ChannelBookmarkWithFileInfo, err error)
|
||||
Save(bookmark *model.ChannelBookmark, increaseSortOrder bool) (b *model.ChannelBookmarkWithFileInfo, err error)
|
||||
Update(bookmark *model.ChannelBookmark) error
|
||||
UpdateSortOrder(bookmarkId, channelId string, newIndex int64) ([]*model.ChannelBookmarkWithFileInfo, error)
|
||||
Delete(bookmarkId string, deleteFile bool) error
|
||||
GetBookmarksForChannelSince(channelId string, since int64) ([]*model.ChannelBookmarkWithFileInfo, error)
|
||||
}
|
||||
|
||||
// ChannelSearchOpts contains options for searching channels.
|
||||
//
|
||||
// NotAssociatedToGroup will exclude channels that have associated, active GroupChannels records.
|
||||
|
||||
483
server/channels/store/storetest/channel_bookmark.go
Обычный файл
483
server/channels/store/storetest/channel_bookmark.go
Обычный файл
@@ -0,0 +1,483 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package storetest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
)
|
||||
|
||||
func find_bookmark(slice []*model.ChannelBookmarkWithFileInfo, id string) *model.ChannelBookmarkWithFileInfo {
|
||||
for _, element := range slice {
|
||||
if element.Id == id {
|
||||
return element
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestChannelBookmarkStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
||||
t.Run("SaveChannelBookmark", func(t *testing.T) { testSaveChannelBookmark(t, rctx, ss) })
|
||||
t.Run("UpdateChannelBookmark", func(t *testing.T) { testUpdateChannelBookmark(t, rctx, ss) })
|
||||
t.Run("UpdateSortOrderChannelBookmark", func(t *testing.T) { testUpdateSortOrderChannelBookmark(t, rctx, ss) })
|
||||
t.Run("DeleteChannelBookmark", func(t *testing.T) { testDeleteChannelBookmark(t, rctx, ss) })
|
||||
t.Run("GetChannelBookmark", func(t *testing.T) { testGetChannelBookmark(t, rctx, ss) })
|
||||
}
|
||||
|
||||
func testSaveChannelBookmark(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
channelId := model.NewId()
|
||||
userId := model.NewId()
|
||||
|
||||
bookmark1 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
OwnerId: userId,
|
||||
DisplayName: "Link bookmark test",
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: model.ChannelBookmarkLink,
|
||||
Emoji: ":smile:",
|
||||
}
|
||||
|
||||
file := &model.FileInfo{
|
||||
Id: model.NewId(),
|
||||
CreatorId: model.BookmarkFileOwner,
|
||||
Path: "somepath",
|
||||
ThumbnailPath: "thumbpath",
|
||||
PreviewPath: "prevPath",
|
||||
Name: "test file",
|
||||
Extension: "png",
|
||||
MimeType: "images/png",
|
||||
Size: 873182,
|
||||
Width: 3076,
|
||||
Height: 2200,
|
||||
HasPreviewImage: true,
|
||||
}
|
||||
|
||||
bookmark2 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
OwnerId: userId,
|
||||
DisplayName: "file bookmark test",
|
||||
FileId: file.Id,
|
||||
Type: model.ChannelBookmarkFile,
|
||||
Emoji: ":smile:",
|
||||
}
|
||||
|
||||
bookmark3 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
OwnerId: userId,
|
||||
DisplayName: "file already attached",
|
||||
FileId: file.Id,
|
||||
Type: model.ChannelBookmarkFile,
|
||||
Emoji: ":smile:",
|
||||
}
|
||||
|
||||
file2 := &model.FileInfo{
|
||||
Id: model.NewId(),
|
||||
CreatorId: userId,
|
||||
Path: "somepath",
|
||||
ThumbnailPath: "thumbpath",
|
||||
PreviewPath: "prevPath",
|
||||
Name: "test file",
|
||||
Extension: "png",
|
||||
MimeType: "images/png",
|
||||
Size: 873182,
|
||||
Width: 3076,
|
||||
Height: 2200,
|
||||
HasPreviewImage: true,
|
||||
}
|
||||
|
||||
bookmark4 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
OwnerId: userId,
|
||||
DisplayName: "file already attached to a post",
|
||||
FileId: file2.Id,
|
||||
Type: model.ChannelBookmarkFile,
|
||||
Emoji: ":smile:",
|
||||
}
|
||||
|
||||
_, err := ss.FileInfo().Save(rctx, file)
|
||||
require.NoError(t, err)
|
||||
defer ss.FileInfo().PermanentDelete(rctx, file.Id)
|
||||
|
||||
_, err = ss.FileInfo().Save(rctx, file2)
|
||||
require.NoError(t, err)
|
||||
defer ss.FileInfo().PermanentDelete(rctx, file2.Id)
|
||||
|
||||
err = ss.FileInfo().AttachToPost(rctx, file2.Id, model.NewId(), channelId, userId)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("save bookmarks", func(t *testing.T) {
|
||||
bookmarkResp, err := ss.ChannelBookmark().Save(bookmark1.Clone(), true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NotEmpty(t, bookmarkResp.Id)
|
||||
assert.Equal(t, bookmark1.ChannelId, bookmarkResp.ChannelId)
|
||||
assert.Nil(t, bookmarkResp.FileInfo)
|
||||
|
||||
bookmarkResp, err = ss.ChannelBookmark().Save(bookmark2.Clone(), true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NotEmpty(t, bookmarkResp.Id)
|
||||
assert.Equal(t, bookmark2.ChannelId, bookmarkResp.ChannelId)
|
||||
assert.NotNil(t, bookmarkResp.FileInfo)
|
||||
|
||||
bookmarks, err := ss.ChannelBookmark().GetBookmarksForChannelSince(channelId, 0)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, bookmarks, 2)
|
||||
|
||||
_, err = ss.ChannelBookmark().Save(bookmark3.Clone(), true)
|
||||
assert.Error(t, err) // Error as the file is attached to a bookmark
|
||||
|
||||
_, err = ss.ChannelBookmark().Save(bookmark4.Clone(), true)
|
||||
assert.Error(t, err) // Error as the file is attached to a post
|
||||
})
|
||||
}
|
||||
|
||||
func testUpdateChannelBookmark(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
channelId := model.NewId()
|
||||
userId := model.NewId()
|
||||
|
||||
bookmark1 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
OwnerId: userId,
|
||||
DisplayName: "Link bookmark test",
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: model.ChannelBookmarkLink,
|
||||
}
|
||||
|
||||
t.Run("update bookmark", func(t *testing.T) {
|
||||
bookmarkResp, err := ss.ChannelBookmark().Save(bookmark1.Clone(), true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
now := model.GetMillis()
|
||||
bookmark2 := bookmarkResp.ChannelBookmark.Clone()
|
||||
bookmark2.DisplayName = "Updated display name"
|
||||
bookmark2.Emoji = ":smile:"
|
||||
bookmark2.LinkUrl = "https://mattermost.com/about"
|
||||
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
|
||||
err = ss.ChannelBookmark().Update(bookmark2.Clone())
|
||||
assert.NoError(t, err)
|
||||
|
||||
bookmarks, err := ss.ChannelBookmark().GetBookmarksForChannelSince(channelId, now)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, bookmarks, 1)
|
||||
|
||||
b := find_bookmark(bookmarks, bookmark2.Id)
|
||||
assert.NotNil(t, b)
|
||||
assert.Equal(t, b.DisplayName, bookmark2.DisplayName)
|
||||
assert.Equal(t, b.Type, model.ChannelBookmarkLink)
|
||||
assert.NotEmpty(t, b.Emoji)
|
||||
assert.Equal(t, b.CreateAt, bookmark2.CreateAt)
|
||||
assert.Greater(t, b.UpdateAt, bookmark2.UpdateAt)
|
||||
|
||||
err = ss.ChannelBookmark().Update(bookmark1.Clone())
|
||||
assert.Error(t, err)
|
||||
|
||||
bookmark3 := bookmark2.Clone()
|
||||
bookmark3.Type = model.ChannelBookmarkFile
|
||||
err = ss.ChannelBookmark().Update(bookmark3)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func testUpdateSortOrderChannelBookmark(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
channelId := model.NewId()
|
||||
userId := model.NewId()
|
||||
|
||||
bookmark0 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
OwnerId: userId,
|
||||
DisplayName: "Bookmark 0",
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: model.ChannelBookmarkLink,
|
||||
Emoji: ":smile:",
|
||||
}
|
||||
|
||||
file := &model.FileInfo{
|
||||
Id: model.NewId(),
|
||||
CreatorId: model.BookmarkFileOwner,
|
||||
Path: "somepath",
|
||||
ThumbnailPath: "thumbpath",
|
||||
PreviewPath: "prevPath",
|
||||
Name: "test file",
|
||||
Extension: "png",
|
||||
MimeType: "images/png",
|
||||
Size: 873182,
|
||||
Width: 3076,
|
||||
Height: 2200,
|
||||
HasPreviewImage: true,
|
||||
}
|
||||
|
||||
bookmark1 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
OwnerId: userId,
|
||||
DisplayName: "Bookmark 1",
|
||||
FileId: file.Id,
|
||||
Type: model.ChannelBookmarkFile,
|
||||
Emoji: ":smile:",
|
||||
}
|
||||
|
||||
_, err := ss.FileInfo().Save(rctx, file)
|
||||
require.NoError(t, err)
|
||||
defer ss.FileInfo().PermanentDelete(rctx, file.Id)
|
||||
|
||||
bookmark2 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
OwnerId: userId,
|
||||
DisplayName: "Bookmark 2",
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: model.ChannelBookmarkLink,
|
||||
}
|
||||
|
||||
bookmark3 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
OwnerId: userId,
|
||||
DisplayName: "Bookmark 3",
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: model.ChannelBookmarkLink,
|
||||
}
|
||||
|
||||
bookmark4 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
OwnerId: userId,
|
||||
DisplayName: "Bookmark 4",
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: model.ChannelBookmarkLink,
|
||||
}
|
||||
|
||||
bookmarkResp, err := ss.ChannelBookmark().Save(bookmark0.Clone(), true)
|
||||
assert.NoError(t, err)
|
||||
bookmark0 = bookmarkResp.ChannelBookmark.Clone()
|
||||
|
||||
assert.NotEmpty(t, bookmarkResp.Id)
|
||||
assert.Equal(t, bookmark0.ChannelId, bookmarkResp.ChannelId)
|
||||
assert.Nil(t, bookmarkResp.FileInfo)
|
||||
|
||||
bookmarkResp, err = ss.ChannelBookmark().Save(bookmark1.Clone(), true)
|
||||
assert.NoError(t, err)
|
||||
bookmark1 = bookmarkResp.ChannelBookmark.Clone()
|
||||
|
||||
bookmarkResp, err = ss.ChannelBookmark().Save(bookmark2.Clone(), true)
|
||||
assert.NoError(t, err)
|
||||
bookmark2 = bookmarkResp.ChannelBookmark.Clone()
|
||||
|
||||
bookmarkResp, err = ss.ChannelBookmark().Save(bookmark3.Clone(), true)
|
||||
assert.NoError(t, err)
|
||||
bookmark3 = bookmarkResp.ChannelBookmark.Clone()
|
||||
|
||||
bookmarkResp, err = ss.ChannelBookmark().Save(bookmark4.Clone(), true)
|
||||
assert.NoError(t, err)
|
||||
bookmark4 = bookmarkResp.ChannelBookmark.Clone()
|
||||
|
||||
t.Run("change order of bookmarks first to last", func(t *testing.T) {
|
||||
bookmarks, sortError := ss.ChannelBookmark().UpdateSortOrder(bookmark0.Id, channelId, 4)
|
||||
assert.NoError(t, sortError)
|
||||
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark1.Id).SortOrder, int64(0))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark2.Id).SortOrder, int64(1))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark3.Id).SortOrder, int64(2))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark4.Id).SortOrder, int64(3))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark0.Id).SortOrder, int64(4))
|
||||
})
|
||||
|
||||
t.Run("change order of bookmarks last to first", func(t *testing.T) {
|
||||
bookmarks, sortError := ss.ChannelBookmark().UpdateSortOrder(bookmark0.Id, channelId, 0)
|
||||
assert.NoError(t, sortError)
|
||||
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark0.Id).SortOrder, int64(0))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark1.Id).SortOrder, int64(1))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark2.Id).SortOrder, int64(2))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark3.Id).SortOrder, int64(3))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark4.Id).SortOrder, int64(4))
|
||||
})
|
||||
|
||||
t.Run("change order of bookmarks first to third", func(t *testing.T) {
|
||||
bookmarks, sortError := ss.ChannelBookmark().UpdateSortOrder(bookmark0.Id, channelId, 2)
|
||||
assert.NoError(t, sortError)
|
||||
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark1.Id).SortOrder, int64(0))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark2.Id).SortOrder, int64(1))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark0.Id).SortOrder, int64(2))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark3.Id).SortOrder, int64(3))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark4.Id).SortOrder, int64(4))
|
||||
|
||||
// now reset order
|
||||
ss.ChannelBookmark().UpdateSortOrder(bookmark0.Id, channelId, 0)
|
||||
})
|
||||
|
||||
t.Run("change order of bookmarks second to third", func(t *testing.T) {
|
||||
bookmarks, sortError := ss.ChannelBookmark().UpdateSortOrder(bookmark1.Id, channelId, 2)
|
||||
assert.NoError(t, sortError)
|
||||
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark0.Id).SortOrder, int64(0))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark2.Id).SortOrder, int64(1))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark1.Id).SortOrder, int64(2))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark3.Id).SortOrder, int64(3))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark4.Id).SortOrder, int64(4))
|
||||
})
|
||||
|
||||
t.Run("change order of bookmarks third to second", func(t *testing.T) {
|
||||
bookmarks, sortError := ss.ChannelBookmark().UpdateSortOrder(bookmark1.Id, channelId, 1)
|
||||
assert.NoError(t, sortError)
|
||||
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark0.Id).SortOrder, int64(0))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark1.Id).SortOrder, int64(1))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark2.Id).SortOrder, int64(2))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark3.Id).SortOrder, int64(3))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark4.Id).SortOrder, int64(4))
|
||||
})
|
||||
|
||||
t.Run("change order of bookmarks last to previous last", func(t *testing.T) {
|
||||
bookmarks, sortError := ss.ChannelBookmark().UpdateSortOrder(bookmark4.Id, channelId, 3)
|
||||
assert.NoError(t, sortError)
|
||||
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark0.Id).SortOrder, int64(0))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark1.Id).SortOrder, int64(1))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark2.Id).SortOrder, int64(2))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark4.Id).SortOrder, int64(3))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark3.Id).SortOrder, int64(4))
|
||||
})
|
||||
|
||||
t.Run("change order of bookmarks last to second", func(t *testing.T) {
|
||||
bookmarks, sortError := ss.ChannelBookmark().UpdateSortOrder(bookmark3.Id, channelId, 1)
|
||||
assert.NoError(t, sortError)
|
||||
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark0.Id).SortOrder, int64(0))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark3.Id).SortOrder, int64(1))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark1.Id).SortOrder, int64(2))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark2.Id).SortOrder, int64(3))
|
||||
assert.Equal(t, find_bookmark(bookmarks, bookmark4.Id).SortOrder, int64(4))
|
||||
})
|
||||
|
||||
t.Run("change order of bookmarks error when new index is out of bounds", func(t *testing.T) {
|
||||
var iiErr *store.ErrInvalidInput
|
||||
_, err = ss.ChannelBookmark().UpdateSortOrder(bookmark3.Id, channelId, -1)
|
||||
assert.Error(t, err)
|
||||
assert.ErrorAs(t, err, &iiErr)
|
||||
_, err = ss.ChannelBookmark().UpdateSortOrder(bookmark3.Id, channelId, 5)
|
||||
assert.Error(t, err)
|
||||
assert.ErrorAs(t, err, &iiErr)
|
||||
})
|
||||
|
||||
t.Run("change order of bookmarks error when bookmark not found", func(t *testing.T) {
|
||||
_, err = ss.ChannelBookmark().UpdateSortOrder(model.NewId(), channelId, 0)
|
||||
assert.Error(t, err)
|
||||
var nfErr *store.ErrNotFound
|
||||
assert.ErrorAs(t, err, &nfErr)
|
||||
})
|
||||
}
|
||||
|
||||
func testDeleteChannelBookmark(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
channelId := model.NewId()
|
||||
userId := model.NewId()
|
||||
|
||||
bookmark1 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
OwnerId: userId,
|
||||
DisplayName: "Link bookmark test",
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: model.ChannelBookmarkLink,
|
||||
Emoji: ":smile:",
|
||||
}
|
||||
|
||||
file := &model.FileInfo{
|
||||
Id: model.NewId(),
|
||||
CreatorId: model.BookmarkFileOwner,
|
||||
Path: "somepath",
|
||||
ThumbnailPath: "thumbpath",
|
||||
PreviewPath: "prevPath",
|
||||
Name: "test file",
|
||||
Extension: "png",
|
||||
MimeType: "images/png",
|
||||
Size: 873182,
|
||||
Width: 3076,
|
||||
Height: 2200,
|
||||
HasPreviewImage: true,
|
||||
}
|
||||
|
||||
bookmark2 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
OwnerId: userId,
|
||||
DisplayName: "file bookmark test",
|
||||
FileId: file.Id,
|
||||
Type: model.ChannelBookmarkFile,
|
||||
Emoji: ":smile:",
|
||||
}
|
||||
|
||||
_, err := ss.FileInfo().Save(rctx, file)
|
||||
require.NoError(t, err)
|
||||
defer ss.FileInfo().PermanentDelete(rctx, file.Id)
|
||||
|
||||
t.Run("delete bookmark", func(t *testing.T) {
|
||||
now := model.GetMillis()
|
||||
bookmarkResp, err := ss.ChannelBookmark().Save(bookmark1.Clone(), true)
|
||||
assert.NoError(t, err)
|
||||
bookmark1 = bookmarkResp.ChannelBookmark.Clone()
|
||||
|
||||
assert.NotEmpty(t, bookmarkResp.Id)
|
||||
assert.Equal(t, bookmark1.ChannelId, bookmarkResp.ChannelId)
|
||||
assert.Nil(t, bookmarkResp.FileInfo)
|
||||
|
||||
bookmarkResp, err = ss.ChannelBookmark().Save(bookmark2.Clone(), true)
|
||||
assert.NoError(t, err)
|
||||
bookmark2 = bookmarkResp.ChannelBookmark.Clone()
|
||||
|
||||
err = ss.ChannelBookmark().Delete(bookmark2.Id, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
bookmarks, err := ss.ChannelBookmark().GetBookmarksForChannelSince(channelId, now)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, bookmarks, 2) // we have two as the deleted record also gets returned for sync'ing purposes
|
||||
|
||||
b := find_bookmark(bookmarks, bookmark2.Id)
|
||||
assert.NotNil(t, b)
|
||||
assert.Equal(t, bookmarks[0].Type, model.ChannelBookmarkLink)
|
||||
})
|
||||
}
|
||||
|
||||
func testGetChannelBookmark(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
channelId := model.NewId()
|
||||
userId := model.NewId()
|
||||
|
||||
bookmark1 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
OwnerId: userId,
|
||||
DisplayName: "Link bookmark test",
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: model.ChannelBookmarkLink,
|
||||
Emoji: ":smile:",
|
||||
}
|
||||
|
||||
t.Run("get bookmark", func(t *testing.T) {
|
||||
bookmarkResp, err := ss.ChannelBookmark().Save(bookmark1.Clone(), true)
|
||||
assert.NoError(t, err)
|
||||
bookmark1 = bookmarkResp.ChannelBookmark.Clone()
|
||||
|
||||
bookmarkResp, err = ss.ChannelBookmark().Get(bookmark1.Id, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NotEmpty(t, bookmarkResp.Id)
|
||||
assert.Equal(t, bookmark1.ChannelId, bookmarkResp.ChannelId)
|
||||
assert.Nil(t, bookmarkResp.FileInfo)
|
||||
|
||||
err = ss.ChannelBookmark().Delete(bookmark1.Id, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
bookmarkResp, err = ss.ChannelBookmark().Get(bookmark1.Id, false)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, bookmarkResp)
|
||||
|
||||
bookmarkResp, err = ss.ChannelBookmark().Get(bookmark1.Id, true)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, bookmarkResp)
|
||||
})
|
||||
}
|
||||
@@ -615,16 +615,26 @@ func testFileInfoPermanentDeleteBatch(t *testing.T, rctx request.CTX, ss store.S
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
bookmarkFile, err := ss.FileInfo().Save(rctx, &model.FileInfo{ // should not be deleted
|
||||
PostId: postId,
|
||||
ChannelId: channelId,
|
||||
CreatorId: model.BookmarkFileOwner,
|
||||
Path: "file.txt",
|
||||
CreateAt: 1000,
|
||||
})
|
||||
defer ss.FileInfo().PermanentDelete(rctx, bookmarkFile.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
postFiles, err := ss.FileInfo().GetForPost(postId, true, false, false)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, postFiles, 3)
|
||||
assert.Len(t, postFiles, 4)
|
||||
|
||||
_, err = ss.FileInfo().PermanentDeleteBatch(rctx, 1500, 1000)
|
||||
require.NoError(t, err)
|
||||
|
||||
postFiles, err = ss.FileInfo().GetForPost(postId, true, false, false)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, postFiles, 1)
|
||||
assert.Len(t, postFiles, 2)
|
||||
}
|
||||
|
||||
func testFileInfoPermanentDeleteByUser(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
|
||||
176
server/channels/store/storetest/mocks/ChannelBookmarkStore.go
Обычный файл
176
server/channels/store/storetest/mocks/ChannelBookmarkStore.go
Обычный файл
@@ -0,0 +1,176 @@
|
||||
// Code generated by mockery v2.23.2. DO NOT EDIT.
|
||||
|
||||
// Regenerate this file using `make store-mocks`.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
model "github.com/mattermost/mattermost/server/public/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// ChannelBookmarkStore is an autogenerated mock type for the ChannelBookmarkStore type
|
||||
type ChannelBookmarkStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: bookmarkId, deleteFile
|
||||
func (_m *ChannelBookmarkStore) Delete(bookmarkId string, deleteFile bool) error {
|
||||
ret := _m.Called(bookmarkId, deleteFile)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, bool) error); ok {
|
||||
r0 = rf(bookmarkId, deleteFile)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ErrorIfBookmarkFileInfoAlreadyAttached provides a mock function with given fields: fileId
|
||||
func (_m *ChannelBookmarkStore) ErrorIfBookmarkFileInfoAlreadyAttached(fileId string) error {
|
||||
ret := _m.Called(fileId)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(fileId)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: Id, includeDeleted
|
||||
func (_m *ChannelBookmarkStore) Get(Id string, includeDeleted bool) (*model.ChannelBookmarkWithFileInfo, error) {
|
||||
ret := _m.Called(Id, includeDeleted)
|
||||
|
||||
var r0 *model.ChannelBookmarkWithFileInfo
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, bool) (*model.ChannelBookmarkWithFileInfo, error)); ok {
|
||||
return rf(Id, includeDeleted)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, bool) *model.ChannelBookmarkWithFileInfo); ok {
|
||||
r0 = rf(Id, includeDeleted)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.ChannelBookmarkWithFileInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, bool) error); ok {
|
||||
r1 = rf(Id, includeDeleted)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetBookmarksForChannelSince provides a mock function with given fields: channelId, since
|
||||
func (_m *ChannelBookmarkStore) GetBookmarksForChannelSince(channelId string, since int64) ([]*model.ChannelBookmarkWithFileInfo, error) {
|
||||
ret := _m.Called(channelId, since)
|
||||
|
||||
var r0 []*model.ChannelBookmarkWithFileInfo
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, int64) ([]*model.ChannelBookmarkWithFileInfo, error)); ok {
|
||||
return rf(channelId, since)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, int64) []*model.ChannelBookmarkWithFileInfo); ok {
|
||||
r0 = rf(channelId, since)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.ChannelBookmarkWithFileInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, int64) error); ok {
|
||||
r1 = rf(channelId, since)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Save provides a mock function with given fields: bookmark, increaseSortOrder
|
||||
func (_m *ChannelBookmarkStore) Save(bookmark *model.ChannelBookmark, increaseSortOrder bool) (*model.ChannelBookmarkWithFileInfo, error) {
|
||||
ret := _m.Called(bookmark, increaseSortOrder)
|
||||
|
||||
var r0 *model.ChannelBookmarkWithFileInfo
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*model.ChannelBookmark, bool) (*model.ChannelBookmarkWithFileInfo, error)); ok {
|
||||
return rf(bookmark, increaseSortOrder)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*model.ChannelBookmark, bool) *model.ChannelBookmarkWithFileInfo); ok {
|
||||
r0 = rf(bookmark, increaseSortOrder)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.ChannelBookmarkWithFileInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*model.ChannelBookmark, bool) error); ok {
|
||||
r1 = rf(bookmark, increaseSortOrder)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: bookmark
|
||||
func (_m *ChannelBookmarkStore) Update(bookmark *model.ChannelBookmark) error {
|
||||
ret := _m.Called(bookmark)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.ChannelBookmark) error); ok {
|
||||
r0 = rf(bookmark)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateSortOrder provides a mock function with given fields: bookmarkId, channelId, newIndex
|
||||
func (_m *ChannelBookmarkStore) UpdateSortOrder(bookmarkId string, channelId string, newIndex int64) ([]*model.ChannelBookmarkWithFileInfo, error) {
|
||||
ret := _m.Called(bookmarkId, channelId, newIndex)
|
||||
|
||||
var r0 []*model.ChannelBookmarkWithFileInfo
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, int64) ([]*model.ChannelBookmarkWithFileInfo, error)); ok {
|
||||
return rf(bookmarkId, channelId, newIndex)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, string, int64) []*model.ChannelBookmarkWithFileInfo); ok {
|
||||
r0 = rf(bookmarkId, channelId, newIndex)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.ChannelBookmarkWithFileInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, string, int64) error); ok {
|
||||
r1 = rf(bookmarkId, channelId, newIndex)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
type mockConstructorTestingTNewChannelBookmarkStore interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// NewChannelBookmarkStore creates a new instance of ChannelBookmarkStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func NewChannelBookmarkStore(t mockConstructorTestingTNewChannelBookmarkStore) *ChannelBookmarkStore {
|
||||
mock := &ChannelBookmarkStore{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -72,6 +72,22 @@ func (_m *Store) Channel() store.ChannelStore {
|
||||
return r0
|
||||
}
|
||||
|
||||
// ChannelBookmark provides a mock function with given fields:
|
||||
func (_m *Store) ChannelBookmark() store.ChannelBookmarkStore {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 store.ChannelBookmarkStore
|
||||
if rf, ok := ret.Get(0).(func() store.ChannelBookmarkStore); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(store.ChannelBookmarkStore)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ChannelMemberHistory provides a mock function with given fields:
|
||||
func (_m *Store) ChannelMemberHistory() store.ChannelMemberHistoryStore {
|
||||
ret := _m.Called()
|
||||
|
||||
@@ -65,6 +65,7 @@ type Store struct {
|
||||
PostPersistentNotificationStore mocks.PostPersistentNotificationStore
|
||||
TrueUpReviewStore mocks.TrueUpReviewStore
|
||||
DesktopTokensStore mocks.DesktopTokensStore
|
||||
ChannelBookmarkStore mocks.ChannelBookmarkStore
|
||||
}
|
||||
|
||||
func (s *Store) SetContext(context context.Context) { s.context = context }
|
||||
@@ -110,13 +111,14 @@ func (s *Store) Draft() store.DraftStore { return &s.D
|
||||
func (s *Store) ChannelMemberHistory() store.ChannelMemberHistoryStore {
|
||||
return &s.ChannelMemberHistoryStore
|
||||
}
|
||||
func (s *Store) TrueUpReview() store.TrueUpReviewStore { return &s.TrueUpReviewStore }
|
||||
func (s *Store) DesktopTokens() store.DesktopTokensStore { return &s.DesktopTokensStore }
|
||||
func (s *Store) NotifyAdmin() store.NotifyAdminStore { return &s.NotifyAdminStore }
|
||||
func (s *Store) Group() store.GroupStore { return &s.GroupStore }
|
||||
func (s *Store) LinkMetadata() store.LinkMetadataStore { return &s.LinkMetadataStore }
|
||||
func (s *Store) SharedChannel() store.SharedChannelStore { return &s.SharedChannelStore }
|
||||
func (s *Store) PostPriority() store.PostPriorityStore { return &s.PostPriorityStore }
|
||||
func (s *Store) ChannelBookmark() store.ChannelBookmarkStore { return &s.ChannelBookmarkStore }
|
||||
func (s *Store) TrueUpReview() store.TrueUpReviewStore { return &s.TrueUpReviewStore }
|
||||
func (s *Store) DesktopTokens() store.DesktopTokensStore { return &s.DesktopTokensStore }
|
||||
func (s *Store) NotifyAdmin() store.NotifyAdminStore { return &s.NotifyAdminStore }
|
||||
func (s *Store) Group() store.GroupStore { return &s.GroupStore }
|
||||
func (s *Store) LinkMetadata() store.LinkMetadataStore { return &s.LinkMetadataStore }
|
||||
func (s *Store) SharedChannel() store.SharedChannelStore { return &s.SharedChannelStore }
|
||||
func (s *Store) PostPriority() store.PostPriorityStore { return &s.PostPriorityStore }
|
||||
func (s *Store) PostAcknowledgement() store.PostAcknowledgementStore {
|
||||
return &s.PostAcknowledgementStore
|
||||
}
|
||||
@@ -187,5 +189,6 @@ func (s *Store) AssertExpectations(t mock.TestingT) bool {
|
||||
&s.PostAcknowledgementStore,
|
||||
&s.PostPersistentNotificationStore,
|
||||
&s.DesktopTokensStore,
|
||||
&s.ChannelBookmarkStore,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ type TimerLayer struct {
|
||||
AuditStore store.AuditStore
|
||||
BotStore store.BotStore
|
||||
ChannelStore store.ChannelStore
|
||||
ChannelBookmarkStore store.ChannelBookmarkStore
|
||||
ChannelMemberHistoryStore store.ChannelMemberHistoryStore
|
||||
ClusterDiscoveryStore store.ClusterDiscoveryStore
|
||||
CommandStore store.CommandStore
|
||||
@@ -78,6 +79,10 @@ func (s *TimerLayer) Channel() store.ChannelStore {
|
||||
return s.ChannelStore
|
||||
}
|
||||
|
||||
func (s *TimerLayer) ChannelBookmark() store.ChannelBookmarkStore {
|
||||
return s.ChannelBookmarkStore
|
||||
}
|
||||
|
||||
func (s *TimerLayer) ChannelMemberHistory() store.ChannelMemberHistoryStore {
|
||||
return s.ChannelMemberHistoryStore
|
||||
}
|
||||
@@ -261,6 +266,11 @@ type TimerLayerChannelStore struct {
|
||||
Root *TimerLayer
|
||||
}
|
||||
|
||||
type TimerLayerChannelBookmarkStore struct {
|
||||
store.ChannelBookmarkStore
|
||||
Root *TimerLayer
|
||||
}
|
||||
|
||||
type TimerLayerChannelMemberHistoryStore struct {
|
||||
store.ChannelMemberHistoryStore
|
||||
Root *TimerLayer
|
||||
@@ -2478,6 +2488,118 @@ func (s *TimerLayerChannelStore) UserBelongsToChannels(userID string, channelIds
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerChannelBookmarkStore) Delete(bookmarkId string, deleteFile bool) error {
|
||||
start := time.Now()
|
||||
|
||||
err := s.ChannelBookmarkStore.Delete(bookmarkId, deleteFile)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("ChannelBookmarkStore.Delete", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerChannelBookmarkStore) ErrorIfBookmarkFileInfoAlreadyAttached(fileId string) error {
|
||||
start := time.Now()
|
||||
|
||||
err := s.ChannelBookmarkStore.ErrorIfBookmarkFileInfoAlreadyAttached(fileId)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("ChannelBookmarkStore.ErrorIfBookmarkFileInfoAlreadyAttached", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerChannelBookmarkStore) Get(Id string, includeDeleted bool) (*model.ChannelBookmarkWithFileInfo, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.ChannelBookmarkStore.Get(Id, includeDeleted)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("ChannelBookmarkStore.Get", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerChannelBookmarkStore) GetBookmarksForChannelSince(channelId string, since int64) ([]*model.ChannelBookmarkWithFileInfo, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.ChannelBookmarkStore.GetBookmarksForChannelSince(channelId, since)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("ChannelBookmarkStore.GetBookmarksForChannelSince", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerChannelBookmarkStore) Save(bookmark *model.ChannelBookmark, increaseSortOrder bool) (*model.ChannelBookmarkWithFileInfo, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.ChannelBookmarkStore.Save(bookmark, increaseSortOrder)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("ChannelBookmarkStore.Save", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerChannelBookmarkStore) Update(bookmark *model.ChannelBookmark) error {
|
||||
start := time.Now()
|
||||
|
||||
err := s.ChannelBookmarkStore.Update(bookmark)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("ChannelBookmarkStore.Update", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerChannelBookmarkStore) UpdateSortOrder(bookmarkId string, channelId string, newIndex int64) ([]*model.ChannelBookmarkWithFileInfo, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.ChannelBookmarkStore.UpdateSortOrder(bookmarkId, channelId, newIndex)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("ChannelBookmarkStore.UpdateSortOrder", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerChannelMemberHistoryStore) DeleteOrphanedRows(limit int) (int64, error) {
|
||||
start := time.Now()
|
||||
|
||||
@@ -11965,6 +12087,7 @@ func New(childStore store.Store, metrics einterfaces.MetricsInterface) *TimerLay
|
||||
newStore.AuditStore = &TimerLayerAuditStore{AuditStore: childStore.Audit(), Root: &newStore}
|
||||
newStore.BotStore = &TimerLayerBotStore{BotStore: childStore.Bot(), Root: &newStore}
|
||||
newStore.ChannelStore = &TimerLayerChannelStore{ChannelStore: childStore.Channel(), Root: &newStore}
|
||||
newStore.ChannelBookmarkStore = &TimerLayerChannelBookmarkStore{ChannelBookmarkStore: childStore.ChannelBookmark(), Root: &newStore}
|
||||
newStore.ChannelMemberHistoryStore = &TimerLayerChannelMemberHistoryStore{ChannelMemberHistoryStore: childStore.ChannelMemberHistory(), Root: &newStore}
|
||||
newStore.ClusterDiscoveryStore = &TimerLayerClusterDiscoveryStore{ClusterDiscoveryStore: childStore.ClusterDiscovery(), Root: &newStore}
|
||||
newStore.CommandStore = &TimerLayerCommandStore{CommandStore: childStore.Command(), Root: &newStore}
|
||||
|
||||
Ссылка в новой задаче
Block a user