Move channel category code into its own files (#15319)
* Move channel category code into its own files * Move channel category tests into app/channel_category_test.go
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
7fe6c94eda
Коммит
52611a1761
253
api4/channel.go
253
api4/channel.go
@@ -1895,256 +1895,3 @@ func moveChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
w.Write([]byte(channel.ToJson()))
|
w.Write([]byte(channel.ToJson()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCategoriesForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
||||||
c.RequireUserId().RequireTeamId()
|
|
||||||
if c.Err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) {
|
|
||||||
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
categories, err := c.App.GetSidebarCategories(c.Params.UserId, c.Params.TeamId)
|
|
||||||
if err != nil {
|
|
||||||
c.Err = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write(categories.ToJson())
|
|
||||||
}
|
|
||||||
|
|
||||||
func createCategoryForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
||||||
c.RequireUserId().RequireTeamId()
|
|
||||||
if c.Err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) {
|
|
||||||
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
auditRec := c.MakeAuditRecord("createCategoryForTeamForUser", audit.Fail)
|
|
||||||
defer c.LogAuditRec(auditRec)
|
|
||||||
|
|
||||||
categoryCreateRequest, err := model.SidebarCategoryFromJson(r.Body)
|
|
||||||
if err != nil || c.Params.UserId != categoryCreateRequest.UserId || c.Params.TeamId != categoryCreateRequest.TeamId {
|
|
||||||
c.SetInvalidParam("category")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if appErr := validateUserChannels("createCategoryForTeamForUser", c, c.Params.TeamId, c.Params.UserId, categoryCreateRequest.Channels); appErr != nil {
|
|
||||||
c.Err = appErr
|
|
||||||
return
|
|
||||||
}
|
|
||||||
category, appErr := c.App.CreateSidebarCategory(c.Params.UserId, c.Params.TeamId, categoryCreateRequest)
|
|
||||||
if appErr != nil {
|
|
||||||
c.Err = appErr
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
auditRec.Success()
|
|
||||||
w.Write(category.ToJson())
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCategoryOrderForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
||||||
c.RequireUserId().RequireTeamId()
|
|
||||||
if c.Err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) {
|
|
||||||
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
order, err := c.App.GetSidebarCategoryOrder(c.Params.UserId, c.Params.TeamId)
|
|
||||||
if err != nil {
|
|
||||||
c.Err = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write([]byte(model.ArrayToJson(order)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateCategoryOrderForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
||||||
c.RequireUserId().RequireTeamId()
|
|
||||||
if c.Err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) {
|
|
||||||
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
auditRec := c.MakeAuditRecord("updateCategoryOrderForTeamForUser", audit.Fail)
|
|
||||||
defer c.LogAuditRec(auditRec)
|
|
||||||
|
|
||||||
categoryOrder := model.ArrayFromJson(r.Body)
|
|
||||||
|
|
||||||
for _, categoryId := range categoryOrder {
|
|
||||||
if !c.App.SessionHasPermissionToCategory(*c.App.Session(), c.Params.UserId, c.Params.TeamId, categoryId) {
|
|
||||||
c.SetInvalidParam("category")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.App.UpdateSidebarCategoryOrder(c.Params.UserId, c.Params.TeamId, categoryOrder)
|
|
||||||
if err != nil {
|
|
||||||
c.Err = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
auditRec.Success()
|
|
||||||
w.Write([]byte(model.ArrayToJson(categoryOrder)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCategoryForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
||||||
c.RequireUserId().RequireTeamId().RequireCategoryId()
|
|
||||||
if c.Err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.App.SessionHasPermissionToCategory(*c.App.Session(), c.Params.UserId, c.Params.TeamId, c.Params.CategoryId) {
|
|
||||||
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
categories, err := c.App.GetSidebarCategory(c.Params.CategoryId)
|
|
||||||
if err != nil {
|
|
||||||
c.Err = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write(categories.ToJson())
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateCategoriesForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
||||||
c.RequireUserId().RequireTeamId()
|
|
||||||
if c.Err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) {
|
|
||||||
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
auditRec := c.MakeAuditRecord("updateCategoriesForTeamForUser", audit.Fail)
|
|
||||||
defer c.LogAuditRec(auditRec)
|
|
||||||
|
|
||||||
categoriesUpdateRequest, err := model.SidebarCategoriesFromJson(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
c.SetInvalidParam("category")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var channelsToCheck []string
|
|
||||||
for _, category := range categoriesUpdateRequest {
|
|
||||||
if !c.App.SessionHasPermissionToCategory(*c.App.Session(), c.Params.UserId, c.Params.TeamId, category.Id) {
|
|
||||||
c.SetInvalidParam("category")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
channelsToCheck = append(channelsToCheck, category.Channels...)
|
|
||||||
}
|
|
||||||
if appErr := validateUserChannels("updateCategoriesForTeamForUser", c, c.Params.TeamId, c.Params.UserId, channelsToCheck); appErr != nil {
|
|
||||||
c.Err = appErr
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
categories, appErr := c.App.UpdateSidebarCategories(c.Params.UserId, c.Params.TeamId, categoriesUpdateRequest)
|
|
||||||
if appErr != nil {
|
|
||||||
c.Err = appErr
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
auditRec.Success()
|
|
||||||
w.Write(model.SidebarCategoriesWithChannelsToJson(categories))
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateUserChannels confirms that the given user is a member of the given channel IDs. Returns an error if the user
|
|
||||||
// is not a member of any channel or nil if the user is a member of each channel.
|
|
||||||
func validateUserChannels(operationName string, c *Context, teamId, userId string, channelIDs []string) *model.AppError {
|
|
||||||
channels, err := c.App.GetChannelsForUser(teamId, userId, true, 0)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewAppError("Api4."+operationName, "api.invalid_channel", nil, err.Error(), http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, channelId := range channelIDs {
|
|
||||||
found := false
|
|
||||||
for _, channel := range *channels {
|
|
||||||
if channel.Id == channelId {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
return model.NewAppError("Api4."+operationName, "api.invalid_channel", nil, "", http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateCategoryForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
||||||
c.RequireUserId().RequireTeamId().RequireCategoryId()
|
|
||||||
if c.Err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.App.SessionHasPermissionToCategory(*c.App.Session(), c.Params.UserId, c.Params.TeamId, c.Params.CategoryId) {
|
|
||||||
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
auditRec := c.MakeAuditRecord("updateCategoryForTeamForUser", audit.Fail)
|
|
||||||
defer c.LogAuditRec(auditRec)
|
|
||||||
|
|
||||||
categoryUpdateRequest, err := model.SidebarCategoryFromJson(r.Body)
|
|
||||||
if err != nil || categoryUpdateRequest.TeamId != c.Params.TeamId || categoryUpdateRequest.UserId != c.Params.UserId {
|
|
||||||
c.SetInvalidParam("category")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if appErr := validateUserChannels("updateCategoryForTeamForUser", c, c.Params.TeamId, c.Params.UserId, categoryUpdateRequest.Channels); appErr != nil {
|
|
||||||
c.Err = appErr
|
|
||||||
return
|
|
||||||
}
|
|
||||||
categoryUpdateRequest.Id = c.Params.CategoryId
|
|
||||||
|
|
||||||
categories, appErr := c.App.UpdateSidebarCategories(c.Params.UserId, c.Params.TeamId, []*model.SidebarCategoryWithChannels{categoryUpdateRequest})
|
|
||||||
if appErr != nil {
|
|
||||||
c.Err = appErr
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
auditRec.Success()
|
|
||||||
w.Write(categories[0].ToJson())
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteCategoryForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
||||||
c.RequireUserId().RequireTeamId().RequireCategoryId()
|
|
||||||
if c.Err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.App.SessionHasPermissionToCategory(*c.App.Session(), c.Params.UserId, c.Params.TeamId, c.Params.CategoryId) {
|
|
||||||
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
auditRec := c.MakeAuditRecord("deleteCategoryForTeamForUser", audit.Fail)
|
|
||||||
defer c.LogAuditRec(auditRec)
|
|
||||||
|
|
||||||
appErr := c.App.DeleteSidebarCategory(c.Params.UserId, c.Params.TeamId, c.Params.CategoryId)
|
|
||||||
if appErr != nil {
|
|
||||||
c.Err = appErr
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
auditRec.Success()
|
|
||||||
ReturnStatusOK(w)
|
|
||||||
}
|
|
||||||
|
|||||||
264
api4/channel_category.go
Обычный файл
264
api4/channel_category.go
Обычный файл
@@ -0,0 +1,264 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v5/audit"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getCategoriesForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.RequireUserId().RequireTeamId()
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
categories, err := c.App.GetSidebarCategories(c.Params.UserId, c.Params.TeamId)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(categories.ToJson())
|
||||||
|
}
|
||||||
|
|
||||||
|
func createCategoryForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.RequireUserId().RequireTeamId()
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
auditRec := c.MakeAuditRecord("createCategoryForTeamForUser", audit.Fail)
|
||||||
|
defer c.LogAuditRec(auditRec)
|
||||||
|
|
||||||
|
categoryCreateRequest, err := model.SidebarCategoryFromJson(r.Body)
|
||||||
|
if err != nil || c.Params.UserId != categoryCreateRequest.UserId || c.Params.TeamId != categoryCreateRequest.TeamId {
|
||||||
|
c.SetInvalidParam("category")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if appErr := validateUserChannels("createCategoryForTeamForUser", c, c.Params.TeamId, c.Params.UserId, categoryCreateRequest.Channels); appErr != nil {
|
||||||
|
c.Err = appErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
category, appErr := c.App.CreateSidebarCategory(c.Params.UserId, c.Params.TeamId, categoryCreateRequest)
|
||||||
|
if appErr != nil {
|
||||||
|
c.Err = appErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
auditRec.Success()
|
||||||
|
w.Write(category.ToJson())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCategoryOrderForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.RequireUserId().RequireTeamId()
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
order, err := c.App.GetSidebarCategoryOrder(c.Params.UserId, c.Params.TeamId)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(model.ArrayToJson(order)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateCategoryOrderForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.RequireUserId().RequireTeamId()
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
auditRec := c.MakeAuditRecord("updateCategoryOrderForTeamForUser", audit.Fail)
|
||||||
|
defer c.LogAuditRec(auditRec)
|
||||||
|
|
||||||
|
categoryOrder := model.ArrayFromJson(r.Body)
|
||||||
|
|
||||||
|
for _, categoryId := range categoryOrder {
|
||||||
|
if !c.App.SessionHasPermissionToCategory(*c.App.Session(), c.Params.UserId, c.Params.TeamId, categoryId) {
|
||||||
|
c.SetInvalidParam("category")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.App.UpdateSidebarCategoryOrder(c.Params.UserId, c.Params.TeamId, categoryOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
auditRec.Success()
|
||||||
|
w.Write([]byte(model.ArrayToJson(categoryOrder)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCategoryForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.RequireUserId().RequireTeamId().RequireCategoryId()
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionToCategory(*c.App.Session(), c.Params.UserId, c.Params.TeamId, c.Params.CategoryId) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
categories, err := c.App.GetSidebarCategory(c.Params.CategoryId)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(categories.ToJson())
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateCategoriesForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.RequireUserId().RequireTeamId()
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
auditRec := c.MakeAuditRecord("updateCategoriesForTeamForUser", audit.Fail)
|
||||||
|
defer c.LogAuditRec(auditRec)
|
||||||
|
|
||||||
|
categoriesUpdateRequest, err := model.SidebarCategoriesFromJson(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
c.SetInvalidParam("category")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var channelsToCheck []string
|
||||||
|
for _, category := range categoriesUpdateRequest {
|
||||||
|
if !c.App.SessionHasPermissionToCategory(*c.App.Session(), c.Params.UserId, c.Params.TeamId, category.Id) {
|
||||||
|
c.SetInvalidParam("category")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
channelsToCheck = append(channelsToCheck, category.Channels...)
|
||||||
|
}
|
||||||
|
if appErr := validateUserChannels("updateCategoriesForTeamForUser", c, c.Params.TeamId, c.Params.UserId, channelsToCheck); appErr != nil {
|
||||||
|
c.Err = appErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
categories, appErr := c.App.UpdateSidebarCategories(c.Params.UserId, c.Params.TeamId, categoriesUpdateRequest)
|
||||||
|
if appErr != nil {
|
||||||
|
c.Err = appErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
auditRec.Success()
|
||||||
|
w.Write(model.SidebarCategoriesWithChannelsToJson(categories))
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateUserChannels confirms that the given user is a member of the given channel IDs. Returns an error if the user
|
||||||
|
// is not a member of any channel or nil if the user is a member of each channel.
|
||||||
|
func validateUserChannels(operationName string, c *Context, teamId, userId string, channelIDs []string) *model.AppError {
|
||||||
|
channels, err := c.App.GetChannelsForUser(teamId, userId, true, 0)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewAppError("Api4."+operationName, "api.invalid_channel", nil, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, channelId := range channelIDs {
|
||||||
|
found := false
|
||||||
|
for _, channel := range *channels {
|
||||||
|
if channel.Id == channelId {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return model.NewAppError("Api4."+operationName, "api.invalid_channel", nil, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateCategoryForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.RequireUserId().RequireTeamId().RequireCategoryId()
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionToCategory(*c.App.Session(), c.Params.UserId, c.Params.TeamId, c.Params.CategoryId) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
auditRec := c.MakeAuditRecord("updateCategoryForTeamForUser", audit.Fail)
|
||||||
|
defer c.LogAuditRec(auditRec)
|
||||||
|
|
||||||
|
categoryUpdateRequest, err := model.SidebarCategoryFromJson(r.Body)
|
||||||
|
if err != nil || categoryUpdateRequest.TeamId != c.Params.TeamId || categoryUpdateRequest.UserId != c.Params.UserId {
|
||||||
|
c.SetInvalidParam("category")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if appErr := validateUserChannels("updateCategoryForTeamForUser", c, c.Params.TeamId, c.Params.UserId, categoryUpdateRequest.Channels); appErr != nil {
|
||||||
|
c.Err = appErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
categoryUpdateRequest.Id = c.Params.CategoryId
|
||||||
|
|
||||||
|
categories, appErr := c.App.UpdateSidebarCategories(c.Params.UserId, c.Params.TeamId, []*model.SidebarCategoryWithChannels{categoryUpdateRequest})
|
||||||
|
if appErr != nil {
|
||||||
|
c.Err = appErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
auditRec.Success()
|
||||||
|
w.Write(categories[0].ToJson())
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteCategoryForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.RequireUserId().RequireTeamId().RequireCategoryId()
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionToCategory(*c.App.Session(), c.Params.UserId, c.Params.TeamId, c.Params.CategoryId) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
auditRec := c.MakeAuditRecord("deleteCategoryForTeamForUser", audit.Fail)
|
||||||
|
defer c.LogAuditRec(auditRec)
|
||||||
|
|
||||||
|
appErr := c.App.DeleteSidebarCategory(c.Params.UserId, c.Params.TeamId, c.Params.CategoryId)
|
||||||
|
if appErr != nil {
|
||||||
|
c.Err = appErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
auditRec.Success()
|
||||||
|
ReturnStatusOK(w)
|
||||||
|
}
|
||||||
139
api4/channel_category_test.go
Обычный файл
139
api4/channel_category_test.go
Обычный файл
@@ -0,0 +1,139 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUpdateCategoryForTeamForUser(t *testing.T) {
|
||||||
|
t.Run("should update the channel order of the Channels category", func(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
categories, resp := th.Client.GetSidebarCategoriesForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, "")
|
||||||
|
require.Nil(t, resp.Error)
|
||||||
|
require.Len(t, categories.Categories, 3)
|
||||||
|
require.Len(t, categories.Order, 3)
|
||||||
|
|
||||||
|
channelsCategory := categories.Categories[1]
|
||||||
|
require.Equal(t, model.SidebarCategoryChannels, channelsCategory.Type)
|
||||||
|
require.Len(t, channelsCategory.Channels, 5) // Town Square, Off Topic, and the 3 channels created by InitBasic
|
||||||
|
|
||||||
|
// Should return the correct values from the API
|
||||||
|
updatedCategory := &model.SidebarCategoryWithChannels{
|
||||||
|
SidebarCategory: channelsCategory.SidebarCategory,
|
||||||
|
Channels: []string{channelsCategory.Channels[1], channelsCategory.Channels[0], channelsCategory.Channels[4], channelsCategory.Channels[3], channelsCategory.Channels[2]},
|
||||||
|
}
|
||||||
|
|
||||||
|
received, resp := th.Client.UpdateSidebarCategoryForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, channelsCategory.Id, updatedCategory)
|
||||||
|
assert.Nil(t, resp.Error)
|
||||||
|
assert.Equal(t, channelsCategory.Id, received.Id)
|
||||||
|
assert.Equal(t, updatedCategory.Channels, received.Channels)
|
||||||
|
|
||||||
|
// And when requesting the category later
|
||||||
|
received, resp = th.Client.GetSidebarCategoryForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, channelsCategory.Id, "")
|
||||||
|
assert.Nil(t, resp.Error)
|
||||||
|
assert.Equal(t, channelsCategory.Id, received.Id)
|
||||||
|
assert.Equal(t, updatedCategory.Channels, received.Channels)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should update the sort order of the DM category", func(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
categories, resp := th.Client.GetSidebarCategoriesForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, "")
|
||||||
|
require.Nil(t, resp.Error)
|
||||||
|
require.Len(t, categories.Categories, 3)
|
||||||
|
require.Len(t, categories.Order, 3)
|
||||||
|
|
||||||
|
dmsCategory := categories.Categories[2]
|
||||||
|
require.Equal(t, model.SidebarCategoryDirectMessages, dmsCategory.Type)
|
||||||
|
require.Equal(t, model.SidebarCategorySortRecent, dmsCategory.Sorting)
|
||||||
|
|
||||||
|
// Should return the correct values from the API
|
||||||
|
updatedCategory := &model.SidebarCategoryWithChannels{
|
||||||
|
SidebarCategory: dmsCategory.SidebarCategory,
|
||||||
|
Channels: dmsCategory.Channels,
|
||||||
|
}
|
||||||
|
updatedCategory.Sorting = model.SidebarCategorySortAlphabetical
|
||||||
|
|
||||||
|
received, resp := th.Client.UpdateSidebarCategoryForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, dmsCategory.Id, updatedCategory)
|
||||||
|
assert.Nil(t, resp.Error)
|
||||||
|
assert.Equal(t, dmsCategory.Id, received.Id)
|
||||||
|
assert.Equal(t, model.SidebarCategorySortAlphabetical, received.Sorting)
|
||||||
|
|
||||||
|
// And when requesting the category later
|
||||||
|
received, resp = th.Client.GetSidebarCategoryForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, dmsCategory.Id, "")
|
||||||
|
assert.Nil(t, resp.Error)
|
||||||
|
assert.Equal(t, dmsCategory.Id, received.Id)
|
||||||
|
assert.Equal(t, model.SidebarCategorySortAlphabetical, received.Sorting)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should update the display name of a custom category", func(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
customCategory, resp := th.Client.CreateSidebarCategoryForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, &model.SidebarCategoryWithChannels{
|
||||||
|
SidebarCategory: model.SidebarCategory{
|
||||||
|
UserId: th.BasicUser.Id,
|
||||||
|
TeamId: th.BasicTeam.Id,
|
||||||
|
DisplayName: "custom123",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.Nil(t, resp.Error)
|
||||||
|
require.Equal(t, "custom123", customCategory.DisplayName)
|
||||||
|
|
||||||
|
// Should return the correct values from the API
|
||||||
|
updatedCategory := &model.SidebarCategoryWithChannels{
|
||||||
|
SidebarCategory: customCategory.SidebarCategory,
|
||||||
|
Channels: customCategory.Channels,
|
||||||
|
}
|
||||||
|
updatedCategory.DisplayName = "abcCustom"
|
||||||
|
|
||||||
|
received, resp := th.Client.UpdateSidebarCategoryForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, customCategory.Id, updatedCategory)
|
||||||
|
assert.Nil(t, resp.Error)
|
||||||
|
assert.Equal(t, customCategory.Id, received.Id)
|
||||||
|
assert.Equal(t, updatedCategory.DisplayName, received.DisplayName)
|
||||||
|
|
||||||
|
// And when requesting the category later
|
||||||
|
received, resp = th.Client.GetSidebarCategoryForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, customCategory.Id, "")
|
||||||
|
assert.Nil(t, resp.Error)
|
||||||
|
assert.Equal(t, customCategory.Id, received.Id)
|
||||||
|
assert.Equal(t, updatedCategory.DisplayName, received.DisplayName)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should update the channel order of the category even if it contains archived channels", func(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
categories, resp := th.Client.GetSidebarCategoriesForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, "")
|
||||||
|
require.Nil(t, resp.Error)
|
||||||
|
require.Len(t, categories.Categories, 3)
|
||||||
|
require.Len(t, categories.Order, 3)
|
||||||
|
|
||||||
|
channelsCategory := categories.Categories[1]
|
||||||
|
require.Equal(t, model.SidebarCategoryChannels, channelsCategory.Type)
|
||||||
|
require.Len(t, channelsCategory.Channels, 5) // Town Square, Off Topic, and the 3 channels created by InitBasic
|
||||||
|
|
||||||
|
// Delete one of the channels
|
||||||
|
_, resp = th.Client.DeleteChannel(th.BasicChannel.Id)
|
||||||
|
require.Nil(t, resp.Error)
|
||||||
|
|
||||||
|
// Should still be able to reorder the channels
|
||||||
|
updatedCategory := &model.SidebarCategoryWithChannels{
|
||||||
|
SidebarCategory: channelsCategory.SidebarCategory,
|
||||||
|
Channels: []string{channelsCategory.Channels[1], channelsCategory.Channels[0], channelsCategory.Channels[4], channelsCategory.Channels[3], channelsCategory.Channels[2]},
|
||||||
|
}
|
||||||
|
|
||||||
|
received, resp := th.Client.UpdateSidebarCategoryForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, channelsCategory.Id, updatedCategory)
|
||||||
|
require.Nil(t, resp.Error)
|
||||||
|
assert.Equal(t, channelsCategory.Id, received.Id)
|
||||||
|
assert.Equal(t, updatedCategory.Channels, received.Channels)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -4001,130 +4001,3 @@ func TestMoveChannel(t *testing.T) {
|
|||||||
require.Equal(t, team2.Id, newChannel.TeamId)
|
require.Equal(t, team2.Id, newChannel.TeamId)
|
||||||
}, "Should be able to (force) move channel by a member that is not member of target team")
|
}, "Should be able to (force) move channel by a member that is not member of target team")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateCategoryForTeamForUser(t *testing.T) {
|
|
||||||
t.Run("should update the channel order of the Channels category", func(t *testing.T) {
|
|
||||||
th := Setup(t).InitBasic()
|
|
||||||
defer th.TearDown()
|
|
||||||
|
|
||||||
categories, resp := th.Client.GetSidebarCategoriesForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, "")
|
|
||||||
require.Nil(t, resp.Error)
|
|
||||||
require.Len(t, categories.Categories, 3)
|
|
||||||
require.Len(t, categories.Order, 3)
|
|
||||||
|
|
||||||
channelsCategory := categories.Categories[1]
|
|
||||||
require.Equal(t, model.SidebarCategoryChannels, channelsCategory.Type)
|
|
||||||
require.Len(t, channelsCategory.Channels, 5) // Town Square, Off Topic, and the 3 channels created by InitBasic
|
|
||||||
|
|
||||||
// Should return the correct values from the API
|
|
||||||
updatedCategory := &model.SidebarCategoryWithChannels{
|
|
||||||
SidebarCategory: channelsCategory.SidebarCategory,
|
|
||||||
Channels: []string{channelsCategory.Channels[1], channelsCategory.Channels[0], channelsCategory.Channels[4], channelsCategory.Channels[3], channelsCategory.Channels[2]},
|
|
||||||
}
|
|
||||||
|
|
||||||
received, resp := th.Client.UpdateSidebarCategoryForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, channelsCategory.Id, updatedCategory)
|
|
||||||
assert.Nil(t, resp.Error)
|
|
||||||
assert.Equal(t, channelsCategory.Id, received.Id)
|
|
||||||
assert.Equal(t, updatedCategory.Channels, received.Channels)
|
|
||||||
|
|
||||||
// And when requesting the category later
|
|
||||||
received, resp = th.Client.GetSidebarCategoryForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, channelsCategory.Id, "")
|
|
||||||
assert.Nil(t, resp.Error)
|
|
||||||
assert.Equal(t, channelsCategory.Id, received.Id)
|
|
||||||
assert.Equal(t, updatedCategory.Channels, received.Channels)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should update the sort order of the DM category", func(t *testing.T) {
|
|
||||||
th := Setup(t).InitBasic()
|
|
||||||
defer th.TearDown()
|
|
||||||
|
|
||||||
categories, resp := th.Client.GetSidebarCategoriesForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, "")
|
|
||||||
require.Nil(t, resp.Error)
|
|
||||||
require.Len(t, categories.Categories, 3)
|
|
||||||
require.Len(t, categories.Order, 3)
|
|
||||||
|
|
||||||
dmsCategory := categories.Categories[2]
|
|
||||||
require.Equal(t, model.SidebarCategoryDirectMessages, dmsCategory.Type)
|
|
||||||
require.Equal(t, model.SidebarCategorySortRecent, dmsCategory.Sorting)
|
|
||||||
|
|
||||||
// Should return the correct values from the API
|
|
||||||
updatedCategory := &model.SidebarCategoryWithChannels{
|
|
||||||
SidebarCategory: dmsCategory.SidebarCategory,
|
|
||||||
Channels: dmsCategory.Channels,
|
|
||||||
}
|
|
||||||
updatedCategory.Sorting = model.SidebarCategorySortAlphabetical
|
|
||||||
|
|
||||||
received, resp := th.Client.UpdateSidebarCategoryForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, dmsCategory.Id, updatedCategory)
|
|
||||||
assert.Nil(t, resp.Error)
|
|
||||||
assert.Equal(t, dmsCategory.Id, received.Id)
|
|
||||||
assert.Equal(t, model.SidebarCategorySortAlphabetical, received.Sorting)
|
|
||||||
|
|
||||||
// And when requesting the category later
|
|
||||||
received, resp = th.Client.GetSidebarCategoryForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, dmsCategory.Id, "")
|
|
||||||
assert.Nil(t, resp.Error)
|
|
||||||
assert.Equal(t, dmsCategory.Id, received.Id)
|
|
||||||
assert.Equal(t, model.SidebarCategorySortAlphabetical, received.Sorting)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should update the display name of a custom category", func(t *testing.T) {
|
|
||||||
th := Setup(t).InitBasic()
|
|
||||||
defer th.TearDown()
|
|
||||||
|
|
||||||
customCategory, resp := th.Client.CreateSidebarCategoryForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, &model.SidebarCategoryWithChannels{
|
|
||||||
SidebarCategory: model.SidebarCategory{
|
|
||||||
UserId: th.BasicUser.Id,
|
|
||||||
TeamId: th.BasicTeam.Id,
|
|
||||||
DisplayName: "custom123",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
require.Nil(t, resp.Error)
|
|
||||||
require.Equal(t, "custom123", customCategory.DisplayName)
|
|
||||||
|
|
||||||
// Should return the correct values from the API
|
|
||||||
updatedCategory := &model.SidebarCategoryWithChannels{
|
|
||||||
SidebarCategory: customCategory.SidebarCategory,
|
|
||||||
Channels: customCategory.Channels,
|
|
||||||
}
|
|
||||||
updatedCategory.DisplayName = "abcCustom"
|
|
||||||
|
|
||||||
received, resp := th.Client.UpdateSidebarCategoryForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, customCategory.Id, updatedCategory)
|
|
||||||
assert.Nil(t, resp.Error)
|
|
||||||
assert.Equal(t, customCategory.Id, received.Id)
|
|
||||||
assert.Equal(t, updatedCategory.DisplayName, received.DisplayName)
|
|
||||||
|
|
||||||
// And when requesting the category later
|
|
||||||
received, resp = th.Client.GetSidebarCategoryForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, customCategory.Id, "")
|
|
||||||
assert.Nil(t, resp.Error)
|
|
||||||
assert.Equal(t, customCategory.Id, received.Id)
|
|
||||||
assert.Equal(t, updatedCategory.DisplayName, received.DisplayName)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should update the channel order of the category even if it contains archived channels", func(t *testing.T) {
|
|
||||||
th := Setup(t).InitBasic()
|
|
||||||
defer th.TearDown()
|
|
||||||
|
|
||||||
categories, resp := th.Client.GetSidebarCategoriesForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, "")
|
|
||||||
require.Nil(t, resp.Error)
|
|
||||||
require.Len(t, categories.Categories, 3)
|
|
||||||
require.Len(t, categories.Order, 3)
|
|
||||||
|
|
||||||
channelsCategory := categories.Categories[1]
|
|
||||||
require.Equal(t, model.SidebarCategoryChannels, channelsCategory.Type)
|
|
||||||
require.Len(t, channelsCategory.Channels, 5) // Town Square, Off Topic, and the 3 channels created by InitBasic
|
|
||||||
|
|
||||||
// Delete one of the channels
|
|
||||||
_, resp = th.Client.DeleteChannel(th.BasicChannel.Id)
|
|
||||||
require.Nil(t, resp.Error)
|
|
||||||
|
|
||||||
// Should still be able to reorder the channels
|
|
||||||
updatedCategory := &model.SidebarCategoryWithChannels{
|
|
||||||
SidebarCategory: channelsCategory.SidebarCategory,
|
|
||||||
Channels: []string{channelsCategory.Channels[1], channelsCategory.Channels[0], channelsCategory.Channels[4], channelsCategory.Channels[3], channelsCategory.Channels[2]},
|
|
||||||
}
|
|
||||||
|
|
||||||
received, resp := th.Client.UpdateSidebarCategoryForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, channelsCategory.Id, updatedCategory)
|
|
||||||
require.Nil(t, resp.Error)
|
|
||||||
assert.Equal(t, channelsCategory.Id, received.Id)
|
|
||||||
assert.Equal(t, updatedCategory.Channels, received.Channels)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
105
app/channel.go
105
app/channel.go
@@ -2630,108 +2630,3 @@ func (a *App) ClearChannelMembersCache(channelID string) {
|
|||||||
page++
|
page++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) createInitialSidebarCategories(userId, teamId string) *model.AppError {
|
|
||||||
nErr := a.Srv().Store.Channel().CreateInitialSidebarCategories(userId, teamId)
|
|
||||||
|
|
||||||
if nErr != nil {
|
|
||||||
return model.NewAppError("createInitialSidebarCategories", "app.channel.create_initial_sidebar_categories.internal_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) GetSidebarCategories(userId, teamId string) (*model.OrderedSidebarCategories, *model.AppError) {
|
|
||||||
categories, err := a.Srv().Store.Channel().GetSidebarCategories(userId, teamId)
|
|
||||||
|
|
||||||
if err == nil && len(categories.Categories) == 0 {
|
|
||||||
// A user must always have categories, so migration must not have happened yet, and we should run it ourselves
|
|
||||||
nErr := a.createInitialSidebarCategories(userId, teamId)
|
|
||||||
if nErr != nil {
|
|
||||||
return nil, nErr
|
|
||||||
}
|
|
||||||
|
|
||||||
categories, err = a.waitForSidebarCategories(userId, teamId)
|
|
||||||
}
|
|
||||||
|
|
||||||
return categories, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitForSidebarCategories is used to get a user's sidebar categories after they've been created since there may be
|
|
||||||
// replication lag if any database replicas exist. It will wait until results are available to return them.
|
|
||||||
func (a *App) waitForSidebarCategories(userId, teamId string) (*model.OrderedSidebarCategories, *model.AppError) {
|
|
||||||
if len(a.Config().SqlSettings.DataSourceReplicas) == 0 {
|
|
||||||
// The categories should be available immediately on a single database
|
|
||||||
return a.Srv().Store.Channel().GetSidebarCategories(userId, teamId)
|
|
||||||
}
|
|
||||||
|
|
||||||
now := model.GetMillis()
|
|
||||||
|
|
||||||
for model.GetMillis()-now < 12000 {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
|
|
||||||
categories, err := a.Srv().Store.Channel().GetSidebarCategories(userId, teamId)
|
|
||||||
|
|
||||||
if err != nil || len(categories.Categories) > 0 {
|
|
||||||
// We've found something, so return
|
|
||||||
return categories, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mlog.Error("waitForSidebarCategories giving up", mlog.String("user_id", userId), mlog.String("team_id", teamId))
|
|
||||||
|
|
||||||
return &model.OrderedSidebarCategories{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) GetSidebarCategoryOrder(userId, teamId string) ([]string, *model.AppError) {
|
|
||||||
return a.Srv().Store.Channel().GetSidebarCategoryOrder(userId, teamId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) GetSidebarCategory(categoryId string) (*model.SidebarCategoryWithChannels, *model.AppError) {
|
|
||||||
return a.Srv().Store.Channel().GetSidebarCategory(categoryId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) CreateSidebarCategory(userId, teamId string, newCategory *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, *model.AppError) {
|
|
||||||
category, err := a.Srv().Store.Channel().CreateSidebarCategory(userId, teamId, newCategory)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_SIDEBAR_CATEGORY_CREATED, teamId, "", userId, nil)
|
|
||||||
message.Add("category_id", category.Id)
|
|
||||||
a.Publish(message)
|
|
||||||
return category, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) UpdateSidebarCategoryOrder(userId, teamId string, categoryOrder []string) *model.AppError {
|
|
||||||
err := a.Srv().Store.Channel().UpdateSidebarCategoryOrder(userId, teamId, categoryOrder)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_SIDEBAR_CATEGORY_ORDER_UPDATED, teamId, "", userId, nil)
|
|
||||||
message.Add("order", categoryOrder)
|
|
||||||
a.Publish(message)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) UpdateSidebarCategories(userId, teamId string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError) {
|
|
||||||
result, err := a.Srv().Store.Channel().UpdateSidebarCategories(userId, teamId, categories)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_SIDEBAR_CATEGORY_UPDATED, teamId, "", userId, nil)
|
|
||||||
a.Publish(message)
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) DeleteSidebarCategory(userId, teamId, categoryId string) *model.AppError {
|
|
||||||
err := a.Srv().Store.Channel().DeleteSidebarCategory(categoryId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_SIDEBAR_CATEGORY_DELETED, teamId, "", userId, nil)
|
|
||||||
message.Add("category_id", categoryId)
|
|
||||||
a.Publish(message)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
117
app/channel_category.go
Обычный файл
117
app/channel_category.go
Обычный файл
@@ -0,0 +1,117 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *App) createInitialSidebarCategories(userId, teamId string) *model.AppError {
|
||||||
|
nErr := a.Srv().Store.Channel().CreateInitialSidebarCategories(userId, teamId)
|
||||||
|
|
||||||
|
if nErr != nil {
|
||||||
|
return model.NewAppError("createInitialSidebarCategories", "app.channel.create_initial_sidebar_categories.internal_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetSidebarCategories(userId, teamId string) (*model.OrderedSidebarCategories, *model.AppError) {
|
||||||
|
categories, err := a.Srv().Store.Channel().GetSidebarCategories(userId, teamId)
|
||||||
|
|
||||||
|
if err == nil && len(categories.Categories) == 0 {
|
||||||
|
// A user must always have categories, so migration must not have happened yet, and we should run it ourselves
|
||||||
|
nErr := a.createInitialSidebarCategories(userId, teamId)
|
||||||
|
if nErr != nil {
|
||||||
|
return nil, nErr
|
||||||
|
}
|
||||||
|
|
||||||
|
categories, err = a.waitForSidebarCategories(userId, teamId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return categories, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForSidebarCategories is used to get a user's sidebar categories after they've been created since there may be
|
||||||
|
// replication lag if any database replicas exist. It will wait until results are available to return them.
|
||||||
|
func (a *App) waitForSidebarCategories(userId, teamId string) (*model.OrderedSidebarCategories, *model.AppError) {
|
||||||
|
if len(a.Config().SqlSettings.DataSourceReplicas) == 0 {
|
||||||
|
// The categories should be available immediately on a single database
|
||||||
|
return a.Srv().Store.Channel().GetSidebarCategories(userId, teamId)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := model.GetMillis()
|
||||||
|
|
||||||
|
for model.GetMillis()-now < 12000 {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
categories, err := a.Srv().Store.Channel().GetSidebarCategories(userId, teamId)
|
||||||
|
|
||||||
|
if err != nil || len(categories.Categories) > 0 {
|
||||||
|
// We've found something, so return
|
||||||
|
return categories, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mlog.Error("waitForSidebarCategories giving up", mlog.String("user_id", userId), mlog.String("team_id", teamId))
|
||||||
|
|
||||||
|
return &model.OrderedSidebarCategories{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetSidebarCategoryOrder(userId, teamId string) ([]string, *model.AppError) {
|
||||||
|
return a.Srv().Store.Channel().GetSidebarCategoryOrder(userId, teamId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetSidebarCategory(categoryId string) (*model.SidebarCategoryWithChannels, *model.AppError) {
|
||||||
|
return a.Srv().Store.Channel().GetSidebarCategory(categoryId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) CreateSidebarCategory(userId, teamId string, newCategory *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, *model.AppError) {
|
||||||
|
category, err := a.Srv().Store.Channel().CreateSidebarCategory(userId, teamId, newCategory)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_SIDEBAR_CATEGORY_CREATED, teamId, "", userId, nil)
|
||||||
|
message.Add("category_id", category.Id)
|
||||||
|
a.Publish(message)
|
||||||
|
return category, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) UpdateSidebarCategoryOrder(userId, teamId string, categoryOrder []string) *model.AppError {
|
||||||
|
err := a.Srv().Store.Channel().UpdateSidebarCategoryOrder(userId, teamId, categoryOrder)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_SIDEBAR_CATEGORY_ORDER_UPDATED, teamId, "", userId, nil)
|
||||||
|
message.Add("order", categoryOrder)
|
||||||
|
a.Publish(message)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) UpdateSidebarCategories(userId, teamId string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError) {
|
||||||
|
result, err := a.Srv().Store.Channel().UpdateSidebarCategories(userId, teamId, categories)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_SIDEBAR_CATEGORY_UPDATED, teamId, "", userId, nil)
|
||||||
|
a.Publish(message)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) DeleteSidebarCategory(userId, teamId, categoryId string) *model.AppError {
|
||||||
|
err := a.Srv().Store.Channel().DeleteSidebarCategory(categoryId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_SIDEBAR_CATEGORY_DELETED, teamId, "", userId, nil)
|
||||||
|
message.Add("category_id", categoryId)
|
||||||
|
a.Publish(message)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
134
app/channel_category_test.go
Обычный файл
134
app/channel_category_test.go
Обычный файл
@@ -0,0 +1,134 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSidebarCategory(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
basicChannel2 := th.CreateChannel(th.BasicTeam)
|
||||||
|
defer th.App.PermanentDeleteChannel(basicChannel2)
|
||||||
|
user := th.CreateUser()
|
||||||
|
defer th.App.Srv().Store.User().PermanentDelete(user.Id)
|
||||||
|
th.LinkUserToTeam(user, th.BasicTeam)
|
||||||
|
th.AddUserToChannel(user, basicChannel2)
|
||||||
|
|
||||||
|
var createdCategory *model.SidebarCategoryWithChannels
|
||||||
|
t.Run("CreateSidebarCategory", func(t *testing.T) {
|
||||||
|
catData := model.SidebarCategoryWithChannels{
|
||||||
|
SidebarCategory: model.SidebarCategory{
|
||||||
|
DisplayName: "TEST",
|
||||||
|
},
|
||||||
|
Channels: []string{th.BasicChannel.Id, basicChannel2.Id, basicChannel2.Id},
|
||||||
|
}
|
||||||
|
_, err := th.App.CreateSidebarCategory(user.Id, th.BasicTeam.Id, &catData)
|
||||||
|
require.NotNil(t, err, "Should return error due to duplicate IDs")
|
||||||
|
catData.Channels = []string{th.BasicChannel.Id, basicChannel2.Id}
|
||||||
|
cat, err := th.App.CreateSidebarCategory(user.Id, th.BasicTeam.Id, &catData)
|
||||||
|
require.Nil(t, err, "Expected no error")
|
||||||
|
require.NotNil(t, cat, "Expected category object, got nil")
|
||||||
|
createdCategory = cat
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UpdateSidebarCategories", func(t *testing.T) {
|
||||||
|
require.NotNil(t, createdCategory)
|
||||||
|
createdCategory.Channels = []string{th.BasicChannel.Id}
|
||||||
|
updatedCat, err := th.App.UpdateSidebarCategories(user.Id, th.BasicTeam.Id, []*model.SidebarCategoryWithChannels{createdCategory})
|
||||||
|
require.Nil(t, err, "Expected no error")
|
||||||
|
require.NotNil(t, updatedCat, "Expected category object, got nil")
|
||||||
|
require.Len(t, updatedCat, 1)
|
||||||
|
require.Len(t, updatedCat[0].Channels, 1)
|
||||||
|
require.Equal(t, updatedCat[0].Channels[0], th.BasicChannel.Id)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UpdateSidebarCategoryOrder", func(t *testing.T) {
|
||||||
|
err := th.App.UpdateSidebarCategoryOrder(user.Id, th.BasicTeam.Id, []string{th.BasicChannel.Id, basicChannel2.Id})
|
||||||
|
require.NotNil(t, err, "Should return error due to invalid order")
|
||||||
|
|
||||||
|
actualOrder, err := th.App.GetSidebarCategoryOrder(user.Id, th.BasicTeam.Id)
|
||||||
|
require.Nil(t, err, "Should fetch order successfully")
|
||||||
|
|
||||||
|
actualOrder[2], actualOrder[3] = actualOrder[3], actualOrder[2]
|
||||||
|
err = th.App.UpdateSidebarCategoryOrder(user.Id, th.BasicTeam.Id, actualOrder)
|
||||||
|
require.Nil(t, err, "Should update order successfully")
|
||||||
|
|
||||||
|
actualOrder[2] = "asd"
|
||||||
|
err = th.App.UpdateSidebarCategoryOrder(user.Id, th.BasicTeam.Id, actualOrder)
|
||||||
|
require.NotNil(t, err, "Should return error due to invalid id")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("GetSidebarCategoryOrder", func(t *testing.T) {
|
||||||
|
catOrder, err := th.App.GetSidebarCategoryOrder(user.Id, th.BasicTeam.Id)
|
||||||
|
require.Nil(t, err, "Expected no error")
|
||||||
|
require.Len(t, catOrder, 4)
|
||||||
|
require.Equal(t, catOrder[1], createdCategory.Id, "the newly created category should be after favorites")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSidebarCategories(t *testing.T) {
|
||||||
|
t.Run("should return the sidebar categories for the given user/team", func(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
_, err := th.App.CreateSidebarCategory(th.BasicUser.Id, th.BasicTeam.Id, &model.SidebarCategoryWithChannels{
|
||||||
|
SidebarCategory: model.SidebarCategory{
|
||||||
|
UserId: th.BasicUser.Id,
|
||||||
|
TeamId: th.BasicTeam.Id,
|
||||||
|
DisplayName: "new category",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
categories, err := th.App.GetSidebarCategories(th.BasicUser.Id, th.BasicTeam.Id)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Len(t, categories.Categories, 4)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should create the initial categories even if migration hasn't ran yet", func(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
// Manually add the user to the team without going through the app layer to simulate a pre-existing user/team
|
||||||
|
// relationship that hasn't been migrated yet
|
||||||
|
team := th.CreateTeam()
|
||||||
|
_, err := th.App.Srv().Store.Team().SaveMember(&model.TeamMember{
|
||||||
|
TeamId: team.Id,
|
||||||
|
UserId: th.BasicUser.Id,
|
||||||
|
SchemeUser: true,
|
||||||
|
}, 100)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
categories, err := th.App.GetSidebarCategories(th.BasicUser.Id, team.Id)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Len(t, categories.Categories, 3)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should return a store error if a db table is missing", func(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
// Temporarily renaming a table to force a DB error.
|
||||||
|
sqlSupplier := mainHelper.GetSQLSupplier()
|
||||||
|
_, err := sqlSupplier.GetMaster().Exec("ALTER TABLE SidebarCategories RENAME TO SidebarCategoriesTest")
|
||||||
|
require.Nil(t, err)
|
||||||
|
defer func() {
|
||||||
|
_, err := sqlSupplier.GetMaster().Exec("ALTER TABLE SidebarCategoriesTest RENAME TO SidebarCategories")
|
||||||
|
require.Nil(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
categories, appErr := th.App.GetSidebarCategories(th.BasicUser.Id, th.BasicTeam.Id)
|
||||||
|
assert.Nil(t, categories)
|
||||||
|
assert.NotNil(t, appErr)
|
||||||
|
assert.Equal(t, "store.sql_channel.sidebar_categories.app_error", appErr.Id)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1898,124 +1898,3 @@ func TestClearChannelMembersCache(t *testing.T) {
|
|||||||
|
|
||||||
th.App.ClearChannelMembersCache("channelID")
|
th.App.ClearChannelMembersCache("channelID")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSidebarCategory(t *testing.T) {
|
|
||||||
th := Setup(t).InitBasic()
|
|
||||||
defer th.TearDown()
|
|
||||||
|
|
||||||
basicChannel2 := th.CreateChannel(th.BasicTeam)
|
|
||||||
defer th.App.PermanentDeleteChannel(basicChannel2)
|
|
||||||
user := th.CreateUser()
|
|
||||||
defer th.App.Srv().Store.User().PermanentDelete(user.Id)
|
|
||||||
th.LinkUserToTeam(user, th.BasicTeam)
|
|
||||||
th.AddUserToChannel(user, basicChannel2)
|
|
||||||
|
|
||||||
var createdCategory *model.SidebarCategoryWithChannels
|
|
||||||
t.Run("CreateSidebarCategory", func(t *testing.T) {
|
|
||||||
catData := model.SidebarCategoryWithChannels{
|
|
||||||
SidebarCategory: model.SidebarCategory{
|
|
||||||
DisplayName: "TEST",
|
|
||||||
},
|
|
||||||
Channels: []string{th.BasicChannel.Id, basicChannel2.Id, basicChannel2.Id},
|
|
||||||
}
|
|
||||||
_, err := th.App.CreateSidebarCategory(user.Id, th.BasicTeam.Id, &catData)
|
|
||||||
require.NotNil(t, err, "Should return error due to duplicate IDs")
|
|
||||||
catData.Channels = []string{th.BasicChannel.Id, basicChannel2.Id}
|
|
||||||
cat, err := th.App.CreateSidebarCategory(user.Id, th.BasicTeam.Id, &catData)
|
|
||||||
require.Nil(t, err, "Expected no error")
|
|
||||||
require.NotNil(t, cat, "Expected category object, got nil")
|
|
||||||
createdCategory = cat
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("UpdateSidebarCategories", func(t *testing.T) {
|
|
||||||
require.NotNil(t, createdCategory)
|
|
||||||
createdCategory.Channels = []string{th.BasicChannel.Id}
|
|
||||||
updatedCat, err := th.App.UpdateSidebarCategories(user.Id, th.BasicTeam.Id, []*model.SidebarCategoryWithChannels{createdCategory})
|
|
||||||
require.Nil(t, err, "Expected no error")
|
|
||||||
require.NotNil(t, updatedCat, "Expected category object, got nil")
|
|
||||||
require.Len(t, updatedCat, 1)
|
|
||||||
require.Len(t, updatedCat[0].Channels, 1)
|
|
||||||
require.Equal(t, updatedCat[0].Channels[0], th.BasicChannel.Id)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("UpdateSidebarCategoryOrder", func(t *testing.T) {
|
|
||||||
err := th.App.UpdateSidebarCategoryOrder(user.Id, th.BasicTeam.Id, []string{th.BasicChannel.Id, basicChannel2.Id})
|
|
||||||
require.NotNil(t, err, "Should return error due to invalid order")
|
|
||||||
|
|
||||||
actualOrder, err := th.App.GetSidebarCategoryOrder(user.Id, th.BasicTeam.Id)
|
|
||||||
require.Nil(t, err, "Should fetch order successfully")
|
|
||||||
|
|
||||||
actualOrder[2], actualOrder[3] = actualOrder[3], actualOrder[2]
|
|
||||||
err = th.App.UpdateSidebarCategoryOrder(user.Id, th.BasicTeam.Id, actualOrder)
|
|
||||||
require.Nil(t, err, "Should update order successfully")
|
|
||||||
|
|
||||||
actualOrder[2] = "asd"
|
|
||||||
err = th.App.UpdateSidebarCategoryOrder(user.Id, th.BasicTeam.Id, actualOrder)
|
|
||||||
require.NotNil(t, err, "Should return error due to invalid id")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetSidebarCategoryOrder", func(t *testing.T) {
|
|
||||||
catOrder, err := th.App.GetSidebarCategoryOrder(user.Id, th.BasicTeam.Id)
|
|
||||||
require.Nil(t, err, "Expected no error")
|
|
||||||
require.Len(t, catOrder, 4)
|
|
||||||
require.Equal(t, catOrder[1], createdCategory.Id, "the newly created category should be after favorites")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetSidebarCategories(t *testing.T) {
|
|
||||||
t.Run("should return the sidebar categories for the given user/team", func(t *testing.T) {
|
|
||||||
th := Setup(t).InitBasic()
|
|
||||||
defer th.TearDown()
|
|
||||||
|
|
||||||
_, err := th.App.CreateSidebarCategory(th.BasicUser.Id, th.BasicTeam.Id, &model.SidebarCategoryWithChannels{
|
|
||||||
SidebarCategory: model.SidebarCategory{
|
|
||||||
UserId: th.BasicUser.Id,
|
|
||||||
TeamId: th.BasicTeam.Id,
|
|
||||||
DisplayName: "new category",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
categories, err := th.App.GetSidebarCategories(th.BasicUser.Id, th.BasicTeam.Id)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Len(t, categories.Categories, 4)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should create the initial categories even if migration hasn't ran yet", func(t *testing.T) {
|
|
||||||
th := Setup(t).InitBasic()
|
|
||||||
defer th.TearDown()
|
|
||||||
|
|
||||||
// Manually add the user to the team without going through the app layer to simulate a pre-existing user/team
|
|
||||||
// relationship that hasn't been migrated yet
|
|
||||||
team := th.CreateTeam()
|
|
||||||
_, err := th.App.Srv().Store.Team().SaveMember(&model.TeamMember{
|
|
||||||
TeamId: team.Id,
|
|
||||||
UserId: th.BasicUser.Id,
|
|
||||||
SchemeUser: true,
|
|
||||||
}, 100)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
categories, err := th.App.GetSidebarCategories(th.BasicUser.Id, team.Id)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Len(t, categories.Categories, 3)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should return a store error if a db table is missing", func(t *testing.T) {
|
|
||||||
th := Setup(t).InitBasic()
|
|
||||||
defer th.TearDown()
|
|
||||||
|
|
||||||
// Temporarily renaming a table to force a DB error.
|
|
||||||
sqlSupplier := mainHelper.GetSQLSupplier()
|
|
||||||
_, err := sqlSupplier.GetMaster().Exec("ALTER TABLE SidebarCategories RENAME TO SidebarCategoriesTest")
|
|
||||||
require.Nil(t, err)
|
|
||||||
defer func() {
|
|
||||||
_, err := sqlSupplier.GetMaster().Exec("ALTER TABLE SidebarCategoriesTest RENAME TO SidebarCategories")
|
|
||||||
require.Nil(t, err)
|
|
||||||
}()
|
|
||||||
|
|
||||||
categories, appErr := th.App.GetSidebarCategories(th.BasicUser.Id, th.BasicTeam.Id)
|
|
||||||
assert.Nil(t, categories)
|
|
||||||
assert.NotNil(t, appErr)
|
|
||||||
assert.Equal(t, "store.sql_channel.sidebar_categories.app_error", appErr.Id)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -436,210 +436,6 @@ func (s SqlChannelStore) createIndexesIfNotExists() {
|
|||||||
s.CreateIndexIfNotExists("idx_channels_scheme_id", "Channels", "SchemeId")
|
s.CreateIndexIfNotExists("idx_channels_scheme_id", "Channels", "SchemeId")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SqlChannelStore) CreateInitialSidebarCategories(userId, teamId string) error {
|
|
||||||
transaction, err := s.GetMaster().Begin()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "CreateInitialSidebarCategories: begin_transaction")
|
|
||||||
}
|
|
||||||
defer finalizeTransaction(transaction)
|
|
||||||
|
|
||||||
if err := s.createInitialSidebarCategoriesT(transaction, userId, teamId); err != nil {
|
|
||||||
return errors.Wrap(err, "CreateInitialSidebarCategories: createInitialSidebarCategoriesT")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := transaction.Commit(); err != nil {
|
|
||||||
return errors.Wrap(err, "CreateInitialSidebarCategories: commit_transaction")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) createInitialSidebarCategoriesT(transaction *gorp.Transaction, userId, teamId string) error {
|
|
||||||
selectQuery, selectParams, _ := s.getQueryBuilder().
|
|
||||||
Select("Type").
|
|
||||||
From("SidebarCategories").
|
|
||||||
Where(sq.Eq{
|
|
||||||
"UserId": userId,
|
|
||||||
"TeamId": teamId,
|
|
||||||
"Type": []model.SidebarCategoryType{model.SidebarCategoryFavorites, model.SidebarCategoryChannels, model.SidebarCategoryDirectMessages},
|
|
||||||
}).ToSql()
|
|
||||||
|
|
||||||
var existingTypes []model.SidebarCategoryType
|
|
||||||
_, err := transaction.Select(&existingTypes, selectQuery, selectParams...)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to select existing categories")
|
|
||||||
}
|
|
||||||
|
|
||||||
hasCategoryOfType := make(map[model.SidebarCategoryType]bool, len(existingTypes))
|
|
||||||
for _, existingType := range existingTypes {
|
|
||||||
hasCategoryOfType[existingType] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasCategoryOfType[model.SidebarCategoryFavorites] {
|
|
||||||
favoritesCategoryId := model.NewId()
|
|
||||||
|
|
||||||
// Create the SidebarChannels first since there's more opportunity for something to fail here
|
|
||||||
if err := s.migrateFavoritesToSidebarT(transaction, userId, teamId, favoritesCategoryId); err != nil {
|
|
||||||
return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to migrate favorites to sidebar")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := transaction.Insert(&model.SidebarCategory{
|
|
||||||
DisplayName: "Favorites", // This will be retranslated by the client into the user's locale
|
|
||||||
Id: favoritesCategoryId,
|
|
||||||
UserId: userId,
|
|
||||||
TeamId: teamId,
|
|
||||||
Sorting: model.SidebarCategorySortDefault,
|
|
||||||
SortOrder: model.DefaultSidebarSortOrderFavorites,
|
|
||||||
Type: model.SidebarCategoryFavorites,
|
|
||||||
}); err != nil {
|
|
||||||
return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to insert favorites category")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasCategoryOfType[model.SidebarCategoryChannels] {
|
|
||||||
if err := transaction.Insert(&model.SidebarCategory{
|
|
||||||
DisplayName: "Channels", // This will be retranslateed by the client into the user's locale
|
|
||||||
Id: model.NewId(),
|
|
||||||
UserId: userId,
|
|
||||||
TeamId: teamId,
|
|
||||||
Sorting: model.SidebarCategorySortDefault,
|
|
||||||
SortOrder: model.DefaultSidebarSortOrderChannels,
|
|
||||||
Type: model.SidebarCategoryChannels,
|
|
||||||
}); err != nil {
|
|
||||||
return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to insert channels category")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasCategoryOfType[model.SidebarCategoryDirectMessages] {
|
|
||||||
if err := transaction.Insert(&model.SidebarCategory{
|
|
||||||
DisplayName: "Direct Messages", // This will be retranslateed by the client into the user's locale
|
|
||||||
Id: model.NewId(),
|
|
||||||
UserId: userId,
|
|
||||||
TeamId: teamId,
|
|
||||||
Sorting: model.SidebarCategorySortRecent,
|
|
||||||
SortOrder: model.DefaultSidebarSortOrderDMs,
|
|
||||||
Type: model.SidebarCategoryDirectMessages,
|
|
||||||
}); err != nil {
|
|
||||||
return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to insert direct messages category")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type userMembership struct {
|
|
||||||
UserId string
|
|
||||||
ChannelId string
|
|
||||||
CategoryId string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) migrateMembershipToSidebar(transaction *gorp.Transaction, runningOrder *int64, sql string, args ...interface{}) ([]userMembership, error) {
|
|
||||||
var memberships []userMembership
|
|
||||||
if _, err := transaction.Select(&memberships, sql, args...); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, favorite := range memberships {
|
|
||||||
sql, args, _ := s.getQueryBuilder().
|
|
||||||
Insert("SidebarChannels").
|
|
||||||
Columns("ChannelId", "UserId", "CategoryId", "SortOrder").
|
|
||||||
Values(favorite.ChannelId, favorite.UserId, favorite.CategoryId, *runningOrder).ToSql()
|
|
||||||
|
|
||||||
if _, err := transaction.Exec(sql, args...); err != nil && !IsUniqueConstraintError(err, []string{"UserId", "PRIMARY"}) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
*runningOrder = *runningOrder + model.MinimalSidebarSortDistance
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := transaction.Commit(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return memberships, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) migrateFavoritesToSidebarT(transaction *gorp.Transaction, userId, teamId, favoritesCategoryId string) error {
|
|
||||||
favoritesQuery, favoritesParams, _ := s.getQueryBuilder().
|
|
||||||
Select("Preferences.Name").
|
|
||||||
From("Preferences").
|
|
||||||
Join("Channels on Preferences.Name = Channels.Id").
|
|
||||||
Join("ChannelMembers on Preferences.Name = ChannelMembers.ChannelId and Preferences.UserId = ChannelMembers.UserId").
|
|
||||||
Where(sq.Eq{
|
|
||||||
"Preferences.UserId": userId,
|
|
||||||
"Preferences.Category": model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL,
|
|
||||||
"Preferences.Value": "true",
|
|
||||||
}).
|
|
||||||
Where(sq.Or{
|
|
||||||
sq.Eq{"Channels.TeamId": teamId},
|
|
||||||
sq.Eq{"Channels.TeamId": ""},
|
|
||||||
}).
|
|
||||||
OrderBy(
|
|
||||||
"Channels.DisplayName",
|
|
||||||
"Channels.Name ASC",
|
|
||||||
).ToSql()
|
|
||||||
|
|
||||||
var favoriteChannelIds []string
|
|
||||||
if _, err := transaction.Select(&favoriteChannelIds, favoritesQuery, favoritesParams...); err != nil {
|
|
||||||
return errors.Wrap(err, "migrateFavoritesToSidebarT: unable to get favorite channel IDs")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, channelId := range favoriteChannelIds {
|
|
||||||
if err := transaction.Insert(&model.SidebarChannel{
|
|
||||||
ChannelId: channelId,
|
|
||||||
CategoryId: favoritesCategoryId,
|
|
||||||
UserId: userId,
|
|
||||||
SortOrder: int64(i * model.MinimalSidebarSortDistance),
|
|
||||||
}); err != nil {
|
|
||||||
return errors.Wrap(err, "migrateFavoritesToSidebarT: unable to insert SidebarChannel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MigrateFavoritesToSidebarChannels populates the SidebarChannels table by analyzing existing user preferences for favorites
|
|
||||||
// **IMPORTANT** This function should only be called from the migration task and shouldn't be used by itself
|
|
||||||
func (s SqlChannelStore) MigrateFavoritesToSidebarChannels(lastUserId string, runningOrder int64) (map[string]interface{}, error) {
|
|
||||||
transaction, err := s.GetMaster().Begin()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer finalizeTransaction(transaction)
|
|
||||||
|
|
||||||
sb := s.
|
|
||||||
getQueryBuilder().
|
|
||||||
Select("Preferences.UserId", "Preferences.Name AS ChannelId", "SidebarCategories.Id AS CategoryId").
|
|
||||||
From("Preferences").
|
|
||||||
Where(sq.And{
|
|
||||||
sq.Eq{"Preferences.Category": model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL},
|
|
||||||
sq.NotEq{"Preferences.Value": "false"},
|
|
||||||
sq.NotEq{"SidebarCategories.Id": nil},
|
|
||||||
sq.Gt{"Preferences.UserId": lastUserId},
|
|
||||||
}).
|
|
||||||
LeftJoin("Channels ON (Channels.Id=Preferences.Name)").
|
|
||||||
LeftJoin("SidebarCategories ON (SidebarCategories.UserId=Preferences.UserId AND SidebarCategories.Type='"+string(model.SidebarCategoryFavorites)+"' AND (SidebarCategories.TeamId=Channels.TeamId OR Channels.TeamId=''))").
|
|
||||||
OrderBy("Preferences.UserId", "Channels.Name DESC").
|
|
||||||
Limit(100)
|
|
||||||
|
|
||||||
sql, args, err := sb.ToSql()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
userFavorites, err := s.migrateMembershipToSidebar(transaction, &runningOrder, sql, args...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(userFavorites) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
data := make(map[string]interface{})
|
|
||||||
data["UserId"] = userFavorites[len(userFavorites)-1].UserId
|
|
||||||
data["SortOrder"] = runningOrder
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MigratePublicChannels initializes the PublicChannels table with data created before this version
|
// MigratePublicChannels initializes the PublicChannels table with data created before this version
|
||||||
// of the Mattermost server kept it up-to-date.
|
// of the Mattermost server kept it up-to-date.
|
||||||
func (s SqlChannelStore) MigratePublicChannels() error {
|
func (s SqlChannelStore) MigratePublicChannels() error {
|
||||||
@@ -3530,759 +3326,3 @@ func (s SqlChannelStore) GroupSyncedChannelCount() (int64, *model.AppError) {
|
|||||||
|
|
||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type sidebarCategoryForJoin struct {
|
|
||||||
model.SidebarCategory
|
|
||||||
ChannelId *string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) CreateSidebarCategory(userId, teamId string, newCategory *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, *model.AppError) {
|
|
||||||
transaction, err := s.GetMaster().Begin()
|
|
||||||
if err != nil {
|
|
||||||
return nil, model.NewAppError("SqlChannelStore.CreateSidebarCategory", "store.sql_channel.sidebar_categories.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer finalizeTransaction(transaction)
|
|
||||||
|
|
||||||
categoriesWithOrder, appErr := s.getSidebarCategoriesT(transaction, userId, teamId)
|
|
||||||
if appErr != nil {
|
|
||||||
return nil, appErr
|
|
||||||
}
|
|
||||||
if len(categoriesWithOrder.Categories) < 1 {
|
|
||||||
return nil, model.NewAppError("SqlChannelStore.CreateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, "", http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
newOrder := categoriesWithOrder.Order
|
|
||||||
newCategoryId := model.NewId()
|
|
||||||
newCategorySortOrder := 0
|
|
||||||
/*
|
|
||||||
When a new category is created, it should be placed as follows:
|
|
||||||
1. If the Favorites category is first, the new category should be placed after it
|
|
||||||
2. Otherwise, the new category should be placed first.
|
|
||||||
*/
|
|
||||||
if categoriesWithOrder.Categories[0].Type == model.SidebarCategoryFavorites {
|
|
||||||
newOrder = append([]string{newOrder[0], newCategoryId}, newOrder[1:]...)
|
|
||||||
newCategorySortOrder = model.MinimalSidebarSortDistance
|
|
||||||
} else {
|
|
||||||
newOrder = append([]string{newCategoryId}, newOrder...)
|
|
||||||
}
|
|
||||||
|
|
||||||
category := &model.SidebarCategory{
|
|
||||||
DisplayName: newCategory.DisplayName,
|
|
||||||
Id: newCategoryId,
|
|
||||||
UserId: userId,
|
|
||||||
TeamId: teamId,
|
|
||||||
Sorting: model.SidebarCategorySortDefault,
|
|
||||||
SortOrder: int64(model.MinimalSidebarSortDistance * len(newOrder)), // first we place it at the end of the list
|
|
||||||
Type: model.SidebarCategoryCustom,
|
|
||||||
}
|
|
||||||
if err = transaction.Insert(category); err != nil {
|
|
||||||
return nil, model.NewAppError("SqlPostStore.CreateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(newCategory.Channels) > 0 {
|
|
||||||
channelIdsKeys, deleteParams := MapStringsToQueryParams(newCategory.Channels, "ChannelId")
|
|
||||||
deleteParams["UserId"] = userId
|
|
||||||
deleteParams["TeamId"] = teamId
|
|
||||||
|
|
||||||
// Remove any channels from their previous categories and add them to the new one
|
|
||||||
var deleteQuery string
|
|
||||||
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
|
|
||||||
deleteQuery = `
|
|
||||||
DELETE
|
|
||||||
SidebarChannels
|
|
||||||
FROM
|
|
||||||
SidebarChannels
|
|
||||||
JOIN
|
|
||||||
SidebarCategories ON SidebarChannels.CategoryId = SidebarCategories.Id
|
|
||||||
WHERE
|
|
||||||
SidebarChannels.UserId = :UserId
|
|
||||||
AND SidebarChannels.ChannelId IN ` + channelIdsKeys + `
|
|
||||||
AND SidebarCategories.TeamId = :TeamId`
|
|
||||||
} else {
|
|
||||||
deleteQuery = `
|
|
||||||
DELETE FROM
|
|
||||||
SidebarChannels
|
|
||||||
USING
|
|
||||||
SidebarCategories
|
|
||||||
WHERE
|
|
||||||
SidebarChannels.CategoryId = SidebarCategories.Id
|
|
||||||
AND SidebarChannels.UserId = :UserId
|
|
||||||
AND SidebarChannels.ChannelId IN ` + channelIdsKeys + `
|
|
||||||
AND SidebarCategories.TeamId = :TeamId`
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = transaction.Exec(deleteQuery, deleteParams)
|
|
||||||
if err != nil {
|
|
||||||
return nil, model.NewAppError("SqlPostStore.CreateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
var channels []interface{}
|
|
||||||
for i, channelID := range newCategory.Channels {
|
|
||||||
channels = append(channels, &model.SidebarChannel{
|
|
||||||
ChannelId: channelID,
|
|
||||||
CategoryId: newCategoryId,
|
|
||||||
SortOrder: int64(i * model.MinimalSidebarSortDistance),
|
|
||||||
UserId: userId,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if err = transaction.Insert(channels...); err != nil {
|
|
||||||
return nil, model.NewAppError("SqlPostStore.CreateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// now we re-order the categories according to the new order
|
|
||||||
if appErr := s.updateSidebarCategoryOrderT(transaction, userId, teamId, newOrder); appErr != nil {
|
|
||||||
return nil, appErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = transaction.Commit(); err != nil {
|
|
||||||
return nil, model.NewAppError("SqlChannelStore.CreateSidebarCategory", "store.sql_channel.sidebar_categories.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// patch category to return proper sort order
|
|
||||||
category.SortOrder = int64(newCategorySortOrder)
|
|
||||||
result := &model.SidebarCategoryWithChannels{
|
|
||||||
SidebarCategory: *category,
|
|
||||||
Channels: newCategory.Channels,
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) completePopulatingCategoryChannels(category *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, *model.AppError) {
|
|
||||||
transaction, err := s.GetMaster().Begin()
|
|
||||||
if err != nil {
|
|
||||||
return nil, model.NewAppError("SqlChannelStore.completePopulatingCategoryChannels", "store.sql_channel.sidebar_categories.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
defer finalizeTransaction(transaction)
|
|
||||||
|
|
||||||
result, appErr := s.completePopulatingCategoryChannelsT(transaction, category)
|
|
||||||
if appErr != nil {
|
|
||||||
return nil, appErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = transaction.Commit(); err != nil {
|
|
||||||
return nil, model.NewAppError("SqlChannelStore.completePopulatingCategoryChannels", "store.sql_channel.sidebar_categories.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) completePopulatingCategoryChannelsT(transation *gorp.Transaction, category *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, *model.AppError) {
|
|
||||||
if category.Type == model.SidebarCategoryCustom || category.Type == model.SidebarCategoryFavorites {
|
|
||||||
return category, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var channelTypeFilter sq.Sqlizer
|
|
||||||
if category.Type == model.SidebarCategoryDirectMessages {
|
|
||||||
// any DM/GM channels that aren't in any category should be returned as part of the Direct Messages category
|
|
||||||
channelTypeFilter = sq.Eq{"Channels.Type": []string{model.CHANNEL_DIRECT, model.CHANNEL_GROUP}}
|
|
||||||
} else if category.Type == model.SidebarCategoryChannels {
|
|
||||||
// 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
|
|
||||||
channelTypeFilter = sq.And{
|
|
||||||
sq.Eq{"Channels.Type": []string{model.CHANNEL_OPEN, model.CHANNEL_PRIVATE}},
|
|
||||||
sq.Eq{"Channels.TeamId": category.TeamId},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A subquery that is true if the channel does not have a SidebarChannel entry for the current user on the current team
|
|
||||||
doesNotHaveSidebarChannel := sq.Select("1").
|
|
||||||
Prefix("NOT EXISTS (").
|
|
||||||
From("SidebarChannels").
|
|
||||||
Join("SidebarCategories on SidebarChannels.CategoryId=SidebarCategories.Id").
|
|
||||||
Where(sq.And{
|
|
||||||
sq.Expr("SidebarChannels.ChannelId = ChannelMembers.ChannelId"),
|
|
||||||
sq.Eq{"SidebarCategories.UserId": category.UserId},
|
|
||||||
sq.Eq{"SidebarCategories.TeamId": category.TeamId},
|
|
||||||
}).
|
|
||||||
Suffix(")")
|
|
||||||
|
|
||||||
var channels []string
|
|
||||||
sql, args, _ := s.getQueryBuilder().
|
|
||||||
Select("Id").
|
|
||||||
From("ChannelMembers").
|
|
||||||
LeftJoin("Channels ON Channels.Id=ChannelMembers.ChannelId").
|
|
||||||
Where(sq.And{
|
|
||||||
sq.Eq{"ChannelMembers.UserId": category.UserId},
|
|
||||||
channelTypeFilter,
|
|
||||||
sq.Eq{"Channels.DeleteAt": 0},
|
|
||||||
doesNotHaveSidebarChannel,
|
|
||||||
}).
|
|
||||||
OrderBy("DisplayName ASC").ToSql()
|
|
||||||
|
|
||||||
if _, err := transation.Select(&channels, sql, args...); err != nil {
|
|
||||||
return nil, model.NewAppError("SqlPostStore.completePopulatingCategoryChannelsT", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
category.Channels = append(channels, category.Channels...)
|
|
||||||
return category, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) GetSidebarCategory(categoryId string) (*model.SidebarCategoryWithChannels, *model.AppError) {
|
|
||||||
var categories []*sidebarCategoryForJoin
|
|
||||||
sql, args, _ := s.getQueryBuilder().
|
|
||||||
Select("SidebarCategories.*", "SidebarChannels.ChannelId").
|
|
||||||
From("SidebarCategories").
|
|
||||||
LeftJoin("SidebarChannels ON SidebarChannels.CategoryId=SidebarCategories.Id").
|
|
||||||
Where(sq.Eq{"SidebarCategories.Id": categoryId}).
|
|
||||||
OrderBy("SidebarChannels.SortOrder ASC").ToSql()
|
|
||||||
if _, err := s.GetReplica().Select(&categories, sql, args...); err != nil {
|
|
||||||
return nil, model.NewAppError("SqlPostStore.GetSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusNotFound)
|
|
||||||
}
|
|
||||||
result := &model.SidebarCategoryWithChannels{
|
|
||||||
SidebarCategory: categories[0].SidebarCategory,
|
|
||||||
Channels: make([]string, 0),
|
|
||||||
}
|
|
||||||
for _, category := range categories {
|
|
||||||
if category.ChannelId != nil {
|
|
||||||
result.Channels = append(result.Channels, *category.ChannelId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s.completePopulatingCategoryChannels(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) getSidebarCategoriesT(transaction *gorp.Transaction, userId, teamId string) (*model.OrderedSidebarCategories, *model.AppError) {
|
|
||||||
oc := model.OrderedSidebarCategories{
|
|
||||||
Categories: make(model.SidebarCategoriesWithChannels, 0),
|
|
||||||
Order: make([]string, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
var categories []*sidebarCategoryForJoin
|
|
||||||
sql, args, _ := s.getQueryBuilder().
|
|
||||||
Select("SidebarCategories.*", "SidebarChannels.ChannelId").
|
|
||||||
From("SidebarCategories").
|
|
||||||
LeftJoin("SidebarChannels ON SidebarChannels.CategoryId=Id").
|
|
||||||
Where(sq.And{
|
|
||||||
sq.Eq{"SidebarCategories.UserId": userId},
|
|
||||||
sq.Eq{"SidebarCategories.TeamId": teamId},
|
|
||||||
}).
|
|
||||||
OrderBy("SidebarCategories.SortOrder ASC, SidebarChannels.SortOrder ASC").ToSql()
|
|
||||||
|
|
||||||
if _, err := transaction.Select(&categories, sql, args...); err != nil {
|
|
||||||
return nil, model.NewAppError("SqlPostStore.GetSidebarCategories", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusNotFound)
|
|
||||||
}
|
|
||||||
for _, category := range categories {
|
|
||||||
var prevCategory *model.SidebarCategoryWithChannels
|
|
||||||
for _, existing := range oc.Categories {
|
|
||||||
if existing.Id == category.Id {
|
|
||||||
prevCategory = existing
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if prevCategory == nil {
|
|
||||||
prevCategory = &model.SidebarCategoryWithChannels{
|
|
||||||
SidebarCategory: category.SidebarCategory,
|
|
||||||
Channels: make([]string, 0),
|
|
||||||
}
|
|
||||||
oc.Categories = append(oc.Categories, prevCategory)
|
|
||||||
oc.Order = append(oc.Order, category.Id)
|
|
||||||
}
|
|
||||||
if category.ChannelId != nil {
|
|
||||||
prevCategory.Channels = append(prevCategory.Channels, *category.ChannelId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, category := range oc.Categories {
|
|
||||||
if _, err := s.completePopulatingCategoryChannelsT(transaction, category); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &oc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) GetSidebarCategories(userId, teamId string) (*model.OrderedSidebarCategories, *model.AppError) {
|
|
||||||
transaction, err := s.GetMaster().Begin()
|
|
||||||
if err != nil {
|
|
||||||
return nil, model.NewAppError("SqlChannelStore.GetSidebarCategories", "store.sql_channel.sidebar_categories.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer finalizeTransaction(transaction)
|
|
||||||
|
|
||||||
oc, appErr := s.getSidebarCategoriesT(transaction, userId, teamId)
|
|
||||||
if appErr != nil {
|
|
||||||
return nil, appErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = transaction.Commit(); err != nil {
|
|
||||||
return nil, model.NewAppError("SqlChannelStore.GetSidebarCategories", "store.sql_channel.sidebar_categories.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
return oc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) GetSidebarCategoryOrder(userId, teamId string) ([]string, *model.AppError) {
|
|
||||||
var ids []string
|
|
||||||
|
|
||||||
sql, args, _ := s.getQueryBuilder().
|
|
||||||
Select("Id").
|
|
||||||
From("SidebarCategories").
|
|
||||||
Where(sq.And{
|
|
||||||
sq.Eq{"UserId": userId},
|
|
||||||
sq.Eq{"TeamId": teamId},
|
|
||||||
}).
|
|
||||||
OrderBy("SidebarCategories.SortOrder ASC").ToSql()
|
|
||||||
|
|
||||||
if _, err := s.GetReplica().Select(&ids, sql, args...); err != nil {
|
|
||||||
return nil, model.NewAppError("SqlPostStore.GetSidebarCategoryOrder", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusNotFound)
|
|
||||||
}
|
|
||||||
return ids, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) updateSidebarCategoryOrderT(transaction *gorp.Transaction, userId, teamId string, categoryOrder []string) *model.AppError {
|
|
||||||
var newOrder []interface{}
|
|
||||||
runningOrder := 0
|
|
||||||
for _, categoryId := range categoryOrder {
|
|
||||||
newOrder = append(newOrder, &model.SidebarCategory{
|
|
||||||
Id: categoryId,
|
|
||||||
SortOrder: int64(runningOrder),
|
|
||||||
})
|
|
||||||
runningOrder += model.MinimalSidebarSortDistance
|
|
||||||
}
|
|
||||||
|
|
||||||
// There's a bug in gorp where UpdateColumns messes up the stored query for any other attempt to use .Update or
|
|
||||||
// .UpdateColumns on this table, so it's okay to use here as long as we don't use those methods for SidebarCategories
|
|
||||||
// anywhere else.
|
|
||||||
if _, err := transaction.UpdateColumns(func(col *gorp.ColumnMap) bool {
|
|
||||||
return col.ColumnName == "SortOrder"
|
|
||||||
}, newOrder...); err != nil {
|
|
||||||
return model.NewAppError("SqlPostStore.UpdateSidebarCategoryOrder", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) UpdateSidebarCategoryOrder(userId, teamId string, categoryOrder []string) *model.AppError {
|
|
||||||
transaction, err := s.GetMaster().Begin()
|
|
||||||
if err != nil {
|
|
||||||
return model.NewAppError("SqlChannelStore.UpdateSidebarCategoryOrder", "store.sql_channel.sidebar_categories.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer finalizeTransaction(transaction)
|
|
||||||
|
|
||||||
// Ensure no invalid categories are included and that no categories are left out
|
|
||||||
existingOrder, appErr := s.GetSidebarCategoryOrder(userId, teamId)
|
|
||||||
if appErr != nil {
|
|
||||||
return appErr
|
|
||||||
}
|
|
||||||
if len(existingOrder) != len(categoryOrder) {
|
|
||||||
return model.NewAppError("SqlPostStore.UpdateSidebarCategoryOrder", "store.sql_channel.sidebar_categories.app_error", nil, "Cannot update category order, passed list of categories different size than in DB", http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
for _, originalCategoryId := range existingOrder {
|
|
||||||
found := false
|
|
||||||
for _, newCategoryId := range categoryOrder {
|
|
||||||
if newCategoryId == originalCategoryId {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return model.NewAppError("SqlPostStore.UpdateSidebarCategoryOrder", "store.sql_channel.sidebar_categories.app_error", nil, "Cannot update category order, passed list of categories contains unrecognized category IDs", http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if appErr := s.updateSidebarCategoryOrderT(transaction, userId, teamId, categoryOrder); appErr != nil {
|
|
||||||
return appErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = transaction.Commit(); err != nil {
|
|
||||||
return model.NewAppError("SqlChannelStore.UpdateSidebarCategoryOrder", "store.sql_channel.sidebar_categories.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) UpdateSidebarCategories(userId, teamId string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError) {
|
|
||||||
transaction, err := s.GetMaster().Begin()
|
|
||||||
if err != nil {
|
|
||||||
return nil, model.NewAppError("SqlChannelStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
defer finalizeTransaction(transaction)
|
|
||||||
|
|
||||||
updatedCategories := []*model.SidebarCategoryWithChannels{}
|
|
||||||
for _, category := range categories {
|
|
||||||
originalCategory, appErr := s.GetSidebarCategory(category.Id)
|
|
||||||
if appErr != nil {
|
|
||||||
return nil, model.NewAppError("SqlPostStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, appErr.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy category to avoid modifying an argument
|
|
||||||
updatedCategory := &model.SidebarCategoryWithChannels{
|
|
||||||
SidebarCategory: category.SidebarCategory,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent any changes to read-only fields of SidebarCategories
|
|
||||||
updatedCategory.UserId = originalCategory.UserId
|
|
||||||
updatedCategory.TeamId = originalCategory.TeamId
|
|
||||||
updatedCategory.SortOrder = originalCategory.SortOrder
|
|
||||||
updatedCategory.Type = originalCategory.Type
|
|
||||||
|
|
||||||
if updatedCategory.Type != model.SidebarCategoryCustom {
|
|
||||||
updatedCategory.DisplayName = originalCategory.DisplayName
|
|
||||||
}
|
|
||||||
|
|
||||||
if category.Type != model.SidebarCategoryDirectMessages {
|
|
||||||
updatedCategory.Channels = make([]string, len(category.Channels))
|
|
||||||
copy(updatedCategory.Channels, category.Channels)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateQuery, updateParams, _ := s.getQueryBuilder().
|
|
||||||
Update("SidebarCategories").
|
|
||||||
Set("DisplayName", updatedCategory.DisplayName).
|
|
||||||
Set("Sorting", updatedCategory.Sorting).
|
|
||||||
Where(sq.Eq{"Id": updatedCategory.Id}).ToSql()
|
|
||||||
|
|
||||||
if _, err = transaction.Exec(updateQuery, updateParams...); err != nil {
|
|
||||||
return nil, model.NewAppError("SqlPostStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we are updating DM category, it's order can't channel order cannot be changed.
|
|
||||||
if category.Type != model.SidebarCategoryDirectMessages {
|
|
||||||
// Remove any SidebarChannels entries that were either:
|
|
||||||
// - previously in this category (and any ones that are still in the category will be recreated below)
|
|
||||||
// - in another category and are being added to this category
|
|
||||||
sql, args, _ := s.getQueryBuilder().
|
|
||||||
Delete("SidebarChannels").
|
|
||||||
Where(
|
|
||||||
sq.And{
|
|
||||||
sq.Or{
|
|
||||||
sq.Eq{"ChannelId": originalCategory.Channels},
|
|
||||||
sq.Eq{"ChannelId": updatedCategory.Channels},
|
|
||||||
},
|
|
||||||
sq.Eq{"CategoryId": category.Id},
|
|
||||||
},
|
|
||||||
).ToSql()
|
|
||||||
|
|
||||||
if _, err = transaction.Exec(sql, args...); err != nil {
|
|
||||||
return nil, model.NewAppError("SqlPostStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
var channels []interface{}
|
|
||||||
runningOrder := 0
|
|
||||||
for _, channelID := range category.Channels {
|
|
||||||
channels = append(channels, &model.SidebarChannel{
|
|
||||||
ChannelId: channelID,
|
|
||||||
CategoryId: category.Id,
|
|
||||||
SortOrder: int64(runningOrder),
|
|
||||||
UserId: userId,
|
|
||||||
})
|
|
||||||
runningOrder += model.MinimalSidebarSortDistance
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = transaction.Insert(channels...); err != nil {
|
|
||||||
return nil, model.NewAppError("SqlPostStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the favorites preferences based on channels moving into or out of the Favorites category for compatibility
|
|
||||||
if category.Type == model.SidebarCategoryFavorites {
|
|
||||||
// Remove any old favorites
|
|
||||||
sql, args, _ := s.getQueryBuilder().Delete("Preferences").Where(
|
|
||||||
sq.Eq{
|
|
||||||
"UserId": userId,
|
|
||||||
"Name": originalCategory.Channels,
|
|
||||||
"Category": model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL,
|
|
||||||
},
|
|
||||||
).ToSql()
|
|
||||||
|
|
||||||
if _, err = transaction.Exec(sql, args...); err != nil {
|
|
||||||
return nil, model.NewAppError("SqlPostStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// And then add the new ones
|
|
||||||
for _, channelID := range category.Channels {
|
|
||||||
// This breaks the PreferenceStore abstraction, but it should be safe to assume that everything is a SQL
|
|
||||||
// store in this package.
|
|
||||||
if err = s.Preference().(*SqlPreferenceStore).save(transaction, &model.Preference{
|
|
||||||
Name: channelID,
|
|
||||||
UserId: userId,
|
|
||||||
Category: model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL,
|
|
||||||
Value: "true",
|
|
||||||
}); err != nil {
|
|
||||||
return nil, model.NewAppError("SqlPostStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Remove any old favorites that might have been in this category
|
|
||||||
sql, args, _ := s.getQueryBuilder().Delete("Preferences").Where(
|
|
||||||
sq.Eq{
|
|
||||||
"UserId": userId,
|
|
||||||
"Name": category.Channels,
|
|
||||||
"Category": model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL,
|
|
||||||
},
|
|
||||||
).ToSql()
|
|
||||||
|
|
||||||
if _, err = transaction.Exec(sql, args...); err != nil {
|
|
||||||
return nil, model.NewAppError("SqlPostStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedCategories = append(updatedCategories, updatedCategory)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure Channels are populated for Channels/Direct Messages category if they change
|
|
||||||
for i, updatedCategory := range updatedCategories {
|
|
||||||
populated, err := s.completePopulatingCategoryChannelsT(transaction, updatedCategory)
|
|
||||||
if err != nil {
|
|
||||||
return nil, model.NewAppError("SqlPostStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedCategories[i] = populated
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = transaction.Commit(); err != nil {
|
|
||||||
return nil, model.NewAppError("SqlChannelStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
return updatedCategories, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateSidebarChannelsByPreferences is called when the Preference table is being updated to keep SidebarCategories in sync
|
|
||||||
// At the moment, it's only handling Favorites and NOT DMs/GMs (those will be handled client side)
|
|
||||||
func (s SqlChannelStore) UpdateSidebarChannelsByPreferences(preferences *model.Preferences) error {
|
|
||||||
transaction, err := s.GetMaster().Begin()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "UpdateSidebarChannelsByPreferences: begin_transaction")
|
|
||||||
}
|
|
||||||
defer finalizeTransaction(transaction)
|
|
||||||
|
|
||||||
for _, preference := range *preferences {
|
|
||||||
preference := preference
|
|
||||||
|
|
||||||
if preference.Category != model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// if new preference is false - remove the channel from the appropriate sidebar category
|
|
||||||
if preference.Value == "false" {
|
|
||||||
if err := s.removeSidebarEntriesForPreferenceT(transaction, &preference); err != nil {
|
|
||||||
return errors.Wrap(err, "UpdateSidebarChannelsByPreferences: removeSidebarEntriesForPreferenceT")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := s.addChannelToFavoritesCategoryT(transaction, &preference); err != nil {
|
|
||||||
return errors.Wrap(err, "UpdateSidebarChannelsByPreferences: addChannelToFavoritesCategoryT")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := transaction.Commit(); err != nil {
|
|
||||||
return errors.Wrap(err, "UpdateSidebarChannelsByPreferences: commit_transaction")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) removeSidebarEntriesForPreferenceT(transaction *gorp.Transaction, preference *model.Preference) error {
|
|
||||||
if preference.Category != model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete any corresponding SidebarChannels entries in a Favorites category corresponding to this preference. This
|
|
||||||
// can't use the query builder because it uses DB-specific syntax
|
|
||||||
params := map[string]interface{}{
|
|
||||||
"UserId": preference.UserId,
|
|
||||||
"ChannelId": preference.Name,
|
|
||||||
"CategoryType": model.SidebarCategoryFavorites,
|
|
||||||
}
|
|
||||||
var query string
|
|
||||||
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
|
|
||||||
query = `
|
|
||||||
DELETE
|
|
||||||
SidebarChannels
|
|
||||||
FROM
|
|
||||||
SidebarChannels
|
|
||||||
JOIN
|
|
||||||
SidebarCategories ON SidebarChannels.CategoryId = SidebarCategories.Id
|
|
||||||
WHERE
|
|
||||||
SidebarChannels.UserId = :UserId
|
|
||||||
AND SidebarChannels.ChannelId = :ChannelId
|
|
||||||
AND SidebarCategories.Type = :CategoryType`
|
|
||||||
} else {
|
|
||||||
query = `
|
|
||||||
DELETE FROM
|
|
||||||
SidebarChannels
|
|
||||||
USING
|
|
||||||
SidebarCategories
|
|
||||||
WHERE
|
|
||||||
SidebarChannels.CategoryId = SidebarCategories.Id
|
|
||||||
AND SidebarChannels.UserId = :UserId
|
|
||||||
AND SidebarChannels.ChannelId = :ChannelId
|
|
||||||
AND SidebarCategories.Type = :CategoryType`
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := transaction.Exec(query, params); err != nil {
|
|
||||||
return errors.Wrap(err, "Failed to remove sidebar entries for preference")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) addChannelToFavoritesCategoryT(transaction *gorp.Transaction, preference *model.Preference) error {
|
|
||||||
if preference.Category != model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var channel *model.Channel
|
|
||||||
if obj, err := transaction.Get(&model.Channel{}, preference.Name); err != nil {
|
|
||||||
return errors.Wrapf(err, "Failed to get favorited channel with id=%s", preference.Name)
|
|
||||||
} else {
|
|
||||||
channel = obj.(*model.Channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the IDs of the Favorites category/categories that the channel needs to be added to
|
|
||||||
builder := s.getQueryBuilder().
|
|
||||||
Select("SidebarCategories.Id").
|
|
||||||
From("SidebarCategories").
|
|
||||||
LeftJoin("SidebarChannels on SidebarCategories.Id = SidebarChannels.CategoryId and SidebarChannels.ChannelId = ?", preference.Name).
|
|
||||||
Where(sq.Eq{
|
|
||||||
"SidebarCategories.UserId": preference.UserId,
|
|
||||||
"Type": model.SidebarCategoryFavorites,
|
|
||||||
}).
|
|
||||||
Where("SidebarChannels.ChannelId is null")
|
|
||||||
|
|
||||||
if channel.TeamId != "" {
|
|
||||||
builder = builder.Where(sq.Eq{"TeamId": channel.TeamId})
|
|
||||||
}
|
|
||||||
|
|
||||||
idsQuery, idsParams, _ := builder.ToSql()
|
|
||||||
|
|
||||||
var categoryIds []string
|
|
||||||
if _, err := transaction.Select(&categoryIds, idsQuery, idsParams...); err != nil {
|
|
||||||
return errors.Wrap(err, "Failed to get Favorites sidebar categories")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(categoryIds) == 0 {
|
|
||||||
// The channel is already in the Favorites category/categories
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// For each category ID, insert a row into SidebarChannels with the given channel ID and a SortOrder that's less than
|
|
||||||
// all existing SortOrders in the category so that the newly favorited channel comes first
|
|
||||||
insertQuery, insertParams, _ := s.getQueryBuilder().
|
|
||||||
Insert("SidebarChannels").
|
|
||||||
Columns(
|
|
||||||
"ChannelId",
|
|
||||||
"CategoryId",
|
|
||||||
"UserId",
|
|
||||||
"SortOrder",
|
|
||||||
).
|
|
||||||
Select(
|
|
||||||
sq.Select().
|
|
||||||
Column("? as ChannelId", preference.Name).
|
|
||||||
Column("SidebarCategories.Id as CategoryId").
|
|
||||||
Column("? as UserId", preference.UserId).
|
|
||||||
Column("COALESCE(MIN(SidebarChannels.SortOrder) - 10, 0) as SortOrder").
|
|
||||||
From("SidebarCategories").
|
|
||||||
LeftJoin("SidebarChannels on SidebarCategories.Id = SidebarChannels.CategoryId").
|
|
||||||
Where(sq.Eq{
|
|
||||||
"SidebarCategories.Id": categoryIds,
|
|
||||||
}).
|
|
||||||
GroupBy("SidebarCategories.Id")).ToSql()
|
|
||||||
|
|
||||||
if _, err := transaction.Exec(insertQuery, insertParams...); err != nil {
|
|
||||||
return errors.Wrap(err, "Failed to add sidebar entries for favorited channel")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteSidebarChannelsByPreferences is called when the Preference table is being updated to keep SidebarCategories in sync
|
|
||||||
// At the moment, it's only handling Favorites and NOT DMs/GMs (those will be handled client side)
|
|
||||||
func (s SqlChannelStore) DeleteSidebarChannelsByPreferences(preferences *model.Preferences) error {
|
|
||||||
transaction, err := s.GetMaster().Begin()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "DeleteSidebarChannelsByPreferences: begin_transaction")
|
|
||||||
}
|
|
||||||
defer finalizeTransaction(transaction)
|
|
||||||
|
|
||||||
for _, preference := range *preferences {
|
|
||||||
preference := preference
|
|
||||||
|
|
||||||
if preference.Category != model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.removeSidebarEntriesForPreferenceT(transaction, &preference); err != nil {
|
|
||||||
return errors.Wrap(err, "DeleteSidebarChannelsByPreferences: removeSidebarEntriesForPreferenceT")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := transaction.Commit(); err != nil {
|
|
||||||
return errors.Wrap(err, "DeleteSidebarChannelsByPreferences: commit_transaction")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) UpdateSidebarChannelCategoryOnMove(channel *model.Channel, newTeamId string) *model.AppError {
|
|
||||||
// if channel is being moved, remove it from the categories, since it's possible that there's no matching category in the new team
|
|
||||||
if _, err := s.GetMaster().Exec("DELETE FROM SidebarChannels WHERE ChannelId=:ChannelId", map[string]interface{}{"ChannelId": channel.Id}); err != nil {
|
|
||||||
return model.NewAppError("SqlChannelStore.UpdateSidebarChannelCategoryOnMove", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) ClearSidebarOnTeamLeave(userId, teamId string) *model.AppError {
|
|
||||||
// if user leaves the team, clean his team related entries in sidebar channels and categories
|
|
||||||
params := map[string]interface{}{
|
|
||||||
"UserId": userId,
|
|
||||||
"TeamId": teamId,
|
|
||||||
}
|
|
||||||
|
|
||||||
var deleteQuery string
|
|
||||||
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
|
|
||||||
deleteQuery = "DELETE SidebarChannels FROM SidebarChannels LEFT JOIN SidebarCategories ON SidebarCategories.Id = SidebarChannels.CategoryId WHERE SidebarCategories.TeamId=:TeamId AND SidebarCategories.UserId=:UserId"
|
|
||||||
} else {
|
|
||||||
deleteQuery = "DELETE FROM SidebarChannels USING SidebarChannels AS chan LEFT OUTER JOIN SidebarCategories AS cat ON cat.Id = chan.CategoryId WHERE cat.UserId = :UserId AND cat.TeamId = :TeamId"
|
|
||||||
}
|
|
||||||
if _, err := s.GetMaster().Exec(deleteQuery, params); err != nil {
|
|
||||||
return model.NewAppError("SqlChannelStore.ClearSidebarOnTeamLeave", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
if _, err := s.GetMaster().Exec("DELETE FROM SidebarCategories WHERE SidebarCategories.TeamId = :TeamId AND SidebarCategories.UserId = :UserId", params); err != nil {
|
|
||||||
return model.NewAppError("SqlChannelStore.ClearSidebarOnTeamLeave", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteSidebarCategory removes a custom category and moves any channels into it into the Channels and Direct Messages
|
|
||||||
// categories respectively. Assumes that the provided user ID and team ID match the given category ID.
|
|
||||||
func (s SqlChannelStore) DeleteSidebarCategory(categoryId string) *model.AppError {
|
|
||||||
transaction, err := s.GetMaster().Begin()
|
|
||||||
if err != nil {
|
|
||||||
return model.NewAppError("SqlChannelStore.DeleteSidebarCategory", "store.sql_channel.sidebar_categories.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
defer finalizeTransaction(transaction)
|
|
||||||
|
|
||||||
// Ensure that we're deleting a custom category
|
|
||||||
var category *model.SidebarCategory
|
|
||||||
if err = transaction.SelectOne(&category, "SELECT * FROM SidebarCategories WHERE Id = :Id", map[string]interface{}{"Id": categoryId}); err != nil {
|
|
||||||
return model.NewAppError("SqlPostStore.DeleteSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
if category.Type != model.SidebarCategoryCustom {
|
|
||||||
return model.NewAppError("SqlPostStore.DeleteSidebarCategory", "store.sql_channel.sidebar_categories.delete_invalid.app_error", nil, "", http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the channels in the category
|
|
||||||
sql, args, _ := s.getQueryBuilder().
|
|
||||||
Delete("SidebarChannels").
|
|
||||||
Where(sq.Eq{"CategoryId": categoryId}).ToSql()
|
|
||||||
|
|
||||||
if _, err := transaction.Exec(sql, args...); err != nil {
|
|
||||||
return model.NewAppError("SqlPostStore.DeleteSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the category itself
|
|
||||||
sql, args, _ = s.getQueryBuilder().
|
|
||||||
Delete("SidebarCategories").
|
|
||||||
Where(sq.Eq{"Id": categoryId}).ToSql()
|
|
||||||
|
|
||||||
if _, err := transaction.Exec(sql, args...); err != nil {
|
|
||||||
return model.NewAppError("SqlChannelStore.DeleteSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := transaction.Commit(); err != nil {
|
|
||||||
return model.NewAppError("SqlChannelStore.DeleteSidebarCategory", "store.sql_channel.sidebar_categories.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
974
store/sqlstore/channel_store_categories.go
Обычный файл
974
store/sqlstore/channel_store_categories.go
Обычный файл
@@ -0,0 +1,974 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package sqlstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/mattermost/gorp"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
|
|
||||||
|
sq "github.com/Masterminds/squirrel"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s SqlChannelStore) CreateInitialSidebarCategories(userId, teamId string) error {
|
||||||
|
transaction, err := s.GetMaster().Begin()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "CreateInitialSidebarCategories: begin_transaction")
|
||||||
|
}
|
||||||
|
defer finalizeTransaction(transaction)
|
||||||
|
|
||||||
|
if err := s.createInitialSidebarCategoriesT(transaction, userId, teamId); err != nil {
|
||||||
|
return errors.Wrap(err, "CreateInitialSidebarCategories: createInitialSidebarCategoriesT")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := transaction.Commit(); err != nil {
|
||||||
|
return errors.Wrap(err, "CreateInitialSidebarCategories: commit_transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) createInitialSidebarCategoriesT(transaction *gorp.Transaction, userId, teamId string) error {
|
||||||
|
selectQuery, selectParams, _ := s.getQueryBuilder().
|
||||||
|
Select("Type").
|
||||||
|
From("SidebarCategories").
|
||||||
|
Where(sq.Eq{
|
||||||
|
"UserId": userId,
|
||||||
|
"TeamId": teamId,
|
||||||
|
"Type": []model.SidebarCategoryType{model.SidebarCategoryFavorites, model.SidebarCategoryChannels, model.SidebarCategoryDirectMessages},
|
||||||
|
}).ToSql()
|
||||||
|
|
||||||
|
var existingTypes []model.SidebarCategoryType
|
||||||
|
_, err := transaction.Select(&existingTypes, selectQuery, selectParams...)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to select existing categories")
|
||||||
|
}
|
||||||
|
|
||||||
|
hasCategoryOfType := make(map[model.SidebarCategoryType]bool, len(existingTypes))
|
||||||
|
for _, existingType := range existingTypes {
|
||||||
|
hasCategoryOfType[existingType] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasCategoryOfType[model.SidebarCategoryFavorites] {
|
||||||
|
favoritesCategoryId := model.NewId()
|
||||||
|
|
||||||
|
// Create the SidebarChannels first since there's more opportunity for something to fail here
|
||||||
|
if err := s.migrateFavoritesToSidebarT(transaction, userId, teamId, favoritesCategoryId); err != nil {
|
||||||
|
return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to migrate favorites to sidebar")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := transaction.Insert(&model.SidebarCategory{
|
||||||
|
DisplayName: "Favorites", // This will be retranslated by the client into the user's locale
|
||||||
|
Id: favoritesCategoryId,
|
||||||
|
UserId: userId,
|
||||||
|
TeamId: teamId,
|
||||||
|
Sorting: model.SidebarCategorySortDefault,
|
||||||
|
SortOrder: model.DefaultSidebarSortOrderFavorites,
|
||||||
|
Type: model.SidebarCategoryFavorites,
|
||||||
|
}); err != nil {
|
||||||
|
return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to insert favorites category")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasCategoryOfType[model.SidebarCategoryChannels] {
|
||||||
|
if err := transaction.Insert(&model.SidebarCategory{
|
||||||
|
DisplayName: "Channels", // This will be retranslateed by the client into the user's locale
|
||||||
|
Id: model.NewId(),
|
||||||
|
UserId: userId,
|
||||||
|
TeamId: teamId,
|
||||||
|
Sorting: model.SidebarCategorySortDefault,
|
||||||
|
SortOrder: model.DefaultSidebarSortOrderChannels,
|
||||||
|
Type: model.SidebarCategoryChannels,
|
||||||
|
}); err != nil {
|
||||||
|
return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to insert channels category")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasCategoryOfType[model.SidebarCategoryDirectMessages] {
|
||||||
|
if err := transaction.Insert(&model.SidebarCategory{
|
||||||
|
DisplayName: "Direct Messages", // This will be retranslateed by the client into the user's locale
|
||||||
|
Id: model.NewId(),
|
||||||
|
UserId: userId,
|
||||||
|
TeamId: teamId,
|
||||||
|
Sorting: model.SidebarCategorySortRecent,
|
||||||
|
SortOrder: model.DefaultSidebarSortOrderDMs,
|
||||||
|
Type: model.SidebarCategoryDirectMessages,
|
||||||
|
}); err != nil {
|
||||||
|
return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to insert direct messages category")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type userMembership struct {
|
||||||
|
UserId string
|
||||||
|
ChannelId string
|
||||||
|
CategoryId string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) migrateMembershipToSidebar(transaction *gorp.Transaction, runningOrder *int64, sql string, args ...interface{}) ([]userMembership, error) {
|
||||||
|
var memberships []userMembership
|
||||||
|
if _, err := transaction.Select(&memberships, sql, args...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, favorite := range memberships {
|
||||||
|
sql, args, _ := s.getQueryBuilder().
|
||||||
|
Insert("SidebarChannels").
|
||||||
|
Columns("ChannelId", "UserId", "CategoryId", "SortOrder").
|
||||||
|
Values(favorite.ChannelId, favorite.UserId, favorite.CategoryId, *runningOrder).ToSql()
|
||||||
|
|
||||||
|
if _, err := transaction.Exec(sql, args...); err != nil && !IsUniqueConstraintError(err, []string{"UserId", "PRIMARY"}) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
*runningOrder = *runningOrder + model.MinimalSidebarSortDistance
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := transaction.Commit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return memberships, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) migrateFavoritesToSidebarT(transaction *gorp.Transaction, userId, teamId, favoritesCategoryId string) error {
|
||||||
|
favoritesQuery, favoritesParams, _ := s.getQueryBuilder().
|
||||||
|
Select("Preferences.Name").
|
||||||
|
From("Preferences").
|
||||||
|
Join("Channels on Preferences.Name = Channels.Id").
|
||||||
|
Join("ChannelMembers on Preferences.Name = ChannelMembers.ChannelId and Preferences.UserId = ChannelMembers.UserId").
|
||||||
|
Where(sq.Eq{
|
||||||
|
"Preferences.UserId": userId,
|
||||||
|
"Preferences.Category": model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL,
|
||||||
|
"Preferences.Value": "true",
|
||||||
|
}).
|
||||||
|
Where(sq.Or{
|
||||||
|
sq.Eq{"Channels.TeamId": teamId},
|
||||||
|
sq.Eq{"Channels.TeamId": ""},
|
||||||
|
}).
|
||||||
|
OrderBy(
|
||||||
|
"Channels.DisplayName",
|
||||||
|
"Channels.Name ASC",
|
||||||
|
).ToSql()
|
||||||
|
|
||||||
|
var favoriteChannelIds []string
|
||||||
|
if _, err := transaction.Select(&favoriteChannelIds, favoritesQuery, favoritesParams...); err != nil {
|
||||||
|
return errors.Wrap(err, "migrateFavoritesToSidebarT: unable to get favorite channel IDs")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, channelId := range favoriteChannelIds {
|
||||||
|
if err := transaction.Insert(&model.SidebarChannel{
|
||||||
|
ChannelId: channelId,
|
||||||
|
CategoryId: favoritesCategoryId,
|
||||||
|
UserId: userId,
|
||||||
|
SortOrder: int64(i * model.MinimalSidebarSortDistance),
|
||||||
|
}); err != nil {
|
||||||
|
return errors.Wrap(err, "migrateFavoritesToSidebarT: unable to insert SidebarChannel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MigrateFavoritesToSidebarChannels populates the SidebarChannels table by analyzing existing user preferences for favorites
|
||||||
|
// **IMPORTANT** This function should only be called from the migration task and shouldn't be used by itself
|
||||||
|
func (s SqlChannelStore) MigrateFavoritesToSidebarChannels(lastUserId string, runningOrder int64) (map[string]interface{}, error) {
|
||||||
|
transaction, err := s.GetMaster().Begin()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer finalizeTransaction(transaction)
|
||||||
|
|
||||||
|
sb := s.
|
||||||
|
getQueryBuilder().
|
||||||
|
Select("Preferences.UserId", "Preferences.Name AS ChannelId", "SidebarCategories.Id AS CategoryId").
|
||||||
|
From("Preferences").
|
||||||
|
Where(sq.And{
|
||||||
|
sq.Eq{"Preferences.Category": model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL},
|
||||||
|
sq.NotEq{"Preferences.Value": "false"},
|
||||||
|
sq.NotEq{"SidebarCategories.Id": nil},
|
||||||
|
sq.Gt{"Preferences.UserId": lastUserId},
|
||||||
|
}).
|
||||||
|
LeftJoin("Channels ON (Channels.Id=Preferences.Name)").
|
||||||
|
LeftJoin("SidebarCategories ON (SidebarCategories.UserId=Preferences.UserId AND SidebarCategories.Type='"+string(model.SidebarCategoryFavorites)+"' AND (SidebarCategories.TeamId=Channels.TeamId OR Channels.TeamId=''))").
|
||||||
|
OrderBy("Preferences.UserId", "Channels.Name DESC").
|
||||||
|
Limit(100)
|
||||||
|
|
||||||
|
sql, args, err := sb.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userFavorites, err := s.migrateMembershipToSidebar(transaction, &runningOrder, sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(userFavorites) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(map[string]interface{})
|
||||||
|
data["UserId"] = userFavorites[len(userFavorites)-1].UserId
|
||||||
|
data["SortOrder"] = runningOrder
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type sidebarCategoryForJoin struct {
|
||||||
|
model.SidebarCategory
|
||||||
|
ChannelId *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) CreateSidebarCategory(userId, teamId string, newCategory *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, *model.AppError) {
|
||||||
|
transaction, err := s.GetMaster().Begin()
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.NewAppError("SqlChannelStore.CreateSidebarCategory", "store.sql_channel.sidebar_categories.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer finalizeTransaction(transaction)
|
||||||
|
|
||||||
|
categoriesWithOrder, appErr := s.getSidebarCategoriesT(transaction, userId, teamId)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
if len(categoriesWithOrder.Categories) < 1 {
|
||||||
|
return nil, model.NewAppError("SqlChannelStore.CreateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, "", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
newOrder := categoriesWithOrder.Order
|
||||||
|
newCategoryId := model.NewId()
|
||||||
|
newCategorySortOrder := 0
|
||||||
|
/*
|
||||||
|
When a new category is created, it should be placed as follows:
|
||||||
|
1. If the Favorites category is first, the new category should be placed after it
|
||||||
|
2. Otherwise, the new category should be placed first.
|
||||||
|
*/
|
||||||
|
if categoriesWithOrder.Categories[0].Type == model.SidebarCategoryFavorites {
|
||||||
|
newOrder = append([]string{newOrder[0], newCategoryId}, newOrder[1:]...)
|
||||||
|
newCategorySortOrder = model.MinimalSidebarSortDistance
|
||||||
|
} else {
|
||||||
|
newOrder = append([]string{newCategoryId}, newOrder...)
|
||||||
|
}
|
||||||
|
|
||||||
|
category := &model.SidebarCategory{
|
||||||
|
DisplayName: newCategory.DisplayName,
|
||||||
|
Id: newCategoryId,
|
||||||
|
UserId: userId,
|
||||||
|
TeamId: teamId,
|
||||||
|
Sorting: model.SidebarCategorySortDefault,
|
||||||
|
SortOrder: int64(model.MinimalSidebarSortDistance * len(newOrder)), // first we place it at the end of the list
|
||||||
|
Type: model.SidebarCategoryCustom,
|
||||||
|
}
|
||||||
|
if err = transaction.Insert(category); err != nil {
|
||||||
|
return nil, model.NewAppError("SqlPostStore.CreateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newCategory.Channels) > 0 {
|
||||||
|
channelIdsKeys, deleteParams := MapStringsToQueryParams(newCategory.Channels, "ChannelId")
|
||||||
|
deleteParams["UserId"] = userId
|
||||||
|
deleteParams["TeamId"] = teamId
|
||||||
|
|
||||||
|
// Remove any channels from their previous categories and add them to the new one
|
||||||
|
var deleteQuery string
|
||||||
|
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
|
||||||
|
deleteQuery = `
|
||||||
|
DELETE
|
||||||
|
SidebarChannels
|
||||||
|
FROM
|
||||||
|
SidebarChannels
|
||||||
|
JOIN
|
||||||
|
SidebarCategories ON SidebarChannels.CategoryId = SidebarCategories.Id
|
||||||
|
WHERE
|
||||||
|
SidebarChannels.UserId = :UserId
|
||||||
|
AND SidebarChannels.ChannelId IN ` + channelIdsKeys + `
|
||||||
|
AND SidebarCategories.TeamId = :TeamId`
|
||||||
|
} else {
|
||||||
|
deleteQuery = `
|
||||||
|
DELETE FROM
|
||||||
|
SidebarChannels
|
||||||
|
USING
|
||||||
|
SidebarCategories
|
||||||
|
WHERE
|
||||||
|
SidebarChannels.CategoryId = SidebarCategories.Id
|
||||||
|
AND SidebarChannels.UserId = :UserId
|
||||||
|
AND SidebarChannels.ChannelId IN ` + channelIdsKeys + `
|
||||||
|
AND SidebarCategories.TeamId = :TeamId`
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = transaction.Exec(deleteQuery, deleteParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.NewAppError("SqlPostStore.CreateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var channels []interface{}
|
||||||
|
for i, channelID := range newCategory.Channels {
|
||||||
|
channels = append(channels, &model.SidebarChannel{
|
||||||
|
ChannelId: channelID,
|
||||||
|
CategoryId: newCategoryId,
|
||||||
|
SortOrder: int64(i * model.MinimalSidebarSortDistance),
|
||||||
|
UserId: userId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err = transaction.Insert(channels...); err != nil {
|
||||||
|
return nil, model.NewAppError("SqlPostStore.CreateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we re-order the categories according to the new order
|
||||||
|
if appErr := s.updateSidebarCategoryOrderT(transaction, userId, teamId, newOrder); appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = transaction.Commit(); err != nil {
|
||||||
|
return nil, model.NewAppError("SqlChannelStore.CreateSidebarCategory", "store.sql_channel.sidebar_categories.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// patch category to return proper sort order
|
||||||
|
category.SortOrder = int64(newCategorySortOrder)
|
||||||
|
result := &model.SidebarCategoryWithChannels{
|
||||||
|
SidebarCategory: *category,
|
||||||
|
Channels: newCategory.Channels,
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) completePopulatingCategoryChannels(category *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, *model.AppError) {
|
||||||
|
transaction, err := s.GetMaster().Begin()
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.NewAppError("SqlChannelStore.completePopulatingCategoryChannels", "store.sql_channel.sidebar_categories.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
defer finalizeTransaction(transaction)
|
||||||
|
|
||||||
|
result, appErr := s.completePopulatingCategoryChannelsT(transaction, category)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = transaction.Commit(); err != nil {
|
||||||
|
return nil, model.NewAppError("SqlChannelStore.completePopulatingCategoryChannels", "store.sql_channel.sidebar_categories.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) completePopulatingCategoryChannelsT(transation *gorp.Transaction, category *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, *model.AppError) {
|
||||||
|
if category.Type == model.SidebarCategoryCustom || category.Type == model.SidebarCategoryFavorites {
|
||||||
|
return category, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var channelTypeFilter sq.Sqlizer
|
||||||
|
if category.Type == model.SidebarCategoryDirectMessages {
|
||||||
|
// any DM/GM channels that aren't in any category should be returned as part of the Direct Messages category
|
||||||
|
channelTypeFilter = sq.Eq{"Channels.Type": []string{model.CHANNEL_DIRECT, model.CHANNEL_GROUP}}
|
||||||
|
} else if category.Type == model.SidebarCategoryChannels {
|
||||||
|
// 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
|
||||||
|
channelTypeFilter = sq.And{
|
||||||
|
sq.Eq{"Channels.Type": []string{model.CHANNEL_OPEN, model.CHANNEL_PRIVATE}},
|
||||||
|
sq.Eq{"Channels.TeamId": category.TeamId},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A subquery that is true if the channel does not have a SidebarChannel entry for the current user on the current team
|
||||||
|
doesNotHaveSidebarChannel := sq.Select("1").
|
||||||
|
Prefix("NOT EXISTS (").
|
||||||
|
From("SidebarChannels").
|
||||||
|
Join("SidebarCategories on SidebarChannels.CategoryId=SidebarCategories.Id").
|
||||||
|
Where(sq.And{
|
||||||
|
sq.Expr("SidebarChannels.ChannelId = ChannelMembers.ChannelId"),
|
||||||
|
sq.Eq{"SidebarCategories.UserId": category.UserId},
|
||||||
|
sq.Eq{"SidebarCategories.TeamId": category.TeamId},
|
||||||
|
}).
|
||||||
|
Suffix(")")
|
||||||
|
|
||||||
|
var channels []string
|
||||||
|
sql, args, _ := s.getQueryBuilder().
|
||||||
|
Select("Id").
|
||||||
|
From("ChannelMembers").
|
||||||
|
LeftJoin("Channels ON Channels.Id=ChannelMembers.ChannelId").
|
||||||
|
Where(sq.And{
|
||||||
|
sq.Eq{"ChannelMembers.UserId": category.UserId},
|
||||||
|
channelTypeFilter,
|
||||||
|
sq.Eq{"Channels.DeleteAt": 0},
|
||||||
|
doesNotHaveSidebarChannel,
|
||||||
|
}).
|
||||||
|
OrderBy("DisplayName ASC").ToSql()
|
||||||
|
|
||||||
|
if _, err := transation.Select(&channels, sql, args...); err != nil {
|
||||||
|
return nil, model.NewAppError("SqlPostStore.completePopulatingCategoryChannelsT", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
category.Channels = append(channels, category.Channels...)
|
||||||
|
return category, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) GetSidebarCategory(categoryId string) (*model.SidebarCategoryWithChannels, *model.AppError) {
|
||||||
|
var categories []*sidebarCategoryForJoin
|
||||||
|
sql, args, _ := s.getQueryBuilder().
|
||||||
|
Select("SidebarCategories.*", "SidebarChannels.ChannelId").
|
||||||
|
From("SidebarCategories").
|
||||||
|
LeftJoin("SidebarChannels ON SidebarChannels.CategoryId=SidebarCategories.Id").
|
||||||
|
Where(sq.Eq{"SidebarCategories.Id": categoryId}).
|
||||||
|
OrderBy("SidebarChannels.SortOrder ASC").ToSql()
|
||||||
|
if _, err := s.GetReplica().Select(&categories, sql, args...); err != nil {
|
||||||
|
return nil, model.NewAppError("SqlPostStore.GetSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusNotFound)
|
||||||
|
}
|
||||||
|
result := &model.SidebarCategoryWithChannels{
|
||||||
|
SidebarCategory: categories[0].SidebarCategory,
|
||||||
|
Channels: make([]string, 0),
|
||||||
|
}
|
||||||
|
for _, category := range categories {
|
||||||
|
if category.ChannelId != nil {
|
||||||
|
result.Channels = append(result.Channels, *category.ChannelId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s.completePopulatingCategoryChannels(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) getSidebarCategoriesT(transaction *gorp.Transaction, userId, teamId string) (*model.OrderedSidebarCategories, *model.AppError) {
|
||||||
|
oc := model.OrderedSidebarCategories{
|
||||||
|
Categories: make(model.SidebarCategoriesWithChannels, 0),
|
||||||
|
Order: make([]string, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
var categories []*sidebarCategoryForJoin
|
||||||
|
sql, args, _ := s.getQueryBuilder().
|
||||||
|
Select("SidebarCategories.*", "SidebarChannels.ChannelId").
|
||||||
|
From("SidebarCategories").
|
||||||
|
LeftJoin("SidebarChannels ON SidebarChannels.CategoryId=Id").
|
||||||
|
Where(sq.And{
|
||||||
|
sq.Eq{"SidebarCategories.UserId": userId},
|
||||||
|
sq.Eq{"SidebarCategories.TeamId": teamId},
|
||||||
|
}).
|
||||||
|
OrderBy("SidebarCategories.SortOrder ASC, SidebarChannels.SortOrder ASC").ToSql()
|
||||||
|
|
||||||
|
if _, err := transaction.Select(&categories, sql, args...); err != nil {
|
||||||
|
return nil, model.NewAppError("SqlPostStore.GetSidebarCategories", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusNotFound)
|
||||||
|
}
|
||||||
|
for _, category := range categories {
|
||||||
|
var prevCategory *model.SidebarCategoryWithChannels
|
||||||
|
for _, existing := range oc.Categories {
|
||||||
|
if existing.Id == category.Id {
|
||||||
|
prevCategory = existing
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if prevCategory == nil {
|
||||||
|
prevCategory = &model.SidebarCategoryWithChannels{
|
||||||
|
SidebarCategory: category.SidebarCategory,
|
||||||
|
Channels: make([]string, 0),
|
||||||
|
}
|
||||||
|
oc.Categories = append(oc.Categories, prevCategory)
|
||||||
|
oc.Order = append(oc.Order, category.Id)
|
||||||
|
}
|
||||||
|
if category.ChannelId != nil {
|
||||||
|
prevCategory.Channels = append(prevCategory.Channels, *category.ChannelId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, category := range oc.Categories {
|
||||||
|
if _, err := s.completePopulatingCategoryChannelsT(transaction, category); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) GetSidebarCategories(userId, teamId string) (*model.OrderedSidebarCategories, *model.AppError) {
|
||||||
|
transaction, err := s.GetMaster().Begin()
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.NewAppError("SqlChannelStore.GetSidebarCategories", "store.sql_channel.sidebar_categories.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer finalizeTransaction(transaction)
|
||||||
|
|
||||||
|
oc, appErr := s.getSidebarCategoriesT(transaction, userId, teamId)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = transaction.Commit(); err != nil {
|
||||||
|
return nil, model.NewAppError("SqlChannelStore.GetSidebarCategories", "store.sql_channel.sidebar_categories.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return oc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) GetSidebarCategoryOrder(userId, teamId string) ([]string, *model.AppError) {
|
||||||
|
var ids []string
|
||||||
|
|
||||||
|
sql, args, _ := s.getQueryBuilder().
|
||||||
|
Select("Id").
|
||||||
|
From("SidebarCategories").
|
||||||
|
Where(sq.And{
|
||||||
|
sq.Eq{"UserId": userId},
|
||||||
|
sq.Eq{"TeamId": teamId},
|
||||||
|
}).
|
||||||
|
OrderBy("SidebarCategories.SortOrder ASC").ToSql()
|
||||||
|
|
||||||
|
if _, err := s.GetReplica().Select(&ids, sql, args...); err != nil {
|
||||||
|
return nil, model.NewAppError("SqlPostStore.GetSidebarCategoryOrder", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusNotFound)
|
||||||
|
}
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) updateSidebarCategoryOrderT(transaction *gorp.Transaction, userId, teamId string, categoryOrder []string) *model.AppError {
|
||||||
|
var newOrder []interface{}
|
||||||
|
runningOrder := 0
|
||||||
|
for _, categoryId := range categoryOrder {
|
||||||
|
newOrder = append(newOrder, &model.SidebarCategory{
|
||||||
|
Id: categoryId,
|
||||||
|
SortOrder: int64(runningOrder),
|
||||||
|
})
|
||||||
|
runningOrder += model.MinimalSidebarSortDistance
|
||||||
|
}
|
||||||
|
|
||||||
|
// There's a bug in gorp where UpdateColumns messes up the stored query for any other attempt to use .Update or
|
||||||
|
// .UpdateColumns on this table, so it's okay to use here as long as we don't use those methods for SidebarCategories
|
||||||
|
// anywhere else.
|
||||||
|
if _, err := transaction.UpdateColumns(func(col *gorp.ColumnMap) bool {
|
||||||
|
return col.ColumnName == "SortOrder"
|
||||||
|
}, newOrder...); err != nil {
|
||||||
|
return model.NewAppError("SqlPostStore.UpdateSidebarCategoryOrder", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) UpdateSidebarCategoryOrder(userId, teamId string, categoryOrder []string) *model.AppError {
|
||||||
|
transaction, err := s.GetMaster().Begin()
|
||||||
|
if err != nil {
|
||||||
|
return model.NewAppError("SqlChannelStore.UpdateSidebarCategoryOrder", "store.sql_channel.sidebar_categories.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer finalizeTransaction(transaction)
|
||||||
|
|
||||||
|
// Ensure no invalid categories are included and that no categories are left out
|
||||||
|
existingOrder, appErr := s.GetSidebarCategoryOrder(userId, teamId)
|
||||||
|
if appErr != nil {
|
||||||
|
return appErr
|
||||||
|
}
|
||||||
|
if len(existingOrder) != len(categoryOrder) {
|
||||||
|
return model.NewAppError("SqlPostStore.UpdateSidebarCategoryOrder", "store.sql_channel.sidebar_categories.app_error", nil, "Cannot update category order, passed list of categories different size than in DB", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
for _, originalCategoryId := range existingOrder {
|
||||||
|
found := false
|
||||||
|
for _, newCategoryId := range categoryOrder {
|
||||||
|
if newCategoryId == originalCategoryId {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return model.NewAppError("SqlPostStore.UpdateSidebarCategoryOrder", "store.sql_channel.sidebar_categories.app_error", nil, "Cannot update category order, passed list of categories contains unrecognized category IDs", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if appErr := s.updateSidebarCategoryOrderT(transaction, userId, teamId, categoryOrder); appErr != nil {
|
||||||
|
return appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = transaction.Commit(); err != nil {
|
||||||
|
return model.NewAppError("SqlChannelStore.UpdateSidebarCategoryOrder", "store.sql_channel.sidebar_categories.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) UpdateSidebarCategories(userId, teamId string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError) {
|
||||||
|
transaction, err := s.GetMaster().Begin()
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.NewAppError("SqlChannelStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
defer finalizeTransaction(transaction)
|
||||||
|
|
||||||
|
updatedCategories := []*model.SidebarCategoryWithChannels{}
|
||||||
|
for _, category := range categories {
|
||||||
|
originalCategory, appErr := s.GetSidebarCategory(category.Id)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, model.NewAppError("SqlPostStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, appErr.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy category to avoid modifying an argument
|
||||||
|
updatedCategory := &model.SidebarCategoryWithChannels{
|
||||||
|
SidebarCategory: category.SidebarCategory,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent any changes to read-only fields of SidebarCategories
|
||||||
|
updatedCategory.UserId = originalCategory.UserId
|
||||||
|
updatedCategory.TeamId = originalCategory.TeamId
|
||||||
|
updatedCategory.SortOrder = originalCategory.SortOrder
|
||||||
|
updatedCategory.Type = originalCategory.Type
|
||||||
|
|
||||||
|
if updatedCategory.Type != model.SidebarCategoryCustom {
|
||||||
|
updatedCategory.DisplayName = originalCategory.DisplayName
|
||||||
|
}
|
||||||
|
|
||||||
|
if category.Type != model.SidebarCategoryDirectMessages {
|
||||||
|
updatedCategory.Channels = make([]string, len(category.Channels))
|
||||||
|
copy(updatedCategory.Channels, category.Channels)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateQuery, updateParams, _ := s.getQueryBuilder().
|
||||||
|
Update("SidebarCategories").
|
||||||
|
Set("DisplayName", updatedCategory.DisplayName).
|
||||||
|
Set("Sorting", updatedCategory.Sorting).
|
||||||
|
Where(sq.Eq{"Id": updatedCategory.Id}).ToSql()
|
||||||
|
|
||||||
|
if _, err = transaction.Exec(updateQuery, updateParams...); err != nil {
|
||||||
|
return nil, model.NewAppError("SqlPostStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we are updating DM category, it's order can't channel order cannot be changed.
|
||||||
|
if category.Type != model.SidebarCategoryDirectMessages {
|
||||||
|
// Remove any SidebarChannels entries that were either:
|
||||||
|
// - previously in this category (and any ones that are still in the category will be recreated below)
|
||||||
|
// - in another category and are being added to this category
|
||||||
|
sql, args, _ := s.getQueryBuilder().
|
||||||
|
Delete("SidebarChannels").
|
||||||
|
Where(
|
||||||
|
sq.And{
|
||||||
|
sq.Or{
|
||||||
|
sq.Eq{"ChannelId": originalCategory.Channels},
|
||||||
|
sq.Eq{"ChannelId": updatedCategory.Channels},
|
||||||
|
},
|
||||||
|
sq.Eq{"CategoryId": category.Id},
|
||||||
|
},
|
||||||
|
).ToSql()
|
||||||
|
|
||||||
|
if _, err = transaction.Exec(sql, args...); err != nil {
|
||||||
|
return nil, model.NewAppError("SqlPostStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var channels []interface{}
|
||||||
|
runningOrder := 0
|
||||||
|
for _, channelID := range category.Channels {
|
||||||
|
channels = append(channels, &model.SidebarChannel{
|
||||||
|
ChannelId: channelID,
|
||||||
|
CategoryId: category.Id,
|
||||||
|
SortOrder: int64(runningOrder),
|
||||||
|
UserId: userId,
|
||||||
|
})
|
||||||
|
runningOrder += model.MinimalSidebarSortDistance
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = transaction.Insert(channels...); err != nil {
|
||||||
|
return nil, model.NewAppError("SqlPostStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the favorites preferences based on channels moving into or out of the Favorites category for compatibility
|
||||||
|
if category.Type == model.SidebarCategoryFavorites {
|
||||||
|
// Remove any old favorites
|
||||||
|
sql, args, _ := s.getQueryBuilder().Delete("Preferences").Where(
|
||||||
|
sq.Eq{
|
||||||
|
"UserId": userId,
|
||||||
|
"Name": originalCategory.Channels,
|
||||||
|
"Category": model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL,
|
||||||
|
},
|
||||||
|
).ToSql()
|
||||||
|
|
||||||
|
if _, err = transaction.Exec(sql, args...); err != nil {
|
||||||
|
return nil, model.NewAppError("SqlPostStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// And then add the new ones
|
||||||
|
for _, channelID := range category.Channels {
|
||||||
|
// This breaks the PreferenceStore abstraction, but it should be safe to assume that everything is a SQL
|
||||||
|
// store in this package.
|
||||||
|
if err = s.Preference().(*SqlPreferenceStore).save(transaction, &model.Preference{
|
||||||
|
Name: channelID,
|
||||||
|
UserId: userId,
|
||||||
|
Category: model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL,
|
||||||
|
Value: "true",
|
||||||
|
}); err != nil {
|
||||||
|
return nil, model.NewAppError("SqlPostStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Remove any old favorites that might have been in this category
|
||||||
|
sql, args, _ := s.getQueryBuilder().Delete("Preferences").Where(
|
||||||
|
sq.Eq{
|
||||||
|
"UserId": userId,
|
||||||
|
"Name": category.Channels,
|
||||||
|
"Category": model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL,
|
||||||
|
},
|
||||||
|
).ToSql()
|
||||||
|
|
||||||
|
if _, err = transaction.Exec(sql, args...); err != nil {
|
||||||
|
return nil, model.NewAppError("SqlPostStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedCategories = append(updatedCategories, updatedCategory)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure Channels are populated for Channels/Direct Messages category if they change
|
||||||
|
for i, updatedCategory := range updatedCategories {
|
||||||
|
populated, err := s.completePopulatingCategoryChannelsT(transaction, updatedCategory)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.NewAppError("SqlPostStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedCategories[i] = populated
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = transaction.Commit(); err != nil {
|
||||||
|
return nil, model.NewAppError("SqlChannelStore.UpdateSidebarCategory", "store.sql_channel.sidebar_categories.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedCategories, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSidebarChannelsByPreferences is called when the Preference table is being updated to keep SidebarCategories in sync
|
||||||
|
// At the moment, it's only handling Favorites and NOT DMs/GMs (those will be handled client side)
|
||||||
|
func (s SqlChannelStore) UpdateSidebarChannelsByPreferences(preferences *model.Preferences) error {
|
||||||
|
transaction, err := s.GetMaster().Begin()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "UpdateSidebarChannelsByPreferences: begin_transaction")
|
||||||
|
}
|
||||||
|
defer finalizeTransaction(transaction)
|
||||||
|
|
||||||
|
for _, preference := range *preferences {
|
||||||
|
preference := preference
|
||||||
|
|
||||||
|
if preference.Category != model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// if new preference is false - remove the channel from the appropriate sidebar category
|
||||||
|
if preference.Value == "false" {
|
||||||
|
if err := s.removeSidebarEntriesForPreferenceT(transaction, &preference); err != nil {
|
||||||
|
return errors.Wrap(err, "UpdateSidebarChannelsByPreferences: removeSidebarEntriesForPreferenceT")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := s.addChannelToFavoritesCategoryT(transaction, &preference); err != nil {
|
||||||
|
return errors.Wrap(err, "UpdateSidebarChannelsByPreferences: addChannelToFavoritesCategoryT")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := transaction.Commit(); err != nil {
|
||||||
|
return errors.Wrap(err, "UpdateSidebarChannelsByPreferences: commit_transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) removeSidebarEntriesForPreferenceT(transaction *gorp.Transaction, preference *model.Preference) error {
|
||||||
|
if preference.Category != model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete any corresponding SidebarChannels entries in a Favorites category corresponding to this preference. This
|
||||||
|
// can't use the query builder because it uses DB-specific syntax
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"UserId": preference.UserId,
|
||||||
|
"ChannelId": preference.Name,
|
||||||
|
"CategoryType": model.SidebarCategoryFavorites,
|
||||||
|
}
|
||||||
|
var query string
|
||||||
|
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
|
||||||
|
query = `
|
||||||
|
DELETE
|
||||||
|
SidebarChannels
|
||||||
|
FROM
|
||||||
|
SidebarChannels
|
||||||
|
JOIN
|
||||||
|
SidebarCategories ON SidebarChannels.CategoryId = SidebarCategories.Id
|
||||||
|
WHERE
|
||||||
|
SidebarChannels.UserId = :UserId
|
||||||
|
AND SidebarChannels.ChannelId = :ChannelId
|
||||||
|
AND SidebarCategories.Type = :CategoryType`
|
||||||
|
} else {
|
||||||
|
query = `
|
||||||
|
DELETE FROM
|
||||||
|
SidebarChannels
|
||||||
|
USING
|
||||||
|
SidebarCategories
|
||||||
|
WHERE
|
||||||
|
SidebarChannels.CategoryId = SidebarCategories.Id
|
||||||
|
AND SidebarChannels.UserId = :UserId
|
||||||
|
AND SidebarChannels.ChannelId = :ChannelId
|
||||||
|
AND SidebarCategories.Type = :CategoryType`
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := transaction.Exec(query, params); err != nil {
|
||||||
|
return errors.Wrap(err, "Failed to remove sidebar entries for preference")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) addChannelToFavoritesCategoryT(transaction *gorp.Transaction, preference *model.Preference) error {
|
||||||
|
if preference.Category != model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var channel *model.Channel
|
||||||
|
if obj, err := transaction.Get(&model.Channel{}, preference.Name); err != nil {
|
||||||
|
return errors.Wrapf(err, "Failed to get favorited channel with id=%s", preference.Name)
|
||||||
|
} else {
|
||||||
|
channel = obj.(*model.Channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the IDs of the Favorites category/categories that the channel needs to be added to
|
||||||
|
builder := s.getQueryBuilder().
|
||||||
|
Select("SidebarCategories.Id").
|
||||||
|
From("SidebarCategories").
|
||||||
|
LeftJoin("SidebarChannels on SidebarCategories.Id = SidebarChannels.CategoryId and SidebarChannels.ChannelId = ?", preference.Name).
|
||||||
|
Where(sq.Eq{
|
||||||
|
"SidebarCategories.UserId": preference.UserId,
|
||||||
|
"Type": model.SidebarCategoryFavorites,
|
||||||
|
}).
|
||||||
|
Where("SidebarChannels.ChannelId is null")
|
||||||
|
|
||||||
|
if channel.TeamId != "" {
|
||||||
|
builder = builder.Where(sq.Eq{"TeamId": channel.TeamId})
|
||||||
|
}
|
||||||
|
|
||||||
|
idsQuery, idsParams, _ := builder.ToSql()
|
||||||
|
|
||||||
|
var categoryIds []string
|
||||||
|
if _, err := transaction.Select(&categoryIds, idsQuery, idsParams...); err != nil {
|
||||||
|
return errors.Wrap(err, "Failed to get Favorites sidebar categories")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(categoryIds) == 0 {
|
||||||
|
// The channel is already in the Favorites category/categories
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each category ID, insert a row into SidebarChannels with the given channel ID and a SortOrder that's less than
|
||||||
|
// all existing SortOrders in the category so that the newly favorited channel comes first
|
||||||
|
insertQuery, insertParams, _ := s.getQueryBuilder().
|
||||||
|
Insert("SidebarChannels").
|
||||||
|
Columns(
|
||||||
|
"ChannelId",
|
||||||
|
"CategoryId",
|
||||||
|
"UserId",
|
||||||
|
"SortOrder",
|
||||||
|
).
|
||||||
|
Select(
|
||||||
|
sq.Select().
|
||||||
|
Column("? as ChannelId", preference.Name).
|
||||||
|
Column("SidebarCategories.Id as CategoryId").
|
||||||
|
Column("? as UserId", preference.UserId).
|
||||||
|
Column("COALESCE(MIN(SidebarChannels.SortOrder) - 10, 0) as SortOrder").
|
||||||
|
From("SidebarCategories").
|
||||||
|
LeftJoin("SidebarChannels on SidebarCategories.Id = SidebarChannels.CategoryId").
|
||||||
|
Where(sq.Eq{
|
||||||
|
"SidebarCategories.Id": categoryIds,
|
||||||
|
}).
|
||||||
|
GroupBy("SidebarCategories.Id")).ToSql()
|
||||||
|
|
||||||
|
if _, err := transaction.Exec(insertQuery, insertParams...); err != nil {
|
||||||
|
return errors.Wrap(err, "Failed to add sidebar entries for favorited channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSidebarChannelsByPreferences is called when the Preference table is being updated to keep SidebarCategories in sync
|
||||||
|
// At the moment, it's only handling Favorites and NOT DMs/GMs (those will be handled client side)
|
||||||
|
func (s SqlChannelStore) DeleteSidebarChannelsByPreferences(preferences *model.Preferences) error {
|
||||||
|
transaction, err := s.GetMaster().Begin()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "DeleteSidebarChannelsByPreferences: begin_transaction")
|
||||||
|
}
|
||||||
|
defer finalizeTransaction(transaction)
|
||||||
|
|
||||||
|
for _, preference := range *preferences {
|
||||||
|
preference := preference
|
||||||
|
|
||||||
|
if preference.Category != model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.removeSidebarEntriesForPreferenceT(transaction, &preference); err != nil {
|
||||||
|
return errors.Wrap(err, "DeleteSidebarChannelsByPreferences: removeSidebarEntriesForPreferenceT")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := transaction.Commit(); err != nil {
|
||||||
|
return errors.Wrap(err, "DeleteSidebarChannelsByPreferences: commit_transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) UpdateSidebarChannelCategoryOnMove(channel *model.Channel, newTeamId string) *model.AppError {
|
||||||
|
// if channel is being moved, remove it from the categories, since it's possible that there's no matching category in the new team
|
||||||
|
if _, err := s.GetMaster().Exec("DELETE FROM SidebarChannels WHERE ChannelId=:ChannelId", map[string]interface{}{"ChannelId": channel.Id}); err != nil {
|
||||||
|
return model.NewAppError("SqlChannelStore.UpdateSidebarChannelCategoryOnMove", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) ClearSidebarOnTeamLeave(userId, teamId string) *model.AppError {
|
||||||
|
// if user leaves the team, clean his team related entries in sidebar channels and categories
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"UserId": userId,
|
||||||
|
"TeamId": teamId,
|
||||||
|
}
|
||||||
|
|
||||||
|
var deleteQuery string
|
||||||
|
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
|
||||||
|
deleteQuery = "DELETE SidebarChannels FROM SidebarChannels LEFT JOIN SidebarCategories ON SidebarCategories.Id = SidebarChannels.CategoryId WHERE SidebarCategories.TeamId=:TeamId AND SidebarCategories.UserId=:UserId"
|
||||||
|
} else {
|
||||||
|
deleteQuery = "DELETE FROM SidebarChannels USING SidebarChannels AS chan LEFT OUTER JOIN SidebarCategories AS cat ON cat.Id = chan.CategoryId WHERE cat.UserId = :UserId AND cat.TeamId = :TeamId"
|
||||||
|
}
|
||||||
|
if _, err := s.GetMaster().Exec(deleteQuery, params); err != nil {
|
||||||
|
return model.NewAppError("SqlChannelStore.ClearSidebarOnTeamLeave", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
if _, err := s.GetMaster().Exec("DELETE FROM SidebarCategories WHERE SidebarCategories.TeamId = :TeamId AND SidebarCategories.UserId = :UserId", params); err != nil {
|
||||||
|
return model.NewAppError("SqlChannelStore.ClearSidebarOnTeamLeave", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSidebarCategory removes a custom category and moves any channels into it into the Channels and Direct Messages
|
||||||
|
// categories respectively. Assumes that the provided user ID and team ID match the given category ID.
|
||||||
|
func (s SqlChannelStore) DeleteSidebarCategory(categoryId string) *model.AppError {
|
||||||
|
transaction, err := s.GetMaster().Begin()
|
||||||
|
if err != nil {
|
||||||
|
return model.NewAppError("SqlChannelStore.DeleteSidebarCategory", "store.sql_channel.sidebar_categories.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
defer finalizeTransaction(transaction)
|
||||||
|
|
||||||
|
// Ensure that we're deleting a custom category
|
||||||
|
var category *model.SidebarCategory
|
||||||
|
if err = transaction.SelectOne(&category, "SELECT * FROM SidebarCategories WHERE Id = :Id", map[string]interface{}{"Id": categoryId}); err != nil {
|
||||||
|
return model.NewAppError("SqlPostStore.DeleteSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if category.Type != model.SidebarCategoryCustom {
|
||||||
|
return model.NewAppError("SqlPostStore.DeleteSidebarCategory", "store.sql_channel.sidebar_categories.delete_invalid.app_error", nil, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the channels in the category
|
||||||
|
sql, args, _ := s.getQueryBuilder().
|
||||||
|
Delete("SidebarChannels").
|
||||||
|
Where(sq.Eq{"CategoryId": categoryId}).ToSql()
|
||||||
|
|
||||||
|
if _, err := transaction.Exec(sql, args...); err != nil {
|
||||||
|
return model.NewAppError("SqlPostStore.DeleteSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the category itself
|
||||||
|
sql, args, _ = s.getQueryBuilder().
|
||||||
|
Delete("SidebarCategories").
|
||||||
|
Where(sq.Eq{"Id": categoryId}).ToSql()
|
||||||
|
|
||||||
|
if _, err := transaction.Exec(sql, args...); err != nil {
|
||||||
|
return model.NewAppError("SqlChannelStore.DeleteSidebarCategory", "store.sql_channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := transaction.Commit(); err != nil {
|
||||||
|
return model.NewAppError("SqlChannelStore.DeleteSidebarCategory", "store.sql_channel.sidebar_categories.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
14
store/sqlstore/channel_store_categories_test.go
Обычный файл
14
store/sqlstore/channel_store_categories_test.go
Обычный файл
@@ -0,0 +1,14 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package sqlstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v5/store/storetest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestChannelStoreCategories(t *testing.T) {
|
||||||
|
StoreTestWithSqlSupplier(t, storetest.TestChannelStoreCategories)
|
||||||
|
}
|
||||||
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
1678
store/storetest/channel_store_categories.go
Обычный файл
1678
store/storetest/channel_store_categories.go
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Ссылка в новой задаче
Block a user