* 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>
323 строки
9.2 KiB
Go
323 строки
9.2 KiB
Go
// 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
|
|
}
|