2020-10-21 11:32:13 +02:00
package app
import (
2022-03-22 16:24:34 +02:00
"errors"
2022-02-05 00:12:28 +02:00
"fmt"
2021-12-10 15:28:52 +02:00
2021-01-27 00:13:46 +02:00
"github.com/mattermost/focalboard/server/model"
2021-09-13 21:36:36 +02:00
"github.com/mattermost/focalboard/server/services/notify"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
2020-10-21 11:32:13 +02:00
)
2022-03-22 16:24:34 +02:00
var ErrBlocksFromMultipleBoards = errors . New ( "the block set contain blocks from multiple boards" )
2022-10-25 22:46:43 +02:00
func ( a * App ) GetBlocks ( boardID , parentID string , blockType string ) ( [ ] * model . Block , error ) {
2022-03-22 16:24:34 +02:00
if boardID == "" {
2022-10-25 22:46:43 +02:00
return [ ] * model . Block { } , nil
2022-03-22 16:24:34 +02:00
}
2021-08-02 17:46:00 +02:00
if blockType != "" && parentID != "" {
2022-03-22 16:24:34 +02:00
return a . store . GetBlocksWithParentAndType ( boardID , parentID , blockType )
2020-10-21 11:32:13 +02:00
}
2020-10-22 15:22:36 +02:00
2021-08-02 17:46:00 +02:00
if blockType != "" {
2022-03-22 16:24:34 +02:00
return a . store . GetBlocksWithType ( boardID , blockType )
2020-10-21 11:32:13 +02:00
}
2020-10-22 15:22:36 +02:00
2022-03-22 16:24:34 +02:00
return a . store . GetBlocksWithParent ( boardID , parentID )
2020-10-21 11:32:13 +02:00
}
2022-10-25 22:46:43 +02:00
func ( a * App ) DuplicateBlock ( boardID string , blockID string , userID string , asTemplate bool ) ( [ ] * model . Block , error ) {
2022-03-22 16:24:34 +02:00
board , err := a . GetBoard ( boardID )
if err != nil {
return nil , err
}
if board == nil {
return nil , fmt . Errorf ( "cannot fetch board %s for DuplicateBlock: %w" , boardID , err )
}
blocks , err := a . store . DuplicateBlock ( boardID , blockID , userID , asTemplate )
if err != nil {
return nil , err
}
2021-05-13 23:04:49 +02:00
2023-05-22 18:31:24 +02:00
err = a . CopyAndUpdateCardFiles ( boardID , userID , blocks , asTemplate )
if err != nil {
return nil , err
}
2022-03-22 16:24:34 +02:00
a . blockChangeNotifier . Enqueue ( func ( ) error {
for _ , block := range blocks {
a . wsAdapter . BroadcastBlockChange ( board . TeamID , block )
}
return nil
} )
2022-06-15 12:17:44 +02:00
go func ( ) {
if uErr := a . UpdateCardLimitTimestamp ( ) ; uErr != nil {
a . logger . Error (
"UpdateCardLimitTimestamp failed duplicating a block" ,
mlog . Err ( uErr ) ,
)
}
} ( )
2022-03-22 16:24:34 +02:00
return blocks , err
2021-01-13 04:49:08 +02:00
}
2022-09-08 13:01:33 +02:00
func ( a * App ) PatchBlock ( blockID string , blockPatch * model . BlockPatch , modifiedByID string ) ( * model . Block , error ) {
2022-08-23 01:06:45 +02:00
return a . PatchBlockAndNotify ( blockID , blockPatch , modifiedByID , false )
}
2022-09-08 13:01:33 +02:00
func ( a * App ) PatchBlockAndNotify ( blockID string , blockPatch * model . BlockPatch , modifiedByID string , disableNotify bool ) ( * model . Block , error ) {
2022-03-22 16:24:34 +02:00
oldBlock , err := a . store . GetBlock ( blockID )
2021-09-13 21:36:36 +02:00
if err != nil {
2022-09-08 13:01:33 +02:00
return nil , err
2022-06-15 12:17:44 +02:00
}
if a . IsCloudLimited ( ) {
2022-10-25 22:46:43 +02:00
containsLimitedBlocks , lErr := a . ContainsLimitedBlocks ( [ ] * model . Block { oldBlock } )
2022-06-15 12:17:44 +02:00
if lErr != nil {
2022-09-08 13:01:33 +02:00
return nil , lErr
2022-06-15 12:17:44 +02:00
}
if containsLimitedBlocks {
2022-09-13 12:18:40 +02:00
return nil , model . ErrPatchUpdatesLimitedCards
2022-06-15 12:17:44 +02:00
}
2021-09-13 21:36:36 +02:00
}
2022-03-22 16:24:34 +02:00
board , err := a . store . GetBoard ( oldBlock . BoardID )
if err != nil {
2022-09-08 13:01:33 +02:00
return nil , err
2022-03-22 16:24:34 +02:00
}
err = a . store . PatchBlock ( blockID , blockPatch , modifiedByID )
2021-08-06 14:10:24 +02:00
if err != nil {
2022-09-08 13:01:33 +02:00
return nil , err
2021-08-06 14:10:24 +02:00
}
2021-09-13 21:36:36 +02:00
2021-08-06 14:10:24 +02:00
a . metrics . IncrementBlocksPatched ( 1 )
2022-03-22 16:24:34 +02:00
block , err := a . store . GetBlock ( blockID )
2021-08-06 14:10:24 +02:00
if err != nil {
2022-09-08 13:01:33 +02:00
return nil , err
2021-08-06 14:10:24 +02:00
}
2022-03-22 16:24:34 +02:00
a . blockChangeNotifier . Enqueue ( func ( ) error {
// broadcast on websocket
2022-10-25 22:46:43 +02:00
a . wsAdapter . BroadcastBlockChange ( board . TeamID , block )
2022-03-22 16:24:34 +02:00
// broadcast on webhooks
2022-10-25 22:46:43 +02:00
a . webhook . NotifyUpdate ( block )
2022-03-22 16:24:34 +02:00
// send notifications
2022-08-23 01:06:45 +02:00
if ! disableNotify {
a . notifyBlockChanged ( notify . Update , block , oldBlock , modifiedByID )
}
2022-03-22 16:24:34 +02:00
return nil
} )
2022-09-08 13:01:33 +02:00
return block , nil
2021-08-06 14:10:24 +02:00
}
2022-03-22 16:24:34 +02:00
func ( a * App ) PatchBlocks ( teamID string , blockPatches * model . BlockPatchBatch , modifiedByID string ) error {
2022-08-23 01:06:45 +02:00
return a . PatchBlocksAndNotify ( teamID , blockPatches , modifiedByID , false )
}
func ( a * App ) PatchBlocksAndNotify ( teamID string , blockPatches * model . BlockPatchBatch , modifiedByID string , disableNotify bool ) error {
2022-06-15 12:17:44 +02:00
oldBlocks , err := a . store . GetBlocksByIDs ( blockPatches . BlockIDs )
if err != nil {
return err
}
if a . IsCloudLimited ( ) {
containsLimitedBlocks , err := a . ContainsLimitedBlocks ( oldBlocks )
2021-12-10 16:17:00 +02:00
if err != nil {
2022-06-15 12:17:44 +02:00
return err
}
if containsLimitedBlocks {
2022-09-13 12:18:40 +02:00
return model . ErrPatchUpdatesLimitedCards
2021-12-10 16:17:00 +02:00
}
}
2022-06-15 12:17:44 +02:00
if err := a . store . PatchBlocks ( blockPatches , modifiedByID ) ; err != nil {
2021-12-10 16:17:00 +02:00
return err
}
2022-03-22 16:24:34 +02:00
a . blockChangeNotifier . Enqueue ( func ( ) error {
a . metrics . IncrementBlocksPatched ( len ( oldBlocks ) )
for i , blockID := range blockPatches . BlockIDs {
newBlock , err := a . store . GetBlock ( blockID )
if err != nil {
2022-06-15 12:17:44 +02:00
return err
2022-03-22 16:24:34 +02:00
}
2022-10-25 22:46:43 +02:00
a . wsAdapter . BroadcastBlockChange ( teamID , newBlock )
a . webhook . NotifyUpdate ( newBlock )
2022-08-23 01:06:45 +02:00
if ! disableNotify {
2022-10-25 22:46:43 +02:00
a . notifyBlockChanged ( notify . Update , newBlock , oldBlocks [ i ] , modifiedByID )
2022-08-23 01:06:45 +02:00
}
2022-03-22 16:24:34 +02:00
}
return nil
} )
2021-12-10 16:17:00 +02:00
return nil
}
2022-10-25 22:46:43 +02:00
func ( a * App ) InsertBlock ( block * model . Block , modifiedByID string ) error {
2022-08-23 01:06:45 +02:00
return a . InsertBlockAndNotify ( block , modifiedByID , false )
}
2022-10-25 22:46:43 +02:00
func ( a * App ) InsertBlockAndNotify ( block * model . Block , modifiedByID string , disableNotify bool ) error {
2022-03-22 16:24:34 +02:00
board , bErr := a . store . GetBoard ( block . BoardID )
if bErr != nil {
return bErr
}
2022-10-25 22:46:43 +02:00
err := a . store . InsertBlock ( block , modifiedByID )
2021-06-04 16:38:49 +02:00
if err == nil {
2022-03-22 16:24:34 +02:00
a . blockChangeNotifier . Enqueue ( func ( ) error {
a . wsAdapter . BroadcastBlockChange ( board . TeamID , block )
a . metrics . IncrementBlocksInserted ( 1 )
2021-09-13 21:36:36 +02:00
a . webhook . NotifyUpdate ( block )
2022-08-23 01:06:45 +02:00
if ! disableNotify {
2022-10-25 22:46:43 +02:00
a . notifyBlockChanged ( notify . Add , block , nil , modifiedByID )
2022-08-23 01:06:45 +02:00
}
2022-03-22 16:24:34 +02:00
return nil
} )
2021-06-04 16:38:49 +02:00
}
2022-06-15 12:17:44 +02:00
go func ( ) {
if uErr := a . UpdateCardLimitTimestamp ( ) ; uErr != nil {
a . logger . Error (
"UpdateCardLimitTimestamp failed after inserting a block" ,
mlog . Err ( uErr ) ,
)
}
} ( )
2021-06-04 16:38:49 +02:00
return err
2020-10-21 11:32:13 +02:00
}
2022-10-25 22:46:43 +02:00
func ( a * App ) isWithinViewsLimit ( boardID string , block * model . Block ) ( bool , error ) {
2022-12-22 19:40:20 +02:00
// ToDo: Cloud Limits have been disabled by design. We should
// revisit the decision and update the related code accordingly
2022-06-29 14:35:24 +02:00
2022-12-22 19:40:20 +02:00
/ *
limits , err := a . GetBoardsCloudLimits ( )
if err != nil {
return false , err
}
2022-06-29 14:35:24 +02:00
2022-12-22 19:40:20 +02:00
if limits . Views == model . LimitUnlimited {
return true , nil
}
views , err := a . store . GetBlocksWithParentAndType ( boardID , block . ParentID , model . TypeView )
if err != nil {
return false , err
}
// < rather than <= because we'll be creating new view if this
// check passes. When that view is created, the limit will be reached.
// That's why we need to check for if existing + the being-created
// view doesn't exceed the limit.
return len ( views ) < limits . Views , nil
* /
2022-06-29 14:35:24 +02:00
2022-12-22 19:40:20 +02:00
return true , nil
2022-06-29 14:35:24 +02:00
}
2022-10-25 22:46:43 +02:00
func ( a * App ) InsertBlocks ( blocks [ ] * model . Block , modifiedByID string ) ( [ ] * model . Block , error ) {
2022-08-23 01:06:45 +02:00
return a . InsertBlocksAndNotify ( blocks , modifiedByID , false )
}
2022-10-25 22:46:43 +02:00
func ( a * App ) InsertBlocksAndNotify ( blocks [ ] * model . Block , modifiedByID string , disableNotify bool ) ( [ ] * model . Block , error ) {
2022-03-22 16:24:34 +02:00
if len ( blocks ) == 0 {
2022-10-25 22:46:43 +02:00
return [ ] * model . Block { } , nil
2022-03-22 16:24:34 +02:00
}
// all blocks must belong to the same board
boardID := blocks [ 0 ] . BoardID
for _ , block := range blocks {
if block . BoardID != boardID {
return nil , ErrBlocksFromMultipleBoards
}
}
board , err := a . store . GetBoard ( boardID )
if err != nil {
return nil , err
}
2022-10-25 22:46:43 +02:00
needsNotify := make ( [ ] * model . Block , 0 , len ( blocks ) )
2021-07-08 16:36:43 +02:00
for i := range blocks {
2022-06-29 14:35:24 +02:00
// this check is needed to whitelist inbuilt template
// initialization. They do contain more than 5 views per board.
if boardID != "0" && blocks [ i ] . Type == model . TypeView {
withinLimit , err := a . isWithinViewsLimit ( board . ID , blocks [ i ] )
if err != nil {
return nil , err
}
if ! withinLimit {
a . logger . Info ( "views limit reached on board" , mlog . String ( "board_id" , blocks [ i ] . ParentID ) , mlog . String ( "team_id" , board . TeamID ) )
2022-09-13 12:18:40 +02:00
return nil , model . ErrViewsLimitReached
2022-06-29 14:35:24 +02:00
}
}
2022-10-25 22:46:43 +02:00
err := a . store . InsertBlock ( blocks [ i ] , modifiedByID )
2020-10-21 11:32:13 +02:00
if err != nil {
2021-11-05 12:54:27 +02:00
return nil , err
2020-10-21 11:32:13 +02:00
}
2021-09-13 21:36:36 +02:00
needsNotify = append ( needsNotify , blocks [ i ] )
2022-03-22 16:24:34 +02:00
a . wsAdapter . BroadcastBlockChange ( board . TeamID , blocks [ i ] )
2021-09-13 21:36:36 +02:00
a . metrics . IncrementBlocksInserted ( 1 )
2020-10-21 11:32:13 +02:00
}
2022-03-22 16:24:34 +02:00
a . blockChangeNotifier . Enqueue ( func ( ) error {
2021-09-13 21:36:36 +02:00
for _ , b := range needsNotify {
block := b
a . webhook . NotifyUpdate ( block )
2022-08-23 01:06:45 +02:00
if ! disableNotify {
2022-10-25 22:46:43 +02:00
a . notifyBlockChanged ( notify . Add , block , nil , modifiedByID )
2021-10-15 23:09:43 +02:00
}
2021-09-13 21:36:36 +02:00
}
2022-03-22 16:24:34 +02:00
return nil
} )
2021-09-13 21:36:36 +02:00
2022-06-15 12:17:44 +02:00
go func ( ) {
if err := a . UpdateCardLimitTimestamp ( ) ; err != nil {
a . logger . Error (
"UpdateCardLimitTimestamp failed after inserting blocks" ,
mlog . Err ( err ) ,
)
}
} ( )
2021-11-05 12:54:27 +02:00
return blocks , nil
2020-10-21 11:32:13 +02:00
}
2022-03-22 16:24:34 +02:00
func ( a * App ) GetBlockByID ( blockID string ) ( * model . Block , error ) {
return a . store . GetBlock ( blockID )
2022-02-02 02:01:29 +02:00
}
2022-03-22 16:24:34 +02:00
func ( a * App ) DeleteBlock ( blockID string , modifiedBy string ) error {
2022-08-23 01:06:45 +02:00
return a . DeleteBlockAndNotify ( blockID , modifiedBy , false )
}
func ( a * App ) DeleteBlockAndNotify ( blockID string , modifiedBy string , disableNotify bool ) error {
2022-03-22 16:24:34 +02:00
block , err := a . store . GetBlock ( blockID )
if err != nil {
return err
}
board , err := a . store . GetBoard ( block . BoardID )
2020-10-21 11:32:13 +02:00
if err != nil {
return err
}
2022-01-05 11:02:06 +02:00
if block == nil {
// deleting non-existing block not considered an error
return nil
}
2022-03-22 16:24:34 +02:00
err = a . store . DeleteBlock ( blockID , modifiedBy )
2020-10-21 11:32:13 +02:00
if err != nil {
return err
}
2022-03-22 16:24:34 +02:00
a . blockChangeNotifier . Enqueue ( func ( ) error {
a . wsAdapter . BroadcastBlockDelete ( board . TeamID , blockID , block . BoardID )
a . metrics . IncrementBlocksDeleted ( 1 )
2022-08-23 01:06:45 +02:00
if ! disableNotify {
a . notifyBlockChanged ( notify . Delete , block , block , modifiedBy )
}
2022-03-22 16:24:34 +02:00
return nil
} )
2022-06-15 12:17:44 +02:00
go func ( ) {
if err := a . UpdateCardLimitTimestamp ( ) ; err != nil {
a . logger . Error (
"UpdateCardLimitTimestamp failed after deleting a block" ,
mlog . Err ( err ) ,
)
}
} ( )
2020-10-21 11:32:13 +02:00
return nil
}
2021-06-04 16:38:49 +02:00
2022-04-05 17:00:04 +02:00
func ( a * App ) GetLastBlockHistoryEntry ( blockID string ) ( * model . Block , error ) {
2022-03-22 16:24:34 +02:00
blocks , err := a . store . GetBlockHistory ( blockID , model . QueryBlockHistoryOptions { Limit : 1 , Descending : true } )
2022-02-22 19:42:49 +02:00
if err != nil {
2022-04-05 17:00:04 +02:00
return nil , err
}
if len ( blocks ) == 0 {
return nil , nil
}
2022-10-25 22:46:43 +02:00
return blocks [ 0 ] , nil
2022-04-05 17:00:04 +02:00
}
func ( a * App ) UndeleteBlock ( blockID string , modifiedBy string ) ( * model . Block , error ) {
blocks , err := a . store . GetBlockHistory ( blockID , model . QueryBlockHistoryOptions { Limit : 1 , Descending : true } )
if err != nil {
return nil , err
2022-02-22 19:42:49 +02:00
}
if len ( blocks ) == 0 {
2022-03-22 16:24:34 +02:00
// undeleting non-existing block not considered an error
2022-04-05 17:00:04 +02:00
return nil , nil
2022-02-22 19:42:49 +02:00
}
2022-03-22 16:24:34 +02:00
err = a . store . UndeleteBlock ( blockID , modifiedBy )
2022-02-22 19:42:49 +02:00
if err != nil {
2022-04-05 17:00:04 +02:00
return nil , err
2022-02-22 19:42:49 +02:00
}
2022-03-22 16:24:34 +02:00
block , err := a . store . GetBlock ( blockID )
2022-09-13 12:18:40 +02:00
if model . IsErrNotFound ( err ) {
a . logger . Error ( "Error loading the block after a successful undelete, not propagating through websockets or notifications" , mlog . String ( "blockID" , blockID ) )
2022-04-05 17:00:04 +02:00
return nil , err
2022-02-22 19:42:49 +02:00
}
2022-09-13 12:18:40 +02:00
if err != nil {
return nil , err
2022-02-22 19:42:49 +02:00
}
2022-03-22 16:24:34 +02:00
board , err := a . store . GetBoard ( block . BoardID )
if err != nil {
2022-04-05 17:00:04 +02:00
return nil , err
2022-03-22 16:24:34 +02:00
}
a . blockChangeNotifier . Enqueue ( func ( ) error {
2022-10-25 22:46:43 +02:00
a . wsAdapter . BroadcastBlockChange ( board . TeamID , block )
2022-03-22 16:24:34 +02:00
a . metrics . IncrementBlocksInserted ( 1 )
2022-10-25 22:46:43 +02:00
a . webhook . NotifyUpdate ( block )
2022-03-22 16:24:34 +02:00
a . notifyBlockChanged ( notify . Add , block , nil , modifiedBy )
2022-06-15 12:17:44 +02:00
2022-03-22 16:24:34 +02:00
return nil
} )
2022-06-15 12:17:44 +02:00
go func ( ) {
if err := a . UpdateCardLimitTimestamp ( ) ; err != nil {
a . logger . Error (
"UpdateCardLimitTimestamp failed after undeleting a block" ,
mlog . Err ( err ) ,
)
}
} ( )
2022-04-05 17:00:04 +02:00
return block , nil
2022-02-22 19:42:49 +02:00
}
2021-06-04 16:38:49 +02:00
func ( a * App ) GetBlockCountsByType ( ) ( map [ string ] int64 , error ) {
return a . store . GetBlockCountsByType ( )
}
2021-09-13 21:36:36 +02:00
2022-10-25 22:46:43 +02:00
func ( a * App ) GetBlocksForBoard ( boardID string ) ( [ ] * model . Block , error ) {
2022-03-22 16:24:34 +02:00
return a . store . GetBlocksForBoard ( boardID )
}
func ( a * App ) notifyBlockChanged ( action notify . Action , block * model . Block , oldBlock * model . Block , modifiedByID string ) {
2022-04-08 13:46:16 +02:00
// don't notify if notifications service disabled, or block change is generated via system user.
if a . notifications == nil || modifiedByID == model . SystemUserID {
2021-09-13 21:36:36 +02:00
return
}
// find card and board for the changed block.
2022-03-22 16:24:34 +02:00
board , card , err := a . getBoardAndCard ( block )
2021-09-13 21:36:36 +02:00
if err != nil {
a . logger . Error ( "Error notifying for block change; cannot determine board or card" , mlog . Err ( err ) )
return
}
2022-04-14 00:09:55 +02:00
boardMember , _ := a . GetMemberForBoard ( board . ID , modifiedByID )
if boardMember == nil {
// create temporary guest board member
boardMember = & model . BoardMember {
BoardID : board . ID ,
UserID : modifiedByID ,
}
}
2021-09-13 21:36:36 +02:00
evt := notify . BlockChangeEvent {
Action : action ,
2022-03-22 16:24:34 +02:00
TeamID : board . TeamID ,
2021-09-13 21:36:36 +02:00
Board : board ,
Card : card ,
BlockChanged : block ,
BlockOld : oldBlock ,
2022-04-14 00:09:55 +02:00
ModifiedBy : boardMember ,
2021-09-13 21:36:36 +02:00
}
a . notifications . BlockChanged ( evt )
}
2022-03-22 16:24:34 +02:00
const (
maxSearchDepth = 50
)
// getBoardAndCard returns the first parent of type `card` its board for the specified block.
// `board` and/or `card` may return nil without error if the block does not belong to a board or card.
func ( a * App ) getBoardAndCard ( block * model . Block ) ( board * model . Board , card * model . Block , err error ) {
board , err = a . store . GetBoard ( block . BoardID )
if err != nil {
return board , card , err
}
var count int // don't let invalid blocks hierarchy cause infinite loop.
iter := block
for {
count ++
if card == nil && iter . Type == model . TypeCard {
card = iter
}
if iter . ParentID == "" || ( board != nil && card != nil ) || count > maxSearchDepth {
break
}
iter , err = a . store . GetBlock ( iter . ParentID )
2022-09-13 12:18:40 +02:00
if model . IsErrNotFound ( err ) {
return board , card , nil
}
if err != nil {
2022-03-22 16:24:34 +02:00
return board , card , err
}
}
return board , card , nil
}