2022-04-14 00:10:53 +02:00
package app
2022-10-26 13:08:03 +02:00
import (
2022-12-14 10:51:53 +02:00
"errors"
2022-10-26 13:08:03 +02:00
"fmt"
"github.com/mattermost/focalboard/server/model"
)
const defaultCategoryBoards = "Boards"
2022-04-14 00:10:53 +02:00
2022-12-14 10:51:53 +02:00
var errCategoryBoardsLengthMismatch = errors . New ( "cannot update category boards order, passed list of categories boards different size than in database" )
var errBoardNotFoundInCategory = errors . New ( "specified board ID not found in specified category ID" )
var errBoardMembershipNotFound = errors . New ( "board membership not found for user's board" )
2022-04-14 00:10:53 +02:00
func ( a * App ) GetUserCategoryBoards ( userID , teamID string ) ( [ ] model . CategoryBoards , error ) {
2022-10-26 13:08:03 +02:00
categoryBoards , err := a . store . GetUserCategoryBoards ( userID , teamID )
if err != nil {
return nil , err
}
createdCategoryBoards , err := a . createDefaultCategoriesIfRequired ( categoryBoards , userID , teamID )
if err != nil {
return nil , err
}
categoryBoards = append ( categoryBoards , createdCategoryBoards ... )
return categoryBoards , nil
}
func ( a * App ) createDefaultCategoriesIfRequired ( existingCategoryBoards [ ] model . CategoryBoards , userID , teamID string ) ( [ ] model . CategoryBoards , error ) {
createdCategories := [ ] model . CategoryBoards { }
boardsCategoryExist := false
for _ , categoryBoard := range existingCategoryBoards {
if categoryBoard . Name == defaultCategoryBoards {
boardsCategoryExist = true
}
}
if ! boardsCategoryExist {
createdCategoryBoards , err := a . createBoardsCategory ( userID , teamID , existingCategoryBoards )
if err != nil {
return nil , err
}
createdCategories = append ( createdCategories , * createdCategoryBoards )
}
return createdCategories , nil
}
func ( a * App ) createBoardsCategory ( userID , teamID string , existingCategoryBoards [ ] model . CategoryBoards ) ( * model . CategoryBoards , error ) {
// create the category
category := model . Category {
Name : defaultCategoryBoards ,
UserID : userID ,
TeamID : teamID ,
Collapsed : false ,
Type : model . CategoryTypeSystem ,
2022-12-14 10:51:53 +02:00
SortOrder : len ( existingCategoryBoards ) * model . CategoryBoardsSortOrderGap ,
2022-10-26 13:08:03 +02:00
}
createdCategory , err := a . CreateCategory ( & category )
if err != nil {
return nil , fmt . Errorf ( "createBoardsCategory default category creation failed: %w" , err )
}
// once the category is created, we need to move all boards which do not
// belong to any category, into this category.
2022-12-14 10:51:53 +02:00
2022-11-24 10:21:17 +02:00
boardMembers , err := a . GetMembersForUser ( userID )
2022-10-26 13:08:03 +02:00
if err != nil {
2022-11-24 11:01:33 +02:00
return nil , fmt . Errorf ( "createBoardsCategory error fetching user's board memberships: %w" , err )
2022-10-26 13:08:03 +02:00
}
2022-12-14 10:51:53 +02:00
boardMemberByBoardID := map [ string ] * model . BoardMember { }
for _ , boardMember := range boardMembers {
boardMemberByBoardID [ boardMember . BoardID ] = boardMember
}
2022-10-26 13:08:03 +02:00
createdCategoryBoards := & model . CategoryBoards {
2023-01-24 12:11:54 +02:00
Category : * createdCategory ,
BoardMetadata : [ ] model . CategoryBoardMetadata { } ,
2022-10-26 13:08:03 +02:00
}
2022-12-14 10:51:53 +02:00
// get user's current team's baords
userTeamBoards , err := a . GetBoardsForUserAndTeam ( userID , teamID , false )
if err != nil {
return nil , fmt . Errorf ( "createBoardsCategory error fetching user's team's boards: %w" , err )
}
2023-01-24 12:11:54 +02:00
boardIDsToAdd := [ ] string { }
2022-12-14 10:51:53 +02:00
for _ , board := range userTeamBoards {
boardMembership , ok := boardMemberByBoardID [ board . ID ]
if ! ok {
return nil , fmt . Errorf ( "createBoardsCategory: %w" , errBoardMembershipNotFound )
}
2022-11-24 10:21:17 +02:00
// boards with implicit access (aka synthetic membership),
// should show up in LHS only when openign them explicitelly.
// So we don't process any synthetic membership boards
// and only add boards with explicit access to, to the the LHS,
// for example, if a user explicitelly added another user to a board.
2022-12-14 10:51:53 +02:00
if boardMembership . Synthetic {
2022-11-24 10:21:17 +02:00
continue
}
2022-10-26 13:08:03 +02:00
belongsToCategory := false
for _ , categoryBoard := range existingCategoryBoards {
2023-01-24 12:11:54 +02:00
for _ , metadata := range categoryBoard . BoardMetadata {
if metadata . BoardID == board . ID {
2022-10-26 13:08:03 +02:00
belongsToCategory = true
break
}
}
// stop looking into other categories if
// the board was found in a category
if belongsToCategory {
break
}
}
if ! belongsToCategory {
2023-01-24 12:11:54 +02:00
boardIDsToAdd = append ( boardIDsToAdd , board . ID )
newBoardMetadata := model . CategoryBoardMetadata {
BoardID : board . ID ,
Hidden : false ,
2022-10-26 13:08:03 +02:00
}
2023-01-24 12:11:54 +02:00
createdCategoryBoards . BoardMetadata = append ( createdCategoryBoards . BoardMetadata , newBoardMetadata )
}
}
2022-10-26 13:08:03 +02:00
2023-01-24 12:11:54 +02:00
if len ( boardIDsToAdd ) > 0 {
if err := a . AddUpdateUserCategoryBoard ( teamID , userID , createdCategory . ID , boardIDsToAdd ) ; err != nil {
return nil , fmt . Errorf ( "createBoardsCategory failed to add category-less board to the default category, defaultCategoryID: %s, error: %w" , createdCategory . ID , err )
2022-10-26 13:08:03 +02:00
}
}
return createdCategoryBoards , nil
2022-04-14 00:10:53 +02:00
}
2023-01-24 12:11:54 +02:00
func ( a * App ) AddUpdateUserCategoryBoard ( teamID , userID , categoryID string , boardIDs [ ] string ) error {
if len ( boardIDs ) == 0 {
return nil
}
err := a . store . AddUpdateCategoryBoard ( userID , categoryID , boardIDs )
2022-04-14 00:10:53 +02:00
if err != nil {
return err
}
2023-01-24 12:11:54 +02:00
userCategoryBoards , err := a . GetUserCategoryBoards ( userID , teamID )
if err != nil {
return err
}
var updatedCategory * model . CategoryBoards
for i := range userCategoryBoards {
if userCategoryBoards [ i ] . ID == categoryID {
updatedCategory = & userCategoryBoards [ i ]
break
}
}
if updatedCategory == nil {
return errCategoryNotFound
}
wsPayload := make ( [ ] * model . BoardCategoryWebsocketData , len ( updatedCategory . BoardMetadata ) )
2022-12-14 10:51:53 +02:00
i := 0
2023-01-24 12:11:54 +02:00
for _ , categoryBoardMetadata := range updatedCategory . BoardMetadata {
2022-12-14 10:51:53 +02:00
wsPayload [ i ] = & model . BoardCategoryWebsocketData {
2023-01-24 12:11:54 +02:00
BoardID : categoryBoardMetadata . BoardID ,
2022-12-14 10:51:53 +02:00
CategoryID : categoryID ,
2023-01-24 12:11:54 +02:00
Hidden : categoryBoardMetadata . Hidden ,
2022-12-14 10:51:53 +02:00
}
i ++
}
2022-04-14 00:10:53 +02:00
a . blockChangeNotifier . Enqueue ( func ( ) error {
a . wsAdapter . BroadcastCategoryBoardChange (
teamID ,
userID ,
2022-12-14 10:51:53 +02:00
wsPayload ,
)
2022-04-14 00:10:53 +02:00
return nil
} )
return nil
}
2022-12-14 10:51:53 +02:00
func ( a * App ) ReorderCategoryBoards ( userID , teamID , categoryID string , newBoardsOrder [ ] string ) ( [ ] string , error ) {
if err := a . verifyNewCategoryBoardsMatchExisting ( userID , teamID , categoryID , newBoardsOrder ) ; err != nil {
return nil , err
}
newOrder , err := a . store . ReorderCategoryBoards ( categoryID , newBoardsOrder )
if err != nil {
return nil , err
}
go func ( ) {
a . wsAdapter . BroadcastCategoryBoardsReorder ( teamID , userID , categoryID , newOrder )
} ( )
return newOrder , nil
}
func ( a * App ) verifyNewCategoryBoardsMatchExisting ( userID , teamID , categoryID string , newBoardsOrder [ ] string ) error {
// this function is to ensure that we don't miss specifying
// all boards of the category while reordering.
existingCategoryBoards , err := a . GetUserCategoryBoards ( userID , teamID )
if err != nil {
return err
}
var targetCategoryBoards * model . CategoryBoards
for i := range existingCategoryBoards {
if existingCategoryBoards [ i ] . Category . ID == categoryID {
targetCategoryBoards = & existingCategoryBoards [ i ]
break
}
}
if targetCategoryBoards == nil {
return fmt . Errorf ( "%w categoryID: %s" , errCategoryNotFound , categoryID )
}
2023-01-24 12:11:54 +02:00
if len ( targetCategoryBoards . BoardMetadata ) != len ( newBoardsOrder ) {
2022-12-14 10:51:53 +02:00
return fmt . Errorf (
"%w length new category boards: %d, length existing category boards: %d, userID: %s, teamID: %s, categoryID: %s" ,
errCategoryBoardsLengthMismatch ,
len ( newBoardsOrder ) ,
2023-01-24 12:11:54 +02:00
len ( targetCategoryBoards . BoardMetadata ) ,
2022-12-14 10:51:53 +02:00
userID ,
teamID ,
categoryID ,
)
}
existingBoardMap := map [ string ] bool { }
2023-01-24 12:11:54 +02:00
for _ , metadata := range targetCategoryBoards . BoardMetadata {
existingBoardMap [ metadata . BoardID ] = true
2022-12-14 10:51:53 +02:00
}
for _ , boardID := range newBoardsOrder {
if _ , found := existingBoardMap [ boardID ] ; ! found {
return fmt . Errorf (
"%w board ID: %s, category ID: %s, userID: %s, teamID: %s" ,
errBoardNotFoundInCategory ,
boardID ,
categoryID ,
userID ,
teamID ,
)
}
}
return nil
}
2023-01-24 12:11:54 +02:00
func ( a * App ) SetBoardVisibility ( teamID , userID , categoryID , boardID string , visible bool ) error {
if err := a . store . SetBoardVisibility ( userID , categoryID , boardID , visible ) ; err != nil {
return fmt . Errorf ( "SetBoardVisibility: failed to update board visibility: %w" , err )
}
a . wsAdapter . BroadcastCategoryBoardChange ( teamID , userID , [ ] * model . BoardCategoryWebsocketData {
{
BoardID : boardID ,
CategoryID : categoryID ,
Hidden : ! visible ,
} ,
} )
return nil
}