2022-03-22 16:24:34 +02:00
|
|
|
package app
|
|
|
|
|
|
|
|
import (
|
2022-10-26 13:08:03 +02:00
|
|
|
"errors"
|
2022-11-24 12:01:32 +02:00
|
|
|
"fmt"
|
2022-10-26 13:08:03 +02:00
|
|
|
|
2022-03-22 16:24:34 +02:00
|
|
|
"github.com/mattermost/focalboard/server/model"
|
|
|
|
"github.com/mattermost/focalboard/server/utils"
|
|
|
|
)
|
|
|
|
|
2022-11-24 12:01:32 +02:00
|
|
|
var errCategoryNotFound = errors.New("category ID specified in input does not exist for user")
|
|
|
|
var errCategoriesLengthMismatch = errors.New("cannot update category order, passed list of categories different size than in database")
|
2022-10-26 13:08:03 +02:00
|
|
|
var ErrCannotDeleteSystemCategory = errors.New("cannot delete a system category")
|
|
|
|
var ErrCannotUpdateSystemCategory = errors.New("cannot update a system category")
|
|
|
|
|
2022-11-24 12:01:32 +02:00
|
|
|
func (a *App) GetCategory(categoryID string) (*model.Category, error) {
|
|
|
|
return a.store.GetCategory(categoryID)
|
|
|
|
}
|
|
|
|
|
2022-03-22 16:24:34 +02:00
|
|
|
func (a *App) CreateCategory(category *model.Category) (*model.Category, error) {
|
|
|
|
category.Hydrate()
|
|
|
|
if err := category.IsValid(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := a.store.CreateCategory(*category); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
createdCategory, err := a.store.GetCategory(category.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
a.wsAdapter.BroadcastCategoryChange(*createdCategory)
|
|
|
|
}()
|
|
|
|
|
|
|
|
return createdCategory, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *App) UpdateCategory(category *model.Category) (*model.Category, error) {
|
2022-11-24 12:01:32 +02:00
|
|
|
category.Hydrate()
|
|
|
|
|
2022-10-26 13:08:03 +02:00
|
|
|
if err := category.IsValid(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-03-22 16:24:34 +02:00
|
|
|
// verify if category belongs to the user
|
|
|
|
existingCategory, err := a.store.GetCategory(category.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if existingCategory.DeleteAt != 0 {
|
2022-09-13 12:18:40 +02:00
|
|
|
return nil, model.ErrCategoryDeleted
|
2022-03-22 16:24:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if existingCategory.UserID != category.UserID {
|
2022-09-13 12:18:40 +02:00
|
|
|
return nil, model.ErrCategoryPermissionDenied
|
2022-03-22 16:24:34 +02:00
|
|
|
}
|
|
|
|
|
2022-10-26 13:08:03 +02:00
|
|
|
if existingCategory.TeamID != category.TeamID {
|
|
|
|
return nil, model.ErrCategoryPermissionDenied
|
|
|
|
}
|
|
|
|
|
2022-11-08 18:40:30 +02:00
|
|
|
// in case type was defaulted above, set to existingCategory.Type
|
|
|
|
category.Type = existingCategory.Type
|
2022-10-26 13:08:03 +02:00
|
|
|
if existingCategory.Type == model.CategoryTypeSystem {
|
|
|
|
// You cannot rename or delete a system category,
|
|
|
|
// So restoring its name and undeleting it if set so.
|
|
|
|
category.Name = existingCategory.Name
|
|
|
|
category.DeleteAt = 0
|
|
|
|
}
|
|
|
|
|
2022-03-22 16:24:34 +02:00
|
|
|
category.UpdateAt = utils.GetMillis()
|
|
|
|
if err = category.IsValid(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err = a.store.UpdateCategory(*category); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
updatedCategory, err := a.store.GetCategory(category.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
a.wsAdapter.BroadcastCategoryChange(*updatedCategory)
|
|
|
|
}()
|
|
|
|
|
|
|
|
return updatedCategory, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *App) DeleteCategory(categoryID, userID, teamID string) (*model.Category, error) {
|
|
|
|
existingCategory, err := a.store.GetCategory(categoryID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// category is already deleted. This avoids
|
|
|
|
// overriding the original deleted at timestamp
|
|
|
|
if existingCategory.DeleteAt != 0 {
|
|
|
|
return existingCategory, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// verify if category belongs to the user
|
|
|
|
if existingCategory.UserID != userID {
|
2022-09-13 12:18:40 +02:00
|
|
|
return nil, model.ErrCategoryPermissionDenied
|
2022-03-22 16:24:34 +02:00
|
|
|
}
|
|
|
|
|
2022-04-05 17:00:04 +02:00
|
|
|
// verify if category belongs to the team
|
|
|
|
if existingCategory.TeamID != teamID {
|
2022-09-13 12:18:40 +02:00
|
|
|
return nil, model.NewErrInvalidCategory("category doesn't belong to the team")
|
2022-04-05 17:00:04 +02:00
|
|
|
}
|
|
|
|
|
2022-10-26 13:08:03 +02:00
|
|
|
if existingCategory.Type == model.CategoryTypeSystem {
|
|
|
|
return nil, ErrCannotDeleteSystemCategory
|
|
|
|
}
|
|
|
|
|
2022-11-24 12:01:32 +02:00
|
|
|
if err = a.moveBoardsToDefaultCategory(userID, teamID, categoryID); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-03-22 16:24:34 +02:00
|
|
|
if err = a.store.DeleteCategory(categoryID, userID, teamID); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
deletedCategory, err := a.store.GetCategory(categoryID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
a.wsAdapter.BroadcastCategoryChange(*deletedCategory)
|
|
|
|
}()
|
|
|
|
|
|
|
|
return deletedCategory, nil
|
|
|
|
}
|
2022-11-24 12:01:32 +02:00
|
|
|
|
|
|
|
func (a *App) moveBoardsToDefaultCategory(userID, teamID, sourceCategoryID string) error {
|
|
|
|
// we need a list of boards associated to this category
|
|
|
|
// so we can move them to user's default Boards category
|
|
|
|
categoryBoards, err := a.GetUserCategoryBoards(userID, teamID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var sourceCategoryBoards *model.CategoryBoards
|
|
|
|
defaultCategoryID := ""
|
|
|
|
|
|
|
|
// iterate user's categories to find the source category
|
|
|
|
// and the default category.
|
|
|
|
// We need source category to get the list of its board
|
|
|
|
// and the default category to know its ID to
|
|
|
|
// move source category's boards to.
|
|
|
|
for i := range categoryBoards {
|
|
|
|
if categoryBoards[i].ID == sourceCategoryID {
|
|
|
|
sourceCategoryBoards = &categoryBoards[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
if categoryBoards[i].Name == defaultCategoryBoards {
|
|
|
|
defaultCategoryID = categoryBoards[i].ID
|
|
|
|
}
|
|
|
|
|
|
|
|
// if both categories are found, no need to iterate furthur.
|
|
|
|
if sourceCategoryBoards != nil && defaultCategoryID != "" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if sourceCategoryBoards == nil {
|
|
|
|
return errCategoryNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
if defaultCategoryID == "" {
|
|
|
|
return fmt.Errorf("moveBoardsToDefaultCategory: %w", errNoDefaultCategoryFound)
|
|
|
|
}
|
|
|
|
|
|
|
|
boardCategoryMapping := map[string]string{}
|
|
|
|
|
|
|
|
for _, boardID := range sourceCategoryBoards.BoardIDs {
|
|
|
|
boardCategoryMapping[boardID] = defaultCategoryID
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := a.AddUpdateUserCategoryBoard(teamID, userID, boardCategoryMapping); err != nil {
|
|
|
|
return fmt.Errorf("moveBoardsToDefaultCategory: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *App) ReorderCategories(userID, teamID string, newCategoryOrder []string) ([]string, error) {
|
|
|
|
if err := a.verifyNewCategoriesMatchExisting(userID, teamID, newCategoryOrder); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
newOrder, err := a.store.ReorderCategories(userID, teamID, newCategoryOrder)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
a.wsAdapter.BroadcastCategoryReorder(teamID, userID, newOrder)
|
|
|
|
}()
|
|
|
|
|
|
|
|
return newOrder, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *App) verifyNewCategoriesMatchExisting(userID, teamID string, newCategoryOrder []string) error {
|
|
|
|
existingCategories, err := a.store.GetUserCategories(userID, teamID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(newCategoryOrder) != len(existingCategories) {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"%w length new categories: %d, length existing categories: %d, userID: %s, teamID: %s",
|
|
|
|
errCategoriesLengthMismatch,
|
|
|
|
len(newCategoryOrder),
|
|
|
|
len(existingCategories),
|
|
|
|
userID,
|
|
|
|
teamID,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
existingCategoriesMap := map[string]bool{}
|
|
|
|
for _, category := range existingCategories {
|
|
|
|
existingCategoriesMap[category.ID] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, newCategoryID := range newCategoryOrder {
|
|
|
|
if _, found := existingCategoriesMap[newCategoryID]; !found {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"%w specified category ID: %s, userID: %s, teamID: %s",
|
|
|
|
errCategoryNotFound,
|
|
|
|
newCategoryID,
|
|
|
|
userID,
|
|
|
|
teamID,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|