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>
Этот коммит содержится в:
@@ -49,6 +49,8 @@ type Routes struct {
|
||||
ChannelMembersForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/teams/{team_id:[A-Za-z0-9]+}/channels/members'
|
||||
ChannelModerations *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/moderations'
|
||||
ChannelCategories *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/teams/{team_id:[A-Za-z0-9]+}/channels/categories'
|
||||
ChannelBookmarks *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/bookmarks'
|
||||
ChannelBookmark *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/bookmarks/{bookmark_id:[A-Za-z0-9]+}'
|
||||
|
||||
Posts *mux.Router // 'api/v4/posts'
|
||||
Post *mux.Router // 'api/v4/posts/{post_id:[A-Za-z0-9]+}'
|
||||
@@ -193,6 +195,8 @@ func Init(srv *app.Server) (*API, error) {
|
||||
api.BaseRoutes.ChannelMembersForUser = api.BaseRoutes.User.PathPrefix("/teams/{team_id:[A-Za-z0-9]+}/channels/members").Subrouter()
|
||||
api.BaseRoutes.ChannelModerations = api.BaseRoutes.Channel.PathPrefix("/moderations").Subrouter()
|
||||
api.BaseRoutes.ChannelCategories = api.BaseRoutes.User.PathPrefix("/teams/{team_id:[A-Za-z0-9]+}/channels/categories").Subrouter()
|
||||
api.BaseRoutes.ChannelBookmarks = api.BaseRoutes.Channel.PathPrefix("/bookmarks").Subrouter()
|
||||
api.BaseRoutes.ChannelBookmark = api.BaseRoutes.ChannelBookmarks.PathPrefix("/{bookmark_id:[A-Za-z0-9]+}").Subrouter()
|
||||
|
||||
api.BaseRoutes.Posts = api.BaseRoutes.APIRoot.PathPrefix("/posts").Subrouter()
|
||||
api.BaseRoutes.Post = api.BaseRoutes.Posts.PathPrefix("/{post_id:[A-Za-z0-9]+}").Subrouter()
|
||||
@@ -323,6 +327,7 @@ func Init(srv *app.Server) (*API, error) {
|
||||
api.InitHostedCustomer()
|
||||
api.InitDrafts()
|
||||
api.InitIPFiltering()
|
||||
api.InitChannelBookmarks()
|
||||
api.InitReports()
|
||||
api.InitLimits()
|
||||
api.InitOutgoingOAuthConnection()
|
||||
|
||||
@@ -623,6 +623,41 @@ func (th *TestHelper) CreateUserWithAuth(authService string) *model.User {
|
||||
return user
|
||||
}
|
||||
|
||||
// CreateGuestAndClient creates a guest user, adds them to the basic
|
||||
// team, basic channel and basic private channel, and generates an API
|
||||
// client ready to use
|
||||
func (th *TestHelper) CreateGuestAndClient() (*model.User, *model.Client4) {
|
||||
id := model.NewId()
|
||||
|
||||
// create a guest user and add it to the basic team and public/private channels
|
||||
guest, cgErr := th.App.CreateGuest(th.Context, &model.User{
|
||||
Email: "test_guest" + id + "@sample.com",
|
||||
Username: "guest_" + id,
|
||||
Nickname: "guest_" + id,
|
||||
Password: "Password1",
|
||||
EmailVerified: true,
|
||||
})
|
||||
if cgErr != nil {
|
||||
panic(cgErr)
|
||||
}
|
||||
|
||||
_, _, tErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, guest.Id, th.SystemAdminUser.Id)
|
||||
if tErr != nil {
|
||||
panic(tErr)
|
||||
}
|
||||
th.AddUserToChannel(guest, th.BasicChannel)
|
||||
th.AddUserToChannel(guest, th.BasicPrivateChannel)
|
||||
|
||||
// create a client and login the guest
|
||||
guestClient := th.CreateClient()
|
||||
_, _, lErr := guestClient.Login(context.Background(), guest.Username, "Password1")
|
||||
if lErr != nil {
|
||||
panic(lErr)
|
||||
}
|
||||
|
||||
return guest, guestClient
|
||||
}
|
||||
|
||||
func (th *TestHelper) SetupLdapConfig() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.EnableMultifactorAuthentication = true
|
||||
@@ -809,6 +844,23 @@ func (th *TestHelper) CreateDmChannel(user *model.User) *model.Channel {
|
||||
return channel
|
||||
}
|
||||
|
||||
func (th *TestHelper) PatchChannelModerationsForMembers(channelId, name string, val bool) {
|
||||
patch := []*model.ChannelModerationPatch{{
|
||||
Name: &name,
|
||||
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewBool(val)},
|
||||
}}
|
||||
|
||||
channel, err := th.App.GetChannel(th.Context, channelId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = th.App.PatchChannelModerationsForChannel(th.Context, channel, patch)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (th *TestHelper) LoginBasic() {
|
||||
th.LoginBasicWithClient(th.Client)
|
||||
}
|
||||
|
||||
408
server/channels/api4/channel_bookmark.go
Обычный файл
408
server/channels/api4/channel_bookmark.go
Обычный файл
@@ -0,0 +1,408 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/audit"
|
||||
)
|
||||
|
||||
func (api *API) InitChannelBookmarks() {
|
||||
if api.srv.Config().FeatureFlags.ChannelBookmarks {
|
||||
api.BaseRoutes.ChannelBookmarks.Handle("", api.APISessionRequired(createChannelBookmark)).Methods("POST")
|
||||
api.BaseRoutes.ChannelBookmark.Handle("", api.APISessionRequired(updateChannelBookmark)).Methods("PATCH")
|
||||
api.BaseRoutes.ChannelBookmark.Handle("/sort_order", api.APISessionRequired(updateChannelBookmarkSortOrder)).Methods("POST")
|
||||
api.BaseRoutes.ChannelBookmark.Handle("", api.APISessionRequired(deleteChannelBookmark)).Methods("DELETE")
|
||||
api.BaseRoutes.ChannelBookmarks.Handle("", api.APISessionRequired(listChannelBookmarksForChannel)).Methods("GET")
|
||||
}
|
||||
}
|
||||
|
||||
func createChannelBookmark(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.App.Channels().License() == nil {
|
||||
c.Err = model.NewAppError("createChannelBookmark", "api.channel.bookmark.channel_bookmark.license.error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
connectionID := r.Header.Get(model.ConnectionId)
|
||||
|
||||
c.RequireChannelId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
channel, appErr := c.App.GetChannel(c.AppContext, c.Params.ChannelId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
var channelBookmark *model.ChannelBookmark
|
||||
err := json.NewDecoder(r.Body).Decode(&channelBookmark)
|
||||
if err != nil || channelBookmark == nil {
|
||||
c.SetInvalidParamWithErr("channelBookmark", err)
|
||||
return
|
||||
}
|
||||
channelBookmark.ChannelId = c.Params.ChannelId
|
||||
|
||||
auditRec := c.MakeAuditRecord("createChannelBookmark", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
audit.AddEventParameterAuditable(auditRec, "channelBookmark", channelBookmark)
|
||||
|
||||
switch channel.Type {
|
||||
case model.ChannelTypeOpen:
|
||||
if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionAddBookmarkPublicChannel) {
|
||||
c.SetPermissionError(model.PermissionAddBookmarkPublicChannel)
|
||||
return
|
||||
}
|
||||
|
||||
case model.ChannelTypePrivate:
|
||||
if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionAddBookmarkPrivateChannel) {
|
||||
c.SetPermissionError(model.PermissionAddBookmarkPrivateChannel)
|
||||
return
|
||||
}
|
||||
|
||||
case model.ChannelTypeGroup, model.ChannelTypeDirect:
|
||||
// Any member of DM/GMs but guests can manage channel bookmarks
|
||||
if _, errGet := c.App.GetChannelMember(c.AppContext, channel.Id, c.AppContext.Session().UserId); errGet != nil {
|
||||
c.Err = model.NewAppError("createChannelBookmark", "api.channel.bookmark.create_channel_bookmark.direct_or_group_channels.forbidden.app_error", nil, errGet.Message, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
user, gAppErr := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if gAppErr != nil {
|
||||
c.Err = gAppErr
|
||||
return
|
||||
}
|
||||
|
||||
if user.IsGuest() {
|
||||
c.Err = model.NewAppError("createChannelBookmark", "api.channel.bookmark.create_channel_bookmark.direct_or_group_channels_by_guests.forbidden.app_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
c.Err = model.NewAppError("createChannelBookmark", "api.channel.bookmark.create_channel_bookmark.forbidden.app_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
newChannelBookmark, appErr := c.App.CreateChannelBookmark(c.AppContext, channelBookmark, connectionID)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
auditRec.AddEventResultState(newChannelBookmark)
|
||||
auditRec.AddEventObjectType("channelBookmarkWithFileInfo")
|
||||
c.LogAudit("display_name=" + newChannelBookmark.DisplayName)
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if err := json.NewEncoder(w).Encode(newChannelBookmark); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func updateChannelBookmark(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.App.Channels().License() == nil {
|
||||
c.Err = model.NewAppError("updateChannelBookmark", "api.channel.bookmark.channel_bookmark.license.error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
connectionID := r.Header.Get(model.ConnectionId)
|
||||
|
||||
c.RequireChannelId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var patch *model.ChannelBookmarkPatch
|
||||
if err := json.NewDecoder(r.Body).Decode(&patch); err != nil || patch == nil {
|
||||
c.SetInvalidParamWithErr("channelBookmarkPatch", err)
|
||||
return
|
||||
}
|
||||
|
||||
originalChannelBookmark, appErr := c.App.GetBookmark(c.Params.ChannelBookmarkId, false)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
patchedBookmark := originalChannelBookmark.Clone()
|
||||
auditRec := c.MakeAuditRecord("updateChannelBookmark", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
audit.AddEventParameterAuditable(auditRec, "channelBookmark", patch)
|
||||
|
||||
// The channel bookmark should belong to the same channel specified in the URL
|
||||
if patchedBookmark.ChannelId != c.Params.ChannelId {
|
||||
c.SetInvalidParam("channel_id")
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.AddEventPriorState(originalChannelBookmark)
|
||||
|
||||
channel, appErr := c.App.GetChannel(c.AppContext, c.Params.ChannelId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
switch channel.Type {
|
||||
case model.ChannelTypeOpen:
|
||||
if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionEditBookmarkPublicChannel) {
|
||||
c.SetPermissionError(model.PermissionEditBookmarkPublicChannel)
|
||||
return
|
||||
}
|
||||
|
||||
case model.ChannelTypePrivate:
|
||||
if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionEditBookmarkPrivateChannel) {
|
||||
c.SetPermissionError(model.PermissionEditBookmarkPrivateChannel)
|
||||
return
|
||||
}
|
||||
|
||||
case model.ChannelTypeGroup, model.ChannelTypeDirect:
|
||||
// Any member of DM/GMs but guests can manage channel bookmarks
|
||||
if _, errGet := c.App.GetChannelMember(c.AppContext, channel.Id, c.AppContext.Session().UserId); errGet != nil {
|
||||
c.Err = model.NewAppError("updateChannelBookmark", "api.channel.bookmark.update_channel_bookmark.direct_or_group_channels.forbidden.app_error", nil, errGet.Message, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
user, gAppErr := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if gAppErr != nil {
|
||||
c.Err = gAppErr
|
||||
return
|
||||
}
|
||||
|
||||
if user.IsGuest() {
|
||||
c.Err = model.NewAppError("updateChannelBookmark", "api.channel.bookmark.update_channel_bookmark.direct_or_group_channels_by_guests.forbidden.app_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
c.Err = model.NewAppError("updateChannelBookmark", "api.channel.bookmark.update_channel_bookmark.forbidden.app_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
patchedBookmark.Patch(patch)
|
||||
updateChannelBookmarkResponse, appErr := c.App.UpdateChannelBookmark(c.AppContext, patchedBookmark, connectionID)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
auditRec.AddEventResultState(updateChannelBookmarkResponse)
|
||||
auditRec.AddEventObjectType("updateChannelBookmarkResponse")
|
||||
c.LogAudit("")
|
||||
|
||||
if err := json.NewEncoder(w).Encode(updateChannelBookmarkResponse); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func updateChannelBookmarkSortOrder(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.App.Channels().License() == nil {
|
||||
c.Err = model.NewAppError("updateChannelBookmarkSortOrder", "api.channel.bookmark.channel_bookmark.license.error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
connectionID := r.Header.Get(model.ConnectionId)
|
||||
|
||||
c.RequireChannelId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var newSortOrder int64
|
||||
if err := json.NewDecoder(r.Body).Decode(&newSortOrder); err != nil {
|
||||
c.SetInvalidParamWithErr("channelBookmarkSortOrder", err)
|
||||
return
|
||||
}
|
||||
|
||||
if newSortOrder < 0 {
|
||||
c.SetInvalidParam("channelBookmarkSortOrder")
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("updateChannelBookmarkSortOrder", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
audit.AddEventParameter(auditRec, "id", c.Params.ChannelBookmarkId)
|
||||
|
||||
channel, appErr := c.App.GetChannel(c.AppContext, c.Params.ChannelId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
switch channel.Type {
|
||||
case model.ChannelTypeOpen:
|
||||
if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionOrderBookmarkPublicChannel) {
|
||||
c.SetPermissionError(model.PermissionOrderBookmarkPublicChannel)
|
||||
return
|
||||
}
|
||||
|
||||
case model.ChannelTypePrivate:
|
||||
if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionOrderBookmarkPrivateChannel) {
|
||||
c.SetPermissionError(model.PermissionOrderBookmarkPrivateChannel)
|
||||
return
|
||||
}
|
||||
|
||||
case model.ChannelTypeGroup, model.ChannelTypeDirect:
|
||||
// Any member of DM/GMs but guests can manage channel bookmarks
|
||||
if _, errGet := c.App.GetChannelMember(c.AppContext, channel.Id, c.AppContext.Session().UserId); errGet != nil {
|
||||
c.Err = model.NewAppError("updateChannelBookmarkSortOrder", "api.channel.bookmark.update_channel_bookmark_sort_order.direct_or_group_channels.forbidden.app_error", nil, errGet.Message, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
user, gAppErr := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if gAppErr != nil {
|
||||
c.Err = gAppErr
|
||||
return
|
||||
}
|
||||
|
||||
if user.IsGuest() {
|
||||
c.Err = model.NewAppError("updateChannelBookmarkSortOrder", "api.channel.bookmark.update_channel_bookmark_sort_order.direct_or_group_channels_by_guests.forbidden.app_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
c.Err = model.NewAppError("updateChannelBookmarkSortOrder", "api.channel.bookmark.update_channel_bookmark_sort_order.forbidden.app_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
bookmarks, appErr := c.App.UpdateChannelBookmarkSortOrder(c.Params.ChannelBookmarkId, c.Params.ChannelId, newSortOrder, connectionID)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
for _, b := range bookmarks {
|
||||
if b.Id == c.Params.ChannelBookmarkId {
|
||||
auditRec.AddEventResultState(b)
|
||||
auditRec.AddEventObjectType("channelBookmarkWithFileInfo")
|
||||
break
|
||||
}
|
||||
}
|
||||
auditRec.Success()
|
||||
c.LogAudit("")
|
||||
|
||||
if err := json.NewEncoder(w).Encode(bookmarks); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func deleteChannelBookmark(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.App.Channels().License() == nil {
|
||||
c.Err = model.NewAppError("deleteChannelBookmark", "api.channel.bookmark.channel_bookmark.license.error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
connectionID := r.Header.Get(model.ConnectionId)
|
||||
|
||||
c.RequireChannelId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("deleteChannelBookmark", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
audit.AddEventParameter(auditRec, "id", c.Params.ChannelBookmarkId)
|
||||
|
||||
channel, appErr := c.App.GetChannel(c.AppContext, c.Params.ChannelId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
switch channel.Type {
|
||||
case model.ChannelTypeOpen:
|
||||
if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionDeleteBookmarkPublicChannel) {
|
||||
c.SetPermissionError(model.PermissionDeleteBookmarkPublicChannel)
|
||||
return
|
||||
}
|
||||
|
||||
case model.ChannelTypePrivate:
|
||||
if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionDeleteBookmarkPrivateChannel) {
|
||||
c.SetPermissionError(model.PermissionDeleteBookmarkPrivateChannel)
|
||||
return
|
||||
}
|
||||
|
||||
case model.ChannelTypeGroup, model.ChannelTypeDirect:
|
||||
// Any member of DM/GMs but guests can manage channel bookmarks
|
||||
if _, errGet := c.App.GetChannelMember(c.AppContext, channel.Id, c.AppContext.Session().UserId); errGet != nil {
|
||||
c.Err = model.NewAppError("deleteChannelBookmark", "api.channel.bookmark.delete_channel_bookmark.direct_or_group_channels.forbidden.app_error", nil, errGet.Message, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
user, gAppErr := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if gAppErr != nil {
|
||||
c.Err = gAppErr
|
||||
return
|
||||
}
|
||||
|
||||
if user.IsGuest() {
|
||||
c.Err = model.NewAppError("deleteChannelBookmark", "api.channel.bookmark.delete_channel_bookmark.direct_or_group_channels_by_guests.forbidden.app_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
c.Err = model.NewAppError("deleteChannelBookmark", "api.channel.bookmark.delete_channel_bookmark.forbidden.app_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
oldBookmark, obErr := c.App.GetBookmark(c.Params.ChannelBookmarkId, false)
|
||||
if obErr != nil {
|
||||
c.Err = obErr
|
||||
return
|
||||
}
|
||||
|
||||
// The channel bookmark should belong to the same channel specified in the URL
|
||||
if oldBookmark.ChannelId != c.Params.ChannelId {
|
||||
c.SetInvalidParam("channel_id")
|
||||
return
|
||||
}
|
||||
auditRec.AddEventPriorState(oldBookmark)
|
||||
|
||||
bookmark, appErr := c.App.DeleteChannelBookmark(c.Params.ChannelBookmarkId, connectionID)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
auditRec.AddEventResultState(bookmark)
|
||||
c.LogAudit("bookmark=" + bookmark.DisplayName)
|
||||
|
||||
if err := json.NewEncoder(w).Encode(bookmark); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func listChannelBookmarksForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.App.Channels().License() == nil {
|
||||
c.Err = model.NewAppError("listChannelBookmarksForChannel", "api.channel.bookmark.channel_bookmark.license.error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
c.RequireChannelId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannelContent) {
|
||||
c.SetPermissionError(model.PermissionReadChannelContent)
|
||||
return
|
||||
}
|
||||
|
||||
bookmarks, appErr := c.App.GetChannelBookmarks(c.Params.ChannelId, c.Params.BookmarksSince)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(bookmarks); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
1551
server/channels/api4/channel_bookmark_test.go
Обычный файл
1551
server/channels/api4/channel_bookmark_test.go
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -4230,9 +4230,9 @@ func TestGetChannelModerations(t *testing.T) {
|
||||
t.Run("Returns default moderations with default roles", func(t *testing.T) {
|
||||
moderations, _, err := th.SystemAdminClient.GetChannelModerations(context.Background(), channel.Id, "")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(moderations), 4)
|
||||
require.Equal(t, len(moderations), 5)
|
||||
for _, moderation := range moderations {
|
||||
if moderation.Name == "manage_members" {
|
||||
if moderation.Name == "manage_members" || moderation.Name == "manage_bookmarks" {
|
||||
require.Empty(t, moderation.Roles.Guests)
|
||||
} else {
|
||||
require.Equal(t, moderation.Roles.Guests.Value, true)
|
||||
@@ -4320,7 +4320,7 @@ func TestGetChannelModerations(t *testing.T) {
|
||||
require.Nil(t, appErr)
|
||||
|
||||
th.RemovePermissionFromRole(model.PermissionManagePublicChannelMembers.Id, scheme.DefaultChannelUserRole)
|
||||
defer th.AddPermissionToRole(model.PermissionCreatePost.Id, scheme.DefaultChannelUserRole)
|
||||
defer th.AddPermissionToRole(model.PermissionManagePublicChannelMembers.Id, scheme.DefaultChannelUserRole)
|
||||
|
||||
// public channel does not have the permission
|
||||
moderations, _, err := th.SystemAdminClient.GetChannelModerations(context.Background(), channel.Id, "")
|
||||
@@ -4341,6 +4341,48 @@ func TestGetChannelModerations(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Returns the correct value for manage_bookmarks depending on whether the channel is public or private", func(t *testing.T) {
|
||||
scheme := th.SetupTeamScheme()
|
||||
team.SchemeId = &scheme.Id
|
||||
_, appErr := th.App.UpdateTeamScheme(team)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
bookmarkPublicPermissions := []string{
|
||||
model.PermissionAddBookmarkPublicChannel.Id,
|
||||
model.PermissionEditBookmarkPublicChannel.Id,
|
||||
model.PermissionDeleteBookmarkPublicChannel.Id,
|
||||
model.PermissionOrderBookmarkPublicChannel.Id,
|
||||
}
|
||||
|
||||
for _, p := range bookmarkPublicPermissions {
|
||||
th.RemovePermissionFromRole(p, scheme.DefaultChannelUserRole)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
for _, p := range bookmarkPublicPermissions {
|
||||
th.AddPermissionToRole(p, scheme.DefaultChannelUserRole)
|
||||
}
|
||||
}()
|
||||
|
||||
// public channel does not have the permissions
|
||||
moderations, _, err := th.SystemAdminClient.GetChannelModerations(context.Background(), channel.Id, "")
|
||||
require.NoError(t, err)
|
||||
for _, moderation := range moderations {
|
||||
if moderation.Name == "manage_bookmarks" {
|
||||
require.Equal(t, moderation.Roles.Members.Value, false)
|
||||
}
|
||||
}
|
||||
|
||||
// private channel does have the permissions
|
||||
moderations, _, err = th.SystemAdminClient.GetChannelModerations(context.Background(), th.BasicPrivateChannel.Id, "")
|
||||
require.NoError(t, err)
|
||||
for _, moderation := range moderations {
|
||||
if moderation.Name == "manage_bookmarks" {
|
||||
require.Equal(t, moderation.Roles.Members.Value, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Does not return an error if the team scheme has a blank DefaultChannelGuestRole field", func(t *testing.T) {
|
||||
scheme := th.SetupTeamScheme()
|
||||
scheme.DefaultChannelGuestRole = ""
|
||||
@@ -4405,9 +4447,9 @@ func TestPatchChannelModerations(t *testing.T) {
|
||||
t.Run("Returns default moderations with empty patch", func(t *testing.T) {
|
||||
moderations, _, err := th.SystemAdminClient.PatchChannelModerations(context.Background(), channel.Id, emptyPatch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(moderations), 4)
|
||||
require.Equal(t, len(moderations), 5)
|
||||
for _, moderation := range moderations {
|
||||
if moderation.Name == "manage_members" {
|
||||
if moderation.Name == "manage_members" || moderation.Name == "manage_bookmarks" {
|
||||
require.Empty(t, moderation.Roles.Guests)
|
||||
} else {
|
||||
require.Equal(t, moderation.Roles.Guests.Value, true)
|
||||
@@ -4431,9 +4473,9 @@ func TestPatchChannelModerations(t *testing.T) {
|
||||
|
||||
moderations, _, err := th.SystemAdminClient.PatchChannelModerations(context.Background(), channel.Id, patch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(moderations), 4)
|
||||
require.Equal(t, len(moderations), 5)
|
||||
for _, moderation := range moderations {
|
||||
if moderation.Name == "manage_members" {
|
||||
if moderation.Name == "manage_members" || moderation.Name == "manage_bookmarks" {
|
||||
require.Empty(t, moderation.Roles.Guests)
|
||||
} else {
|
||||
require.Equal(t, moderation.Roles.Guests.Value, true)
|
||||
@@ -4468,9 +4510,9 @@ func TestPatchChannelModerations(t *testing.T) {
|
||||
|
||||
moderations, _, err := th.SystemAdminClient.PatchChannelModerations(context.Background(), channel.Id, patch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(moderations), 4)
|
||||
require.Equal(t, len(moderations), 5)
|
||||
for _, moderation := range moderations {
|
||||
if moderation.Name == "manage_members" {
|
||||
if moderation.Name == "manage_members" || moderation.Name == "manage_bookmarks" {
|
||||
require.Empty(t, moderation.Roles.Guests)
|
||||
} else {
|
||||
require.Equal(t, moderation.Roles.Guests.Value, true)
|
||||
@@ -4523,9 +4565,9 @@ func TestPatchChannelModerations(t *testing.T) {
|
||||
|
||||
moderations, _, err := th.SystemAdminClient.PatchChannelModerations(context.Background(), channel.Id, emptyPatch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(moderations), 4)
|
||||
require.Equal(t, len(moderations), 5)
|
||||
for _, moderation := range moderations {
|
||||
if moderation.Name == "manage_members" {
|
||||
if moderation.Name == "manage_members" || moderation.Name == "manage_bookmarks" {
|
||||
require.Empty(t, moderation.Roles.Guests)
|
||||
} else {
|
||||
require.Equal(t, moderation.Roles.Guests.Value, false)
|
||||
@@ -4545,9 +4587,9 @@ func TestPatchChannelModerations(t *testing.T) {
|
||||
|
||||
moderations, _, err = th.SystemAdminClient.PatchChannelModerations(context.Background(), channel.Id, patch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(moderations), 4)
|
||||
require.Equal(t, len(moderations), 5)
|
||||
for _, moderation := range moderations {
|
||||
if moderation.Name == "manage_members" {
|
||||
if moderation.Name == "manage_members" || moderation.Name == "manage_bookmarks" {
|
||||
require.Empty(t, moderation.Roles.Guests)
|
||||
} else {
|
||||
require.Equal(t, moderation.Roles.Guests.Value, false)
|
||||
|
||||
@@ -150,9 +150,15 @@ func uploadFileSimple(c *Context, r *http.Request, timestamp time.Time) *model.F
|
||||
clientId := r.Form.Get("client_id")
|
||||
audit.AddEventParameter(auditRec, "client_id", clientId)
|
||||
|
||||
creatorId := c.AppContext.Session().UserId
|
||||
if isBookmark, err := strconv.ParseBool(r.URL.Query().Get(model.BookmarkFileOwner)); err == nil && isBookmark {
|
||||
creatorId = model.BookmarkFileOwner
|
||||
audit.AddEventParameter(auditRec, model.BookmarkFileOwner, true)
|
||||
}
|
||||
|
||||
info, appErr := c.App.UploadFileX(c.AppContext, c.Params.ChannelId, c.Params.Filename, r.Body,
|
||||
app.UploadFileSetTeamId(FileTeamId),
|
||||
app.UploadFileSetUserId(c.AppContext.Session().UserId),
|
||||
app.UploadFileSetUserId(creatorId),
|
||||
app.UploadFileSetTimestamp(timestamp),
|
||||
app.UploadFileSetContentLength(r.ContentLength),
|
||||
app.UploadFileSetClientId(clientId))
|
||||
@@ -267,6 +273,11 @@ NextPart:
|
||||
continue NextPart
|
||||
}
|
||||
|
||||
isBookmark := false
|
||||
if val, queryErr := strconv.ParseBool(r.URL.Query().Get(model.BookmarkFileOwner)); queryErr == nil {
|
||||
isBookmark = val
|
||||
}
|
||||
|
||||
// A file part.
|
||||
|
||||
if c.Params.ChannelId == "" && asStream == nil {
|
||||
@@ -279,7 +290,7 @@ NextPart:
|
||||
return nil
|
||||
}
|
||||
|
||||
return uploadFileMultipartLegacy(c, mr, timestamp)
|
||||
return uploadFileMultipartLegacy(c, mr, timestamp, isBookmark)
|
||||
}
|
||||
|
||||
c.RequireChannelId()
|
||||
@@ -312,9 +323,15 @@ NextPart:
|
||||
audit.AddEventParameter(auditRec, "channel_id", c.Params.ChannelId)
|
||||
audit.AddEventParameter(auditRec, "client_id", clientId)
|
||||
|
||||
creatorId := c.AppContext.Session().UserId
|
||||
if isBookmark {
|
||||
creatorId = model.BookmarkFileOwner
|
||||
audit.AddEventParameter(auditRec, model.BookmarkFileOwner, true)
|
||||
}
|
||||
|
||||
info, appErr := c.App.UploadFileX(c.AppContext, c.Params.ChannelId, filename, part,
|
||||
app.UploadFileSetTeamId(FileTeamId),
|
||||
app.UploadFileSetUserId(c.AppContext.Session().UserId),
|
||||
app.UploadFileSetUserId(creatorId),
|
||||
app.UploadFileSetTimestamp(timestamp),
|
||||
app.UploadFileSetContentLength(-1),
|
||||
app.UploadFileSetClientId(clientId))
|
||||
@@ -353,7 +370,7 @@ NextPart:
|
||||
// borrowing from http.ParseMultipartForm. If successful it returns a
|
||||
// *model.FileUploadResponse filled in with the individual model.FileInfo's.
|
||||
func uploadFileMultipartLegacy(c *Context, mr *multipart.Reader,
|
||||
timestamp time.Time) *model.FileUploadResponse {
|
||||
timestamp time.Time, isBookmark bool) *model.FileUploadResponse {
|
||||
// Parse the entire form.
|
||||
form, err := mr.ReadForm(*c.App.Config().FileSettings.MaxFileSize)
|
||||
if err != nil {
|
||||
@@ -414,9 +431,15 @@ func uploadFileMultipartLegacy(c *Context, mr *multipart.Reader,
|
||||
audit.AddEventParameter(auditRec, "channel_id", channelId)
|
||||
audit.AddEventParameter(auditRec, "client_id", clientId)
|
||||
|
||||
creatorId := c.AppContext.Session().UserId
|
||||
if isBookmark {
|
||||
creatorId = model.BookmarkFileOwner
|
||||
audit.AddEventParameter(auditRec, model.BookmarkFileOwner, true)
|
||||
}
|
||||
|
||||
info, appErr := c.App.UploadFileX(c.AppContext, c.Params.ChannelId, fileHeader.Filename, f,
|
||||
app.UploadFileSetTeamId(FileTeamId),
|
||||
app.UploadFileSetUserId(c.AppContext.Session().UserId),
|
||||
app.UploadFileSetUserId(creatorId),
|
||||
app.UploadFileSetTimestamp(timestamp),
|
||||
app.UploadFileSetContentLength(-1),
|
||||
app.UploadFileSetClientId(clientId))
|
||||
@@ -461,8 +484,12 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
audit.AddEventParameterAuditable(auditRec, "file", info)
|
||||
|
||||
perm := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), info.ChannelId, model.PermissionReadChannelContent)
|
||||
|
||||
if info.CreatorId != c.AppContext.Session().UserId && !perm {
|
||||
if info.CreatorId == model.BookmarkFileOwner {
|
||||
if !perm {
|
||||
c.SetPermissionError(model.PermissionReadChannelContent)
|
||||
return
|
||||
}
|
||||
} else if info.CreatorId != c.AppContext.Session().UserId && !perm {
|
||||
c.SetPermissionError(model.PermissionReadChannelContent)
|
||||
return
|
||||
}
|
||||
@@ -495,7 +522,12 @@ func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
perm := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), info.ChannelId, model.PermissionReadChannelContent)
|
||||
if info.CreatorId != c.AppContext.Session().UserId && !perm {
|
||||
if info.CreatorId == model.BookmarkFileOwner {
|
||||
if !perm {
|
||||
c.SetPermissionError(model.PermissionReadChannelContent)
|
||||
return
|
||||
}
|
||||
} else if info.CreatorId != c.AppContext.Session().UserId && !perm {
|
||||
c.SetPermissionError(model.PermissionReadChannelContent)
|
||||
return
|
||||
}
|
||||
@@ -539,12 +571,17 @@ func getFileLink(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
audit.AddEventParameterAuditable(auditRec, "file", info)
|
||||
|
||||
perm := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), info.ChannelId, model.PermissionReadChannelContent)
|
||||
if info.CreatorId != c.AppContext.Session().UserId && !perm {
|
||||
if info.CreatorId == model.BookmarkFileOwner {
|
||||
if !perm {
|
||||
c.SetPermissionError(model.PermissionReadChannelContent)
|
||||
return
|
||||
}
|
||||
} else if info.CreatorId != c.AppContext.Session().UserId && !perm {
|
||||
c.SetPermissionError(model.PermissionReadChannelContent)
|
||||
return
|
||||
}
|
||||
|
||||
if info.PostId == "" {
|
||||
if info.PostId == "" && info.CreatorId != model.BookmarkFileOwner {
|
||||
c.Err = model.NewAppError("getPublicLink", "api.file.get_public_link.no_post.app_error", nil, "file_id="+info.Id, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -573,7 +610,12 @@ func getFilePreview(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
perm := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), info.ChannelId, model.PermissionReadChannelContent)
|
||||
if info.CreatorId != c.AppContext.Session().UserId && !perm {
|
||||
if info.CreatorId == model.BookmarkFileOwner {
|
||||
if !perm {
|
||||
c.SetPermissionError(model.PermissionReadChannelContent)
|
||||
return
|
||||
}
|
||||
} else if info.CreatorId != c.AppContext.Session().UserId && !perm {
|
||||
c.SetPermissionError(model.PermissionReadChannelContent)
|
||||
return
|
||||
}
|
||||
@@ -608,8 +650,12 @@ func getFileInfo(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
perm := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), info.ChannelId, model.PermissionReadChannelContent)
|
||||
|
||||
if info.CreatorId != c.AppContext.Session().UserId && !perm {
|
||||
if info.CreatorId == model.BookmarkFileOwner {
|
||||
if !perm {
|
||||
c.SetPermissionError(model.PermissionReadChannelContent)
|
||||
return
|
||||
}
|
||||
} else if info.CreatorId != c.AppContext.Session().UserId && !perm {
|
||||
c.SetPermissionError(model.PermissionReadChannelContent)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ func testUploadFilesPost(
|
||||
blobs [][]byte,
|
||||
clientIds []string,
|
||||
useChunked bool,
|
||||
isBookmark bool,
|
||||
) (*model.FileUploadResponse, *model.Response, error) {
|
||||
// Do not check len(clientIds), leave it entirely to the user to
|
||||
// provide. The server will error out if it does not match the number
|
||||
@@ -120,6 +121,10 @@ func testUploadFilesPost(
|
||||
postURL += fmt.Sprintf("&client_id=%v", url.QueryEscape(clientIds[i]))
|
||||
}
|
||||
|
||||
if isBookmark {
|
||||
postURL += "&bookmark=true"
|
||||
}
|
||||
|
||||
fur, resp, err := testDoUploadFileRequest(t, c, postURL, blob, ct, cl)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
@@ -145,6 +150,7 @@ func testUploadFilesMultipart(
|
||||
names []string,
|
||||
blobs [][]byte,
|
||||
clientIds []string,
|
||||
isBookmark bool,
|
||||
) (
|
||||
*model.FileUploadResponse,
|
||||
*model.Response,
|
||||
@@ -185,7 +191,11 @@ func testUploadFilesMultipart(
|
||||
}
|
||||
|
||||
require.NoError(t, mw.Close())
|
||||
fur, resp, err := testDoUploadFileRequest(t, c, "", mwBody.Bytes(), mw.FormDataContentType(), -1)
|
||||
url := ""
|
||||
if isBookmark {
|
||||
url += "?bookmark=true"
|
||||
}
|
||||
fur, resp, err := testDoUploadFileRequest(t, c, url, mwBody.Bytes(), mw.FormDataContentType(), -1)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
@@ -230,6 +240,7 @@ func TestUploadFiles(t *testing.T) {
|
||||
expectedImageMiniPreview []bool
|
||||
setupConfig func(a *app.App) func(a *app.App)
|
||||
checkResponse func(t testing.TB, resp *model.Response)
|
||||
uploadAsBookmark bool
|
||||
}{
|
||||
// Upload a bunch of files, mixed images and non-images
|
||||
{
|
||||
@@ -578,6 +589,20 @@ func TestUploadFiles(t *testing.T) {
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Bookmark images",
|
||||
names: []string{"orientation_test_5.jpeg"},
|
||||
expectedImageThumbnailNames: []string{"orientation_test_5_expected_thumb.jpeg"},
|
||||
expectedImagePreviewNames: []string{"orientation_test_5_expected_preview.jpeg"},
|
||||
channelId: channel.Id,
|
||||
expectImage: true,
|
||||
expectedCreatorId: model.BookmarkFileOwner,
|
||||
expectedImageWidths: []int{2860},
|
||||
expectedImageHeights: []int{1578},
|
||||
expectedImageHasPreview: []bool{true},
|
||||
expectedImageMiniPreview: []bool{true},
|
||||
uploadAsBookmark: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, useMultipart := range []bool{true, false} {
|
||||
@@ -627,9 +652,9 @@ func TestUploadFiles(t *testing.T) {
|
||||
var resp *model.Response
|
||||
var err error
|
||||
if useMultipart {
|
||||
fileResp, resp, err = testUploadFilesMultipart(t, client, channelId, tc.names, blobs, tc.clientIds)
|
||||
fileResp, resp, err = testUploadFilesMultipart(t, client, channelId, tc.names, blobs, tc.clientIds, tc.uploadAsBookmark)
|
||||
} else {
|
||||
fileResp, resp, err = testUploadFilesPost(t, client, channelId, tc.names, blobs, tc.clientIds, tc.useChunkedInSimplePost)
|
||||
fileResp, resp, err = testUploadFilesPost(t, client, channelId, tc.names, blobs, tc.clientIds, tc.useChunkedInSimplePost, tc.uploadAsBookmark)
|
||||
}
|
||||
|
||||
if tc.checkResponse != nil {
|
||||
@@ -672,6 +697,9 @@ func TestUploadFiles(t *testing.T) {
|
||||
ext := filepath.Ext(fname)
|
||||
name := fname[:len(fname)-len(ext)]
|
||||
expectedDir := fmt.Sprintf("%v/teams/%v/channels/%v/users/%s/%s", date, FileTeamId, channel.Id, ri.CreatorId, ri.Id)
|
||||
if tc.uploadAsBookmark {
|
||||
expectedDir = fmt.Sprintf("%v/teams/%v/channels/%v/%s", model.BookmarkFileOwner, FileTeamId, channel.Id, ri.Id)
|
||||
}
|
||||
expectedPath := fmt.Sprintf("%s/%s", expectedDir, fname)
|
||||
assert.Equal(t, dbInfo.Path, expectedPath,
|
||||
fmt.Sprintf("File %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.Path, expectedPath))
|
||||
@@ -774,7 +802,7 @@ func TestGetFile(t *testing.T) {
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
|
||||
_, _, err = th.SystemAdminClient.GetFile(context.Background(), fileId)
|
||||
require.Error(t, err)
|
||||
require.NoError(t, err)
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
}
|
||||
|
||||
@@ -889,7 +917,7 @@ func TestGetFileThumbnail(t *testing.T) {
|
||||
|
||||
client.Logout(context.Background())
|
||||
_, _, err = th.SystemAdminClient.GetFileThumbnail(context.Background(), fileId)
|
||||
require.Error(t, err)
|
||||
require.NoError(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
}
|
||||
|
||||
@@ -1002,7 +1030,7 @@ func TestGetFilePreview(t *testing.T) {
|
||||
|
||||
client.Logout(context.Background())
|
||||
_, _, err = th.SystemAdminClient.GetFilePreview(context.Background(), fileId)
|
||||
require.Error(t, err)
|
||||
require.NoError(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
}
|
||||
|
||||
@@ -1027,7 +1055,6 @@ func TestGetFileInfo(t *testing.T) {
|
||||
info, _, err := client.GetFileInfo(context.Background(), fileId)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fileId, info.Id, "got incorrect file")
|
||||
require.Equal(t, user.Id, info.CreatorId, "file should be assigned to user")
|
||||
require.Equal(t, "", info.PostId, "file shouldn't have a post")
|
||||
@@ -1057,7 +1084,7 @@ func TestGetFileInfo(t *testing.T) {
|
||||
|
||||
client.Logout(context.Background())
|
||||
_, _, err = th.SystemAdminClient.GetFileInfo(context.Background(), fileId)
|
||||
require.Error(t, err)
|
||||
require.NoError(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
}
|
||||
|
||||
|
||||
@@ -506,6 +506,7 @@ type AppIface interface {
|
||||
CopyFileInfos(rctx request.CTX, userID string, fileIDs []string) ([]string, *model.AppError)
|
||||
CopyWranglerPostlist(c request.CTX, wpl *model.WranglerPostList, targetChannel *model.Channel) (*model.Post, *model.AppError)
|
||||
CreateChannel(c request.CTX, channel *model.Channel, addMember bool) (*model.Channel, *model.AppError)
|
||||
CreateChannelBookmark(c request.CTX, newBookmark *model.ChannelBookmark, connectionId string) (*model.ChannelBookmarkWithFileInfo, *model.AppError)
|
||||
CreateChannelWithUser(c request.CTX, channel *model.Channel, userID string) (*model.Channel, *model.AppError)
|
||||
CreateCommand(cmd *model.Command) (*model.Command, *model.AppError)
|
||||
CreateCommandWebhook(commandID string, args *model.CommandArgs) (*model.CommandWebhook, *model.AppError)
|
||||
@@ -550,6 +551,7 @@ type AppIface interface {
|
||||
DeleteAllKeysForPlugin(pluginID string) *model.AppError
|
||||
DeleteBrandImage(rctx request.CTX) *model.AppError
|
||||
DeleteChannel(c request.CTX, channel *model.Channel, userID string) *model.AppError
|
||||
DeleteChannelBookmark(bookmarkId, connectionId string) (*model.ChannelBookmarkWithFileInfo, *model.AppError)
|
||||
DeleteCommand(commandID string) *model.AppError
|
||||
DeleteDraft(rctx request.CTX, draft *model.Draft, connectionID string) *model.AppError
|
||||
DeleteEmoji(c request.CTX, emoji *model.Emoji) *model.AppError
|
||||
@@ -627,9 +629,11 @@ type AppIface interface {
|
||||
GetAuditsPage(rctx request.CTX, userID string, page int, perPage int) (model.Audits, *model.AppError)
|
||||
GetAuthorizationCode(c request.CTX, w http.ResponseWriter, r *http.Request, service string, props map[string]string, loginHint string) (string, *model.AppError)
|
||||
GetAuthorizedAppsForUser(userID string, page, perPage int) ([]*model.OAuthApp, *model.AppError)
|
||||
GetBookmark(bookmarkId string, includeDeleted bool) (*model.ChannelBookmarkWithFileInfo, *model.AppError)
|
||||
GetBrandImage(rctx request.CTX) ([]byte, *model.AppError)
|
||||
GetBulkReactionsForPosts(postIDs []string) (map[string][]*model.Reaction, *model.AppError)
|
||||
GetChannel(c request.CTX, channelID string) (*model.Channel, *model.AppError)
|
||||
GetChannelBookmarks(channelId string, since int64) ([]*model.ChannelBookmarkWithFileInfo, *model.AppError)
|
||||
GetChannelByName(c request.CTX, channelName, teamID string, includeDeleted bool) (*model.Channel, *model.AppError)
|
||||
GetChannelByNameForTeamName(c request.CTX, channelName, teamName string, includeDeleted bool) (*model.Channel, *model.AppError)
|
||||
GetChannelCounts(c request.CTX, teamID string, userID string) (*model.ChannelCounts, *model.AppError)
|
||||
@@ -1147,6 +1151,8 @@ type AppIface interface {
|
||||
UnregisterPluginForSharedChannels(pluginID string) error
|
||||
UnshareChannel(channelID string) (bool, error)
|
||||
UpdateActive(c request.CTX, user *model.User, active bool) (*model.User, *model.AppError)
|
||||
UpdateChannelBookmark(c request.CTX, updateBookmark *model.ChannelBookmarkWithFileInfo, connectionId string) (*model.UpdateChannelBookmarkResponse, *model.AppError)
|
||||
UpdateChannelBookmarkSortOrder(bookmarkId, channelId string, newIndex int64, connectionId string) ([]*model.ChannelBookmarkWithFileInfo, *model.AppError)
|
||||
UpdateChannelMemberNotifyProps(c request.CTX, data map[string]string, channelID string, userID string) (*model.ChannelMember, *model.AppError)
|
||||
UpdateChannelMemberRoles(c request.CTX, channelID string, userID string, newRoles string) (*model.ChannelMember, *model.AppError)
|
||||
UpdateChannelMemberSchemeRoles(c request.CTX, channelID string, userID string, isSchemeGuest bool, isSchemeUser bool, isSchemeAdmin bool) (*model.ChannelMember, *model.AppError)
|
||||
|
||||
@@ -127,10 +127,26 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
|
||||
model.PermissionManagePrivateChannelMembers.Id,
|
||||
model.PermissionDeletePost.Id,
|
||||
model.PermissionEditPost.Id,
|
||||
model.PermissionAddBookmarkPublicChannel.Id,
|
||||
model.PermissionEditBookmarkPublicChannel.Id,
|
||||
model.PermissionDeleteBookmarkPublicChannel.Id,
|
||||
model.PermissionOrderBookmarkPublicChannel.Id,
|
||||
model.PermissionAddBookmarkPrivateChannel.Id,
|
||||
model.PermissionEditBookmarkPrivateChannel.Id,
|
||||
model.PermissionDeleteBookmarkPrivateChannel.Id,
|
||||
model.PermissionOrderBookmarkPrivateChannel.Id,
|
||||
},
|
||||
"channel_admin": {
|
||||
model.PermissionManageChannelRoles.Id,
|
||||
model.PermissionUseGroupMentions.Id,
|
||||
model.PermissionAddBookmarkPublicChannel.Id,
|
||||
model.PermissionEditBookmarkPublicChannel.Id,
|
||||
model.PermissionDeleteBookmarkPublicChannel.Id,
|
||||
model.PermissionOrderBookmarkPublicChannel.Id,
|
||||
model.PermissionAddBookmarkPrivateChannel.Id,
|
||||
model.PermissionEditBookmarkPrivateChannel.Id,
|
||||
model.PermissionDeleteBookmarkPrivateChannel.Id,
|
||||
model.PermissionOrderBookmarkPrivateChannel.Id,
|
||||
},
|
||||
"team_user": {
|
||||
model.PermissionListTeamChannels.Id,
|
||||
@@ -166,6 +182,14 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
|
||||
model.PermissionConvertPrivateChannelToPublic.Id,
|
||||
model.PermissionDeletePost.Id,
|
||||
model.PermissionDeleteOthersPosts.Id,
|
||||
model.PermissionAddBookmarkPublicChannel.Id,
|
||||
model.PermissionEditBookmarkPublicChannel.Id,
|
||||
model.PermissionDeleteBookmarkPublicChannel.Id,
|
||||
model.PermissionOrderBookmarkPublicChannel.Id,
|
||||
model.PermissionAddBookmarkPrivateChannel.Id,
|
||||
model.PermissionEditBookmarkPrivateChannel.Id,
|
||||
model.PermissionDeleteBookmarkPrivateChannel.Id,
|
||||
model.PermissionOrderBookmarkPrivateChannel.Id,
|
||||
},
|
||||
"system_user": {
|
||||
model.PermissionListPublicTeams.Id,
|
||||
|
||||
@@ -1122,7 +1122,7 @@ func buildChannelModerations(c request.CTX, channelType model.ChannelType, membe
|
||||
Enabled: higherScopedMemberPermissions[permissionKey],
|
||||
}
|
||||
|
||||
if permissionKey == "manage_members" {
|
||||
if permissionKey == "manage_members" || permissionKey == "manage_bookmarks" {
|
||||
roles.Guests = nil
|
||||
} else {
|
||||
roles.Guests = &model.ChannelModeratedRole{
|
||||
|
||||
152
server/channels/app/channel_bookmark.go
Обычный файл
152
server/channels/app/channel_bookmark.go
Обычный файл
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
)
|
||||
|
||||
func (a *App) GetChannelBookmarks(channelId string, since int64) ([]*model.ChannelBookmarkWithFileInfo, *model.AppError) {
|
||||
bookmarks, err := a.Srv().Store().ChannelBookmark().GetBookmarksForChannelSince(channelId, since)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetChannelBookmarks", "app.channel.bookmark.get.app_error", nil, "", http.StatusNotFound).Wrap(err)
|
||||
}
|
||||
|
||||
return bookmarks, nil
|
||||
}
|
||||
|
||||
func (a *App) GetBookmark(bookmarkId string, includeDeleted bool) (*model.ChannelBookmarkWithFileInfo, *model.AppError) {
|
||||
bookmark, err := a.Srv().Store().ChannelBookmark().Get(bookmarkId, includeDeleted)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetBookmark", "app.channel.bookmark.get.app_error", nil, "", http.StatusNotFound).Wrap(err)
|
||||
}
|
||||
|
||||
return bookmark, nil
|
||||
}
|
||||
|
||||
func (a *App) CreateChannelBookmark(c request.CTX, newBookmark *model.ChannelBookmark, connectionId string) (*model.ChannelBookmarkWithFileInfo, *model.AppError) {
|
||||
newBookmark.OwnerId = c.Session().UserId //ensure that the bookmark is being created by the user who owns the session
|
||||
newBookmark.Id = "" // ensure that creating a new bookmark generates a new ID
|
||||
bookmark, err := a.Srv().Store().ChannelBookmark().Save(newBookmark, true)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("CreateChannelBookmark", "app.channel.bookmark.save.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventChannelBookmarkCreated, "", bookmark.ChannelId, "", nil, connectionId)
|
||||
bookmarkJSON, jsonErr := json.Marshal(bookmark)
|
||||
if jsonErr != nil {
|
||||
return nil, model.NewAppError("CreateChannelBookmark", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr)
|
||||
}
|
||||
message.Add("bookmark", string(bookmarkJSON))
|
||||
a.Publish(message)
|
||||
return bookmark, nil
|
||||
}
|
||||
|
||||
func (a *App) UpdateChannelBookmark(c request.CTX, updateBookmark *model.ChannelBookmarkWithFileInfo, connectionId string) (*model.UpdateChannelBookmarkResponse, *model.AppError) {
|
||||
response := &model.UpdateChannelBookmarkResponse{}
|
||||
if updateBookmark.OwnerId == c.Session().UserId {
|
||||
isAnotherFile := updateBookmark.FileInfo != nil && updateBookmark.FileId != "" && updateBookmark.FileId != updateBookmark.FileInfo.Id
|
||||
|
||||
if isAnotherFile {
|
||||
if fileAlreadyAttachedErr := a.Srv().Store().ChannelBookmark().ErrorIfBookmarkFileInfoAlreadyAttached(updateBookmark.FileId); fileAlreadyAttachedErr != nil {
|
||||
return nil, model.NewAppError("UpdateChannelBookmark", "app.channel.bookmark.update.app_error", nil, "", http.StatusInternalServerError).Wrap(fileAlreadyAttachedErr)
|
||||
}
|
||||
}
|
||||
|
||||
if err := a.Srv().Store().ChannelBookmark().Update(updateBookmark.ChannelBookmark); err != nil {
|
||||
return nil, model.NewAppError("UpdateChannelBookmark", "app.channel.bookmark.update.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
if isAnotherFile {
|
||||
fileInfo, fileErr := a.Srv().Store().FileInfo().Get(updateBookmark.FileId)
|
||||
if fileErr != nil {
|
||||
return nil, model.NewAppError("UpdateChannelBookmark", "app.channel.bookmark.get_existing.app_err", nil, "", http.StatusNotFound).Wrap(fileErr)
|
||||
}
|
||||
response.Updated = updateBookmark.ToBookmarkWithFileInfo(fileInfo)
|
||||
} else {
|
||||
response.Updated = updateBookmark.ToBookmarkWithFileInfo(updateBookmark.FileInfo)
|
||||
}
|
||||
} else {
|
||||
existingBookmark, ebErr := a.Srv().Store().ChannelBookmark().Get(updateBookmark.Id, false)
|
||||
if ebErr != nil {
|
||||
return nil, model.NewAppError("UpdateChannelBookmark", "app.channel.bookmark.get_existing.app_err", nil, "", http.StatusNotFound).Wrap(ebErr)
|
||||
}
|
||||
|
||||
existingBookmark.DeleteAt = model.GetMillis()
|
||||
if err := a.Srv().Store().ChannelBookmark().Delete(updateBookmark.Id, false); err != nil {
|
||||
return nil, model.NewAppError("UpdateChannelBookmark", "app.channel.bookmark.delete.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
newBookmark := updateBookmark.SetOriginal(c.Session().UserId)
|
||||
bookmark, err := a.Srv().Store().ChannelBookmark().Save(newBookmark, false)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("UpdateChannelBookmark", "app.channel.bookmark.save.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
response.Updated = bookmark
|
||||
response.Deleted = existingBookmark.ToBookmarkWithFileInfo(nil)
|
||||
}
|
||||
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventChannelBookmarkUpdated, "", updateBookmark.ChannelId, "", nil, connectionId)
|
||||
bookmarkJSON, jsonErr := json.Marshal(response)
|
||||
if jsonErr != nil {
|
||||
return nil, model.NewAppError("UpdateChannelBookmark", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr)
|
||||
}
|
||||
message.Add("bookmarks", string(bookmarkJSON))
|
||||
a.Publish(message)
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (a *App) DeleteChannelBookmark(bookmarkId, connectionId string) (*model.ChannelBookmarkWithFileInfo, *model.AppError) {
|
||||
if err := a.Srv().Store().ChannelBookmark().Delete(bookmarkId, true); err != nil {
|
||||
return nil, model.NewAppError("DeleteChannelBookmark", "app.channel.bookmark.delete.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
bookmark, err := a.GetBookmark(bookmarkId, true)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("DeleteChannelBookmark", "app.channel.bookmark.get.app_error", nil, "", http.StatusNotFound).Wrap(err)
|
||||
}
|
||||
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventChannelBookmarkDeleted, "", bookmark.ChannelId, "", nil, connectionId)
|
||||
bookmarkJSON, jsonErr := json.Marshal(bookmark)
|
||||
if jsonErr != nil {
|
||||
return nil, model.NewAppError("DeleteChannelBookmark", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr)
|
||||
}
|
||||
message.Add("bookmark", string(bookmarkJSON))
|
||||
a.Publish(message)
|
||||
|
||||
return bookmark, nil
|
||||
}
|
||||
|
||||
func (a *App) UpdateChannelBookmarkSortOrder(bookmarkId, channelId string, newIndex int64, connectionId string) ([]*model.ChannelBookmarkWithFileInfo, *model.AppError) {
|
||||
bookmarks, err := a.Srv().Store().ChannelBookmark().UpdateSortOrder(bookmarkId, channelId, newIndex)
|
||||
if err != nil {
|
||||
var iiErr *store.ErrInvalidInput
|
||||
var nfErr *store.ErrNotFound
|
||||
switch {
|
||||
case errors.As(err, &iiErr):
|
||||
return nil, model.NewAppError("UpdateSortOrder", "app.channel.bookmark.update_sort.invalid_input.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
case errors.As(err, &nfErr):
|
||||
return nil, model.NewAppError("UpdateSortOrder", "app.channel.bookmark.update_sort.missing_bookmark.app_error", nil, "", http.StatusNotFound).Wrap(err)
|
||||
default:
|
||||
return nil, model.NewAppError("UpdateSortOrder", "app.channel.bookmark.update_sort.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventChannelBookmarkSorted, "", channelId, "", nil, connectionId)
|
||||
bookmarkJSON, jsonErr := json.Marshal(bookmarks)
|
||||
if jsonErr != nil {
|
||||
return nil, model.NewAppError("UpdateSortOrder", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr)
|
||||
}
|
||||
message.Add("bookmarks", string(bookmarkJSON))
|
||||
a.Publish(message)
|
||||
|
||||
return bookmarks, nil
|
||||
}
|
||||
498
server/channels/app/channel_bookmark_test.go
Обычный файл
498
server/channels/app/channel_bookmark_test.go
Обычный файл
@@ -0,0 +1,498 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func find_bookmark(slice []*model.ChannelBookmarkWithFileInfo, id string) *model.ChannelBookmarkWithFileInfo {
|
||||
for _, element := range slice {
|
||||
if element.Id == id {
|
||||
return element
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createBookmark(name string, bookmarkType model.ChannelBookmarkType, channelId string, fileId string) *model.ChannelBookmark {
|
||||
bookmark := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
DisplayName: name,
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: bookmarkType,
|
||||
Emoji: ":smile:",
|
||||
FileId: fileId,
|
||||
}
|
||||
|
||||
return bookmark
|
||||
}
|
||||
|
||||
func TestCreateBookmark(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("create a channel bookmark", func(t *testing.T) {
|
||||
th.Context.Session().UserId = th.BasicUser.Id // set the user for the session
|
||||
|
||||
bookmark1 := createBookmark("Link bookmark test", model.ChannelBookmarkLink, th.BasicChannel.Id, "")
|
||||
bookmarkResp, err := th.App.CreateChannelBookmark(th.Context, bookmark1, "")
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, bookmarkResp)
|
||||
|
||||
assert.Equal(t, bookmarkResp.ChannelId, th.BasicChannel.Id)
|
||||
assert.NotEmpty(t, bookmarkResp.Id)
|
||||
|
||||
bookmark2 := createBookmark("File bookmark test", model.ChannelBookmarkFile, th.BasicChannel.Id, "")
|
||||
|
||||
bookmarkResp, err = th.App.CreateChannelBookmark(th.Context, bookmark2, "")
|
||||
assert.Nil(t, bookmarkResp)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
|
||||
t.Run("Cannot create more than MaxBookmarksPerChannel", func(t *testing.T) {
|
||||
th.Context.Session().UserId = th.BasicUser.Id // set the user for the session
|
||||
|
||||
for i := 1; i < model.MaxBookmarksPerChannel; i++ {
|
||||
bookmark := createBookmark(fmt.Sprintf("Link bookmark test %d", i), model.ChannelBookmarkLink, th.BasicChannel.Id, "")
|
||||
bookmarkResp, err := th.App.CreateChannelBookmark(th.Context, bookmark, "")
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, bookmarkResp)
|
||||
assert.Equal(t, bookmarkResp.ChannelId, th.BasicChannel.Id)
|
||||
assert.NotEmpty(t, bookmarkResp.Id)
|
||||
}
|
||||
|
||||
bookmark := createBookmark("Bookmark that should not be added", model.ChannelBookmarkLink, th.BasicChannel.Id, "")
|
||||
bookmarkResp, err := th.App.CreateChannelBookmark(th.Context, bookmark, "")
|
||||
assert.Nil(t, bookmarkResp)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateBookmark(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
var updateBookmark *model.ChannelBookmarkWithFileInfo
|
||||
|
||||
var testUpdateAnotherFile = func(th *TestHelper, t *testing.T) {
|
||||
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,
|
||||
}
|
||||
|
||||
th.App.Srv().Store().FileInfo().Save(th.Context, file)
|
||||
defer th.App.Srv().Store().FileInfo().PermanentDelete(th.Context, file.Id)
|
||||
|
||||
bookmark2 := createBookmark("File to be updated", model.ChannelBookmarkFile, th.BasicChannel.Id, file.Id)
|
||||
bookmarkResp, err := th.App.CreateChannelBookmark(th.Context, bookmark2, "")
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, bookmarkResp)
|
||||
|
||||
file2 := &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,
|
||||
}
|
||||
|
||||
th.App.Srv().Store().FileInfo().Save(th.Context, file2)
|
||||
th.App.Srv().Store().FileInfo().AttachToPost(th.Context, file2.Id, model.NewId(), th.BasicChannel.Id, model.BookmarkFileOwner)
|
||||
defer th.App.Srv().Store().FileInfo().PermanentDelete(th.Context, file2.Id)
|
||||
|
||||
bookmark2.FileId = file2.Id
|
||||
bookmarkResp, err = th.App.CreateChannelBookmark(th.Context, bookmark2, "")
|
||||
require.NotNil(t, err)
|
||||
require.Nil(t, bookmarkResp)
|
||||
}
|
||||
|
||||
t.Run("same user update a channel bookmark", func(t *testing.T) {
|
||||
bookmark1 := &model.ChannelBookmark{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
DisplayName: "Link bookmark test",
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: model.ChannelBookmarkLink,
|
||||
Emoji: ":smile:",
|
||||
}
|
||||
|
||||
th.Context.Session().UserId = th.BasicUser.Id // set the user for the session
|
||||
bookmarkResp, err := th.App.CreateChannelBookmark(th.Context, bookmark1, "")
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, bookmarkResp)
|
||||
|
||||
updateBookmark = bookmarkResp.Clone()
|
||||
updateBookmark.DisplayName = "New name"
|
||||
time.Sleep(1 * time.Millisecond) // to avoid collisions
|
||||
response, _ := th.App.UpdateChannelBookmark(th.Context, updateBookmark, "")
|
||||
require.NotNil(t, response)
|
||||
assert.Greater(t, response.Updated.UpdateAt, response.Updated.CreateAt)
|
||||
|
||||
testUpdateAnotherFile(th, t)
|
||||
})
|
||||
|
||||
t.Run("another user update a channel bookmark", func(t *testing.T) {
|
||||
updateBookmark2 := updateBookmark.Clone()
|
||||
updateBookmark2.DisplayName = "Another new name"
|
||||
th.Context.Session().UserId = th.BasicUser2.Id
|
||||
response, _ := th.App.UpdateChannelBookmark(th.Context, updateBookmark2, "")
|
||||
require.NotNil(t, response)
|
||||
assert.Equal(t, response.Updated.OriginalId, response.Deleted.Id)
|
||||
assert.Equal(t, response.Updated.DeleteAt, int64(0))
|
||||
assert.Greater(t, response.Deleted.DeleteAt, int64(0))
|
||||
assert.Equal(t, "Another new name", response.Updated.DisplayName)
|
||||
assert.Equal(t, "New name", response.Deleted.DisplayName)
|
||||
|
||||
testUpdateAnotherFile(th, t)
|
||||
})
|
||||
|
||||
t.Run("update an already deleted channel bookmark", func(t *testing.T) {
|
||||
bookmark1 := &model.ChannelBookmark{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
DisplayName: "Link bookmark test",
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: model.ChannelBookmarkLink,
|
||||
Emoji: ":smile:",
|
||||
}
|
||||
|
||||
th.Context.Session().UserId = th.BasicUser.Id // set the user for the session
|
||||
bookmarkResp, err := th.App.CreateChannelBookmark(th.Context, bookmark1, "")
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, bookmarkResp)
|
||||
|
||||
updateBookmark = bookmarkResp.Clone()
|
||||
_, err = th.App.DeleteChannelBookmark(updateBookmark.Id, "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
updateBookmark.DisplayName = "New name"
|
||||
_, err = th.App.UpdateChannelBookmark(th.Context, updateBookmark, "")
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
|
||||
t.Run("update a nonexisting channel bookmark", func(t *testing.T) {
|
||||
updateBookmark := &model.ChannelBookmark{
|
||||
Id: model.NewId(),
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
DisplayName: "Link bookmark test",
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: model.ChannelBookmarkLink,
|
||||
Emoji: ":smile:",
|
||||
}
|
||||
_, err := th.App.UpdateChannelBookmark(th.Context, updateBookmark.ToBookmarkWithFileInfo(nil), "")
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "app.channel.bookmark.get_existing.app_err", err.Id)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteBookmark(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("delete a channel bookmark", func(t *testing.T) {
|
||||
bookmark1 := &model.ChannelBookmark{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
DisplayName: "Link bookmark test",
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: model.ChannelBookmarkLink,
|
||||
Emoji: ":smile:",
|
||||
}
|
||||
|
||||
th.Context.Session().UserId = th.BasicUser.Id // set the user for the session
|
||||
bookmarkResp, err := th.App.CreateChannelBookmark(th.Context, bookmark1, "")
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, bookmarkResp)
|
||||
|
||||
bookmarkResp, err = th.App.DeleteChannelBookmark(bookmarkResp.Id, "")
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, bookmarkResp)
|
||||
assert.Greater(t, bookmarkResp.DeleteAt, int64(0))
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetChannelBookmarks(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Context.Session().UserId = th.BasicUser.Id // set the user for the session
|
||||
|
||||
bookmark1 := &model.ChannelBookmark{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
DisplayName: "Bookmark 1",
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: model.ChannelBookmarkLink,
|
||||
Emoji: ":smile:",
|
||||
}
|
||||
|
||||
th.App.CreateChannelBookmark(th.Context, bookmark1, "")
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
th.App.Srv().Store().FileInfo().Save(th.Context, file)
|
||||
defer th.App.Srv().Store().FileInfo().PermanentDelete(th.Context, file.Id)
|
||||
|
||||
bookmark2 := &model.ChannelBookmark{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
DisplayName: "Bookmark 2",
|
||||
FileId: file.Id,
|
||||
Type: model.ChannelBookmarkFile,
|
||||
Emoji: ":smile:",
|
||||
}
|
||||
|
||||
th.App.CreateChannelBookmark(th.Context, bookmark2, "")
|
||||
|
||||
t.Run("get bookmarks of a channel", func(t *testing.T) {
|
||||
bookmarks, err := th.App.GetChannelBookmarks(th.BasicChannel.Id, 0)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, bookmarks)
|
||||
assert.Len(t, bookmarks, 2)
|
||||
})
|
||||
|
||||
t.Run("get bookmarks of a channel after one is deleted (aka only return the changed bookmarks)", func(t *testing.T) {
|
||||
now := model.GetMillis()
|
||||
th.App.DeleteChannelBookmark(bookmark1.Id, "")
|
||||
|
||||
bookmarks, err := th.App.GetChannelBookmarks(th.BasicChannel.Id, 0)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, bookmarks)
|
||||
assert.Len(t, bookmarks, 1)
|
||||
|
||||
bookmarks, err = th.App.GetChannelBookmarks(th.BasicChannel.Id, now)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, bookmarks)
|
||||
assert.Len(t, bookmarks, 1)
|
||||
|
||||
deleted := false
|
||||
for _, b := range bookmarks {
|
||||
if b.DeleteAt > 0 {
|
||||
deleted = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.Equal(t, deleted, true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateChannelBookmarkSortOrder(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
channelId := th.BasicChannel.Id
|
||||
th.Context.Session().UserId = th.BasicUser.Id // set the user for the session
|
||||
|
||||
bookmark0 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
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,
|
||||
DisplayName: "Bookmark 1",
|
||||
FileId: file.Id,
|
||||
Type: model.ChannelBookmarkFile,
|
||||
Emoji: ":smile:",
|
||||
}
|
||||
|
||||
_, err := th.App.Srv().Store().FileInfo().Save(th.Context, file)
|
||||
require.NoError(t, err)
|
||||
defer th.App.Srv().Store().FileInfo().PermanentDelete(th.Context, file.Id)
|
||||
|
||||
bookmark2 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
DisplayName: "Bookmark 2",
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: model.ChannelBookmarkLink,
|
||||
}
|
||||
|
||||
bookmark3 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
DisplayName: "Bookmark 3",
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: model.ChannelBookmarkLink,
|
||||
}
|
||||
|
||||
bookmark4 := &model.ChannelBookmark{
|
||||
ChannelId: channelId,
|
||||
DisplayName: "Bookmark 4",
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: model.ChannelBookmarkLink,
|
||||
}
|
||||
|
||||
bookmarkResp, appErr := th.App.CreateChannelBookmark(th.Context, bookmark0, "")
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, bookmarkResp)
|
||||
bookmark0 = bookmarkResp.ChannelBookmark.Clone()
|
||||
|
||||
bookmarkResp, appErr = th.App.CreateChannelBookmark(th.Context, bookmark1, "")
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, bookmarkResp)
|
||||
bookmark1 = bookmarkResp.ChannelBookmark.Clone()
|
||||
|
||||
bookmarkResp, appErr = th.App.CreateChannelBookmark(th.Context, bookmark2, "")
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, bookmarkResp)
|
||||
bookmark2 = bookmarkResp.ChannelBookmark.Clone()
|
||||
|
||||
bookmarkResp, appErr = th.App.CreateChannelBookmark(th.Context, bookmark3, "")
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, bookmarkResp)
|
||||
bookmark3 = bookmarkResp.ChannelBookmark.Clone()
|
||||
|
||||
bookmarkResp, appErr = th.App.CreateChannelBookmark(th.Context, bookmark4, "")
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, bookmarkResp)
|
||||
bookmark4 = bookmarkResp.ChannelBookmark.Clone()
|
||||
|
||||
t.Run("change order of bookmarks first to last", func(t *testing.T) {
|
||||
bookmarks, sortErr := th.App.UpdateChannelBookmarkSortOrder(bookmark0.Id, channelId, int64(4), "")
|
||||
require.Nil(t, sortErr)
|
||||
require.NotNil(t, bookmarks)
|
||||
|
||||
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, sortErr := th.App.UpdateChannelBookmarkSortOrder(bookmark0.Id, channelId, int64(0), "")
|
||||
require.Nil(t, sortErr)
|
||||
require.NotNil(t, bookmarks)
|
||||
|
||||
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, sortErr := th.App.UpdateChannelBookmarkSortOrder(bookmark0.Id, channelId, int64(2), "")
|
||||
require.Nil(t, sortErr)
|
||||
require.NotNil(t, bookmarks)
|
||||
|
||||
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
|
||||
th.App.UpdateChannelBookmarkSortOrder(bookmark0.Id, channelId, int64(0), "")
|
||||
})
|
||||
|
||||
t.Run("change order of bookmarks second to third", func(t *testing.T) {
|
||||
bookmarks, sortErr := th.App.UpdateChannelBookmarkSortOrder(bookmark1.Id, channelId, int64(2), "")
|
||||
require.Nil(t, sortErr)
|
||||
require.NotNil(t, bookmarks)
|
||||
|
||||
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, sortErr := th.App.UpdateChannelBookmarkSortOrder(bookmark1.Id, channelId, int64(1), "")
|
||||
require.Nil(t, sortErr)
|
||||
require.NotNil(t, bookmarks)
|
||||
|
||||
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, sortErr := th.App.UpdateChannelBookmarkSortOrder(bookmark4.Id, channelId, int64(3), "")
|
||||
require.Nil(t, sortErr)
|
||||
require.NotNil(t, bookmarks)
|
||||
|
||||
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, sortErr := th.App.UpdateChannelBookmarkSortOrder(bookmark3.Id, channelId, int64(1), "")
|
||||
require.Nil(t, sortErr)
|
||||
require.NotNil(t, bookmarks)
|
||||
|
||||
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) {
|
||||
_, appErr = th.App.UpdateChannelBookmarkSortOrder(bookmark3.Id, channelId, int64(-1), "")
|
||||
assert.Error(t, appErr)
|
||||
_, appErr = th.App.UpdateChannelBookmarkSortOrder(bookmark3.Id, channelId, int64(5), "")
|
||||
assert.Error(t, appErr)
|
||||
})
|
||||
|
||||
t.Run("change order of bookmarks error when bookmark not found", func(t *testing.T) {
|
||||
_, appErr = th.App.UpdateChannelBookmarkSortOrder(model.NewId(), channelId, int64(0), "")
|
||||
assert.Error(t, appErr)
|
||||
})
|
||||
}
|
||||
@@ -1716,6 +1716,7 @@ func TestPatchChannelModerationsForChannel(t *testing.T) {
|
||||
createReactions := model.ChannelModeratedPermissions[1]
|
||||
manageMembers := model.ChannelModeratedPermissions[2]
|
||||
channelMentions := model.ChannelModeratedPermissions[3]
|
||||
manageBookmarks := model.ChannelModeratedPermissions[4]
|
||||
|
||||
nonChannelModeratedPermission := model.PermissionCreateBot.Id
|
||||
|
||||
@@ -1809,6 +1810,26 @@ func TestPatchChannelModerationsForChannel(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Removing manage bookmarks from members role",
|
||||
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
||||
{
|
||||
Name: &manageBookmarks,
|
||||
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewBool(false)},
|
||||
},
|
||||
},
|
||||
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
|
||||
manageBookmarks: {
|
||||
Members: &model.ChannelModeratedRole{Value: false, Enabled: true},
|
||||
},
|
||||
},
|
||||
RevertChannelModerationsPatch: []*model.ChannelModerationPatch{
|
||||
{
|
||||
Name: &manageBookmarks,
|
||||
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewBool(true)},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Removing create posts from guests role",
|
||||
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
||||
@@ -1881,6 +1902,18 @@ func TestPatchChannelModerationsForChannel(t *testing.T) {
|
||||
ShouldError: false,
|
||||
ShouldHaveNoChannelScheme: true,
|
||||
},
|
||||
{
|
||||
Name: "Removing manage bookmarks from guests role should not error",
|
||||
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
||||
{
|
||||
Name: &manageBookmarks,
|
||||
Roles: &model.ChannelModeratedRolesPatch{Guests: model.NewBool(false)},
|
||||
},
|
||||
},
|
||||
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{},
|
||||
ShouldError: false,
|
||||
ShouldHaveNoChannelScheme: true,
|
||||
},
|
||||
{
|
||||
Name: "Removing a permission that is not channel moderated should not error",
|
||||
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
||||
@@ -1981,6 +2014,12 @@ func TestPatchChannelModerationsForChannel(t *testing.T) {
|
||||
Members: model.NewBool(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: &manageBookmarks,
|
||||
Roles: &model.ChannelModeratedRolesPatch{
|
||||
Members: model.NewBool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{},
|
||||
ShouldHaveNoChannelScheme: true,
|
||||
@@ -2039,7 +2078,7 @@ func TestPatchChannelModerationsForChannel(t *testing.T) {
|
||||
if permission, found := tc.PermissionsModeratedByPatch[moderation.Name]; found && permission.Guests != nil {
|
||||
require.Equal(t, moderation.Roles.Guests.Value, permission.Guests.Value)
|
||||
require.Equal(t, moderation.Roles.Guests.Enabled, permission.Guests.Enabled)
|
||||
} else if moderation.Name == manageMembers {
|
||||
} else if moderation.Name == manageMembers || moderation.Name == "manage_bookmarks" {
|
||||
require.Empty(t, moderation.Roles.Guests)
|
||||
} else {
|
||||
require.Equal(t, moderation.Roles.Guests.Value, true)
|
||||
|
||||
@@ -704,6 +704,7 @@ func (t *UploadFileTask) init(a *App) {
|
||||
t.fileinfo.CreatorId = t.UserId
|
||||
t.fileinfo.CreateAt = t.Timestamp.UnixNano() / int64(time.Millisecond)
|
||||
t.fileinfo.Path = t.pathPrefix() + t.Name
|
||||
t.fileinfo.ChannelId = t.ChannelId
|
||||
|
||||
t.limitedInput = &io.LimitedReader{
|
||||
R: t.Input,
|
||||
@@ -950,6 +951,12 @@ func (t *UploadFileTask) postprocessImage(file io.Reader) {
|
||||
}
|
||||
|
||||
func (t UploadFileTask) pathPrefix() string {
|
||||
if t.UserId == model.BookmarkFileOwner {
|
||||
return model.BookmarkFileOwner +
|
||||
"/teams/" + t.TeamId +
|
||||
"/channels/" + t.ChannelId +
|
||||
"/" + t.fileinfo.Id + "/"
|
||||
}
|
||||
return t.Timestamp.Format("20060102") +
|
||||
"/teams/" + t.TeamId +
|
||||
"/channels/" + t.ChannelId +
|
||||
@@ -1003,6 +1010,9 @@ func (a *App) DoUploadFileExpectModification(c request.CTX, now time.Time, rawTe
|
||||
info.CreateAt = now.UnixNano() / int64(time.Millisecond)
|
||||
|
||||
pathPrefix := now.Format("20060102") + "/teams/" + teamID + "/channels/" + channelID + "/users/" + userID + "/" + info.Id + "/"
|
||||
if userID == model.BookmarkFileOwner {
|
||||
pathPrefix = model.BookmarkFileOwner + "/teams/" + teamID + "/channels/" + channelID + "/" + info.Id + "/"
|
||||
}
|
||||
info.Path = pathPrefix + filename
|
||||
|
||||
if info.IsImage() && !info.IsSvg() {
|
||||
|
||||
@@ -96,6 +96,16 @@ func TestDoUploadFile(t *testing.T) {
|
||||
|
||||
value = fmt.Sprintf("20090305/teams/%v/channels/%v/users/%v/%v/%v", teamID, channelID, userID, info4.Id, filename)
|
||||
assert.Equal(t, value, info4.Path, "stored file at incorrect path")
|
||||
|
||||
info5, err := th.App.DoUploadFile(th.Context, time.Date(2008, 3, 5, 1, 2, 3, 4, time.Local), teamID, channelID, model.BookmarkFileOwner, filename, data)
|
||||
require.Nil(t, err, "DoUploadFile should succeed with valid data")
|
||||
defer func() {
|
||||
th.App.Srv().Store().FileInfo().PermanentDelete(th.Context, info5.Id)
|
||||
th.App.RemoveFile(info3.Path)
|
||||
}()
|
||||
|
||||
value = fmt.Sprintf("%v/teams/%v/channels/%v/%v/%v", model.BookmarkFileOwner, teamID, channelID, info5.Id, filename)
|
||||
assert.Equal(t, value, info5.Path, "stored file at incorrect path")
|
||||
}
|
||||
|
||||
func TestUploadFile(t *testing.T) {
|
||||
|
||||
@@ -1994,6 +1994,28 @@ func (a *OpenTracingAppLayer) CreateChannel(c request.CTX, channel *model.Channe
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CreateChannelBookmark(c request.CTX, newBookmark *model.ChannelBookmark, connectionId string) (*model.ChannelBookmarkWithFileInfo, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateChannelBookmark")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.CreateChannelBookmark(c, newBookmark, connectionId)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CreateChannelScheme(c request.CTX, channel *model.Channel) (*model.Scheme, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateChannelScheme")
|
||||
@@ -3093,6 +3115,28 @@ func (a *OpenTracingAppLayer) DeleteChannel(c request.CTX, channel *model.Channe
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) DeleteChannelBookmark(bookmarkId string, connectionId string) (*model.ChannelBookmarkWithFileInfo, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DeleteChannelBookmark")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.DeleteChannelBookmark(bookmarkId, connectionId)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) DeleteChannelScheme(c request.CTX, channel *model.Channel) (*model.Channel, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DeleteChannelScheme")
|
||||
@@ -5138,6 +5182,28 @@ func (a *OpenTracingAppLayer) GetAuthorizedAppsForUser(userID string, page int,
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetBookmark(bookmarkId string, includeDeleted bool) (*model.ChannelBookmarkWithFileInfo, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetBookmark")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetBookmark(bookmarkId, includeDeleted)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetBot(rctx request.CTX, botUserId string, includeDeleted bool) (*model.Bot, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetBot")
|
||||
@@ -5248,6 +5314,28 @@ func (a *OpenTracingAppLayer) GetChannel(c request.CTX, channelID string) (*mode
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetChannelBookmarks(channelId string, since int64) ([]*model.ChannelBookmarkWithFileInfo, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetChannelBookmarks")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetChannelBookmarks(channelId, since)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetChannelByName(c request.CTX, channelName string, teamID string, includeDeleted bool) (*model.Channel, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetChannelByName")
|
||||
@@ -17562,6 +17650,50 @@ func (a *OpenTracingAppLayer) UpdateChannel(c request.CTX, channel *model.Channe
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) UpdateChannelBookmark(c request.CTX, updateBookmark *model.ChannelBookmarkWithFileInfo, connectionId string) (*model.UpdateChannelBookmarkResponse, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateChannelBookmark")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.UpdateChannelBookmark(c, updateBookmark, connectionId)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) UpdateChannelBookmarkSortOrder(bookmarkId string, channelId string, newIndex int64, connectionId string) ([]*model.ChannelBookmarkWithFileInfo, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateChannelBookmarkSortOrder")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.UpdateChannelBookmarkSortOrder(bookmarkId, channelId, newIndex, connectionId)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) UpdateChannelMemberNotifyProps(c request.CTX, data map[string]string, channelID string, userID string) (*model.ChannelMember, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateChannelMemberNotifyProps")
|
||||
|
||||
@@ -1156,6 +1156,31 @@ func (a *App) getAddOutgoingOAuthConnectionsPermissions() (permissionsMap, error
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (a *App) getAddChannelBookmarksPermissionsMigration() (permissionsMap, error) {
|
||||
transformations := []permissionTransformation{}
|
||||
|
||||
transformations = append(transformations, permissionTransformation{
|
||||
On: permissionOr(
|
||||
isRole(model.ChannelUserRoleId),
|
||||
isRole(model.ChannelAdminRoleId),
|
||||
isRole(model.TeamAdminRoleId),
|
||||
isRole(model.SystemAdminRoleId),
|
||||
),
|
||||
Add: []string{
|
||||
model.PermissionAddBookmarkPublicChannel.Id,
|
||||
model.PermissionEditBookmarkPublicChannel.Id,
|
||||
model.PermissionDeleteBookmarkPublicChannel.Id,
|
||||
model.PermissionOrderBookmarkPublicChannel.Id,
|
||||
model.PermissionAddBookmarkPrivateChannel.Id,
|
||||
model.PermissionEditBookmarkPrivateChannel.Id,
|
||||
model.PermissionDeleteBookmarkPrivateChannel.Id,
|
||||
model.PermissionOrderBookmarkPrivateChannel.Id,
|
||||
},
|
||||
})
|
||||
|
||||
return transformations, nil
|
||||
}
|
||||
|
||||
// DoPermissionsMigrations execute all the permissions migrations need by the current version.
|
||||
func (a *App) DoPermissionsMigrations() error {
|
||||
return a.Srv().doPermissionsMigrations()
|
||||
@@ -1202,6 +1227,7 @@ func (s *Server) doPermissionsMigrations() error {
|
||||
{Key: model.MigrationKeyAddReadChannelContentPermissions, Migration: a.getAddChannelReadContentPermissions},
|
||||
{Key: model.MigrationKeyAddIPFilteringPermissions, Migration: a.getAddIPFilterPermissionsMigration},
|
||||
{Key: model.MigrationKeyAddOutgoingOAuthConnectionsPermissions, Migration: a.getAddOutgoingOAuthConnectionsPermissions},
|
||||
{Key: model.MigrationKeyAddChannelBookmarksPermissions, Migration: a.getAddChannelBookmarksPermissionsMigration},
|
||||
}
|
||||
|
||||
roles, err := s.Store().Role().GetAll()
|
||||
|
||||
@@ -236,6 +236,8 @@ channels/db/migrations/mysql/000118_create_index_poststats.down.sql
|
||||
channels/db/migrations/mysql/000118_create_index_poststats.up.sql
|
||||
channels/db/migrations/mysql/000119_msteams_shared_channels_opts.down.sql
|
||||
channels/db/migrations/mysql/000119_msteams_shared_channels_opts.up.sql
|
||||
channels/db/migrations/mysql/000120_create_channelbookmarks_table.down.sql
|
||||
channels/db/migrations/mysql/000120_create_channelbookmarks_table.up.sql
|
||||
channels/db/migrations/postgres/000001_create_teams.down.sql
|
||||
channels/db/migrations/postgres/000001_create_teams.up.sql
|
||||
channels/db/migrations/postgres/000002_create_team_members.down.sql
|
||||
@@ -472,3 +474,5 @@ channels/db/migrations/postgres/000118_create_index_poststats.down.sql
|
||||
channels/db/migrations/postgres/000118_create_index_poststats.up.sql
|
||||
channels/db/migrations/postgres/000119_msteams_shared_channels_opts.down.sql
|
||||
channels/db/migrations/postgres/000119_msteams_shared_channels_opts.up.sql
|
||||
channels/db/migrations/postgres/000120_create_channelbookmarks_table.down.sql
|
||||
channels/db/migrations/postgres/000120_create_channelbookmarks_table.up.sql
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS ChannelBookmarks;
|
||||
@@ -0,0 +1,21 @@
|
||||
CREATE TABLE IF NOT EXISTS ChannelBookmarks (
|
||||
Id varchar(26) NOT NULL,
|
||||
OwnerId varchar(26) NOT NULL,
|
||||
ChannelId varchar(26) NOT NULL,
|
||||
FileInfoId varchar(26) DEFAULT NULL,
|
||||
CreateAt bigint(20) DEFAULT 0,
|
||||
UpdateAt bigint(20) DEFAULT 0,
|
||||
DeleteAt bigint(20) DEFAULT 0,
|
||||
DisplayName text,
|
||||
SortOrder bigint(20) DEFAULT 0,
|
||||
LinkUrl text DEFAULT NULL,
|
||||
ImageUrl text DEFAULT NULL,
|
||||
Emoji varchar(64) DEFAULT NULL,
|
||||
Type ENUM('link', 'file'),
|
||||
OriginalId varchar(26) DEFAULT NULL,
|
||||
ParentId varchar(26) DEFAULT NULL,
|
||||
PRIMARY KEY (Id),
|
||||
KEY idx_channelbookmarks_channelid (ChannelId),
|
||||
KEY idx_channelbookmarks_update_at (UpdateAt),
|
||||
KEY idx_channelbookmarks_delete_at (DeleteAt)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
@@ -0,0 +1,18 @@
|
||||
DROP INDEX IF EXISTS idx_channelbookmarks_channelid;
|
||||
DROP INDEX IF EXISTS idx_channelbookmarks_update_at;
|
||||
DROP INDEX IF EXISTS idx_channelbookmarks_delete_at;
|
||||
|
||||
DROP TABLE IF EXISTS channelbookmarks;
|
||||
|
||||
DO
|
||||
$$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT * FROM pg_type typ
|
||||
INNER JOIN pg_namespace nsp ON nsp.oid = typ.typnamespace
|
||||
WHERE nsp.nspname = current_schema()
|
||||
AND typ.typname = 'channel_bookmark_type') THEN
|
||||
DROP TYPE channel_bookmark_type;
|
||||
END IF;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
@@ -0,0 +1,34 @@
|
||||
DO
|
||||
$$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT * FROM pg_type typ
|
||||
INNER JOIN pg_namespace nsp ON nsp.oid = typ.typnamespace
|
||||
WHERE nsp.nspname = current_schema()
|
||||
AND typ.typname = 'channel_bookmark_type') THEN
|
||||
CREATE TYPE channel_bookmark_type AS ENUM ('link', 'file');
|
||||
END IF;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channelbookmarks (
|
||||
id varchar(26) PRIMARY KEY,
|
||||
ownerid varchar(26) NOT NULL,
|
||||
channelid varchar(26) NOT NULL,
|
||||
fileinfoid varchar(26) DEFAULT NULL,
|
||||
createat bigint DEFAULT 0,
|
||||
updateat bigint DEFAULT 0,
|
||||
deleteat bigint DEFAULT 0,
|
||||
displayname text DEFAULT '',
|
||||
sortorder integer DEFAULT 0,
|
||||
linkurl text DEFAULT NULL,
|
||||
imageurl text DEFAULT NULL,
|
||||
emoji varchar(64) DEFAULT NULL,
|
||||
type channel_bookmark_type DEFAULT 'link',
|
||||
originalid varchar(26) DEFAULT NULL,
|
||||
parentid varchar(26) DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_channelbookmarks_channelid ON channelbookmarks (channelid);
|
||||
CREATE INDEX IF NOT EXISTS idx_channelbookmarks_update_at ON channelbookmarks (updateat);
|
||||
CREATE INDEX IF NOT EXISTS idx_channelbookmarks_delete_at ON channelbookmarks (deleteat);
|
||||
@@ -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}
|
||||
|
||||
@@ -75,6 +75,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
|
||||
systemStore.On("GetByName", model.MigrationKeyDeleteOrphanDrafts).Return(&model.System{Name: model.MigrationKeyDeleteOrphanDrafts, Value: "true"}, nil)
|
||||
systemStore.On("GetByName", model.MigrationKeyAddIPFilteringPermissions).Return(&model.System{Name: model.MigrationKeyAddIPFilteringPermissions, Value: "true"}, nil)
|
||||
systemStore.On("GetByName", model.MigrationKeyAddOutgoingOAuthConnectionsPermissions).Return(&model.System{Name: model.MigrationKeyAddOutgoingOAuthConnectionsPermissions, Value: "true"}, nil)
|
||||
systemStore.On("GetByName", model.MigrationKeyAddChannelBookmarksPermissions).Return(&model.System{Name: model.MigrationKeyAddChannelBookmarksPermissions, Value: "true"}, nil)
|
||||
systemStore.On("GetByName", "CustomGroupAdminRoleCreationMigrationComplete").Return(&model.System{Name: model.MigrationKeyAddPlayboosksManageRolesPermissions, Value: "true"}, nil)
|
||||
systemStore.On("GetByName", "products_boards").Return(&model.System{Name: "products_boards", Value: "true"}, nil)
|
||||
systemStore.On("GetByName", "elasticsearch_fix_channel_index_migration").Return(&model.System{Name: "elasticsearch_fix_channel_index_migration", Value: "true"}, nil)
|
||||
|
||||
@@ -87,6 +87,19 @@ func StringSliceDiff(a, b []string) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
func InsertElementToSliceAtIndex[T comparable](slice []T, element T, index int) []T {
|
||||
if len(slice) == index {
|
||||
return append(slice, element)
|
||||
}
|
||||
slice = append(slice[:index+1], slice[index:]...)
|
||||
slice[index] = element
|
||||
return slice
|
||||
}
|
||||
|
||||
func RemoveElementFromSliceAtIndex[T comparable](slice []T, index int) []T {
|
||||
return append(slice[:index], slice[index+1:]...)
|
||||
}
|
||||
|
||||
func GetIPAddress(r *http.Request, trustedProxyIPHeader []string) string {
|
||||
address := ""
|
||||
|
||||
|
||||
@@ -94,6 +94,10 @@ type Params struct {
|
||||
IncludeChannelMemberCount string
|
||||
OutgoingOAuthConnectionID string
|
||||
|
||||
//Bookmarks
|
||||
ChannelBookmarkId string
|
||||
BookmarksSince int64
|
||||
|
||||
// Cloud
|
||||
InvoiceId string
|
||||
}
|
||||
@@ -146,6 +150,7 @@ func ParamsFromRequest(r *http.Request) *Params {
|
||||
params.RemoteId = props["remote_id"]
|
||||
params.InvoiceId = props["invoice_id"]
|
||||
params.OutgoingOAuthConnectionID = props["outgoing_oauth_connection_id"]
|
||||
params.ChannelBookmarkId = props["bookmark_id"]
|
||||
params.Scope = query.Get("scope")
|
||||
|
||||
if val, err := strconv.Atoi(query.Get("page")); err != nil || val < 0 {
|
||||
@@ -240,6 +245,12 @@ func ParamsFromRequest(r *http.Request) *Params {
|
||||
|
||||
params.FilterHasMember = query.Get("filter_has_member")
|
||||
|
||||
if val, err := strconv.ParseInt(query.Get("bookmarks_since"), 10, 64); err != nil || val < 0 {
|
||||
params.BookmarksSince = 0
|
||||
} else {
|
||||
params.BookmarksSince = val
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
|
||||
@@ -459,6 +459,48 @@ func TestParamsFromRequest(t *testing.T) {
|
||||
LimitAfter: LimitDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
"include channel bookmarks",
|
||||
mustURL("/?include_bookmarks=true"),
|
||||
nil,
|
||||
&Params{
|
||||
BookmarksSince: 0,
|
||||
|
||||
LimitAfter: LimitDefault,
|
||||
|
||||
PerPage: PerPageDefault,
|
||||
LogsPerPage: LogsPerPageDefault,
|
||||
LimitBefore: LimitDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
"include channel bookmarks with negative bookmark since",
|
||||
mustURL("/?include_bookmarks=true&bookmarks_since=-1"),
|
||||
nil,
|
||||
&Params{
|
||||
BookmarksSince: 0,
|
||||
|
||||
LimitAfter: LimitDefault,
|
||||
|
||||
PerPage: PerPageDefault,
|
||||
LogsPerPage: LogsPerPageDefault,
|
||||
LimitBefore: LimitDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
"include channel bookmarks with bookmark since",
|
||||
mustURL("/?include_bookmarks=true&bookmarks_since=123456789"),
|
||||
nil,
|
||||
&Params{
|
||||
BookmarksSince: 123456789,
|
||||
|
||||
LimitAfter: LimitDefault,
|
||||
|
||||
PerPage: PerPageDefault,
|
||||
LogsPerPage: LogsPerPageDefault,
|
||||
LimitBefore: LimitDefault,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
|
||||
@@ -235,7 +235,18 @@ func (s *MmctlUnitTestSuite) TestResetPermissionsCmd() {
|
||||
Permissions: []string{"view_foos", "delete_bars"},
|
||||
}
|
||||
|
||||
expectedPermissions := []string{"manage_channel_roles", "use_group_mentions"}
|
||||
expectedPermissions := []string{
|
||||
"manage_channel_roles",
|
||||
"use_group_mentions",
|
||||
"add_bookmark_public_channel",
|
||||
"edit_bookmark_public_channel",
|
||||
"delete_bookmark_public_channel",
|
||||
"order_bookmark_public_channel",
|
||||
"add_bookmark_private_channel",
|
||||
"edit_bookmark_private_channel",
|
||||
"delete_bookmark_private_channel",
|
||||
"order_bookmark_private_channel",
|
||||
}
|
||||
expectedPatch := &model.RolePatch{
|
||||
Permissions: &expectedPermissions,
|
||||
}
|
||||
|
||||
@@ -231,6 +231,58 @@
|
||||
"id": "api.channel.add_user_to_channel.type.app_error",
|
||||
"translation": "Can not add user to this channel type."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.bookmark.channel_bookmark.license.error",
|
||||
"translation": "Your license does not support channel bookmarks."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.bookmark.create_channel_bookmark.direct_or_group_channels.forbidden.app_error",
|
||||
"translation": "User is not allowed to create a channel bookmark."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.bookmark.create_channel_bookmark.direct_or_group_channels_by_guests.forbidden.app_error",
|
||||
"translation": "Failed to create the channel bookmark."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.bookmark.create_channel_bookmark.forbidden.app_error",
|
||||
"translation": "Failed to create the channel bookmark."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.bookmark.delete_channel_bookmark.direct_or_group_channels.forbidden.app_error",
|
||||
"translation": "Failed to delete the channel bookmark."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.bookmark.delete_channel_bookmark.direct_or_group_channels_by_guests.forbidden.app_error",
|
||||
"translation": "Failed to delete the channel bookmark."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.bookmark.delete_channel_bookmark.forbidden.app_error",
|
||||
"translation": "Failed to delete the channel bookmark."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.bookmark.update_channel_bookmark.direct_or_group_channels.forbidden.app_error",
|
||||
"translation": "Failed to update the channel bookmark."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.bookmark.update_channel_bookmark.direct_or_group_channels_by_guests.forbidden.app_error",
|
||||
"translation": "Failed to update the channel bookmark."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.bookmark.update_channel_bookmark.forbidden.app_error",
|
||||
"translation": "Failed to update the channel bookmark."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.bookmark.update_channel_bookmark_sort_order.direct_or_group_channels.forbidden.app_error",
|
||||
"translation": "Failed to update the channel bookmark's sort order."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.bookmark.update_channel_bookmark_sort_order.direct_or_group_channels_by_guests.forbidden.app_error",
|
||||
"translation": "Failed to update the channel bookmark's sort order."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.bookmark.update_channel_bookmark_sort_order.forbidden.app_error",
|
||||
"translation": "Failed to update the channel bookmark's sort order."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.change_channel_privacy.private_to_public",
|
||||
"translation": "This channel has been converted to a Public Channel and can be joined by any team member."
|
||||
@@ -4638,6 +4690,38 @@
|
||||
"id": "app.channel.autofollow.app_error",
|
||||
"translation": "Failed to update thread membership for mentioned user"
|
||||
},
|
||||
{
|
||||
"id": "app.channel.bookmark.delete.app_error",
|
||||
"translation": "Could not delete bookmark."
|
||||
},
|
||||
{
|
||||
"id": "app.channel.bookmark.get.app_error",
|
||||
"translation": "Could not get bookmark."
|
||||
},
|
||||
{
|
||||
"id": "app.channel.bookmark.get_existing.app_err",
|
||||
"translation": "Could not get existing bookmark to update."
|
||||
},
|
||||
{
|
||||
"id": "app.channel.bookmark.save.app_error",
|
||||
"translation": "Could not save bookmark."
|
||||
},
|
||||
{
|
||||
"id": "app.channel.bookmark.update.app_error",
|
||||
"translation": "Could not update bookmark."
|
||||
},
|
||||
{
|
||||
"id": "app.channel.bookmark.update_sort.app_error",
|
||||
"translation": "Could not sort the bookmark."
|
||||
},
|
||||
{
|
||||
"id": "app.channel.bookmark.update_sort.invalid_input.app_error",
|
||||
"translation": "Could not sort the bookmark. Invalid input."
|
||||
},
|
||||
{
|
||||
"id": "app.channel.bookmark.update_sort.missing_bookmark.app_error",
|
||||
"translation": "Could not sort the bookmark. Not found."
|
||||
},
|
||||
{
|
||||
"id": "app.channel.clear_all_custom_role_assignments.select.app_error",
|
||||
"translation": "Failed to retrieve the channel members."
|
||||
@@ -8438,6 +8522,58 @@
|
||||
"id": "model.channel.is_valid.update_at.app_error",
|
||||
"translation": "Update at must be a valid time."
|
||||
},
|
||||
{
|
||||
"id": "model.channel_bookmark.is_valid.channel_id.app_error",
|
||||
"translation": "Invalid channel id."
|
||||
},
|
||||
{
|
||||
"id": "model.channel_bookmark.is_valid.create_at.app_error",
|
||||
"translation": "Create at must be a valid time."
|
||||
},
|
||||
{
|
||||
"id": "model.channel_bookmark.is_valid.display_name.app_error",
|
||||
"translation": "Display name missing."
|
||||
},
|
||||
{
|
||||
"id": "model.channel_bookmark.is_valid.file_id.missing_or_invalid.app_error",
|
||||
"translation": "File id is missing or invalid."
|
||||
},
|
||||
{
|
||||
"id": "model.channel_bookmark.is_valid.id.app_error",
|
||||
"translation": "Invalid Id."
|
||||
},
|
||||
{
|
||||
"id": "model.channel_bookmark.is_valid.image_url.app_error",
|
||||
"translation": "Invalid image url."
|
||||
},
|
||||
{
|
||||
"id": "model.channel_bookmark.is_valid.link_file.app_error",
|
||||
"translation": "Cannot set a link and a file in the same bookmark."
|
||||
},
|
||||
{
|
||||
"id": "model.channel_bookmark.is_valid.link_url.missing_or_invalid.app_error",
|
||||
"translation": "Link url is missing or invalid."
|
||||
},
|
||||
{
|
||||
"id": "model.channel_bookmark.is_valid.original_id.app_error",
|
||||
"translation": "Invalid original id."
|
||||
},
|
||||
{
|
||||
"id": "model.channel_bookmark.is_valid.owner_id.app_error",
|
||||
"translation": "Invalid owner id."
|
||||
},
|
||||
{
|
||||
"id": "model.channel_bookmark.is_valid.parent_id.app_error",
|
||||
"translation": "Invalid parent id."
|
||||
},
|
||||
{
|
||||
"id": "model.channel_bookmark.is_valid.type.app_error",
|
||||
"translation": "Invalid type."
|
||||
},
|
||||
{
|
||||
"id": "model.channel_bookmark.is_valid.update_at.app_error",
|
||||
"translation": "Update at must be a valid time."
|
||||
},
|
||||
{
|
||||
"id": "model.channel_member.is_valid.channel_auto_follow_threads_value.app_error",
|
||||
"translation": "Invalid channel-auto-follow-threads value."
|
||||
|
||||
322
server/public/model/channel_bookmark.go
Обычный файл
322
server/public/model/channel_bookmark.go
Обычный файл
@@ -0,0 +1,322 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ChannelBookmarkType string
|
||||
|
||||
const (
|
||||
ChannelBookmarkLink ChannelBookmarkType = "link"
|
||||
ChannelBookmarkFile ChannelBookmarkType = "file"
|
||||
BookmarkFileOwner = "bookmark"
|
||||
MaxBookmarksPerChannel = 50
|
||||
)
|
||||
|
||||
type ChannelBookmark struct {
|
||||
Id string `json:"id"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
UpdateAt int64 `json:"update_at"`
|
||||
DeleteAt int64 `json:"delete_at"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
OwnerId string `json:"owner_id"`
|
||||
FileId string `json:"file_id"`
|
||||
DisplayName string `json:"display_name"`
|
||||
SortOrder int64 `json:"sort_order"`
|
||||
LinkUrl string `json:"link_url,omitempty"`
|
||||
ImageUrl string `json:"image_url,omitempty"`
|
||||
Emoji string `json:"emoji,omitempty"`
|
||||
Type ChannelBookmarkType `json:"type"`
|
||||
OriginalId string `json:"original_id,omitempty"`
|
||||
ParentId string `json:"parent_id,omitempty"`
|
||||
}
|
||||
|
||||
func (o *ChannelBookmark) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"id": o.Id,
|
||||
"create_at": o.CreateAt,
|
||||
"update_at": o.UpdateAt,
|
||||
"delete_at": o.DeleteAt,
|
||||
"channel_id": o.ChannelId,
|
||||
"owner_id": o.OwnerId,
|
||||
"file_id": o.FileId,
|
||||
"type": o.Type,
|
||||
"original_id": o.OriginalId,
|
||||
"parent_id": o.ParentId,
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a shallow copy of the channel bookmark.
|
||||
func (o *ChannelBookmark) Clone() *ChannelBookmark {
|
||||
bCopy := *o
|
||||
return &bCopy
|
||||
}
|
||||
|
||||
// SetOriginal generates a new bookmark copying the data of the
|
||||
// receiver bookmark, resets its timestamps and main ID, updates its
|
||||
// OriginalId and sets the owner to the ID passed as a parameter
|
||||
func (o *ChannelBookmark) SetOriginal(newOwnerId string) *ChannelBookmark {
|
||||
bCopy := *o
|
||||
bCopy.Id = ""
|
||||
bCopy.CreateAt = 0
|
||||
bCopy.DeleteAt = 0
|
||||
bCopy.UpdateAt = 0
|
||||
bCopy.OriginalId = o.Id
|
||||
bCopy.OwnerId = newOwnerId
|
||||
return &bCopy
|
||||
}
|
||||
|
||||
func (o *ChannelBookmark) IsValid() *AppError {
|
||||
if !IsValidId(o.Id) {
|
||||
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.id.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if o.CreateAt == 0 {
|
||||
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if o.UpdateAt == 0 {
|
||||
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if !IsValidId(o.ChannelId) {
|
||||
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if !IsValidId(o.OwnerId) {
|
||||
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.owner_id.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if o.DisplayName == "" {
|
||||
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.display_name.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if !(o.Type == ChannelBookmarkFile || o.Type == ChannelBookmarkLink) {
|
||||
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.type.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if o.Type == ChannelBookmarkLink && (o.LinkUrl == "" || !IsValidHTTPURL(o.LinkUrl)) {
|
||||
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.link_url.missing_or_invalid.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if o.Type == ChannelBookmarkLink && o.ImageUrl != "" && !IsValidHTTPURL(o.ImageUrl) {
|
||||
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.image_url.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if o.Type == ChannelBookmarkFile && (o.FileId == "" || !IsValidId(o.FileId)) {
|
||||
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.file_id.missing_or_invalid.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if o.ImageUrl != "" && o.FileId != "" {
|
||||
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.link_file.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if o.OriginalId != "" && !IsValidId(o.OriginalId) {
|
||||
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.original_id.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if o.ParentId != "" && !IsValidId(o.ParentId) {
|
||||
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.parent_id.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ChannelBookmark) PreSave() {
|
||||
if o.Id == "" {
|
||||
o.Id = NewId()
|
||||
}
|
||||
|
||||
o.DisplayName = SanitizeUnicode(o.DisplayName)
|
||||
if o.CreateAt == 0 {
|
||||
o.CreateAt = GetMillis()
|
||||
}
|
||||
o.UpdateAt = o.CreateAt
|
||||
}
|
||||
|
||||
func (o *ChannelBookmark) PreUpdate() {
|
||||
o.UpdateAt = GetMillis()
|
||||
o.DisplayName = SanitizeUnicode(o.DisplayName)
|
||||
}
|
||||
|
||||
func (o *ChannelBookmark) ToBookmarkWithFileInfo(f *FileInfo) *ChannelBookmarkWithFileInfo {
|
||||
bwf := ChannelBookmarkWithFileInfo{
|
||||
ChannelBookmark: &ChannelBookmark{
|
||||
Id: o.Id,
|
||||
CreateAt: o.CreateAt,
|
||||
UpdateAt: o.UpdateAt,
|
||||
DeleteAt: o.DeleteAt,
|
||||
ChannelId: o.ChannelId,
|
||||
OwnerId: o.OwnerId,
|
||||
FileId: o.FileId,
|
||||
DisplayName: o.DisplayName,
|
||||
SortOrder: o.SortOrder,
|
||||
LinkUrl: o.LinkUrl,
|
||||
ImageUrl: o.ImageUrl,
|
||||
Emoji: o.Emoji,
|
||||
Type: o.Type,
|
||||
OriginalId: o.OriginalId,
|
||||
ParentId: o.ParentId,
|
||||
},
|
||||
}
|
||||
|
||||
if f != nil && f.Id != "" {
|
||||
bwf.FileInfo = f
|
||||
}
|
||||
|
||||
return &bwf
|
||||
}
|
||||
|
||||
type ChannelBookmarkPatch struct {
|
||||
FileId *string `json:"file_id"`
|
||||
DisplayName *string `json:"display_name"`
|
||||
SortOrder *int64 `json:"sort_order"`
|
||||
LinkUrl *string `json:"link_url,omitempty"`
|
||||
ImageUrl *string `json:"image_url,omitempty"`
|
||||
Emoji *string `json:"emoji,omitempty"`
|
||||
}
|
||||
|
||||
func (o *ChannelBookmarkPatch) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"file_id": o.FileId,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *ChannelBookmark) Patch(patch *ChannelBookmarkPatch) {
|
||||
if patch.FileId != nil {
|
||||
o.FileId = *patch.FileId
|
||||
}
|
||||
|
||||
if patch.DisplayName != nil {
|
||||
o.DisplayName = *patch.DisplayName
|
||||
}
|
||||
if patch.SortOrder != nil {
|
||||
o.SortOrder = *patch.SortOrder
|
||||
}
|
||||
if patch.LinkUrl != nil {
|
||||
o.LinkUrl = *patch.LinkUrl
|
||||
}
|
||||
if patch.ImageUrl != nil {
|
||||
o.ImageUrl = *patch.ImageUrl
|
||||
}
|
||||
if patch.Emoji != nil {
|
||||
o.Emoji = *patch.Emoji
|
||||
}
|
||||
}
|
||||
|
||||
type ChannelBookmarkWithFileInfo struct {
|
||||
*ChannelBookmark
|
||||
FileInfo *FileInfo `json:"file,omitempty"`
|
||||
}
|
||||
|
||||
func (o *ChannelBookmarkWithFileInfo) Auditable() map[string]interface{} {
|
||||
a := o.ChannelBookmark.Auditable()
|
||||
if o.FileInfo != nil {
|
||||
a["file"] = o.FileInfo.Auditable()
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// Clone returns a shallow copy of the channel bookmark with file info.
|
||||
func (o *ChannelBookmarkWithFileInfo) Clone() *ChannelBookmarkWithFileInfo {
|
||||
bCopy := *o
|
||||
return &bCopy
|
||||
}
|
||||
|
||||
type ChannelWithBookmarks struct {
|
||||
*Channel
|
||||
Bookmarks []*ChannelBookmarkWithFileInfo `json:"bookmarks,omitempty"`
|
||||
}
|
||||
|
||||
type ChannelWithTeamDataAndBookmarks struct {
|
||||
*ChannelWithTeamData
|
||||
Bookmarks []*ChannelBookmarkWithFileInfo `json:"bookmarks,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateChannelBookmarkResponse struct {
|
||||
Updated *ChannelBookmarkWithFileInfo `json:"updated,omitempty"`
|
||||
Deleted *ChannelBookmarkWithFileInfo `json:"deleted,omitempty"`
|
||||
}
|
||||
|
||||
func (o *UpdateChannelBookmarkResponse) Auditable() map[string]any {
|
||||
a := map[string]any{}
|
||||
if o.Updated != nil {
|
||||
a["updated"] = o.Updated.Auditable()
|
||||
}
|
||||
if o.Deleted != nil {
|
||||
a["updated"] = o.Deleted.Auditable()
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
type ChannelBookmarkAndFileInfo struct {
|
||||
Id string
|
||||
CreateAt int64
|
||||
UpdateAt int64
|
||||
DeleteAt int64
|
||||
ChannelId string
|
||||
OwnerId string
|
||||
FileInfoId string
|
||||
DisplayName string
|
||||
SortOrder int64
|
||||
LinkUrl string
|
||||
ImageUrl string
|
||||
Emoji string
|
||||
Type ChannelBookmarkType
|
||||
OriginalId string
|
||||
ParentId string
|
||||
FileId string
|
||||
FileName string
|
||||
Extension string
|
||||
Size int64
|
||||
MimeType string
|
||||
Width int
|
||||
Height int
|
||||
HasPreviewImage bool
|
||||
MiniPreview *[]byte
|
||||
}
|
||||
|
||||
func (o *ChannelBookmarkAndFileInfo) ToChannelBookmarkWithFileInfo() *ChannelBookmarkWithFileInfo {
|
||||
bwf := &ChannelBookmarkWithFileInfo{
|
||||
ChannelBookmark: &ChannelBookmark{
|
||||
Id: o.Id,
|
||||
CreateAt: o.CreateAt,
|
||||
UpdateAt: o.UpdateAt,
|
||||
DeleteAt: o.DeleteAt,
|
||||
ChannelId: o.ChannelId,
|
||||
OwnerId: o.OwnerId,
|
||||
FileId: o.FileInfoId,
|
||||
DisplayName: o.DisplayName,
|
||||
SortOrder: o.SortOrder,
|
||||
LinkUrl: o.LinkUrl,
|
||||
ImageUrl: o.ImageUrl,
|
||||
Emoji: o.Emoji,
|
||||
Type: o.Type,
|
||||
OriginalId: o.OriginalId,
|
||||
ParentId: o.ParentId,
|
||||
},
|
||||
}
|
||||
|
||||
if o.FileInfoId != "" && o.FileId != "" {
|
||||
miniPreview := o.MiniPreview
|
||||
if len(*miniPreview) == 0 {
|
||||
miniPreview = nil
|
||||
}
|
||||
bwf.FileInfo = &FileInfo{
|
||||
Id: o.FileId,
|
||||
Name: o.FileName,
|
||||
Extension: o.Extension,
|
||||
Size: o.Size,
|
||||
MimeType: o.MimeType,
|
||||
Width: o.Width,
|
||||
Height: o.Height,
|
||||
HasPreviewImage: o.HasPreviewImage,
|
||||
MiniPreview: miniPreview,
|
||||
}
|
||||
}
|
||||
return bwf
|
||||
}
|
||||
544
server/public/model/channel_bookmark_test.go
Обычный файл
544
server/public/model/channel_bookmark_test.go
Обычный файл
@@ -0,0 +1,544 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestChannelBookmarkIsValid(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Description string
|
||||
Bookmark *ChannelBookmark
|
||||
ExpectedIsValid bool
|
||||
}{
|
||||
{
|
||||
"nil bookmark",
|
||||
&ChannelBookmark{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark without create at timestamp",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
OwnerId: NewId(),
|
||||
ChannelId: "",
|
||||
FileId: "",
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkLink,
|
||||
CreateAt: 0,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark without update at timestamp",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
OwnerId: NewId(),
|
||||
ChannelId: "",
|
||||
FileId: "",
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkLink,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 0,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark with missing channel id",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
OwnerId: NewId(),
|
||||
ChannelId: "",
|
||||
FileId: "",
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkLink,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark with invalid channel id",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
OwnerId: NewId(),
|
||||
ChannelId: "invalid",
|
||||
FileId: "",
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkLink,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark with missing owner id",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: "",
|
||||
FileId: "",
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkLink,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark with invalid user id",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: "invalid",
|
||||
FileId: "",
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkLink,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark with missing displayname",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: NewId(),
|
||||
FileId: "",
|
||||
DisplayName: "",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkLink,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark with missing type",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: NewId(),
|
||||
FileId: "",
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: "",
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark with invalid type",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: NewId(),
|
||||
FileId: "",
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: "invalid",
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark of type link with missing link url",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: NewId(),
|
||||
FileId: "",
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkLink,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark of type link with invalid link url",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: NewId(),
|
||||
FileId: "",
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "invalid",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkLink,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark of type link with valid link url",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: NewId(),
|
||||
FileId: "",
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "https://mattermost.com",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkLink,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"bookmark of type link with empty image url",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: NewId(),
|
||||
FileId: "",
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "https://mattermost.com",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkLink,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"bookmark of type link with invalid image url",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: NewId(),
|
||||
FileId: "",
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "https://mattermost.com",
|
||||
ImageUrl: "invalid",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkLink,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark of type link with invalid image url",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: NewId(),
|
||||
FileId: "",
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "https://mattermost.com",
|
||||
ImageUrl: "https://mattermost.com/some-image-without-extension", // we don't care if the URL is an actual image as the client should handle the error
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkLink,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"bookmark of type file with missing file id",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: NewId(),
|
||||
FileId: "",
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkFile,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark of type file with invalid file id",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: NewId(),
|
||||
FileId: "invalid",
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkFile,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark of type file with valid file id",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: NewId(),
|
||||
FileId: NewId(),
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkFile,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"bookmark of type file with invalid original id",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: NewId(),
|
||||
FileId: NewId(),
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkFile,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
OriginalId: "invalid",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark of type file with invalid parent id",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: NewId(),
|
||||
FileId: NewId(),
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkFile,
|
||||
CreateAt: 2,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 4,
|
||||
ParentId: "invalid",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark of type link with a file Id attached",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
OwnerId: NewId(),
|
||||
ChannelId: "",
|
||||
FileId: NewId(),
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "http://somelink",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkLink,
|
||||
CreateAt: 0,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 0,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"bookmark of type file with a url",
|
||||
&ChannelBookmark{
|
||||
Id: NewId(),
|
||||
OwnerId: NewId(),
|
||||
ChannelId: "",
|
||||
FileId: NewId(),
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "http://somelink",
|
||||
ImageUrl: "",
|
||||
Emoji: "",
|
||||
Type: ChannelBookmarkFile,
|
||||
CreateAt: 0,
|
||||
UpdateAt: 3,
|
||||
DeleteAt: 0,
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
if testCase.ExpectedIsValid {
|
||||
require.Nil(t, testCase.Bookmark.IsValid())
|
||||
} else {
|
||||
require.NotNil(t, testCase.Bookmark.IsValid())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelBookmarkPreSave(t *testing.T) {
|
||||
bookmark := &ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: NewId(),
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: ChannelBookmarkLink,
|
||||
DeleteAt: 0,
|
||||
}
|
||||
|
||||
originalBookmark := &ChannelBookmark{
|
||||
Id: bookmark.Id,
|
||||
ChannelId: bookmark.ChannelId,
|
||||
OwnerId: bookmark.OwnerId,
|
||||
DisplayName: bookmark.DisplayName,
|
||||
SortOrder: bookmark.SortOrder,
|
||||
LinkUrl: bookmark.LinkUrl,
|
||||
Type: bookmark.Type,
|
||||
DeleteAt: bookmark.DeleteAt,
|
||||
}
|
||||
|
||||
bookmark.PreSave()
|
||||
assert.NotEqual(t, 0, bookmark.CreateAt)
|
||||
assert.NotEqual(t, 0, bookmark.UpdateAt)
|
||||
|
||||
originalBookmark.CreateAt = bookmark.CreateAt
|
||||
originalBookmark.UpdateAt = bookmark.UpdateAt
|
||||
assert.Equal(t, originalBookmark, bookmark)
|
||||
}
|
||||
|
||||
func TestChannelBookmarkPreUpdate(t *testing.T) {
|
||||
bookmark := &ChannelBookmark{
|
||||
Id: NewId(),
|
||||
ChannelId: NewId(),
|
||||
OwnerId: NewId(),
|
||||
DisplayName: "display name",
|
||||
SortOrder: 0,
|
||||
LinkUrl: "https://mattermost.com",
|
||||
Type: ChannelBookmarkLink,
|
||||
CreateAt: 2,
|
||||
DeleteAt: 0,
|
||||
}
|
||||
|
||||
originalBookmark := &ChannelBookmark{
|
||||
Id: bookmark.Id,
|
||||
ChannelId: bookmark.ChannelId,
|
||||
OwnerId: bookmark.OwnerId,
|
||||
DisplayName: bookmark.DisplayName,
|
||||
SortOrder: bookmark.SortOrder,
|
||||
LinkUrl: bookmark.LinkUrl,
|
||||
Type: bookmark.Type,
|
||||
DeleteAt: bookmark.DeleteAt,
|
||||
}
|
||||
|
||||
bookmark.PreSave()
|
||||
assert.NotEqual(t, 0, bookmark.UpdateAt)
|
||||
|
||||
originalBookmark.CreateAt = bookmark.CreateAt
|
||||
originalBookmark.UpdateAt = bookmark.UpdateAt
|
||||
assert.Equal(t, originalBookmark, bookmark)
|
||||
|
||||
bookmark.PreUpdate()
|
||||
assert.Greater(t, bookmark.UpdateAt, originalBookmark.UpdateAt)
|
||||
}
|
||||
|
||||
func TestChannelBookmarkPatch(t *testing.T) {
|
||||
p := &ChannelBookmarkPatch{
|
||||
DisplayName: NewString(NewId()),
|
||||
SortOrder: NewInt64(1),
|
||||
LinkUrl: NewString(NewId()),
|
||||
}
|
||||
|
||||
b := ChannelBookmark{
|
||||
Id: NewId(),
|
||||
DisplayName: NewId(),
|
||||
Type: ChannelBookmarkLink, // should not update
|
||||
LinkUrl: NewId(),
|
||||
}
|
||||
b.Patch(p)
|
||||
|
||||
require.Empty(t, b.FileId)
|
||||
require.Equal(t, *p.DisplayName, b.DisplayName)
|
||||
require.Equal(t, *p.SortOrder, b.SortOrder)
|
||||
require.Equal(t, *p.LinkUrl, b.LinkUrl)
|
||||
require.Equal(t, ChannelBookmarkLink, b.Type)
|
||||
}
|
||||
@@ -588,6 +588,14 @@ func (c *Client4) limitsRoute() string {
|
||||
return "/limits"
|
||||
}
|
||||
|
||||
func (c *Client4) bookmarksRoute(channelId string) string {
|
||||
return c.channelRoute(channelId) + "/bookmarks"
|
||||
}
|
||||
|
||||
func (c *Client4) bookmarkRoute(channelId, bookmarkId string) string {
|
||||
return fmt.Sprintf(c.bookmarksRoute(channelId)+"/%v", bookmarkId)
|
||||
}
|
||||
|
||||
func (c *Client4) DoAPIGet(ctx context.Context, url string, etag string) (*http.Response, error) {
|
||||
return c.DoAPIRequest(ctx, http.MethodGet, c.APIURL+url, "", etag)
|
||||
}
|
||||
@@ -8917,3 +8925,85 @@ func (c *Client4) GetUserLimits(ctx context.Context) (*UserLimits, *Response, er
|
||||
}
|
||||
return &userLimits, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// CreateChannelBookmark creates a channel bookmark based on the provided struct.
|
||||
func (c *Client4) CreateChannelBookmark(ctx context.Context, channelBookmark *ChannelBookmark) (*ChannelBookmark, *Response, error) {
|
||||
channelBookmarkJSON, err := json.Marshal(channelBookmark)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("CreateChannelBookmark", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
r, err := c.DoAPIPostBytes(ctx, c.bookmarksRoute(channelBookmark.ChannelId), channelBookmarkJSON)
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
var cb ChannelBookmark
|
||||
if err := json.NewDecoder(r.Body).Decode(&cb); err != nil {
|
||||
return nil, nil, NewAppError("CreateChannelBookmark", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
return &cb, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// UpdateChannelBookmark updates a channel bookmark based on the provided struct.
|
||||
func (c *Client4) UpdateChannelBookmark(ctx context.Context, channelId, bookmarkId string, patch *ChannelBookmarkPatch) (*UpdateChannelBookmarkResponse, *Response, error) {
|
||||
buf, err := json.Marshal(patch)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("UpdateChannelBookmark", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
r, err := c.DoAPIPatchBytes(ctx, c.bookmarkRoute(channelId, bookmarkId), buf)
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
var ucb UpdateChannelBookmarkResponse
|
||||
if err := json.NewDecoder(r.Body).Decode(&ucb); err != nil {
|
||||
return nil, nil, NewAppError("UpdateChannelBookmark", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
return &ucb, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// UpdateChannelBookmarkSortOrder updates a channel bookmark's sort order based on the provided new index.
|
||||
func (c *Client4) UpdateChannelBookmarkSortOrder(ctx context.Context, channelId, bookmarkId string, sortOrder int64) ([]*ChannelBookmarkWithFileInfo, *Response, error) {
|
||||
buf, err := json.Marshal(sortOrder)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("UpdateChannelBookmarkSortOrder", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
r, err := c.DoAPIPostBytes(ctx, c.bookmarkRoute(channelId, bookmarkId)+"/sort_order", buf)
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
var b []*ChannelBookmarkWithFileInfo
|
||||
if err := json.NewDecoder(r.Body).Decode(&b); err != nil {
|
||||
return nil, nil, NewAppError("UpdateChannelBookmarkSortOrder", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
return b, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// DeleteChannelBookmark deletes a channel bookmark.
|
||||
func (c *Client4) DeleteChannelBookmark(ctx context.Context, channelId, bookmarkId string) (*ChannelBookmarkWithFileInfo, *Response, error) {
|
||||
r, err := c.DoAPIDelete(ctx, c.bookmarkRoute(channelId, bookmarkId))
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
var b *ChannelBookmarkWithFileInfo
|
||||
if err := json.NewDecoder(r.Body).Decode(&b); err != nil {
|
||||
return nil, nil, NewAppError("DeleteChannelBookmark", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
return b, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) ListChannelBookmarksForChannel(ctx context.Context, channelId string, since int64) ([]*ChannelBookmarkWithFileInfo, *Response, error) {
|
||||
query := fmt.Sprintf("?bookmarks_since=%v", since)
|
||||
r, err := c.DoAPIGet(ctx, c.bookmarksRoute(channelId)+query, "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
var b []*ChannelBookmarkWithFileInfo
|
||||
if err := json.NewDecoder(r.Body).Decode(&b); err != nil {
|
||||
return nil, nil, NewAppError("ListChannelBookmarksForChannel", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
return b, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
@@ -49,10 +49,11 @@ type FeatureFlags struct {
|
||||
CloudIPFiltering bool
|
||||
ConsumePostHook bool
|
||||
|
||||
CloudAnnualRenewals bool
|
||||
|
||||
CloudAnnualRenewals bool
|
||||
CloudDedicatedExportUI bool
|
||||
|
||||
ChannelBookmarks bool
|
||||
|
||||
WebSocketEventScope bool
|
||||
}
|
||||
|
||||
@@ -74,6 +75,7 @@ func (f *FeatureFlags) SetDefaults() {
|
||||
f.ConsumePostHook = false
|
||||
f.CloudAnnualRenewals = false
|
||||
f.CloudDedicatedExportUI = false
|
||||
f.ChannelBookmarks = false
|
||||
f.WebSocketEventScope = false
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ func (fi *FileInfo) IsValid() *AppError {
|
||||
return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.id.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if !IsValidId(fi.CreatorId) && fi.CreatorId != "nouser" {
|
||||
if !IsValidId(fi.CreatorId) && (fi.CreatorId != "nouser" && fi.CreatorId != BookmarkFileOwner) {
|
||||
return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.user_id.app_error", nil, "id="+fi.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,13 @@ func TestFileInfoIsValid(t *testing.T) {
|
||||
assert.NotNil(t, info.IsValid(), "empty Path isn't valid")
|
||||
info.Path = "fake/path.png"
|
||||
})
|
||||
|
||||
t.Run("Creator ID for bookmarks is valid", func(t *testing.T) {
|
||||
creatorId := info.CreatorId
|
||||
info.CreatorId = BookmarkFileOwner
|
||||
assert.Nil(t, info.IsValid(), "creatorId isn't valid")
|
||||
info.CreatorId = creatorId
|
||||
})
|
||||
}
|
||||
|
||||
func TestFileInfoIsImage(t *testing.T) {
|
||||
|
||||
@@ -47,4 +47,5 @@ const (
|
||||
MigrationKeyDeleteOrphanDrafts = "delete_orphan_drafts_migration"
|
||||
MigrationKeyAddIPFilteringPermissions = "add_ip_filtering_permissions"
|
||||
MigrationKeyAddOutgoingOAuthConnectionsPermissions = "add_outgoing_oauth_connections_permissions"
|
||||
MigrationKeyAddChannelBookmarksPermissions = "add_channel_bookmarks_permissions"
|
||||
)
|
||||
|
||||
@@ -109,6 +109,14 @@ var PermissionPromoteGuest *Permission
|
||||
var PermissionDemoteToGuest *Permission
|
||||
var PermissionUseChannelMentions *Permission
|
||||
var PermissionUseGroupMentions *Permission
|
||||
var PermissionAddBookmarkPublicChannel *Permission
|
||||
var PermissionEditBookmarkPublicChannel *Permission
|
||||
var PermissionDeleteBookmarkPublicChannel *Permission
|
||||
var PermissionOrderBookmarkPublicChannel *Permission
|
||||
var PermissionAddBookmarkPrivateChannel *Permission
|
||||
var PermissionEditBookmarkPrivateChannel *Permission
|
||||
var PermissionDeleteBookmarkPrivateChannel *Permission
|
||||
var PermissionOrderBookmarkPrivateChannel *Permission
|
||||
var PermissionReadOtherUsersTeams *Permission
|
||||
var PermissionEditBrand *Permission
|
||||
var PermissionManageSharedChannels *Permission
|
||||
@@ -389,6 +397,7 @@ var SysconsoleReadPermissions []*Permission
|
||||
var SysconsoleWritePermissions []*Permission
|
||||
|
||||
var PermissionManageOutgoingOAuthConnections *Permission
|
||||
var ModeratedBookmarkPermissions []*Permission
|
||||
|
||||
func initializePermissions() {
|
||||
PermissionInviteUser = &Permission{
|
||||
@@ -1177,6 +1186,57 @@ func initializePermissions() {
|
||||
"authentication.permissions.use_group_mentions.description",
|
||||
PermissionScopeChannel,
|
||||
}
|
||||
|
||||
// Channel bookmarks
|
||||
PermissionAddBookmarkPublicChannel = &Permission{
|
||||
"add_bookmark_public_channel",
|
||||
"",
|
||||
"",
|
||||
PermissionScopeChannel,
|
||||
}
|
||||
PermissionEditBookmarkPublicChannel = &Permission{
|
||||
"edit_bookmark_public_channel",
|
||||
"",
|
||||
"",
|
||||
PermissionScopeChannel,
|
||||
}
|
||||
PermissionDeleteBookmarkPublicChannel = &Permission{
|
||||
"delete_bookmark_public_channel",
|
||||
"",
|
||||
"",
|
||||
PermissionScopeChannel,
|
||||
}
|
||||
PermissionOrderBookmarkPublicChannel = &Permission{
|
||||
"order_bookmark_public_channel",
|
||||
"",
|
||||
"",
|
||||
PermissionScopeChannel,
|
||||
}
|
||||
PermissionAddBookmarkPrivateChannel = &Permission{
|
||||
"add_bookmark_private_channel",
|
||||
"",
|
||||
"",
|
||||
PermissionScopeChannel,
|
||||
}
|
||||
PermissionEditBookmarkPrivateChannel = &Permission{
|
||||
"edit_bookmark_private_channel",
|
||||
"",
|
||||
"",
|
||||
PermissionScopeChannel,
|
||||
}
|
||||
PermissionDeleteBookmarkPrivateChannel = &Permission{
|
||||
"delete_bookmark_private_channel",
|
||||
"",
|
||||
"",
|
||||
PermissionScopeChannel,
|
||||
}
|
||||
PermissionOrderBookmarkPrivateChannel = &Permission{
|
||||
"order_bookmark_private_channel",
|
||||
"",
|
||||
"",
|
||||
PermissionScopeChannel,
|
||||
}
|
||||
|
||||
PermissionReadOtherUsersTeams = &Permission{
|
||||
"read_other_users_teams",
|
||||
"authentication.permissions.read_other_users_teams.name",
|
||||
@@ -2386,6 +2446,14 @@ func initializePermissions() {
|
||||
PermissionDeleteOthersPosts,
|
||||
PermissionUseChannelMentions,
|
||||
PermissionUseGroupMentions,
|
||||
PermissionAddBookmarkPublicChannel,
|
||||
PermissionEditBookmarkPublicChannel,
|
||||
PermissionDeleteBookmarkPublicChannel,
|
||||
PermissionOrderBookmarkPublicChannel,
|
||||
PermissionAddBookmarkPrivateChannel,
|
||||
PermissionEditBookmarkPrivateChannel,
|
||||
PermissionDeleteBookmarkPrivateChannel,
|
||||
PermissionOrderBookmarkPrivateChannel,
|
||||
}
|
||||
|
||||
GroupScopedPermissions := []*Permission{
|
||||
@@ -2454,6 +2522,7 @@ func initializePermissions() {
|
||||
"create_reactions",
|
||||
"manage_members",
|
||||
PermissionUseChannelMentions.Id,
|
||||
"manage_bookmarks",
|
||||
}
|
||||
|
||||
ChannelModeratedPermissionsMap = map[string]string{
|
||||
@@ -2464,6 +2533,21 @@ func initializePermissions() {
|
||||
PermissionManagePrivateChannelMembers.Id: ChannelModeratedPermissions[2],
|
||||
PermissionUseChannelMentions.Id: ChannelModeratedPermissions[3],
|
||||
}
|
||||
|
||||
ModeratedBookmarkPermissions = []*Permission{
|
||||
PermissionAddBookmarkPublicChannel,
|
||||
PermissionEditBookmarkPublicChannel,
|
||||
PermissionDeleteBookmarkPublicChannel,
|
||||
PermissionOrderBookmarkPublicChannel,
|
||||
PermissionAddBookmarkPrivateChannel,
|
||||
PermissionEditBookmarkPrivateChannel,
|
||||
PermissionDeleteBookmarkPrivateChannel,
|
||||
PermissionOrderBookmarkPrivateChannel,
|
||||
}
|
||||
|
||||
for _, mbp := range ModeratedBookmarkPermissions {
|
||||
ChannelModeratedPermissionsMap[mbp.Id] = ChannelModeratedPermissions[4]
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -582,6 +582,15 @@ func ChannelModeratedPermissionsChangedByPatch(role *Role, patch *RolePatch) []s
|
||||
return result
|
||||
}
|
||||
|
||||
func isModeratedBookmarkPermission(permission string) bool {
|
||||
for _, mbp := range ModeratedBookmarkPermissions {
|
||||
if mbp.Id == permission {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetChannelModeratedPermissions returns a map of channel moderated permissions that the role has access to
|
||||
func (r *Role) GetChannelModeratedPermissions(channelType ChannelType) map[string]bool {
|
||||
moderatedPermissions := make(map[string]bool)
|
||||
@@ -597,11 +606,22 @@ func (r *Role) GetChannelModeratedPermissions(channelType ChannelType) map[strin
|
||||
}
|
||||
|
||||
if moderated == permission {
|
||||
// Special case where the channel moderated permission for `manage_members` is different depending on whether the channel is private or public
|
||||
// Special case where the channel moderated permission for `manage_members` is different depending
|
||||
// on whether the channel is private or public
|
||||
if moderated == PermissionManagePublicChannelMembers.Id || moderated == PermissionManagePrivateChannelMembers.Id {
|
||||
canManagePublic := channelType == ChannelTypeOpen && moderated == PermissionManagePublicChannelMembers.Id
|
||||
canManagePrivate := channelType == ChannelTypePrivate && moderated == PermissionManagePrivateChannelMembers.Id
|
||||
moderatedPermissions[moderatedPermissionValue] = canManagePublic || canManagePrivate
|
||||
|
||||
// Special case where the channel moderated permission for `manage_bookmarks` is different
|
||||
// depending on whether the channel is private or public.
|
||||
//
|
||||
// Only AddBookmark is checked even if the permission includes four (add, delete, edit and
|
||||
// order) as all of them are enabled or disabled in together
|
||||
} else if isModeratedBookmarkPermission(moderated) {
|
||||
canManagePublic := channelType == ChannelTypeOpen && moderated == PermissionAddBookmarkPublicChannel.Id
|
||||
canManagePrivate := channelType == ChannelTypePrivate && moderated == PermissionAddBookmarkPrivateChannel.Id
|
||||
moderatedPermissions[moderatedPermissionValue] = canManagePublic || canManagePrivate
|
||||
} else {
|
||||
moderatedPermissions[moderatedPermissionValue] = true
|
||||
}
|
||||
@@ -783,6 +803,14 @@ func MakeDefaultRoles() map[string]*Role {
|
||||
PermissionManagePrivateChannelMembers.Id,
|
||||
PermissionDeletePost.Id,
|
||||
PermissionEditPost.Id,
|
||||
PermissionAddBookmarkPublicChannel.Id,
|
||||
PermissionEditBookmarkPublicChannel.Id,
|
||||
PermissionDeleteBookmarkPublicChannel.Id,
|
||||
PermissionOrderBookmarkPublicChannel.Id,
|
||||
PermissionAddBookmarkPrivateChannel.Id,
|
||||
PermissionEditBookmarkPrivateChannel.Id,
|
||||
PermissionDeleteBookmarkPrivateChannel.Id,
|
||||
PermissionOrderBookmarkPrivateChannel.Id,
|
||||
},
|
||||
SchemeManaged: true,
|
||||
BuiltIn: true,
|
||||
@@ -795,6 +823,14 @@ func MakeDefaultRoles() map[string]*Role {
|
||||
Permissions: []string{
|
||||
PermissionManageChannelRoles.Id,
|
||||
PermissionUseGroupMentions.Id,
|
||||
PermissionAddBookmarkPublicChannel.Id,
|
||||
PermissionEditBookmarkPublicChannel.Id,
|
||||
PermissionDeleteBookmarkPublicChannel.Id,
|
||||
PermissionOrderBookmarkPublicChannel.Id,
|
||||
PermissionAddBookmarkPrivateChannel.Id,
|
||||
PermissionEditBookmarkPrivateChannel.Id,
|
||||
PermissionDeleteBookmarkPrivateChannel.Id,
|
||||
PermissionOrderBookmarkPrivateChannel.Id,
|
||||
},
|
||||
SchemeManaged: true,
|
||||
BuiltIn: true,
|
||||
@@ -873,6 +909,14 @@ func MakeDefaultRoles() map[string]*Role {
|
||||
PermissionConvertPrivateChannelToPublic.Id,
|
||||
PermissionDeletePost.Id,
|
||||
PermissionDeleteOthersPosts.Id,
|
||||
PermissionAddBookmarkPublicChannel.Id,
|
||||
PermissionEditBookmarkPublicChannel.Id,
|
||||
PermissionDeleteBookmarkPublicChannel.Id,
|
||||
PermissionOrderBookmarkPublicChannel.Id,
|
||||
PermissionAddBookmarkPrivateChannel.Id,
|
||||
PermissionEditBookmarkPrivateChannel.Id,
|
||||
PermissionDeleteBookmarkPrivateChannel.Id,
|
||||
PermissionOrderBookmarkPrivateChannel.Id,
|
||||
},
|
||||
SchemeManaged: true,
|
||||
BuiltIn: true,
|
||||
|
||||
@@ -86,6 +86,10 @@ const (
|
||||
WebsocketEventAcknowledgementRemoved WebsocketEventType = "post_acknowledgement_removed"
|
||||
WebsocketEventPersistentNotificationTriggered WebsocketEventType = "persistent_notification_triggered"
|
||||
WebsocketEventHostedCustomerSignupProgressUpdated WebsocketEventType = "hosted_customer_signup_progress_updated"
|
||||
WebsocketEventChannelBookmarkCreated = "channel_bookmark_created"
|
||||
WebsocketEventChannelBookmarkUpdated = "channel_bookmark_updated"
|
||||
WebsocketEventChannelBookmarkDeleted = "channel_bookmark_deleted"
|
||||
WebsocketEventChannelBookmarkSorted = "channel_bookmark_sorted"
|
||||
WebsocketPresenceIndicator WebsocketEventType = "presence"
|
||||
)
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user