github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/app/channel_category.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package app 5 6 import ( 7 "errors" 8 "net/http" 9 "time" 10 11 "github.com/mattermost/mattermost-server/v5/mlog" 12 "github.com/mattermost/mattermost-server/v5/model" 13 "github.com/mattermost/mattermost-server/v5/store" 14 ) 15 16 func (a *App) createInitialSidebarCategories(userID, teamID string) *model.AppError { 17 nErr := a.Srv().Store.Channel().CreateInitialSidebarCategories(userID, teamID) 18 19 if nErr != nil { 20 return model.NewAppError("createInitialSidebarCategories", "app.channel.create_initial_sidebar_categories.internal_error", nil, nErr.Error(), http.StatusInternalServerError) 21 } 22 23 return nil 24 } 25 26 func (a *App) GetSidebarCategories(userID, teamID string) (*model.OrderedSidebarCategories, *model.AppError) { 27 categories, err := a.Srv().Store.Channel().GetSidebarCategories(userID, teamID) 28 29 if err == nil && len(categories.Categories) == 0 { 30 // A user must always have categories, so migration must not have happened yet, and we should run it ourselves 31 appErr := a.createInitialSidebarCategories(userID, teamID) 32 if appErr != nil { 33 return nil, appErr 34 } 35 36 categories, err = a.waitForSidebarCategories(userID, teamID) 37 } 38 39 if err != nil { 40 var nfErr *store.ErrNotFound 41 switch { 42 case errors.As(err, &nfErr): 43 return nil, model.NewAppError("GetSidebarCategories", "app.channel.sidebar_categories.app_error", nil, nfErr.Error(), http.StatusNotFound) 44 default: 45 return nil, model.NewAppError("GetSidebarCategories", "app.channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError) 46 } 47 } 48 49 return categories, nil 50 } 51 52 // waitForSidebarCategories is used to get a user's sidebar categories after they've been created since there may be 53 // replication lag if any database replicas exist. It will wait until results are available to return them. 54 func (a *App) waitForSidebarCategories(userID, teamID string) (*model.OrderedSidebarCategories, error) { 55 if len(a.Config().SqlSettings.DataSourceReplicas) == 0 { 56 // The categories should be available immediately on a single database 57 return a.Srv().Store.Channel().GetSidebarCategories(userID, teamID) 58 } 59 60 now := model.GetMillis() 61 62 for model.GetMillis()-now < 12000 { 63 time.Sleep(100 * time.Millisecond) 64 65 categories, err := a.Srv().Store.Channel().GetSidebarCategories(userID, teamID) 66 67 if err != nil || len(categories.Categories) > 0 { 68 // We've found something, so return 69 return categories, err 70 } 71 } 72 73 mlog.Error("waitForSidebarCategories giving up", mlog.String("user_id", userID), mlog.String("team_id", teamID)) 74 75 return &model.OrderedSidebarCategories{}, nil 76 } 77 78 func (a *App) GetSidebarCategoryOrder(userID, teamID string) ([]string, *model.AppError) { 79 categories, err := a.Srv().Store.Channel().GetSidebarCategoryOrder(userID, teamID) 80 if err != nil { 81 var nfErr *store.ErrNotFound 82 switch { 83 case errors.As(err, &nfErr): 84 return nil, model.NewAppError("GetSidebarCategoryOrder", "app.channel.sidebar_categories.app_error", nil, nfErr.Error(), http.StatusNotFound) 85 default: 86 return nil, model.NewAppError("GetSidebarCategoryOrder", "app.channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError) 87 } 88 } 89 90 return categories, nil 91 } 92 93 func (a *App) GetSidebarCategory(categoryId string) (*model.SidebarCategoryWithChannels, *model.AppError) { 94 category, err := a.Srv().Store.Channel().GetSidebarCategory(categoryId) 95 if err != nil { 96 var nfErr *store.ErrNotFound 97 switch { 98 case errors.As(err, &nfErr): 99 return nil, model.NewAppError("GetSidebarCategory", "app.channel.sidebar_categories.app_error", nil, nfErr.Error(), http.StatusNotFound) 100 default: 101 return nil, model.NewAppError("GetSidebarCategory", "app.channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError) 102 } 103 } 104 105 return category, nil 106 } 107 108 func (a *App) CreateSidebarCategory(userID, teamID string, newCategory *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, *model.AppError) { 109 category, err := a.Srv().Store.Channel().CreateSidebarCategory(userID, teamID, newCategory) 110 if err != nil { 111 var nfErr *store.ErrNotFound 112 switch { 113 case errors.As(err, &nfErr): 114 return nil, model.NewAppError("CreateSidebarCategory", "app.channel.sidebar_categories.app_error", nil, nfErr.Error(), http.StatusNotFound) 115 default: 116 return nil, model.NewAppError("CreateSidebarCategory", "app.channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError) 117 } 118 } 119 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_SIDEBAR_CATEGORY_CREATED, teamID, "", userID, nil) 120 message.Add("category_id", category.Id) 121 a.Publish(message) 122 return category, nil 123 } 124 125 func (a *App) UpdateSidebarCategoryOrder(userID, teamID string, categoryOrder []string) *model.AppError { 126 err := a.Srv().Store.Channel().UpdateSidebarCategoryOrder(userID, teamID, categoryOrder) 127 if err != nil { 128 var nfErr *store.ErrNotFound 129 var invErr *store.ErrInvalidInput 130 switch { 131 case errors.As(err, &nfErr): 132 return model.NewAppError("UpdateSidebarCategoryOrder", "app.channel.sidebar_categories.app_error", nil, nfErr.Error(), http.StatusNotFound) 133 case errors.As(err, &invErr): 134 return model.NewAppError("UpdateSidebarCategoryOrder", "app.channel.sidebar_categories.app_error", nil, invErr.Error(), http.StatusBadRequest) 135 default: 136 return model.NewAppError("UpdateSidebarCategoryOrder", "app.channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError) 137 } 138 } 139 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_SIDEBAR_CATEGORY_ORDER_UPDATED, teamID, "", userID, nil) 140 message.Add("order", categoryOrder) 141 a.Publish(message) 142 return nil 143 } 144 145 func (a *App) UpdateSidebarCategories(userID, teamID string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError) { 146 updatedCategories, originalCategories, err := a.Srv().Store.Channel().UpdateSidebarCategories(userID, teamID, categories) 147 if err != nil { 148 return nil, model.NewAppError("UpdateSidebarCategories", "app.channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError) 149 } 150 151 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_SIDEBAR_CATEGORY_UPDATED, teamID, "", userID, nil) 152 a.Publish(message) 153 154 a.muteChannelsForUpdatedCategories(userID, updatedCategories, originalCategories) 155 156 return updatedCategories, nil 157 } 158 159 func (a *App) muteChannelsForUpdatedCategories(userID string, updatedCategories []*model.SidebarCategoryWithChannels, originalCategories []*model.SidebarCategoryWithChannels) { 160 var channelsToMute []string 161 var channelsToUnmute []string 162 163 // Mute or unmute all channels in categories that were muted or unmuted 164 for i, updatedCategory := range updatedCategories { 165 if i > len(originalCategories)-1 { 166 // The two slices should be the same length, but double check that to be safe 167 continue 168 } 169 170 originalCategory := originalCategories[i] 171 172 if updatedCategory.Muted && !originalCategory.Muted { 173 channelsToMute = append(channelsToMute, updatedCategory.Channels...) 174 } else if !updatedCategory.Muted && originalCategory.Muted { 175 channelsToUnmute = append(channelsToUnmute, updatedCategory.Channels...) 176 } 177 } 178 179 // Mute any channels moved from an unmuted category into a muted one and vice versa 180 channelsDiff := diffChannelsBetweenCategories(updatedCategories, originalCategories) 181 if len(channelsDiff) != 0 { 182 makeCategoryMap := func(categories []*model.SidebarCategoryWithChannels) map[string]*model.SidebarCategoryWithChannels { 183 result := make(map[string]*model.SidebarCategoryWithChannels) 184 for _, category := range categories { 185 result[category.Id] = category 186 } 187 188 return result 189 } 190 191 updatedCategoriesById := makeCategoryMap(updatedCategories) 192 originalCategoriesById := makeCategoryMap(originalCategories) 193 194 for channelId, diff := range channelsDiff { 195 fromCategory := originalCategoriesById[diff.fromCategoryId] 196 toCategory := updatedCategoriesById[diff.toCategoryId] 197 198 if toCategory.Muted && !fromCategory.Muted { 199 channelsToMute = append(channelsToMute, channelId) 200 } else if !toCategory.Muted && fromCategory.Muted { 201 channelsToUnmute = append(channelsToUnmute, channelId) 202 } 203 } 204 } 205 206 if len(channelsToMute) > 0 { 207 _, err := a.setChannelsMuted(channelsToMute, userID, true) 208 if err != nil { 209 mlog.Error( 210 "Failed to mute channels to match category", 211 mlog.String("user_id", userID), 212 mlog.Err(err), 213 ) 214 } 215 } 216 217 if len(channelsToUnmute) > 0 { 218 _, err := a.setChannelsMuted(channelsToUnmute, userID, false) 219 if err != nil { 220 mlog.Error( 221 "Failed to unmute channels to match category", 222 mlog.String("user_id", userID), 223 mlog.Err(err), 224 ) 225 } 226 } 227 } 228 229 type categoryChannelDiff struct { 230 fromCategoryId string 231 toCategoryId string 232 } 233 234 func diffChannelsBetweenCategories(updatedCategories []*model.SidebarCategoryWithChannels, originalCategories []*model.SidebarCategoryWithChannels) map[string]*categoryChannelDiff { 235 // mapChannelIdsToCategories returns a map of channel IDs to the IDs of the categories that they're a member of. 236 mapChannelIdsToCategories := func(categories []*model.SidebarCategoryWithChannels) map[string]string { 237 result := make(map[string]string) 238 for _, category := range categories { 239 for _, channelId := range category.Channels { 240 result[channelId] = category.Id 241 } 242 } 243 244 return result 245 } 246 247 updatedChannelIdsMap := mapChannelIdsToCategories(updatedCategories) 248 originalChannelIdsMap := mapChannelIdsToCategories(originalCategories) 249 250 // Check for any channels that have changed categories. Note that we don't worry about any channels that have moved 251 // outside of these categories since that heavily complicates things and doesn't currently happen in our apps. 252 channelsDiff := make(map[string]*categoryChannelDiff) 253 for channelId, originalCategoryId := range originalChannelIdsMap { 254 updatedCategoryId := updatedChannelIdsMap[channelId] 255 256 if originalCategoryId != updatedCategoryId && updatedCategoryId != "" { 257 channelsDiff[channelId] = &categoryChannelDiff{originalCategoryId, updatedCategoryId} 258 } 259 } 260 261 return channelsDiff 262 } 263 264 func (a *App) DeleteSidebarCategory(userID, teamID, categoryId string) *model.AppError { 265 err := a.Srv().Store.Channel().DeleteSidebarCategory(categoryId) 266 if err != nil { 267 var invErr *store.ErrInvalidInput 268 switch { 269 case errors.As(err, &invErr): 270 return model.NewAppError("DeleteSidebarCategory", "app.channel.sidebar_categories.app_error", nil, invErr.Error(), http.StatusBadRequest) 271 default: 272 return model.NewAppError("DeleteSidebarCategory", "app.channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError) 273 } 274 } 275 276 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_SIDEBAR_CATEGORY_DELETED, teamID, "", userID, nil) 277 message.Add("category_id", categoryId) 278 a.Publish(message) 279 280 return nil 281 }