1
0
mirror of https://github.com/mattermost/focalboard.git synced 2024-12-24 13:43:12 +02:00

Merge branch 'main' into MM48320-refactor-channel-with-associated-board-to-plugable

This commit is contained in:
Pablo Andrés Vélez Vidal 2023-01-25 10:37:26 +01:00
commit 66c255e2cd
263 changed files with 12521 additions and 5290 deletions

View File

@ -48,7 +48,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18.1
go-version: 1.19.5
- name: "Test server: ${{matrix['db']}}"
run: cd focalboard; make server-test-${{matrix['db']}}
@ -83,7 +83,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18.1
go-version: 1.19.5
- name: Setup Node
uses: actions/setup-node@v3
@ -137,7 +137,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18.1
go-version: 1.19.5
- name: "Test server (minimum): ${{matrix['db']}}"
run: cd focalboard; make server-test-mini-${{matrix['db']}}
@ -174,7 +174,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18.1
go-version: 1.19.5
- name: "Test server (minimum): ${{matrix['db']}}"
run: cd focalboard; make server-test-mini-${{matrix['db']}}

View File

@ -54,7 +54,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18.1
go-version: 1.19.5
- name: Setup Node
uses: actions/setup-node@v3
@ -129,7 +129,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18.1
go-version: 1.19.5
- name: List Xcode versions
run: ls -n /Applications/ | grep Xcode*
@ -190,7 +190,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18.1
go-version: 1.19.5
- name: Setup NuGet
uses: nuget/setup-nuget@v1
@ -258,7 +258,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18.1
go-version: 1.19.5
- name: Set up Node
uses: actions/setup-node@v3

View File

@ -30,7 +30,7 @@ jobs:
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.18.1
go-version: 1.19.5
- uses: actions/checkout@v3
with:
path: "focalboard"
@ -50,7 +50,7 @@ jobs:
path: "mattermost-server"
ref : "master"
- name: set up golangci-lint
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.46.2
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.50.1
- name: lint
run: |
cd focalboard

View File

@ -50,7 +50,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18.1
go-version: 1.19.5
- name: Setup Node
uses: actions/setup-node@v3
@ -126,7 +126,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18.1
go-version: 1.19.5
- name: List Xcode versions
run: ls -n /Applications/ | grep Xcode*
@ -188,7 +188,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18.1
go-version: 1.19.5
- name: Setup NuGet
uses: nuget/setup-nuget@v1
@ -257,7 +257,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18.1
go-version: 1.19.5
- name: Set up Node
uses: actions/setup-node@v3

View File

@ -4,7 +4,7 @@ stages:
variables:
BUILD: "yes"
IMAGE_BUILDER: $CI_REGISTRY/mattermost/ci/images/builder:go-1.18.1-node-16.15.0-1
IMAGE_BUILDER: $CI_REGISTRY/mattermost/ci/images/builder:go-1.19.5-node-16.15.0-1
include:
- project: mattermost/ci/focalboard

View File

