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  }