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  }