@ -51,7 +51,7 @@ func makeGoWork(ci bool) string {
var b strings.Builder
b.WriteString("go 1.18\n\n")
b.WriteString("go 1.19\n\n")
b.WriteString("use ./server\n")
for repo, envVarName := range repos {

View File

@ -1,6 +1,6 @@
module github.com/mattermost/focalboard/linux
go 1.18
go 1.19
replace github.com/mattermost/focalboard/server => ../server

View File

@ -1,6 +1,6 @@
module github.com/mattermost/mattermost-plugin-starter-template/build
go 1.18
go 1.19
require (
github.com/go-git/go-git/v5 v5.1.0

View File

@ -1,6 +1,6 @@
module github.com/mattermost/focalboard/mattermost-plugin
go 1.18
go 1.19
require (
github.com/golang/mock v1.6.0

View File

@ -49,6 +49,11 @@ func (a *serviceAPIAdapter) GetDirectChannel(userID1, userID2 string) (*mm_model
return channel, normalizeAppErr(appErr)
}
func (a *serviceAPIAdapter) GetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error) {
channel, appErr := a.api.channelService.GetDirectChannelOrCreate(userID1, userID2)
return channel, normalizeAppErr(appErr)
}
func (a *serviceAPIAdapter) GetChannelByID(channelID string) (*mm_model.Channel, error) {
channel, appErr := a.api.channelService.GetChannelByID(channelID)
return channel, normalizeAppErr(appErr)

View File

@ -54,6 +54,12 @@ func (a *pluginAPIAdapter) GetDirectChannel(userID1, userID2 string) (*mm_model.
return channel, normalizeAppErr(appErr)
}
func (a *pluginAPIAdapter) GetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error) {
// plugin API's GetDirectChannel will create channel if it does not exist.
channel, appErr := a.api.GetDirectChannel(userID1, userID2)
return channel, normalizeAppErr(appErr)
}
func (a *pluginAPIAdapter) GetChannelByID(channelID string) (*mm_model.Channel, error) {
channel, appErr := a.api.GetChannel(channelID)
return channel, normalizeAppErr(appErr)

View File

@ -80,8 +80,12 @@ func createBoardsConfig(mmconfig mm_model.Config, baseURL string, serverID strin
showFullName = *mmconfig.PrivacySettings.ShowFullName
}
serverRoot := baseURL + "/plugins/focalboard"
if mmconfig.FeatureFlags.BoardsProduct {
serverRoot = baseURL + "/boards"
}
return &config.Configuration{
ServerRoot: baseURL + "/plugins/focalboard",
ServerRoot: serverRoot,
Port: -1,
DBType: *mmconfig.SqlSettings.DriverName,
DBConfigString: *mmconfig.SqlSettings.DataSource,

View File

@ -80,6 +80,9 @@ func (b *BoardsApp) OnConfigurationChange() error {
if mmconfig.PluginSettings.Plugins[PluginName][SharedBoardsName] == true {
enableShareBoards = true
}
if mmconfig.ProductSettings.EnablePublicSharedBoards != nil {
enableShareBoards = *mmconfig.ProductSettings.EnablePublicSharedBoards
}
configuration := &configuration{
EnablePublicSharedBoards: enableShareBoards,
}

View File

@ -45,8 +45,7 @@ const manifestStr = `
"type": "bool",
"help_text": "This allows board editors to share boards that can be accessed by anyone with the link.",
"placeholder": "",
"default": false,
"hosting": ""
"default": false
}
]
}

View File

@ -6,7 +6,7 @@ exports[`components/boardSelector escape button should unmount the component 1`]
class="focalboard-body"
>
<div
class="Dialog dialog-back BoardSelector"
class="Dialog dialog-back BoardSelector size--medium"
>
<div
class="backdrop"
@ -111,7 +111,7 @@ exports[`components/boardSelector renders with no results 1`] = `
class="focalboard-body"
>
<div
class="Dialog dialog-back BoardSelector"
class="Dialog dialog-back BoardSelector size--medium"
>
<div
class="backdrop"
@ -217,7 +217,7 @@ exports[`components/boardSelector renders with some results 1`] = `
class="focalboard-body"
>
<div
class="Dialog dialog-back BoardSelector"
class="Dialog dialog-back BoardSelector size--medium"
>
<div
class="backdrop"
@ -421,7 +421,7 @@ exports[`components/boardSelector renders without start searching 1`] = `
class="focalboard-body"
>
<div
class="Dialog dialog-back BoardSelector"
class="Dialog dialog-back BoardSelector size--medium"
>
<div
class="backdrop"

View File

@ -172,6 +172,7 @@ if (TARGET_IS_PRODUCT) {
config.output = {
path: path.join(__dirname, '/dist'),
chunkFilename: '[name].[contenthash].js',
};
} else {
config.resolve.alias['react-intl'] = path.resolve(__dirname, '../../webapp/node_modules/react-intl/');

View File

@ -27,10 +27,7 @@ linters:
enable:
- gofmt
- goimports
- deadcode
- ineffassign
- structcheck
- varcheck
- unparam
- errcheck
- govet

View File

@ -220,7 +220,7 @@ func stringResponse(w http.ResponseWriter, message string) {
_, _ = fmt.Fprint(w, message)
}
func jsonStringResponse(w http.ResponseWriter, code int, message string) {
func jsonStringResponse(w http.ResponseWriter, code int, message string) { //nolint:unparam
setResponseHeader(w, "Content-Type", "application/json")
w.WriteHeader(code)
fmt.Fprint(w, message)
@ -232,7 +232,7 @@ func jsonBytesResponse(w http.ResponseWriter, code int, json []byte) {
_, _ = w.Write(json)
}
func setResponseHeader(w http.ResponseWriter, key string, value string) {
func setResponseHeader(w http.ResponseWriter, key string, value string) { //nolint:unparam
header := w.Header()
if header == nil {
return

View File

@ -8,7 +8,7 @@ import (
)
// makeAuditRecord creates an audit record pre-populated with data from the request.
func (a *API) makeAuditRecord(r *http.Request, event string, initialStatus string) *audit.Record {
func (a *API) makeAuditRecord(r *http.Request, event string, initialStatus string) *audit.Record { //nolint:unparam
ctx := r.Context()
var sessionID string
var userID string

View File

@ -383,12 +383,13 @@ func (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request)
authService := session.AuthService
if authService != a.authService {
a.logger.Error(`Session authService mismatch`,
msg := `Session authService mismatch`
a.logger.Error(msg,
mlog.String("sessionID", session.ID),
mlog.String("want", a.authService),
mlog.String("got", authService),
)
a.errorResponse(w, r, model.NewErrUnauthorized(err.Error()))
a.errorResponse(w, r, model.NewErrUnauthorized(msg))
return
}

View File

@ -20,6 +20,8 @@ func (a *API) registerCategoriesRoutes(r *mux.Router) {
r.HandleFunc("/teams/{teamID}/categories", a.sessionRequired(a.handleGetUserCategoryBoards)).Methods(http.MethodGet)
r.HandleFunc("/teams/{teamID}/categories/{categoryID}/boards/reorder", a.sessionRequired(a.handleReorderCategoryBoards)).Methods(http.MethodPut)
r.HandleFunc("/teams/{teamID}/categories/{categoryID}/boards/{boardID}", a.sessionRequired(a.handleUpdateCategoryBoard)).Methods(http.MethodPost)
r.HandleFunc("/teams/{teamID}/categories/{categoryID}/boards/{boardID}/hide", a.sessionRequired(a.handleHideBoard)).Methods(http.MethodPut)
r.HandleFunc("/teams/{teamID}/categories/{categoryID}/boards/{boardID}/unhide", a.sessionRequired(a.handleUnhideBoard)).Methods(http.MethodPut)
}
func (a *API) handleCreateCategory(w http.ResponseWriter, r *http.Request) {
@ -355,7 +357,7 @@ func (a *API) handleUpdateCategoryBoard(w http.ResponseWriter, r *http.Request)
userID := session.UserID
// TODO: Check the category and the team matches
err := a.app.AddUpdateUserCategoryBoard(teamID, userID, map[string]string{boardID: categoryID})
err := a.app.AddUpdateUserCategoryBoard(teamID, userID, categoryID, []string{boardID})
if err != nil {
a.errorResponse(w, r, err)
return
@ -521,3 +523,125 @@ func (a *API) handleReorderCategoryBoards(w http.ResponseWriter, r *http.Request
jsonBytesResponse(w, http.StatusOK, data)
auditRec.Success()
}
func (a *API) handleHideBoard(w http.ResponseWriter, r *http.Request) {
// swagger:operation POST /teams/{teamID}/categories/{categoryID}/boards/{boardID}/hide hideBoard
//
// Hide the specified board for the user
//
// ---
// produces:
// - application/json
// parameters:
// - name: teamID
// in: path
// description: Team ID
// required: true
// type: string
// - name: categoryID
// in: path
// description: Category ID to which the board to be hidden belongs to
// required: true
// type: string
// - name: boardID
// in: path
// description: ID of board to be hidden
// required: true
// type: string
// security:
// - BearerAuth: []
// responses:
// '200':
// description: success
// schema:
// "$ref": "#/definitions/Category"
// default:
// description: internal error
// schema:
// "$ref": "#/definitions/ErrorResponse"
userID := getUserID(r)
vars := mux.Vars(r)
teamID := vars["teamID"]
boardID := vars["boardID"]
categoryID := vars["categoryID"]
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r, model.NewErrPermission("access denied to category"))
return
}
auditRec := a.makeAuditRecord(r, "hideBoard", audit.Fail)
defer a.audit.LogRecord(audit.LevelModify, auditRec)
auditRec.AddMeta("board_id", boardID)
auditRec.AddMeta("team_id", teamID)
auditRec.AddMeta("category_id", categoryID)
if err := a.app.SetBoardVisibility(teamID, userID, categoryID, boardID, false); err != nil {
a.errorResponse(w, r, err)
return
}
jsonStringResponse(w, http.StatusOK, "{}")
auditRec.Success()
}
func (a *API) handleUnhideBoard(w http.ResponseWriter, r *http.Request) {
// swagger:operation POST /teams/{teamID}/categories/{categoryID}/boards/{boardID}/hide unhideBoard
//
// Unhides the specified board for the user
//
// ---
// produces:
// - application/json
// parameters:
// - name: teamID
// in: path
// description: Team ID
// required: true
// type: string
// - name: categoryID
// in: path
// description: Category ID to which the board to be unhidden belongs to
// required: true
// type: string
// - name: boardID
// in: path
// description: ID of board to be unhidden
// required: true
// type: string
// security:
// - BearerAuth: []
// responses:
// '200':
// description: success
// schema:
// "$ref": "#/definitions/Category"
// default:
// description: internal error
// schema:
// "$ref": "#/definitions/ErrorResponse"
userID := getUserID(r)
vars := mux.Vars(r)
teamID := vars["teamID"]
boardID := vars["boardID"]
categoryID := vars["categoryID"]
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r, model.NewErrPermission("access denied to category"))
return
}
auditRec := a.makeAuditRecord(r, "unhideBoard", audit.Fail)
defer a.audit.LogRecord(audit.LevelModify, auditRec)
auditRec.AddMeta("boardID", boardID)
if err := a.app.SetBoardVisibility(teamID, userID, categoryID, boardID, true); err != nil {
a.errorResponse(w, r, err)
return
}
jsonStringResponse(w, http.StatusOK, "{}")
auditRec.Success()
}

View File

@ -146,7 +146,7 @@ func (a *API) handleAddMember(w http.ResponseWriter, r *http.Request) {
}
if reqBoardMember.UserID == "" {
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
a.errorResponse(w, r, model.NewErrBadRequest("empty userID"))
return
}

View File

@ -154,8 +154,8 @@ func (a *App) setBoardCategoryFromSource(sourceBoardID, destinationBoardID, user
var destinationCategoryID string
for _, categoryBoard := range userCategoryBoards {
for _, boardID := range categoryBoard.BoardIDs {
if boardID == sourceBoardID {
for _, metadata := range categoryBoard.BoardMetadata {
if metadata.BoardID == sourceBoardID {
// category found!
destinationCategoryID = categoryBoard.ID
break
@ -175,7 +175,7 @@ func (a *App) setBoardCategoryFromSource(sourceBoardID, destinationBoardID, user
// now that we have source board's category,
// we send destination board to the same category
return a.AddUpdateUserCategoryBoard(teamID, userID, map[string]string{destinationBoardID: destinationCategoryID})
return a.AddUpdateUserCategoryBoard(teamID, userID, destinationCategoryID, []string{destinationBoardID})
}
func (a *App) DuplicateBoard(boardID, userID, toTeam string, asTemplate bool) (*model.BoardsAndBlocks, []*model.BoardMember, error) {
@ -329,12 +329,12 @@ func (a *App) addBoardsToDefaultCategory(userID, teamID string, boards []*model.
return fmt.Errorf("%w userID: %s", errNoDefaultCategoryFound, userID)
}
boardCategoryMapping := map[string]string{}
for _, board := range boards {
boardCategoryMapping[board.ID] = defaultCategoryID
boardIDs := make([]string, len(boards))
for i := range boards {
boardIDs[i] = boards[i].ID
}
if err := a.AddUpdateUserCategoryBoard(teamID, userID, boardCategoryMapping); err != nil {
if err := a.AddUpdateUserCategoryBoard(teamID, userID, defaultCategoryID, boardIDs); err != nil {
return err
}

View File

@ -51,8 +51,8 @@ func TestAddMemberToBoard(t *testing.T) {
Type: "system",
},
},
}, nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", map[string]string{"board_id_1": "default_category_id"}).Return(nil)
}, nil).Times(2)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", "default_category_id", []string{"board_id_1"}).Return(nil)
addedBoardMember, err := th.App.AddMemberToBoard(boardMember)
require.NoError(t, err)
@ -125,8 +125,8 @@ func TestAddMemberToBoard(t *testing.T) {
Type: "system",
},
},
}, nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", map[string]string{"board_id_1": "default_category_id"}).Return(nil)
}, nil).Times(2)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", "default_category_id", []string{"board_id_1"}).Return(nil)
addedBoardMember, err := th.App.AddMemberToBoard(boardMember)
require.NoError(t, err)
@ -411,45 +411,72 @@ func TestBoardCategory(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
t.Run("test addBoardsToDefaultCategory", func(t *testing.T) {
t.Run("no boards default category exists", func(t *testing.T) {
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
{
Category: model.Category{ID: "category_id_1", Name: "Category 1"},
BoardIDs: []string{"board_id_1", "board_id_2"},
t.Run("no boards default category exists", func(t *testing.T) {
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
{
Category: model.Category{ID: "category_id_1", Name: "Category 1"},
BoardMetadata: []model.CategoryBoardMetadata{
{BoardID: "board_id_1"},
{BoardID: "board_id_2"},
},
{
Category: model.Category{ID: "category_id_2", Name: "Category 2"},
BoardIDs: []string{"board_id_3"},
},
{
Category: model.Category{ID: "category_id_2", Name: "Category 2"},
BoardMetadata: []model.CategoryBoardMetadata{
{BoardID: "board_id_3"},
},
{
Category: model.Category{ID: "category_id_3", Name: "Category 3"},
BoardIDs: []string{},
},
{
Category: model.Category{ID: "category_id_3", Name: "Category 3"},
BoardMetadata: []model.CategoryBoardMetadata{},
},
}, nil).Times(1)
// when this function is called the second time, the default category is created
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
{
Category: model.Category{ID: "category_id_1", Name: "Category 1"},
BoardMetadata: []model.CategoryBoardMetadata{
{BoardID: "board_id_1"},
{BoardID: "board_id_2"},
},
}, nil)
},
{
Category: model.Category{ID: "category_id_2", Name: "Category 2"},
BoardMetadata: []model.CategoryBoardMetadata{
{BoardID: "board_id_3"},
},
},
{
Category: model.Category{ID: "category_id_3", Name: "Category 3"},
BoardMetadata: []model.CategoryBoardMetadata{},
},
{
Category: model.Category{ID: "default_category_id", Type: model.CategoryTypeSystem, Name: "Boards"},
},
}, nil).Times(1)
th.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)
th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{
ID: "default_category_id",
Name: "Boards",
}, nil)
th.Store.EXPECT().GetMembersForUser("user_id").Return([]*model.BoardMember{}, nil)
th.Store.EXPECT().GetBoardsForUserAndTeam("user_id", "team_id", false).Return([]*model.Board{}, nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", map[string]string{
"board_id_1": "default_category_id",
"board_id_2": "default_category_id",
"board_id_3": "default_category_id",
}).Return(nil)
th.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)
th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{
ID: "default_category_id",
Name: "Boards",
}, nil)
th.Store.EXPECT().GetMembersForUser("user_id").Return([]*model.BoardMember{}, nil)
th.Store.EXPECT().GetBoardsForUserAndTeam("user_id", "team_id", false).Return([]*model.Board{}, nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", "default_category_id", []string{
"board_id_1",
"board_id_2",
"board_id_3",
}).Return(nil)
boards := []*model.Board{
{ID: "board_id_1"},
{ID: "board_id_2"},
{ID: "board_id_3"},
}
boards := []*model.Board{
{ID: "board_id_1"},
{ID: "board_id_2"},
{ID: "board_id_3"},
}
err := th.App.addBoardsToDefaultCategory("user_id", "team_id", boards)
assert.NoError(t, err)
})
err := th.App.addBoardsToDefaultCategory("user_id", "team_id", boards)
assert.NoError(t, err)
})
}
@ -491,9 +518,9 @@ func TestDuplicateBoard(t *testing.T) {
Type: "system",
},
},
}, nil).Times(2)
}, nil).Times(3)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", utils.Anything).Return(nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", "category_id_1", utils.Anything).Return(nil)
// for WS change broadcast
th.Store.EXPECT().GetMembersForBoard(utils.Anything).Return([]*model.BoardMember{}, nil).Times(2)

View File

@ -178,13 +178,12 @@ func (a *App) moveBoardsToDefaultCategory(userID, teamID, sourceCategoryID strin
return fmt.Errorf("moveBoardsToDefaultCategory: %w", errNoDefaultCategoryFound)
}
boardCategoryMapping := map[string]string{}
for _, boardID := range sourceCategoryBoards.BoardIDs {
boardCategoryMapping[boardID] = defaultCategoryID
boardIDs := make([]string, len(sourceCategoryBoards.BoardMetadata))
for i := range sourceCategoryBoards.BoardMetadata {
boardIDs[i] = sourceCategoryBoards.BoardMetadata[i].BoardID
}
if err := a.AddUpdateUserCategoryBoard(teamID, userID, boardCategoryMapping); err != nil {
if err := a.AddUpdateUserCategoryBoard(teamID, userID, defaultCategoryID, boardIDs); err != nil {
return fmt.Errorf("moveBoardsToDefaultCategory: %w", err)
}

View File

@ -79,8 +79,8 @@ func (a *App) createBoardsCategory(userID, teamID string, existingCategoryBoards
}
createdCategoryBoards := &model.CategoryBoards{
Category: *createdCategory,
BoardIDs: []string{},
Category: *createdCategory,
BoardMetadata: []model.CategoryBoardMetadata{},
}
// get user's current team's baords
@ -89,6 +89,8 @@ func (a *App) createBoardsCategory(userID, teamID string, existingCategoryBoards
return nil, fmt.Errorf("createBoardsCategory error fetching user's team's boards: %w", err)
}
boardIDsToAdd := []string{}
for _, board := range userTeamBoards {
boardMembership, ok := boardMemberByBoardID[board.ID]
if !ok {
@ -107,8 +109,8 @@ func (a *App) createBoardsCategory(userID, teamID string, existingCategoryBoards
belongsToCategory := false
for _, categoryBoard := range existingCategoryBoards {
for _, boardID := range categoryBoard.BoardIDs {
if boardID == board.ID {
for _, metadata := range categoryBoard.BoardMetadata {
if metadata.BoardID == board.ID {
belongsToCategory = true
break
}
@ -122,29 +124,58 @@ func (a *App) createBoardsCategory(userID, teamID string, existingCategoryBoards
}
if !belongsToCategory {
if err := a.AddUpdateUserCategoryBoard(teamID, userID, map[string]string{board.ID: createdCategory.ID}); err != nil {
return nil, fmt.Errorf("createBoardsCategory failed to add category-less board to the default category, defaultCategoryID: %s, error: %w", createdCategory.ID, err)
boardIDsToAdd = append(boardIDsToAdd, board.ID)
newBoardMetadata := model.CategoryBoardMetadata{
BoardID: board.ID,
Hidden: false,
}
createdCategoryBoards.BoardMetadata = append(createdCategoryBoards.BoardMetadata, newBoardMetadata)
}
}
createdCategoryBoards.BoardIDs = append(createdCategoryBoards.BoardIDs, board.ID)
if len(boardIDsToAdd) > 0 {
if err := a.AddUpdateUserCategoryBoard(teamID, userID, createdCategory.ID, boardIDsToAdd); err != nil {
return nil, fmt.Errorf("createBoardsCategory failed to add category-less board to the default category, defaultCategoryID: %s, error: %w", createdCategory.ID, err)
}
}
return createdCategoryBoards, nil
}
func (a *App) AddUpdateUserCategoryBoard(teamID, userID string, boardCategoryMapping map[string]string) error {
err := a.store.AddUpdateCategoryBoard(userID, boardCategoryMapping)
func (a *App) AddUpdateUserCategoryBoard(teamID, userID, categoryID string, boardIDs []string) error {
if len(boardIDs) == 0 {
return nil
}
err := a.store.AddUpdateCategoryBoard(userID, categoryID, boardIDs)
if err != nil {
return err
}
wsPayload := make([]*model.BoardCategoryWebsocketData, len(boardCategoryMapping))
userCategoryBoards, err := a.GetUserCategoryBoards(userID, teamID)
if err != nil {
return err
}
var updatedCategory *model.CategoryBoards
for i := range userCategoryBoards {
if userCategoryBoards[i].ID == categoryID {
updatedCategory = &userCategoryBoards[i]
break
}
}
if updatedCategory == nil {
return errCategoryNotFound
}
wsPayload := make([]*model.BoardCategoryWebsocketData, len(updatedCategory.BoardMetadata))
i := 0
for boardID, categoryID := range boardCategoryMapping {
for _, categoryBoardMetadata := range updatedCategory.BoardMetadata {
wsPayload[i] = &model.BoardCategoryWebsocketData{
BoardID: boardID,
BoardID: categoryBoardMetadata.BoardID,
CategoryID: categoryID,
Hidden: categoryBoardMetadata.Hidden,
}
i++
}
@ -198,12 +229,12 @@ func (a *App) verifyNewCategoryBoardsMatchExisting(userID, teamID, categoryID st
return fmt.Errorf("%w categoryID: %s", errCategoryNotFound, categoryID)
}
if len(targetCategoryBoards.BoardIDs) != len(newBoardsOrder) {
if len(targetCategoryBoards.BoardMetadata) != len(newBoardsOrder) {
return fmt.Errorf(
"%w length new category boards: %d, length existing category boards: %d, userID: %s, teamID: %s, categoryID: %s",
errCategoryBoardsLengthMismatch,
len(newBoardsOrder),
len(targetCategoryBoards.BoardIDs),
len(targetCategoryBoards.BoardMetadata),
userID,
teamID,
categoryID,
@ -211,8 +242,8 @@ func (a *App) verifyNewCategoryBoardsMatchExisting(userID, teamID, categoryID st
}
existingBoardMap := map[string]bool{}
for _, boardID := range targetCategoryBoards.BoardIDs {
existingBoardMap[boardID] = true
for _, metadata := range targetCategoryBoards.BoardMetadata {
existingBoardMap[metadata.BoardID] = true
}
for _, boardID := range newBoardsOrder {
@ -230,3 +261,19 @@ func (a *App) verifyNewCategoryBoardsMatchExisting(userID, teamID, categoryID st
return nil
}
func (a *App) SetBoardVisibility(teamID, userID, categoryID, boardID string, visible bool) error {
if err := a.store.SetBoardVisibility(userID, categoryID, boardID, visible); err != nil {
return fmt.Errorf("SetBoardVisibility: failed to update board visibility: %w", err)
}
a.wsAdapter.BroadcastCategoryBoardChange(teamID, userID, []*model.BoardCategoryWebsocketData{
{
BoardID: boardID,
CategoryID: categoryID,
Hidden: !visible,
},
})
return nil
}

View File

@ -14,7 +14,16 @@ func TestGetUserCategoryBoards(t *testing.T) {
defer tearDown()
t.Run("user had no default category and had boards", func(t *testing.T) {
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{}, nil)
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{}, nil).Times(1)
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
{
Category: model.Category{
ID: "boards_category_id",
Type: model.CategoryTypeSystem,
Name: "Boards",
},
},
}, nil).Times(1)
th.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)
th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{
ID: "boards_category_id",
@ -49,18 +58,16 @@ func TestGetUserCategoryBoards(t *testing.T) {
Synthetic: false,
},
}, nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", map[string]string{"board_id_1": "boards_category_id"}).Return(nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", map[string]string{"board_id_2": "boards_category_id"}).Return(nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", map[string]string{"board_id_3": "boards_category_id"}).Return(nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", "boards_category_id", []string{"board_id_1", "board_id_2", "board_id_3"}).Return(nil)
categoryBoards, err := th.App.GetUserCategoryBoards("user_id", "team_id")
assert.NoError(t, err)
assert.Equal(t, 1, len(categoryBoards))
assert.Equal(t, "Boards", categoryBoards[0].Name)
assert.Equal(t, 3, len(categoryBoards[0].BoardIDs))
assert.Contains(t, categoryBoards[0].BoardIDs, "board_id_1")
assert.Contains(t, categoryBoards[0].BoardIDs, "board_id_2")
assert.Contains(t, categoryBoards[0].BoardIDs, "board_id_3")
assert.Equal(t, 3, len(categoryBoards[0].BoardMetadata))
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_1", Hidden: false})
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_2", Hidden: false})
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_3", Hidden: false})
})
t.Run("user had no default category BUT had no boards", func(t *testing.T) {
@ -78,14 +85,17 @@ func TestGetUserCategoryBoards(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, 1, len(categoryBoards))
assert.Equal(t, "Boards", categoryBoards[0].Name)
assert.Equal(t, 0, len(categoryBoards[0].BoardIDs))
assert.Equal(t, 0, len(categoryBoards[0].BoardMetadata))
})
t.Run("user already had a default Boards category with boards in it", func(t *testing.T) {
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
{
Category: model.Category{Name: "Boards"},
BoardIDs: []string{"board_id_1", "board_id_2"},
BoardMetadata: []model.CategoryBoardMetadata{
{BoardID: "board_id_1", Hidden: false},
{BoardID: "board_id_2", Hidden: false},
},
},
}, nil)
@ -93,7 +103,7 @@ func TestGetUserCategoryBoards(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, 1, len(categoryBoards))
assert.Equal(t, "Boards", categoryBoards[0].Name)
assert.Equal(t, 2, len(categoryBoards[0].BoardIDs))
assert.Equal(t, 2, len(categoryBoards[0].BoardMetadata))
})
}
@ -116,7 +126,7 @@ func TestCreateBoardsCategory(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, boardsCategory)
assert.Equal(t, "Boards", boardsCategory.Name)
assert.Equal(t, 0, len(boardsCategory.BoardIDs))
assert.Equal(t, 0, len(boardsCategory.BoardMetadata))
})
t.Run("user has implicit access to some board", func(t *testing.T) {
@ -150,7 +160,7 @@ func TestCreateBoardsCategory(t *testing.T) {
// there should still be no boards in the default category as
// the user had only implicit access to boards
assert.Equal(t, 0, len(boardsCategory.BoardIDs))
assert.Equal(t, 0, len(boardsCategory.BoardMetadata))
})
t.Run("user has explicit access to some board", func(t *testing.T) {
@ -185,9 +195,17 @@ func TestCreateBoardsCategory(t *testing.T) {
Synthetic: false,
},
}, nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", map[string]string{"board_id_1": "boards_category_id"}).Return(nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", map[string]string{"board_id_2": "boards_category_id"}).Return(nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", map[string]string{"board_id_3": "boards_category_id"}).Return(nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", "boards_category_id", []string{"board_id_1", "board_id_2", "board_id_3"}).Return(nil)
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
{
Category: model.Category{
Type: model.CategoryTypeSystem,
ID: "boards_category_id",
Name: "Boards",
},
},
}, nil)
existingCategoryBoards := []model.CategoryBoards{}
boardsCategory, err := th.App.createBoardsCategory("user_id", "team_id", existingCategoryBoards)
@ -197,7 +215,7 @@ func TestCreateBoardsCategory(t *testing.T) {
// since user has explicit access to three boards,
// they should all end up in the default category
assert.Equal(t, 3, len(boardsCategory.BoardIDs))
assert.Equal(t, 3, len(boardsCategory.BoardMetadata))
})
t.Run("user has both implicit and explicit access to some board", func(t *testing.T) {
@ -226,7 +244,17 @@ func TestCreateBoardsCategory(t *testing.T) {
Synthetic: true,
},
}, nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", map[string]string{"board_id_1": "boards_category_id"}).Return(nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", "boards_category_id", []string{"board_id_1"}).Return(nil)
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
{
Category: model.Category{
Type: model.CategoryTypeSystem,
ID: "boards_category_id",
Name: "Boards",
},
},
}, nil)
existingCategoryBoards := []model.CategoryBoards{}
boardsCategory, err := th.App.createBoardsCategory("user_id", "team_id", existingCategoryBoards)
@ -237,7 +265,7 @@ func TestCreateBoardsCategory(t *testing.T) {
// there was only one explicit board access,
// and so only that one should end up in the
// default category
assert.Equal(t, 1, len(boardsCategory.BoardIDs))
assert.Equal(t, 1, len(boardsCategory.BoardMetadata))
})
}
@ -249,15 +277,20 @@ func TestReorderCategoryBoards(t *testing.T) {
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
{
Category: model.Category{ID: "category_id_1", Name: "Category 1"},
BoardIDs: []string{"board_id_1", "board_id_2"},
BoardMetadata: []model.CategoryBoardMetadata{
{BoardID: "board_id_1", Hidden: false},
{BoardID: "board_id_2", Hidden: false},
},
},
{
Category: model.Category{ID: "category_id_2", Name: "Boards", Type: "system"},
BoardIDs: []string{"board_id_3"},
BoardMetadata: []model.CategoryBoardMetadata{
{BoardID: "board_id_3", Hidden: false},
},
},
{
Category: model.Category{ID: "category_id_3", Name: "Category 3"},
BoardIDs: []string{},
Category: model.Category{ID: "category_id_3", Name: "Category 3"},
BoardMetadata: []model.CategoryBoardMetadata{},
},
}, nil)
@ -274,15 +307,21 @@ func TestReorderCategoryBoards(t *testing.T) {
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
{
Category: model.Category{ID: "category_id_1", Name: "Category 1"},
BoardIDs: []string{"board_id_1", "board_id_2", "board_id_3"},
BoardMetadata: []model.CategoryBoardMetadata{
{BoardID: "board_id_1", Hidden: false},
{BoardID: "board_id_2", Hidden: false},
{BoardID: "board_id_3", Hidden: false},
},
},
{
Category: model.Category{ID: "category_id_2", Name: "Boards", Type: "system"},
BoardIDs: []string{"board_id_3"},
BoardMetadata: []model.CategoryBoardMetadata{
{BoardID: "board_id_3", Hidden: false},
},
},
{
Category: model.Category{ID: "category_id_3", Name: "Category 3"},
BoardIDs: []string{},
Category: model.Category{ID: "category_id_3", Name: "Category 3"},
BoardMetadata: []model.CategoryBoardMetadata{},
},
}, nil)

