focalboard/server/model/board.go
Doug Lauder 73d77682ac
Expose boards APIs for MPA (#4372)
* add Boards APIs to services map
2022-12-23 10:47:51 -05:00

427 lines
10 KiB
Go

package model
import (
"encoding/json"
"io"
"time"
)
type BoardType string
type BoardRole string
type BoardSearchField string
const (
BoardTypeOpen BoardType = "O"
BoardTypePrivate BoardType = "P"
)
const (
BoardRoleNone BoardRole = ""
BoardRoleViewer BoardRole = "viewer"
BoardRoleCommenter BoardRole = "commenter"
BoardRoleEditor BoardRole = "editor"
BoardRoleAdmin BoardRole = "admin"
)
const (
BoardSearchFieldNone BoardSearchField = ""
BoardSearchFieldTitle BoardSearchField = "title"
BoardSearchFieldPropertyName BoardSearchField = "property_name"
)
// Board groups a set of blocks and its layout
// swagger:model
type Board struct {
// The ID for the board
// required: true
ID string `json:"id"`
// The ID of the team that the board belongs to
// required: true
TeamID string `json:"teamId"`
// The ID of the channel that the board was created from
// required: false
ChannelID string `json:"channelId"`
// The ID of the user that created the board
// required: true
CreatedBy string `json:"createdBy"`
// The ID of the last user that updated the board
// required: true
ModifiedBy string `json:"modifiedBy"`
// The type of the board
// required: true
Type BoardType `json:"type"`
// The minimum role applied when somebody joins the board
// required: true
MinimumRole BoardRole `json:"minimumRole"`
// The title of the board
// required: false
Title string `json:"title"`
// The description of the board
// required: false
Description string `json:"description"`
// The icon of the board
// required: false
Icon string `json:"icon"`
// Indicates if the board shows the description on the interface
// required: false
ShowDescription bool `json:"showDescription"`
// Marks the template boards
// required: false
IsTemplate bool `json:"isTemplate"`
// Marks the template boards
// required: false
TemplateVersion int `json:"templateVersion"`
// The properties of the board
// required: false
Properties map[string]interface{} `json:"properties"`
// The properties of the board cards
// required: false
CardProperties []map[string]interface{} `json:"cardProperties"`
// The creation time in miliseconds since the current epoch
// required: true
CreateAt int64 `json:"createAt"`
// The last modified time in miliseconds since the current epoch
// required: true
UpdateAt int64 `json:"updateAt"`
// The deleted time in miliseconds since the current epoch. Set to indicate this block is deleted
// required: false
DeleteAt int64 `json:"deleteAt"`
}
// GetPropertyString returns the value of the specified property as a string,
// or error if the property does not exist or is not of type string.
func (b *Board) GetPropertyString(propName string) (string, error) {
val, ok := b.Properties[propName]
if !ok {
return "", NewErrNotFound(propName)
}
s, ok := val.(string)
if !ok {
return "", ErrInvalidPropertyValueType
}
return s, nil
}
// BoardPatch is a patch for modify boards
// swagger:model
type BoardPatch struct {
// The type of the board
// required: false
Type *BoardType `json:"type"`
// The minimum role applied when somebody joins the board
// required: false
MinimumRole *BoardRole `json:"minimumRole"`
// The title of the board
// required: false
Title *string `json:"title"`
// The description of the board
// required: false
Description *string `json:"description"`
// The icon of the board
// required: false
Icon *string `json:"icon"`
// Indicates if the board shows the description on the interface
// required: false
ShowDescription *bool `json:"showDescription"`
// Indicates if the board shows the description on the interface
// required: false
ChannelID *string `json:"channelId"`
// The board updated properties
// required: false
UpdatedProperties map[string]interface{} `json:"updatedProperties"`
// The board removed properties
// required: false
DeletedProperties []string `json:"deletedProperties"`
// The board updated card properties
// required: false
UpdatedCardProperties []map[string]interface{} `json:"updatedCardProperties"`
// The board removed card properties
// required: false
DeletedCardProperties []string `json:"deletedCardProperties"`
}
// BoardMember stores the information of the membership of a user on a board
// swagger:model
type BoardMember struct {
// The ID of the board
// required: true
BoardID string `json:"boardId"`
// The ID of the user
// required: true
UserID string `json:"userId"`
// The independent roles of the user on the board
// required: false
Roles string `json:"roles"`
// Minimum role because the board configuration
// required: false
MinimumRole string `json:"minimumRole"`
// Marks the user as an admin of the board
// required: true
SchemeAdmin bool `json:"schemeAdmin"`
// Marks the user as an editor of the board
// required: true
SchemeEditor bool `json:"schemeEditor"`
// Marks the user as an commenter of the board
// required: true
SchemeCommenter bool `json:"schemeCommenter"`
// Marks the user as an viewer of the board
// required: true
SchemeViewer bool `json:"schemeViewer"`
// Marks the membership as generated by an access group
// required: true
Synthetic bool `json:"synthetic"`
}
// BoardMetadata contains metadata for a Board
// swagger:model
type BoardMetadata struct {
// The ID for the board
// required: true
BoardID string `json:"boardId"`
// The most recent time a descendant of this board was added, modified, or deleted
// required: true
DescendantLastUpdateAt int64 `json:"descendantLastUpdateAt"`
// The earliest time a descendant of this board was added, modified, or deleted
// required: true
DescendantFirstUpdateAt int64 `json:"descendantFirstUpdateAt"`
// The ID of the user that created the board
// required: true
CreatedBy string `json:"createdBy"`
// The ID of the user that last modified the most recently modified descendant
// required: true
LastModifiedBy string `json:"lastModifiedBy"`
}
func BoardFromJSON(data io.Reader) *Board {
var board *Board
_ = json.NewDecoder(data).Decode(&board)
return board
}
func BoardsFromJSON(data io.Reader) []*Board {
var boards []*Board
_ = json.NewDecoder(data).Decode(&boards)
return boards
}
func BoardMemberFromJSON(data io.Reader) *BoardMember {
var boardMember *BoardMember
_ = json.NewDecoder(data).Decode(&boardMember)
return boardMember
}
func BoardMembersFromJSON(data io.Reader) []*BoardMember {
var boardMembers []*BoardMember
_ = json.NewDecoder(data).Decode(&boardMembers)
return boardMembers
}
func BoardMetadataFromJSON(data io.Reader) *BoardMetadata {
var boardMetadata *BoardMetadata
_ = json.NewDecoder(data).Decode(&boardMetadata)
return boardMetadata
}
// Patch returns an updated version of the board.
func (p *BoardPatch) Patch(board *Board) *Board {
if p.Type != nil {
board.Type = *p.Type
}
if p.Title != nil {
board.Title = *p.Title
}
if p.MinimumRole != nil {
board.MinimumRole = *p.MinimumRole
}
if p.Description != nil {
board.Description = *p.Description
}
if p.Icon != nil {
board.Icon = *p.Icon
}
if p.ShowDescription != nil {
board.ShowDescription = *p.ShowDescription
}
if p.ChannelID != nil {
board.ChannelID = *p.ChannelID
}
for key, property := range p.UpdatedProperties {
board.Properties[key] = property
}
for _, key := range p.DeletedProperties {
delete(board.Properties, key)
}
if len(p.UpdatedCardProperties) != 0 || len(p.DeletedCardProperties) != 0 {
// first we accumulate all properties indexed by, and maintain their order
keyOrder := []string{}
cardPropertyMap := map[string]map[string]interface{}{}
for _, prop := range board.CardProperties {
id, ok := prop["id"].(string)
if !ok {
// bad property, skipping
continue
}
cardPropertyMap[id] = prop
keyOrder = append(keyOrder, id)
}
// if there are properties marked for removal, we delete them
for _, propertyID := range p.DeletedCardProperties {
delete(cardPropertyMap, propertyID)
}
// if there are properties marked for update, we replace the
// existing ones or add them
for _, newprop := range p.UpdatedCardProperties {
id, ok := newprop["id"].(string)
if !ok {
// bad new property, skipping
continue
}
_, exists := cardPropertyMap[id]
if !exists {
keyOrder = append(keyOrder, id)
}
cardPropertyMap[id] = newprop
}
// and finally we flatten and save the updated properties
newCardProperties := []map[string]interface{}{}
for _, key := range keyOrder {
p, exists := cardPropertyMap[key]
if exists {
newCardProperties = append(newCardProperties, p)
}
}
board.CardProperties = newCardProperties
}
return board
}
func IsBoardTypeValid(t BoardType) bool {
return t == BoardTypeOpen || t == BoardTypePrivate
}
func IsBoardMinimumRoleValid(r BoardRole) bool {
return r == BoardRoleNone || r == BoardRoleAdmin || r == BoardRoleEditor || r == BoardRoleCommenter || r == BoardRoleViewer
}
func (p *BoardPatch) IsValid() error {
if p.Type != nil && !IsBoardTypeValid(*p.Type) {
return InvalidBoardErr{"invalid-board-type"}
}
if p.MinimumRole != nil && !IsBoardMinimumRoleValid(*p.MinimumRole) {
return InvalidBoardErr{"invalid-board-minimum-role"}
}
return nil
}
type InvalidBoardErr struct {
msg string
}
func (ibe InvalidBoardErr) Error() string {
return ibe.msg
}
func (b *Board) IsValid() error {
if b.TeamID == "" {
return InvalidBoardErr{"empty-team-id"}
}
if !IsBoardTypeValid(b.Type) {
return InvalidBoardErr{"invalid-board-type"}
}
if !IsBoardMinimumRoleValid(b.MinimumRole) {
return InvalidBoardErr{"invalid-board-minimum-role"}
}
return nil
}
// BoardMemberHistoryEntry stores the information of the membership of a user on a board
// swagger:model
type BoardMemberHistoryEntry struct {
// The ID of the board
// required: true
BoardID string `json:"boardId"`
// The ID of the user
// required: true
UserID string `json:"userId"`
// The action that added this history entry (created or deleted)
// required: false
Action string `json:"action"`
// The insertion time
// required: true
InsertAt time.Time `json:"insertAt"`
}
func BoardSearchFieldFromString(field string) (BoardSearchField, error) {
switch field {
case string(BoardSearchFieldTitle):
return BoardSearchFieldTitle, nil
case string(BoardSearchFieldPropertyName):
return BoardSearchFieldPropertyName, nil
}
return BoardSearchFieldNone, ErrInvalidBoardSearchField
}