github.com/haalcala/mattermost-server-change-repo@v0.0.0-20210713015153-16753fbeee5f/store/sqlstore/channel_store_categories.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package sqlstore 5 6 import ( 7 "fmt" 8 9 sq "github.com/Masterminds/squirrel" 10 "github.com/mattermost/gorp" 11 "github.com/pkg/errors" 12 13 "github.com/mattermost/mattermost-server/v5/model" 14 "github.com/mattermost/mattermost-server/v5/store" 15 ) 16 17 func (s SqlChannelStore) CreateInitialSidebarCategories(userId, teamId string) error { 18 transaction, err := s.GetMaster().Begin() 19 if err != nil { 20 return errors.Wrap(err, "CreateInitialSidebarCategories: begin_transaction") 21 } 22 defer finalizeTransaction(transaction) 23 24 if err := s.createInitialSidebarCategoriesT(transaction, userId, teamId); err != nil { 25 return errors.Wrap(err, "CreateInitialSidebarCategories: createInitialSidebarCategoriesT") 26 } 27 28 if err := transaction.Commit(); err != nil { 29 return errors.Wrap(err, "CreateInitialSidebarCategories: commit_transaction") 30 } 31 32 return nil 33 } 34 35 func (s SqlChannelStore) createInitialSidebarCategoriesT(transaction *gorp.Transaction, userId, teamId string) error { 36 selectQuery, selectParams, _ := s.getQueryBuilder(). 37 Select("Type"). 38 From("SidebarCategories"). 39 Where(sq.Eq{ 40 "UserId": userId, 41 "TeamId": teamId, 42 "Type": []model.SidebarCategoryType{model.SidebarCategoryFavorites, model.SidebarCategoryChannels, model.SidebarCategoryDirectMessages}, 43 }).ToSql() 44 45 var existingTypes []model.SidebarCategoryType 46 _, err := transaction.Select(&existingTypes, selectQuery, selectParams...) 47 if err != nil { 48 return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to select existing categories") 49 } 50 51 hasCategoryOfType := make(map[model.SidebarCategoryType]bool, len(existingTypes)) 52 for _, existingType := range existingTypes { 53 hasCategoryOfType[existingType] = true 54 } 55 56 // Use deterministic IDs for default categories to prevent potentially creating multiple copies of a default category 57 favoritesCategoryId := fmt.Sprintf("%s_%s_%s", model.SidebarCategoryFavorites, userId, teamId) 58 channelsCategoryId := fmt.Sprintf("%s_%s_%s", model.SidebarCategoryChannels, userId, teamId) 59 directMessagesCategoryId := fmt.Sprintf("%s_%s_%s", model.SidebarCategoryDirectMessages, userId, teamId) 60 61 if !hasCategoryOfType[model.SidebarCategoryFavorites] { 62 // Create the SidebarChannels first since there's more opportunity for something to fail here 63 if err := s.migrateFavoritesToSidebarT(transaction, userId, teamId, favoritesCategoryId); err != nil { 64 return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to migrate favorites to sidebar") 65 } 66 67 if err := transaction.Insert(&model.SidebarCategory{ 68 DisplayName: "Favorites", // This will be retranslated by the client into the user's locale 69 Id: favoritesCategoryId, 70 UserId: userId, 71 TeamId: teamId, 72 Sorting: model.SidebarCategorySortDefault, 73 SortOrder: model.DefaultSidebarSortOrderFavorites, 74 Type: model.SidebarCategoryFavorites, 75 }); err != nil { 76 return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to insert favorites category") 77 } 78 } 79 80 if !hasCategoryOfType[model.SidebarCategoryChannels] { 81 if err := transaction.Insert(&model.SidebarCategory{ 82 DisplayName: "Channels", // This will be retranslateed by the client into the user's locale 83 Id: channelsCategoryId, 84 UserId: userId, 85 TeamId: teamId, 86 Sorting: model.SidebarCategorySortDefault, 87 SortOrder: model.DefaultSidebarSortOrderChannels, 88 Type: model.SidebarCategoryChannels, 89 }); err != nil { 90 return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to insert channels category") 91 } 92 } 93 94 if !hasCategoryOfType[model.SidebarCategoryDirectMessages] { 95 if err := transaction.Insert(&model.SidebarCategory{ 96 DisplayName: "Direct Messages", // This will be retranslateed by the client into the user's locale 97 Id: directMessagesCategoryId, 98 UserId: userId, 99 TeamId: teamId, 100 Sorting: model.SidebarCategorySortRecent, 101 SortOrder: model.DefaultSidebarSortOrderDMs, 102 Type: model.SidebarCategoryDirectMessages, 103 }); err != nil { 104 return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to insert direct messages category") 105 } 106 } 107 108 return nil 109 } 110 111 type userMembership struct { 112 UserId string 113 ChannelId string 114 CategoryId string 115 } 116 117 func (s SqlChannelStore) migrateMembershipToSidebar(transaction *gorp.Transaction, runningOrder *int64, sql string, args ...interface{}) ([]userMembership, error) { 118 var memberships []userMembership 119 if _, err := transaction.Select(&memberships, sql, args...); err != nil { 120 return nil, err 121 } 122 123 for _, favorite := range memberships { 124 sql, args, _ := s.getQueryBuilder(). 125 Insert("SidebarChannels"). 126 Columns("ChannelId", "UserId", "CategoryId", "SortOrder"). 127 Values(favorite.ChannelId, favorite.UserId, favorite.CategoryId, *runningOrder).ToSql() 128 129 if _, err := transaction.Exec(sql, args...); err != nil && !IsUniqueConstraintError(err, []string{"UserId", "PRIMARY"}) { 130 return nil, err 131 } 132 *runningOrder = *runningOrder + model.MinimalSidebarSortDistance 133 } 134 135 if err := transaction.Commit(); err != nil { 136 return nil, err 137 } 138 return memberships, nil 139 } 140 141 func (s SqlChannelStore) migrateFavoritesToSidebarT(transaction *gorp.Transaction, userId, teamId, favoritesCategoryId string) error { 142 favoritesQuery, favoritesParams, _ := s.getQueryBuilder(). 143 Select("Preferences.Name"). 144 From("Preferences"). 145 Join("Channels on Preferences.Name = Channels.Id"). 146 Join("ChannelMembers on Preferences.Name = ChannelMembers.ChannelId and Preferences.UserId = ChannelMembers.UserId"). 147 Where(sq.Eq{ 148 "Preferences.UserId": userId, 149 "Preferences.Category": model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, 150 "Preferences.Value": "true", 151 }). 152 Where(sq.Or{ 153 sq.Eq{"Channels.TeamId": teamId}, 154 sq.Eq{"Channels.TeamId": ""}, 155 }). 156 OrderBy( 157 "Channels.DisplayName", 158 "Channels.Name ASC", 159 ).ToSql() 160 161 var favoriteChannelIds []string 162 if _, err := transaction.Select(&favoriteChannelIds, favoritesQuery, favoritesParams...); err != nil { 163 return errors.Wrap(err, "migrateFavoritesToSidebarT: unable to get favorite channel IDs") 164 } 165 166 for i, channelId := range favoriteChannelIds { 167 if err := transaction.Insert(&model.SidebarChannel{ 168 ChannelId: channelId, 169 CategoryId: favoritesCategoryId, 170 UserId: userId, 171 SortOrder: int64(i * model.MinimalSidebarSortDistance), 172 }); err != nil { 173 return errors.Wrap(err, "migrateFavoritesToSidebarT: unable to insert SidebarChannel") 174 } 175 } 176 177 return nil 178 } 179 180 // MigrateFavoritesToSidebarChannels populates the SidebarChannels table by analyzing existing user preferences for favorites 181 // **IMPORTANT** This function should only be called from the migration task and shouldn't be used by itself 182 func (s SqlChannelStore) MigrateFavoritesToSidebarChannels(lastUserId string, runningOrder int64) (map[string]interface{}, error) { 183 transaction, err := s.GetMaster().Begin() 184 if err != nil { 185 return nil, err 186 } 187 188 defer finalizeTransaction(transaction) 189 190 sb := s. 191 getQueryBuilder(). 192 Select("Preferences.UserId", "Preferences.Name AS ChannelId", "SidebarCategories.Id AS CategoryId"). 193 From("Preferences"). 194 Where(sq.And{ 195 sq.Eq{"Preferences.Category": model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL}, 196 sq.NotEq{"Preferences.Value": "false"}, 197 sq.NotEq{"SidebarCategories.Id": nil}, 198 sq.Gt{"Preferences.UserId": lastUserId}, 199 }). 200 LeftJoin("Channels ON (Channels.Id=Preferences.Name)"). 201 LeftJoin("SidebarCategories ON (SidebarCategories.UserId=Preferences.UserId AND SidebarCategories.Type='"+string(model.SidebarCategoryFavorites)+"' AND (SidebarCategories.TeamId=Channels.TeamId OR Channels.TeamId=''))"). 202 OrderBy("Preferences.UserId", "Channels.Name DESC"). 203 Limit(100) 204 205 sql, args, err := sb.ToSql() 206 if err != nil { 207 return nil, err 208 } 209 210 userFavorites, err := s.migrateMembershipToSidebar(transaction, &runningOrder, sql, args...) 211 if err != nil { 212 return nil, err 213 } 214 if len(userFavorites) == 0 { 215 return nil, nil 216 } 217 218 data := make(map[string]interface{}) 219 data["UserId"] = userFavorites[len(userFavorites)-1].UserId 220 data["SortOrder"] = runningOrder 221 return data, nil 222 } 223 224 type sidebarCategoryForJoin struct { 225 model.SidebarCategory 226 ChannelId *string 227 } 228 229 func (s SqlChannelStore) CreateSidebarCategory(userId, teamId string, newCategory *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, error) { 230 transaction, err := s.GetMaster().Begin() 231 if err != nil { 232 return nil, errors.Wrap(err, "begin_transaction") 233 } 234 235 defer finalizeTransaction(transaction) 236 237 categoriesWithOrder, err := s.getSidebarCategoriesT(transaction, userId, teamId) 238 if err != nil { 239 return nil, err 240 } 241 242 if len(categoriesWithOrder.Categories) < 1 { 243 return nil, errors.Wrap(err, "categories not found") 244 } 245 246 newOrder := categoriesWithOrder.Order 247 newCategoryId := model.NewId() 248 newCategorySortOrder := 0 249 /* 250 When a new category is created, it should be placed as follows: 251 1. If the Favorites category is first, the new category should be placed after it 252 2. Otherwise, the new category should be placed first. 253 */ 254 if categoriesWithOrder.Categories[0].Type == model.SidebarCategoryFavorites { 255 newOrder = append([]string{newOrder[0], newCategoryId}, newOrder[1:]...) 256 newCategorySortOrder = model.MinimalSidebarSortDistance 257 } else { 258 newOrder = append([]string{newCategoryId}, newOrder...) 259 } 260 261 category := &model.SidebarCategory{ 262 DisplayName: newCategory.DisplayName, 263 Id: newCategoryId, 264 UserId: userId, 265 TeamId: teamId, 266 Sorting: model.SidebarCategorySortDefault, 267 SortOrder: int64(model.MinimalSidebarSortDistance * len(newOrder)), // first we place it at the end of the list 268 Type: model.SidebarCategoryCustom, 269 Muted: newCategory.Muted, 270 } 271 if err = transaction.Insert(category); err != nil { 272 return nil, errors.Wrap(err, "failed to save SidebarCategory") 273 } 274 275 if len(newCategory.Channels) > 0 { 276 channelIdsKeys, deleteParams := MapStringsToQueryParams(newCategory.Channels, "ChannelId") 277 deleteParams["UserId"] = userId 278 deleteParams["TeamId"] = teamId 279 280 // Remove any channels from their previous categories and add them to the new one 281 var deleteQuery string 282 if s.DriverName() == model.DATABASE_DRIVER_MYSQL { 283 deleteQuery = ` 284 DELETE 285 SidebarChannels 286 FROM 287 SidebarChannels 288 JOIN 289 SidebarCategories ON SidebarChannels.CategoryId = SidebarCategories.Id 290 WHERE 291 SidebarChannels.UserId = :UserId 292 AND SidebarChannels.ChannelId IN ` + channelIdsKeys + ` 293 AND SidebarCategories.TeamId = :TeamId` 294 } else { 295 deleteQuery = ` 296 DELETE FROM 297 SidebarChannels 298 USING 299 SidebarCategories 300 WHERE 301 SidebarChannels.CategoryId = SidebarCategories.Id 302 AND SidebarChannels.UserId = :UserId 303 AND SidebarChannels.ChannelId IN ` + channelIdsKeys + ` 304 AND SidebarCategories.TeamId = :TeamId` 305 } 306 307 _, err = transaction.Exec(deleteQuery, deleteParams) 308 if err != nil { 309 return nil, errors.Wrap(err, "failed to delete SidebarChannels") 310 } 311 312 var channels []interface{} 313 for i, channelID := range newCategory.Channels { 314 channels = append(channels, &model.SidebarChannel{ 315 ChannelId: channelID, 316 CategoryId: newCategoryId, 317 SortOrder: int64(i * model.MinimalSidebarSortDistance), 318 UserId: userId, 319 }) 320 } 321 if err = transaction.Insert(channels...); err != nil { 322 return nil, errors.Wrap(err, "failed to save SidebarChannels") 323 } 324 } 325 326 // now we re-order the categories according to the new order 327 if err = s.updateSidebarCategoryOrderT(transaction, newOrder); err != nil { 328 return nil, err 329 } 330 331 if err = transaction.Commit(); err != nil { 332 return nil, errors.Wrap(err, "commit_transaction") 333 } 334 335 // patch category to return proper sort order 336 category.SortOrder = int64(newCategorySortOrder) 337 result := &model.SidebarCategoryWithChannels{ 338 SidebarCategory: *category, 339 Channels: newCategory.Channels, 340 } 341 342 return result, nil 343 } 344 345 func (s SqlChannelStore) completePopulatingCategoryChannels(category *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, error) { 346 transaction, err := s.GetMaster().Begin() 347 if err != nil { 348 return nil, errors.Wrap(err, "begin_transaction") 349 } 350 defer finalizeTransaction(transaction) 351 352 result, err := s.completePopulatingCategoryChannelsT(transaction, category) 353 if err != nil { 354 return nil, err 355 } 356 357 if err = transaction.Commit(); err != nil { 358 return nil, errors.Wrap(err, "commit_transaction") 359 } 360 361 return result, nil 362 } 363 364 func (s SqlChannelStore) completePopulatingCategoryChannelsT(transaction *gorp.Transaction, category *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, error) { 365 if category.Type == model.SidebarCategoryCustom || category.Type == model.SidebarCategoryFavorites { 366 return category, nil 367 } 368 369 var channelTypeFilter sq.Sqlizer 370 if category.Type == model.SidebarCategoryDirectMessages { 371 // any DM/GM channels that aren't in any category should be returned as part of the Direct Messages category 372 channelTypeFilter = sq.Eq{"Channels.Type": []string{model.CHANNEL_DIRECT, model.CHANNEL_GROUP}} 373 } else if category.Type == model.SidebarCategoryChannels { 374 // any public/private channels that are on the current team and aren't in any category should be returned as part of the Channels category 375 channelTypeFilter = sq.And{ 376 sq.Eq{"Channels.Type": []string{model.CHANNEL_OPEN, model.CHANNEL_PRIVATE}}, 377 sq.Eq{"Channels.TeamId": category.TeamId}, 378 } 379 } 380 381 // A subquery that is true if the channel does not have a SidebarChannel entry for the current user on the current team 382 doesNotHaveSidebarChannel := sq.Select("1"). 383 Prefix("NOT EXISTS ("). 384 From("SidebarChannels"). 385 Join("SidebarCategories on SidebarChannels.CategoryId=SidebarCategories.Id"). 386 Where(sq.And{ 387 sq.Expr("SidebarChannels.ChannelId = ChannelMembers.ChannelId"), 388 sq.Eq{"SidebarCategories.UserId": category.UserId}, 389 sq.Eq{"SidebarCategories.TeamId": category.TeamId}, 390 }). 391 Suffix(")") 392 393 var channels []string 394 sql, args, err := s.getQueryBuilder(). 395 Select("Id"). 396 From("ChannelMembers"). 397 LeftJoin("Channels ON Channels.Id=ChannelMembers.ChannelId"). 398 Where(sq.And{ 399 sq.Eq{"ChannelMembers.UserId": category.UserId}, 400 channelTypeFilter, 401 sq.Eq{"Channels.DeleteAt": 0}, 402 doesNotHaveSidebarChannel, 403 }). 404 OrderBy("DisplayName ASC").ToSql() 405 if err != nil { 406 return nil, errors.Wrap(err, "channel_tosql") 407 } 408 409 if _, err = transaction.Select(&channels, sql, args...); err != nil { 410 return nil, store.NewErrNotFound("ChannelMembers", "<too many fields>") 411 } 412 413 category.Channels = append(channels, category.Channels...) 414 return category, nil 415 } 416 417 func (s SqlChannelStore) GetSidebarCategory(categoryId string) (*model.SidebarCategoryWithChannels, error) { 418 var categories []*sidebarCategoryForJoin 419 sql, args, err := s.getQueryBuilder(). 420 Select("SidebarCategories.*", "SidebarChannels.ChannelId"). 421 From("SidebarCategories"). 422 LeftJoin("SidebarChannels ON SidebarChannels.CategoryId=SidebarCategories.Id"). 423 Where(sq.Eq{"SidebarCategories.Id": categoryId}). 424 OrderBy("SidebarChannels.SortOrder ASC").ToSql() 425 if err != nil { 426 return nil, errors.Wrap(err, "sidebar_category_tosql") 427 } 428 429 if _, err = s.GetReplica().Select(&categories, sql, args...); err != nil { 430 return nil, store.NewErrNotFound("SidebarCategories", categoryId) 431 } 432 433 if len(categories) == 0 { 434 return nil, store.NewErrNotFound("SidebarCategories", categoryId) 435 } 436 437 result := &model.SidebarCategoryWithChannels{ 438 SidebarCategory: categories[0].SidebarCategory, 439 Channels: make([]string, 0), 440 } 441 for _, category := range categories { 442 if category.ChannelId != nil { 443 result.Channels = append(result.Channels, *category.ChannelId) 444 } 445 } 446 return s.completePopulatingCategoryChannels(result) 447 } 448 449 func (s SqlChannelStore) getSidebarCategoriesT(transaction *gorp.Transaction, userId, teamId string) (*model.OrderedSidebarCategories, error) { 450 oc := model.OrderedSidebarCategories{ 451 Categories: make(model.SidebarCategoriesWithChannels, 0), 452 Order: make([]string, 0), 453 } 454 455 var categories []*sidebarCategoryForJoin 456 query, args, err := s.getQueryBuilder(). 457 Select("SidebarCategories.*", "SidebarChannels.ChannelId"). 458 From("SidebarCategories"). 459 LeftJoin("SidebarChannels ON SidebarChannels.CategoryId=Id"). 460 Where(sq.And{ 461 sq.Eq{"SidebarCategories.UserId": userId}, 462 sq.Eq{"SidebarCategories.TeamId": teamId}, 463 }). 464 OrderBy("SidebarCategories.SortOrder ASC, SidebarChannels.SortOrder ASC").ToSql() 465 if err != nil { 466 return nil, errors.Wrap(err, "sidebar_categories_tosql") 467 } 468 469 if _, err = transaction.Select(&categories, query, args...); err != nil { 470 return nil, store.NewErrNotFound("SidebarCategories", fmt.Sprintf("userId=%s,teamId=%s", userId, teamId)) 471 } 472 for _, category := range categories { 473 var prevCategory *model.SidebarCategoryWithChannels 474 for _, existing := range oc.Categories { 475 if existing.Id == category.Id { 476 prevCategory = existing 477 break 478 } 479 } 480 if prevCategory == nil { 481 prevCategory = &model.SidebarCategoryWithChannels{ 482 SidebarCategory: category.SidebarCategory, 483 Channels: make([]string, 0), 484 } 485 oc.Categories = append(oc.Categories, prevCategory) 486 oc.Order = append(oc.Order, category.Id) 487 } 488 if category.ChannelId != nil { 489 prevCategory.Channels = append(prevCategory.Channels, *category.ChannelId) 490 } 491 } 492 for _, category := range oc.Categories { 493 if _, err := s.completePopulatingCategoryChannelsT(transaction, category); err != nil { 494 return nil, err 495 } 496 } 497 498 return &oc, nil 499 } 500 501 func (s SqlChannelStore) GetSidebarCategories(userId, teamId string) (*model.OrderedSidebarCategories, error) { 502 transaction, err := s.GetMaster().Begin() 503 if err != nil { 504 return nil, errors.Wrap(err, "begin_transaction") 505 } 506 507 defer finalizeTransaction(transaction) 508 509 oc, err := s.getSidebarCategoriesT(transaction, userId, teamId) 510 if err != nil { 511 return nil, err 512 } 513 514 if err = transaction.Commit(); err != nil { 515 return nil, errors.Wrap(err, "commit_transaction") 516 } 517 518 return oc, nil 519 } 520 521 func (s SqlChannelStore) GetSidebarCategoryOrder(userId, teamId string) ([]string, error) { 522 var ids []string 523 524 sql, args, err := s.getQueryBuilder(). 525 Select("Id"). 526 From("SidebarCategories"). 527 Where(sq.And{ 528 sq.Eq{"UserId": userId}, 529 sq.Eq{"TeamId": teamId}, 530 }). 531 OrderBy("SidebarCategories.SortOrder ASC").ToSql() 532 533 if err != nil { 534 return nil, errors.Wrap(err, "sidebar_category_tosql") 535 } 536 537 if _, err := s.GetReplica().Select(&ids, sql, args...); err != nil { 538 return nil, store.NewErrNotFound("SidebarCategories", fmt.Sprintf("userId=%s,teamId=%s", userId, teamId)) 539 } 540 541 return ids, nil 542 } 543 544 func (s SqlChannelStore) updateSidebarCategoryOrderT(transaction *gorp.Transaction, categoryOrder []string) error { 545 var newOrder []interface{} 546 runningOrder := 0 547 for _, categoryId := range categoryOrder { 548 newOrder = append(newOrder, &model.SidebarCategory{ 549 Id: categoryId, 550 SortOrder: int64(runningOrder), 551 }) 552 runningOrder += model.MinimalSidebarSortDistance 553 } 554 555 // There's a bug in gorp where UpdateColumns messes up the stored query for any other attempt to use .Update or 556 // .UpdateColumns on this table, so it's okay to use here as long as we don't use those methods for SidebarCategories 557 // anywhere else. 558 if _, err := transaction.UpdateColumns(func(col *gorp.ColumnMap) bool { 559 return col.ColumnName == "SortOrder" 560 }, newOrder...); err != nil { 561 return errors.Wrap(err, "failed to update SidebarCategory") 562 } 563 564 return nil 565 } 566 567 func (s SqlChannelStore) UpdateSidebarCategoryOrder(userId, teamId string, categoryOrder []string) error { 568 transaction, err := s.GetMaster().Begin() 569 if err != nil { 570 return errors.Wrap(err, "begin_transaction") 571 } 572 573 defer finalizeTransaction(transaction) 574 575 // Ensure no invalid categories are included and that no categories are left out 576 existingOrder, err := s.GetSidebarCategoryOrder(userId, teamId) 577 if err != nil { 578 return err 579 } 580 581 if len(existingOrder) != len(categoryOrder) { 582 return errors.New("cannot update category order, passed list of categories different size than in DB") 583 } 584 585 for _, originalCategoryId := range existingOrder { 586 found := false 587 for _, newCategoryId := range categoryOrder { 588 if newCategoryId == originalCategoryId { 589 found = true 590 break 591 } 592 } 593 if !found { 594 return store.NewErrInvalidInput("SidebarCategories", "id", fmt.Sprintf("%v", categoryOrder)) 595 } 596 } 597 598 if err = s.updateSidebarCategoryOrderT(transaction, categoryOrder); err != nil { 599 return err 600 } 601 602 if err = transaction.Commit(); err != nil { 603 return errors.Wrap(err, "commit_transaction") 604 } 605 606 return nil 607 } 608 609 //nolint:unparam 610 func (s SqlChannelStore) UpdateSidebarCategories(userId, teamId string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, []*model.SidebarCategoryWithChannels, error) { 611 transaction, err := s.GetMaster().Begin() 612 if err != nil { 613 return nil, nil, errors.Wrap(err, "begin_transaction") 614 } 615 defer finalizeTransaction(transaction) 616 617 updatedCategories := []*model.SidebarCategoryWithChannels{} 618 originalCategories := []*model.SidebarCategoryWithChannels{} 619 for _, category := range categories { 620 originalCategory, err2 := s.GetSidebarCategory(category.Id) 621 if err2 != nil { 622 return nil, nil, errors.Wrap(err2, "failed to find SidebarCategories") 623 } 624 625 // Copy category to avoid modifying an argument 626 updatedCategory := &model.SidebarCategoryWithChannels{ 627 SidebarCategory: category.SidebarCategory, 628 } 629 630 // Prevent any changes to read-only fields of SidebarCategories 631 updatedCategory.UserId = originalCategory.UserId 632 updatedCategory.TeamId = originalCategory.TeamId 633 updatedCategory.SortOrder = originalCategory.SortOrder 634 updatedCategory.Type = originalCategory.Type 635 updatedCategory.Muted = originalCategory.Muted 636 637 if updatedCategory.Type != model.SidebarCategoryCustom { 638 updatedCategory.DisplayName = originalCategory.DisplayName 639 } 640 641 if updatedCategory.Type != model.SidebarCategoryDirectMessages { 642 updatedCategory.Channels = make([]string, len(category.Channels)) 643 copy(updatedCategory.Channels, category.Channels) 644 645 updatedCategory.Muted = category.Muted 646 } 647 648 updateQuery, updateParams, _ := s.getQueryBuilder(). 649 Update("SidebarCategories"). 650 Set("DisplayName", updatedCategory.DisplayName). 651 Set("Sorting", updatedCategory.Sorting). 652 Set("Muted", updatedCategory.Muted). 653 Where(sq.Eq{"Id": updatedCategory.Id}).ToSql() 654 655 if _, err = transaction.Exec(updateQuery, updateParams...); err != nil { 656 return nil, nil, errors.Wrap(err, "failed to update SidebarCategories") 657 } 658 659 // if we are updating DM category, it's order can't channel order cannot be changed. 660 if category.Type != model.SidebarCategoryDirectMessages { 661 // Remove any SidebarChannels entries that were either: 662 // - previously in this category (and any ones that are still in the category will be recreated below) 663 // - in another category and are being added to this category 664 query, args, err2 := s.getQueryBuilder(). 665 Delete("SidebarChannels"). 666 Where( 667 sq.And{ 668 sq.Or{ 669 sq.Eq{"ChannelId": originalCategory.Channels}, 670 sq.Eq{"ChannelId": updatedCategory.Channels}, 671 }, 672 sq.Eq{"CategoryId": category.Id}, 673 }, 674 ).ToSql() 675 676 if err2 != nil { 677 return nil, nil, errors.Wrap(err2, "update_sidebar_catetories_tosql") 678 } 679 680 if _, err = transaction.Exec(query, args...); err != nil { 681 return nil, nil, errors.Wrap(err, "failed to delete SidebarChannels") 682 } 683 684 var channels []interface{} 685 runningOrder := 0 686 for _, channelID := range category.Channels { 687 channels = append(channels, &model.SidebarChannel{ 688 ChannelId: channelID, 689 CategoryId: category.Id, 690 SortOrder: int64(runningOrder), 691 UserId: userId, 692 }) 693 runningOrder += model.MinimalSidebarSortDistance 694 } 695 696 if err = transaction.Insert(channels...); err != nil { 697 return nil, nil, errors.Wrap(err, "failed to save SidebarChannels") 698 } 699 } 700 701 // Update the favorites preferences based on channels moving into or out of the Favorites category for compatibility 702 if category.Type == model.SidebarCategoryFavorites { 703 // Remove any old favorites 704 sql, args, _ := s.getQueryBuilder().Delete("Preferences").Where( 705 sq.Eq{ 706 "UserId": userId, 707 "Name": originalCategory.Channels, 708 "Category": model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, 709 }, 710 ).ToSql() 711 712 if _, err = transaction.Exec(sql, args...); err != nil { 713 return nil, nil, errors.Wrap(err, "failed to delete Preferences") 714 } 715 716 // And then add the new ones 717 for _, channelID := range category.Channels { 718 // This breaks the PreferenceStore abstraction, but it should be safe to assume that everything is a SQL 719 // store in this package. 720 if err = s.Preference().(*SqlPreferenceStore).save(transaction, &model.Preference{ 721 Name: channelID, 722 UserId: userId, 723 Category: model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, 724 Value: "true", 725 }); err != nil { 726 return nil, nil, errors.Wrap(err, "failed to save Preference") 727 } 728 } 729 } else { 730 // Remove any old favorites that might have been in this category 731 query, args, nErr := s.getQueryBuilder().Delete("Preferences").Where( 732 sq.Eq{ 733 "UserId": userId, 734 "Name": category.Channels, 735 "Category": model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, 736 }, 737 ).ToSql() 738 if nErr != nil { 739 return nil, nil, errors.Wrap(nErr, "update_sidebar_categories_tosql") 740 } 741 742 if _, nErr = transaction.Exec(query, args...); nErr != nil { 743 return nil, nil, errors.Wrap(nErr, "failed to delete Preferences") 744 } 745 } 746 747 updatedCategories = append(updatedCategories, updatedCategory) 748 originalCategories = append(originalCategories, originalCategory) 749 } 750 751 // Ensure Channels are populated for Channels/Direct Messages category if they change 752 for i, updatedCategory := range updatedCategories { 753 populated, nErr := s.completePopulatingCategoryChannelsT(transaction, updatedCategory) 754 if nErr != nil { 755 return nil, nil, nErr 756 } 757 758 updatedCategories[i] = populated 759 } 760 761 if err = transaction.Commit(); err != nil { 762 return nil, nil, errors.Wrap(err, "commit_transaction") 763 } 764 765 return updatedCategories, originalCategories, nil 766 } 767 768 // UpdateSidebarChannelsByPreferences is called when the Preference table is being updated to keep SidebarCategories in sync 769 // At the moment, it's only handling Favorites and NOT DMs/GMs (those will be handled client side) 770 func (s SqlChannelStore) UpdateSidebarChannelsByPreferences(preferences *model.Preferences) error { 771 transaction, err := s.GetMaster().Begin() 772 if err != nil { 773 return errors.Wrap(err, "UpdateSidebarChannelsByPreferences: begin_transaction") 774 } 775 defer finalizeTransaction(transaction) 776 777 for _, preference := range *preferences { 778 preference := preference 779 780 if preference.Category != model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL { 781 continue 782 } 783 784 // if new preference is false - remove the channel from the appropriate sidebar category 785 if preference.Value == "false" { 786 if err := s.removeSidebarEntriesForPreferenceT(transaction, &preference); err != nil { 787 return errors.Wrap(err, "UpdateSidebarChannelsByPreferences: removeSidebarEntriesForPreferenceT") 788 } 789 } else { 790 if err := s.addChannelToFavoritesCategoryT(transaction, &preference); err != nil { 791 return errors.Wrap(err, "UpdateSidebarChannelsByPreferences: addChannelToFavoritesCategoryT") 792 } 793 } 794 } 795 796 if err := transaction.Commit(); err != nil { 797 return errors.Wrap(err, "UpdateSidebarChannelsByPreferences: commit_transaction") 798 } 799 800 return nil 801 } 802 803 func (s SqlChannelStore) removeSidebarEntriesForPreferenceT(transaction *gorp.Transaction, preference *model.Preference) error { 804 if preference.Category != model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL { 805 return nil 806 } 807 808 // Delete any corresponding SidebarChannels entries in a Favorites category corresponding to this preference. This 809 // can't use the query builder because it uses DB-specific syntax 810 params := map[string]interface{}{ 811 "UserId": preference.UserId, 812 "ChannelId": preference.Name, 813 "CategoryType": model.SidebarCategoryFavorites, 814 } 815 var query string 816 if s.DriverName() == model.DATABASE_DRIVER_MYSQL { 817 query = ` 818 DELETE 819 SidebarChannels 820 FROM 821 SidebarChannels 822 JOIN 823 SidebarCategories ON SidebarChannels.CategoryId = SidebarCategories.Id 824 WHERE 825 SidebarChannels.UserId = :UserId 826 AND SidebarChannels.ChannelId = :ChannelId 827 AND SidebarCategories.Type = :CategoryType` 828 } else { 829 query = ` 830 DELETE FROM 831 SidebarChannels 832 USING 833 SidebarCategories 834 WHERE 835 SidebarChannels.CategoryId = SidebarCategories.Id 836 AND SidebarChannels.UserId = :UserId 837 AND SidebarChannels.ChannelId = :ChannelId 838 AND SidebarCategories.Type = :CategoryType` 839 } 840 841 if _, err := transaction.Exec(query, params); err != nil { 842 return errors.Wrap(err, "Failed to remove sidebar entries for preference") 843 } 844 845 return nil 846 } 847 848 func (s SqlChannelStore) addChannelToFavoritesCategoryT(transaction *gorp.Transaction, preference *model.Preference) error { 849 if preference.Category != model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL { 850 return nil 851 } 852 853 var channel *model.Channel 854 if obj, err := transaction.Get(&model.Channel{}, preference.Name); err != nil { 855 return errors.Wrapf(err, "Failed to get favorited channel with id=%s", preference.Name) 856 } else if obj == nil { 857 return store.NewErrNotFound("Channel", preference.Name) 858 } else { 859 channel = obj.(*model.Channel) 860 } 861 862 // Get the IDs of the Favorites category/categories that the channel needs to be added to 863 builder := s.getQueryBuilder(). 864 Select("SidebarCategories.Id"). 865 From("SidebarCategories"). 866 LeftJoin("SidebarChannels on SidebarCategories.Id = SidebarChannels.CategoryId and SidebarChannels.ChannelId = ?", preference.Name). 867 Where(sq.Eq{ 868 "SidebarCategories.UserId": preference.UserId, 869 "Type": model.SidebarCategoryFavorites, 870 }). 871 Where("SidebarChannels.ChannelId is null") 872 873 if channel.TeamId != "" { 874 builder = builder.Where(sq.Eq{"TeamId": channel.TeamId}) 875 } 876 877 idsQuery, idsParams, _ := builder.ToSql() 878 879 var categoryIds []string 880 if _, err := transaction.Select(&categoryIds, idsQuery, idsParams...); err != nil { 881 return errors.Wrap(err, "Failed to get Favorites sidebar categories") 882 } 883 884 if len(categoryIds) == 0 { 885 // The channel is already in the Favorites category/categories 886 return nil 887 } 888 889 // For each category ID, insert a row into SidebarChannels with the given channel ID and a SortOrder that's less than 890 // all existing SortOrders in the category so that the newly favorited channel comes first 891 insertQuery, insertParams, _ := s.getQueryBuilder(). 892 Insert("SidebarChannels"). 893 Columns( 894 "ChannelId", 895 "CategoryId", 896 "UserId", 897 "SortOrder", 898 ). 899 Select( 900 sq.Select(). 901 Column("? as ChannelId", preference.Name). 902 Column("SidebarCategories.Id as CategoryId"). 903 Column("? as UserId", preference.UserId). 904 Column("COALESCE(MIN(SidebarChannels.SortOrder) - 10, 0) as SortOrder"). 905 From("SidebarCategories"). 906 LeftJoin("SidebarChannels on SidebarCategories.Id = SidebarChannels.CategoryId"). 907 Where(sq.Eq{ 908 "SidebarCategories.Id": categoryIds, 909 }). 910 GroupBy("SidebarCategories.Id")).ToSql() 911 912 if _, err := transaction.Exec(insertQuery, insertParams...); err != nil { 913 return errors.Wrap(err, "Failed to add sidebar entries for favorited channel") 914 } 915 916 return nil 917 } 918 919 // DeleteSidebarChannelsByPreferences is called when the Preference table is being updated to keep SidebarCategories in sync 920 // At the moment, it's only handling Favorites and NOT DMs/GMs (those will be handled client side) 921 func (s SqlChannelStore) DeleteSidebarChannelsByPreferences(preferences *model.Preferences) error { 922 transaction, err := s.GetMaster().Begin() 923 if err != nil { 924 return errors.Wrap(err, "DeleteSidebarChannelsByPreferences: begin_transaction") 925 } 926 defer finalizeTransaction(transaction) 927 928 for _, preference := range *preferences { 929 preference := preference 930 931 if preference.Category != model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL { 932 continue 933 } 934 935 if err := s.removeSidebarEntriesForPreferenceT(transaction, &preference); err != nil { 936 return errors.Wrap(err, "DeleteSidebarChannelsByPreferences: removeSidebarEntriesForPreferenceT") 937 } 938 } 939 940 if err := transaction.Commit(); err != nil { 941 return errors.Wrap(err, "DeleteSidebarChannelsByPreferences: commit_transaction") 942 } 943 944 return nil 945 } 946 947 //nolint:unparam 948 func (s SqlChannelStore) UpdateSidebarChannelCategoryOnMove(channel *model.Channel, newTeamId string) error { 949 // if channel is being moved, remove it from the categories, since it's possible that there's no matching category in the new team 950 if _, err := s.GetMaster().Exec("DELETE FROM SidebarChannels WHERE ChannelId=:ChannelId", map[string]interface{}{"ChannelId": channel.Id}); err != nil { 951 return errors.Wrapf(err, "failed to delete SidebarChannels with channelId=%s", channel.Id) 952 } 953 return nil 954 } 955 956 func (s SqlChannelStore) ClearSidebarOnTeamLeave(userId, teamId string) error { 957 // if user leaves the team, clean his team related entries in sidebar channels and categories 958 params := map[string]interface{}{ 959 "UserId": userId, 960 "TeamId": teamId, 961 } 962 963 var deleteQuery string 964 if s.DriverName() == model.DATABASE_DRIVER_MYSQL { 965 deleteQuery = "DELETE SidebarChannels FROM SidebarChannels LEFT JOIN SidebarCategories ON SidebarCategories.Id = SidebarChannels.CategoryId WHERE SidebarCategories.TeamId=:TeamId AND SidebarCategories.UserId=:UserId" 966 } else { 967 deleteQuery = ` 968 DELETE FROM 969 SidebarChannels 970 WHERE 971 CategoryId IN ( 972 SELECT 973 CategoryId 974 FROM 975 SidebarChannels, 976 SidebarCategories 977 WHERE 978 SidebarChannels.CategoryId = SidebarCategories.Id 979 AND SidebarCategories.TeamId = :TeamId 980 AND SidebarChannels.UserId = :UserId)` 981 } 982 if _, err := s.GetMaster().Exec(deleteQuery, params); err != nil { 983 return errors.Wrap(err, "failed to delete from SidebarChannels") 984 } 985 if _, err := s.GetMaster().Exec("DELETE FROM SidebarCategories WHERE SidebarCategories.TeamId = :TeamId AND SidebarCategories.UserId = :UserId", params); err != nil { 986 return errors.Wrap(err, "failed to delete from SidebarCategories") 987 } 988 return nil 989 } 990 991 // DeleteSidebarCategory removes a custom category and moves any channels into it into the Channels and Direct Messages 992 // categories respectively. Assumes that the provided user ID and team ID match the given category ID. 993 func (s SqlChannelStore) DeleteSidebarCategory(categoryId string) error { 994 transaction, err := s.GetMaster().Begin() 995 if err != nil { 996 return errors.Wrap(err, "begin_transaction") 997 } 998 defer finalizeTransaction(transaction) 999 1000 // Ensure that we're deleting a custom category 1001 var category *model.SidebarCategory 1002 if err = transaction.SelectOne(&category, "SELECT * FROM SidebarCategories WHERE Id = :Id", map[string]interface{}{"Id": categoryId}); err != nil { 1003 return errors.Wrapf(err, "failed to find SidebarCategories with id=%s", categoryId) 1004 } 1005 1006 if category.Type != model.SidebarCategoryCustom { 1007 return store.NewErrInvalidInput("SidebarCategory", "id", categoryId) 1008 } 1009 1010 // Delete the channels in the category 1011 query, args, err := s.getQueryBuilder(). 1012 Delete("SidebarChannels"). 1013 Where(sq.Eq{"CategoryId": categoryId}).ToSql() 1014 if err != nil { 1015 return errors.Wrap(err, "delete_sidebar_cateory_tosql") 1016 } 1017 1018 if _, err = transaction.Exec(query, args...); err != nil { 1019 return errors.Wrap(err, "failed to delete SidebarChannel") 1020 } 1021 1022 // Delete the category itself 1023 query, args, err = s.getQueryBuilder(). 1024 Delete("SidebarCategories"). 1025 Where(sq.Eq{"Id": categoryId}).ToSql() 1026 if err != nil { 1027 return errors.Wrap(err, "delete_sidebar_cateory_tosql") 1028 } 1029 1030 if _, err = transaction.Exec(query, args...); err != nil { 1031 return errors.Wrap(err, "failed to delete SidebarCategory") 1032 } 1033 1034 if err := transaction.Commit(); err != nil { 1035 return errors.Wrap(err, "commit_transaction") 1036 } 1037 1038 return nil 1039 }