View File

@ -278,7 +278,7 @@ func TestDeleteCategory(t *testing.T) {
Type: "default",
Name: "Boards",
},
BoardIDs: []string{},
BoardMetadata: []model.CategoryBoardMetadata{},
},
{
Category: model.Category{
@ -289,12 +289,10 @@ func TestDeleteCategory(t *testing.T) {
Type: "custom",
Name: "Category 1",
},
BoardIDs: []string{},
BoardMetadata: []model.CategoryBoardMetadata{},
},
}, nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", utils.Anything).Return(nil)
deletedCategory, err := th.App.DeleteCategory("category_id_1", "user_id_1", "team_id_1")
assert.NotNil(t, deletedCategory)
assert.NoError(t, err)
@ -351,8 +349,6 @@ func TestMoveBoardsToDefaultCategory(t *testing.T) {
},
}, nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", utils.Anything).Return(nil)
err := th.App.moveBoardsToDefaultCategory("user_id", "team_id", "category_id_2")
assert.NoError(t, err)
})
@ -376,7 +372,6 @@ func TestMoveBoardsToDefaultCategory(t *testing.T) {
}, nil)
th.Store.EXPECT().GetMembersForUser("user_id").Return([]*model.BoardMember{}, nil)
th.Store.EXPECT().GetBoardsForUserAndTeam("user_id", "team_id", false).Return([]*model.Board{}, nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", utils.Anything).Return(nil)
err := th.App.moveBoardsToDefaultCategory("user_id", "team_id", "category_id_2")
assert.NoError(t, err)

View File

@ -92,14 +92,25 @@ func (a *App) writeArchiveBoard(zw *zip.Writer, board model.Board, opt model.Exp
return err
}
if block.Type == model.TypeImage {
filename, err := extractImageFilename(block)
if err != nil {
filename, err2 := extractImageFilename(block)
if err2 != nil {
return err
}
files = append(files, filename)
}
}
boardMembers, err := a.GetMembersForBoard(board.ID)
if err != nil {
return err
}
for _, boardMember := range boardMembers {
if err = a.writeArchiveBoardMemberLine(w, boardMember); err != nil {
return err
}
}
// write the files
for _, filename := range files {
if err := a.writeArchiveFile(zw, filename, board.ID, opt); err != nil {
@ -109,6 +120,31 @@ func (a *App) writeArchiveBoard(zw *zip.Writer, board model.Board, opt model.Exp
return nil
}
// writeArchiveBoardMemberLine writes a single boardMember to the archive.
func (a *App) writeArchiveBoardMemberLine(w io.Writer, boardMember *model.BoardMember) error {
bm, err := json.Marshal(&boardMember)
if err != nil {
return err
}
line := model.ArchiveLine{
Type: "boardMember",
Data: bm,
}
bm, err = json.Marshal(&line)
if err != nil {
return err
}
_, err = w.Write(bm)
if err != nil {
return err
}
_, err = w.Write(newline)
return err
}
// writeArchiveBlockLine writes a single block to the archive.
func (a *App) writeArchiveBlockLine(w io.Writer, block *model.Block) error {
b, err := json.Marshal(&block)

View File

@ -137,6 +137,7 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str
}
now := utils.GetMillis()
var boardID string
var boardMembers []*model.BoardMember
lineNum := 1
firstLine := true
@ -196,6 +197,12 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str
block.UpdateAt = now
block.BoardID = boardID
boardsAndBlocks.Blocks = append(boardsAndBlocks.Blocks, block)
case "boardMember":
var boardMember *model.BoardMember
if err2 := json.Unmarshal(archiveLine.Data, &boardMember); err2 != nil {
return "", fmt.Errorf("invalid board Member in archive line %d: %w", lineNum, err2)
}
boardMembers = append(boardMembers, boardMember)
default:
return "", model.NewErrUnsupportedArchiveLineType(lineNum, archiveLine.Type)
}
@ -212,6 +219,13 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str
lineNum++
}
// loop to remove the people how are not part of the team and system
for i := len(boardMembers) - 1; i >= 0; i-- {
if _, err := a.GetUser(boardMembers[i].UserID); err != nil {
boardMembers = append(boardMembers[:i], boardMembers[i+1:]...)
}
}
a.fixBoardsandBlocks(boardsAndBlocks, opt)
var err error
@ -225,16 +239,22 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str
return "", fmt.Errorf("error inserting archive blocks: %w", err)
}
// add user to all the new boards (if not the fake system user).
if opt.ModifiedBy != model.SystemUserID {
for _, board := range boardsAndBlocks.Boards {
boardMember := &model.BoardMember{
BoardID: board.ID,
UserID: opt.ModifiedBy,
SchemeAdmin: true,
// add users to all the new boards (if not the fake system user).
for _, board := range boardsAndBlocks.Boards {
for _, boardMember := range boardMembers {
bm := &model.BoardMember{
BoardID: board.ID,
UserID: boardMember.UserID,
Roles: boardMember.Roles,
MinimumRole: boardMember.MinimumRole,
SchemeAdmin: boardMember.SchemeAdmin,
SchemeEditor: boardMember.SchemeEditor,
SchemeCommenter: boardMember.SchemeCommenter,
SchemeViewer: boardMember.SchemeViewer,
Synthetic: boardMember.Synthetic,
}
if _, err := a.AddMemberToBoard(boardMember); err != nil {
return "", fmt.Errorf("cannot add member to board: %w", err)
if _, err2 := a.AddMemberToBoard(bm); err2 != nil {
return "", fmt.Errorf("cannot add member to board: %w", err2)
}
}
}

View File

@ -47,8 +47,18 @@ func TestApp_ImportArchive(t *testing.T) {
th.Store.EXPECT().CreateBoardsAndBlocks(gomock.AssignableToTypeOf(&model.BoardsAndBlocks{}), "user").Return(babs, nil)
th.Store.EXPECT().GetMembersForBoard(board.ID).AnyTimes().Return([]*model.BoardMember{boardMember}, nil)
th.Store.EXPECT().GetBoard(board.ID).Return(board, nil)
th.Store.EXPECT().GetMemberForBoard(board.ID, "user").Return(boardMember, nil)
// th.Store.EXPECT().GetBoard(board.ID).Return(board, nil)
// th.Store.EXPECT().GetMemberForBoard(board.ID, "user").Return(boardMember, nil)
// th.Store.EXPECT().GetUserCategoryBoards("user", "test-team").Return([]model.CategoryBoards{}, nil)
th.Store.EXPECT().GetUserCategoryBoards("user", "test-team").Return([]model.CategoryBoards{
{
Category: model.Category{
Type: "default",
Name: "Boards",
ID: "boards_category_id",
},
},
}, nil)
th.Store.EXPECT().GetUserCategoryBoards("user", "test-team")
th.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)
th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{
@ -57,11 +67,78 @@ func TestApp_ImportArchive(t *testing.T) {
}, nil)
th.Store.EXPECT().GetBoardsForUserAndTeam("user", "test-team", false).Return([]*model.Board{}, nil)
th.Store.EXPECT().GetMembersForUser("user").Return([]*model.BoardMember{}, nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user", utils.Anything).Return(nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user", utils.Anything, utils.Anything).Return(nil)
err := th.App.ImportArchive(r, opts)
require.NoError(t, err, "import archive should not fail")
})
t.Run("import board archive", func(t *testing.T) {
r := bytes.NewReader([]byte(boardArchive))
opts := model.ImportArchiveOptions{
TeamID: "test-team",
ModifiedBy: "f1tydgc697fcbp8ampr6881jea",
}
bm1 := &model.BoardMember{
BoardID: board.ID,
UserID: "f1tydgc697fcbp8ampr6881jea",
}
bm2 := &model.BoardMember{
BoardID: board.ID,
UserID: "hxxzooc3ff8cubsgtcmpn8733e",
}
bm3 := &model.BoardMember{
BoardID: board.ID,
UserID: "nto73edn5ir6ifimo5a53y1dwa",
}
user1 := &model.User{
ID: "f1tydgc697fcbp8ampr6881jea",
}
user2 := &model.User{
ID: "hxxzooc3ff8cubsgtcmpn8733e",
}
user3 := &model.User{
ID: "nto73edn5ir6ifimo5a53y1dwa",
}
th.Store.EXPECT().CreateBoardsAndBlocks(gomock.AssignableToTypeOf(&model.BoardsAndBlocks{}), "f1tydgc697fcbp8ampr6881jea").Return(babs, nil)
th.Store.EXPECT().GetMembersForBoard(board.ID).AnyTimes().Return([]*model.BoardMember{bm1, bm2, bm3}, nil)
th.Store.EXPECT().GetUserCategoryBoards("f1tydgc697fcbp8ampr6881jea", "test-team").Return([]model.CategoryBoards{}, nil)
th.Store.EXPECT().GetUserCategoryBoards("f1tydgc697fcbp8ampr6881jea", "test-team").Return([]model.CategoryBoards{
{
Category: model.Category{
ID: "boards_category_id",
Name: "Boards",
Type: model.CategoryTypeSystem,
},
},
}, nil)
th.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)
th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{
ID: "boards_category_id",
Name: "Boards",
}, nil)
th.Store.EXPECT().GetMembersForUser("f1tydgc697fcbp8ampr6881jea").Return([]*model.BoardMember{}, nil)
th.Store.EXPECT().GetBoardsForUserAndTeam("f1tydgc697fcbp8ampr6881jea", "test-team", false).Return([]*model.Board{}, nil)
th.Store.EXPECT().AddUpdateCategoryBoard("f1tydgc697fcbp8ampr6881jea", utils.Anything, utils.Anything).Return(nil)
th.Store.EXPECT().GetBoard(board.ID).AnyTimes().Return(board, nil)
th.Store.EXPECT().GetMemberForBoard(board.ID, "f1tydgc697fcbp8ampr6881jea").AnyTimes().Return(bm1, nil)
th.Store.EXPECT().GetMemberForBoard(board.ID, "hxxzooc3ff8cubsgtcmpn8733e").AnyTimes().Return(bm2, nil)
th.Store.EXPECT().GetMemberForBoard(board.ID, "nto73edn5ir6ifimo5a53y1dwa").AnyTimes().Return(bm3, nil)
th.Store.EXPECT().GetUserByID("f1tydgc697fcbp8ampr6881jea").AnyTimes().Return(user1, nil)
th.Store.EXPECT().GetUserByID("hxxzooc3ff8cubsgtcmpn8733e").AnyTimes().Return(user2, nil)
th.Store.EXPECT().GetUserByID("nto73edn5ir6ifimo5a53y1dwa").AnyTimes().Return(user3, nil)
boardID, err := th.App.ImportBoardJSONL(r, opts)
require.Equal(t, board.ID, boardID, "Board ID should be same")
require.NoError(t, err, "import archive should not fail")
})
}
//nolint:lll
@ -78,3 +155,12 @@ const asana = `{"version":1,"date":1614714686842}
{"type":"block","data":{"id":"db1dd596-0999-4741-8b05-72ca8e438e31","fields":{"icon":"","properties":{"3bdcbaeb-bc78-4884-8531-a0323b74676a":"deaab476-c690-48df-828f-725b064dc476"},"contentOrder":[]},"createAt":1614714686841,"updateAt":1614714686841,"deleteAt":0,"schema":1,"parentId":"d14b9df9-1f31-4732-8a64-92bc7162cd28","rootId":"d14b9df9-1f31-4732-8a64-92bc7162cd28","modifiedBy":"","type":"card","title":"[EXAMPLE TASK] Approve campaign copy"}}
{"type":"block","data":{"id":"16861c05-f31f-46af-8429-80a87b5aa93a","fields":{"icon":"","properties":{"3bdcbaeb-bc78-4884-8531-a0323b74676a":"2138305a-3157-461c-8bbe-f19ebb55846d"},"contentOrder":[]},"createAt":1614714686841,"updateAt":1614714686841,"deleteAt":0,"schema":1,"parentId":"d14b9df9-1f31-4732-8a64-92bc7162cd28","rootId":"d14b9df9-1f31-4732-8a64-92bc7162cd28","modifiedBy":"","type":"card","title":"[EXAMPLE TASK] Send out updated attendee list"}}
`
//nolint:lll
const boardArchive = `{"type":"board","data":{"id":"bfoi6yy6pa3yzika53spj7pq9ee","teamId":"wsmqbtwb5jb35jb3mtp85c8a9h","channelId":"","createdBy":"nto73edn5ir6ifimo5a53y1dwa","modifiedBy":"nto73edn5ir6ifimo5a53y1dwa","type":"P","minimumRole":"","title":"Custom","description":"","icon":"","showDescription":false,"isTemplate":false,"templateVersion":0,"properties":{},"cardProperties":[{"id":"aonihehbifijmx56aqzu3cc7w1r","name":"Status","options":[],"type":"select"},{"id":"aohjkzt769rxhtcz1o9xcoce5to","name":"Person","options":[],"type":"person"}],"createAt":1672750481591,"updateAt":1672750481591,"deleteAt":0}}
{"type":"block","data":{"id":"ckpc3b1dp3pbw7bqntfryy9jbzo","parentId":"bjaqxtbyqz3bu7pgyddpgpms74a","createdBy":"nto73edn5ir6ifimo5a53y1dwa","modifiedBy":"nto73edn5ir6ifimo5a53y1dwa","schema":1,"type":"card","title":"Test","fields":{"contentOrder":[],"icon":"","isTemplate":false,"properties":{"aohjkzt769rxhtcz1o9xcoce5to":"hxxzooc3ff8cubsgtcmpn8733e"}},"createAt":1672750481612,"updateAt":1672845003530,"deleteAt":0,"boardId":"bfoi6yy6pa3yzika53spj7pq9ee"}}
{"type":"block","data":{"id":"v7tdajwpm47r3u8duedk89bhxar","parentId":"bpypang3a3errqstj1agx9kuqay","createdBy":"nto73edn5ir6ifimo5a53y1dwa","modifiedBy":"nto73edn5ir6ifimo5a53y1dwa","schema":1,"type":"view","title":"Board view","fields":{"cardOrder":["crsyw7tbr3pnjznok6ppngmmyya","c5titiemp4pgaxbs4jksgybbj4y"],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{},"defaultTemplateId":"","filter":{"filters":[],"operation":"and"},"hiddenOptionIds":[],"kanbanCalculations":{},"sortOptions":[],"viewType":"board","visibleOptionIds":[],"visiblePropertyIds":["aohjkzt769rxhtcz1o9xcoce5to"]},"createAt":1672750481626,"updateAt":1672750481626,"deleteAt":0,"boardId":"bfoi6yy6pa3yzika53spj7pq9ee"}}
{"type":"boardMember","data":{"boardId":"bfoi6yy6pa3yzika53spj7pq9ee","userId":"f1tydgc697fcbp8ampr6881jea","roles":"","minimumRole":"","schemeAdmin":false,"schemeEditor":false,"schemeCommenter":false,"schemeViewer":true,"synthetic":false}}
{"type":"boardMember","data":{"boardId":"bfoi6yy6pa3yzika53spj7pq9ee","userId":"hxxzooc3ff8cubsgtcmpn8733e","roles":"","minimumRole":"","schemeAdmin":false,"schemeEditor":false,"schemeCommenter":false,"schemeViewer":true,"synthetic":false}}
{"type":"boardMember","data":{"boardId":"bfoi6yy6pa3yzika53spj7pq9ee","userId":"nto73edn5ir6ifimo5a53y1dwa","roles":"","minimumRole":"","schemeAdmin":true,"schemeEditor":false,"schemeCommenter":false,"schemeViewer":false,"synthetic":false}}
`

