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
"path/filepath"
2023-02-09 16:24:34 +02:00
"strings"
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"
2022-02-05 00:12:28 +02:00
"github.com/mattermost/focalboard/server/utils"
2021-09-13 21:36:36 +02:00
"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
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-10-25 22:46:43 +02:00
func ( a * App ) CopyCardFiles ( sourceBoardID string , copiedBlocks [ ] * model . Block ) error {
2022-02-02 19:12:59 +02:00
// Images attached in cards have a path comprising the card's board ID.
// When we create a template from this board, we need to copy the files
// with the new board ID in path.
// Not doing so causing images in templates (and boards created from this
// template) to fail to load.
2022-04-25 18:37:57 +02:00
// look up ID of source sourceBoard, which may be different than the blocks.
sourceBoard , err := a . GetBoard ( sourceBoardID )
if err != nil || sourceBoard == nil {
return fmt . Errorf ( "cannot fetch source board %s for CopyCardFiles: %w" , sourceBoardID , err )
2022-02-05 00:12:28 +02:00
}
2022-04-25 18:37:57 +02:00
var destTeamID string
var destBoardID string
for i := range copiedBlocks {
block := copiedBlocks [ i ]
2023-02-09 16:24:34 +02:00
fileName := ""
isOk := false
switch block . Type {
case model . TypeImage :
fileName , isOk = block . Fields [ "fileId" ] . ( string )
if ! isOk || fileName == "" {
continue
}
case model . TypeAttachment :
fileName , isOk = block . Fields [ "attachmentId" ] . ( string )
if ! isOk || fileName == "" {
continue
}
default :
continue
2022-04-25 18:37:57 +02:00
}
2022-02-05 00:12:28 +02:00
2022-04-25 18:37:57 +02:00
// create unique filename in case we are copying cards within the same board.
2023-02-09 16:24:34 +02:00
ext := filepath . Ext ( fileName )
2022-04-25 18:37:57 +02:00
destFilename := utils . NewID ( utils . IDTypeNone ) + ext
2022-02-05 00:12:28 +02:00
2022-04-25 18:37:57 +02:00
if destBoardID == "" || block . BoardID != destBoardID {
destBoardID = block . BoardID
destBoard , err := a . GetBoard ( destBoardID )
if err != nil {
return fmt . Errorf ( "cannot fetch destination board %s for CopyCardFiles: %w" , sourceBoardID , err )
}
destTeamID = destBoard . TeamID
}
2023-02-09 16:24:34 +02:00
sourceFilePath := filepath . Join ( sourceBoard . TeamID , sourceBoard . ID , fileName )
2022-04-25 18:37:57 +02:00
destinationFilePath := filepath . Join ( destTeamID , block . BoardID , destFilename )
a . logger . Debug (
"Copying card file" ,
mlog . String ( "sourceFilePath" , sourceFilePath ) ,
mlog . String ( "destinationFilePath" , destinationFilePath ) ,
)
if err := a . filesBackend . CopyFile ( sourceFilePath , destinationFilePath ) ; err != nil {
a . logger . Error (
"CopyCardFiles failed to copy file" ,
2022-02-05 00:12:28 +02:00
mlog . String ( "sourceFilePath" , sourceFilePath ) ,
mlog . String ( "destinationFilePath" , destinationFilePath ) ,
2022-04-25 18:37:57 +02:00
mlog . Err ( err ) ,
2022-02-05 00:12:28 +02:00
)
2022-02-02 19:12:59 +02:00
}
2023-02-09 16:24:34 +02:00
if block . Type == model . TypeAttachment {
block . Fields [ "attachmentId" ] = destFilename
parts := strings . Split ( fileName , "." )
fileInfoID := parts [ 0 ] [ 1 : ]
fileInfo , err := a . store . GetFileInfo ( fileInfoID )
if err != nil {
return fmt . Errorf ( "CopyCardFiles: cannot retrieve original fileinfo: %w" , err )
}
newParts := strings . Split ( destFilename , "." )
newFileID := newParts [ 0 ] [ 1 : ]
fileInfo . Id = newFileID
err = a . store . SaveFileInfo ( fileInfo )
if err != nil {
return fmt . Errorf ( "CopyCardFiles: cannot create fileinfo: %w" , err )
}
} else {
block . Fields [ "fileId" ] = destFilename
}
2022-02-02 19:12:59 +02:00
}
return nil
}
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
}