Mono repo -> Master (#22553)
Combines the following repositories into one: https://github.com/mattermost/mattermost-server https://github.com/mattermost/mattermost-webapp https://github.com/mattermost/focalboard https://github.com/mattermost/mattermost-plugin-playbooks
Этот коммит содержится в:
138
server/boards/model/auth.go
Обычный файл
138
server/boards/model/auth.go
Обычный файл
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/boards/services/auth"
|
||||
)
|
||||
|
||||
const (
|
||||
MinimumPasswordLength = 8
|
||||
)
|
||||
|
||||
func NewErrAuthParam(msg string) *ErrAuthParam {
|
||||
return &ErrAuthParam{
|
||||
msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
type ErrAuthParam struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (pe *ErrAuthParam) Error() string {
|
||||
return pe.msg
|
||||
}
|
||||
|
||||
// LoginRequest is a login request
|
||||
// swagger:model
|
||||
type LoginRequest struct {
|
||||
// Type of login, currently must be set to "normal"
|
||||
// required: true
|
||||
Type string `json:"type"`
|
||||
|
||||
// If specified, login using username
|
||||
// required: false
|
||||
Username string `json:"username"`
|
||||
|
||||
// If specified, login using email
|
||||
// required: false
|
||||
Email string `json:"email"`
|
||||
|
||||
// Password
|
||||
// required: true
|
||||
Password string `json:"password"`
|
||||
|
||||
// MFA token
|
||||
// required: false
|
||||
// swagger:ignore
|
||||
MfaToken string `json:"mfa_token"`
|
||||
}
|
||||
|
||||
// LoginResponse is a login response
|
||||
// swagger:model
|
||||
type LoginResponse struct {
|
||||
// Session token
|
||||
// required: true
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func LoginResponseFromJSON(data io.Reader) (*LoginResponse, error) {
|
||||
var resp LoginResponse
|
||||
if err := json.NewDecoder(data).Decode(&resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// RegisterRequest is a user registration request
|
||||
// swagger:model
|
||||
type RegisterRequest struct {
|
||||
// User name
|
||||
// required: true
|
||||
Username string `json:"username"`
|
||||
|
||||
// User's email
|
||||
// required: true
|
||||
Email string `json:"email"`
|
||||
|
||||
// Password
|
||||
// required: true
|
||||
Password string `json:"password"`
|
||||
|
||||
// Registration authorization token
|
||||
// required: true
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func (rd *RegisterRequest) IsValid() error {
|
||||
if strings.TrimSpace(rd.Username) == "" {
|
||||
return NewErrAuthParam("username is required")
|
||||
}
|
||||
if strings.TrimSpace(rd.Email) == "" {
|
||||
return NewErrAuthParam("email is required")
|
||||
}
|
||||
if !auth.IsEmailValid(rd.Email) {
|
||||
return NewErrAuthParam("invalid email format")
|
||||
}
|
||||
if rd.Password == "" {
|
||||
return NewErrAuthParam("password is required")
|
||||
}
|
||||
return isValidPassword(rd.Password)
|
||||
}
|
||||
|
||||
// ChangePasswordRequest is a user password change request
|
||||
// swagger:model
|
||||
type ChangePasswordRequest struct {
|
||||
// Old password
|
||||
// required: true
|
||||
OldPassword string `json:"oldPassword"`
|
||||
|
||||
// New password
|
||||
// required: true
|
||||
NewPassword string `json:"newPassword"`
|
||||
}
|
||||
|
||||
// IsValid validates a password change request.
|
||||
func (rd *ChangePasswordRequest) IsValid() error {
|
||||
if rd.OldPassword == "" {
|
||||
return NewErrAuthParam("old password is required")
|
||||
}
|
||||
if rd.NewPassword == "" {
|
||||
return NewErrAuthParam("new password is required")
|
||||
}
|
||||
return isValidPassword(rd.NewPassword)
|
||||
}
|
||||
|
||||
func isValidPassword(password string) error {
|
||||
if len(password) < MinimumPasswordLength {
|
||||
return NewErrAuthParam(fmt.Sprintf("password must be at least %d characters", MinimumPasswordLength))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
258
server/boards/model/block.go
Обычный файл
258
server/boards/model/block.go
Обычный файл
@@ -0,0 +1,258 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/boards/services/audit"
|
||||
)
|
||||
|
||||
// Block is the basic data unit
|
||||
// swagger:model
|
||||
type Block struct {
|
||||
// The id for this block
|
||||
// required: true
|
||||
ID string `json:"id"`
|
||||
|
||||
// The id for this block's parent block. Empty for root blocks
|
||||
// required: false
|
||||
ParentID string `json:"parentId"`
|
||||
|
||||
// The id for user who created this block
|
||||
// required: true
|
||||
CreatedBy string `json:"createdBy"`
|
||||
|
||||
// The id for user who last modified this block
|
||||
// required: true
|
||||
ModifiedBy string `json:"modifiedBy"`
|
||||
|
||||
// The schema version of this block
|
||||
// required: true
|
||||
Schema int64 `json:"schema"`
|
||||
|
||||
// The block type
|
||||
// required: true
|
||||
Type BlockType `json:"type"`
|
||||
|
||||
// The display title
|
||||
// required: false
|
||||
Title string `json:"title"`
|
||||
|
||||
// The block fields
|
||||
// required: false
|
||||
Fields map[string]interface{} `json:"fields"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// Deprecated. The workspace id that the block belongs to
|
||||
// required: false
|
||||
WorkspaceID string `json:"-"`
|
||||
|
||||
// The board id that the block belongs to
|
||||
// required: true
|
||||
BoardID string `json:"boardId"`
|
||||
|
||||
// Indicates if the card is limited
|
||||
// required: false
|
||||
Limited bool `json:"limited,omitempty"`
|
||||
}
|
||||
|
||||
// BlockPatch is a patch for modify blocks
|
||||
// swagger:model
|
||||
type BlockPatch struct {
|
||||
// The id for this block's parent block. Empty for root blocks
|
||||
// required: false
|
||||
ParentID *string `json:"parentId"`
|
||||
|
||||
// The schema version of this block
|
||||
// required: false
|
||||
Schema *int64 `json:"schema"`
|
||||
|
||||
// The block type
|
||||
// required: false
|
||||
Type *BlockType `json:"type"`
|
||||
|
||||
// The display title
|
||||
// required: false
|
||||
Title *string `json:"title"`
|
||||
|
||||
// The block updated fields
|
||||
// required: false
|
||||
UpdatedFields map[string]interface{} `json:"updatedFields"`
|
||||
|
||||
// The block removed fields
|
||||
// required: false
|
||||
DeletedFields []string `json:"deletedFields"`
|
||||
}
|
||||
|
||||
// BlockPatchBatch is a batch of IDs and patches for modify blocks
|
||||
// swagger:model
|
||||
type BlockPatchBatch struct {
|
||||
// The id's for of the blocks to patch
|
||||
BlockIDs []string `json:"block_ids"`
|
||||
|
||||
// The BlockPatches to be applied
|
||||
BlockPatches []BlockPatch `json:"block_patches"`
|
||||
}
|
||||
|
||||
// BoardModifier is a callback that can modify each board during an import.
|
||||
// A cache of arbitrary data will be passed for each call and any changes
|
||||
// to the cache will be preserved for the next call.
|
||||
// Return true to import the block or false to skip import.
|
||||
type BoardModifier func(board *Board, cache map[string]interface{}) bool
|
||||
|
||||
// BlockModifier is a callback that can modify each block during an import.
|
||||
// A cache of arbitrary data will be passed for each call and any changes
|
||||
// to the cache will be preserved for the next call.
|
||||
// Return true to import the block or false to skip import.
|
||||
type BlockModifier func(block *Block, cache map[string]interface{}) bool
|
||||
|
||||
func BlocksFromJSON(data io.Reader) []*Block {
|
||||
var blocks []*Block
|
||||
_ = json.NewDecoder(data).Decode(&blocks)
|
||||
return blocks
|
||||
}
|
||||
|
||||
// LogClone implements the `mlog.LogCloner` interface to provide a subset of Block fields for logging.
|
||||
func (b *Block) LogClone() interface{} {
|
||||
return struct {
|
||||
ID string
|
||||
ParentID string
|
||||
BoardID string
|
||||
Type BlockType
|
||||
}{
|
||||
ID: b.ID,
|
||||
ParentID: b.ParentID,
|
||||
BoardID: b.BoardID,
|
||||
Type: b.Type,
|
||||
}
|
||||
}
|
||||
|
||||
// Patch returns an update version of the block.
|
||||
func (p *BlockPatch) Patch(block *Block) *Block {
|
||||
if p.ParentID != nil {
|
||||
block.ParentID = *p.ParentID
|
||||
}
|
||||
|
||||
if p.Schema != nil {
|
||||
block.Schema = *p.Schema
|
||||
}
|
||||
|
||||
if p.Type != nil {
|
||||
block.Type = *p.Type
|
||||
}
|
||||
|
||||
if p.Title != nil {
|
||||
block.Title = *p.Title
|
||||
}
|
||||
|
||||
for key, field := range p.UpdatedFields {
|
||||
block.Fields[key] = field
|
||||
}
|
||||
|
||||
for _, key := range p.DeletedFields {
|
||||
delete(block.Fields, key)
|
||||
}
|
||||
|
||||
return block
|
||||
}
|
||||
|
||||
type QueryBlocksOptions struct {
|
||||
BoardID string // if not empty then filter for blocks belonging to specified board
|
||||
ParentID string // if not empty then filter for blocks belonging to specified parent
|
||||
BlockType BlockType // if not empty and not `TypeUnknown` then filter for records of specified block type
|
||||
Page int // page number to select when paginating
|
||||
PerPage int // number of blocks per page (default=-1, meaning unlimited)
|
||||
}
|
||||
|
||||
// QuerySubtreeOptions are query options that can be passed to GetSubTree methods.
|
||||
type QuerySubtreeOptions struct {
|
||||
BeforeUpdateAt int64 // if non-zero then filter for records with update_at less than BeforeUpdateAt
|
||||
AfterUpdateAt int64 // if non-zero then filter for records with update_at greater than AfterUpdateAt
|
||||
Limit uint64 // if non-zero then limit the number of returned records
|
||||
}
|
||||
|
||||
// QueryBlockHistoryOptions are query options that can be passed to GetBlockHistory.
|
||||
type QueryBlockHistoryOptions struct {
|
||||
BeforeUpdateAt int64 // if non-zero then filter for records with update_at less than BeforeUpdateAt
|
||||
AfterUpdateAt int64 // if non-zero then filter for records with update_at greater than AfterUpdateAt
|
||||
Limit uint64 // if non-zero then limit the number of returned records
|
||||
Descending bool // if true then the records are sorted by insert_at in descending order
|
||||
}
|
||||
|
||||
// QueryBoardHistoryOptions are query options that can be passed to GetBoardHistory.
|
||||
type QueryBoardHistoryOptions struct {
|
||||
BeforeUpdateAt int64 // if non-zero then filter for records with update_at less than BeforeUpdateAt
|
||||
AfterUpdateAt int64 // if non-zero then filter for records with update_at greater than AfterUpdateAt
|
||||
Limit uint64 // if non-zero then limit the number of returned records
|
||||
Descending bool // if true then the records are sorted by insert_at in descending order
|
||||
}
|
||||
|
||||
// QueryBlockHistoryOptions are query options that can be passed to GetBlockHistory.
|
||||
type QueryBlockHistoryChildOptions struct {
|
||||
BeforeUpdateAt int64 // if non-zero then filter for records with update_at less than BeforeUpdateAt
|
||||
AfterUpdateAt int64 // if non-zero then filter for records with update_at greater than AfterUpdateAt
|
||||
Page int // page number to select when paginating
|
||||
PerPage int // number of blocks per page (default=-1, meaning unlimited)
|
||||
}
|
||||
|
||||
func StampModificationMetadata(userID string, blocks []*Block, auditRec *audit.Record) {
|
||||
if userID == SingleUser {
|
||||
userID = ""
|
||||
}
|
||||
|
||||
now := GetMillis()
|
||||
for i := range blocks {
|
||||
blocks[i].ModifiedBy = userID
|
||||
blocks[i].UpdateAt = now
|
||||
|
||||
if auditRec != nil {
|
||||
auditRec.AddMeta("block_"+strconv.FormatInt(int64(i), 10), blocks[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Block) ShouldBeLimited(cardLimitTimestamp int64) bool {
|
||||
return b.Type == TypeCard &&
|
||||
b.UpdateAt < cardLimitTimestamp
|
||||
}
|
||||
|
||||
// Returns a limited version of the block that doesn't contain the
|
||||
// contents of the block, only its IDs and type.
|
||||
func (b *Block) GetLimited() *Block {
|
||||
newBlock := &Block{
|
||||
Title: b.Title,
|
||||
ID: b.ID,
|
||||
ParentID: b.ParentID,
|
||||
BoardID: b.BoardID,
|
||||
Schema: b.Schema,
|
||||
Type: b.Type,
|
||||
CreateAt: b.CreateAt,
|
||||
UpdateAt: b.UpdateAt,
|
||||
DeleteAt: b.DeleteAt,
|
||||
WorkspaceID: b.WorkspaceID,
|
||||
Limited: true,
|
||||
}
|
||||
|
||||
if iconField, ok := b.Fields["icon"]; ok {
|
||||
newBlock.Fields = map[string]interface{}{
|
||||
"icon": iconField,
|
||||
}
|
||||
}
|
||||
|
||||
return newBlock
|
||||
}
|
||||
312
server/boards/model/block_test.go
Обычный файл
312
server/boards/model/block_test.go
Обычный файл
@@ -0,0 +1,312 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/boards/utils"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGenerateBlockIDs(t *testing.T) {
|
||||
t.Run("Should generate a new ID for a single block with no references", func(t *testing.T) {
|
||||
blockID := utils.NewID(utils.IDTypeBlock)
|
||||
blocks := []*Block{{ID: blockID}}
|
||||
|
||||
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
||||
|
||||
require.NotEqual(t, blockID, blocks[0].ID)
|
||||
require.Zero(t, blocks[0].BoardID)
|
||||
require.Zero(t, blocks[0].ParentID)
|
||||
})
|
||||
|
||||
t.Run("Should generate a new ID for a single block with references", func(t *testing.T) {
|
||||
blockID := utils.NewID(utils.IDTypeBlock)
|
||||
boardID := utils.NewID(utils.IDTypeBlock)
|
||||
parentID := utils.NewID(utils.IDTypeBlock)
|
||||
blocks := []*Block{{ID: blockID, BoardID: boardID, ParentID: parentID}}
|
||||
|
||||
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
||||
|
||||
require.NotEqual(t, blockID, blocks[0].ID)
|
||||
require.Equal(t, boardID, blocks[0].BoardID)
|
||||
require.Equal(t, parentID, blocks[0].ParentID)
|
||||
})
|
||||
|
||||
t.Run("Should generate IDs and link multiple blocks with existing references", func(t *testing.T) {
|
||||
blockID1 := utils.NewID(utils.IDTypeBlock)
|
||||
boardID1 := utils.NewID(utils.IDTypeBlock)
|
||||
parentID1 := utils.NewID(utils.IDTypeBlock)
|
||||
block1 := &Block{ID: blockID1, BoardID: boardID1, ParentID: parentID1}
|
||||
|
||||
blockID2 := utils.NewID(utils.IDTypeBlock)
|
||||
boardID2 := blockID1
|
||||
parentID2 := utils.NewID(utils.IDTypeBlock)
|
||||
block2 := &Block{ID: blockID2, BoardID: boardID2, ParentID: parentID2}
|
||||
|
||||
blocks := []*Block{block1, block2}
|
||||
|
||||
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
||||
|
||||
require.NotEqual(t, blockID1, blocks[0].ID)
|
||||
require.Equal(t, boardID1, blocks[0].BoardID)
|
||||
require.Equal(t, parentID1, blocks[0].ParentID)
|
||||
|
||||
require.NotEqual(t, blockID2, blocks[1].ID)
|
||||
require.NotEqual(t, boardID2, blocks[1].BoardID)
|
||||
require.Equal(t, parentID2, blocks[1].ParentID)
|
||||
|
||||
// blockID1 was referenced, so it should still be after the ID
|
||||
// changes
|
||||
require.Equal(t, blocks[0].ID, blocks[1].BoardID)
|
||||
})
|
||||
|
||||
t.Run("Should generate new IDs but not modify nonexisting references", func(t *testing.T) {
|
||||
blockID1 := utils.NewID(utils.IDTypeBlock)
|
||||
boardID1 := ""
|
||||
parentID1 := utils.NewID(utils.IDTypeBlock)
|
||||
block1 := &Block{ID: blockID1, BoardID: boardID1, ParentID: parentID1}
|
||||
|
||||
blockID2 := utils.NewID(utils.IDTypeBlock)
|
||||
boardID2 := utils.NewID(utils.IDTypeBlock)
|
||||
parentID2 := ""
|
||||
block2 := &Block{ID: blockID2, BoardID: boardID2, ParentID: parentID2}
|
||||
|
||||
blocks := []*Block{block1, block2}
|
||||
|
||||
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
||||
|
||||
// only the IDs should have changed
|
||||
require.NotEqual(t, blockID1, blocks[0].ID)
|
||||
require.Zero(t, blocks[0].BoardID)
|
||||
require.Equal(t, parentID1, blocks[0].ParentID)
|
||||
|
||||
require.NotEqual(t, blockID2, blocks[1].ID)
|
||||
require.Equal(t, boardID2, blocks[1].BoardID)
|
||||
require.Zero(t, blocks[1].ParentID)
|
||||
})
|
||||
|
||||
t.Run("Should modify correctly multiple blocks with existing and nonexisting references", func(t *testing.T) {
|
||||
blockID1 := utils.NewID(utils.IDTypeBlock)
|
||||
boardID1 := utils.NewID(utils.IDTypeBlock)
|
||||
parentID1 := utils.NewID(utils.IDTypeBlock)
|
||||
block1 := &Block{ID: blockID1, BoardID: boardID1, ParentID: parentID1}
|
||||
|
||||
// linked to 1
|
||||
blockID2 := utils.NewID(utils.IDTypeBlock)
|
||||
boardID2 := blockID1
|
||||
parentID2 := utils.NewID(utils.IDTypeBlock)
|
||||
block2 := &Block{ID: blockID2, BoardID: boardID2, ParentID: parentID2}
|
||||
|
||||
// linked to 2
|
||||
blockID3 := utils.NewID(utils.IDTypeBlock)
|
||||
boardID3 := blockID2
|
||||
parentID3 := utils.NewID(utils.IDTypeBlock)
|
||||
block3 := &Block{ID: blockID3, BoardID: boardID3, ParentID: parentID3}
|
||||
|
||||
// linked to 1
|
||||
blockID4 := utils.NewID(utils.IDTypeBlock)
|
||||
boardID4 := blockID1
|
||||
parentID4 := utils.NewID(utils.IDTypeBlock)
|
||||
block4 := &Block{ID: blockID4, BoardID: boardID4, ParentID: parentID4}
|
||||
|
||||
// blocks are shuffled
|
||||
blocks := []*Block{block4, block2, block1, block3}
|
||||
|
||||
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
||||
|
||||
// block 1
|
||||
require.NotEqual(t, blockID1, blocks[2].ID)
|
||||
require.Equal(t, boardID1, blocks[2].BoardID)
|
||||
require.Equal(t, parentID1, blocks[2].ParentID)
|
||||
|
||||
// block 2
|
||||
require.NotEqual(t, blockID2, blocks[1].ID)
|
||||
require.NotEqual(t, boardID2, blocks[1].BoardID)
|
||||
require.Equal(t, blocks[2].ID, blocks[1].BoardID) // link to 1
|
||||
require.Equal(t, parentID2, blocks[1].ParentID)
|
||||
|
||||
// block 3
|
||||
require.NotEqual(t, blockID3, blocks[3].ID)
|
||||
require.NotEqual(t, boardID3, blocks[3].BoardID)
|
||||
require.Equal(t, blocks[1].ID, blocks[3].BoardID) // link to 2
|
||||
require.Equal(t, parentID3, blocks[3].ParentID)
|
||||
|
||||
// block 4
|
||||
require.NotEqual(t, blockID4, blocks[0].ID)
|
||||
require.NotEqual(t, boardID4, blocks[0].BoardID)
|
||||
require.Equal(t, blocks[2].ID, blocks[0].BoardID) // link to 1
|
||||
require.Equal(t, parentID4, blocks[0].ParentID)
|
||||
})
|
||||
|
||||
t.Run("Should update content order", func(t *testing.T) {
|
||||
blockID1 := utils.NewID(utils.IDTypeBlock)
|
||||
boardID1 := utils.NewID(utils.IDTypeBlock)
|
||||
parentID1 := utils.NewID(utils.IDTypeBlock)
|
||||
block1 := &Block{
|
||||
ID: blockID1,
|
||||
BoardID: boardID1,
|
||||
ParentID: parentID1,
|
||||
}
|
||||
|
||||
blockID2 := utils.NewID(utils.IDTypeBlock)
|
||||
boardID2 := utils.NewID(utils.IDTypeBlock)
|
||||
parentID2 := utils.NewID(utils.IDTypeBlock)
|
||||
block2 := &Block{
|
||||
ID: blockID2,
|
||||
BoardID: boardID2,
|
||||
ParentID: parentID2,
|
||||
Fields: map[string]interface{}{
|
||||
"contentOrder": []interface{}{
|
||||
blockID1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
blocks := []*Block{block1, block2}
|
||||
|
||||
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
||||
|
||||
require.NotEqual(t, blockID1, blocks[0].ID)
|
||||
require.Equal(t, boardID1, blocks[0].BoardID)
|
||||
require.Equal(t, parentID1, blocks[0].ParentID)
|
||||
|
||||
require.NotEqual(t, blockID2, blocks[1].ID)
|
||||
require.Equal(t, boardID2, blocks[1].BoardID)
|
||||
require.Equal(t, parentID2, blocks[1].ParentID)
|
||||
|
||||
// since block 1 was referenced in block 2,
|
||||
// the ID should have been changed in content order
|
||||
block2ContentOrder, ok := block2.Fields["contentOrder"].([]interface{})
|
||||
require.True(t, ok)
|
||||
require.NotEqual(t, blockID1, block2ContentOrder[0].(string))
|
||||
require.Equal(t, blocks[0].ID, block2ContentOrder[0].(string))
|
||||
})
|
||||
|
||||
t.Run("Should update content order when it contain slices", func(t *testing.T) {
|
||||
blockID1 := utils.NewID(utils.IDTypeBlock)
|
||||
boardID1 := utils.NewID(utils.IDTypeBlock)
|
||||
parentID1 := utils.NewID(utils.IDTypeBlock)
|
||||
block1 := &Block{
|
||||
ID: blockID1,
|
||||
BoardID: boardID1,
|
||||
ParentID: parentID1,
|
||||
}
|
||||
|
||||
blockID2 := utils.NewID(utils.IDTypeBlock)
|
||||
block2 := &Block{
|
||||
ID: blockID2,
|
||||
BoardID: boardID1,
|
||||
ParentID: parentID1,
|
||||
}
|
||||
|
||||
blockID3 := utils.NewID(utils.IDTypeBlock)
|
||||
block3 := &Block{
|
||||
ID: blockID3,
|
||||
BoardID: boardID1,
|
||||
ParentID: parentID1,
|
||||
}
|
||||
|
||||
blockID4 := utils.NewID(utils.IDTypeBlock)
|
||||
boardID2 := utils.NewID(utils.IDTypeBlock)
|
||||
parentID2 := utils.NewID(utils.IDTypeBlock)
|
||||
|
||||
block4 := &Block{
|
||||
ID: blockID4,
|
||||
BoardID: boardID2,
|
||||
ParentID: parentID2,
|
||||
Fields: map[string]interface{}{
|
||||
"contentOrder": []interface{}{
|
||||
blockID1,
|
||||
[]interface{}{
|
||||
blockID2,
|
||||
blockID3,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
blocks := []*Block{block1, block2, block3, block4}
|
||||
|
||||
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
||||
|
||||
require.NotEqual(t, blockID1, blocks[0].ID)
|
||||
require.Equal(t, boardID1, blocks[0].BoardID)
|
||||
require.Equal(t, parentID1, blocks[0].ParentID)
|
||||
|
||||
require.NotEqual(t, blockID4, blocks[3].ID)
|
||||
require.Equal(t, boardID2, blocks[3].BoardID)
|
||||
require.Equal(t, parentID2, blocks[3].ParentID)
|
||||
|
||||
// since block 1 was referenced in block 2,
|
||||
// the ID should have been changed in content order
|
||||
block4ContentOrder, ok := block4.Fields["contentOrder"].([]interface{})
|
||||
require.True(t, ok)
|
||||
require.NotEqual(t, blockID1, block4ContentOrder[0].(string))
|
||||
require.NotEqual(t, blockID2, block4ContentOrder[1].([]interface{})[0])
|
||||
require.NotEqual(t, blockID3, block4ContentOrder[1].([]interface{})[1])
|
||||
require.Equal(t, blocks[0].ID, block4ContentOrder[0].(string))
|
||||
require.Equal(t, blocks[1].ID, block4ContentOrder[1].([]interface{})[0])
|
||||
require.Equal(t, blocks[2].ID, block4ContentOrder[1].([]interface{})[1])
|
||||
})
|
||||
|
||||
t.Run("Should update Id of default template view", func(t *testing.T) {
|
||||
blockID1 := utils.NewID(utils.IDTypeBlock)
|
||||
boardID1 := utils.NewID(utils.IDTypeBlock)
|
||||
parentID1 := utils.NewID(utils.IDTypeBlock)
|
||||
block1 := &Block{
|
||||
ID: blockID1,
|
||||
BoardID: boardID1,
|
||||
ParentID: parentID1,
|
||||
}
|
||||
|
||||
blockID2 := utils.NewID(utils.IDTypeBlock)
|
||||
boardID2 := utils.NewID(utils.IDTypeBlock)
|
||||
parentID2 := utils.NewID(utils.IDTypeBlock)
|
||||
block2 := &Block{
|
||||
ID: blockID2,
|
||||
BoardID: boardID2,
|
||||
ParentID: parentID2,
|
||||
Fields: map[string]interface{}{
|
||||
"defaultTemplateId": blockID1,
|
||||
},
|
||||
}
|
||||
|
||||
blocks := []*Block{block1, block2}
|
||||
|
||||
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
||||
|
||||
require.NotEqual(t, blockID1, blocks[0].ID)
|
||||
require.Equal(t, boardID1, blocks[0].BoardID)
|
||||
require.Equal(t, parentID1, blocks[0].ParentID)
|
||||
|
||||
require.NotEqual(t, blockID2, blocks[1].ID)
|
||||
require.Equal(t, boardID2, blocks[1].BoardID)
|
||||
require.Equal(t, parentID2, blocks[1].ParentID)
|
||||
|
||||
block2DefaultTemplateID, ok := block2.Fields["defaultTemplateId"].(string)
|
||||
require.True(t, ok)
|
||||
require.NotEqual(t, blockID1, block2DefaultTemplateID)
|
||||
require.Equal(t, blocks[0].ID, block2DefaultTemplateID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStampModificationMetadata(t *testing.T) {
|
||||
t.Run("base case", func(t *testing.T) {
|
||||
block := &Block{}
|
||||
blocks := []*Block{block}
|
||||
assert.Empty(t, block.ModifiedBy)
|
||||
assert.Empty(t, block.UpdateAt)
|
||||
|
||||
StampModificationMetadata("user_id_1", blocks, nil)
|
||||
assert.Equal(t, "user_id_1", blocks[0].ModifiedBy)
|
||||
assert.NotEmpty(t, blocks[0].UpdateAt)
|
||||
})
|
||||
}
|
||||
159
server/boards/model/blockid.go
Обычный файл
159
server/boards/model/blockid.go
Обычный файл
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/boards/utils"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog"
|
||||
)
|
||||
|
||||
// GenerateBlockIDs generates new IDs for all the blocks of the list,
|
||||
// keeping consistent any references that other blocks would made to
|
||||
// the original IDs, so a tree of blocks can get new IDs and maintain
|
||||
// its shape.
|
||||
func GenerateBlockIDs(blocks []*Block, logger mlog.LoggerIFace) []*Block {
|
||||
blockIDs := map[string]BlockType{}
|
||||
referenceIDs := map[string]bool{}
|
||||
for _, block := range blocks {
|
||||
if _, ok := blockIDs[block.ID]; !ok {
|
||||
blockIDs[block.ID] = block.Type
|
||||
}
|
||||
|
||||
if _, ok := referenceIDs[block.BoardID]; !ok {
|
||||
referenceIDs[block.BoardID] = true
|
||||
}
|
||||
if _, ok := referenceIDs[block.ParentID]; !ok {
|
||||
referenceIDs[block.ParentID] = true
|
||||
}
|
||||
|
||||
if _, ok := block.Fields["contentOrder"]; ok {
|
||||
contentOrder, typeOk := block.Fields["contentOrder"].([]interface{})
|
||||
if !typeOk {
|
||||
logger.Warn(
|
||||
"type assertion failed for content order when saving reference block IDs",
|
||||
mlog.String("blockID", block.ID),
|
||||
mlog.String("actionType", fmt.Sprintf("%T", block.Fields["contentOrder"])),
|
||||
mlog.String("expectedType", "[]interface{}"),
|
||||
mlog.String("contentOrder", fmt.Sprintf("%v", block.Fields["contentOrder"])),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, blockID := range contentOrder {
|
||||
switch v := blockID.(type) {
|
||||
case []interface{}:
|
||||
for _, columnBlockID := range v {
|
||||
referenceIDs[columnBlockID.(string)] = true
|
||||
}
|
||||
case string:
|
||||
referenceIDs[v] = true
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := block.Fields["defaultTemplateId"]; ok {
|
||||
defaultTemplateID, typeOk := block.Fields["defaultTemplateId"].(string)
|
||||
if !typeOk {
|
||||
logger.Warn(
|
||||
"type assertion failed for default template ID when saving reference block IDs",
|
||||
mlog.String("blockID", block.ID),
|
||||
mlog.String("actionType", fmt.Sprintf("%T", block.Fields["defaultTemplateId"])),
|
||||
mlog.String("expectedType", "string"),
|
||||
mlog.String("defaultTemplateId", fmt.Sprintf("%v", block.Fields["defaultTemplateId"])),
|
||||
)
|
||||
continue
|
||||
}
|
||||
referenceIDs[defaultTemplateID] = true
|
||||
}
|
||||
}
|
||||
|
||||
newIDs := map[string]string{}
|
||||
for id, blockType := range blockIDs {
|
||||
for referenceID := range referenceIDs {
|
||||
if id == referenceID {
|
||||
newIDs[id] = utils.NewID(BlockType2IDType(blockType))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getExistingOrOldID := func(id string) string {
|
||||
if existingID, ok := newIDs[id]; ok {
|
||||
return existingID
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
getExistingOrNewID := func(id string) string {
|
||||
if existingID, ok := newIDs[id]; ok {
|
||||
return existingID
|
||||
}
|
||||
return utils.NewID(BlockType2IDType(blockIDs[id]))
|
||||
}
|
||||
|
||||
newBlocks := make([]*Block, len(blocks))
|
||||
for i, block := range blocks {
|
||||
block.ID = getExistingOrNewID(block.ID)
|
||||
block.BoardID = getExistingOrOldID(block.BoardID)
|
||||
block.ParentID = getExistingOrOldID(block.ParentID)
|
||||
|
||||
blockMod := block
|
||||
if _, ok := blockMod.Fields["contentOrder"]; ok {
|
||||
fixFieldIDs(blockMod, "contentOrder", getExistingOrOldID, logger)
|
||||
}
|
||||
|
||||
if _, ok := blockMod.Fields["cardOrder"]; ok {
|
||||
fixFieldIDs(blockMod, "cardOrder", getExistingOrOldID, logger)
|
||||
}
|
||||
|
||||
if _, ok := blockMod.Fields["defaultTemplateId"]; ok {
|
||||
defaultTemplateID, typeOk := blockMod.Fields["defaultTemplateId"].(string)
|
||||
if !typeOk {
|
||||
logger.Warn(
|
||||
"type assertion failed for default template ID when saving reference block IDs",
|
||||
mlog.String("blockID", blockMod.ID),
|
||||
mlog.String("actionType", fmt.Sprintf("%T", blockMod.Fields["defaultTemplateId"])),
|
||||
mlog.String("expectedType", "string"),
|
||||
mlog.String("defaultTemplateId", fmt.Sprintf("%v", blockMod.Fields["defaultTemplateId"])),
|
||||
)
|
||||
} else {
|
||||
blockMod.Fields["defaultTemplateId"] = getExistingOrOldID(defaultTemplateID)
|
||||
}
|
||||
}
|
||||
|
||||
newBlocks[i] = blockMod
|
||||
}
|
||||
|
||||
return newBlocks
|
||||
}
|
||||
|
||||
func fixFieldIDs(block *Block, fieldName string, getExistingOrOldID func(string) string, logger mlog.LoggerIFace) {
|
||||
field, typeOk := block.Fields[fieldName].([]interface{})
|
||||
if !typeOk {
|
||||
logger.Warn(
|
||||
"type assertion failed for JSON field when setting new block IDs",
|
||||
mlog.String("blockID", block.ID),
|
||||
mlog.String("fieldName", fieldName),
|
||||
mlog.String("actionType", fmt.Sprintf("%T", block.Fields[fieldName])),
|
||||
mlog.String("expectedType", "[]interface{}"),
|
||||
mlog.String("value", fmt.Sprintf("%v", block.Fields[fieldName])),
|
||||
)
|
||||
} else {
|
||||
for j := range field {
|
||||
switch v := field[j].(type) {
|
||||
case string:
|
||||
field[j] = getExistingOrOldID(v)
|
||||
case []interface{}:
|
||||
subOrder := field[j].([]interface{})
|
||||
for k := range v {
|
||||
subOrder[k] = getExistingOrOldID(v[k].(string))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
88
server/boards/model/blocktype.go
Обычный файл
88
server/boards/model/blocktype.go
Обычный файл
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/boards/utils"
|
||||
)
|
||||
|
||||
// BlockType represents a block type.
|
||||
type BlockType string
|
||||
|
||||
const (
|
||||
TypeUnknown = "unknown"
|
||||
TypeBoard = "board"
|
||||
TypeCard = "card"
|
||||
TypeView = "view"
|
||||
TypeText = "text"
|
||||
TypeCheckbox = "checkbox"
|
||||
TypeComment = "comment"
|
||||
TypeImage = "image"
|
||||
TypeAttachment = "attachment"
|
||||
TypeDivider = "divider"
|
||||
)
|
||||
|
||||
func (bt BlockType) String() string {
|
||||
return string(bt)
|
||||
}
|
||||
|
||||
// BlockTypeFromString returns an appropriate BlockType for the specified string.
|
||||
func BlockTypeFromString(s string) (BlockType, error) {
|
||||
switch strings.ToLower(s) {
|
||||
case "board":
|
||||
return TypeBoard, nil
|
||||
case "card":
|
||||
return TypeCard, nil
|
||||
case "view":
|
||||
return TypeView, nil
|
||||
case "text":
|
||||
return TypeText, nil
|
||||
case "checkbox":
|
||||
return TypeCheckbox, nil
|
||||
case "comment":
|
||||
return TypeComment, nil
|
||||
case "image":
|
||||
return TypeImage, nil
|
||||
case "attachment":
|
||||
return TypeAttachment, nil
|
||||
case "divider":
|
||||
return TypeDivider, nil
|
||||
}
|
||||
return TypeUnknown, ErrInvalidBlockType{s}
|
||||
}
|
||||
|
||||
// BlockType2IDType returns an appropriate IDType for the specified BlockType.
|
||||
func BlockType2IDType(blockType BlockType) utils.IDType {
|
||||
switch blockType {
|
||||
case TypeBoard:
|
||||
return utils.IDTypeBoard
|
||||
case TypeCard:
|
||||
return utils.IDTypeCard
|
||||
case TypeView:
|
||||
return utils.IDTypeView
|
||||
case TypeText, TypeCheckbox, TypeComment, TypeDivider:
|
||||
return utils.IDTypeBlock
|
||||
case TypeImage, TypeAttachment:
|
||||
return utils.IDTypeAttachment
|
||||
}
|
||||
return utils.IDTypeNone
|
||||
}
|
||||
|
||||
// ErrInvalidBlockType is returned wherever an invalid block type was provided.
|
||||
type ErrInvalidBlockType struct {
|
||||
Type string
|
||||
}
|
||||
|
||||
func (e ErrInvalidBlockType) Error() string {
|
||||
return e.Type + " is an invalid block type."
|
||||
}
|
||||
|
||||
// IsErrInvalidBlockType returns true if `err` is a IsErrInvalidBlockType or wraps one.
|
||||
func IsErrInvalidBlockType(err error) bool {
|
||||
var eibt *ErrInvalidBlockType
|
||||
return errors.As(err, &eibt)
|
||||
}
|
||||
429
server/boards/model/board.go
Обычный файл
429
server/boards/model/board.go
Обычный файл
@@ -0,0 +1,429 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
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
|
||||
}
|
||||
65
server/boards/model/board_insights.go
Обычный файл
65
server/boards/model/board_insights.go
Обычный файл
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
mm_model "github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
// BoardInsightsList is a response type with pagination support.
|
||||
type BoardInsightsList struct {
|
||||
mm_model.InsightsListData
|
||||
Items []*BoardInsight `json:"items"`
|
||||
}
|
||||
|
||||
// BoardInsight gives insight into activities in a Board
|
||||
// swagger:model
|
||||
type BoardInsight struct {
|
||||
// ID of the board
|
||||
// required: true
|
||||
BoardID string `json:"boardID"`
|
||||
|
||||
// icon of the board
|
||||
// required: false
|
||||
Icon string `json:"icon"`
|
||||
|
||||
// Title of the board
|
||||
// required: false
|
||||
Title string `json:"title"`
|
||||
|
||||
// Metric of how active the board is
|
||||
// required: true
|
||||
ActivityCount string `json:"activityCount"`
|
||||
|
||||
// IDs of users active on the board
|
||||
// required: true
|
||||
ActiveUsers mm_model.StringArray `json:"activeUsers"`
|
||||
|
||||
// ID of user who created the board
|
||||
// required: true
|
||||
CreatedBy string `json:"createdBy"`
|
||||
}
|
||||
|
||||
func BoardInsightsFromJSON(data io.Reader) []BoardInsight {
|
||||
var boardInsights []BoardInsight
|
||||
_ = json.NewDecoder(data).Decode(&boardInsights)
|
||||
return boardInsights
|
||||
}
|
||||
|
||||
// GetTopBoardInsightsListWithPagination adds a rank to each item in the given list of BoardInsight and checks if there is
|
||||
// another page that can be fetched based on the given limit and offset. The given list of BoardInsight is assumed to be
|
||||
// sorted by ActivityCount(score). Returns a BoardInsightsList.
|
||||
func GetTopBoardInsightsListWithPagination(boards []*BoardInsight, limit int) *BoardInsightsList {
|
||||
// Add pagination support
|
||||
var hasNext bool
|
||||
if limit != 0 && len(boards) == limit+1 {
|
||||
hasNext = true
|
||||
boards = boards[:len(boards)-1]
|
||||
}
|
||||
|
||||
return &BoardInsightsList{InsightsListData: mm_model.InsightsListData{HasNext: hasNext}, Items: boards}
|
||||
}
|
||||
15
server/boards/model/board_statistics.go
Обычный файл
15
server/boards/model/board_statistics.go
Обычный файл
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
package model
|
||||
|
||||
// BoardsStatistics is the representation of the statistics for the Boards server
|
||||
// swagger:model
|
||||
type BoardsStatistics struct {
|
||||
// The maximum number of cards on the server
|
||||
// required: true
|
||||
Boards int `json:"board_count"`
|
||||
|
||||
// The maximum number of cards on the server
|
||||
// required: true
|
||||
Cards int `json:"card_count"`
|
||||
}
|
||||
175
server/boards/model/boards_and_blocks.go
Обычный файл
175
server/boards/model/boards_and_blocks.go
Обычный файл
@@ -0,0 +1,175 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/boards/utils"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog"
|
||||
)
|
||||
|
||||
var ErrNoBoardsInBoardsAndBlocks = errors.New("at least one board is required")
|
||||
var ErrNoBlocksInBoardsAndBlocks = errors.New("at least one block is required")
|
||||
var ErrNoTeamInBoardsAndBlocks = errors.New("team ID cannot be empty")
|
||||
var ErrBoardIDsAndPatchesMissmatchInBoardsAndBlocks = errors.New("board ids and patches need to match")
|
||||
var ErrBlockIDsAndPatchesMissmatchInBoardsAndBlocks = errors.New("block ids and patches need to match")
|
||||
|
||||
type BlockDoesntBelongToAnyBoardErr struct {
|
||||
blockID string
|
||||
}
|
||||
|
||||
func (e BlockDoesntBelongToAnyBoardErr) Error() string {
|
||||
return fmt.Sprintf("block %s doesn't belong to any board", e.blockID)
|
||||
}
|
||||
|
||||
// BoardsAndBlocks is used to operate over boards and blocks at the
|
||||
// same time
|
||||
// swagger:model
|
||||
type BoardsAndBlocks struct {
|
||||
// The boards
|
||||
// required: false
|
||||
Boards []*Board `json:"boards"`
|
||||
|
||||
// The blocks
|
||||
// required: false
|
||||
Blocks []*Block `json:"blocks"`
|
||||
}
|
||||
|
||||
func (bab *BoardsAndBlocks) IsValid() error {
|
||||
if len(bab.Boards) == 0 {
|
||||
return ErrNoBoardsInBoardsAndBlocks
|
||||
}
|
||||
|
||||
if len(bab.Blocks) == 0 {
|
||||
return ErrNoBlocksInBoardsAndBlocks
|
||||
}
|
||||
|
||||
boardsMap := map[string]bool{}
|
||||
for _, board := range bab.Boards {
|
||||
boardsMap[board.ID] = true
|
||||
}
|
||||
|
||||
for _, block := range bab.Blocks {
|
||||
if _, ok := boardsMap[block.BoardID]; !ok {
|
||||
return BlockDoesntBelongToAnyBoardErr{block.ID}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteBoardsAndBlocks is used to list the boards and blocks to
|
||||
// delete on a request
|
||||
// swagger:model
|
||||
type DeleteBoardsAndBlocks struct {
|
||||
// The boards
|
||||
// required: true
|
||||
Boards []string `json:"boards"`
|
||||
|
||||
// The blocks
|
||||
// required: true
|
||||
Blocks []string `json:"blocks"`
|
||||
}
|
||||
|
||||
func NewDeleteBoardsAndBlocksFromBabs(babs *BoardsAndBlocks) *DeleteBoardsAndBlocks {
|
||||
boardIDs := make([]string, 0, len(babs.Boards))
|
||||
blockIDs := make([]string, 0, len(babs.Boards))
|
||||
|
||||
for _, board := range babs.Boards {
|
||||
boardIDs = append(boardIDs, board.ID)
|
||||
}
|
||||
for _, block := range babs.Blocks {
|
||||
blockIDs = append(blockIDs, block.ID)
|
||||
}
|
||||
return &DeleteBoardsAndBlocks{
|
||||
Boards: boardIDs,
|
||||
Blocks: blockIDs,
|
||||
}
|
||||
}
|
||||
|
||||
func (dbab *DeleteBoardsAndBlocks) IsValid() error {
|
||||
if len(dbab.Boards) == 0 {
|
||||
return ErrNoBoardsInBoardsAndBlocks
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PatchBoardsAndBlocks is used to patch multiple boards and blocks on
|
||||
// a single request
|
||||
// swagger:model
|
||||
type PatchBoardsAndBlocks struct {
|
||||
// The board IDs to patch
|
||||
// required: true
|
||||
BoardIDs []string `json:"boardIDs"`
|
||||
|
||||
// The board patches
|
||||
// required: true
|
||||
BoardPatches []*BoardPatch `json:"boardPatches"`
|
||||
|
||||
// The block IDs to patch
|
||||
// required: true
|
||||
BlockIDs []string `json:"blockIDs"`
|
||||
|
||||
// The block patches
|
||||
// required: true
|
||||
BlockPatches []*BlockPatch `json:"blockPatches"`
|
||||
}
|
||||
|
||||
func (dbab *PatchBoardsAndBlocks) IsValid() error {
|
||||
if len(dbab.BoardIDs) == 0 {
|
||||
return ErrNoBoardsInBoardsAndBlocks
|
||||
}
|
||||
|
||||
if len(dbab.BoardIDs) != len(dbab.BoardPatches) {
|
||||
return ErrBoardIDsAndPatchesMissmatchInBoardsAndBlocks
|
||||
}
|
||||
|
||||
if len(dbab.BlockIDs) != len(dbab.BlockPatches) {
|
||||
return ErrBlockIDsAndPatchesMissmatchInBoardsAndBlocks
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenerateBoardsAndBlocksIDs(bab *BoardsAndBlocks, logger mlog.LoggerIFace) (*BoardsAndBlocks, error) {
|
||||
if err := bab.IsValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blocksByBoard := map[string][]*Block{}
|
||||
for _, block := range bab.Blocks {
|
||||
blocksByBoard[block.BoardID] = append(blocksByBoard[block.BoardID], block)
|
||||
}
|
||||
|
||||
boards := []*Board{}
|
||||
blocks := []*Block{}
|
||||
for _, board := range bab.Boards {
|
||||
newID := utils.NewID(utils.IDTypeBoard)
|
||||
for _, block := range blocksByBoard[board.ID] {
|
||||
block.BoardID = newID
|
||||
blocks = append(blocks, block)
|
||||
}
|
||||
|
||||
board.ID = newID
|
||||
boards = append(boards, board)
|
||||
}
|
||||
|
||||
newBab := &BoardsAndBlocks{
|
||||
Boards: boards,
|
||||
Blocks: GenerateBlockIDs(blocks, logger),
|
||||
}
|
||||
|
||||
return newBab, nil
|
||||
}
|
||||
|
||||
func BoardsAndBlocksFromJSON(data io.Reader) *BoardsAndBlocks {
|
||||
var bab *BoardsAndBlocks
|
||||
_ = json.NewDecoder(data).Decode(&bab)
|
||||
return bab
|
||||
}
|
||||
254
server/boards/model/boards_and_blocks_test.go
Обычный файл
254
server/boards/model/boards_and_blocks_test.go
Обычный файл
@@ -0,0 +1,254 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog"
|
||||
)
|
||||
|
||||
func TestIsValidBoardsAndBlocks(t *testing.T) {
|
||||
t.Run("no boards", func(t *testing.T) {
|
||||
bab := &BoardsAndBlocks{
|
||||
Blocks: []*Block{
|
||||
{ID: "block-id-1", BoardID: "board-id-1", Type: TypeCard},
|
||||
{ID: "block-id-2", BoardID: "board-id-2", Type: TypeCard},
|
||||
},
|
||||
}
|
||||
|
||||
require.ErrorIs(t, bab.IsValid(), ErrNoBoardsInBoardsAndBlocks)
|
||||
})
|
||||
|
||||
t.Run("no blocks", func(t *testing.T) {
|
||||
bab := &BoardsAndBlocks{
|
||||
Boards: []*Board{
|
||||
{ID: "board-id-1", Type: BoardTypeOpen},
|
||||
{ID: "board-id-2", Type: BoardTypePrivate},
|
||||
},
|
||||
}
|
||||
|
||||
require.ErrorIs(t, bab.IsValid(), ErrNoBlocksInBoardsAndBlocks)
|
||||
})
|
||||
|
||||
t.Run("block that doesn't belong to the boards", func(t *testing.T) {
|
||||
bab := &BoardsAndBlocks{
|
||||
Boards: []*Board{
|
||||
{ID: "board-id-1", Type: BoardTypeOpen},
|
||||
{ID: "board-id-2", Type: BoardTypePrivate},
|
||||
},
|
||||
Blocks: []*Block{
|
||||
{ID: "block-id-1", BoardID: "board-id-1", Type: TypeCard},
|
||||
{ID: "block-id-3", BoardID: "board-id-3", Type: TypeCard},
|
||||
{ID: "block-id-2", BoardID: "board-id-2", Type: TypeCard},
|
||||
},
|
||||
}
|
||||
|
||||
require.ErrorIs(t, bab.IsValid(), BlockDoesntBelongToAnyBoardErr{"block-id-3"})
|
||||
})
|
||||
|
||||
t.Run("valid boards and blocks", func(t *testing.T) {
|
||||
bab := &BoardsAndBlocks{
|
||||
Boards: []*Board{
|
||||
{ID: "board-id-1", Type: BoardTypeOpen},
|
||||
{ID: "board-id-2", Type: BoardTypePrivate},
|
||||
},
|
||||
Blocks: []*Block{
|
||||
{ID: "block-id-1", BoardID: "board-id-1", Type: TypeCard},
|
||||
{ID: "block-id-3", BoardID: "board-id-2", Type: TypeCard},
|
||||
{ID: "block-id-2", BoardID: "board-id-2", Type: TypeCard},
|
||||
},
|
||||
}
|
||||
|
||||
require.NoError(t, bab.IsValid())
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenerateBoardsAndBlocksIDs(t *testing.T) {
|
||||
logger, err := mlog.NewLogger()
|
||||
require.NoError(t, err)
|
||||
|
||||
getBlockByType := func(blocks []*Block, blockType BlockType) *Block {
|
||||
for _, b := range blocks {
|
||||
if b.Type == blockType {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return &Block{}
|
||||
}
|
||||
|
||||
getBoardByTitle := func(boards []*Board, title string) *Board {
|
||||
for _, b := range boards {
|
||||
if b.Title == title {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
t.Run("invalid boards and blocks", func(t *testing.T) {
|
||||
bab := &BoardsAndBlocks{
|
||||
Blocks: []*Block{
|
||||
{ID: "block-id-1", BoardID: "board-id-1", Type: TypeCard},
|
||||
{ID: "block-id-2", BoardID: "board-id-2", Type: TypeCard},
|
||||
},
|
||||
}
|
||||
|
||||
rBab, err := GenerateBoardsAndBlocksIDs(bab, logger)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, rBab)
|
||||
})
|
||||
|
||||
t.Run("correctly generates IDs for all the boards and links the blocks to them, with new IDs too", func(t *testing.T) {
|
||||
bab := &BoardsAndBlocks{
|
||||
Boards: []*Board{
|
||||
{ID: "board-id-1", Type: BoardTypeOpen, Title: "board1"},
|
||||
{ID: "board-id-2", Type: BoardTypePrivate, Title: "board2"},
|
||||
{ID: "board-id-3", Type: BoardTypeOpen, Title: "board3"},
|
||||
},
|
||||
Blocks: []*Block{
|
||||
{ID: "block-id-1", BoardID: "board-id-1", Type: TypeCard},
|
||||
{ID: "block-id-2", BoardID: "board-id-2", Type: TypeView},
|
||||
{ID: "block-id-3", BoardID: "board-id-2", Type: TypeText},
|
||||
},
|
||||
}
|
||||
|
||||
rBab, err := GenerateBoardsAndBlocksIDs(bab, logger)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, rBab)
|
||||
|
||||
// all boards and blocks should have refreshed their IDs, and
|
||||
// blocks should be correctly linked to the new board IDs
|
||||
board1 := getBoardByTitle(rBab.Boards, "board1")
|
||||
require.NotNil(t, board1)
|
||||
require.NotEmpty(t, board1.ID)
|
||||
require.NotEqual(t, "board-id-1", board1.ID)
|
||||
board2 := getBoardByTitle(rBab.Boards, "board2")
|
||||
require.NotNil(t, board2)
|
||||
require.NotEmpty(t, board2.ID)
|
||||
require.NotEqual(t, "board-id-2", board2.ID)
|
||||
board3 := getBoardByTitle(rBab.Boards, "board3")
|
||||
require.NotNil(t, board3)
|
||||
require.NotEmpty(t, board3.ID)
|
||||
require.NotEqual(t, "board-id-3", board3.ID)
|
||||
|
||||
block1 := getBlockByType(rBab.Blocks, TypeCard)
|
||||
require.NotNil(t, block1)
|
||||
require.NotEmpty(t, block1.ID)
|
||||
require.NotEqual(t, "block-id-1", block1.ID)
|
||||
require.Equal(t, board1.ID, block1.BoardID)
|
||||
block2 := getBlockByType(rBab.Blocks, TypeView)
|
||||
require.NotNil(t, block2)
|
||||
require.NotEmpty(t, block2.ID)
|
||||
require.NotEqual(t, "block-id-2", block2.ID)
|
||||
require.Equal(t, board2.ID, block2.BoardID)
|
||||
block3 := getBlockByType(rBab.Blocks, TypeText)
|
||||
require.NotNil(t, block3)
|
||||
require.NotEmpty(t, block3.ID)
|
||||
require.NotEqual(t, "block-id-3", block3.ID)
|
||||
require.Equal(t, board2.ID, block3.BoardID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIsValidPatchBoardsAndBlocks(t *testing.T) {
|
||||
newTitle := "new title"
|
||||
newDescription := "new description"
|
||||
var schema int64 = 1
|
||||
|
||||
t.Run("no board ids", func(t *testing.T) {
|
||||
pbab := &PatchBoardsAndBlocks{
|
||||
BoardIDs: []string{},
|
||||
BlockIDs: []string{"block-id-1"},
|
||||
BlockPatches: []*BlockPatch{
|
||||
{Title: &newTitle},
|
||||
{Schema: &schema},
|
||||
},
|
||||
}
|
||||
|
||||
require.ErrorIs(t, pbab.IsValid(), ErrNoBoardsInBoardsAndBlocks)
|
||||
})
|
||||
|
||||
t.Run("missmatch board IDs and patches", func(t *testing.T) {
|
||||
pbab := &PatchBoardsAndBlocks{
|
||||
BoardIDs: []string{"board-id-1", "board-id-2"},
|
||||
BoardPatches: []*BoardPatch{
|
||||
{Title: &newTitle},
|
||||
},
|
||||
BlockIDs: []string{"block-id-1"},
|
||||
BlockPatches: []*BlockPatch{
|
||||
{Title: &newTitle},
|
||||
},
|
||||
}
|
||||
|
||||
require.ErrorIs(t, pbab.IsValid(), ErrBoardIDsAndPatchesMissmatchInBoardsAndBlocks)
|
||||
})
|
||||
|
||||
t.Run("missmatch block IDs and patches", func(t *testing.T) {
|
||||
pbab := &PatchBoardsAndBlocks{
|
||||
BoardIDs: []string{"board-id-1", "board-id-2"},
|
||||
BoardPatches: []*BoardPatch{
|
||||
{Title: &newTitle},
|
||||
{Description: &newDescription},
|
||||
},
|
||||
BlockIDs: []string{"block-id-1"},
|
||||
BlockPatches: []*BlockPatch{
|
||||
{Title: &newTitle},
|
||||
{Schema: &schema},
|
||||
},
|
||||
}
|
||||
|
||||
require.ErrorIs(t, pbab.IsValid(), ErrBlockIDsAndPatchesMissmatchInBoardsAndBlocks)
|
||||
})
|
||||
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
pbab := &PatchBoardsAndBlocks{
|
||||
BoardIDs: []string{"board-id-1", "board-id-2"},
|
||||
BoardPatches: []*BoardPatch{
|
||||
{Title: &newTitle},
|
||||
{Description: &newDescription},
|
||||
},
|
||||
BlockIDs: []string{"block-id-1"},
|
||||
BlockPatches: []*BlockPatch{
|
||||
{Title: &newTitle},
|
||||
},
|
||||
}
|
||||
|
||||
require.NoError(t, pbab.IsValid())
|
||||
})
|
||||
}
|
||||
|
||||
func TestIsValidDeleteBoardsAndBlocks(t *testing.T) {
|
||||
/*
|
||||
TODO fix this
|
||||
t.Run("no board ids", func(t *testing.T) {
|
||||
dbab := &DeleteBoardsAndBlocks{
|
||||
TeamID: "team-id",
|
||||
Blocks: []string{"block-id-1"},
|
||||
}
|
||||
|
||||
require.ErrorIs(t, dbab.IsValid(), NoBoardsInBoardsAndBlocksErr)
|
||||
})
|
||||
|
||||
t.Run("no block ids", func(t *testing.T) {
|
||||
dbab := &DeleteBoardsAndBlocks{
|
||||
TeamID: "team-id",
|
||||
Boards: []string{"board-id-1", "board-id-2"},
|
||||
}
|
||||
|
||||
require.ErrorIs(t, dbab.IsValid(), NoBlocksInBoardsAndBlocksErr)
|
||||
})
|
||||
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
dbab := &DeleteBoardsAndBlocks{
|
||||
TeamID: "team-id",
|
||||
Boards: []string{"board-id-1", "board-id-2"},
|
||||
Blocks: []string{"block-id-1"},
|
||||
}
|
||||
|
||||
require.NoError(t, dbab.IsValid())
|
||||
})
|
||||
*/
|
||||
}
|
||||
327
server/boards/model/card.go
Обычный файл
327
server/boards/model/card.go
Обычный файл
@@ -0,0 +1,327 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/rivo/uniseg"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/boards/utils"
|
||||
)
|
||||
|
||||
var ErrBoardIDMismatch = errors.New("Board IDs do not match")
|
||||
|
||||
type ErrInvalidCard struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func NewErrInvalidCard(msg string) ErrInvalidCard {
|
||||
return ErrInvalidCard{
|
||||
msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
func (e ErrInvalidCard) Error() string {
|
||||
return fmt.Sprintf("invalid card, %s", e.msg)
|
||||
}
|
||||
|
||||
var ErrNotCardBlock = errors.New("not a card block")
|
||||
|
||||
type ErrInvalidFieldType struct {
|
||||
field string
|
||||
}
|
||||
|
||||
func (e ErrInvalidFieldType) Error() string {
|
||||
return fmt.Sprintf("invalid type for field '%s'", e.field)
|
||||
}
|
||||
|
||||
// Card represents a group of content blocks and properties.
|
||||
// swagger:model
|
||||
type Card struct {
|
||||
// The id for this card
|
||||
// required: false
|
||||
ID string `json:"id"`
|
||||
|
||||
// The id for board this card belongs to.
|
||||
// required: false
|
||||
BoardID string `json:"boardId"`
|
||||
|
||||
// The id for user who created this card
|
||||
// required: false
|
||||
CreatedBy string `json:"createdBy"`
|
||||
|
||||
// The id for user who last modified this card
|
||||
// required: false
|
||||
ModifiedBy string `json:"modifiedBy"`
|
||||
|
||||
// The display title
|
||||
// required: false
|
||||
Title string `json:"title"`
|
||||
|
||||
// An array of content block ids specifying the ordering of content for this card.
|
||||
// required: false
|
||||
ContentOrder []string `json:"contentOrder"`
|
||||
|
||||
// The icon of the card
|
||||
// required: false
|
||||
Icon string `json:"icon"`
|
||||
|
||||
// True if this card belongs to a template
|
||||
// required: false
|
||||
IsTemplate bool `json:"isTemplate"`
|
||||
|
||||
// A map of property ids to property values (option ids, strings, array of option ids)
|
||||
// required: false
|
||||
Properties map[string]any `json:"properties"`
|
||||
|
||||
// The creation time in milliseconds since the current epoch
|
||||
// required: false
|
||||
CreateAt int64 `json:"createAt"`
|
||||
|
||||
// The last modified time in milliseconds since the current epoch
|
||||
// required: false
|
||||
UpdateAt int64 `json:"updateAt"`
|
||||
|
||||
// The deleted time in milliseconds since the current epoch. Set to indicate this card is deleted
|
||||
// required: false
|
||||
DeleteAt int64 `json:"deleteAt"`
|
||||
}
|
||||
|
||||
// Populate populates a Card with default values.
|
||||
func (c *Card) Populate() {
|
||||
if c.ID == "" {
|
||||
c.ID = utils.NewID(utils.IDTypeCard)
|
||||
}
|
||||
if c.ContentOrder == nil {
|
||||
c.ContentOrder = make([]string, 0)
|
||||
}
|
||||
if c.Properties == nil {
|
||||
c.Properties = make(map[string]any)
|
||||
}
|
||||
now := utils.GetMillis()
|
||||
if c.CreateAt == 0 {
|
||||
c.CreateAt = now
|
||||
}
|
||||
if c.UpdateAt == 0 {
|
||||
c.UpdateAt = now
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Card) PopulateWithBoardID(boardID string) {
|
||||
c.BoardID = boardID
|
||||
c.Populate()
|
||||
}
|
||||
|
||||
// CheckValid returns an error if the Card has invalid field values.
|
||||
func (c *Card) CheckValid() error {
|
||||
if c.ID == "" {
|
||||
return ErrInvalidCard{"ID is missing"}
|
||||
}
|
||||
if c.BoardID == "" {
|
||||
return ErrInvalidCard{"BoardID is missing"}
|
||||
}
|
||||
if c.ContentOrder == nil {
|
||||
return ErrInvalidCard{"ContentOrder is missing"}
|
||||
}
|
||||
if uniseg.GraphemeClusterCount(c.Icon) > 1 {
|
||||
return ErrInvalidCard{"Icon can have only one grapheme"}
|
||||
}
|
||||
if c.Properties == nil {
|
||||
return ErrInvalidCard{"Properties"}
|
||||
}
|
||||
if c.CreateAt == 0 {
|
||||
return ErrInvalidCard{"CreateAt"}
|
||||
}
|
||||
if c.UpdateAt == 0 {
|
||||
return ErrInvalidCard{"UpdateAt"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CardPatch is a patch for modifying cards
|
||||
// swagger:model
|
||||
type CardPatch struct {
|
||||
// The display title
|
||||
// required: false
|
||||
Title *string `json:"title"`
|
||||
|
||||
// An array of content block ids specifying the ordering of content for this card.
|
||||
// required: false
|
||||
ContentOrder *[]string `json:"contentOrder"`
|
||||
|
||||
// The icon of the card
|
||||
// required: false
|
||||
Icon *string `json:"icon"`
|
||||
|
||||
// A map of property ids to property option ids to be updated
|
||||
// required: false
|
||||
UpdatedProperties map[string]any `json:"updatedProperties"`
|
||||
}
|
||||
|
||||
// Patch returns an updated version of the card.
|
||||
func (p *CardPatch) Patch(card *Card) *Card {
|
||||
if p.Title != nil {
|
||||
card.Title = *p.Title
|
||||
}
|
||||
|
||||
if p.ContentOrder != nil {
|
||||
card.ContentOrder = *p.ContentOrder
|
||||
}
|
||||
|
||||
if p.Icon != nil {
|
||||
card.Icon = *p.Icon
|
||||
}
|
||||
|
||||
if card.Properties == nil {
|
||||
card.Properties = make(map[string]any)
|
||||
}
|
||||
|
||||
// if there are properties marked for update, we replace the
|
||||
// existing ones or add them
|
||||
for propID, propVal := range p.UpdatedProperties {
|
||||
card.Properties[propID] = propVal
|
||||
}
|
||||
|
||||
return card
|
||||
}
|
||||
|
||||
// CheckValid returns an error if the CardPatch has invalid field values.
|
||||
func (p *CardPatch) CheckValid() error {
|
||||
if p.Icon != nil && uniseg.GraphemeClusterCount(*p.Icon) > 1 {
|
||||
return ErrInvalidCard{"Icon can have only one grapheme"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Card2Block converts a card to block using a shallow copy. Not needed once cards are first class entities.
|
||||
func Card2Block(card *Card) *Block {
|
||||
fields := make(map[string]interface{})
|
||||
|
||||
fields["contentOrder"] = card.ContentOrder
|
||||
fields["icon"] = card.Icon
|
||||
fields["isTemplate"] = card.IsTemplate
|
||||
fields["properties"] = card.Properties
|
||||
|
||||
return &Block{
|
||||
ID: card.ID,
|
||||
ParentID: card.BoardID,
|
||||
CreatedBy: card.CreatedBy,
|
||||
ModifiedBy: card.ModifiedBy,
|
||||
Schema: 1,
|
||||
Type: TypeCard,
|
||||
Title: card.Title,
|
||||
Fields: fields,
|
||||
CreateAt: card.CreateAt,
|
||||
UpdateAt: card.UpdateAt,
|
||||
DeleteAt: card.DeleteAt,
|
||||
BoardID: card.BoardID,
|
||||
}
|
||||
}
|
||||
|
||||
// Block2Card converts a block to a card. Not needed once cards are first class entities.
|
||||
func Block2Card(block *Block) (*Card, error) {
|
||||
if block.Type != TypeCard {
|
||||
return nil, fmt.Errorf("cannot convert block to card: %w", ErrNotCardBlock)
|
||||
}
|
||||
|
||||
contentOrder := make([]string, 0)
|
||||
icon := ""
|
||||
isTemplate := false
|
||||
properties := make(map[string]any)
|
||||
|
||||
if co, ok := block.Fields["contentOrder"]; ok {
|
||||
switch arr := co.(type) {
|
||||
case []any:
|
||||
for _, str := range arr {
|
||||
if id, ok := str.(string); ok {
|
||||
contentOrder = append(contentOrder, id)
|
||||
} else {
|
||||
return nil, ErrInvalidFieldType{"contentOrder item"}
|
||||
}
|
||||
}
|
||||
case []string:
|
||||
contentOrder = append(contentOrder, arr...)
|
||||
default:
|
||||
return nil, ErrInvalidFieldType{"contentOrder"}
|
||||
}
|
||||
}
|
||||
|
||||
if iconAny, ok := block.Fields["icon"]; ok {
|
||||
if id, ok := iconAny.(string); ok {
|
||||
icon = id
|
||||
} else {
|
||||
return nil, ErrInvalidFieldType{"icon"}
|
||||
}
|
||||
}
|
||||
|
||||
if isTemplateAny, ok := block.Fields["isTemplate"]; ok {
|
||||
if b, ok := isTemplateAny.(bool); ok {
|
||||
isTemplate = b
|
||||
} else {
|
||||
return nil, ErrInvalidFieldType{"isTemplate"}
|
||||
}
|
||||
}
|
||||
|
||||
if props, ok := block.Fields["properties"]; ok {
|
||||
if propMap, ok := props.(map[string]any); ok {
|
||||
for k, v := range propMap {
|
||||
properties[k] = v
|
||||
}
|
||||
} else {
|
||||
return nil, ErrInvalidFieldType{"properties"}
|
||||
}
|
||||
}
|
||||
|
||||
card := &Card{
|
||||
ID: block.ID,
|
||||
BoardID: block.BoardID,
|
||||
CreatedBy: block.CreatedBy,
|
||||
ModifiedBy: block.ModifiedBy,
|
||||
Title: block.Title,
|
||||
ContentOrder: contentOrder,
|
||||
Icon: icon,
|
||||
IsTemplate: isTemplate,
|
||||
Properties: properties,
|
||||
CreateAt: block.CreateAt,
|
||||
UpdateAt: block.UpdateAt,
|
||||
DeleteAt: block.DeleteAt,
|
||||
}
|
||||
card.Populate()
|
||||
return card, nil
|
||||
}
|
||||
|
||||
// CardPatch2BlockPatch converts a CardPatch to a BlockPatch. Not needed once cards are first class entities.
|
||||
func CardPatch2BlockPatch(cardPatch *CardPatch) (*BlockPatch, error) {
|
||||
if err := cardPatch.CheckValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockPatch := &BlockPatch{
|
||||
Title: cardPatch.Title,
|
||||
}
|
||||
|
||||
updatedFields := make(map[string]any, 0)
|
||||
|
||||
if cardPatch.ContentOrder != nil {
|
||||
updatedFields["contentOrder"] = cardPatch.ContentOrder
|
||||
}
|
||||
if cardPatch.Icon != nil {
|
||||
updatedFields["icon"] = cardPatch.Icon
|
||||
}
|
||||
|
||||
properties := make(map[string]any)
|
||||
for k, v := range cardPatch.UpdatedProperties {
|
||||
properties[k] = v
|
||||
}
|
||||
|
||||
if len(properties) != 0 {
|
||||
updatedFields["properties"] = cardPatch.UpdatedProperties
|
||||
}
|
||||
|
||||
blockPatch.UpdatedFields = updatedFields
|
||||
|
||||
return blockPatch, nil
|
||||
}
|
||||
88
server/boards/model/card_test.go
Обычный файл
88
server/boards/model/card_test.go
Обычный файл
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/boards/utils"
|
||||
)
|
||||
|
||||
func TestBlock2Card(t *testing.T) {
|
||||
blockID := utils.NewID(utils.IDTypeCard)
|
||||
boardID := utils.NewID(utils.IDTypeBoard)
|
||||
userID := utils.NewID(utils.IDTypeUser)
|
||||
now := utils.GetMillis()
|
||||
|
||||
var fields map[string]any
|
||||
err := json.Unmarshal([]byte(sampleBlockFieldsJSON), &fields)
|
||||
require.NoError(t, err)
|
||||
|
||||
block := &Block{
|
||||
ID: blockID,
|
||||
ParentID: boardID,
|
||||
CreatedBy: userID,
|
||||
ModifiedBy: userID,
|
||||
Schema: 1,
|
||||
Type: TypeCard,
|
||||
Title: "My card title",
|
||||
Fields: fields,
|
||||
CreateAt: now,
|
||||
UpdateAt: now,
|
||||
DeleteAt: 0,
|
||||
BoardID: boardID,
|
||||
}
|
||||
|
||||
t.Run("Good block", func(t *testing.T) {
|
||||
card, err := Block2Card(block)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, block.ID, card.ID)
|
||||
assert.Equal(t, []string{"acdxa8r8aht85pyoeuj1ed7tu8w", "73urm1huoupd4idzkdq5yaeuyay", "ay6sogs9owtd9xbyn49qt3395ko"}, card.ContentOrder)
|
||||
assert.EqualValues(t, fields["icon"], card.Icon)
|
||||
assert.EqualValues(t, fields["isTemplate"], card.IsTemplate)
|
||||
assert.EqualValues(t, fields["properties"], card.Properties)
|
||||
})
|
||||
|
||||
t.Run("Not a card", func(t *testing.T) {
|
||||
blockNotCard := &Block{}
|
||||
|
||||
card, err := Block2Card(blockNotCard)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, card)
|
||||
})
|
||||
}
|
||||
|
||||
const sampleBlockFieldsJSON = `
|
||||
{
|
||||
"contentOrder":[
|
||||
"acdxa8r8aht85pyoeuj1ed7tu8w",
|
||||
"73urm1huoupd4idzkdq5yaeuyay",
|
||||
"ay6sogs9owtd9xbyn49qt3395ko"
|
||||
],
|
||||
"icon":"🎨",
|
||||
"isTemplate":false,
|
||||
"properties":{
|
||||
"aa7swu9zz3ofdkcna3h867cum4y":"212-444-1234",
|
||||
"af6fcbb8-ca56-4b73-83eb-37437b9a667d":"77c539af-309c-4db1-8329-d20ef7e9eacd",
|
||||
"aiwt9ibi8jjrf9hzi1xzk8no8mo":"foo",
|
||||
"aj65h4s6ghr6wgh3bnhqbzzmiaa":"77",
|
||||
"ajy6xbebzopojaenbnmfpgtdwso":"{\"from\":1660046400000}",
|
||||
"amc8wnk1xqj54rymkoqffhtw7ie":"zhqsoeqs1pg9i8gk81k9ryy83h",
|
||||
"aooz77t119y7xtfmoyeiy4up75c":"someone@example.com",
|
||||
"auskzaoaccsn55icuwarf4o3tfe":"https://www.google.com",
|
||||
"aydsk41h6cs1z7nmghaw16jqcia":[
|
||||
"aw565znut6zphbxqhbwyawiuggy",
|
||||
"aefd3pxciomrkur4rc6smg1usoc",
|
||||
"a6c96kwrqaskbtochq9wunmzweh",
|
||||
"atyexeuq993fwwb84bxoqixxqqr"
|
||||
],
|
||||
"d6b1249b-bc18-45fc-889e-bec48fce80ef":"9a090e33-b110-4268-8909-132c5002c90e",
|
||||
"d9725d14-d5a8-48e5-8de1-6f8c004a9680":"3245a32d-f688-463b-87f4-8e7142c1b397"
|
||||
}
|
||||
}`
|
||||
118
server/boards/model/category.go
Обычный файл
118
server/boards/model/category.go
Обычный файл
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/boards/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
CategoryTypeSystem = "system"
|
||||
CategoryTypeCustom = "custom"
|
||||
)
|
||||
|
||||
// Category is a board category
|
||||
// swagger:model
|
||||
type Category struct {
|
||||
// The id for this category
|
||||
// required: true
|
||||
ID string `json:"id"`
|
||||
|
||||
// The name for this category
|
||||
// required: true
|
||||
Name string `json:"name"`
|
||||
|
||||
// The user's id for this category
|
||||
// required: true
|
||||
UserID string `json:"userID"`
|
||||
|
||||
// The team id for this category
|
||||
// required: true
|
||||
TeamID string `json:"teamID"`
|
||||
|
||||
// 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 category is deleted
|
||||
// required: false
|
||||
DeleteAt int64 `json:"deleteAt"`
|
||||
|
||||
// Category's state in client side
|
||||
// required: true
|
||||
Collapsed bool `json:"collapsed"`
|
||||
|
||||
// Inter-category sort order per user
|
||||
// required: true
|
||||
SortOrder int `json:"sortOrder"`
|
||||
|
||||
// The sorting method applied on this category
|
||||
// required: true
|
||||
Sorting string `json:"sorting"`
|
||||
|
||||
// Category's type
|
||||
// required: true
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func (c *Category) Hydrate() {
|
||||
if c.ID == "" {
|
||||
c.ID = utils.NewID(utils.IDTypeNone)
|
||||
}
|
||||
|
||||
if c.CreateAt == 0 {
|
||||
c.CreateAt = utils.GetMillis()
|
||||
}
|
||||
|
||||
if c.UpdateAt == 0 {
|
||||
c.UpdateAt = c.CreateAt
|
||||
}
|
||||
|
||||
if c.SortOrder < 0 {
|
||||
c.SortOrder = 0
|
||||
}
|
||||
|
||||
if strings.TrimSpace(c.Type) == "" {
|
||||
c.Type = CategoryTypeCustom
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Category) IsValid() error {
|
||||
if strings.TrimSpace(c.ID) == "" {
|
||||
return NewErrInvalidCategory("category ID cannot be empty")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(c.Name) == "" {
|
||||
return NewErrInvalidCategory("category name cannot be empty")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(c.UserID) == "" {
|
||||
return NewErrInvalidCategory("category user ID cannot be empty")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(c.TeamID) == "" {
|
||||
return NewErrInvalidCategory("category team id ID cannot be empty")
|
||||
}
|
||||
|
||||
if c.Type != CategoryTypeCustom && c.Type != CategoryTypeSystem {
|
||||
return NewErrInvalidCategory(fmt.Sprintf("category type is invalid. Allowed types: %s and %s", CategoryTypeSystem, CategoryTypeCustom))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CategoryFromJSON(data io.Reader) *Category {
|
||||
var category *Category
|
||||
_ = json.NewDecoder(data).Decode(&category)
|
||||
return category
|
||||
}
|
||||
31
server/boards/model/category_boards.go
Обычный файл
31
server/boards/model/category_boards.go
Обычный файл
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
const CategoryBoardsSortOrderGap = 10
|
||||
|
||||
// CategoryBoards is a board category and associated boards
|
||||
// swagger:model
|
||||
type CategoryBoards struct {
|
||||
Category
|
||||
|
||||
// The IDs of boards in this category
|
||||
// required: true
|
||||
BoardMetadata []CategoryBoardMetadata `json:"boardMetadata"`
|
||||
|
||||
// The relative sort order of this board in its category
|
||||
// required: true
|
||||
SortOrder int `json:"sortOrder"`
|
||||
}
|
||||
|
||||
type BoardCategoryWebsocketData struct {
|
||||
BoardID string `json:"boardID"`
|
||||
CategoryID string `json:"categoryID"`
|
||||
Hidden bool `json:"hidden"`
|
||||
}
|
||||
|
||||
type CategoryBoardMetadata struct {
|
||||
BoardID string `json:"boardID"`
|
||||
Hidden bool `json:"hidden"`
|
||||
}
|
||||
32
server/boards/model/clientConfig.go
Обычный файл
32
server/boards/model/clientConfig.go
Обычный файл
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
// ClientConfig is the client configuration
|
||||
// swagger:model
|
||||
type ClientConfig struct {
|
||||
// Is telemetry enabled
|
||||
// required: true
|
||||
Telemetry bool `json:"telemetry"`
|
||||
|
||||
// The telemetry ID
|
||||
// required: true
|
||||
TelemetryID string `json:"telemetryid"`
|
||||
|
||||
// Is public shared boards enabled
|
||||
// required: true
|
||||
EnablePublicSharedBoards bool `json:"enablePublicSharedBoards"`
|
||||
|
||||
// Is public shared boards enabled
|
||||
// required: true
|
||||
TeammateNameDisplay string `json:"teammateNameDisplay"`
|
||||
|
||||
// The server feature flags
|
||||
// required: true
|
||||
FeatureFlags map[string]string `json:"featureFlags"`
|
||||
|
||||
// Required for file upload to check the size of the file
|
||||
// required: true
|
||||
MaxFileSize int64 `json:"maxFileSize"`
|
||||
}
|
||||
27
server/boards/model/cloud.go
Обычный файл
27
server/boards/model/cloud.go
Обычный файл
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
const LimitUnlimited = 0
|
||||
|
||||
// BoardsCloudLimits is the representation of the limits for the
|
||||
// Boards server
|
||||
// swagger:model
|
||||
type BoardsCloudLimits struct {
|
||||
// The maximum number of cards on the server
|
||||
// required: true
|
||||
Cards int `json:"cards"`
|
||||
|
||||
// The current number of cards on the server
|
||||
// required: true
|
||||
UsedCards int `json:"used_cards"`
|
||||
|
||||
// The updated_at timestamp of the limit card
|
||||
// required: true
|
||||
CardLimitTimestamp int64 `json:"card_limit_timestamp"`
|
||||
|
||||
// The maximum number of views for each board
|
||||
// required: true
|
||||
Views int `json:"views"`
|
||||
}
|
||||
88
server/boards/model/compliance.go
Обычный файл
88
server/boards/model/compliance.go
Обычный файл
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
package model
|
||||
|
||||
// BaordsComplianceResponse is the response body to a request for boards.
|
||||
// swagger:model
|
||||
type BoardsComplianceResponse struct {
|
||||
// True if there is a next page for pagination
|
||||
// required: true
|
||||
HasNext bool `json:"hasNext"`
|
||||
|
||||
// The array of board records.
|
||||
// required: true
|
||||
Results []*Board `json:"results"`
|
||||
}
|
||||
|
||||
// BoardsComplianceHistoryResponse is the response body to a request for boards history.
|
||||
// swagger:model
|
||||
type BoardsComplianceHistoryResponse struct {
|
||||
// True if there is a next page for pagination
|
||||
// required: true
|
||||
HasNext bool `json:"hasNext"`
|
||||
|
||||
// The array of BoardHistory records.
|
||||
// required: true
|
||||
Results []*BoardHistory `json:"results"`
|
||||
}
|
||||
|
||||
// BlocksComplianceHistoryResponse is the response body to a request for blocks history.
|
||||
// swagger:model
|
||||
type BlocksComplianceHistoryResponse struct {
|
||||
// True if there is a next page for pagination
|
||||
// required: true
|
||||
HasNext bool `json:"hasNext"`
|
||||
|
||||
// The array of BlockHistory records.
|
||||
// required: true
|
||||
Results []*BlockHistory `json:"results"`
|
||||
}
|
||||
|
||||
// BoardHistory provides information about the history of a board.
|
||||
// swagger:model
|
||||
type BoardHistory struct {
|
||||
ID string `json:"id"`
|
||||
TeamID string `json:"teamId"`
|
||||
IsDeleted bool `json:"isDeleted"`
|
||||
DescendantLastUpdateAt int64 `json:"descendantLastUpdateAt"`
|
||||
DescendantFirstUpdateAt int64 `json:"descendantFirstUpdateAt"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
LastModifiedBy string `json:"lastModifiedBy"`
|
||||
}
|
||||
|
||||
// BlockHistory provides information about the history of a block.
|
||||
// swagger:model
|
||||
type BlockHistory struct {
|
||||
ID string `json:"id"`
|
||||
TeamID string `json:"teamId"`
|
||||
BoardID string `json:"boardId"`
|
||||
Type string `json:"type"`
|
||||
IsDeleted bool `json:"isDeleted"`
|
||||
LastUpdateAt int64 `json:"lastUpdateAt"`
|
||||
FirstUpdateAt int64 `json:"firstUpdateAt"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
LastModifiedBy string `json:"lastModifiedBy"`
|
||||
}
|
||||
|
||||
type QueryBoardsForComplianceOptions struct {
|
||||
TeamID string // if not empty then filter for specific team, otherwise all teams are included
|
||||
Page int // page number to select when paginating
|
||||
PerPage int // number of blocks per page (default=60)
|
||||
}
|
||||
|
||||
type QueryBoardsComplianceHistoryOptions struct {
|
||||
ModifiedSince int64 // if non-zero then filter for records with update_at greater than ModifiedSince
|
||||
IncludeDeleted bool // if true then deleted blocks are included
|
||||
TeamID string // if not empty then filter for specific team, otherwise all teams are included
|
||||
Page int // page number to select when paginating
|
||||
PerPage int // number of blocks per page (default=60)
|
||||
}
|
||||
|
||||
type QueryBlocksComplianceHistoryOptions struct {
|
||||
ModifiedSince int64 // if non-zero then filter for records with update_at greater than ModifiedSince
|
||||
IncludeDeleted bool // if true then deleted blocks are included
|
||||
TeamID string // if not empty then filter for specific team, otherwise all teams are included
|
||||
BoardID string // if not empty then filter for specific board, otherwise all boards are included
|
||||
Page int // page number to select when paginating
|
||||
PerPage int // number of blocks per page (default=60)
|
||||
}
|
||||
9
server/boards/model/database.go
Обычный файл
9
server/boards/model/database.go
Обычный файл
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
const (
|
||||
PostgresDBType = "postgres"
|
||||
MysqlDBType = "mysql"
|
||||
)
|
||||
314
server/boards/model/error.go
Обычный файл
314
server/boards/model/error.go
Обычный файл
@@ -0,0 +1,314 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
mm_model "github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrViewsLimitReached = errors.New("views limit reached for board")
|
||||
ErrPatchUpdatesLimitedCards = errors.New("patch updates cards that are limited")
|
||||
|
||||
ErrInsufficientLicense = errors.New("appropriate license required")
|
||||
|
||||
ErrCategoryPermissionDenied = errors.New("category doesn't belong to user")
|
||||
ErrCategoryDeleted = errors.New("category is deleted")
|
||||
|
||||
ErrBoardMemberIsLastAdmin = errors.New("cannot leave a board with no admins")
|
||||
|
||||
ErrRequestEntityTooLarge = errors.New("request entity too large")
|
||||
|
||||
ErrInvalidBoardSearchField = errors.New("invalid board search field")
|
||||
)
|
||||
|
||||
// ErrNotFound is an error type that can be returned by store APIs
|
||||
// when a query unexpectedly fetches no records.
|
||||
type ErrNotFound struct {
|
||||
entity string
|
||||
}
|
||||
|
||||
// NewErrNotFound creates a new ErrNotFound instance.
|
||||
func NewErrNotFound(entity string) *ErrNotFound {
|
||||
return &ErrNotFound{
|
||||
entity: entity,
|
||||
}
|
||||
}
|
||||
|
||||
func (nf *ErrNotFound) Error() string {
|
||||
return fmt.Sprintf("{%s} not found", nf.entity)
|
||||
}
|
||||
|
||||
// ErrNotAllFound is an error type that can be returned by store APIs
|
||||
// when a query that should fetch a certain amount of records
|
||||
// unexpectedly fetches less.
|
||||
type ErrNotAllFound struct {
|
||||
entity string
|
||||
resources []string
|
||||
}
|
||||
|
||||
func NewErrNotAllFound(entity string, resources []string) *ErrNotAllFound {
|
||||
return &ErrNotAllFound{
|
||||
entity: entity,
|
||||
resources: resources,
|
||||
}
|
||||
}
|
||||
|
||||
func (naf *ErrNotAllFound) Error() string {
|
||||
return fmt.Sprintf("not all instances of {%s} in {%s} found", naf.entity, strings.Join(naf.resources, ", "))
|
||||
}
|
||||
|
||||
// ErrBadRequest can be returned when the API handler receives a
|
||||
// malformed request.
|
||||
type ErrBadRequest struct {
|
||||
reason string
|
||||
}
|
||||
|
||||
// NewErrNotFound creates a new ErrNotFound instance.
|
||||
func NewErrBadRequest(reason string) *ErrBadRequest {
|
||||
return &ErrBadRequest{
|
||||
reason: reason,
|
||||
}
|
||||
}
|
||||
|
||||
func (br *ErrBadRequest) Error() string {
|
||||
return br.reason
|
||||
}
|
||||
|
||||
// ErrUnauthorized can be returned when requester has provided an
|
||||
// invalid authorization for a given resource or has not provided any.
|
||||
type ErrUnauthorized struct {
|
||||
reason string
|
||||
}
|
||||
|
||||
// NewErrUnauthorized creates a new ErrUnauthorized instance.
|
||||
func NewErrUnauthorized(reason string) *ErrUnauthorized {
|
||||
return &ErrUnauthorized{
|
||||
reason: reason,
|
||||
}
|
||||
}
|
||||
|
||||
func (br *ErrUnauthorized) Error() string {
|
||||
return br.reason
|
||||
}
|
||||
|
||||
// ErrPermission can be returned when requester lacks a permission for
|
||||
// a given resource.
|
||||
type ErrPermission struct {
|
||||
reason string
|
||||
}
|
||||
|
||||
// NewErrPermission creates a new ErrPermission instance.
|
||||
func NewErrPermission(reason string) *ErrPermission {
|
||||
return &ErrPermission{
|
||||
reason: reason,
|
||||
}
|
||||
}
|
||||
|
||||
func (br *ErrPermission) Error() string {
|
||||
return br.reason
|
||||
}
|
||||
|
||||
// ErrForbidden can be returned when requester doesn't have access to
|
||||
// a given resource.
|
||||
type ErrForbidden struct {
|
||||
reason string
|
||||
}
|
||||
|
||||
// NewErrForbidden creates a new ErrForbidden instance.
|
||||
func NewErrForbidden(reason string) *ErrForbidden {
|
||||
return &ErrForbidden{
|
||||
reason: reason,
|
||||
}
|
||||
}
|
||||
|
||||
func (br *ErrForbidden) Error() string {
|
||||
return br.reason
|
||||
}
|
||||
|
||||
type ErrInvalidCategory struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func NewErrInvalidCategory(msg string) *ErrInvalidCategory {
|
||||
return &ErrInvalidCategory{
|
||||
msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ErrInvalidCategory) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
type ErrNotImplemented struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func NewErrNotImplemented(msg string) *ErrNotImplemented {
|
||||
return &ErrNotImplemented{
|
||||
msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
func (ni *ErrNotImplemented) Error() string {
|
||||
return ni.msg
|
||||
}
|
||||
|
||||
// IsErrBadRequest returns true if `err` is or wraps one of:
|
||||
// - model.ErrBadRequest
|
||||
// - model.ErrViewsLimitReached
|
||||
// - model.ErrAuthParam
|
||||
// - model.ErrInvalidCategory
|
||||
// - model.ErrBoardMemberIsLastAdmin
|
||||
// - model.ErrBoardIDMismatch.
|
||||
func IsErrBadRequest(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if this is a model.ErrBadRequest
|
||||
var br *ErrBadRequest
|
||||
if errors.As(err, &br) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check if this is a model.ErrAuthParam
|
||||
var ap *ErrAuthParam
|
||||
if errors.As(err, &ap) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check if this is a model.ErrViewsLimitReached
|
||||
if errors.Is(err, ErrViewsLimitReached) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check if this is a model.ErrInvalidCategory
|
||||
var ic *ErrInvalidCategory
|
||||
if errors.As(err, &ic) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check if this is a model.ErrBoardIDMismatch
|
||||
if errors.Is(err, ErrBoardMemberIsLastAdmin) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check if this is a model.ErrBoardMemberIsLastAdmin
|
||||
return errors.Is(err, ErrBoardIDMismatch)
|
||||
}
|
||||
|
||||
// IsErrUnauthorized returns true if `err` is or wraps one of:
|
||||
// - model.ErrUnauthorized.
|
||||
func IsErrUnauthorized(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if this is a model.ErrUnauthorized
|
||||
var u *ErrUnauthorized
|
||||
return errors.As(err, &u)
|
||||
}
|
||||
|
||||
// IsErrForbidden returns true if `err` is or wraps one of:
|
||||
// - model.ErrForbidden
|
||||
// - model.ErrPermission
|
||||
// - model.ErrPatchUpdatesLimitedCards
|
||||
// - model.ErrorCategoryPermissionDenied.
|
||||
func IsErrForbidden(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if this is a model.ErrForbidden
|
||||
var f *ErrForbidden
|
||||
if errors.As(err, &f) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check if this is a model.ErrPermission
|
||||
var p *ErrPermission
|
||||
if errors.As(err, &p) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check if this is a model.ErrPatchUpdatesLimitedCards
|
||||
if errors.Is(err, ErrPatchUpdatesLimitedCards) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check if this is a model.ErrCategoryPermissionDenied
|
||||
return errors.Is(err, ErrCategoryPermissionDenied)
|
||||
}
|
||||
|
||||
// IsErrNotFound returns true if `err` is or wraps one of:
|
||||
// - model.ErrNotFound
|
||||
// - model.ErrNotAllFound
|
||||
// - sql.ErrNoRows
|
||||
// - mattermost-plugin-api/ErrNotFound.
|
||||
// - model.ErrCategoryDeleted.
|
||||
func IsErrNotFound(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if this is a model.ErrNotFound
|
||||
var nf *ErrNotFound
|
||||
if errors.As(err, &nf) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check if this is a model.ErrNotAllFound
|
||||
var naf *ErrNotAllFound
|
||||
if errors.As(err, &naf) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check if this is a sql.ErrNotFound
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check if this is a Mattermost AppError with a Not Found status
|
||||
var appErr *mm_model.AppError
|
||||
if errors.As(err, &appErr) {
|
||||
if appErr.StatusCode == http.StatusNotFound {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// check if this is a model.ErrCategoryDeleted
|
||||
return errors.Is(err, ErrCategoryDeleted)
|
||||
}
|
||||
|
||||
// IsErrRequestEntityTooLarge returns true if `err` is or wraps one of:
|
||||
// - model.ErrRequestEntityTooLarge.
|
||||
func IsErrRequestEntityTooLarge(err error) bool {
|
||||
// check if this is a model.ErrRequestEntityTooLarge
|
||||
return errors.Is(err, ErrRequestEntityTooLarge)
|
||||
}
|
||||
|
||||
// IsErrNotImplemented returns true if `err` is or wraps one of:
|
||||
// - model.ErrNotImplemented
|
||||
// - model.ErrInsufficientLicense.
|
||||
func IsErrNotImplemented(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if this is a model.ErrNotImplemented
|
||||
var eni *ErrNotImplemented
|
||||
if errors.As(err, &eni) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check if this is a model.ErrInsufficientLicense
|
||||
return errors.Is(err, ErrInsufficientLicense)
|
||||
}
|
||||
16
server/boards/model/errorResponse.go
Обычный файл
16
server/boards/model/errorResponse.go
Обычный файл
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
// ErrorResponse is an error response
|
||||
// swagger:model
|
||||
type ErrorResponse struct {
|
||||
// The error message
|
||||
// required: false
|
||||
Error string `json:"error"`
|
||||
|
||||
// The error code
|
||||
// required: false
|
||||
ErrorCode int `json:"errorCode"`
|
||||
}
|
||||
90
server/boards/model/import_export.go
Обычный файл
90
server/boards/model/import_export.go
Обычный файл
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidImageBlock = errors.New("invalid image block")
|
||||
)
|
||||
|
||||
// Archive is an import / export archive.
|
||||
// TODO: remove once default templates are converted to new archive format.
|
||||
type Archive struct {
|
||||
Version int64 `json:"version"`
|
||||
Date int64 `json:"date"`
|
||||
Blocks []Block `json:"blocks"`
|
||||
}
|
||||
|
||||
// ArchiveHeader is the content of the first file (`version.json`) within an archive.
|
||||
type ArchiveHeader struct {
|
||||
Version int `json:"version"`
|
||||
Date int64 `json:"date"`
|
||||
}
|
||||
|
||||
// ArchiveLine is any line in an archive.
|
||||
type ArchiveLine struct {
|
||||
Type string `json:"type"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
// ExportArchiveOptions provides options when exporting one or more boards
|
||||
// to an archive.
|
||||
type ExportArchiveOptions struct {
|
||||
TeamID string
|
||||
|
||||
// BoardIDs is the list of boards to include in the archive.
|
||||
// Empty slice means export all boards from workspace/team.
|
||||
BoardIDs []string
|
||||
}
|
||||
|
||||
// ImportArchiveOptions provides options when importing an archive.
|
||||
type ImportArchiveOptions struct {
|
||||
TeamID string
|
||||
ModifiedBy string
|
||||
BoardModifier BoardModifier
|
||||
BlockModifier BlockModifier
|
||||
}
|
||||
|
||||
// ErrUnsupportedArchiveVersion is an error returned when trying to import an
|
||||
// archive with a version that this server does not support.
|
||||
type ErrUnsupportedArchiveVersion struct {
|
||||
got int
|
||||
want int
|
||||
}
|
||||
|
||||
// NewErrUnsupportedArchiveVersion creates a ErrUnsupportedArchiveVersion error.
|
||||
func NewErrUnsupportedArchiveVersion(got int, want int) ErrUnsupportedArchiveVersion {
|
||||
return ErrUnsupportedArchiveVersion{
|
||||
got: got,
|
||||
want: want,
|
||||
}
|
||||
}
|
||||
|
||||
func (e ErrUnsupportedArchiveVersion) Error() string {
|
||||
return fmt.Sprintf("unsupported archive version; got %d, want %d", e.got, e.want)
|
||||
}
|
||||
|
||||
// ErrUnsupportedArchiveLineType is an error returned when trying to import an
|
||||
// archive containing an unsupported line type.
|
||||
type ErrUnsupportedArchiveLineType struct {
|
||||
line int
|
||||
got string
|
||||
}
|
||||
|
||||
// NewErrUnsupportedArchiveLineType creates a ErrUnsupportedArchiveLineType error.
|
||||
func NewErrUnsupportedArchiveLineType(line int, got string) ErrUnsupportedArchiveLineType {
|
||||
return ErrUnsupportedArchiveLineType{
|
||||
line: line,
|
||||
got: got,
|
||||
}
|
||||
}
|
||||
|
||||
func (e ErrUnsupportedArchiveLineType) Error() string {
|
||||
return fmt.Sprintf("unsupported archive line type; got %s, line %d", e.got, e.line)
|
||||
}
|
||||
490
server/boards/model/mocks/mockservicesapi.go
Обычный файл
490
server/boards/model/mocks/mockservicesapi.go
Обычный файл
@@ -0,0 +1,490 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/mattermost/mattermost-server/v6/server/boards/model (interfaces: ServicesAPI)
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
sql "database/sql"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
mux "github.com/gorilla/mux"
|
||||
model "github.com/mattermost/mattermost-server/v6/model"
|
||||
mlog "github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog"
|
||||
)
|
||||
|
||||
// MockServicesAPI is a mock of ServicesAPI interface.
|
||||
type MockServicesAPI struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockServicesAPIMockRecorder
|
||||
}
|
||||
|
||||
// MockServicesAPIMockRecorder is the mock recorder for MockServicesAPI.
|
||||
type MockServicesAPIMockRecorder struct {
|
||||
mock *MockServicesAPI
|
||||
}
|
||||
|
||||
// NewMockServicesAPI creates a new mock instance.
|
||||
func NewMockServicesAPI(ctrl *gomock.Controller) *MockServicesAPI {
|
||||
mock := &MockServicesAPI{ctrl: ctrl}
|
||||
mock.recorder = &MockServicesAPIMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockServicesAPI) EXPECT() *MockServicesAPIMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// CreateMember mocks base method.
|
||||
func (m *MockServicesAPI) CreateMember(arg0, arg1 string) (*model.TeamMember, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateMember", arg0, arg1)
|
||||
ret0, _ := ret[0].(*model.TeamMember)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateMember indicates an expected call of CreateMember.
|
||||
func (mr *MockServicesAPIMockRecorder) CreateMember(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMember", reflect.TypeOf((*MockServicesAPI)(nil).CreateMember), arg0, arg1)
|
||||
}
|
||||
|
||||
// CreatePost mocks base method.
|
||||
func (m *MockServicesAPI) CreatePost(arg0 *model.Post) (*model.Post, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreatePost", arg0)
|
||||
ret0, _ := ret[0].(*model.Post)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreatePost indicates an expected call of CreatePost.
|
||||
func (mr *MockServicesAPIMockRecorder) CreatePost(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePost", reflect.TypeOf((*MockServicesAPI)(nil).CreatePost), arg0)
|
||||
}
|
||||
|
||||
// DeletePreferencesForUser mocks base method.
|
||||
func (m *MockServicesAPI) DeletePreferencesForUser(arg0 string, arg1 model.Preferences) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeletePreferencesForUser", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeletePreferencesForUser indicates an expected call of DeletePreferencesForUser.
|
||||
func (mr *MockServicesAPIMockRecorder) DeletePreferencesForUser(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePreferencesForUser", reflect.TypeOf((*MockServicesAPI)(nil).DeletePreferencesForUser), arg0, arg1)
|
||||
}
|
||||
|
||||
// EnsureBot mocks base method.
|
||||
func (m *MockServicesAPI) EnsureBot(arg0 *model.Bot) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EnsureBot", arg0)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// EnsureBot indicates an expected call of EnsureBot.
|
||||
func (mr *MockServicesAPIMockRecorder) EnsureBot(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureBot", reflect.TypeOf((*MockServicesAPI)(nil).EnsureBot), arg0)
|
||||
}
|
||||
|
||||
// GetChannelByID mocks base method.
|
||||
func (m *MockServicesAPI) GetChannelByID(arg0 string) (*model.Channel, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetChannelByID", arg0)
|
||||
ret0, _ := ret[0].(*model.Channel)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetChannelByID indicates an expected call of GetChannelByID.
|
||||
func (mr *MockServicesAPIMockRecorder) GetChannelByID(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChannelByID", reflect.TypeOf((*MockServicesAPI)(nil).GetChannelByID), arg0)
|
||||
}
|
||||
|
||||
// GetChannelMember mocks base method.
|
||||
func (m *MockServicesAPI) GetChannelMember(arg0, arg1 string) (*model.ChannelMember, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetChannelMember", arg0, arg1)
|
||||
ret0, _ := ret[0].(*model.ChannelMember)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetChannelMember indicates an expected call of GetChannelMember.
|
||||
func (mr *MockServicesAPIMockRecorder) GetChannelMember(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChannelMember", reflect.TypeOf((*MockServicesAPI)(nil).GetChannelMember), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetChannelsForTeamForUser mocks base method.
|
||||
func (m *MockServicesAPI) GetChannelsForTeamForUser(arg0, arg1 string, arg2 bool) (model.ChannelList, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetChannelsForTeamForUser", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(model.ChannelList)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetChannelsForTeamForUser indicates an expected call of GetChannelsForTeamForUser.
|
||||
func (mr *MockServicesAPIMockRecorder) GetChannelsForTeamForUser(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChannelsForTeamForUser", reflect.TypeOf((*MockServicesAPI)(nil).GetChannelsForTeamForUser), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// GetCloudLimits mocks base method.
|
||||
func (m *MockServicesAPI) GetCloudLimits() (*model.ProductLimits, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetCloudLimits")
|
||||
ret0, _ := ret[0].(*model.ProductLimits)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetCloudLimits indicates an expected call of GetCloudLimits.
|
||||
func (mr *MockServicesAPIMockRecorder) GetCloudLimits() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCloudLimits", reflect.TypeOf((*MockServicesAPI)(nil).GetCloudLimits))
|
||||
}
|
||||
|
||||
// GetConfig mocks base method.
|
||||
func (m *MockServicesAPI) GetConfig() *model.Config {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetConfig")
|
||||
ret0, _ := ret[0].(*model.Config)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetConfig indicates an expected call of GetConfig.
|
||||
func (mr *MockServicesAPIMockRecorder) GetConfig() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfig", reflect.TypeOf((*MockServicesAPI)(nil).GetConfig))
|
||||
}
|
||||
|
||||
// GetDiagnosticID mocks base method.
|
||||
func (m *MockServicesAPI) GetDiagnosticID() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetDiagnosticID")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetDiagnosticID indicates an expected call of GetDiagnosticID.
|
||||
func (mr *MockServicesAPIMockRecorder) GetDiagnosticID() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDiagnosticID", reflect.TypeOf((*MockServicesAPI)(nil).GetDiagnosticID))
|
||||
}
|
||||
|
||||
// GetDirectChannel mocks base method.
|
||||
func (m *MockServicesAPI) GetDirectChannel(arg0, arg1 string) (*model.Channel, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetDirectChannel", arg0, arg1)
|
||||
ret0, _ := ret[0].(*model.Channel)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetDirectChannel indicates an expected call of GetDirectChannel.
|
||||
func (mr *MockServicesAPIMockRecorder) GetDirectChannel(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDirectChannel", reflect.TypeOf((*MockServicesAPI)(nil).GetDirectChannel), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetDirectChannelOrCreate mocks base method.
|
||||
func (m *MockServicesAPI) GetDirectChannelOrCreate(arg0, arg1 string) (*model.Channel, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetDirectChannelOrCreate", arg0, arg1)
|
||||
ret0, _ := ret[0].(*model.Channel)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetDirectChannelOrCreate indicates an expected call of GetDirectChannelOrCreate.
|
||||
func (mr *MockServicesAPIMockRecorder) GetDirectChannelOrCreate(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDirectChannelOrCreate", reflect.TypeOf((*MockServicesAPI)(nil).GetDirectChannelOrCreate), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetFileInfo mocks base method.
|
||||
func (m *MockServicesAPI) GetFileInfo(arg0 string) (*model.FileInfo, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetFileInfo", arg0)
|
||||
ret0, _ := ret[0].(*model.FileInfo)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetFileInfo indicates an expected call of GetFileInfo.
|
||||
func (mr *MockServicesAPIMockRecorder) GetFileInfo(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFileInfo", reflect.TypeOf((*MockServicesAPI)(nil).GetFileInfo), arg0)
|
||||
}
|
||||
|
||||
// GetLicense mocks base method.
|
||||
func (m *MockServicesAPI) GetLicense() *model.License {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetLicense")
|
||||
ret0, _ := ret[0].(*model.License)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetLicense indicates an expected call of GetLicense.
|
||||
func (mr *MockServicesAPIMockRecorder) GetLicense() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLicense", reflect.TypeOf((*MockServicesAPI)(nil).GetLicense))
|
||||
}
|
||||
|
||||
// GetLogger mocks base method.
|
||||
func (m *MockServicesAPI) GetLogger() mlog.LoggerIFace {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetLogger")
|
||||
ret0, _ := ret[0].(mlog.LoggerIFace)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetLogger indicates an expected call of GetLogger.
|
||||
func (mr *MockServicesAPIMockRecorder) GetLogger() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogger", reflect.TypeOf((*MockServicesAPI)(nil).GetLogger))
|
||||
}
|
||||
|
||||
// GetMasterDB mocks base method.
|
||||
func (m *MockServicesAPI) GetMasterDB() (*sql.DB, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetMasterDB")
|
||||
ret0, _ := ret[0].(*sql.DB)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetMasterDB indicates an expected call of GetMasterDB.
|
||||
func (mr *MockServicesAPIMockRecorder) GetMasterDB() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMasterDB", reflect.TypeOf((*MockServicesAPI)(nil).GetMasterDB))
|
||||
}
|
||||
|
||||
// GetPreferencesForUser mocks base method.
|
||||
func (m *MockServicesAPI) GetPreferencesForUser(arg0 string) (model.Preferences, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetPreferencesForUser", arg0)
|
||||
ret0, _ := ret[0].(model.Preferences)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetPreferencesForUser indicates an expected call of GetPreferencesForUser.
|
||||
func (mr *MockServicesAPIMockRecorder) GetPreferencesForUser(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPreferencesForUser", reflect.TypeOf((*MockServicesAPI)(nil).GetPreferencesForUser), arg0)
|
||||
}
|
||||
|
||||
// GetTeamMember mocks base method.
|
||||
func (m *MockServicesAPI) GetTeamMember(arg0, arg1 string) (*model.TeamMember, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetTeamMember", arg0, arg1)
|
||||
ret0, _ := ret[0].(*model.TeamMember)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetTeamMember indicates an expected call of GetTeamMember.
|
||||
func (mr *MockServicesAPIMockRecorder) GetTeamMember(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTeamMember", reflect.TypeOf((*MockServicesAPI)(nil).GetTeamMember), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetUserByEmail mocks base method.
|
||||
func (m *MockServicesAPI) GetUserByEmail(arg0 string) (*model.User, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetUserByEmail", arg0)
|
||||
ret0, _ := ret[0].(*model.User)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetUserByEmail indicates an expected call of GetUserByEmail.
|
||||
func (mr *MockServicesAPIMockRecorder) GetUserByEmail(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByEmail", reflect.TypeOf((*MockServicesAPI)(nil).GetUserByEmail), arg0)
|
||||
}
|
||||
|
||||
// GetUserByID mocks base method.
|
||||
func (m *MockServicesAPI) GetUserByID(arg0 string) (*model.User, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetUserByID", arg0)
|
||||
ret0, _ := ret[0].(*model.User)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetUserByID indicates an expected call of GetUserByID.
|
||||
func (mr *MockServicesAPIMockRecorder) GetUserByID(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByID", reflect.TypeOf((*MockServicesAPI)(nil).GetUserByID), arg0)
|
||||
}
|
||||
|
||||
// GetUserByUsername mocks base method.
|
||||
func (m *MockServicesAPI) GetUserByUsername(arg0 string) (*model.User, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetUserByUsername", arg0)
|
||||
ret0, _ := ret[0].(*model.User)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetUserByUsername indicates an expected call of GetUserByUsername.
|
||||
func (mr *MockServicesAPIMockRecorder) GetUserByUsername(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByUsername", reflect.TypeOf((*MockServicesAPI)(nil).GetUserByUsername), arg0)
|
||||
}
|
||||
|
||||
// GetUsersFromProfiles mocks base method.
|
||||
func (m *MockServicesAPI) GetUsersFromProfiles(arg0 *model.UserGetOptions) ([]*model.User, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetUsersFromProfiles", arg0)
|
||||
ret0, _ := ret[0].([]*model.User)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetUsersFromProfiles indicates an expected call of GetUsersFromProfiles.
|
||||
func (mr *MockServicesAPIMockRecorder) GetUsersFromProfiles(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsersFromProfiles", reflect.TypeOf((*MockServicesAPI)(nil).GetUsersFromProfiles), arg0)
|
||||
}
|
||||
|
||||
// HasPermissionTo mocks base method.
|
||||
func (m *MockServicesAPI) HasPermissionTo(arg0 string, arg1 *model.Permission) bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HasPermissionTo", arg0, arg1)
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// HasPermissionTo indicates an expected call of HasPermissionTo.
|
||||
func (mr *MockServicesAPIMockRecorder) HasPermissionTo(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasPermissionTo", reflect.TypeOf((*MockServicesAPI)(nil).HasPermissionTo), arg0, arg1)
|
||||
}
|
||||
|
||||
// HasPermissionToChannel mocks base method.
|
||||
func (m *MockServicesAPI) HasPermissionToChannel(arg0, arg1 string, arg2 *model.Permission) bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HasPermissionToChannel", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// HasPermissionToChannel indicates an expected call of HasPermissionToChannel.
|
||||
func (mr *MockServicesAPIMockRecorder) HasPermissionToChannel(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasPermissionToChannel", reflect.TypeOf((*MockServicesAPI)(nil).HasPermissionToChannel), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// HasPermissionToTeam mocks base method.
|
||||
func (m *MockServicesAPI) HasPermissionToTeam(arg0, arg1 string, arg2 *model.Permission) bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HasPermissionToTeam", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// HasPermissionToTeam indicates an expected call of HasPermissionToTeam.
|
||||
func (mr *MockServicesAPIMockRecorder) HasPermissionToTeam(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasPermissionToTeam", reflect.TypeOf((*MockServicesAPI)(nil).HasPermissionToTeam), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// KVSetWithOptions mocks base method.
|
||||
func (m *MockServicesAPI) KVSetWithOptions(arg0 string, arg1 []byte, arg2 model.PluginKVSetOptions) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "KVSetWithOptions", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// KVSetWithOptions indicates an expected call of KVSetWithOptions.
|
||||
func (mr *MockServicesAPIMockRecorder) KVSetWithOptions(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KVSetWithOptions", reflect.TypeOf((*MockServicesAPI)(nil).KVSetWithOptions), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// PublishPluginClusterEvent mocks base method.
|
||||
func (m *MockServicesAPI) PublishPluginClusterEvent(arg0 model.PluginClusterEvent, arg1 model.PluginClusterEventSendOptions) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "PublishPluginClusterEvent", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// PublishPluginClusterEvent indicates an expected call of PublishPluginClusterEvent.
|
||||
func (mr *MockServicesAPIMockRecorder) PublishPluginClusterEvent(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishPluginClusterEvent", reflect.TypeOf((*MockServicesAPI)(nil).PublishPluginClusterEvent), arg0, arg1)
|
||||
}
|
||||
|
||||
// PublishWebSocketEvent mocks base method.
|
||||
func (m *MockServicesAPI) PublishWebSocketEvent(arg0 string, arg1 map[string]interface{}, arg2 *model.WebsocketBroadcast) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "PublishWebSocketEvent", arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// PublishWebSocketEvent indicates an expected call of PublishWebSocketEvent.
|
||||
func (mr *MockServicesAPIMockRecorder) PublishWebSocketEvent(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishWebSocketEvent", reflect.TypeOf((*MockServicesAPI)(nil).PublishWebSocketEvent), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// RegisterRouter mocks base method.
|
||||
func (m *MockServicesAPI) RegisterRouter(arg0 *mux.Router) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "RegisterRouter", arg0)
|
||||
}
|
||||
|
||||
// RegisterRouter indicates an expected call of RegisterRouter.
|
||||
func (mr *MockServicesAPIMockRecorder) RegisterRouter(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterRouter", reflect.TypeOf((*MockServicesAPI)(nil).RegisterRouter), arg0)
|
||||
}
|
||||
|
||||
// UpdatePreferencesForUser mocks base method.
|
||||
func (m *MockServicesAPI) UpdatePreferencesForUser(arg0 string, arg1 model.Preferences) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdatePreferencesForUser", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpdatePreferencesForUser indicates an expected call of UpdatePreferencesForUser.
|
||||
func (mr *MockServicesAPIMockRecorder) UpdatePreferencesForUser(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePreferencesForUser", reflect.TypeOf((*MockServicesAPI)(nil).UpdatePreferencesForUser), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdateUser mocks base method.
|
||||
func (m *MockServicesAPI) UpdateUser(arg0 *model.User) (*model.User, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateUser", arg0)
|
||||
ret0, _ := ret[0].(*model.User)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateUser indicates an expected call of UpdateUser.
|
||||
func (mr *MockServicesAPIMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockServicesAPI)(nil).UpdateUser), arg0)
|
||||
}
|
||||
53
server/boards/model/mocks/propValueResolverMock.go
Обычный файл
53
server/boards/model/mocks/propValueResolverMock.go
Обычный файл
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/mattermost/mattermost-server/v6/server/boards/model (interfaces: PropValueResolver)
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
model "github.com/mattermost/mattermost-server/v6/server/boards/model"
|
||||
)
|
||||
|
||||
// MockPropValueResolver is a mock of PropValueResolver interface.
|
||||
type MockPropValueResolver struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockPropValueResolverMockRecorder
|
||||
}
|
||||
|
||||
// MockPropValueResolverMockRecorder is the mock recorder for MockPropValueResolver.
|
||||
type MockPropValueResolverMockRecorder struct {
|
||||
mock *MockPropValueResolver
|
||||
}
|
||||
|
||||
// NewMockPropValueResolver creates a new mock instance.
|
||||
func NewMockPropValueResolver(ctrl *gomock.Controller) *MockPropValueResolver {
|
||||
mock := &MockPropValueResolver{ctrl: ctrl}
|
||||
mock.recorder = &MockPropValueResolverMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockPropValueResolver) EXPECT() *MockPropValueResolverMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetUserByID mocks base method.
|
||||
func (m *MockPropValueResolver) GetUserByID(arg0 string) (*model.User, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetUserByID", arg0)
|
||||
ret0, _ := ret[0].(*model.User)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetUserByID indicates an expected call of GetUserByID.
|
||||
func (mr *MockPropValueResolverMockRecorder) GetUserByID(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByID", reflect.TypeOf((*MockPropValueResolver)(nil).GetUserByID), arg0)
|
||||
}
|
||||
84
server/boards/model/notification.go
Обычный файл
84
server/boards/model/notification.go
Обычный файл
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/channels/utils"
|
||||
)
|
||||
|
||||
// NotificationHint provides a hint that a block has been modified and has subscribers that
|
||||
// should be notified.
|
||||
// swagger:model
|
||||
type NotificationHint struct {
|
||||
// BlockType is the block type of the entity (e.g. board, card) that was updated
|
||||
// required: true
|
||||
BlockType BlockType `json:"block_type"`
|
||||
|
||||
// BlockID is id of the entity that was updated
|
||||
// required: true
|
||||
BlockID string `json:"block_id"`
|
||||
|
||||
// ModifiedByID is the id of the user who made the block change
|
||||
ModifiedByID string `json:"modified_by_id"`
|
||||
|
||||
// CreatedAt is the timestamp this notification hint was created in miliseconds since the current epoch
|
||||
// required: true
|
||||
CreateAt int64 `json:"create_at"`
|
||||
|
||||
// NotifyAt is the timestamp this notification should be scheduled in miliseconds since the current epoch
|
||||
// required: true
|
||||
NotifyAt int64 `json:"notify_at"`
|
||||
}
|
||||
|
||||
func (s *NotificationHint) IsValid() error {
|
||||
if s == nil {
|
||||
return ErrInvalidNotificationHint{"cannot be nil"}
|
||||
}
|
||||
if s.BlockID == "" {
|
||||
return ErrInvalidNotificationHint{"missing block id"}
|
||||
}
|
||||
if s.BlockType == "" {
|
||||
return ErrInvalidNotificationHint{"missing block type"}
|
||||
}
|
||||
if s.ModifiedByID == "" {
|
||||
return ErrInvalidNotificationHint{"missing modified_by id"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *NotificationHint) Copy() *NotificationHint {
|
||||
return &NotificationHint{
|
||||
BlockType: s.BlockType,
|
||||
BlockID: s.BlockID,
|
||||
ModifiedByID: s.ModifiedByID,
|
||||
CreateAt: s.CreateAt,
|
||||
NotifyAt: s.NotifyAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *NotificationHint) LogClone() interface{} {
|
||||
return struct {
|
||||
BlockType BlockType `json:"block_type"`
|
||||
BlockID string `json:"block_id"`
|
||||
ModifiedByID string `json:"modified_by_id"`
|
||||
CreateAt string `json:"create_at"`
|
||||
NotifyAt string `json:"notify_at"`
|
||||
}{
|
||||
BlockType: s.BlockType,
|
||||
BlockID: s.BlockID,
|
||||
ModifiedByID: s.ModifiedByID,
|
||||
CreateAt: utils.TimeFromMillis(s.CreateAt).Format(time.StampMilli),
|
||||
NotifyAt: utils.TimeFromMillis(s.NotifyAt).Format(time.StampMilli),
|
||||
}
|
||||
}
|
||||
|
||||
type ErrInvalidNotificationHint struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e ErrInvalidNotificationHint) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
28
server/boards/model/permission.go
Обычный файл
28
server/boards/model/permission.go
Обычный файл
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
mm_model "github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
var (
|
||||
PermissionViewTeam = mm_model.PermissionViewTeam
|
||||
PermissionManageTeam = mm_model.PermissionManageTeam
|
||||
PermissionManageSystem = mm_model.PermissionManageSystem
|
||||
PermissionReadChannel = mm_model.PermissionReadChannel
|
||||
PermissionCreatePost = mm_model.PermissionCreatePost
|
||||
PermissionViewMembers = mm_model.PermissionViewMembers
|
||||
PermissionCreatePublicChannel = mm_model.PermissionCreatePublicChannel
|
||||
PermissionCreatePrivateChannel = mm_model.PermissionCreatePrivateChannel
|
||||
PermissionManageBoardType = &mm_model.Permission{Id: "manage_board_type", Name: "", Description: "", Scope: ""}
|
||||
PermissionDeleteBoard = &mm_model.Permission{Id: "delete_board", Name: "", Description: "", Scope: ""}
|
||||
PermissionViewBoard = &mm_model.Permission{Id: "view_board", Name: "", Description: "", Scope: ""}
|
||||
PermissionManageBoardRoles = &mm_model.Permission{Id: "manage_board_roles", Name: "", Description: "", Scope: ""}
|
||||
PermissionShareBoard = &mm_model.Permission{Id: "share_board", Name: "", Description: "", Scope: ""}
|
||||
PermissionManageBoardCards = &mm_model.Permission{Id: "manage_board_cards", Name: "", Description: "", Scope: ""}
|
||||
PermissionManageBoardProperties = &mm_model.Permission{Id: "manage_board_properties", Name: "", Description: "", Scope: ""}
|
||||
PermissionCommentBoardCards = &mm_model.Permission{Id: "comment_board_cards", Name: "", Description: "", Scope: ""}
|
||||
PermissionDeleteOthersComments = &mm_model.Permission{Id: "delete_others_comments", Name: "", Description: "", Scope: ""}
|
||||
)
|
||||
273
server/boards/model/properties.go
Обычный файл
273
server/boards/model/properties.go
Обычный файл
@@ -0,0 +1,273 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
//go:generate mockgen -copyright_file=../../copyright.txt -destination=mocks/propValueResolverMock.go -package mocks . PropValueResolver
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/boards/utils"
|
||||
)
|
||||
|
||||
var ErrInvalidBoardBlock = errors.New("invalid board block")
|
||||
var ErrInvalidPropSchema = errors.New("invalid property schema")
|
||||
var ErrInvalidProperty = errors.New("invalid property")
|
||||
var ErrInvalidPropertyValue = errors.New("invalid property value")
|
||||
var ErrInvalidPropertyValueType = errors.New("invalid property value type")
|
||||
var ErrInvalidDate = errors.New("invalid date property")
|
||||
|
||||
// PropValueResolver allows PropDef.GetValue to further decode property values, such as
|
||||
// looking up usernames from ids.
|
||||
type PropValueResolver interface {
|
||||
GetUserByID(userID string) (*User, error)
|
||||
}
|
||||
|
||||
// BlockProperties is a map of Prop's keyed by property id.
|
||||
type BlockProperties map[string]BlockProp
|
||||
|
||||
// BlockProp represent a property attached to a block (typically a card).
|
||||
type BlockProp struct {
|
||||
ID string `json:"id"`
|
||||
Index int `json:"index"`
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// PropSchema is a map of PropDef's keyed by property id.
|
||||
type PropSchema map[string]PropDef
|
||||
|
||||
// PropDefOption represents an option within a property definition.
|
||||
type PropDefOption struct {
|
||||
ID string `json:"id"`
|
||||
Index int `json:"index"`
|
||||
Color string `json:"color"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// PropDef represents a property definition as defined in a board's Fields member.
|
||||
type PropDef struct {
|
||||
ID string `json:"id"`
|
||||
Index int `json:"index"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Options map[string]PropDefOption `json:"options"`
|
||||
}
|
||||
|
||||
// GetValue resolves the value of a property if the passed value is an ID for an option,
|
||||
// otherwise returns the original value.
|
||||
func (pd PropDef) GetValue(v interface{}, resolver PropValueResolver) (string, error) {
|
||||
switch pd.Type {
|
||||
case "select":
|
||||
// v is the id of an option
|
||||
id, ok := v.(string)
|
||||
if !ok {
|
||||
return "", ErrInvalidPropertyValueType
|
||||
}
|
||||
opt, ok := pd.Options[id]
|
||||
if !ok {
|
||||
return "", ErrInvalidPropertyValue
|
||||
}
|
||||
return strings.ToUpper(opt.Value), nil
|
||||
|
||||
case "date":
|
||||
// v is a JSON string
|
||||
date, ok := v.(string)
|
||||
if !ok {
|
||||
return "", ErrInvalidPropertyValueType
|
||||
}
|
||||
return pd.ParseDate(date)
|
||||
|
||||
case "person":
|
||||
// v is a userid
|
||||
userID, ok := v.(string)
|
||||
if !ok {
|
||||
return "", ErrInvalidPropertyValueType
|
||||
}
|
||||
if resolver != nil {
|
||||
user, err := resolver.GetUserByID(userID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if user == nil {
|
||||
return userID, nil
|
||||
}
|
||||
return user.Username, nil
|
||||
}
|
||||
return userID, nil
|
||||
|
||||
case "multiPerson":
|
||||
// v is a slice of user IDs
|
||||
userIDs, ok := v.([]interface{})
|
||||
if !ok {
|
||||
return "", fmt.Errorf("multiPerson property type: %w", ErrInvalidPropertyValueType)
|
||||
}
|
||||
if resolver != nil {
|
||||
usernames := make([]string, len(userIDs))
|
||||
|
||||
for i, userIDInterface := range userIDs {
|
||||
userID := userIDInterface.(string)
|
||||
|
||||
user, err := resolver.GetUserByID(userID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if user == nil {
|
||||
usernames[i] = userID
|
||||
} else {
|
||||
usernames[i] = user.Username
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(usernames, ", "), nil
|
||||
}
|
||||
|
||||
case "multiSelect":
|
||||
// v is a slice of strings containing option ids
|
||||
ms, ok := v.([]interface{})
|
||||
if !ok {
|
||||
return "", ErrInvalidPropertyValueType
|
||||
}
|
||||
var sb strings.Builder
|
||||
prefix := ""
|
||||
for _, optid := range ms {
|
||||
id, ok := optid.(string)
|
||||
if !ok {
|
||||
return "", ErrInvalidPropertyValueType
|
||||
}
|
||||
opt, ok := pd.Options[id]
|
||||
if !ok {
|
||||
return "", ErrInvalidPropertyValue
|
||||
}
|
||||
sb.WriteString(prefix)
|
||||
prefix = ", "
|
||||
sb.WriteString(strings.ToUpper(opt.Value))
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
return fmt.Sprintf("%v", v), nil
|
||||
}
|
||||
|
||||
func (pd PropDef) ParseDate(s string) (string, error) {
|
||||
// s is a JSON snippet of the form: {"from":1642161600000, "to":1642161600000} in milliseconds UTC
|
||||
// The UI does not yet support date ranges.
|
||||
var m map[string]int64
|
||||
if err := json.Unmarshal([]byte(s), &m); err != nil {
|
||||
return s, err
|
||||
}
|
||||
tsFrom, ok := m["from"]
|
||||
if !ok {
|
||||
return s, ErrInvalidDate
|
||||
}
|
||||
date := utils.GetTimeForMillis(tsFrom).Format("January 02, 2006")
|
||||
tsTo, ok := m["to"]
|
||||
if ok {
|
||||
date += " -> " + utils.GetTimeForMillis(tsTo).Format("January 02, 2006")
|
||||
}
|
||||
return date, nil
|
||||
}
|
||||
|
||||
// ParsePropertySchema parses a board block's `Fields` to extract the properties
|
||||
// schema for all cards within the board.
|
||||
// The result is provided as a map for quick lookup, and the original order is
|
||||
// preserved via the `Index` field.
|
||||
func ParsePropertySchema(board *Board) (PropSchema, error) {
|
||||
schema := make(map[string]PropDef)
|
||||
|
||||
for i, prop := range board.CardProperties {
|
||||
pd := PropDef{
|
||||
ID: getMapString("id", prop),
|
||||
Index: i,
|
||||
Name: getMapString("name", prop),
|
||||
Type: getMapString("type", prop),
|
||||
Options: make(map[string]PropDefOption),
|
||||
}
|
||||
optsIface, ok := prop["options"]
|
||||
if ok {
|
||||
opts, ok := optsIface.([]interface{})
|
||||
if !ok {
|
||||
return nil, ErrInvalidPropSchema
|
||||
}
|
||||
for j, propOptIface := range opts {
|
||||
propOpt, ok := propOptIface.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, ErrInvalidPropSchema
|
||||
}
|
||||
po := PropDefOption{
|
||||
ID: getMapString("id", propOpt),
|
||||
Index: j,
|
||||
Value: getMapString("value", propOpt),
|
||||
Color: getMapString("color", propOpt),
|
||||
}
|
||||
pd.Options[po.ID] = po
|
||||
}
|
||||
}
|
||||
schema[pd.ID] = pd
|
||||
}
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
func getMapString(key string, m map[string]interface{}) string {
|
||||
iface, ok := m[key]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
s, ok := iface.(string)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ParseProperties parses a block's `Fields` to extract the properties. Properties typically exist on
|
||||
// card blocks. A resolver can optionally be provided to fetch usernames for `person` prop type.
|
||||
func ParseProperties(block *Block, schema PropSchema, resolver PropValueResolver) (BlockProperties, error) {
|
||||
props := make(map[string]BlockProp)
|
||||
|
||||
if block == nil {
|
||||
return props, nil
|
||||
}
|
||||
|
||||
// `properties` contains a map (untyped at this point).
|
||||
propsIface, ok := block.Fields["properties"]
|
||||
if !ok {
|
||||
return props, nil // this is expected for blocks that don't have any properties.
|
||||
}
|
||||
|
||||
blockProps, ok := propsIface.(map[string]interface{})
|
||||
if !ok {
|
||||
return props, fmt.Errorf("`properties` field wrong type: %w", ErrInvalidProperty)
|
||||
}
|
||||
|
||||
if len(blockProps) == 0 {
|
||||
return props, nil
|
||||
}
|
||||
|
||||
for k, v := range blockProps {
|
||||
s := fmt.Sprintf("%v", v)
|
||||
|
||||
prop := BlockProp{
|
||||
ID: k,
|
||||
Name: k,
|
||||
Value: s,
|
||||
}
|
||||
|
||||
def, ok := schema[k]
|
||||
if ok {
|
||||
val, err := def.GetValue(v, resolver)
|
||||
if err != nil {
|
||||
return props, fmt.Errorf("could not parse property value (%s): %w", fmt.Sprintf("%v", v), err)
|
||||
}
|
||||
prop.Name = def.Name
|
||||
prop.Value = val
|
||||
prop.Index = def.Index
|
||||
}
|
||||
props[k] = prop
|
||||
}
|
||||
return props, nil
|
||||
}
|
||||
164
server/boards/model/properties_test.go
Обычный файл
164
server/boards/model/properties_test.go
Обычный файл
@@ -0,0 +1,164 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/boards/utils"
|
||||
)
|
||||
|
||||
type MockResolver struct{}
|
||||
|
||||
func (r MockResolver) GetUserByID(userID string) (*User, error) {
|
||||
if userID == "user_id_1" {
|
||||
return &User{
|
||||
ID: "user_id_1",
|
||||
Username: "username_1",
|
||||
}, nil
|
||||
} else if userID == "user_id_2" {
|
||||
return &User{
|
||||
ID: "user_id_2",
|
||||
Username: "username_2",
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func Test_parsePropertySchema(t *testing.T) {
|
||||
board := &Board{
|
||||
ID: utils.NewID(utils.IDTypeBoard),
|
||||
Title: "Test Board",
|
||||
TeamID: utils.NewID(utils.IDTypeTeam),
|
||||
}
|
||||
|
||||
err := json.Unmarshal([]byte(cardPropertiesExample), &board.CardProperties)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("parse schema", func(t *testing.T) {
|
||||
schema, err := ParsePropertySchema(board)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, schema, 6)
|
||||
|
||||
prop, ok := schema["7c212e78-9345-4c60-81b5-0b0e37ce463f"]
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, "select", prop.Type)
|
||||
assert.Equal(t, "Type", prop.Name)
|
||||
assert.Len(t, prop.Options, 3)
|
||||
|
||||
prop, ok = schema["a8spou7if43eo1rqzb9qeq488so"]
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, "date", prop.Type)
|
||||
assert.Equal(t, "MyDate", prop.Name)
|
||||
assert.Empty(t, prop.Options)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_GetValue(t *testing.T) {
|
||||
resolver := MockResolver{}
|
||||
|
||||
propDef := PropDef{
|
||||
Type: "multiPerson",
|
||||
}
|
||||
|
||||
value, err := propDef.GetValue([]interface{}{"user_id_1", "user_id_2"}, resolver)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "username_1, username_2", value)
|
||||
|
||||
// trying with only user
|
||||
value, err = propDef.GetValue([]interface{}{"user_id_1"}, resolver)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "username_1", value)
|
||||
|
||||
// trying with unknown user
|
||||
value, err = propDef.GetValue([]interface{}{"user_id_1", "user_id_unknown"}, resolver)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "username_1, user_id_unknown", value)
|
||||
|
||||
// trying with multiple unknown users
|
||||
value, err = propDef.GetValue([]interface{}{"michael_scott", "jim_halpert"}, resolver)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "michael_scott, jim_halpert", value)
|
||||
}
|
||||
|
||||
const (
|
||||
cardPropertiesExample = `[
|
||||
{
|
||||
"id":"7c212e78-9345-4c60-81b5-0b0e37ce463f",
|
||||
"name":"Type",
|
||||
"options":[
|
||||
{
|
||||
"color":"propColorYellow",
|
||||
"id":"31da50ca-f1a9-4d21-8636-17dc387c1a23",
|
||||
"value":"Ad Hoc"
|
||||
},
|
||||
{
|
||||
"color":"propColorBlue",
|
||||
"id":"def6317c-ec11-410d-8a6b-ea461320f392",
|
||||
"value":"Standup"
|
||||
},
|
||||
{
|
||||
"color":"propColorPurple",
|
||||
"id":"700f83f8-6a41-46cd-87e2-53e0d0b12cc7",
|
||||
"value":"Weekly Sync"
|
||||
}
|
||||
],
|
||||
"type":"select"
|
||||
},
|
||||
{
|
||||
"id":"13d2394a-eb5e-4f22-8c22-6515ec41c4a4",
|
||||
"name":"Summary",
|
||||
"options":[],
|
||||
"type":"text"
|
||||
},
|
||||
{
|
||||
"id":"566cd860-bbae-4bcd-86a8-7df4db2ba15c",
|
||||
"name":"Color",
|
||||
"options":[
|
||||
{
|
||||
"color":"propColorDefault",
|
||||
"id":"efb0c783-f9ea-4938-8b86-9cf425296cd1",
|
||||
"value":"RED"
|
||||
},
|
||||
{
|
||||
"color":"propColorDefault",
|
||||
"id":"2f100e13-e7c4-4ab6-81c9-a17baf98b311",
|
||||
"value":"GREEN"
|
||||
},
|
||||
{
|
||||
"color":"propColorDefault",
|
||||
"id":"a05bdc80-bd90-45b0-8805-a7e77a4884be",
|
||||
"value":"BLUE"
|
||||
}
|
||||
],
|
||||
"type":"select"
|
||||
},
|
||||
{
|
||||
"id":"aawg1s8rxq8o1bbksxmsmpsdd3r",
|
||||
"name":"MyTextProp",
|
||||
"options":[],
|
||||
"type":"text"
|
||||
},
|
||||
{
|
||||
"id":"awdwfigo4kse63bdfp56mzhip6w",
|
||||
"name":"MyCheckBox",
|
||||
"options":[],
|
||||
"type":"checkbox"
|
||||
},
|
||||
{
|
||||
"id":"a8spou7if43eo1rqzb9qeq488so",
|
||||
"name":"MyDate",
|
||||
"options":[],
|
||||
"type":"date"
|
||||
}
|
||||
]`
|
||||
)
|
||||
95
server/boards/model/services_api.go
Обычный файл
95
server/boards/model/services_api.go
Обычный файл
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
//go:generate mockgen --build_flags= -copyright_file=../../copyright.txt -destination=mocks/mockservicesapi.go -package mocks . ServicesAPI
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
mm_model "github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog"
|
||||
)
|
||||
|
||||
const (
|
||||
botUsername = "boards"
|
||||
botDisplayname = "Boards"
|
||||
botDescription = "Created by Boards plugin."
|
||||
)
|
||||
|
||||
var FocalboardBot = &mm_model.Bot{
|
||||
Username: botUsername,
|
||||
DisplayName: botDisplayname,
|
||||
Description: botDescription,
|
||||
OwnerId: SystemUserID,
|
||||
}
|
||||
|
||||
type ServicesAPI interface {
|
||||
// Channels service
|
||||
GetDirectChannel(userID1, userID2 string) (*mm_model.Channel, error)
|
||||
GetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error)
|
||||
GetChannelByID(channelID string) (*mm_model.Channel, error)
|
||||
GetChannelMember(channelID string, userID string) (*mm_model.ChannelMember, error)
|
||||
GetChannelsForTeamForUser(teamID string, userID string, includeDeleted bool) (mm_model.ChannelList, error)
|
||||
|
||||
// Post service
|
||||
CreatePost(post *mm_model.Post) (*mm_model.Post, error)
|
||||
|
||||
// User service
|
||||
GetUserByID(userID string) (*mm_model.User, error)
|
||||
GetUserByUsername(name string) (*mm_model.User, error)
|
||||
GetUserByEmail(email string) (*mm_model.User, error)
|
||||
UpdateUser(user *mm_model.User) (*mm_model.User, error)
|
||||
GetUsersFromProfiles(options *mm_model.UserGetOptions) ([]*mm_model.User, error)
|
||||
|
||||
// Team service
|
||||
GetTeamMember(teamID string, userID string) (*mm_model.TeamMember, error)
|
||||
CreateMember(teamID string, userID string) (*mm_model.TeamMember, error)
|
||||
|
||||
// Permissions service
|
||||
HasPermissionTo(userID string, permission *mm_model.Permission) bool
|
||||
HasPermissionToTeam(userID, teamID string, permission *mm_model.Permission) bool
|
||||
HasPermissionToChannel(askingUserID string, channelID string, permission *mm_model.Permission) bool
|
||||
|
||||
// Bot service
|
||||
EnsureBot(bot *mm_model.Bot) (string, error)
|
||||
|
||||
// License service
|
||||
GetLicense() *mm_model.License
|
||||
|
||||
// FileInfoStore service
|
||||
GetFileInfo(fileID string) (*mm_model.FileInfo, error)
|
||||
|
||||
// Cluster service
|
||||
PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *mm_model.WebsocketBroadcast)
|
||||
PublishPluginClusterEvent(ev mm_model.PluginClusterEvent, opts mm_model.PluginClusterEventSendOptions) error
|
||||
|
||||
// Cloud service
|
||||
GetCloudLimits() (*mm_model.ProductLimits, error)
|
||||
|
||||
// Config service
|
||||
GetConfig() *mm_model.Config
|
||||
|
||||
// Logger service
|
||||
GetLogger() mlog.LoggerIFace
|
||||
|
||||
// KVStore service
|
||||
KVSetWithOptions(key string, value []byte, options mm_model.PluginKVSetOptions) (bool, error)
|
||||
|
||||
// Store service
|
||||
GetMasterDB() (*sql.DB, error)
|
||||
|
||||
// System service
|
||||
GetDiagnosticID() string
|
||||
|
||||
// Router service
|
||||
RegisterRouter(sub *mux.Router)
|
||||
|
||||
// Preferences services
|
||||
GetPreferencesForUser(userID string) (mm_model.Preferences, error)
|
||||
UpdatePreferencesForUser(userID string, preferences mm_model.Preferences) error
|
||||
DeletePreferencesForUser(userID string, preferences mm_model.Preferences) error
|
||||
}
|
||||
39
server/boards/model/sharing.go
Обычный файл
39
server/boards/model/sharing.go
Обычный файл
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Sharing is sharing information for a root block
|
||||
// swagger:model
|
||||
type Sharing struct {
|
||||
// ID of the root block
|
||||
// required: true
|
||||
ID string `json:"id"`
|
||||
|
||||
// Is sharing enabled
|
||||
// required: true
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
// Access token
|
||||
// required: true
|
||||
Token string `json:"token"`
|
||||
|
||||
// ID of the user who last modified this
|
||||
// required: true
|
||||
ModifiedBy string `json:"modifiedBy"`
|
||||
|
||||
// Updated time in miliseconds since the current epoch
|
||||
// required: true
|
||||
UpdateAt int64 `json:"update_at,omitempty"`
|
||||
}
|
||||
|
||||
func SharingFromJSON(data io.Reader) Sharing {
|
||||
var sharing Sharing
|
||||
_ = json.NewDecoder(data).Decode(&sharing)
|
||||
return sharing
|
||||
}
|
||||
106
server/boards/model/subscription.go
Обычный файл
106
server/boards/model/subscription.go
Обычный файл
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
SubTypeUser = "user"
|
||||
SubTypeChannel = "channel"
|
||||
)
|
||||
|
||||
type SubscriberType string
|
||||
|
||||
func (st SubscriberType) IsValid() bool {
|
||||
switch st {
|
||||
case SubTypeUser, SubTypeChannel:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Subscription is a subscription to a board, card, etc, for a user or channel.
|
||||
// swagger:model
|
||||
type Subscription struct {
|
||||
// BlockType is the block type of the entity (e.g. board, card) subscribed to
|
||||
// required: true
|
||||
BlockType BlockType `json:"blockType"`
|
||||
|
||||
// BlockID is id of the entity being subscribed to
|
||||
// required: true
|
||||
BlockID string `json:"blockId"`
|
||||
|
||||
// SubscriberType is the type of the entity (e.g. user, channel) that is subscribing
|
||||
// required: true
|
||||
SubscriberType SubscriberType `json:"subscriberType"`
|
||||
|
||||
// SubscriberID is the id of the entity that is subscribing
|
||||
// required: true
|
||||
SubscriberID string `json:"subscriberId"`
|
||||
|
||||
// NotifiedAt is the timestamp of the last notification sent for this subscription
|
||||
// required: true
|
||||
NotifiedAt int64 `json:"notifiedAt,omitempty"`
|
||||
|
||||
// CreatedAt is the timestamp this subscription was created in miliseconds since the current epoch
|
||||
// required: true
|
||||
CreateAt int64 `json:"createAt"`
|
||||
|
||||
// DeleteAt is the timestamp this subscription was deleted in miliseconds since the current epoch, or zero if not deleted
|
||||
// required: true
|
||||
DeleteAt int64 `json:"deleteAt"`
|
||||
}
|
||||
|
||||
func (s *Subscription) IsValid() error {
|
||||
if s == nil {
|
||||
return ErrInvalidSubscription{"cannot be nil"}
|
||||
}
|
||||
if s.BlockID == "" {
|
||||
return ErrInvalidSubscription{"missing block id"}
|
||||
}
|
||||
if s.BlockType == "" {
|
||||
return ErrInvalidSubscription{"missing block type"}
|
||||
}
|
||||
if s.SubscriberID == "" {
|
||||
return ErrInvalidSubscription{"missing subscriber id"}
|
||||
}
|
||||
if !s.SubscriberType.IsValid() {
|
||||
return ErrInvalidSubscription{"invalid subscriber type"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SubscriptionFromJSON(data io.Reader) (*Subscription, error) {
|
||||
var subscription Subscription
|
||||
if err := json.NewDecoder(data).Decode(&subscription); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &subscription, nil
|
||||
}
|
||||
|
||||
type ErrInvalidSubscription struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e ErrInvalidSubscription) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
// Subscriber is an entity (e.g. user, channel) that can subscribe to events from boards, cards, etc
|
||||
// swagger:model
|
||||
type Subscriber struct {
|
||||
// SubscriberType is the type of the entity (e.g. user, channel) that is subscribing
|
||||
// required: true
|
||||
SubscriberType SubscriberType `json:"subscriber_type"`
|
||||
|
||||
// SubscriberID is the id of the entity that is subscribing
|
||||
// required: true
|
||||
SubscriberID string `json:"subscriber_id"`
|
||||
|
||||
// NotifiedAt is the timestamp this subscriber was last notified
|
||||
NotifiedAt int64 `json:"notified_at"`
|
||||
}
|
||||
49
server/boards/model/team.go
Обычный файл
49
server/boards/model/team.go
Обычный файл
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Team is information global to a team
|
||||
// swagger:model
|
||||
type Team struct {
|
||||
// ID of the team
|
||||
// required: true
|
||||
ID string `json:"id"`
|
||||
|
||||
// Title of the team
|
||||
// required: false
|
||||
Title string `json:"title"`
|
||||
|
||||
// Token required to register new users
|
||||
// required: true
|
||||
SignupToken string `json:"signupToken"`
|
||||
|
||||
// Team settings
|
||||
// required: false
|
||||
Settings map[string]interface{} `json:"settings"`
|
||||
|
||||
// ID of user who last modified this
|
||||
// required: true
|
||||
ModifiedBy string `json:"modifiedBy"`
|
||||
|
||||
// Updated time in miliseconds since the current epoch
|
||||
// required: true
|
||||
UpdateAt int64 `json:"updateAt"`
|
||||
}
|
||||
|
||||
func TeamFromJSON(data io.Reader) *Team {
|
||||
var team *Team
|
||||
_ = json.NewDecoder(data).Decode(&team)
|
||||
return team
|
||||
}
|
||||
|
||||
func TeamsFromJSON(data io.Reader) []*Team {
|
||||
var teams []*Team
|
||||
_ = json.NewDecoder(data).Decode(&teams)
|
||||
return teams
|
||||
}
|
||||
106
server/boards/model/user.go
Обычный файл
106
server/boards/model/user.go
Обычный файл
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
SingleUser = "single-user"
|
||||
GlobalTeamID = "0"
|
||||
SystemUserID = "system"
|
||||
PreferencesCategoryFocalboard = "focalboard"
|
||||
)
|
||||
|
||||
// User is a user
|
||||
// swagger:model
|
||||
type User struct {
|
||||
// The user ID
|
||||
// required: true
|
||||
ID string `json:"id"`
|
||||
|
||||
// The user name
|
||||
// required: true
|
||||
Username string `json:"username"`
|
||||
|
||||
// The user's email
|
||||
// required: true
|
||||
Email string `json:"-"`
|
||||
|
||||
// The user's nickname
|
||||
Nickname string `json:"nickname"`
|
||||
// The user's first name
|
||||
FirstName string `json:"firstname"`
|
||||
// The user's last name
|
||||
LastName string `json:"lastname"`
|
||||
|
||||
// swagger:ignore
|
||||
Password string `json:"-"`
|
||||
|
||||
// swagger:ignore
|
||||
MfaSecret string `json:"-"`
|
||||
|
||||
// swagger:ignore
|
||||
AuthService string `json:"-"`
|
||||
|
||||
// swagger:ignore
|
||||
AuthData string `json:"-"`
|
||||
|
||||
// Created time in miliseconds since the current epoch
|
||||
// required: true
|
||||
CreateAt int64 `json:"create_at,omitempty"`
|
||||
|
||||
// Updated time in miliseconds since the current epoch
|
||||
// required: true
|
||||
UpdateAt int64 `json:"update_at,omitempty"`
|
||||
|
||||
// Deleted time in miliseconds since the current epoch, set to indicate user is deleted
|
||||
// required: true
|
||||
DeleteAt int64 `json:"delete_at"`
|
||||
|
||||
// If the user is a bot or not
|
||||
// required: true
|
||||
IsBot bool `json:"is_bot"`
|
||||
|
||||
// If the user is a guest or not
|
||||
// required: true
|
||||
IsGuest bool `json:"is_guest"`
|
||||
|
||||
// Special Permissions the user may have
|
||||
Permissions []string `json:"permissions,omitempty"`
|
||||
|
||||
Roles string `json:"roles"`
|
||||
}
|
||||
|
||||
// UserPreferencesPatch is a user property patch
|
||||
// swagger:model
|
||||
type UserPreferencesPatch struct {
|
||||
// The user preference updated fields
|
||||
// required: false
|
||||
UpdatedFields map[string]string `json:"updatedFields"`
|
||||
|
||||
// The user preference removed fields
|
||||
// required: false
|
||||
DeletedFields []string `json:"deletedFields"`
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
ID string `json:"id"`
|
||||
Token string `json:"token"`
|
||||
UserID string `json:"user_id"`
|
||||
AuthService string `json:"authService"`
|
||||
Props map[string]interface{} `json:"props"`
|
||||
CreateAt int64 `json:"create_at,omitempty"`
|
||||
UpdateAt int64 `json:"update_at,omitempty"`
|
||||
}
|
||||
|
||||
func UserFromJSON(data io.Reader) (*User, error) {
|
||||
var user User
|
||||
if err := json.NewDecoder(data).Decode(&user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
25
server/boards/model/util.go
Обычный файл
25
server/boards/model/util.go
Обычный файл
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
mm_model "github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
// GetMillis is a convenience method to get milliseconds since epoch.
|
||||
func GetMillis() int64 {
|
||||
return mm_model.GetMillis()
|
||||
}
|
||||
|
||||
// GetMillisForTime is a convenience method to get milliseconds since epoch for provided Time.
|
||||
func GetMillisForTime(thisTime time.Time) int64 {
|
||||
return mm_model.GetMillisForTime(thisTime)
|
||||
}
|
||||
|
||||
// GetTimeForMillis is a convenience method to get time.Time for milliseconds since epoch.
|
||||
func GetTimeForMillis(millis int64) time.Time {
|
||||
return mm_model.GetTimeForMillis(millis)
|
||||
}
|
||||
67
server/boards/model/version.go
Обычный файл
67
server/boards/model/version.go
Обычный файл
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog"
|
||||
)
|
||||
|
||||
// This is a list of all the current versions including any patches.
|
||||
// It should be maintained in chronological order with most current
|
||||
// release at the front of the list.
|
||||
var versions = []string{
|
||||
"7.9.0",
|
||||
"7.8.0",
|
||||
"7.7.0",
|
||||
"7.6.0",
|
||||
"7.5.0",
|
||||
"7.4.0",
|
||||
"7.3.0",
|
||||
"7.2.0",
|
||||
"7.0.0",
|
||||
"0.16.0",
|
||||
"0.15.0",
|
||||
"0.14.0",
|
||||
"0.12.0",
|
||||
"0.11.0",
|
||||
"0.10.0",
|
||||
"0.9.4",
|
||||
"0.9.3",
|
||||
"0.9.2",
|
||||
"0.9.1",
|
||||
"0.9.0",
|
||||
"0.8.2",
|
||||
"0.8.1",
|
||||
"0.8.0",
|
||||
"0.7.3",
|
||||
"0.7.2",
|
||||
"0.7.1",
|
||||
"0.7.0",
|
||||
"0.6.7",
|
||||
"0.6.6",
|
||||
"0.6.5",
|
||||
"0.6.2",
|
||||
"0.6.1",
|
||||
"0.6.0",
|
||||
"0.5.0",
|
||||
}
|
||||
|
||||
var (
|
||||
CurrentVersion = versions[0]
|
||||
BuildNumber string
|
||||
BuildDate string
|
||||
BuildHash string
|
||||
Edition string
|
||||
)
|
||||
|
||||
// LogServerInfo logs information about the server instance.
|
||||
func LogServerInfo(logger mlog.LoggerIFace) {
|
||||
logger.Info("Focalboard server",
|
||||
mlog.String("version", CurrentVersion),
|
||||
mlog.String("edition", Edition),
|
||||
mlog.String("build_number", BuildNumber),
|
||||
mlog.String("build_date", BuildDate),
|
||||
mlog.String("build_hash", BuildHash),
|
||||
)
|
||||
}
|
||||
Ссылка в новой задаче
Block a user