View File

@ -19,7 +19,7 @@ func (a *App) GetTeamBoardsInsights(userID string, teamID string, opts *mmModel.
if err != nil {
return nil, err
}
return a.store.GetTeamBoardsInsights(teamID, userID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage, boardIDs)
return a.store.GetTeamBoardsInsights(teamID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage, boardIDs)
}
func (a *App) GetUserBoardsInsights(userID string, teamID string, opts *mmModel.InsightsOpts) (*model.BoardInsightsList, error) {

View File

@ -51,7 +51,7 @@ func TestGetTeamAndUserBoardsInsights(t *testing.T) {
th.Store.EXPECT().GetUserByID("user-id").Return(fakeUser, nil).AnyTimes()
th.Store.EXPECT().GetBoardsForUserAndTeam("user-id", "team-id", true).Return(mockInsightsBoards, nil).AnyTimes()
th.Store.EXPECT().
GetTeamBoardsInsights("team-id", "user-id", int64(0), 0, 10, []string{"mock-user-workspace-id"}).
GetTeamBoardsInsights("team-id", int64(0), 0, 10, []string{"mock-user-workspace-id"}).
Return(mockTeamInsightsList, nil)
results, err := th.App.GetTeamBoardsInsights("user-id", "team-id", &mmModel.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 10})
require.NoError(t, err)
@ -74,7 +74,7 @@ func TestGetTeamAndUserBoardsInsights(t *testing.T) {
th.Store.EXPECT().GetUserByID("user-id").Return(fakeUser, nil).AnyTimes()
th.Store.EXPECT().GetBoardsForUserAndTeam("user-id", "team-id", true).Return(mockInsightsBoards, nil).AnyTimes()
th.Store.EXPECT().
GetTeamBoardsInsights("team-id", "user-id", int64(0), 0, 10, []string{"mock-user-workspace-id"}).
GetTeamBoardsInsights("team-id", int64(0), 0, 10, []string{"mock-user-workspace-id"}).
Return(nil, insightError{"board-insight-error"})
_, err := th.App.GetTeamBoardsInsights("user-id", "team-id", &mmModel.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 10})
require.Error(t, err)

View File

@ -70,7 +70,7 @@ func TestPrepareOnboardingTour(t *testing.T) {
{
Category: model.Category{ID: "boards_category_id", Name: "Boards"},
},
}, nil).Times(1)
}, nil).Times(2)
th.Store.EXPECT().CreateCategory(utils.Anything).Return(nil).Times(1)
th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{
@ -78,7 +78,7 @@ func TestPrepareOnboardingTour(t *testing.T) {
Name: "Boards",
}, nil)
th.Store.EXPECT().GetBoardsForUserAndTeam("user_id_1", teamID, false).Return([]*model.Board{}, nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", map[string]string{"board_id_2": "boards_category_id"}).Return(nil)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", "boards_category_id", []string{"board_id_2"}).Return(nil)
teamID, boardID, err := th.App.PrepareOnboardingTour(userID, teamID)
assert.NoError(t, err)
@ -120,8 +120,8 @@ func TestCreateWelcomeBoard(t *testing.T) {
{
Category: model.Category{ID: "boards_category_id", Name: "Boards"},
},
}, nil).Times(2)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", map[string]string{"board_id_1": "boards_category_id"}).Return(nil)
}, nil).Times(3)
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", "boards_category_id", []string{"board_id_1"}).Return(nil)
boardID, err := th.App.createWelcomeBoard(userID, teamID)
assert.Nil(t, err)

View File

@ -7,5 +7,6 @@ import (
// DefaultTemplatesArchive is an embedded archive file containing the default
// templates to be imported to team 0.
// This archive is generated with `make templates-archive`
//
//go:embed templates.boardarchive
var DefaultTemplatesArchive []byte

View File

@ -986,3 +986,21 @@ func (c *Client) GetStatistics() (*model.BoardsStatistics, *Response) {
return stats, BuildResponse(r)
}
func (c *Client) HideBoard(teamID, categoryID, boardID string) *Response {
r, err := c.DoAPIPut(c.GetTeamRoute(teamID)+"/categories/"+categoryID+"/boards/"+boardID+"/hide", "")
if err != nil {
return BuildErrorResponse(r, err)
}
return BuildResponse(r)
}
func (c *Client) UnhideBoard(teamID, categoryID, boardID string) *Response {
r, err := c.DoAPIPut(c.GetTeamRoute(teamID)+"/categories/"+categoryID+"/boards/"+boardID+"/unhide", "")
if err != nil {
return BuildErrorResponse(r, err)
}
return BuildResponse(r)
}

View File

@ -1,6 +1,6 @@
module github.com/mattermost/focalboard/server
go 1.18
go 1.19
require (
github.com/Masterminds/squirrel v1.5.3
@ -9,8 +9,8 @@ require (
github.com/gorilla/websocket v1.5.0
github.com/krolaw/zipstream v0.0.0-20180621105154-0a2661891f94
github.com/lib/pq v1.10.7
github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb
github.com/mattermost/mattermost-server/v6 v6.0.0-20221214122404-8d90c7042f93
github.com/mattermost/mattermost-plugin-api v0.1.1
github.com/mattermost/mattermost-server/v6 v6.0.0-20230116174708-240304ad0728
github.com/mattermost/morph v1.0.5-0.20221115094356-4c18a75b1f5e
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/mgdelacroix/foundation v0.0.0-20220812143423-0bfc18f73538
@ -23,14 +23,14 @@ require (
github.com/spf13/viper v1.10.1
github.com/stretchr/testify v1.8.1
github.com/wiggin77/merror v1.0.4
golang.org/x/crypto v0.2.0
golang.org/x/crypto v0.5.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a // indirect
@ -38,20 +38,20 @@ require (
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/golang-migrate/migrate/v4 v4.15.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/graph-gophers/graphql-go v1.4.0 // indirect
github.com/hashicorp/go-hclog v1.3.1 // indirect
github.com/hashicorp/go-plugin v1.4.6 // indirect
github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a // indirect
github.com/hashicorp/go-hclog v1.4.0 // indirect
github.com/hashicorp/go-plugin v1.4.8 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.15.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.1 // indirect
github.com/klauspost/compress v1.15.14 // indirect
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
@ -60,10 +60,10 @@ require (
github.com/mattermost/logr/v2 v2.0.15 // indirect
github.com/mattermost/squirrel v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minio-go/v7 v7.0.43 // indirect
github.com/minio/minio-go/v7 v7.0.45 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
@ -72,12 +72,12 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pborman/uuid v1.2.1 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/philhofer/fwd v1.1.1 // indirect
github.com/philhofer/fwd v1.1.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.33.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect
github.com/rs/xid v1.4.0 // indirect
github.com/segmentio/backo-go v1.0.1 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
@ -87,36 +87,36 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/tidwall/gjson v1.14.3 // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tinylib/msgp v1.1.6 // indirect
github.com/tinylib/msgp v1.1.8 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/wiggin77/srslog v1.0.1 // indirect
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
github.com/yuin/goldmark v1.5.3 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/tools v0.3.0 // indirect
google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1 // indirect
google.golang.org/grpc v1.50.1 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/text v0.6.0 // indirect
golang.org/x/tools v0.5.0 // indirect
google.golang.org/genproto v0.0.0-20230104163317-caabf589fcbf // indirect
google.golang.org/grpc v1.51.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.36.0 // indirect
modernc.org/ccgo/v3 v3.16.6 // indirect
modernc.org/libc v1.16.7 // indirect
modernc.org/mathutil v1.4.1 // indirect
modernc.org/memory v1.1.1 // indirect
modernc.org/opt v0.1.1 // indirect
modernc.org/sqlite v1.18.0 // indirect
modernc.org/strutil v1.1.1 // indirect
modernc.org/token v1.0.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.22.2 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/sqlite v1.20.1 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.1.0 // indirect
)

View File

@ -203,6 +203,7 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
@ -487,6 +488,7 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
@ -655,6 +657,7 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/graph-gophers/graphql-go v1.4.0 h1:JE9wveRTSXwJyjdRd6bOQ7Ob5bewTUQ58Jv4OiVdpdE=
github.com/graph-gophers/graphql-go v1.4.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os=
github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@ -675,6 +678,7 @@ github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw=
github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-hclog v1.3.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-hclog v1.4.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
@ -684,6 +688,7 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9
github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ=
github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s=
github.com/hashicorp/go-plugin v1.4.6/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s=
github.com/hashicorp/go-plugin v1.4.8/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
@ -809,11 +814,13 @@ github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e
github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY=
github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/klauspost/compress v1.15.14/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.13 h1:1XxvOiqXZ8SULZUKim/wncr3wZ38H4yCuVDvKdK9OGs=
github.com/klauspost/cpuid/v2 v2.0.13/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -871,12 +878,15 @@ github.com/mattermost/logr/v2 v2.0.15 h1:+WNbGcsc3dBao65eXlceB6dTILNJRIrvubnsTl3
github.com/mattermost/logr/v2 v2.0.15/go.mod h1:mpPp935r5dIkFDo2y9Q87cQWhFR/4xXpNh0k/y8Hmwg=
github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb h1:q1qXKVv59rA2gcQ7lVLc5OlWBmfsR3i8mdGD5EZesyk=
github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb/go.mod h1:PIeo40t9VTA4Wu1FwjzH7QmcgC3SRyk/ohCwJw4/oSo=
github.com/mattermost/mattermost-plugin-api v0.1.1/go.mod h1:9yZhtg0bBj3kqSTjXnjYBMZoTsWbe3ajdFMdl9/Jz34=
github.com/mattermost/mattermost-server/v6 v6.0.0-20220802151854-f07c31c5d933 h1:h7EibO8cwWeK8dLhC/A5tKGbkYSuJKZ0+2EXW7jDHoA=
github.com/mattermost/mattermost-server/v6 v6.0.0-20220802151854-f07c31c5d933/go.mod h1:otnBnKY9Y0eNkUKeD161de+BUBlESwANTnrkPT/392Y=
github.com/mattermost/mattermost-server/v6 v6.0.0-20221130200243-06e964b86b0d h1:CKJXDUCkRrfy1U9sZHOpvACOtkthV5iWt2boHUK720I=
github.com/mattermost/mattermost-server/v6 v6.0.0-20221130200243-06e964b86b0d/go.mod h1:U3gSM0I15WSMHPpDEU30mmc4JrbSDk+8F1+MFLOHWD0=
github.com/mattermost/mattermost-server/v6 v6.0.0-20221214122404-8d90c7042f93 h1:mGN2D6KhjKosQdZ+BHzmWxsA/tRK9FiR+nUd38nSZQY=
github.com/mattermost/mattermost-server/v6 v6.0.0-20221214122404-8d90c7042f93/go.mod h1:U3gSM0I15WSMHPpDEU30mmc4JrbSDk+8F1+MFLOHWD0=
github.com/mattermost/mattermost-server/v6 v6.0.0-20230116174708-240304ad0728 h1:fegj7GaXjiVH+/j1DsPtkobczafvUJynfFSwNeqIA84=
github.com/mattermost/mattermost-server/v6 v6.0.0-20230116174708-240304ad0728/go.mod h1:FPN2+SAU9ndEpMFcjClvdillSpvS2eQ+i1qiSgAUxPI=
github.com/mattermost/morph v0.0.0-20220401091636-39f834798da8 h1:gwliVjCTqAC01mSCNqa5nJ/4MmGq50vrjsottIhQ4d8=
github.com/mattermost/morph v0.0.0-20220401091636-39f834798da8/go.mod h1:jxM3g1bx+k2Thz7jofcHguBS8TZn5Pc+o5MGmORObhw=
github.com/mattermost/morph v1.0.5-0.20221115094356-4c18a75b1f5e h1:VfNz+fvJ3DxOlALM22Eea8ONp5jHrybKBCcCtDPVlss=
@ -903,6 +913,7 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
@ -928,6 +939,7 @@ github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEp
github.com/minio/minio-go/v7 v7.0.28 h1:VMr3K5qGIEt+/KW3poopRh8mzi5RwuCjmrmstK196Fg=
github.com/minio/minio-go/v7 v7.0.28/go.mod h1:x81+AX5gHSfCSqw7jxRKHvxUXMlE5uKX0Vb75Xk5yYg=
github.com/minio/minio-go/v7 v7.0.43/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
github.com/minio/minio-go/v7 v7.0.45/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
@ -1048,6 +1060,7 @@ github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@ -1111,6 +1124,7 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@ -1249,6 +1263,7 @@ github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
@ -1257,6 +1272,7 @@ github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhso
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@ -1307,6 +1323,7 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.12 h1:6hffw6vALvEDqJ19dOJvJKOoAOKe4NDaTqvd2sktGN0=
github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.5.3/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
@ -1405,6 +1422,7 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -1529,7 +1547,10 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220614195744-fb05da6f9022 h1:0qjDla5xICC2suMtyRH/QqX3B1btXTfNsIt/i4LFgO0=
golang.org/x/net v0.0.0-20220614195744-fb05da6f9022/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -1566,6 +1587,7 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -1696,18 +1718,23 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098 h1:PgOr27OhUx2IRqGJ2RxAWI4dJQ7bi9cSrB82uzFzfUA=
golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1719,6 +1746,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1812,7 +1841,10 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -1951,6 +1983,7 @@ google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2I
google.golang.org/genproto v0.0.0-20220614165028-45ed7f3ff16e h1:ubR4JUtqN3ffdFjpKylv8scWk/mZstGmzXbgYSkuMl0=
google.golang.org/genproto v0.0.0-20220614165028-45ed7f3ff16e/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
google.golang.org/genproto v0.0.0-20230104163317-caabf589fcbf/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
@ -1990,6 +2023,7 @@ google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11
google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@ -2133,6 +2167,8 @@ modernc.org/cc/v3 v3.35.24 h1:vlCqjhVwX15t1uwlMPpOpNRC7JTjMZ9lT9DYHKQTFuA=
modernc.org/cc/v3 v3.35.24/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/cc/v3 v3.36.0 h1:0kmRkTmqNidmu3c7BNDSdVHCxXCkWLmWmCIVX4LUboo=
modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=
modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=
modernc.org/ccgo/v3 v3.9.2/go.mod h1:gnJpy6NIVqkETT+L5zPsQFj7L2kkhfPMzOghRNv/CFo=
@ -2187,6 +2223,8 @@ modernc.org/ccgo/v3 v3.15.17/go.mod h1:bofnFkpRFf5gLY+mBZIyTW6FEcp26xi2lgOFk2Rlv
modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
modernc.org/ccgo/v3 v3.16.6 h1:3l18poV+iUemQ98O3X5OMr97LOqlzis+ytivU4NqGhA=
modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
@ -2255,6 +2293,7 @@ modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=
modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU=
modernc.org/libc v1.16.7 h1:qzQtHhsZNpVPpeCu+aMIQldXeV1P0vRhSqCL0nOIJOA=
modernc.org/libc v1.16.7/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
@ -2262,6 +2301,7 @@ modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
modernc.org/memory v1.0.6/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
@ -2269,8 +2309,11 @@ modernc.org/memory v1.0.7 h1:UE3cxTRFa5tfUibAV7Jqq8P7zRY0OlJg+yWVIIaluEE=
modernc.org/memory v1.0.7/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/memory v1.1.1 h1:bDOL0DIDLQv7bWhP3gMvIrnoFw+Eo6F7a2QK9HPDiFU=
modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY=
modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k=
modernc.org/sqlite v1.10.6/go.mod h1:Z9FEjUtZP4qFEg6/SiADg9XCER7aYy9a/j7Pg9P7CPs=
@ -2279,9 +2322,12 @@ modernc.org/sqlite v1.15.3 h1:3C4AWicF7S5vUUFJuBi7Ws8eWlPjqyo/c4Z1UGYBbyg=
modernc.org/sqlite v1.15.3/go.mod h1:J7GAPbk8Txp0DJnT8TGwpUqJW0Z1cK2YpzjoXaZRU8k=
modernc.org/sqlite v1.18.0 h1:ef66qJSgKeyLyrF4kQ2RHw/Ue3V89fyFNbGL073aDjI=
modernc.org/sqlite v1.18.0/go.mod h1:B9fRWZacNxJBHoCJZQr1R54zhVn3fjfl0aszflrTSxY=
modernc.org/sqlite v1.20.1/go.mod h1:fODt+bFmc/j8LcoCbMSkAuKuGmhxjG45KGc25N2705M=
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/tcl v1.5.2/go.mod h1:pmJYOLgpiys3oI4AeAafkcUfE+TKKilminxNyU/+Zlo=
modernc.org/tcl v1.9.2/go.mod h1:aw7OnlIoiuJgu1gwbTZtrKnGpDqH9wyH++jZcxdqNsg=
modernc.org/tcl v1.11.2 h1:mXpsx3AZqJt83uDiFu9UYQVBjNjaWKGCF1YDSlpCL6Y=
@ -2289,6 +2335,8 @@ modernc.org/tcl v1.11.2/go.mod h1:BRzgpajcGdS2qTxniOx9c/dcxjlbA7p12eJNmiriQYo=
modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
modernc.org/z v1.2.20/go.mod h1:zU9FiF4PbHdOTUxw+IF8j7ArBMRPsHgq10uVPt6xTzo=

View File

@ -1,6 +1,6 @@
module github.com/mattermost/focalboard/server
go 1.18
go 1.19
require github.com/golang/mock v1.6.0

View File

@ -2082,8 +2082,8 @@ func TestDuplicateBoard(t *testing.T) {
var duplicateBoardCategoryID string
for _, categoryBoard := range userCategoryBoards {
for _, boardID := range categoryBoard.BoardIDs {
if boardID == duplicateBoard.ID {
for _, boardMetadata := range categoryBoard.BoardMetadata {
if boardMetadata.BoardID == duplicateBoard.ID {
duplicateBoardCategoryID = categoryBoard.Category.ID
}
}

View File

@ -269,9 +269,7 @@ func TestGetCard(t *testing.T) {
})
}
//
// Helpers.
//
func reverse(src []string) []string {
out := make([]string, 0, len(src))
for i := len(src) - 1; i >= 0; i-- {

View File

@ -18,8 +18,8 @@ func TestSidebar(t *testing.T) {
categoryBoards := th.GetUserCategoryBoards("team-id")
require.Equal(t, 1, len(categoryBoards))
require.Equal(t, "Boards", categoryBoards[0].Name)
require.Equal(t, 1, len(categoryBoards[0].BoardIDs))
require.Equal(t, board.ID, categoryBoards[0].BoardIDs[0])
require.Equal(t, 1, len(categoryBoards[0].BoardMetadata))
require.Equal(t, board.ID, categoryBoards[0].BoardMetadata[0].BoardID)
// create a new category, a new board
// and move that board into the new category
@ -39,8 +39,8 @@ func TestSidebar(t *testing.T) {
// the newly created category should be the first one array
// as new categories end up on top in LHS
require.Equal(t, "Category 2", categoryBoards[0].Name)
require.Equal(t, 1, len(categoryBoards[0].BoardIDs))
require.Equal(t, board2.ID, categoryBoards[0].BoardIDs[0])
require.Equal(t, 1, len(categoryBoards[0].BoardMetadata))
require.Equal(t, board2.ID, categoryBoards[0].BoardMetadata[0].BoardID)
// now we'll delete the custom category we created, "Category 2"
// and all it's boards should get moved to the Boards category
@ -48,7 +48,51 @@ func TestSidebar(t *testing.T) {
categoryBoards = th.GetUserCategoryBoards("team-id")
require.Equal(t, 1, len(categoryBoards))
require.Equal(t, "Boards", categoryBoards[0].Name)
require.Equal(t, 2, len(categoryBoards[0].BoardIDs))
require.Contains(t, categoryBoards[0].BoardIDs, board.ID)
require.Contains(t, categoryBoards[0].BoardIDs, board2.ID)
require.Equal(t, 2, len(categoryBoards[0].BoardMetadata))
require.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: board.ID, Hidden: false})
require.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: board2.ID, Hidden: false})
}
func TestHideUnhideBoard(t *testing.T) {
th := SetupTestHelperWithToken(t).Start()
defer th.TearDown()
// we'll create a new board.
// The board should end up in a default "Boards" category
th.CreateBoard("team-id", "O")
// the created board should not be hidden
categoryBoards := th.GetUserCategoryBoards("team-id")
require.Equal(t, 1, len(categoryBoards))
require.Equal(t, "Boards", categoryBoards[0].Name)
require.Equal(t, 1, len(categoryBoards[0].BoardMetadata))
require.False(t, categoryBoards[0].BoardMetadata[0].Hidden)
// now we'll hide the board
response := th.Client.HideBoard("team-id", categoryBoards[0].ID, categoryBoards[0].BoardMetadata[0].BoardID)
th.CheckOK(response)
// verifying if the board has been marked as hidden
categoryBoards = th.GetUserCategoryBoards("team-id")
require.True(t, categoryBoards[0].BoardMetadata[0].Hidden)
// trying to hide the already hidden board.This should have no effect
response = th.Client.HideBoard("team-id", categoryBoards[0].ID, categoryBoards[0].BoardMetadata[0].BoardID)
th.CheckOK(response)
categoryBoards = th.GetUserCategoryBoards("team-id")
require.True(t, categoryBoards[0].BoardMetadata[0].Hidden)
// now we'll unhide the board
response = th.Client.UnhideBoard("team-id", categoryBoards[0].ID, categoryBoards[0].BoardMetadata[0].BoardID)
th.CheckOK(response)
// verifying
categoryBoards = th.GetUserCategoryBoards("team-id")
require.False(t, categoryBoards[0].BoardMetadata[0].Hidden)
// trying to unhide the already visible board.This should have no effect
response = th.Client.UnhideBoard("team-id", categoryBoards[0].ID, categoryBoards[0].BoardMetadata[0].BoardID)
th.CheckOK(response)
categoryBoards = th.GetUserCategoryBoards("team-id")
require.False(t, categoryBoards[0].BoardMetadata[0].Hidden)
}

View File

@ -122,7 +122,7 @@ func main() {
if pDBConfig != nil && len(*pDBConfig) > 0 {
config.DBConfigString = *pDBConfig
// Don't echo, as the confix string may contain passwords
logger.Info("DBConfigString overriden from commandline")
logger.Info("DBConfigString overridden from commandline")
}
if pPort != nil && *pPort > 0 && *pPort != config.Port {
@ -166,6 +166,7 @@ func main() {
}
// StartServer starts the server
//
//export StartServer
func StartServer(webPath *C.char, filesPath *C.char, port int, singleUserToken, dbConfigString, configFilePath *C.char) {
startServer(
@ -179,6 +180,7 @@ func StartServer(webPath *C.char, filesPath *C.char, port int, singleUserToken,
}
// StopServer stops the server
//
//export StopServer
func StopServer() {
stopServer()

View File

@ -9,7 +9,7 @@ type CategoryBoards struct {
// The IDs of boards in this category
// required: true
BoardIDs []string `json:"boardIDs"`
BoardMetadata []CategoryBoardMetadata `json:"boardMetadata"`
// The relative sort order of this board in its category
// required: true
@ -19,4 +19,10 @@ type CategoryBoards struct {
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"`
}

View File

@ -199,6 +199,21 @@ func (mr *MockServicesAPIMockRecorder) GetDirectChannel(arg0, arg1 interface{})
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()

View File

@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
//go:generate mockgen -destination=mocks/propValueResolverMock.go -package mocks . PropValueResolver
package model
import (

View File

@ -30,6 +30,7 @@ var FocalboardBot = &mm_model.Bot{
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)

View File

@ -53,7 +53,7 @@ var (
// LogServerInfo logs information about the server instance.
func LogServerInfo(logger mlog.LoggerIFace) {
logger.Info("FocalBoard Server",
logger.Info("Focalboard server",
mlog.String("version", CurrentVersion),
mlog.String("edition", Edition),
mlog.String("build_number", BuildNumber),

View File

@ -377,7 +377,7 @@ func (s *Server) UpdateAppConfig() {
// Local server
func (s *Server) startLocalModeServer() error {
s.localModeServer = &http.Server{
s.localModeServer = &http.Server{ //nolint:gosec
Handler: s.localRouter,
ConnContext: api.SetContextConn,
}

View File

@ -46,12 +46,14 @@ func NewAudit(options ...mlog.Option) (*Audit, error) {
// Configure provides a new configuration for this audit service.
// Zero or more sources of config can be provided:
// cfgFile - path to file containing JSON
// cfgEscaped - JSON string probably from ENV var
//
// cfgFile - path to file containing JSON
// cfgEscaped - JSON string probably from ENV var
//
// For each case JSON containing log targets is provided. Target name collisions are resolved
// using the following precedence:
// cfgFile > cfgEscaped
//
// cfgFile > cfgEscaped
func (a *Audit) Configure(cfgFile string, cfgEscaped string) error {
return a.auditLogger.Configure(cfgFile, cfgEscaped, nil)
}

View File

@ -17,7 +17,7 @@ type Service struct {
// NewMetricsServer factory method to create a new prometheus server.
func NewMetricsServer(address string, metricsService *Metrics, logger mlog.LoggerIFace) *Service {
return &Service{
&http.Server{
&http.Server{ //nolint:gosec
Addr: address,
Handler: promhttp.HandlerFor(metricsService.registry, promhttp.HandlerOpts{
ErrorLog: logger.StdLogger(mlog.LvlError),

View File

@ -8,9 +8,9 @@ import (
)
type servicesAPI interface {
// GetDirectChannel gets a direct message channel.
// If the channel does not exist it will create it.
GetDirectChannel(userID1, userID2 string) (*mm_model.Channel, error)
// GetDirectChannelOrCreate gets a direct message channel,
// or creates one if it does not already exist
GetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error)
// CreatePost creates a post.
CreatePost(post *mm_model.Post) (*mm_model.Post, error)

View File

@ -53,7 +53,7 @@ func (pd *PluginDelivery) getDirectChannelID(teamID string, subscriberID string,
return "", fmt.Errorf("cannot find user: %w", err)
}
channel, err := pd.getDirectChannel(teamID, user.Id, botID)
if err != nil {
if err != nil || channel == nil {
return "", fmt.Errorf("cannot get direct channel: %w", err)
}
return channel.Id, nil
@ -70,5 +70,5 @@ func (pd *PluginDelivery) getDirectChannel(teamID string, userID string, botID s
if err != nil {
return nil, fmt.Errorf("cannot add bot to team %s: %w", teamID, err)
}
return pd.api.GetDirectChannel(userID, botID)
return pd.api.GetDirectChannelOrCreate(userID, botID)
}

View File

@ -101,6 +101,10 @@ func (m servicesAPIMock) GetDirectChannel(userID1, userID2 string) (*mm_model.Ch
return nil, nil
}
func (m servicesAPIMock) GetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error) {
return nil, nil
}
func (m servicesAPIMock) CreatePost(post *mm_model.Post) (*mm_model.Post, error) {
return post, nil
}

View File

@ -599,7 +599,7 @@ func (s *MattermostAuthLayer) GetLicense() *mmModel.License {
return s.servicesAPI.GetLicense()
}
func boardFields(prefix string) []string {
func boardFields(prefix string) []string { //nolint:unparam
fields := []string{
"id",
"team_id",
@ -1114,10 +1114,12 @@ func (s *MattermostAuthLayer) GetMembersForBoard(boardID string) ([]*model.Board
From(s.tablePrefix + "boards AS B").
Join("ChannelMembers AS CM ON B.channel_id=CM.channelId").
Join("Users as U on CM.userID = U.id").
LeftJoin("Bots as bo on U.id = bo.UserID").
Where(sq.Eq{"B.id": boardID}).
Where(sq.NotEq{"B.channel_id": ""}).
// Filter out guests as they don't have synthetic membership
Where(sq.NotEq{"U.roles": "system_guest"})
Where(sq.NotEq{"U.roles": "system_guest"}).
Where(sq.Eq{"bo.UserId IS NOT NULL": false})
rows, err := query.Query()
if err != nil {

View File

@ -37,17 +37,17 @@ func (m *MockStore) EXPECT() *MockStoreMockRecorder {
}
// AddUpdateCategoryBoard mocks base method.
func (m *MockStore) AddUpdateCategoryBoard(arg0 string, arg1 map[string]string) error {
func (m *MockStore) AddUpdateCategoryBoard(arg0, arg1 string, arg2 []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddUpdateCategoryBoard", arg0, arg1)
ret := m.ctrl.Call(m, "AddUpdateCategoryBoard", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// AddUpdateCategoryBoard indicates an expected call of AddUpdateCategoryBoard.
func (mr *MockStoreMockRecorder) AddUpdateCategoryBoard(arg0, arg1 interface{}) *gomock.Call {
func (mr *MockStoreMockRecorder) AddUpdateCategoryBoard(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUpdateCategoryBoard", reflect.TypeOf((*MockStore)(nil).AddUpdateCategoryBoard), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUpdateCategoryBoard", reflect.TypeOf((*MockStore)(nil).AddUpdateCategoryBoard), arg0, arg1, arg2)
}
// CanSeeUser mocks base method.
@ -999,18 +999,18 @@ func (mr *MockStoreMockRecorder) GetTeam(arg0 interface{}) *gomock.Call {
}
// GetTeamBoardsInsights mocks base method.
func (m *MockStore) GetTeamBoardsInsights(arg0, arg1 string, arg2 int64, arg3, arg4 int, arg5 []string) (*model.BoardInsightsList, error) {
func (m *MockStore) GetTeamBoardsInsights(arg0 string, arg1 int64, arg2, arg3 int, arg4 []string) (*model.BoardInsightsList, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetTeamBoardsInsights", arg0, arg1, arg2, arg3, arg4, arg5)
ret := m.ctrl.Call(m, "GetTeamBoardsInsights", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(*model.BoardInsightsList)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetTeamBoardsInsights indicates an expected call of GetTeamBoardsInsights.
func (mr *MockStoreMockRecorder) GetTeamBoardsInsights(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
func (mr *MockStoreMockRecorder) GetTeamBoardsInsights(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTeamBoardsInsights", reflect.TypeOf((*MockStore)(nil).GetTeamBoardsInsights), arg0, arg1, arg2, arg3, arg4, arg5)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTeamBoardsInsights", reflect.TypeOf((*MockStore)(nil).GetTeamBoardsInsights), arg0, arg1, arg2, arg3, arg4)
}
// GetTeamCount mocks base method.
@ -1545,6 +1545,20 @@ func (mr *MockStoreMockRecorder) SendMessage(arg0, arg1, arg2 interface{}) *gomo
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockStore)(nil).SendMessage), arg0, arg1, arg2)
}
// SetBoardVisibility mocks base method.
func (m *MockStore) SetBoardVisibility(arg0, arg1, arg2 string, arg3 bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetBoardVisibility", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// SetBoardVisibility indicates an expected call of SetBoardVisibility.
func (mr *MockStoreMockRecorder) SetBoardVisibility(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBoardVisibility", reflect.TypeOf((*MockStore)(nil).SetBoardVisibility), arg0, arg1, arg2, arg3)
}
// SetSystemSetting mocks base method.
func (m *MockStore) SetSystemSetting(arg0, arg1 string) error {
m.ctrl.T.Helper()

View File

@ -14,7 +14,7 @@ import (
"github.com/mattermost/mattermost-server/v6/shared/mlog"
)
func (s *SQLStore) getTeamBoardsInsights(db sq.BaseRunner, teamID string, userID string, since int64, offset int, limit int, boardIDs []string) (*model.BoardInsightsList, error) {
func (s *SQLStore) getTeamBoardsInsights(db sq.BaseRunner, teamID string, since int64, offset int, limit int, boardIDs []string) (*model.BoardInsightsList, error) {
boardsHistoryQuery := s.getQueryBuilder(db).
Select("boards.id, boards.icon, boards.title, count(boards_history.id) as count, boards_history.modified_by, boards.created_by").
From(s.tablePrefix + "boards_history as boards_history").

View File

@ -19,14 +19,14 @@ func (s *SQLStore) getUserCategoryBoards(db sq.BaseRunner, userID, teamID string
userCategoryBoards := []model.CategoryBoards{}
for _, category := range categories {
boardIDs, err := s.getCategoryBoardAttributes(db, category.ID)
boardMetadata, err := s.getCategoryBoardAttributes(db, category.ID)
if err != nil {
return nil, err
}
userCategoryBoard := model.CategoryBoards{
Category: category,
BoardIDs: boardIDs,
Category: category,
BoardMetadata: boardMetadata,
}
userCategoryBoards = append(userCategoryBoards, userCategoryBoard)
@ -35,13 +35,12 @@ func (s *SQLStore) getUserCategoryBoards(db sq.BaseRunner, userID, teamID string
return userCategoryBoards, nil
}
func (s *SQLStore) getCategoryBoardAttributes(db sq.BaseRunner, categoryID string) ([]string, error) {
func (s *SQLStore) getCategoryBoardAttributes(db sq.BaseRunner, categoryID string) ([]model.CategoryBoardMetadata, error) {
query := s.getQueryBuilder(db).
Select("board_id").
Select("board_id, COALESCE(hidden, false)").
From(s.tablePrefix + "category_boards").
Where(sq.Eq{
"category_id": categoryID,
"delete_at": 0,
}).
OrderBy("sort_order")
@ -54,21 +53,17 @@ func (s *SQLStore) getCategoryBoardAttributes(db sq.BaseRunner, categoryID strin
return s.categoryBoardsFromRows(rows)
}
func (s *SQLStore) addUpdateCategoryBoard(db sq.BaseRunner, userID string, boardCategoryMapping map[string]string) error {
boardIDs := []string{}
for boardID := range boardCategoryMapping {
boardIDs = append(boardIDs, boardID)
}
func (s *SQLStore) addUpdateCategoryBoard(db sq.BaseRunner, userID, categoryID string, boardIDsParam []string) error {
// we need to de-duplicate this array as Postgres failes to
// handle upsert if there are multiple incoming rows
// that conflict the same existing row.
// For example, having the entry "1" in DB and trying to upsert "1" and "1" will fail
// as there are multiple duplicates of the same "1".
//
// Source: https://stackoverflow.com/questions/42994373/postgresql-on-conflict-cannot-affect-row-a-second-time
boardIDs := utils.DedupeStringArr(boardIDsParam)
if err := s.deleteUserCategoryBoards(db, userID, boardIDs); err != nil {
return err
}
return s.addUserCategoryBoard(db, userID, boardCategoryMapping)
}
func (s *SQLStore) addUserCategoryBoard(db sq.BaseRunner, userID string, boardCategoryMapping map[string]string) error {
if len(boardCategoryMapping) == 0 {
if len(boardIDs) == 0 {
return nil
}
@ -81,73 +76,62 @@ func (s *SQLStore) addUserCategoryBoard(db sq.BaseRunner, userID string, boardCa
"board_id",
"create_at",
"update_at",
"delete_at",
"sort_order",
"hidden",
)
now := utils.GetMillis()
for boardID, categoryID := range boardCategoryMapping {
query = query.
Values(
utils.NewID(utils.IDTypeNone),
userID,
categoryID,
boardID,
now,
now,
0,
0,
)
for _, boardID := range boardIDs {
query = query.Values(
utils.NewID(utils.IDTypeNone),
userID,
categoryID,
boardID,
now,
now,
0,
false,
)
}
if s.dbType == model.MysqlDBType {
query = query.Suffix(
"ON DUPLICATE KEY UPDATE category_id = ?",
categoryID,
)
} else {
query = query.Suffix(
`ON CONFLICT (user_id, board_id)
DO UPDATE SET category_id = EXCLUDED.category_id, update_at = EXCLUDED.update_at`,
)
}
if _, err := query.Exec(); err != nil {
s.logger.Error("addUserCategoryBoard error", mlog.Err(err))
return err
}
return nil
}
func (s *SQLStore) deleteUserCategoryBoards(db sq.BaseRunner, userID string, boardIDs []string) error {
if len(boardIDs) == 0 {
return nil
}
_, err := s.getQueryBuilder(db).
Update(s.tablePrefix+"category_boards").
Set("delete_at", utils.GetMillis()).
Where(sq.Eq{
"user_id": userID,
"board_id": boardIDs,
"delete_at": 0,
}).Exec()
if err != nil {
s.logger.Error(
"deleteUserCategoryBoards delete error",
mlog.String("userID", userID),
mlog.Array("boardID", boardIDs),
mlog.Err(err),
return fmt.Errorf(
"store addUpdateCategoryBoard: failed to upsert user-board-category userID: %s, categoryID: %s, board_count: %d, error: %w",
userID, categoryID, len(boardIDs), err,
)
return err
}
return nil
}
func (s *SQLStore) categoryBoardsFromRows(rows *sql.Rows) ([]string, error) {
blocks := []string{}
func (s *SQLStore) categoryBoardsFromRows(rows *sql.Rows) ([]model.CategoryBoardMetadata, error) {
metadata := []model.CategoryBoardMetadata{}
for rows.Next() {
boardID := ""
if err := rows.Scan(&boardID); err != nil {
datum := model.CategoryBoardMetadata{}
err := rows.Scan(&datum.BoardID, &datum.Hidden)
if err != nil {
s.logger.Error("categoryBoardsFromRows row scan error", mlog.Err(err))
return nil, err
}
blocks = append(blocks, boardID)
metadata = append(metadata, datum)
}
return blocks, nil
return metadata, nil
}
func (s *SQLStore) reorderCategoryBoards(db sq.BaseRunner, categoryID string, newBoardsOrder []string) ([]string, error) {
@ -166,7 +150,6 @@ func (s *SQLStore) reorderCategoryBoards(db sq.BaseRunner, categoryID string, ne
Set("sort_order", updateCase).
Where(sq.Eq{
"category_id": categoryID,
"delete_at": 0,
})
if _, err := query.Exec(); err != nil {
@ -181,3 +164,28 @@ func (s *SQLStore) reorderCategoryBoards(db sq.BaseRunner, categoryID string, ne
return newBoardsOrder, nil
}
func (s *SQLStore) setBoardVisibility(db sq.BaseRunner, userID, categoryID, boardID string, visible bool) error {
query := s.getQueryBuilder(db).
Update(s.tablePrefix+"category_boards").
Set("hidden", !visible).
Where(sq.Eq{
"user_id": userID,
"category_id": categoryID,
"board_id": boardID,
})
if _, err := query.Exec(); err != nil {
s.logger.Error(
"SQLStore setBoardVisibility: failed to update board visibility",
mlog.String("user_id", userID),
mlog.String("board_id", boardID),
mlog.Bool("visible", visible),
mlog.Err(err),
)
return err
}
return nil
}

View File

@ -295,13 +295,14 @@ func (s *SQLStore) ensureMigrationsAppliedUpToVersion(engine *morph.Morph, drive
func (s *SQLStore) GetTemplateHelperFuncs() template.FuncMap {
funcs := template.FuncMap{
"addColumnIfNeeded": s.genAddColumnIfNeeded,
"dropColumnIfNeeded": s.genDropColumnIfNeeded,
"createIndexIfNeeded": s.genCreateIndexIfNeeded,
"renameTableIfNeeded": s.genRenameTableIfNeeded,
"renameColumnIfNeeded": s.genRenameColumnIfNeeded,
"doesTableExist": s.doesTableExist,
"doesColumnExist": s.doesColumnExist,
"addColumnIfNeeded": s.genAddColumnIfNeeded,
"dropColumnIfNeeded": s.genDropColumnIfNeeded,
"createIndexIfNeeded": s.genCreateIndexIfNeeded,
"renameTableIfNeeded": s.genRenameTableIfNeeded,
"renameColumnIfNeeded": s.genRenameColumnIfNeeded,
"doesTableExist": s.doesTableExist,
"doesColumnExist": s.doesColumnExist,
"addConstraintIfNeeded": s.genAddConstraintIfNeeded,
}
return funcs
}
@ -607,6 +608,67 @@ func (s *SQLStore) doesColumnExist(tableName, columnName string) (bool, error) {
return exists, nil
}
func (s *SQLStore) genAddConstraintIfNeeded(tableName, constraintName, constraintType, constraintDefinition string) (string, error) {
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
normTableName := normalizeTablename(s.schemaName, tableName)
var query string
vars := map[string]string{
"schema": s.schemaName,
"constraint_name": constraintName,
"constraint_type": constraintType,
"table_name": tableName,
"constraint_definition": constraintDefinition,
"norm_table_name": normTableName,
}
switch s.dbType {
case model.SqliteDBType:
// SQLite doesn't have a generic way to add constraint. For example, you can only create indexes on existing tables.
// For other constraints, you need to re-build the table. So skipping here.
// Include SQLite specific migration in original migration file.
query = fmt.Sprintf("\n-- Sqlite3 cannot drop constraints; drop constraint '%s' in table '%s' skipped\n", constraintName, tableName)
case model.MysqlDBType:
query = replaceVars(`
SET @stmt = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE constraint_schema = '[[schema]]'
AND constraint_name = '[[constraint_name]]'
AND constraint_type = '[[constraint_type]]'
AND table_name = '[[table_name]]'
) > 0,
'SELECT 1;',
'ALTER TABLE [[norm_table_name]] ADD CONSTRAINT [[constraint_name]] [[constraint_definition]];'
));
PREPARE addConstraintIfNeeded FROM @stmt;
EXECUTE addConstraintIfNeeded;
DEALLOCATE PREPARE addConstraintIfNeeded;
`, vars)
case model.PostgresDBType:
query = replaceVars(`
DO
$$
BEGIN
IF NOT EXISTS (
SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE constraint_schema = '[[schema]]'
AND constraint_name = '[[constraint_name]]'
AND constraint_type = '[[constraint_type]]'
AND table_name = '[[table_name]]'
) THEN
ALTER TABLE [[norm_table_name]] ADD CONSTRAINT [[constraint_name]] [[constraint_definition]];
END IF;
END;
$$
LANGUAGE plpgsql;
`, vars)
}
return query, nil
}
func addPrefixIfNeeded(s, prefix string) string {
if !strings.HasPrefix(s, prefix) {
return prefix + s

View File

@ -0,0 +1 @@
DELETE FROM {{.prefix}}category_boards WHERE delete_at > 0;

View File

@ -0,0 +1,3 @@
{{ if or .postgres .mysql }}
{{ dropColumnIfNeeded "category_boards" "delete_at" }}
{{end}}

View File

@ -0,0 +1 @@
{{ addColumnIfNeeded "category_boards" "hidden" "boolean" "" }}

View File

@ -0,0 +1,26 @@
{{if or .mysql .postgres}}
{{ addConstraintIfNeeded "category_boards" "unique_user_category_board" "UNIQUE" "UNIQUE(user_id, board_id)"}}
{{end}}
{{if .sqlite}}
ALTER TABLE {{.prefix}}category_boards RENAME TO {{.prefix}}category_boards_old;
CREATE TABLE {{.prefix}}category_boards (
id varchar(36) NOT NULL,
user_id varchar(36) NOT NULL,
category_id varchar(36) NOT NULL,
board_id VARCHAR(36) NOT NULL,
create_at BIGINT,
update_at BIGINT,
sort_order BIGINT,
hidden boolean,
PRIMARY KEY (id),
CONSTRAINT unique_user_category_board UNIQUE (user_id, board_id)
);
INSERT INTO {{.prefix}}category_boards
(id, user_id, category_id, board_id, create_at, update_at, sort_order, hidden)
SELECT id, user_id, category_id, board_id, create_at, update_at, sort_order, hidden FROM {{.prefix}}category_boards_old;
DROP TABLE {{.prefix}}category_boards_old;
{{end}}

View File

@ -0,0 +1,57 @@
{{if .plugin}}
{{if .mysql}}
UPDATE {{.prefix}}category_boards AS fcb
JOIN Preferences p
ON fcb.user_id = p.userid
AND p.category = 'focalboard'
AND p.name = 'hiddenBoardIDs'
SET hidden = true
WHERE p.value LIKE concat('%', fcb.board_id, '%');
{{end}}
{{if .postgres}}
UPDATE {{.prefix}}category_boards as fcb
SET hidden = true
FROM preferences p
WHERE p.userid = fcb.user_id
AND p.category = 'focalboard'
AND p.name = 'hiddenBoardIDs'
AND p.value like ('%' || fcb.board_id || '%');
{{end}}
{{else}}
{{if .mysql}}
UPDATE {{.prefix}}category_boards AS fcb
JOIN {{.prefix}}preferences p
ON fcb.user_id = p.userid
AND p.category = 'focalboard'
AND p.name = 'hiddenBoardIDs'
SET hidden = true
WHERE p.value LIKE concat('%', fcb.board_id, '%');
{{end}}
{{if .postgres}}
UPDATE {{.prefix}}category_boards as fcb
SET hidden = true
FROM {{.prefix}}preferences p
WHERE p.userid = fcb.user_id
AND p.category = 'focalboard'
AND p.name = 'hiddenBoardIDs'
AND p.value like ('%' || fcb.board_id || '%');
{{end}}
{{end}}
{{if .sqlite}}
UPDATE {{.prefix}}category_boards
SET hidden = true
WHERE (user_id || '_' || board_id)
IN
(
SELECT (fcb.user_id || '_' || fcb.board_id)
FROM {{.prefix}}category_boards AS fcb
JOIN {{.prefix}}preferences p
ON p.userid = fcb.user_id
AND p.category = 'focalboard'
AND p.name = 'hiddenBoardIDs' WHERE
p.value LIKE ('%' || fcb.board_id || '%')
);
{{end}}

View File

@ -0,0 +1,5 @@
{{if .plugin}}
DELETE FROM Preferences WHERE category = 'focalboard' AND name = 'hiddenBoardIDs';
{{else}}
DELETE FROM {{.prefix}}preferences WHERE category = 'focalboard' AND name = 'hiddenBoardIDs';
{{end}}

View File

@ -0,0 +1,6 @@
INSERT INTO focalboard_category_boards values
('id-1', 'user_id-1', 'category-id-1', 'board-id-1', 1672988834402, 1672988834402, 0, 0),
('id-2', 'user_id-1', 'category-id-2', 'board-id-1', 1672988834402, 1672988834402, 0, 0),
('id-3', 'user_id-2', 'category-id-3', 'board-id-2', 1672988834402, 1672988834402, 1672988834402, 0),
('id-4', 'user_id-2', 'category-id-3', 'board-id-4', 1672988834402, 1672988834402, 0, 0),
('id-5', 'user_id-3', 'category-id-4', 'board-id-3', 1672988834402, 1672988834402, 1672988834402, 0);

View File

@ -0,0 +1,6 @@
INSERT INTO focalboard_category_boards values
('id-1', 'user_id-1', 'category-id-1', 'board-id-1', 1672988834402, 1672988834402, 0, 0),
('id-2', 'user_id-1', 'category-id-2', 'board-id-1', 1672988834402, 1672988834402, 0, 0),
('id-3', 'user_id-2', 'category-id-3', 'board-id-2', 1672988834402, 1672988834402, 0, 0),
('id-4', 'user_id-2', 'category-id-3', 'board-id-4', 1672988834402, 1672988834402, 0, 0),
('id-5', 'user_id-3', 'category-id-4', 'board-id-3', 1672988834402, 1672988834402, 0, 0);

View File

@ -0,0 +1 @@
ALTER TABLE focalboard_category_boards DROP COLUMN delete_at;

View File

@ -0,0 +1 @@
ALTER TABLE focalboard_category_boards ADD COLUMN hidden boolean;

View File

@ -0,0 +1,10 @@
INSERT INTO focalboard_category_boards VALUES
('id-1', 'user-id-1', 'category-id-1', 'board-id-1', 1672889246832, 1672889246832, 0, false),
('id-2', 'user-id-1', 'category-id-2', 'board-id-2', 1672889246832, 1672889246832, 0, false),
('id-3', 'user-id-2', 'category-id-3', 'board-id-3', 1672889246832, 1672889246832, 0, false),
('id-4', 'user-id-2', 'category-id-3', 'board-id-4', 1672889246832, 1672889246832, 0, false),
('id-5', 'user-id-3', 'category-id-4', 'board-id-5', 1672889246832, 1672889246832, 0, false);
INSERT INTO Preferences VALUES
('user-id-1', 'focalboard', 'hiddenBoardIDs', '["board-id-1"]'),
('user-id-2', 'focalboard', 'hiddenBoardIDs', '["board-id-3", "board-id-4"]');

View File

@ -0,0 +1,6 @@
INSERT INTO focalboard_category_boards VALUES
('id-1', 'user-id-1', 'category-id-1', 'board-id-1', 1672889246832, 1672889246832, 0, false),
('id-2', 'user-id-1', 'category-id-2', 'board-id-2', 1672889246832, 1672889246832, 0, false),
('id-3', 'user-id-2', 'category-id-3', 'board-id-3', 1672889246832, 1672889246832, 0, false),
('id-4', 'user-id-2', 'category-id-3', 'board-id-4', 1672889246832, 1672889246832, 0, false),
('id-5', 'user-id-3', 'category-id-4', 'board-id-5', 1672889246832, 1672889246832, 0, false);

View File

@ -0,0 +1,10 @@
INSERT INTO focalboard_category_boards VALUES
('id-1', 'user-id-1', 'category-id-1', 'board-id-1', 1672889246832, 1672889246832, 0, false),
('id-2', 'user-id-1', 'category-id-2', 'board-id-2', 1672889246832, 1672889246832, 0, false),
('id-3', 'user-id-2', 'category-id-3', 'board-id-3', 1672889246832, 1672889246832, 0, false),
('id-4', 'user-id-2', 'category-id-3', 'board-id-4', 1672889246832, 1672889246832, 0, false),
('id-5', 'user-id-3', 'category-id-4', 'board-id-5', 1672889246832, 1672889246832, 0, false);
INSERT INTO Preferences VALUES
('user-id-1', 'focalboard', 'hiddenBoardIDs', ''),
('user-id-2', 'focalboard', 'hiddenBoardIDs', '');

View File

@ -0,0 +1,10 @@
INSERT INTO focalboard_category_boards VALUES
('id-1', 'user-id-1', 'category-id-1', 'board-id-1', 1672889246832, 1672889246832, 0, false),
('id-2', 'user-id-1', 'category-id-2', 'board-id-2', 1672889246832, 1672889246832, 0, false),
('id-3', 'user-id-2', 'category-id-3', 'board-id-3', 1672889246832, 1672889246832, 0, false),
('id-4', 'user-id-2', 'category-id-3', 'board-id-4', 1672889246832, 1672889246832, 0, false),
('id-5', 'user-id-3', 'category-id-4', 'board-id-5', 1672889246832, 1672889246832, 0, false);
INSERT INTO focalboard_preferences VALUES
('user-id-1', 'focalboard', 'hiddenBoardIDs', '["board-id-1"]'),
('user-id-2', 'focalboard', 'hiddenBoardIDs', '["board-id-3", "board-id-4"]');

View File

@ -0,0 +1,10 @@
INSERT INTO focalboard_category_boards VALUES
('id-1', 'user-id-1', 'category-id-1', 'board-id-1', 1672889246832, 1672889246832, 0, false),
('id-2', 'user-id-1', 'category-id-2', 'board-id-2', 1672889246832, 1672889246832, 0, false),
('id-3', 'user-id-2', 'category-id-3', 'board-id-3', 1672889246832, 1672889246832, 0, false),
('id-4', 'user-id-2', 'category-id-3', 'board-id-4', 1672889246832, 1672889246832, 0, false),
('id-5', 'user-id-3', 'category-id-4', 'board-id-5', 1672889246832, 1672889246832, 0, false);
INSERT INTO focalboard_preferences VALUES
('user-id-1', 'focalboard', 'hiddenBoardIDs', ''),
('user-id-2', 'focalboard', 'hiddenBoardIDs', '');

View File

@ -0,0 +1,5 @@
INSERT INTO Preferences VALUES
('user-id-1', 'focalboard', 'hiddenBoardIDs', '["board-id-1"]'),
('user-id-2', 'focalboard', 'hiddenBoardIDs', '["board-id-3", "board-id-4"]'),
('user-id-3', 'lorem', 'lorem', ''),
('user-id-4', 'ipsum', 'ipsum', '');

View File

@ -0,0 +1,5 @@
INSERT INTO focalboard_preferences VALUES
('user-id-1', 'focalboard', 'hiddenBoardIDs', '["board-id-1"]'),
('user-id-2', 'focalboard', 'hiddenBoardIDs', '["board-id-3", "board-id-4"]'),
('user-id-2', 'lorem', 'lorem', ''),
('user-id-2', 'ipsum', 'ipsum', '');

View File

@ -15,6 +15,18 @@ type TestHelper struct {
isPlugin bool
}
func (th *TestHelper) IsPostgres() bool {
return th.f.DB().DriverName() == "postgres"
}
func (th *TestHelper) IsMySQL() bool {
return th.f.DB().DriverName() == "mysql"
}
func (th *TestHelper) IsSQLite() bool {
return th.f.DB().DriverName() == "sqlite3"
}
func SetupPluginTestHelper(t *testing.T) (*TestHelper, func()) {
dbType := strings.TrimSpace(os.Getenv("FOCALBOARD_STORE_TEST_DB_TYPE"))
if dbType == "" || dbType == model.SqliteDBType {

View File

@ -0,0 +1,57 @@
package migrationstests
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test34DropDeleteAtColumnMySQLPostgres(t *testing.T) {
t.Run("column exists", func(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
th.f.MigrateToStep(34)
// migration 34 only works for MySQL and PostgreSQL
if th.IsMySQL() {
var count int
query := "SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'focalboard_category_boards' AND column_name = 'delete_at'"
th.f.DB().Get(&count, query)
require.Equal(t, 0, count)
} else if th.IsPostgres() {
var count int
query := "select count(*) from information_schema.columns where table_name = 'focalboard_category_boards' and column_name = 'delete_at'"
th.f.DB().Get(&count, query)
require.Equal(t, 0, count)
}
})
t.Run("column already deleted", func(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
// For migration 34, we don't drop column
// on SQLite, so no need to test for it.
if th.IsSQLite() {
return
}
th.f.MigrateToStep(33).
ExecFile("./fixtures/test34_drop_delete_at_column.sql")
th.f.MigrateToStep(34)
if th.IsMySQL() {
var count int
query := "SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'focalboard_category_boards' AND column_name = 'delete_at'"
th.f.DB().Get(&count, query)
require.Equal(t, 0, count)
} else if th.IsPostgres() {
var count int
query := "select count(*) from information_schema.columns where table_name = 'focalboard_category_boards' and column_name = 'delete_at'"
th.f.DB().Get(&count, query)
require.Equal(t, 0, count)
}
})
}

View File

@ -0,0 +1,27 @@
package migrationstests
import "testing"
func Test35AddHIddenColumnToCategoryBoards(t *testing.T) {
t.Run("base case - column doesn't already exist", func(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
th.f.MigrateToStep(35)
})
t.Run("column already exist", func(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
// We don't support adding column in idempotent manner
// for SQLite, so no need to check for it.
if th.IsSQLite() {
return
}
th.f.MigrateToStep(34).
ExecFile("./fixtures/test35_add_hidden_column.sql")
th.f.MigrateToStep(35)
})
}

View File

@ -0,0 +1,69 @@
package migrationstests
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test36AddUniqueConstraintToCategoryBoards(t *testing.T) {
t.Run("constraint doesn't alreadt exists", func(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
th.f.MigrateToStep(36)
// verifying if constraint has been added
//can't verify in sqlite, so skipping it
if th.IsSQLite() {
return
}
var count int
query := "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS " +
"WHERE constraint_name = 'unique_user_category_board' " +
"AND constraint_type = 'UNIQUE' " +
"AND table_name = 'focalboard_category_boards'"
th.f.DB().Get(&count, query)
require.Equal(t, 1, count)
})
t.Run("constraint already exists", func(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
// SQLIte doesn't support adding constraint to existing table
// and neither do we, so skipping for sqlite
if th.IsSQLite() {
return
}
th.f.MigrateToStep(35)
if th.IsMySQL() {
th.f.DB().Exec("alter table focalboard_category_boards add constraint unique_user_category_board UNIQUE(user_id, board_id);")
} else if th.IsPostgres() {
th.f.DB().Exec("ALTER TABLE focalboard_category_boards ADD CONSTRAINT unique_user_category_board UNIQUE(user_id, board_id);")
}
th.f.MigrateToStep(36)
var schema string
if th.IsMySQL() {
schema = "DATABASE()"
} else if th.IsPostgres() {
schema = "'public'"
}
var count int
query := "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS " +
"WHERE constraint_schema = " + schema + " " +
"AND constraint_name = 'unique_user_category_board' " +
"AND constraint_type = 'UNIQUE' " +
"AND table_name = 'focalboard_category_boards'"
th.f.DB().Get(&count, query)
require.Equal(t, 1, count)
})
}

View File

@ -0,0 +1,134 @@
package migrationstests
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test37MigrateHiddenBoardIDTest(t *testing.T) {
t.Run("no existing hidden boards exist", func(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
th.f.MigrateToStep(37)
})
t.Run("SQLite - existsing category boards with some hidden boards", func(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
if th.IsMySQL() || th.IsPostgres() {
return
}
th.f.MigrateToStep(36).
ExecFile("./fixtures/test37_valid_data_sqlite.sql")
th.f.MigrateToStep(37)
type categoryBoard struct {
User_ID string
Category_ID string
Board_ID string
Hidden bool
}
var hiddenCategoryBoards []categoryBoard
query := "SELECT user_id, category_id, board_id, hidden FROM focalboard_category_boards WHERE hidden = true"
err := th.f.DB().Select(&hiddenCategoryBoards, query)
require.NoError(t, err)
require.Equal(t, 3, len(hiddenCategoryBoards))
require.Contains(t, hiddenCategoryBoards, categoryBoard{User_ID: "user-id-1", Category_ID: "category-id-1", Board_ID: "board-id-1", Hidden: true})
require.Contains(t, hiddenCategoryBoards, categoryBoard{User_ID: "user-id-2", Category_ID: "category-id-3", Board_ID: "board-id-3", Hidden: true})
require.Contains(t, hiddenCategoryBoards, categoryBoard{User_ID: "user-id-2", Category_ID: "category-id-3", Board_ID: "board-id-4", Hidden: true})
})
t.Run("MySQL and PostgreSQL - existsing category boards with some hidden boards", func(t *testing.T) {
th, tearDown := SetupPluginTestHelper(t)
defer tearDown()
if th.IsSQLite() {
return
}
th.f.MigrateToStep(36).
ExecFile("./fixtures/test37_valid_data.sql")
th.f.MigrateToStep(37)
type categoryBoard struct {
User_ID string
Category_ID string
Board_ID string
Hidden bool
}
var hiddenCategoryBoards []categoryBoard
query := "SELECT user_id, category_id, board_id, hidden FROM focalboard_category_boards WHERE hidden = true"
err := th.f.DB().Select(&hiddenCategoryBoards, query)
require.NoError(t, err)
require.Equal(t, 3, len(hiddenCategoryBoards))
require.Contains(t, hiddenCategoryBoards, categoryBoard{User_ID: "user-id-1", Category_ID: "category-id-1", Board_ID: "board-id-1", Hidden: true})
require.Contains(t, hiddenCategoryBoards, categoryBoard{User_ID: "user-id-2", Category_ID: "category-id-3", Board_ID: "board-id-3", Hidden: true})
require.Contains(t, hiddenCategoryBoards, categoryBoard{User_ID: "user-id-2", Category_ID: "category-id-3", Board_ID: "board-id-4", Hidden: true})
})
t.Run("no hidden boards", func(t *testing.T) {
th, tearDown := SetupPluginTestHelper(t)
defer tearDown()
th.f.MigrateToStep(36).
ExecFile("./fixtures/test37_valid_data_no_hidden_boards.sql")
th.f.MigrateToStep(37)
var count int
query := "SELECT count(*) FROM focalboard_category_boards WHERE hidden = true"
err := th.f.DB().Get(&count, query)
require.NoError(t, err)
require.Equal(t, 0, count)
})
t.Run("SQLite - preference but no hidden board", func(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
if th.IsMySQL() || th.IsPostgres() {
return
}
th.f.MigrateToStep(36).
ExecFile("./fixtures/test37_valid_data_sqlite_preference_but_no_hidden_board.sql")
th.f.MigrateToStep(37)
var count int
query := "SELECT count(*) FROM focalboard_category_boards WHERE hidden = true"
err := th.f.DB().Get(&count, query)
require.NoError(t, err)
require.Equal(t, 0, count)
})
t.Run("MySQL and PostgreSQL - preference but no hidden board", func(t *testing.T) {
th, tearDown := SetupPluginTestHelper(t)
defer tearDown()
if th.IsSQLite() {
return
}
th.f.MigrateToStep(36).
ExecFile("./fixtures/test37_valid_data_preference_but_no_hidden_board.sql")
th.f.MigrateToStep(37)
var count int
query := "SELECT count(*) FROM focalboard_category_boards WHERE hidden = true"
err := th.f.DB().Get(&count, query)
require.NoError(t, err)
require.Equal(t, 0, count)
})
}

View File

@ -0,0 +1,63 @@
package migrationstests
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test38RemoveHiddenBoardIDsFromPreferences(t *testing.T) {
t.Run("standalone - no data exist", func(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
th.f.MigrateToStep(38)
})
t.Run("plugin - no data exist", func(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
th.f.MigrateToStep(38)
})
t.Run("standalone - some data exist", func(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
th.f.MigrateToStep(37).
ExecFile("./fixtures/test38_add_standalone_preferences.sql")
// verify existing data count
var count int
countQuery := "SELECT COUNT(*) FROM focalboard_preferences"
err := th.f.DB().Get(&count, countQuery)
require.NoError(t, err)
require.Equal(t, 4, count)
th.f.MigrateToStep(38)
// now the count should be 0
err = th.f.DB().Get(&count, countQuery)
require.NoError(t, err)
require.Equal(t, 2, count)
})
t.Run("plugin - some data exist", func(t *testing.T) {
th, tearDown := SetupPluginTestHelper(t)
defer tearDown()
th.f.MigrateToStep(37).
ExecFile("./fixtures/test38_add_plugin_preferences.sql")
// verify existing data count
var count int
countQuery := "SELECT COUNT(*) FROM Preferences"
err := th.f.DB().Get(&count, countQuery)
require.NoError(t, err)
require.Equal(t, 4, count)
th.f.MigrateToStep(38)
// now the count should be 0
err = th.f.DB().Get(&count, countQuery)
require.NoError(t, err)
require.Equal(t, 2, count)
})
}

View File

@ -0,0 +1,63 @@
package migrationstests
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test33RemoveDeletedCategoryBoards(t *testing.T) {
t.Run("base case - no data in table", func(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
th.f.MigrateToStep(33)
})
t.Run("existing data - 2 soft deleted records", func(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
th.f.MigrateToStep(32).
ExecFile("./fixtures/test33_with_deleted_data.sql")
// cound total records
var count int
err := th.f.DB().Get(&count, "SELECT COUNT(*) FROM focalboard_category_boards")
require.NoError(t, err)
require.Equal(t, 5, count)
// now we run the migration
th.f.MigrateToStep(33)
// and verify record count again.
// The soft deleted records should have been removed from the DB now
err = th.f.DB().Get(&count, "SELECT COUNT(*) FROM focalboard_category_boards")
require.NoError(t, err)
require.Equal(t, 3, count)
})
t.Run("existing data - no soft deleted records", func(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
th.f.MigrateToStep(32).
ExecFile("./fixtures/test33_with_no_deleted_data.sql")
// cound total records
var count int
err := th.f.DB().Get(&count, "SELECT COUNT(*) FROM focalboard_category_boards")
require.NoError(t, err)
require.Equal(t, 5, count)
// now we run the migration
th.f.MigrateToStep(33)
// and verify record count again.
// Since there were no soft-deleted records, nothing should have been
// deleted from the database.
err = th.f.DB().Get(&count, "SELECT COUNT(*) FROM focalboard_category_boards")
require.NoError(t, err)
require.Equal(t, 5, count)
})
}

View File

@ -22,15 +22,15 @@ import (
"github.com/mattermost/mattermost-server/v6/shared/mlog"
)
func (s *SQLStore) AddUpdateCategoryBoard(userID string, boardCategoryMapping map[string]string) error {
func (s *SQLStore) AddUpdateCategoryBoard(userID string, categoryID string, boardIDs []string) error {
if s.dbType == model.SqliteDBType {
return s.addUpdateCategoryBoard(s.db, userID, boardCategoryMapping)
return s.addUpdateCategoryBoard(s.db, userID, categoryID, boardIDs)
}
tx, txErr := s.db.BeginTx(context.Background(), nil)
if txErr != nil {
return txErr
}
err := s.addUpdateCategoryBoard(tx, userID, boardCategoryMapping)
err := s.addUpdateCategoryBoard(tx, userID, categoryID, boardIDs)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
s.logger.Error("transaction rollback error", mlog.Err(rollbackErr), mlog.String("methodName", "AddUpdateCategoryBoard"))
@ -144,7 +144,7 @@ func (s *SQLStore) CreateUser(user *model.User) (*model.User, error) {
}
func (s *SQLStore) DBVersion() string {
return s.dBVersion(s.db)
return s.dBVersion()
}
@ -513,8 +513,8 @@ func (s *SQLStore) GetTeam(ID string) (*model.Team, error) {
}
func (s *SQLStore) GetTeamBoardsInsights(teamID string, userID string, since int64, offset int, limit int, boardIDs []string) (*model.BoardInsightsList, error) {
return s.getTeamBoardsInsights(s.db, teamID, userID, since, offset, limit, boardIDs)
func (s *SQLStore) GetTeamBoardsInsights(teamID string, since int64, offset int, limit int, boardIDs []string) (*model.BoardInsightsList, error) {
return s.getTeamBoardsInsights(s.db, teamID, since, offset, limit, boardIDs)
}
@ -850,6 +850,11 @@ func (s *SQLStore) SendMessage(message string, postType string, receipts []strin
}
func (s *SQLStore) SetBoardVisibility(userID string, categoryID string, boardID string, visible bool) error {
return s.setBoardVisibility(s.db, userID, categoryID, boardID, visible)
}
func (s *SQLStore) SetSystemSetting(key string, value string) error {
return s.setSystemSetting(s.db, key, value)

View File

@ -136,7 +136,7 @@ func (s *SQLStore) getQueryBuilder(db sq.BaseRunner) sq.StatementBuilderType {
return builder.RunWith(db)
}
func (s *SQLStore) escapeField(fieldName string) string {
func (s *SQLStore) escapeField(fieldName string) string { //nolint:unparam
if s.dbType == model.MysqlDBType {
return "`" + fieldName + "`"
}
@ -185,7 +185,7 @@ func (s *SQLStore) getChannel(db sq.BaseRunner, teamID, channel string) (*mmMode
return nil, store.NewNotSupportedError("get channel not supported on standalone mode")
}
func (s *SQLStore) dBVersion(db sq.BaseRunner) string {
func (s *SQLStore) dBVersion() string {
var version string
var row *sql.Row

View File

@ -131,8 +131,9 @@ type Store interface {
SaveFileInfo(fileInfo *mmModel.FileInfo) error
// @withTransaction
AddUpdateCategoryBoard(userID string, boardCategoryMapping map[string]string) error
AddUpdateCategoryBoard(userID, categoryID string, boardIDs []string) error
ReorderCategoryBoards(categoryID string, newBoardsOrder []string) ([]string, error)
SetBoardVisibility(userID, categoryID, boardID string, visible bool) error
CreateSubscription(sub *model.Subscription) (*model.Subscription, error)
DeleteSubscription(blockID string, subscriberID string) error
@ -168,7 +169,7 @@ type Store interface {
SendMessage(message, postType string, receipts []string) error
// Insights
GetTeamBoardsInsights(teamID string, userID string, since int64, offset int, limit int, boardIDs []string) (*model.BoardInsightsList, error)
GetTeamBoardsInsights(teamID string, since int64, offset int, limit int, boardIDs []string) (*model.BoardInsightsList, error)
GetUserBoardsInsights(teamID string, userID string, since int64, offset int, limit int, boardIDs []string) (*model.BoardInsightsList, error)
GetUserTimezone(userID string) (string, error)

View File

@ -72,7 +72,7 @@ func getBoardsInsightsTest(t *testing.T, store store.Store) {
boardsUser2, _ := store.GetBoardsForUserAndTeam(testInsightsUserID1, testTeamID, true)
t.Run("team insights", func(t *testing.T) {
boardIDs := []string{boardsUser1[0].ID, boardsUser1[1].ID, boardsUser1[2].ID}
topTeamBoards, err := store.GetTeamBoardsInsights(testTeamID, testUserID,
topTeamBoards, err := store.GetTeamBoardsInsights(testTeamID,
0, 0, 10, boardIDs)
require.NoError(t, err)
require.Len(t, topTeamBoards.Items, 3)

View File

@ -294,11 +294,11 @@ func testReorderCategoryBoards(t *testing.T, store store.Store) {
})
assert.NoError(t, err)
err = store.AddUpdateCategoryBoard("user_id", map[string]string{
"board_id_1": "category_id_1",
"board_id_2": "category_id_1",
"board_id_3": "category_id_1",
"board_id_4": "category_id_1",
err = store.AddUpdateCategoryBoard("user_id", "category_id_1", []string{
"board_id_1",
"board_id_2",
"board_id_3",
"board_id_4",
})
assert.NoError(t, err)
@ -306,11 +306,11 @@ func testReorderCategoryBoards(t *testing.T, store store.Store) {
categoryBoards, err := store.GetUserCategoryBoards("user_id", "team_id")
assert.NoError(t, err)
assert.Equal(t, 1, len(categoryBoards))
assert.Equal(t, 4, len(categoryBoards[0].BoardIDs))
assert.Contains(t, categoryBoards[0].BoardIDs, "board_id_1")
assert.Contains(t, categoryBoards[0].BoardIDs, "board_id_2")
assert.Contains(t, categoryBoards[0].BoardIDs, "board_id_3")
assert.Contains(t, categoryBoards[0].BoardIDs, "board_id_4")
assert.Equal(t, 4, len(categoryBoards[0].BoardMetadata))
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_1", Hidden: false})
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_2", Hidden: false})
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_3", Hidden: false})
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_4", Hidden: false})
// reordering
newOrder, err := store.ReorderCategoryBoards("category_id_1", []string{
@ -329,9 +329,9 @@ func testReorderCategoryBoards(t *testing.T, store store.Store) {
categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id")
assert.NoError(t, err)
assert.Equal(t, 1, len(categoryBoards))
assert.Equal(t, 4, len(categoryBoards[0].BoardIDs))
assert.Equal(t, "board_id_3", categoryBoards[0].BoardIDs[0])
assert.Equal(t, "board_id_1", categoryBoards[0].BoardIDs[1])
assert.Equal(t, "board_id_2", categoryBoards[0].BoardIDs[2])
assert.Equal(t, "board_id_4", categoryBoards[0].BoardIDs[3])
assert.Equal(t, 4, len(categoryBoards[0].BoardMetadata))
assert.Equal(t, "board_id_3", categoryBoards[0].BoardMetadata[0].BoardID)
assert.Equal(t, "board_id_1", categoryBoards[0].BoardMetadata[1].BoardID)
assert.Equal(t, "board_id_2", categoryBoards[0].BoardMetadata[2].BoardID)
assert.Equal(t, "board_id_4", categoryBoards[0].BoardMetadata[3].BoardID)
}

View File

@ -15,6 +15,18 @@ func StoreTestCategoryBoardsStore(t *testing.T, setup func(t *testing.T) (store.
defer tearDown()
testGetUserCategoryBoards(t, store)
})
t.Run("AddUpdateCategoryBoard", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
testAddUpdateCategoryBoard(t, store)
})
t.Run("SetBoardVisibility", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
testSetBoardVisibility(t, store)
})
}
func testGetUserCategoryBoards(t *testing.T, store store.Store) {
@ -60,14 +72,14 @@ func testGetUserCategoryBoards(t *testing.T, store store.Store) {
// Adding Board 1 and Board 2 to Category 1
// The boards don't need to exists in DB for this test
err = store.AddUpdateCategoryBoard("user_id_1", map[string]string{"board_1": "category_id_1"})
err = store.AddUpdateCategoryBoard("user_id_1", "category_id_1", []string{"board_1"})
assert.NoError(t, err)
err = store.AddUpdateCategoryBoard("user_id_1", map[string]string{"board_2": "category_id_1"})
err = store.AddUpdateCategoryBoard("user_id_1", "category_id_1", []string{"board_2"})
assert.NoError(t, err)
// Adding Board 3 to Category 2
err = store.AddUpdateCategoryBoard("user_id_1", map[string]string{"board_3": "category_id_2"})
err = store.AddUpdateCategoryBoard("user_id_1", "category_id_2", []string{"board_3"})
assert.NoError(t, err)
// we'll leave category 3 empty
@ -94,13 +106,13 @@ func testGetUserCategoryBoards(t *testing.T, store store.Store) {
}
assert.NotEmpty(t, category1BoardCategory)
assert.Equal(t, 2, len(category1BoardCategory.BoardIDs))
assert.Equal(t, 2, len(category1BoardCategory.BoardMetadata))
assert.NotEmpty(t, category1BoardCategory)
assert.Equal(t, 1, len(category2BoardCategory.BoardIDs))
assert.Equal(t, 1, len(category2BoardCategory.BoardMetadata))
assert.NotEmpty(t, category1BoardCategory)
assert.Equal(t, 0, len(category3BoardCategory.BoardIDs))
assert.Equal(t, 0, len(category3BoardCategory.BoardMetadata))
t.Run("get empty category boards", func(t *testing.T) {
userCategoryBoards, err := store.GetUserCategoryBoards("nonexistent-user-id", "nonexistent-team-id")
@ -108,3 +120,142 @@ func testGetUserCategoryBoards(t *testing.T, store store.Store) {
assert.Empty(t, userCategoryBoards)
})
}
func testAddUpdateCategoryBoard(t *testing.T, store store.Store) {
// creating few boards and categories to later associoate with the category
_, _, err := store.CreateBoardsAndBlocksWithAdmin(&model.BoardsAndBlocks{
Boards: []*model.Board{
{
ID: "board_id_1",
TeamID: "team_id",
},
{
ID: "board_id_2",
TeamID: "team_id",
},
},
}, "user_id")
assert.NoError(t, err)
err = store.CreateCategory(model.Category{
ID: "category_id",
Name: "Category",
UserID: "user_id",
TeamID: "team_id",
})
assert.NoError(t, err)
// adding a few boards to the category
err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_1", "board_id_2"})
assert.NoError(t, err)
// verify inserted data
categoryBoards, err := store.GetUserCategoryBoards("user_id", "team_id")
assert.NoError(t, err)
assert.Equal(t, 1, len(categoryBoards))
assert.Equal(t, "category_id", categoryBoards[0].ID)
assert.Equal(t, 2, len(categoryBoards[0].BoardMetadata))
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_1", Hidden: false})
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_2", Hidden: false})
// adding new boards to the same category
err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_3"})
assert.NoError(t, err)
// verify inserted data
categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id")
assert.NoError(t, err)
assert.Equal(t, 1, len(categoryBoards))
assert.Equal(t, "category_id", categoryBoards[0].ID)
assert.Equal(t, 3, len(categoryBoards[0].BoardMetadata))
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_3", Hidden: false})
// passing empty array
err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{})
assert.NoError(t, err)
// verify inserted data
categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id")
assert.NoError(t, err)
assert.Equal(t, 1, len(categoryBoards))
assert.Equal(t, "category_id", categoryBoards[0].ID)
assert.Equal(t, 3, len(categoryBoards[0].BoardMetadata))
// passing duplicate data in input
err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_4", "board_id_4"})
assert.NoError(t, err)
// verify inserted data
categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id")
assert.NoError(t, err)
assert.Equal(t, 1, len(categoryBoards))
assert.Equal(t, "category_id", categoryBoards[0].ID)
assert.Equal(t, 4, len(categoryBoards[0].BoardMetadata))
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_4", Hidden: false})
// adding already added board
err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_1", "board_id_2"})
assert.NoError(t, err)
// verify inserted data
categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id")
assert.NoError(t, err)
assert.Equal(t, 1, len(categoryBoards))
assert.Equal(t, "category_id", categoryBoards[0].ID)
assert.Equal(t, 4, len(categoryBoards[0].BoardMetadata))
// passing already added board along with a new board
err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_1", "board_id_5"})
assert.NoError(t, err)
// verify inserted data
categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id")
assert.NoError(t, err)
assert.Equal(t, 1, len(categoryBoards))
assert.Equal(t, "category_id", categoryBoards[0].ID)
assert.Equal(t, 5, len(categoryBoards[0].BoardMetadata))
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_5", Hidden: false})
}
func testSetBoardVisibility(t *testing.T, store store.Store) {
_, _, err := store.CreateBoardsAndBlocksWithAdmin(&model.BoardsAndBlocks{
Boards: []*model.Board{
{
ID: "board_id_1",
TeamID: "team_id",
},
},
}, "user_id")
assert.NoError(t, err)
err = store.CreateCategory(model.Category{
ID: "category_id",
Name: "Category",
UserID: "user_id",
TeamID: "team_id",
})
assert.NoError(t, err)
// adding a few boards to the category
err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_1"})
assert.NoError(t, err)
err = store.SetBoardVisibility("user_id", "category_id", "board_id_1", true)
assert.NoError(t, err)
// verify set visibility
categoryBoards, err := store.GetUserCategoryBoards("user_id", "team_id")
assert.NoError(t, err)
assert.Equal(t, 1, len(categoryBoards))
assert.Equal(t, "category_id", categoryBoards[0].ID)
assert.Equal(t, 1, len(categoryBoards[0].BoardMetadata))
assert.False(t, categoryBoards[0].BoardMetadata[0].Hidden)
err = store.SetBoardVisibility("user_id", "category_id", "board_id_1", false)
assert.NoError(t, err)
// verify set visibility
categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id")
assert.NoError(t, err)
assert.True(t, categoryBoards[0].BoardMetadata[0].Hidden)
}

View File

@ -92,7 +92,7 @@ func LoadData(t *testing.T, store store.Store) {
err = store.UpsertSharing(sharing)
require.NoError(t, err)
err = store.AddUpdateCategoryBoard(testUserID, map[string]string{boardID: categoryID})
err = store.AddUpdateCategoryBoard(testUserID, categoryID, []string{boardID})
require.NoError(t, err)
}

View File

@ -102,3 +102,20 @@ func IsCloudLicense(license *mmModel.License) bool {
license.Features.Cloud != nil &&
*license.Features.Cloud
}
func DedupeStringArr(arr []string) []string {
hashMap := map[string]bool{}
for _, item := range arr {
hashMap[item] = true
}
dedupedArr := make([]string, len(hashMap))
i := 0
for key := range hashMap {
dedupedArr[i] = key
i++
}
return dedupedArr
}

View File

@ -59,7 +59,8 @@ func NewServer(rootPath string, serverRoot string, port int, ssl, localOnly bool
baseURL = url.Path
ws := &Server{
Server: http.Server{
// (TODO: Add ReadHeaderTimeout)
Server: http.Server{ //nolint:gosec
Addr: addr,
Handler: r,
},

View File

@ -422,7 +422,7 @@ func (pa *PluginAdapter) sendTeamMessage(event, teamID string, payload map[strin
EnsureUsers: ensureUserIDs,
}
pa.sendMessageToCluster("websocket_message", clusterMessage)
pa.sendMessageToCluster(clusterMessage)
}()
pa.sendTeamMessageSkipCluster(event, teamID, payload)
@ -447,7 +447,7 @@ func (pa *PluginAdapter) sendBoardMessage(teamID, boardID string, payload map[st
EnsureUsers: ensureUserIDs,
}
pa.sendMessageToCluster("websocket_message", clusterMessage)
pa.sendMessageToCluster(clusterMessage)
}()
pa.sendBoardMessageSkipCluster(teamID, boardID, payload, ensureUserIDs...)
@ -490,7 +490,7 @@ func (pa *PluginAdapter) BroadcastCategoryChange(category model.Category) {
UserID: category.UserID,
}
pa.sendMessageToCluster("websocket_message", clusterMessage)
pa.sendMessageToCluster(clusterMessage)
}()
pa.sendUserMessageSkipCluster(websocketActionUpdateCategory, payload, category.UserID)
@ -514,7 +514,7 @@ func (pa *PluginAdapter) BroadcastCategoryReorder(teamID, userID string, categor
UserID: userID,
}
pa.sendMessageToCluster("websocket_message", clusterMessage)
pa.sendMessageToCluster(clusterMessage)
}()
pa.sendUserMessageSkipCluster(message.Action, payload, userID)
@ -540,7 +540,7 @@ func (pa *PluginAdapter) BroadcastCategoryBoardsReorder(teamID, userID, category
UserID: userID,
}
pa.sendMessageToCluster("websocket_message", clusterMessage)
pa.sendMessageToCluster(clusterMessage)
}()
pa.sendUserMessageSkipCluster(message.Action, payload, userID)
@ -568,7 +568,7 @@ func (pa *PluginAdapter) BroadcastCategoryBoardChange(teamID, userID string, boa
UserID: userID,
}
pa.sendMessageToCluster("websocket_message", clusterMessage)
pa.sendMessageToCluster(clusterMessage)
}()
pa.sendUserMessageSkipCluster(websocketActionUpdateCategoryBoard, utils.StructToMap(message), userID)

Some files were not shown because too many files have changed in this diff Show More