mirror of
https://github.com/mattermost/focalboard.git
synced 2025-04-25 06:27:07 -04:00
Permissions feature branch (#2578)
* wip
* Added data migration for populating categories
* wip
* Added data migration for populating categories
* Store WIP
* migration WIP
* category CRUD APIs complete
* category block API WIP
* block category update API done
* Fetcehed data into store
* Started displayting sidebar data
* sidebar WIP
* Dashboard - basic changes
* Sidebar dashboard btn and board switcher UI only
* Sidebar dashboard btn and board switcher UI only
* create category dialog WIP
* Create category webapp side done
* Integrated move card to other category
* board to block
* Disabled dashboard route for now as we'll implement it in phase 2
* WIP
* Added logic to open last board/view on per team level
* Add workspace to teams and boards migrations (#1986)
* Add workspace to teams and boards migrations
* Update json annotations on board models
* boards search dialog WIP
* Seach dialog WIP
* Implemented opening boiard from search results
* Boards switcher styliung
* Handled update category WS event
* Template support
* personal server support and styling fixes
* test fix WIP
* Fixed a bug causing boards to not be moved correctly beteen categories
* Fixed webapp tests
* fix
* Store changes (#2011)
* Permissions phase 1 - Websocket updates (#2014)
* Store changes
* Websockets changes
* Permissions phase 1 - Permissions service (#2015)
* Store changes
* Websockets changes
* Permissions service
* Api and app updates (#2016)
* Store changes
* Websockets changes
* Permissions service
* New API and App changes
* Delete and Patch boards and blocks endpoints
* Used correct variable
* Webapp changes WIP
* Open correct team URL
* Fixed get block API
* Used React context for workspace users
* WIP
* On load navigation sorted out
* WIP
* Nav fix
* categories WS broadcast
* Used real search API
* Fixed unfurl ppreview
* set active team in sidebar
* IMplemented navigation on changing team in sidebar
* Misc fixes
* close rows inside transaction (#2045)
* update syntax for mysql (#2044)
* Upadted mutator for new patchBlock API
* Updated patchBlock API to use new URL
* Listeining to correct event in plugin mode
* Implemented WS messages for category operations:
* Fix duplicated build tags on Makefile
* Sidebar enhancements
* Add missing prefix to SQLite migration and fix flaky tests
* Sidebar boards menu enhancement
* Fix board page interactions (#2144)
* Fix patch board card properties error
* Fix board interactions
* Fix insert blocks interactions
* Fix app tests (#2104)
* Add json1 tag to vscode launch (#2157)
* Fix add, delete and update boards and add board patch generation (#2146)
* Fix update boards and add board patch generation
* Make add board and add template work, as well as deleting a board
* Update the state on board deletion
* Delete unused variable
* Fix bad parenthesis
* Fix board creation inside plugin, options were coming null due websocket message serialization
* update property type mutators to use boards API (#2168)
* Add permissions modal (#2196)
* Initial integration
* Permissions modal, websocket updates and API tests implemented
* Avoid updating/removing user if there is only one admin left
* Fix duplicates on board search
* Adds integration test
* Addressing PR review comments
Co-authored-by: Jesús Espino <jespinog@gmail.com>
* Merge
* I'm able to compile now
* Some fixes around tests execution
* Fixing migrations
* Fixing migrations order
* WIP
* Fixing some other compilation problems on tests
* Some typescript tests fixed
* Fixing javascript tests
* Fixing compilation
* Fixing some problems to create boards
* Load the templates on initial load
* Improvements over initial team templates import
* Adding new fields in the database
* Working on adding duplicate board api
* Removing RootID concept entirely
* Improving a bit the subscriptions
* Fixing store tests for notificationHints
* Fixing more tests
* fixing tests
* Fixing tests
* Fixing tests
* Fixing some small bugs related to templates
* Fixing registration link generation/regeneration
* Fixing cypress tests
* Adding store tests for duplicateBoard and duplicateBlock
* Addressing some TODO comments
* Making the export api simpler
* Add redirect component for old workspace urls
* Removing Dashboard code
* Delete only the built-in templates on update
* fixing tests
* Adding users autocompletion
* Updating snapshots
* Fixing bad merge
* fix panic when creating new card in notifysubscriptions (#2352)
* fix lint errors (#2353)
* fix lint errors
* fix panic when creating new card in notifysubscriptions (#2352)
* fix lint errors
* fix unit test
* Revert "fix unit test"
This reverts commit 0ad78aed65
.
Co-authored-by: Doug Lauder <wiggin77@warpmail.net>
* fix sql syntax error for SearchUsersByTeam (#2357)
* Fix mentions delivery (#2358)
* fix sql syntax error for SearchUsersByTeam
* fix mentions delivery
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
* update api for octoClient calls, pass correct variables to mutator (#2359)
* Fixing tests after merge
* Fix sidebar context menu UI issue (#2399)
* Fix notification diff for text blocks (#2386)
* fix notification diff for text blocks; fix various linter errors.
* fix URLs to cards
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
* Permissions branch: Fix card links (#2391)
* fix notification diff for text blocks; fix various linter errors.
* fix URLs to cards
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
* Fixing sqlite tests
* Fixing server tests
* Update migrations to create global templates. (#2397)
* fix duplicate templates
* revert migrate.go
* update UI for empty templates
* implement updating built-in templates as global (teamId = 0)
* handle error if board not found
* update unit test
* fix more tests
* Update blocks_test.go
Fix merge issue
* fix migration sql error (#2414)
* Fixing frontend tests
* Set target team ID when using a global template (#2419)
* Fix some server tests
* Fixing onboarding creation
* Permissions branch: Fix unit tests and CI errors (part 1) (#2425)
* Fixing some small memory leaks (#2400)
* Fixing some small memory leaks
* fixing tests
* passing the tags to all test targets
* Increasing the timeout of the tests
* Fix some type checkings
* Permissions branch: Fixes all the linter errors (#2429)
* fix linter errors
* Reestructuring the router and splitting in more subcomponents (#2403)
* Reestructuring the router and splitting in more subcomponents
* Removing console.log calls
* Removing unneeded selector
* Addressing PR comment
* Fix redirection to one team when you load directly the boards home path
* Using properly the lastTeamID to redirect the user if needed
* don't allow last admin change/deleted (#2416)
* don't allow last admin change/deleted
* update for i18-extract
* fixed en.json
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Harshil Sharma <harshilsharma63@gmail.com>
* Splitting BoardPage component into simpler/smaller components (#2435)
* Splitting BoardPage component into simpler/smaller components
* Removing unneeded import
* Replace go migrate with morph permissions (#2424)
* merge origin/replace-go-migrate-with-morph
* run go mod tidy on mattermost-plugin and increase test timeout
* fix merge issue temprorarily
* remove some debug changes
* fixing the linter
* Allow always team 0 (global) templates fetch (#2472)
* Fix problem with viewId 0 in the URL (#2473)
* Migrate from binddata to goembed (#2471)
* Adding join logic to the board switcher (#2434)
* Adding join logic to the board switcher
* Using already existing client function and removing the joinBoard one
* Adding support for autojoin based on url
* Fixing frontend tests
* fix webapp compile error, missing enableSharedBoards (#2501)
* Fixing duplication on postgres
* Adding back views to the sidebar (#2494)
* Fix #2507. Update Swagger comments (#2508)
* Fix the flash of the template selector on board/team switch (#2490)
* Fix the flash of the template selector on board/team switch
* More fixes specially around error handling
* Fixing the bot badge (#2487)
* simplifying a bit the team store sync between channels and focalboard (#2481)
* Fix menu tests (#2528)
* fix failing menu tests
* fix lint error
* Added keyboard shortcut for boards switcher (#2407)
* Added keyboard shortcut for boards switcher
* Fixed a type error
* Added some inline comments
* Fixed lint
* Fixed bug with scroll jumping when the card is opened: (#2477)
- avoid remounting of `ScrollingComponent` for each render of `Kanban` component
- property `autoFocus` set to false for `CalculationOptions` because it triggers `blur` even for the button in Jest tests and closes the menu
- snapshots for tests with `CalculationOptions` updated
* Adding the frontend support for permissions and applying it to a big part of the interface. (#2536)
* Initial work on permissions gates
* Applying permissions gates in more places
* Adding more checks to the interface
* Adding more permissions gates and keeping the store up to date
* fixing some tests
* Fixing some more tests
* Fixing another test
* Fixing all tests and adding some more
* Adding no-permission snapshot tests
* Addressing PR review comments
* Fixing invert behavior
* Permissions branch: No sqlstore calls after app shutdown (#2530)
* fix webapp compile error, missing enableSharedBoards
* refactor app init wip
* - ensure all block change notifications are finished before shutting down app
- fix unit tests for mysql (insert_at only has 1 second resolution!)
* adjust logging
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
* Fixed migrations to allow upgrading from previous version (#2535)
* Added mechanism to check if schema migration is needed
* WIP
* WIP
* WIP
* WIP
* Fixed migration
* Fixed for SQLite
* minor cleaniup
* Deleted old schema migration table after running migrations
* Removed a debug log
* Fixed a bug where the code always tried to delete a table which may or may not exist
* Show properly the user avatar in the ShareBoard component (#2542)
* Fixing the last CI problems from the permissions-branch (#2541)
* Fix history ordering
* Giving some times to avoid possible race conditions
* Empty
* Reverting accidental change in the config.json
* Optimizing table view (#2540)
* Optimizing table view
* Reducing the amount of rendering for tables
* Some other performance improvements
* Improve the activeView updates
* Some extra simplifications
* Another small improvement
* Fixing tests
* Fixing linter errors
* Reducing a bit the amount of dependency with big objects in the store
* Small simplification
* Removing Commenter role from the user role selector (#2561)
* Shareboard cleanup (#2550)
* Initial work on permissions gates
* Applying permissions gates in more places
* Adding more checks to the interface
* Adding more permissions gates and keeping the store up to date
* fixing some tests
* Fixing some more tests
* Fixing another test
* Fixing all tests and adding some more
* Adding no-permission snapshot tests
* Addressing PR review comments
* cleanup some shareboard settings
* remove unused property, fix for user items being displayed for non admin
* revert change, allow users to show
Co-authored-by: Jesús Espino <jespinog@gmail.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
* Fixing comments and cards with the new optimizations in the store (#2560)
* Fixing property creation (#2563)
* Fix user selection in table view (#2565)
* Fixing focus new row in table view (#2567)
* Permissions branch: Fix sqlite table lock (CI) (#2568)
* fix sqlite table lock
* remove test db on teardown
* revert .gitignore
* fix goimport on migration code
* fix typo
* more linter fixes
* clean up tmp db for sqlstore tests
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
* Fixing snapshots
* Migrating center panel to functional component (#2562)
* Migrating center panel to functional component
* Fixing some tests
* Fixing another test
* Fixing linter errors
* Fixing types errors
* Fixing linter error
* Fixing cypress tests
* Fixing the last cypress test
* Simpliying a bit the code
* Making property insertion more robust
* Updating checkbox test
Co-authored-by: Harshil Sharma <harshilsharma63@gmail.com>
Co-authored-by: Miguel de la Cruz <miguel@mcrx.me>
Co-authored-by: Scott Bishel <scott.bishel@mattermost.com>
Co-authored-by: Chen-I Lim <46905241+chenilim@users.noreply.github.com>
Co-authored-by: Doug Lauder <wiggin77@warpmail.net>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Harshil Sharma <18575143+harshilsharma63@users.noreply.github.com>
Co-authored-by: Ibrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>
Co-authored-by: kamre <eremchenko@gmail.com>
This commit is contained in:
parent
f3b8a49ea9
commit
aa540e73ce
452 changed files with 33279 additions and 14457 deletions
1
.vscode/launch.json
vendored
1
.vscode/launch.json
vendored
|
@ -9,6 +9,7 @@
|
||||||
"type": "go",
|
"type": "go",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"mode": "debug",
|
"mode": "debug",
|
||||||
|
"buildFlags": "-tags 'json1'",
|
||||||
"program": "${workspaceFolder}/server/main",
|
"program": "${workspaceFolder}/server/main",
|
||||||
"cwd": "${workspaceFolder}"
|
"cwd": "${workspaceFolder}"
|
||||||
},
|
},
|
||||||
|
|
29
Makefile
29
Makefile
|
@ -12,6 +12,8 @@ ifeq ($(BUILD_NUMBER),)
|
||||||
BUILD_DATE := n/a
|
BUILD_DATE := n/a
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
BUILD_TAGS += json1
|
||||||
|
|
||||||
LDFLAGS += -X "github.com/mattermost/focalboard/server/model.BuildNumber=$(BUILD_NUMBER)"
|
LDFLAGS += -X "github.com/mattermost/focalboard/server/model.BuildNumber=$(BUILD_NUMBER)"
|
||||||
LDFLAGS += -X "github.com/mattermost/focalboard/server/model.BuildDate=$(BUILD_DATE)"
|
LDFLAGS += -X "github.com/mattermost/focalboard/server/model.BuildDate=$(BUILD_DATE)"
|
||||||
LDFLAGS += -X "github.com/mattermost/focalboard/server/model.BuildHash=$(BUILD_HASH)"
|
LDFLAGS += -X "github.com/mattermost/focalboard/server/model.BuildHash=$(BUILD_HASH)"
|
||||||
|
@ -35,25 +37,25 @@ ci: server-test
|
||||||
|
|
||||||
server: ## Build server for local environment.
|
server: ## Build server for local environment.
|
||||||
$(eval LDFLAGS += -X "github.com/mattermost/focalboard/server/model.Edition=dev")
|
$(eval LDFLAGS += -X "github.com/mattermost/focalboard/server/model.Edition=dev")
|
||||||
cd server; go build -ldflags '$(LDFLAGS)' -o ../bin/focalboard-server ./main
|
cd server; go build -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' -o ../bin/focalboard-server ./main
|
||||||
|
|
||||||
server-mac: ## Build server for Mac.
|
server-mac: ## Build server for Mac.
|
||||||
mkdir -p bin/mac
|
mkdir -p bin/mac
|
||||||
$(eval LDFLAGS += -X "github.com/mattermost/focalboard/server/model.Edition=mac")
|
$(eval LDFLAGS += -X "github.com/mattermost/focalboard/server/model.Edition=mac")
|
||||||
cd server; env GOOS=darwin GOARCH=$(MAC_GO_ARCH) go build -ldflags '$(LDFLAGS)' -o ../bin/mac/focalboard-server ./main
|
cd server; env GOOS=darwin GOARCH=$(MAC_GO_ARCH) go build -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' -o ../bin/mac/focalboard-server ./main
|
||||||
|
|
||||||
server-linux: ## Build server for Linux.
|
server-linux: ## Build server for Linux.
|
||||||
mkdir -p bin/linux
|
mkdir -p bin/linux
|
||||||
$(eval LDFLAGS += -X "github.com/mattermost/focalboard/server/model.Edition=linux")
|
$(eval LDFLAGS += -X "github.com/mattermost/focalboard/server/model.Edition=linux")
|
||||||
cd server; env GOOS=linux GOARCH=amd64 go build -ldflags '$(LDFLAGS)' -o ../bin/linux/focalboard-server ./main
|
cd server; env GOOS=linux GOARCH=amd64 go build -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' -o ../bin/linux/focalboard-server ./main
|
||||||
|
|
||||||
server-win: ## Build server for Windows.
|
server-win: ## Build server for Windows.
|
||||||
$(eval LDFLAGS += -X "github.com/mattermost/focalboard/server/model.Edition=win")
|
$(eval LDFLAGS += -X "github.com/mattermost/focalboard/server/model.Edition=win")
|
||||||
cd server; env GOOS=windows GOARCH=amd64 go build -ldflags '$(LDFLAGS)' -o ../bin/win/focalboard-server.exe ./main
|
cd server; env GOOS=windows GOARCH=amd64 go build -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' -o ../bin/win/focalboard-server.exe ./main
|
||||||
|
|
||||||
server-dll: ## Build server as Windows DLL.
|
server-dll: ## Build server as Windows DLL.
|
||||||
$(eval LDFLAGS += -X "github.com/mattermost/focalboard/server/model.Edition=win")
|
$(eval LDFLAGS += -X "github.com/mattermost/focalboard/server/model.Edition=win")
|
||||||
cd server; env GOOS=windows GOARCH=amd64 go build -ldflags '$(LDFLAGS)' -buildmode=c-shared -o ../bin/win-dll/focalboard-server.dll ./main
|
cd server; env GOOS=windows GOARCH=amd64 go build -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' -buildmode=c-shared -o ../bin/win-dll/focalboard-server.dll ./main
|
||||||
|
|
||||||
server-linux-package: server-linux webapp
|
server-linux-package: server-linux webapp
|
||||||
rm -rf package
|
rm -rf package
|
||||||
|
@ -83,7 +85,6 @@ server-linux-package-docker:
|
||||||
|
|
||||||
generate: ## Install and run code generators.
|
generate: ## Install and run code generators.
|
||||||
cd server; go get -modfile=go.tools.mod github.com/golang/mock/mockgen
|
cd server; go get -modfile=go.tools.mod github.com/golang/mock/mockgen
|
||||||
cd server; go get -modfile=go.tools.mod github.com/jteeuwen/go-bindata
|
|
||||||
cd server; go generate ./...
|
cd server; go generate ./...
|
||||||
|
|
||||||
server-lint: ## Run linters on server code.
|
server-lint: ## Run linters on server code.
|
||||||
|
@ -101,18 +102,18 @@ modd-precheck:
|
||||||
fi; \
|
fi; \
|
||||||
|
|
||||||
watch: modd-precheck ## Run both server and webapp watching for changes
|
watch: modd-precheck ## Run both server and webapp watching for changes
|
||||||
modd
|
env FOCALBOARD_BUILD_TAGS='$(BUILD_TAGS)' modd
|
||||||
|
|
||||||
watch-single-user: modd-precheck ## Run both server and webapp in single user mode watching for changes
|
watch-single-user: modd-precheck ## Run both server and webapp in single user mode watching for changes
|
||||||
env FOCALBOARDSERVER_ARGS=--single-user modd
|
env FOCALBOARDSERVER_ARGS=--single-user FOCALBOARD_BUILD_TAGS='$(BUILD_TAGS)' modd
|
||||||
|
|
||||||
watch-server-test: modd-precheck ## Run server tests watching for changes
|
watch-server-test: modd-precheck ## Run server tests watching for changes
|
||||||
modd -f modd-servertest.conf
|
env FOCALBOARD_BUILD_TAGS='$(BUILD_TAGS)' modd -f modd-servertest.conf
|
||||||
|
|
||||||
server-test: server-test-sqlite server-test-mysql server-test-postgres ## Run server tests
|
server-test: server-test-sqlite server-test-mysql server-test-postgres ## Run server tests
|
||||||
|
|
||||||
server-test-sqlite: ## Run server tests using sqlite
|
server-test-sqlite: ## Run server tests using sqlite
|
||||||
cd server; go test -race -v -count=1 ./...
|
cd server; go test -tags '$(BUILD_TAGS)' -race -v -count=1 -timeout=30m ./...
|
||||||
|
|
||||||
server-test-mysql: export FB_UNIT_TESTING=1
|
server-test-mysql: export FB_UNIT_TESTING=1
|
||||||
server-test-mysql: export FB_STORE_TEST_DB_TYPE=mysql
|
server-test-mysql: export FB_STORE_TEST_DB_TYPE=mysql
|
||||||
|
@ -120,8 +121,9 @@ server-test-mysql: export FB_STORE_TEST_DOCKER_PORT=44445
|
||||||
|
|
||||||
server-test-mysql: ## Run server tests using mysql
|
server-test-mysql: ## Run server tests using mysql
|
||||||
@echo Starting docker container for mysql
|
@echo Starting docker container for mysql
|
||||||
|
docker-compose -f ./docker-testing/docker-compose-mysql.yml down -v --remove-orphans
|
||||||
docker-compose -f ./docker-testing/docker-compose-mysql.yml run start_dependencies
|
docker-compose -f ./docker-testing/docker-compose-mysql.yml run start_dependencies
|
||||||
cd server; go test -race -v -count=1 ./...
|
cd server; go test -tags '$(BUILD_TAGS)' -race -v -count=1 -timeout=30m ./...
|
||||||
docker-compose -f ./docker-testing/docker-compose-mysql.yml down -v --remove-orphans
|
docker-compose -f ./docker-testing/docker-compose-mysql.yml down -v --remove-orphans
|
||||||
|
|
||||||
server-test-postgres: export FB_UNIT_TESTING=1
|
server-test-postgres: export FB_UNIT_TESTING=1
|
||||||
|
@ -130,8 +132,9 @@ server-test-postgres: export FB_STORE_TEST_DOCKER_PORT=44446
|
||||||
|
|
||||||
server-test-postgres: ## Run server tests using postgres
|
server-test-postgres: ## Run server tests using postgres
|
||||||
@echo Starting docker container for postgres
|
@echo Starting docker container for postgres
|
||||||
|
docker-compose -f ./docker-testing/docker-compose-postgres.yml down -v --remove-orphans
|
||||||
docker-compose -f ./docker-testing/docker-compose-postgres.yml run start_dependencies
|
docker-compose -f ./docker-testing/docker-compose-postgres.yml run start_dependencies
|
||||||
cd server; go test -race -v -count=1 ./...
|
cd server; go test -tags '$(BUILD_TAGS)' -race -v -count=1 -timeout=30m ./...
|
||||||
docker-compose -f ./docker-testing/docker-compose-postgres.yml down -v --remove-orphans
|
docker-compose -f ./docker-testing/docker-compose-postgres.yml down -v --remove-orphans
|
||||||
|
|
||||||
webapp: ## Build webapp.
|
webapp: ## Build webapp.
|
||||||
|
@ -141,7 +144,7 @@ webapp-test: ## jest tests for webapp
|
||||||
cd webapp; npm run test
|
cd webapp; npm run test
|
||||||
|
|
||||||
watch-plugin: modd-precheck ## Run and upload the plugin to a development server
|
watch-plugin: modd-precheck ## Run and upload the plugin to a development server
|
||||||
modd -f modd-watchplugin.conf
|
env FOCALBOARD_BUILD_TAGS='$(BUILD_TAGS)' modd -f modd-watchplugin.conf
|
||||||
|
|
||||||
live-watch-plugin: modd-precheck ## Run and update locally the plugin in the development server
|
live-watch-plugin: modd-precheck ## Run and update locally the plugin in the development server
|
||||||
cd mattermost-plugin; make live-watch
|
cd mattermost-plugin; make live-watch
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"serverRoot": "http://localhost:8000",
|
"serverRoot": "http://localhost:8000",
|
||||||
"port": 8000,
|
"port": 8000,
|
||||||
"dbtype": "sqlite3",
|
"dbtype": "sqlite3",
|
||||||
"dbconfig": "./focalboard.db",
|
"dbconfig": "./focalboard.db",
|
||||||
"dbtableprefix": "",
|
"dbtableprefix": "",
|
||||||
"postgres_dbconfig": "dbname=focalboard sslmode=disable",
|
"postgres_dbconfig": "dbname=focalboard sslmode=disable",
|
||||||
"test_dbconfig": "file::memory:?cache=shared",
|
"test_dbconfig": "file::memory:?cache=shared",
|
||||||
|
@ -21,5 +21,5 @@
|
||||||
"authMode": "native",
|
"authMode": "native",
|
||||||
"logging_cfg_file": "",
|
"logging_cfg_file": "",
|
||||||
"audit_cfg_file": "",
|
"audit_cfg_file": "",
|
||||||
"enablepublicsharedboards": false
|
"enablePublicSharedBoards": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ export async function logIn(host: string, username: string, password: string) {
|
||||||
|
|
||||||
export async function getBoards(host: string, token: string) {
|
export async function getBoards(host: string, token: string) {
|
||||||
const json = await request('GET', host, 'workspaces/0/blocks?type=board', null, token) as Board[]
|
const json = await request('GET', host, 'workspaces/0/blocks?type=board', null, token) as Board[]
|
||||||
return json.filter(board => !board.fields.isTemplate)
|
return json.filter(board => !board.isTemplate)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function findUrlPropertyId(host: string, token: string, boardId: string) {
|
export async function findUrlPropertyId(host: string, token: string, boardId: string) {
|
||||||
|
|
|
@ -129,7 +129,7 @@ function convert(input: Asana): Block[] {
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options
|
options
|
||||||
}
|
}
|
||||||
board.fields.cardProperties = [cardProperty]
|
board.cardProperties = [cardProperty]
|
||||||
blocks.push(board)
|
blocks.push(board)
|
||||||
|
|
||||||
// Board view
|
// Board view
|
||||||
|
|
|
@ -90,25 +90,25 @@ function convert(items: any[]) {
|
||||||
board.title = 'Jira import'
|
board.title = 'Jira import'
|
||||||
|
|
||||||
// Compile standard properties
|
// Compile standard properties
|
||||||
board.fields.cardProperties = []
|
board.cardProperties = []
|
||||||
|
|
||||||
const priorityProperty = buildCardPropertyFromValues('Priority', items.map(o => o.priority?._))
|
const priorityProperty = buildCardPropertyFromValues('Priority', items.map(o => o.priority?._))
|
||||||
board.fields.cardProperties.push(priorityProperty)
|
board.cardProperties.push(priorityProperty)
|
||||||
|
|
||||||
const statusProperty = buildCardPropertyFromValues('Status', items.map(o => o.status?._))
|
const statusProperty = buildCardPropertyFromValues('Status', items.map(o => o.status?._))
|
||||||
board.fields.cardProperties.push(statusProperty)
|
board.cardProperties.push(statusProperty)
|
||||||
|
|
||||||
const resolutionProperty = buildCardPropertyFromValues('Resolution', items.map(o => o.resolution?._))
|
const resolutionProperty = buildCardPropertyFromValues('Resolution', items.map(o => o.resolution?._))
|
||||||
board.fields.cardProperties.push(resolutionProperty)
|
board.cardProperties.push(resolutionProperty)
|
||||||
|
|
||||||
const typeProperty = buildCardPropertyFromValues('Type', items.map(o => o.type?._))
|
const typeProperty = buildCardPropertyFromValues('Type', items.map(o => o.type?._))
|
||||||
board.fields.cardProperties.push(typeProperty)
|
board.cardProperties.push(typeProperty)
|
||||||
|
|
||||||
const assigneeProperty = buildCardPropertyFromValues('Assignee', items.map(o => o.assignee?._))
|
const assigneeProperty = buildCardPropertyFromValues('Assignee', items.map(o => o.assignee?._))
|
||||||
board.fields.cardProperties.push(assigneeProperty)
|
board.cardProperties.push(assigneeProperty)
|
||||||
|
|
||||||
const reporterProperty = buildCardPropertyFromValues('Reporter', items.map(o => o.reporter?._))
|
const reporterProperty = buildCardPropertyFromValues('Reporter', items.map(o => o.reporter?._))
|
||||||
board.fields.cardProperties.push(reporterProperty)
|
board.cardProperties.push(reporterProperty)
|
||||||
|
|
||||||
const originalUrlProperty: IPropertyTemplate = {
|
const originalUrlProperty: IPropertyTemplate = {
|
||||||
id: Utils.createGuid(),
|
id: Utils.createGuid(),
|
||||||
|
@ -116,7 +116,7 @@ function convert(items: any[]) {
|
||||||
type: 'url',
|
type: 'url',
|
||||||
options: []
|
options: []
|
||||||
}
|
}
|
||||||
board.fields.cardProperties.push(originalUrlProperty)
|
board.cardProperties.push(originalUrlProperty)
|
||||||
|
|
||||||
const createdDateProperty: IPropertyTemplate = {
|
const createdDateProperty: IPropertyTemplate = {
|
||||||
id: Utils.createGuid(),
|
id: Utils.createGuid(),
|
||||||
|
@ -124,7 +124,7 @@ function convert(items: any[]) {
|
||||||
type: 'date',
|
type: 'date',
|
||||||
options: []
|
options: []
|
||||||
}
|
}
|
||||||
board.fields.cardProperties.push(createdDateProperty)
|
board.cardProperties.push(createdDateProperty)
|
||||||
|
|
||||||
blocks.push(board)
|
blocks.push(board)
|
||||||
|
|
||||||
|
@ -240,4 +240,4 @@ function showHelp() {
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { run }
|
export { run }
|
||||||
|
|
|
@ -135,7 +135,7 @@ function convert(input: any[], title: string): Block[] {
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: []
|
options: []
|
||||||
}
|
}
|
||||||
board.fields.cardProperties.push(cardProperty)
|
board.cardProperties.push(cardProperty)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set all column types to select
|
// Set all column types to select
|
||||||
|
@ -177,7 +177,7 @@ function convert(input: any[], title: string): Block[] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardProperty = board.fields.cardProperties.find((o) => o.name === key)!
|
const cardProperty = board.cardProperties.find((o) => o.name === key)!
|
||||||
let option = cardProperty.options.find((o) => o.value === value)
|
let option = cardProperty.options.find((o) => o.value === value)
|
||||||
if (!option) {
|
if (!option) {
|
||||||
const color = optionColors[optionColorIndex % optionColors.length]
|
const color = optionColors[optionColorIndex % optionColors.length]
|
||||||
|
|
|
@ -82,7 +82,7 @@ function convert(input: Todoist, project: Project): Block[] {
|
||||||
console.log(`Board: ${project.name}`)
|
console.log(`Board: ${project.name}`)
|
||||||
board.rootId = board.id
|
board.rootId = board.id
|
||||||
board.title = project.name
|
board.title = project.name
|
||||||
board.fields.description = project.name
|
board.description = project.name
|
||||||
|
|
||||||
// Convert lists (columns) to a Select property
|
// Convert lists (columns) to a Select property
|
||||||
const optionIdMap = new Map<string, string>()
|
const optionIdMap = new Map<string, string>()
|
||||||
|
@ -114,7 +114,7 @@ function convert(input: Todoist, project: Project): Block[] {
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options
|
options
|
||||||
}
|
}
|
||||||
board.fields.cardProperties = [cardProperty]
|
board.cardProperties = [cardProperty]
|
||||||
blocks.push(board)
|
blocks.push(board)
|
||||||
|
|
||||||
// Board view
|
// Board view
|
||||||
|
|
|
@ -68,7 +68,7 @@ function convert(input: Trello): Block[] {
|
||||||
console.log(`Board: ${input.name}`)
|
console.log(`Board: ${input.name}`)
|
||||||
board.rootId = board.id
|
board.rootId = board.id
|
||||||
board.title = input.name
|
board.title = input.name
|
||||||
board.fields.description = input.desc
|
board.description = input.desc
|
||||||
|
|
||||||
// Convert lists (columns) to a Select property
|
// Convert lists (columns) to a Select property
|
||||||
const optionIdMap = new Map<string, string>()
|
const optionIdMap = new Map<string, string>()
|
||||||
|
@ -92,7 +92,7 @@ function convert(input: Trello): Block[] {
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options
|
options
|
||||||
}
|
}
|
||||||
board.fields.cardProperties = [cardProperty]
|
board.cardProperties = [cardProperty]
|
||||||
blocks.push(board)
|
blocks.push(board)
|
||||||
|
|
||||||
// Board view
|
// Board view
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/mattermost/focalboard/server/server"
|
"github.com/mattermost/focalboard/server/server"
|
||||||
"github.com/mattermost/focalboard/server/services/config"
|
"github.com/mattermost/focalboard/server/services/config"
|
||||||
|
"github.com/mattermost/focalboard/server/services/permissions/localpermissions"
|
||||||
"github.com/webview/webview"
|
"github.com/webview/webview"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
|
@ -66,6 +67,8 @@ func runServer(port int) (*server.Server, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
permissionsService := localpermissions.New(db, logger)
|
||||||
|
|
||||||
params := server.Params{
|
params := server.Params{
|
||||||
Cfg: config,
|
Cfg: config,
|
||||||
SingleUserToken: sessionToken,
|
SingleUserToken: sessionToken,
|
||||||
|
@ -74,6 +77,7 @@ func runServer(port int) (*server.Server, error) {
|
||||||
ServerID: "",
|
ServerID: "",
|
||||||
WSAdapter: nil,
|
WSAdapter: nil,
|
||||||
NotifyBackends: nil,
|
NotifyBackends: nil,
|
||||||
|
PermissionsService: permissionsService,
|
||||||
}
|
}
|
||||||
|
|
||||||
server, err := server.New(params)
|
server, err := server.New(params)
|
||||||
|
|
|
@ -61,7 +61,6 @@ github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOv
|
||||||
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
|
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
|
||||||
github.com/Azure/azure-sdk-for-go v26.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
github.com/Azure/azure-sdk-for-go v26.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||||
github.com/Azure/azure-storage-blob-go v0.13.0/go.mod h1:pA9kNqtjUeQF2zOSu4s//nUdBD+e64lEuc4sVnuOfNs=
|
github.com/Azure/azure-storage-blob-go v0.13.0/go.mod h1:pA9kNqtjUeQF2zOSu4s//nUdBD+e64lEuc4sVnuOfNs=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||||
github.com/Azure/go-autorest v11.5.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
github.com/Azure/go-autorest v11.5.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||||
|
@ -91,7 +90,6 @@ github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/
|
||||||
github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
||||||
github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
|
github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
|
||||||
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||||
github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
|
|
||||||
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/PaulARoy/azurestoragecache v0.0.0-20170906084534-3c249a3ba788/go.mod h1:lY1dZd8HBzJ10eqKERHn3CU59tfhzcAVb2c0ZhIWSOk=
|
github.com/PaulARoy/azurestoragecache v0.0.0-20170906084534-3c249a3ba788/go.mod h1:lY1dZd8HBzJ10eqKERHn3CU59tfhzcAVb2c0ZhIWSOk=
|
||||||
|
@ -235,7 +233,6 @@ github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkb
|
||||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||||
github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||||
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||||
github.com/containerd/containerd v1.4.3 h1:ijQT13JedHSHrQGWFcGEwzcNKrAGIiZ+jSD5QQG07SY=
|
|
||||||
github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
|
@ -260,6 +257,7 @@ github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ1
|
||||||
github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
|
github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
|
||||||
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
|
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
|
||||||
github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
|
github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
|
||||||
|
github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpTVGlwA=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -272,20 +270,15 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
github.com/dhui/dktest v0.3.3/go.mod h1:EML9sP4sqJELHn4jV7B0TY8oF6077nk83/tz7M56jcQ=
|
github.com/dhui/dktest v0.3.3/go.mod h1:EML9sP4sqJELHn4jV7B0TY8oF6077nk83/tz7M56jcQ=
|
||||||
github.com/dhui/dktest v0.3.4 h1:VbUEcaSP+U2/yUr9d2JhSThXYEnDlGabRSHe2rIE46E=
|
|
||||||
github.com/dhui/dktest v0.3.4/go.mod h1:4m4n6lmXlmVfESth7mzdcv8nBI5mOb5UROPqjM02csU=
|
github.com/dhui/dktest v0.3.4/go.mod h1:4m4n6lmXlmVfESth7mzdcv8nBI5mOb5UROPqjM02csU=
|
||||||
github.com/die-net/lrucache v0.0.0-20181227122439-19a39ef22a11/go.mod h1:ew0MSjCVDdtGMjF3kzLK9hwdgF5mOE8SbYVF3Rc7mkU=
|
github.com/die-net/lrucache v0.0.0-20181227122439-19a39ef22a11/go.mod h1:ew0MSjCVDdtGMjF3kzLK9hwdgF5mOE8SbYVF3Rc7mkU=
|
||||||
github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
|
github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker v17.12.0-ce-rc1.0.20210128214336-420b1d36250f+incompatible h1:nhVo1udYfMj0Jsw0lnqrTjjf33aLpdgW9Wve9fHVzhQ=
|
|
||||||
github.com/docker/docker v17.12.0-ce-rc1.0.20210128214336-420b1d36250f+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v17.12.0-ce-rc1.0.20210128214336-420b1d36250f+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
|
||||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
||||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||||
|
@ -315,6 +308,7 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqL
|
||||||
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
|
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
|
||||||
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
|
@ -410,10 +404,8 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
|
||||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang-migrate/migrate/v4 v4.14.1/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0=
|
github.com/golang-migrate/migrate/v4 v4.14.1/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0=
|
||||||
github.com/golang-migrate/migrate/v4 v4.15.0 h1:LKvQ+CgezLw0zuR/ib1y9sQStG0vepWaEVUsQof0bo0=
|
|
||||||
github.com/golang-migrate/migrate/v4 v4.15.0/go.mod h1:g9qbiDvB47WyrRnNu2t2gMZFNHKnatsYRxsGZbCi4EM=
|
github.com/golang-migrate/migrate/v4 v4.15.0/go.mod h1:g9qbiDvB47WyrRnNu2t2gMZFNHKnatsYRxsGZbCi4EM=
|
||||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
|
@ -542,7 +534,6 @@ github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1p
|
||||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
|
@ -557,7 +548,6 @@ github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP
|
||||||
github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4=
|
github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4=
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
github.com/hashicorp/go-plugin v1.4.2/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
|
github.com/hashicorp/go-plugin v1.4.2/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
|
||||||
github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM=
|
github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM=
|
||||||
|
@ -684,6 +674,7 @@ github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYb
|
||||||
github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
|
github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
|
||||||
github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
|
github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
|
||||||
github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
|
github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||||
|
@ -778,6 +769,8 @@ github.com/mattermost/mattermost-server/v6 v6.0.0-20210901153517-42e75fad4dae/go
|
||||||
github.com/mattermost/mattermost-server/v6 v6.0.0-20210913141218-bb659d03fde0/go.mod h1:TUSk5lYJmwfTKTJLXR0eAsjJNlKkWzS5aGZegXG0J08=
|
github.com/mattermost/mattermost-server/v6 v6.0.0-20210913141218-bb659d03fde0/go.mod h1:TUSk5lYJmwfTKTJLXR0eAsjJNlKkWzS5aGZegXG0J08=
|
||||||
github.com/mattermost/mattermost-server/v6 v6.0.0-20211022142730-a6cca93ba3c3 h1:+E2WOqMrCGZTGjhmWVsszj2Qqx7Amh/OBUedkLLtnP4=
|
github.com/mattermost/mattermost-server/v6 v6.0.0-20211022142730-a6cca93ba3c3 h1:+E2WOqMrCGZTGjhmWVsszj2Qqx7Amh/OBUedkLLtnP4=
|
||||||
github.com/mattermost/mattermost-server/v6 v6.0.0-20211022142730-a6cca93ba3c3/go.mod h1:VH26NcOr3xgkSBAvh4q+9+RoBD/M9gYO2F1PISq9KMw=
|
github.com/mattermost/mattermost-server/v6 v6.0.0-20211022142730-a6cca93ba3c3/go.mod h1:VH26NcOr3xgkSBAvh4q+9+RoBD/M9gYO2F1PISq9KMw=
|
||||||
|
github.com/mattermost/morph v0.0.0-20220222074146-cff3f12ff131 h1:agJMxBP8LV0nyV90PZ/BHmmjNyvzTWqR20wLwiXHx14=
|
||||||
|
github.com/mattermost/morph v0.0.0-20220222074146-cff3f12ff131/go.mod h1:jxM3g1bx+k2Thz7jofcHguBS8TZn5Pc+o5MGmORObhw=
|
||||||
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs=
|
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||||
|
@ -807,6 +800,7 @@ github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh
|
||||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||||
|
@ -852,7 +846,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|
||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||||
|
@ -905,9 +898,7 @@ github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
||||||
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
|
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
|
||||||
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
github.com/oov/psd v0.0.0-20210618170533-9fb823ddb631/go.mod h1:GHI1bnmAcbp96z6LNfBJvtrjxhaXGkbsk967utPlvL8=
|
github.com/oov/psd v0.0.0-20210618170533-9fb823ddb631/go.mod h1:GHI1bnmAcbp96z6LNfBJvtrjxhaXGkbsk967utPlvL8=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
|
||||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
|
@ -990,6 +981,7 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
github.com/reflog/dateconstraints v0.2.1/go.mod h1:Ax8AxTBcJc3E/oVS2hd2j7RDM/5MDtuPwuR7lIHtPLo=
|
github.com/reflog/dateconstraints v0.2.1/go.mod h1:Ax8AxTBcJc3E/oVS2hd2j7RDM/5MDtuPwuR7lIHtPLo=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
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-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/richardlehane/mscfb v1.0.3/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
github.com/richardlehane/mscfb v1.0.3/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||||
|
@ -1233,7 +1225,6 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/atomic v1.8.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.8.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
|
||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||||
|
@ -1321,6 +1312,7 @@ golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hM
|
||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
|
||||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
@ -1512,9 +1504,11 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211006225509-1a26e0398eed h1:E159xujlywdAeN3FqsTBPzRKGUq/pDHolXbuttkC36E=
|
|
||||||
golang.org/x/sys v0.0.0-20211006225509-1a26e0398eed/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211006225509-1a26e0398eed/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw=
|
||||||
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/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-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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -1612,6 +1606,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
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.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
|
||||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@ -1830,31 +1825,147 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
|
||||||
|
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||||
modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg=
|
modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg=
|
||||||
modernc.org/cc/v3 v3.32.4/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878=
|
modernc.org/cc/v3 v3.32.4/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878=
|
||||||
|
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U=
|
||||||
|
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
modernc.org/ccgo/v3 v3.9.2/go.mod h1:gnJpy6NIVqkETT+L5zPsQFj7L2kkhfPMzOghRNv/CFo=
|
modernc.org/ccgo/v3 v3.9.2/go.mod h1:gnJpy6NIVqkETT+L5zPsQFj7L2kkhfPMzOghRNv/CFo=
|
||||||
|
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
|
||||||
|
modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
|
||||||
|
modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
|
||||||
|
modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
|
||||||
|
modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw=
|
||||||
|
modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ=
|
||||||
|
modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c=
|
||||||
|
modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo=
|
||||||
|
modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg=
|
||||||
|
modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I=
|
||||||
|
modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs=
|
||||||
|
modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8=
|
||||||
|
modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE=
|
||||||
|
modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk=
|
||||||
|
modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w=
|
||||||
|
modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE=
|
||||||
|
modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8=
|
||||||
|
modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc=
|
||||||
|
modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU=
|
||||||
|
modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE=
|
||||||
|
modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk=
|
||||||
|
modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI=
|
||||||
|
modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE=
|
||||||
|
modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg=
|
||||||
|
modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74=
|
||||||
|
modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU=
|
||||||
|
modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
|
||||||
|
modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
|
||||||
|
modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
|
||||||
|
modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
|
||||||
|
modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
|
||||||
|
modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
|
||||||
|
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
|
||||||
|
modernc.org/ccgo/v3 v3.12.84/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
|
||||||
|
modernc.org/ccgo/v3 v3.12.86/go.mod h1:dN7S26DLTgVSni1PVA3KxxHTcykyDurf3OgUzNqTSrU=
|
||||||
|
modernc.org/ccgo/v3 v3.12.88/go.mod h1:0MFzUHIuSIthpVZyMWiFYMwjiFnhrN5MkvBrUwON+ZM=
|
||||||
|
modernc.org/ccgo/v3 v3.12.90/go.mod h1:obhSc3CdivCRpYZmrvO88TXlW0NvoSVvdh/ccRjJYko=
|
||||||
|
modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzqHA=
|
||||||
|
modernc.org/ccgo/v3 v3.12.95 h1:Ym2JG2G3P4IyZqjTTojHTl7qO0RysXeGSYPSoKPSBxc=
|
||||||
|
modernc.org/ccgo/v3 v3.12.95/go.mod h1:ZcLyvtocXYi8uF+9Ebm3G8EF8HNY5hGomBqthDp4eC8=
|
||||||
|
modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA=
|
||||||
|
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||||
modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8=
|
modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8=
|
||||||
modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw=
|
modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw=
|
||||||
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
|
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
|
||||||
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
|
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
|
||||||
|
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||||
modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM=
|
modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM=
|
||||||
modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||||
modernc.org/libc v1.9.5/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
modernc.org/libc v1.9.5/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||||
|
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||||
|
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
|
||||||
|
modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
|
||||||
|
modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
|
||||||
|
modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
|
||||||
|
modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE=
|
||||||
|
modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso=
|
||||||
|
modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8=
|
||||||
|
modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8=
|
||||||
|
modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I=
|
||||||
|
modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk=
|
||||||
|
modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY=
|
||||||
|
modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE=
|
||||||
|
modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg=
|
||||||
|
modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM=
|
||||||
|
modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg=
|
||||||
|
modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo=
|
||||||
|
modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8=
|
||||||
|
modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ=
|
||||||
|
modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA=
|
||||||
|
modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM=
|
||||||
|
modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg=
|
||||||
|
modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE=
|
||||||
|
modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM=
|
||||||
|
modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU=
|
||||||
|
modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
|
||||||
|
modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
|
||||||
|
modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
|
||||||
|
modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
|
||||||
|
modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
|
||||||
|
modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
|
||||||
|
modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
|
||||||
|
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
|
||||||
|
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
|
||||||
|
modernc.org/libc v1.11.88/go.mod h1:h3oIVe8dxmTcchcFuCcJ4nAWaoiwzKCdv82MM0oiIdQ=
|
||||||
|
modernc.org/libc v1.11.90/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
|
||||||
|
modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
|
||||||
|
modernc.org/libc v1.11.99/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
|
||||||
|
modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
|
||||||
|
modernc.org/libc v1.11.104 h1:gxoa5b3HPo7OzD4tKZjgnwXk/w//u1oovvjSMP3Q96Q=
|
||||||
|
modernc.org/libc v1.11.104/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
|
||||||
modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8=
|
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.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
|
||||||
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
|
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/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
||||||
|
modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
|
||||||
|
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
|
||||||
|
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
|
||||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||||
modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY=
|
modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY=
|
||||||
modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k=
|
modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k=
|
||||||
modernc.org/sqlite v1.10.6/go.mod h1:Z9FEjUtZP4qFEg6/SiADg9XCER7aYy9a/j7Pg9P7CPs=
|
modernc.org/sqlite v1.10.6/go.mod h1:Z9FEjUtZP4qFEg6/SiADg9XCER7aYy9a/j7Pg9P7CPs=
|
||||||
|
modernc.org/sqlite v1.14.3 h1:psrTwgpEujgWEP3FNdsC9yNh5tSeA77U0GeWhHH4XmQ=
|
||||||
|
modernc.org/sqlite v1.14.3/go.mod h1:xMpicS1i2MJ4C8+Ap0vYBqTwYfpFvdnPE6brbFOtV2Y=
|
||||||
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
|
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/tcl v1.5.2/go.mod h1:pmJYOLgpiys3oI4AeAafkcUfE+TKKilminxNyU/+Zlo=
|
modernc.org/tcl v1.5.2/go.mod h1:pmJYOLgpiys3oI4AeAafkcUfE+TKKilminxNyU/+Zlo=
|
||||||
|
modernc.org/tcl v1.9.2 h1:YA87dFLOsR2KqMka371a2Xgr+YsyUwo7OmHVSv/kztw=
|
||||||
|
modernc.org/tcl v1.9.2/go.mod h1:aw7OnlIoiuJgu1gwbTZtrKnGpDqH9wyH++jZcxdqNsg=
|
||||||
|
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
||||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.0.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-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.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
|
||||||
|
modernc.org/z v1.2.20 h1:DyboxM1sJR2NB803j2StnbnL6jcQXz273OhHDGu8dGk=
|
||||||
|
modernc.org/z v1.2.20/go.mod h1:zU9FiF4PbHdOTUxw+IF8j7ArBMRPsHgq10uVPt6xTzo=
|
||||||
modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4=
|
modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/mattermost/focalboard/server/server"
|
"github.com/mattermost/focalboard/server/server"
|
||||||
"github.com/mattermost/focalboard/server/services/config"
|
"github.com/mattermost/focalboard/server/services/config"
|
||||||
"github.com/mattermost/focalboard/server/services/notify"
|
"github.com/mattermost/focalboard/server/services/notify"
|
||||||
|
"github.com/mattermost/focalboard/server/services/permissions/mmpermissions"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
"github.com/mattermost/focalboard/server/services/store"
|
||||||
"github.com/mattermost/focalboard/server/services/store/mattermostauthlayer"
|
"github.com/mattermost/focalboard/server/services/store/mattermostauthlayer"
|
||||||
"github.com/mattermost/focalboard/server/services/store/sqlstore"
|
"github.com/mattermost/focalboard/server/services/store/sqlstore"
|
||||||
|
@ -29,16 +30,17 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
boardsFeatureFlagName = "BoardsFeatureFlags"
|
boardsFeatureFlagName = "BoardsFeatureFlags"
|
||||||
pluginName = "focalboard"
|
pluginName = "focalboard"
|
||||||
sharedBoardsName = "enablepublicsharedboards"
|
sharedBoardsName = "enablepublicsharedboards"
|
||||||
|
|
||||||
notifyFreqCardSecondsKey = "notify_freq_card_seconds"
|
notifyFreqCardSecondsKey = "notify_freq_card_seconds"
|
||||||
notifyFreqBoardSecondsKey = "notify_freq_board_seconds"
|
notifyFreqBoardSecondsKey = "notify_freq_board_seconds"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BoardsEmbed struct {
|
type BoardsEmbed struct {
|
||||||
OriginalPath string `json:"originalPath"`
|
OriginalPath string `json:"originalPath"`
|
||||||
WorkspaceID string `json:"workspaceID"`
|
TeamID string `json:"teamID"`
|
||||||
ViewID string `json:"viewID"`
|
ViewID string `json:"viewID"`
|
||||||
BoardID string `json:"boardID"`
|
BoardID string `json:"boardID"`
|
||||||
CardID string `json:"cardID"`
|
CardID string `json:"cardID"`
|
||||||
|
@ -112,7 +114,9 @@ func (p *Plugin) OnActivate() error {
|
||||||
db = layeredStore
|
db = layeredStore
|
||||||
}
|
}
|
||||||
|
|
||||||
p.wsPluginAdapter = ws.NewPluginAdapter(p.API, auth.New(cfg, db), logger)
|
permissionsService := mmpermissions.New(db, p.API)
|
||||||
|
|
||||||
|
p.wsPluginAdapter = ws.NewPluginAdapter(p.API, auth.New(cfg, db, permissionsService), db, logger)
|
||||||
|
|
||||||
backendParams := notifyBackendParams{
|
backendParams := notifyBackendParams{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
|
@ -137,13 +141,14 @@ func (p *Plugin) OnActivate() error {
|
||||||
mentionsBackend.AddListener(subscriptionsBackend)
|
mentionsBackend.AddListener(subscriptionsBackend)
|
||||||
|
|
||||||
params := server.Params{
|
params := server.Params{
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
SingleUserToken: "",
|
SingleUserToken: "",
|
||||||
DBStore: db,
|
DBStore: db,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
ServerID: serverID,
|
ServerID: serverID,
|
||||||
WSAdapter: p.wsPluginAdapter,
|
WSAdapter: p.wsPluginAdapter,
|
||||||
NotifyBackends: notifyBackends,
|
NotifyBackends: notifyBackends,
|
||||||
|
PermissionsService: permissionsService,
|
||||||
}
|
}
|
||||||
|
|
||||||
server, err := server.New(params)
|
server, err := server.New(params)
|
||||||
|
@ -373,11 +378,11 @@ func postWithBoardsEmbed(post *mmModel.Post) *mmModel.Post {
|
||||||
return post
|
return post
|
||||||
}
|
}
|
||||||
|
|
||||||
workspaceID, boardID, viewID, cardID := returnBoardsParams(pathSplit)
|
teamID, boardID, viewID, cardID := returnBoardsParams(pathSplit)
|
||||||
|
|
||||||
if workspaceID != "" && boardID != "" && viewID != "" && cardID != "" {
|
if teamID != "" && boardID != "" && viewID != "" && cardID != "" {
|
||||||
b, _ := json.Marshal(BoardsEmbed{
|
b, _ := json.Marshal(BoardsEmbed{
|
||||||
WorkspaceID: workspaceID,
|
TeamID: teamID,
|
||||||
BoardID: boardID,
|
BoardID: boardID,
|
||||||
ViewID: viewID,
|
ViewID: viewID,
|
||||||
CardID: cardID,
|
CardID: cardID,
|
||||||
|
@ -430,7 +435,7 @@ func getFirstLinkAndShortenAllBoardsLink(postMessage string) (firstLink, newPost
|
||||||
return firstLink, newPostMessage
|
return firstLink, newPostMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
func returnBoardsParams(pathArray []string) (workspaceID, boardID, viewID, cardID string) {
|
func returnBoardsParams(pathArray []string) (teamID, boardID, viewID, cardID string) {
|
||||||
// The reason we are doing this search for the first instance of boards or plugins is to take into account URL subpaths
|
// The reason we are doing this search for the first instance of boards or plugins is to take into account URL subpaths
|
||||||
index := -1
|
index := -1
|
||||||
for i := 0; i < len(pathArray); i++ {
|
for i := 0; i < len(pathArray); i++ {
|
||||||
|
@ -441,7 +446,7 @@ func returnBoardsParams(pathArray []string) (workspaceID, boardID, viewID, cardI
|
||||||
}
|
}
|
||||||
|
|
||||||
if index == -1 {
|
if index == -1 {
|
||||||
return workspaceID, boardID, viewID, cardID
|
return teamID, boardID, viewID, cardID
|
||||||
}
|
}
|
||||||
|
|
||||||
// If at index, the parameter in the path is boards,
|
// If at index, the parameter in the path is boards,
|
||||||
|
@ -450,27 +455,27 @@ func returnBoardsParams(pathArray []string) (workspaceID, boardID, viewID, cardI
|
||||||
// If at index, the parameter in the path is plugins,
|
// If at index, the parameter in the path is plugins,
|
||||||
// then we've copied this from a shared board
|
// then we've copied this from a shared board
|
||||||
|
|
||||||
// For card links copied on a non-shared board, the path looks like {...Mattermost Url}.../boards/workspace/workspaceID/boardID/viewID/cardID
|
// For card links copied on a non-shared board, the path looks like {...Mattermost Url}.../boards/team/teamID/boardID/viewID/cardID
|
||||||
|
|
||||||
// For card links copied on a shared board, the path looks like
|
// For card links copied on a shared board, the path looks like
|
||||||
// {...Mattermost Url}.../plugins/focalboard/workspace/workspaceID/shared/boardID/viewID/cardID?r=read_token
|
// {...Mattermost Url}.../plugins/focalboard/team/teamID/shared/boardID/viewID/cardID?r=read_token
|
||||||
|
|
||||||
// This is a non-shared board card link
|
// This is a non-shared board card link
|
||||||
if len(pathArray)-index == 6 && pathArray[index] == "boards" && pathArray[index+1] == "workspace" {
|
if len(pathArray)-index == 6 && pathArray[index] == "boards" && pathArray[index+1] == "team" {
|
||||||
workspaceID = pathArray[index+2]
|
teamID = pathArray[index+2]
|
||||||
boardID = pathArray[index+3]
|
boardID = pathArray[index+3]
|
||||||
viewID = pathArray[index+4]
|
viewID = pathArray[index+4]
|
||||||
cardID = pathArray[index+5]
|
cardID = pathArray[index+5]
|
||||||
} else if len(pathArray)-index == 8 && pathArray[index] == "plugins" &&
|
} else if len(pathArray)-index == 8 && pathArray[index] == "plugins" &&
|
||||||
pathArray[index+1] == "focalboard" &&
|
pathArray[index+1] == "focalboard" &&
|
||||||
pathArray[index+2] == "workspace" &&
|
pathArray[index+2] == "team" &&
|
||||||
pathArray[index+4] == "shared" { // This is a shared board card link
|
pathArray[index+4] == "shared" { // This is a shared board card link
|
||||||
workspaceID = pathArray[index+3]
|
teamID = pathArray[index+3]
|
||||||
boardID = pathArray[index+5]
|
boardID = pathArray[index+5]
|
||||||
viewID = pathArray[index+6]
|
viewID = pathArray[index+6]
|
||||||
cardID = pathArray[index+7]
|
cardID = pathArray[index+7]
|
||||||
}
|
}
|
||||||
return workspaceID, boardID, viewID, cardID
|
return teamID, boardID, viewID, cardID
|
||||||
}
|
}
|
||||||
|
|
||||||
func isBoardsLink(link string) bool {
|
func isBoardsLink(link string) bool {
|
||||||
|
@ -489,6 +494,6 @@ func isBoardsLink(link string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
workspaceID, boardID, viewID, cardID := returnBoardsParams(pathSplit)
|
teamID, boardID, viewID, cardID := returnBoardsParams(pathSplit)
|
||||||
return workspaceID != "" && boardID != "" && viewID != "" && cardID != ""
|
return teamID != "" && boardID != "" && viewID != "" && cardID != ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,17 +36,34 @@ function mapStateToProps(state: GlobalState) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FocalboardEmbeddedData {
|
||||||
|
teamID: string
|
||||||
|
cardID: string
|
||||||
|
boardID: string
|
||||||
|
readToken: string
|
||||||
|
originalPath: string
|
||||||
|
|
||||||
|
constructor(rawData: string) {
|
||||||
|
const parsed = JSON.parse(rawData)
|
||||||
|
this.teamID = parsed.teamID || parsed.workspaceID
|
||||||
|
this.cardID = parsed.cardID
|
||||||
|
this.boardID = parsed.boardID
|
||||||
|
this.readToken = parsed.readToken
|
||||||
|
this.originalPath = parsed.originalPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const BoardsUnfurl = (props: Props): JSX.Element => {
|
const BoardsUnfurl = (props: Props): JSX.Element => {
|
||||||
if (!props.embed || !props.embed.data) {
|
if (!props.embed || !props.embed.data) {
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
const {embed, locale} = props
|
const {embed, locale} = props
|
||||||
const focalboardInformation = JSON.parse(embed.data)
|
const focalboardInformation: FocalboardEmbeddedData = new FocalboardEmbeddedData(embed.data)
|
||||||
const {workspaceID, cardID, boardID, readToken, originalPath} = focalboardInformation
|
const {teamID, cardID, boardID, readToken, originalPath} = focalboardInformation
|
||||||
const baseURL = window.location.origin
|
const baseURL = window.location.origin
|
||||||
|
|
||||||
if (!workspaceID || !cardID || !boardID) {
|
if (!teamID || !cardID || !boardID) {
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,20 +74,19 @@ const BoardsUnfurl = (props: Props): JSX.Element => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const [cards, boards] = await Promise.all(
|
const [cards, fetchedBoard] = await Promise.all(
|
||||||
[
|
[
|
||||||
octoClient.getBlocksWithBlockID(cardID, workspaceID, readToken),
|
octoClient.getBlocksWithBlockID(cardID, boardID, readToken),
|
||||||
octoClient.getBlocksWithBlockID(boardID, workspaceID, readToken),
|
octoClient.getBoard(boardID),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
const [firstCard] = cards as Card[]
|
const [firstCard] = cards as Card[]
|
||||||
const [firstBoard] = boards as Board[]
|
if (!firstCard || !fetchedBoard) {
|
||||||
if (!firstCard || !firstBoard) {
|
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
setCard(firstCard)
|
setCard(firstCard)
|
||||||
setBoard(firstBoard)
|
setBoard(fetchedBoard)
|
||||||
|
|
||||||
if (firstCard.fields.contentOrder.length) {
|
if (firstCard.fields.contentOrder.length) {
|
||||||
let [firstContentBlockID] = firstCard.fields?.contentOrder
|
let [firstContentBlockID] = firstCard.fields?.contentOrder
|
||||||
|
@ -79,7 +95,7 @@ const BoardsUnfurl = (props: Props): JSX.Element => {
|
||||||
[firstContentBlockID] = firstContentBlockID
|
[firstContentBlockID] = firstContentBlockID
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentBlock = await octoClient.getBlocksWithBlockID(firstContentBlockID, workspaceID, readToken) as ContentBlock[]
|
const contentBlock = await octoClient.getBlocksWithBlockID(firstContentBlockID, boardID, readToken) as ContentBlock[]
|
||||||
const [firstContentBlock] = contentBlock
|
const [firstContentBlock] = contentBlock
|
||||||
if (!firstContentBlock) {
|
if (!firstContentBlock) {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
@ -103,8 +119,8 @@ const BoardsUnfurl = (props: Props): JSX.Element => {
|
||||||
let totalNumberOfCheckBoxes = 0
|
let totalNumberOfCheckBoxes = 0
|
||||||
|
|
||||||
// We will just display the first 3 or less select/multi-select properties and do a +n for remainder if any remainder
|
// We will just display the first 3 or less select/multi-select properties and do a +n for remainder if any remainder
|
||||||
for (let i = 0; i < board.fields.cardProperties.length; i++) {
|
for (let i = 0; i < board.cardProperties.length; i++) {
|
||||||
const optionInBoard = board.fields.cardProperties[i]
|
const optionInBoard = board.cardProperties[i]
|
||||||
let valueToLookUp = card.fields.properties[optionInBoard.id]
|
let valueToLookUp = card.fields.properties[optionInBoard.id]
|
||||||
|
|
||||||
// Since these are always set and not included in the card properties
|
// Since these are always set and not included in the card properties
|
||||||
|
@ -132,7 +148,11 @@ const BoardsUnfurl = (props: Props): JSX.Element => {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
propertiesToDisplay.push({optionName: optionInBoard.name, optionValue: optionSelected.value, optionValueColour: optionSelected.color})
|
propertiesToDisplay.push({
|
||||||
|
optionName: optionInBoard.name,
|
||||||
|
optionValue: optionSelected.value,
|
||||||
|
optionValueColour: optionSelected.color,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
remainder += (Object.keys(card.fields.properties).length - propertiesToDisplay.length - totalNumberOfCheckBoxes)
|
remainder += (Object.keys(card.fields.properties).length - propertiesToDisplay.length - totalNumberOfCheckBoxes)
|
||||||
html = Utils.htmlFromMarkdown(content?.title || '')
|
html = Utils.htmlFromMarkdown(content?.title || '')
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import {Utils} from '../../../webapp/src/utils'
|
import {Utils} from '../../../webapp/src/utils'
|
||||||
|
|
||||||
|
@ -10,9 +9,12 @@ type State = {
|
||||||
hasError: boolean
|
hasError: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ErrorBoundary extends React.Component {
|
type Props = {
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ErrorBoundary extends React.Component<Props, State> {
|
||||||
state = {hasError: false}
|
state = {hasError: false}
|
||||||
propTypes = {children: PropTypes.node.isRequired}
|
|
||||||
msg = 'Redirecting to error page...'
|
msg = 'Redirecting to error page...'
|
||||||
|
|
||||||
handleError = (): void => {
|
handleError = (): void => {
|
||||||
|
|
|
@ -9,18 +9,22 @@ import {rudderAnalytics, RudderTelemetryHandler} from 'mattermost-redux/client/r
|
||||||
|
|
||||||
import {GlobalState} from 'mattermost-redux/types/store'
|
import {GlobalState} from 'mattermost-redux/types/store'
|
||||||
|
|
||||||
const windowAny = (window as any)
|
import {selectTeam} from 'mattermost-redux/actions/teams'
|
||||||
|
|
||||||
|
import {SuiteWindow} from '../../../webapp/src/types/index'
|
||||||
|
|
||||||
|
const windowAny = (window as SuiteWindow)
|
||||||
windowAny.baseURL = '/plugins/focalboard'
|
windowAny.baseURL = '/plugins/focalboard'
|
||||||
windowAny.frontendBaseURL = '/boards'
|
windowAny.frontendBaseURL = '/boards'
|
||||||
windowAny.isFocalboardPlugin = true
|
windowAny.isFocalboardPlugin = true
|
||||||
|
|
||||||
import App from '../../../webapp/src/app'
|
import App from '../../../webapp/src/app'
|
||||||
import store from '../../../webapp/src/store'
|
import store from '../../../webapp/src/store'
|
||||||
|
import {setTeam} from '../../../webapp/src/store/teams'
|
||||||
import {Utils} from '../../../webapp/src/utils'
|
import {Utils} from '../../../webapp/src/utils'
|
||||||
import GlobalHeader from '../../../webapp/src/components/globalHeader/globalHeader'
|
import GlobalHeader from '../../../webapp/src/components/globalHeader/globalHeader'
|
||||||
import FocalboardIcon from '../../../webapp/src/widgets/icons/logo'
|
import FocalboardIcon from '../../../webapp/src/widgets/icons/logo'
|
||||||
import {setMattermostTheme} from '../../../webapp/src/theme'
|
import {setMattermostTheme} from '../../../webapp/src/theme'
|
||||||
import {UserSettings} from '../../../webapp/src/userSettings'
|
|
||||||
|
|
||||||
import TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../../webapp/src/telemetry/telemetryClient'
|
import TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../../webapp/src/telemetry/telemetryClient'
|
||||||
|
|
||||||
|
@ -30,7 +34,13 @@ import '../../../webapp/src/styles/labels.scss'
|
||||||
import octoClient from '../../../webapp/src/octoClient'
|
import octoClient from '../../../webapp/src/octoClient'
|
||||||
|
|
||||||
import BoardsUnfurl from './components/boardsUnfurl/boardsUnfurl'
|
import BoardsUnfurl from './components/boardsUnfurl/boardsUnfurl'
|
||||||
import wsClient, {MMWebSocketClient, ACTION_UPDATE_BLOCK, ACTION_UPDATE_CLIENT_CONFIG, ACTION_UPDATE_SUBSCRIPTION} from './../../../webapp/src/wsclient'
|
import wsClient, {
|
||||||
|
MMWebSocketClient,
|
||||||
|
ACTION_UPDATE_BLOCK,
|
||||||
|
ACTION_UPDATE_CLIENT_CONFIG,
|
||||||
|
ACTION_UPDATE_SUBSCRIPTION,
|
||||||
|
ACTION_UPDATE_CATEGORY, ACTION_UPDATE_BLOCK_CATEGORY, ACTION_UPDATE_BOARD,
|
||||||
|
} from './../../../webapp/src/wsclient'
|
||||||
|
|
||||||
import manifest from './manifest'
|
import manifest from './manifest'
|
||||||
import ErrorBoundary from './error_boundary'
|
import ErrorBoundary from './error_boundary'
|
||||||
|
@ -164,6 +174,7 @@ export default class Plugin {
|
||||||
let theme = mmStore.getState().entities.preferences.myPreferences.theme
|
let theme = mmStore.getState().entities.preferences.myPreferences.theme
|
||||||
setMattermostTheme(theme)
|
setMattermostTheme(theme)
|
||||||
let lastViewedChannel = mmStore.getState().entities.channels.currentChannelId
|
let lastViewedChannel = mmStore.getState().entities.channels.currentChannelId
|
||||||
|
let prevTeamID: string
|
||||||
mmStore.subscribe(() => {
|
mmStore.subscribe(() => {
|
||||||
const currentUserId = mmStore.getState().entities.users.currentUserId
|
const currentUserId = mmStore.getState().entities.users.currentUserId
|
||||||
const currentChannel = mmStore.getState().entities.channels.currentChannelId
|
const currentChannel = mmStore.getState().entities.channels.currentChannelId
|
||||||
|
@ -171,22 +182,41 @@ export default class Plugin {
|
||||||
localStorage.setItem('focalboardLastViewedChannel:' + currentUserId, currentChannel)
|
localStorage.setItem('focalboardLastViewedChannel:' + currentUserId, currentChannel)
|
||||||
lastViewedChannel = currentChannel
|
lastViewedChannel = currentChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Watch for change in active team.
|
||||||
|
// This handles the user selecting a team from the team sidebar.
|
||||||
|
const currentTeamID = mmStore.getState().entities.teams.currentTeamId
|
||||||
|
if (currentTeamID && currentTeamID !== prevTeamID) {
|
||||||
|
prevTeamID = currentTeamID
|
||||||
|
store.dispatch(setTeam(currentTeamID))
|
||||||
|
browserHistory.push(`/team/${currentTeamID}`)
|
||||||
|
wsClient.subscribeToTeam(currentTeamID)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (this.registry.registerProduct) {
|
if (this.registry.registerProduct) {
|
||||||
windowAny.frontendBaseURL = subpath + '/boards'
|
windowAny.frontendBaseURL = subpath + '/boards'
|
||||||
const goToFocalboardWorkspace = () => {
|
const goToFocalboard = () => {
|
||||||
const currentChannel = mmStore.getState().entities.channels.currentChannelId
|
const currentTeam = mmStore.getState().entities.teams.currentTeamId
|
||||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ClickChannelHeader, {workspaceID: currentChannel})
|
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ClickChannelHeader, {teamID: currentTeam})
|
||||||
window.open(`${windowAny.frontendBaseURL}/workspace/${currentChannel}`, '_blank', 'noopener')
|
window.open(`${windowAny.frontendBaseURL}/team/${currentTeam}`, '_blank', 'noopener')
|
||||||
}
|
}
|
||||||
this.channelHeaderButtonId = registry.registerChannelHeaderButtonAction(<FocalboardIcon/>, goToFocalboardWorkspace, 'Boards', 'Boards')
|
|
||||||
|
this.channelHeaderButtonId = registry.registerChannelHeaderButtonAction(<FocalboardIcon/>, goToFocalboard, 'Boards', 'Boards')
|
||||||
|
this.registry.registerProduct(
|
||||||
|
'/boards',
|
||||||
|
'product-boards',
|
||||||
|
'Boards',
|
||||||
|
'/boards',
|
||||||
|
MainApp,
|
||||||
|
HeaderComponent,
|
||||||
|
() => null,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
|
||||||
const goToFocalboardTemplate = () => {
|
const goToFocalboardTemplate = () => {
|
||||||
const currentChannel = mmStore.getState().entities.channels.currentChannelId
|
const currentChannel = mmStore.getState().entities.channels.currentChannelId
|
||||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ClickChannelIntro, {workspaceID: currentChannel})
|
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ClickChannelIntro, {channelID: currentChannel})
|
||||||
UserSettings.lastBoardId = null
|
|
||||||
UserSettings.lastViewId = null
|
|
||||||
window.open(`${windowAny.frontendBaseURL}/workspace/${currentChannel}`, '_blank', 'noopener')
|
window.open(`${windowAny.frontendBaseURL}/workspace/${currentChannel}`, '_blank', 'noopener')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,11 +224,9 @@ export default class Plugin {
|
||||||
this.channelHeaderButtonId = registry.registerChannelIntroButtonAction(<FocalboardIcon/>, goToFocalboardTemplate, 'Boards')
|
this.channelHeaderButtonId = registry.registerChannelIntroButtonAction(<FocalboardIcon/>, goToFocalboardTemplate, 'Boards')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.registry.registerProduct('/boards', 'product-boards', 'Boards', '/boards/welcome', MainApp, HeaderComponent)
|
|
||||||
|
|
||||||
if (this.registry.registerAppBarComponent) {
|
if (this.registry.registerAppBarComponent) {
|
||||||
const appBarIconURL = windowAny.baseURL + '/public/app-bar-icon.png'
|
const appBarIconURL = windowAny.baseURL + '/public/app-bar-icon.png'
|
||||||
this.registry.registerAppBarComponent(appBarIconURL, goToFocalboardWorkspace, 'Open Boards Workspace')
|
this.registry.registerAppBarComponent(appBarIconURL, goToFocalboard, 'Open Boards')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.registry.registerPostWillRenderEmbedComponent((embed) => embed.type === 'boards', BoardsUnfurl, false)
|
this.registry.registerPostWillRenderEmbedComponent((embed) => embed.type === 'boards', BoardsUnfurl, false)
|
||||||
|
@ -247,7 +275,9 @@ export default class Plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
// register websocket handlers
|
// register websocket handlers
|
||||||
this.registry?.registerWebSocketEventHandler(`custom_${manifest.id}_${ACTION_UPDATE_BLOCK}`, (e: any) => wsClient.updateBlockHandler(e.data))
|
this.registry?.registerWebSocketEventHandler(`custom_${manifest.id}_${ACTION_UPDATE_BOARD}`, (e: any) => wsClient.updateHandler(e.data))
|
||||||
|
this.registry?.registerWebSocketEventHandler(`custom_${manifest.id}_${ACTION_UPDATE_CATEGORY}`, (e: any) => wsClient.updateHandler(e.data))
|
||||||
|
this.registry?.registerWebSocketEventHandler(`custom_${manifest.id}_${ACTION_UPDATE_BLOCK_CATEGORY}`, (e: any) => wsClient.updateHandler(e.data))
|
||||||
this.registry?.registerWebSocketEventHandler(`custom_${manifest.id}_${ACTION_UPDATE_CLIENT_CONFIG}`, (e: any) => wsClient.updateClientConfigHandler(e.data))
|
this.registry?.registerWebSocketEventHandler(`custom_${manifest.id}_${ACTION_UPDATE_CLIENT_CONFIG}`, (e: any) => wsClient.updateClientConfigHandler(e.data))
|
||||||
this.registry?.registerWebSocketEventHandler(`custom_${manifest.id}_${ACTION_UPDATE_SUBSCRIPTION}`, (e: any) => wsClient.updateSubscriptionHandler(e.data))
|
this.registry?.registerWebSocketEventHandler(`custom_${manifest.id}_${ACTION_UPDATE_SUBSCRIPTION}`, (e: any) => wsClient.updateSubscriptionHandler(e.data))
|
||||||
this.registry?.registerWebSocketEventHandler('plugin_statuses_changed', (e: any) => wsClient.pluginStatusesChangedHandler(e.data))
|
this.registry?.registerWebSocketEventHandler('plugin_statuses_changed', (e: any) => wsClient.pluginStatusesChangedHandler(e.data))
|
||||||
|
@ -267,6 +297,16 @@ export default class Plugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
windowAny.setTeamInSidebar = (teamID: string) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
mmStore.dispatch(selectTeam(teamID))
|
||||||
|
}
|
||||||
|
windowAny.getCurrentTeamId = (): string => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
return mmStore.getState().entities.teams.currentTeamId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uninitialize(): void {
|
uninitialize(): void {
|
||||||
|
|
|
@ -10,7 +10,7 @@ export interface PluginRegistry {
|
||||||
registerCustomRoute(route: string, component: React.ElementType)
|
registerCustomRoute(route: string, component: React.ElementType)
|
||||||
registerProductRoute(route: string, component: React.ElementType)
|
registerProductRoute(route: string, component: React.ElementType)
|
||||||
unregisterComponent(componentId: string)
|
unregisterComponent(componentId: string)
|
||||||
registerProduct(baseURL: string, switcherIcon: string, switcherText: string, switcherLinkURL: string, mainComponent: React.ElementType, headerCompoent: React.ElementType)
|
registerProduct(baseURL: string, switcherIcon: string, switcherText: string, switcherLinkURL: string, mainComponent: React.ElementType, headerCentreComponent: React.ElementType, headerRightComponent?: React.ElementType, showTeamSidebar: boolean)
|
||||||
registerPostWillRenderEmbedComponent(match: (embed: {type: string, data: any}) => void, component: any, toggleable: boolean)
|
registerPostWillRenderEmbedComponent(match: (embed: {type: string, data: any}) => void, component: any, toggleable: boolean)
|
||||||
registerWebSocketEventHandler(event: string, handler: (e: any) => void)
|
registerWebSocketEventHandler(event: string, handler: (e: any) => void)
|
||||||
unregisterWebSocketEventHandler(event: string)
|
unregisterWebSocketEventHandler(event: string)
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
**/*.go {
|
**/*.go {
|
||||||
prep: cd server && go test -race -v ./...
|
prep: cd server && go test -tags $FOCALBOARD_BUILD_TAGS -race -v ./...
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
**/*.go !**/*_test.go {
|
**/*.go !**/*_test.go {
|
||||||
prep: cd server && go build -o ../bin/focalboard-server ./main
|
prep: cd server && go build -tags $FOCALBOARD_BUILD_TAGS -o ../bin/focalboard-server ./main
|
||||||
daemon +sigterm: ./bin/focalboard-server $FOCALBOARDSERVER_ARGS
|
daemon +sigterm: ./bin/focalboard-server $FOCALBOARDSERVER_ARGS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ linters-settings:
|
||||||
disable:
|
disable:
|
||||||
- fieldalignment
|
- fieldalignment
|
||||||
lll:
|
lll:
|
||||||
line-length: 150
|
line-length: 180
|
||||||
dupl:
|
dupl:
|
||||||
threshold: 200
|
threshold: 200
|
||||||
revive:
|
revive:
|
||||||
|
|
2627
server/api/api.go
2627
server/api/api.go
File diff suppressed because it is too large
Load diff
|
@ -5,6 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/services/audit"
|
"github.com/mattermost/focalboard/server/services/audit"
|
||||||
)
|
)
|
||||||
|
@ -13,26 +14,20 @@ const (
|
||||||
archiveExtension = ".boardarchive"
|
archiveExtension = ".boardarchive"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *API) handleArchiveExport(w http.ResponseWriter, r *http.Request) {
|
func (a *API) handleArchiveExportBoard(w http.ResponseWriter, r *http.Request) {
|
||||||
// swagger:operation GET /api/v1/workspaces/{workspaceID}/archive/export archiveExport
|
// swagger:operation GET /api/v1/boards/{boardID}/archive/export archiveExportBoard
|
||||||
//
|
//
|
||||||
// Exports an archive of all blocks for one or more boards. If board_id is provided then
|
// Exports an archive of all blocks for one boards.
|
||||||
// only that board will be exported, otherwise all boards in the workspace are exported.
|
|
||||||
//
|
//
|
||||||
// ---
|
// ---
|
||||||
// produces:
|
// produces:
|
||||||
// - application/json
|
// - application/json
|
||||||
// parameters:
|
// parameters:
|
||||||
// - name: workspaceID
|
// - name: boardID
|
||||||
// in: path
|
// in: path
|
||||||
// description: Workspace ID
|
// description: Id of board to export
|
||||||
// required: true
|
// required: true
|
||||||
// type: string
|
// type: string
|
||||||
// - name: board_id
|
|
||||||
// in: path
|
|
||||||
// description: Id of board to to export
|
|
||||||
// required: false
|
|
||||||
// type: string
|
|
||||||
// security:
|
// security:
|
||||||
// - BearerAuth: []
|
// - BearerAuth: []
|
||||||
// responses:
|
// responses:
|
||||||
|
@ -47,25 +42,92 @@ func (a *API) handleArchiveExport(w http.ResponseWriter, r *http.Request) {
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/ErrorResponse"
|
// "$ref": "#/definitions/ErrorResponse"
|
||||||
|
|
||||||
query := r.URL.Query()
|
vars := mux.Vars(r)
|
||||||
boardID := query.Get("board_id")
|
boardID := vars["boardID"]
|
||||||
container, err := a.getContainer(r)
|
|
||||||
if err != nil {
|
|
||||||
a.noContainerErrorResponse(w, r.URL.Path, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
auditRec := a.makeAuditRecord(r, "archiveExport", audit.Fail)
|
auditRec := a.makeAuditRecord(r, "archiveExportBoard", audit.Fail)
|
||||||
defer a.audit.LogRecord(audit.LevelRead, auditRec)
|
defer a.audit.LogRecord(audit.LevelRead, auditRec)
|
||||||
auditRec.AddMeta("BoardID", boardID)
|
auditRec.AddMeta("BoardID", boardID)
|
||||||
|
|
||||||
var boardIDs []string
|
board, err := a.app.GetBoard(boardID)
|
||||||
if boardID != "" {
|
if err != nil {
|
||||||
boardIDs = []string{boardID}
|
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
if board == nil {
|
||||||
|
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
opts := model.ExportArchiveOptions{
|
opts := model.ExportArchiveOptions{
|
||||||
WorkspaceID: container.WorkspaceID,
|
TeamID: board.TeamID,
|
||||||
BoardIDs: boardIDs,
|
BoardIDs: []string{board.ID},
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("archive-%s%s", time.Now().Format("2006-01-02"), archiveExtension)
|
||||||
|
w.Header().Set("Content-Type", "application/octet-stream")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename="+filename)
|
||||||
|
w.Header().Set("Content-Transfer-Encoding", "binary")
|
||||||
|
|
||||||
|
if err := a.app.ExportArchive(w, opts); err != nil {
|
||||||
|
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
auditRec.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *API) handleArchiveExportTeam(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// swagger:operation GET /api/v1/teams/{teamID}/archive/export archiveExportTeam
|
||||||
|
//
|
||||||
|
// Exports an archive of all blocks for all the boards in a team.
|
||||||
|
//
|
||||||
|
// ---
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: teamID
|
||||||
|
// in: path
|
||||||
|
// description: Id of team
|
||||||
|
// required: true
|
||||||
|
// type: string
|
||||||
|
// security:
|
||||||
|
// - BearerAuth: []
|
||||||
|
// responses:
|
||||||
|
// '200':
|
||||||
|
// description: success
|
||||||
|
// content:
|
||||||
|
// application-octet-stream:
|
||||||
|
// type: string
|
||||||
|
// format: binary
|
||||||
|
// default:
|
||||||
|
// description: internal error
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/ErrorResponse"
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
teamID := vars["teamID"]
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
session, _ := ctx.Value(sessionContextKey).(*model.Session)
|
||||||
|
userID := session.UserID
|
||||||
|
|
||||||
|
auditRec := a.makeAuditRecord(r, "archiveExportTeam", audit.Fail)
|
||||||
|
defer a.audit.LogRecord(audit.LevelRead, auditRec)
|
||||||
|
auditRec.AddMeta("TeamID", teamID)
|
||||||
|
|
||||||
|
boards, err := a.app.GetBoardsForUserAndTeam(userID, teamID)
|
||||||
|
if err != nil {
|
||||||
|
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ids := []string{}
|
||||||
|
for _, board := range boards {
|
||||||
|
ids = append(ids, board.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := model.ExportArchiveOptions{
|
||||||
|
TeamID: teamID,
|
||||||
|
BoardIDs: ids,
|
||||||
}
|
}
|
||||||
|
|
||||||
filename := fmt.Sprintf("archive-%s%s", time.Now().Format("2006-01-02"), archiveExtension)
|
filename := fmt.Sprintf("archive-%s%s", time.Now().Format("2006-01-02"), archiveExtension)
|
||||||
|
@ -81,7 +143,7 @@ func (a *API) handleArchiveExport(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) handleArchiveImport(w http.ResponseWriter, r *http.Request) {
|
func (a *API) handleArchiveImport(w http.ResponseWriter, r *http.Request) {
|
||||||
// swagger:operation POST /api/v1/workspaces/{workspaceID}/archive/import archiveImport
|
// swagger:operation POST /api/v1/boards/{boardID}/archive/import archiveImport
|
||||||
//
|
//
|
||||||
// Import an archive of boards.
|
// Import an archive of boards.
|
||||||
//
|
//
|
||||||
|
@ -91,7 +153,7 @@ func (a *API) handleArchiveImport(w http.ResponseWriter, r *http.Request) {
|
||||||
// consumes:
|
// consumes:
|
||||||
// - multipart/form-data
|
// - multipart/form-data
|
||||||
// parameters:
|
// parameters:
|
||||||
// - name: workspaceID
|
// - name: boardID
|
||||||
// in: path
|
// in: path
|
||||||
// description: Workspace ID
|
// description: Workspace ID
|
||||||
// required: true
|
// required: true
|
||||||
|
@ -111,16 +173,13 @@ func (a *API) handleArchiveImport(w http.ResponseWriter, r *http.Request) {
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/ErrorResponse"
|
// "$ref": "#/definitions/ErrorResponse"
|
||||||
|
|
||||||
container, err := a.getContainer(r)
|
|
||||||
if err != nil {
|
|
||||||
a.noContainerErrorResponse(w, r.URL.Path, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
session, _ := ctx.Value(sessionContextKey).(*model.Session)
|
session, _ := ctx.Value(sessionContextKey).(*model.Session)
|
||||||
userID := session.UserID
|
userID := session.UserID
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
teamID := vars["teamID"]
|
||||||
|
|
||||||
file, handle, err := r.FormFile(UploadFormFileKey)
|
file, handle, err := r.FormFile(UploadFormFileKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(w, "%v", err)
|
fmt.Fprintf(w, "%v", err)
|
||||||
|
@ -134,8 +193,8 @@ func (a *API) handleArchiveImport(w http.ResponseWriter, r *http.Request) {
|
||||||
auditRec.AddMeta("size", handle.Size)
|
auditRec.AddMeta("size", handle.Size)
|
||||||
|
|
||||||
opt := model.ImportArchiveOptions{
|
opt := model.ImportArchiveOptions{
|
||||||
WorkspaceID: container.WorkspaceID,
|
TeamID: teamID,
|
||||||
ModifiedBy: userID,
|
ModifiedBy: userID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := a.app.ImportArchive(file, opt); err != nil {
|
if err := a.app.ImportArchive(file, opt); err != nil {
|
||||||
|
|
|
@ -17,12 +17,7 @@ func (a *API) makeAuditRecord(r *http.Request, event string, initialStatus strin
|
||||||
userID = session.UserID
|
userID = session.UserID
|
||||||
}
|
}
|
||||||
|
|
||||||
workspaceID := "unknown"
|
teamID := "unknown"
|
||||||
container, err := a.getContainer(r)
|
|
||||||
if err == nil {
|
|
||||||
workspaceID = container.WorkspaceID
|
|
||||||
}
|
|
||||||
|
|
||||||
rec := &audit.Record{
|
rec := &audit.Record{
|
||||||
APIPath: r.URL.Path,
|
APIPath: r.URL.Path,
|
||||||
Event: event,
|
Event: event,
|
||||||
|
@ -31,7 +26,7 @@ func (a *API) makeAuditRecord(r *http.Request, event string, initialStatus strin
|
||||||
SessionID: sessionID,
|
SessionID: sessionID,
|
||||||
Client: r.UserAgent(),
|
Client: r.UserAgent(),
|
||||||
IPAddress: r.RemoteAddr,
|
IPAddress: r.RemoteAddr,
|
||||||
Meta: []audit.Meta{{K: audit.KeyWorkspaceID, V: workspaceID}},
|
Meta: []audit.Meta{{K: audit.KeyTeamID, V: teamID}},
|
||||||
}
|
}
|
||||||
|
|
||||||
return rec
|
return rec
|
||||||
|
|
|
@ -302,13 +302,13 @@ func (a *API) handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Validate token
|
// Validate token
|
||||||
if len(registerData.Token) > 0 {
|
if len(registerData.Token) > 0 {
|
||||||
workspace, err2 := a.app.GetRootWorkspace()
|
team, err2 := a.app.GetRootTeam()
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err2)
|
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err2)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if registerData.Token != workspace.SignupToken {
|
if registerData.Token != team.SignupToken {
|
||||||
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "invalid token", nil)
|
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "invalid token", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/auth"
|
"github.com/mattermost/focalboard/server/auth"
|
||||||
"github.com/mattermost/focalboard/server/services/config"
|
"github.com/mattermost/focalboard/server/services/config"
|
||||||
"github.com/mattermost/focalboard/server/services/metrics"
|
"github.com/mattermost/focalboard/server/services/metrics"
|
||||||
"github.com/mattermost/focalboard/server/services/notify"
|
"github.com/mattermost/focalboard/server/services/notify"
|
||||||
|
"github.com/mattermost/focalboard/server/services/permissions"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
"github.com/mattermost/focalboard/server/services/store"
|
||||||
"github.com/mattermost/focalboard/server/services/webhook"
|
"github.com/mattermost/focalboard/server/services/webhook"
|
||||||
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
"github.com/mattermost/focalboard/server/ws"
|
"github.com/mattermost/focalboard/server/ws"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
|
@ -14,6 +18,12 @@ import (
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/filestore"
|
"github.com/mattermost/mattermost-server/v6/shared/filestore"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
blockChangeNotifierQueueSize = 100
|
||||||
|
blockChangeNotifierPoolSize = 10
|
||||||
|
blockChangeNotifierShutdownTimeout = time.Second * 10
|
||||||
|
)
|
||||||
|
|
||||||
type Services struct {
|
type Services struct {
|
||||||
Auth *auth.Auth
|
Auth *auth.Auth
|
||||||
Store store.Store
|
Store store.Store
|
||||||
|
@ -22,19 +32,21 @@ type Services struct {
|
||||||
Metrics *metrics.Metrics
|
Metrics *metrics.Metrics
|
||||||
Notifications *notify.Service
|
Notifications *notify.Service
|
||||||
Logger *mlog.Logger
|
Logger *mlog.Logger
|
||||||
|
Permissions permissions.PermissionsService
|
||||||
SkipTemplateInit bool
|
SkipTemplateInit bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
config *config.Configuration
|
config *config.Configuration
|
||||||
store store.Store
|
store store.Store
|
||||||
auth *auth.Auth
|
auth *auth.Auth
|
||||||
wsAdapter ws.Adapter
|
wsAdapter ws.Adapter
|
||||||
filesBackend filestore.FileBackend
|
filesBackend filestore.FileBackend
|
||||||
webhook *webhook.Client
|
webhook *webhook.Client
|
||||||
metrics *metrics.Metrics
|
metrics *metrics.Metrics
|
||||||
notifications *notify.Service
|
notifications *notify.Service
|
||||||
logger *mlog.Logger
|
logger *mlog.Logger
|
||||||
|
blockChangeNotifier *utils.CallbackQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) SetConfig(config *config.Configuration) {
|
func (a *App) SetConfig(config *config.Configuration) {
|
||||||
|
@ -43,15 +55,16 @@ func (a *App) SetConfig(config *config.Configuration) {
|
||||||
|
|
||||||
func New(config *config.Configuration, wsAdapter ws.Adapter, services Services) *App {
|
func New(config *config.Configuration, wsAdapter ws.Adapter, services Services) *App {
|
||||||
app := &App{
|
app := &App{
|
||||||
config: config,
|
config: config,
|
||||||
store: services.Store,
|
store: services.Store,
|
||||||
auth: services.Auth,
|
auth: services.Auth,
|
||||||
wsAdapter: wsAdapter,
|
wsAdapter: wsAdapter,
|
||||||
filesBackend: services.FilesBackend,
|
filesBackend: services.FilesBackend,
|
||||||
webhook: services.Webhook,
|
webhook: services.Webhook,
|
||||||
metrics: services.Metrics,
|
metrics: services.Metrics,
|
||||||
notifications: services.Notifications,
|
notifications: services.Notifications,
|
||||||
logger: services.Logger,
|
logger: services.Logger,
|
||||||
|
blockChangeNotifier: utils.NewCallbackQueue("blockChangeNotifier", blockChangeNotifierQueueSize, blockChangeNotifierPoolSize, services.Logger),
|
||||||
}
|
}
|
||||||
app.initialize(services.SkipTemplateInit)
|
app.initialize(services.SkipTemplateInit)
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -3,7 +3,6 @@ package app
|
||||||
import (
|
import (
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/services/auth"
|
"github.com/mattermost/focalboard/server/services/auth"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
|
||||||
"github.com/mattermost/focalboard/server/utils"
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
|
@ -25,8 +24,8 @@ func (a *App) GetSession(token string) (*model.Session, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValidReadToken validates the read token for a block.
|
// IsValidReadToken validates the read token for a block.
|
||||||
func (a *App) IsValidReadToken(c store.Container, blockID string, readToken string) (bool, error) {
|
func (a *App) IsValidReadToken(boardID string, readToken string) (bool, error) {
|
||||||
return a.auth.IsValidReadToken(c, blockID, readToken)
|
return a.auth.IsValidReadToken(boardID, readToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRegisteredUserCount returns the number of registered users.
|
// GetRegisteredUserCount returns the number of registered users.
|
||||||
|
|
|
@ -1,137 +1,192 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/services/notify"
|
"github.com/mattermost/focalboard/server/services/notify"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
|
||||||
"github.com/mattermost/focalboard/server/utils"
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) GetBlocks(c store.Container, parentID string, blockType string) ([]model.Block, error) {
|
var ErrBlocksFromMultipleBoards = errors.New("the block set contain blocks from multiple boards")
|
||||||
|
|
||||||
|
func (a *App) GetBlocks(boardID, parentID string, blockType string) ([]model.Block, error) {
|
||||||
|
if boardID == "" {
|
||||||
|
return []model.Block{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
if blockType != "" && parentID != "" {
|
if blockType != "" && parentID != "" {
|
||||||
return a.store.GetBlocksWithParentAndType(c, parentID, blockType)
|
return a.store.GetBlocksWithParentAndType(boardID, parentID, blockType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if blockType != "" {
|
if blockType != "" {
|
||||||
return a.store.GetBlocksWithType(c, blockType)
|
return a.store.GetBlocksWithType(boardID, blockType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.store.GetBlocksWithParent(c, parentID)
|
return a.store.GetBlocksWithParent(boardID, parentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetBlocksWithRootID(c store.Container, rootID string) ([]model.Block, error) {
|
func (a *App) DuplicateBlock(boardID string, blockID string, userID string, asTemplate bool) ([]model.Block, error) {
|
||||||
return a.store.GetBlocksWithRootID(c, rootID)
|
board, err := a.GetBoard(boardID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if board == nil {
|
||||||
|
return nil, fmt.Errorf("cannot fetch board %s for DuplicateBlock: %w", boardID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks, err := a.store.DuplicateBlock(boardID, blockID, userID, asTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.blockChangeNotifier.Enqueue(func() error {
|
||||||
|
for _, block := range blocks {
|
||||||
|
a.wsAdapter.BroadcastBlockChange(board.TeamID, block)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return blocks, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetRootID(c store.Container, blockID string) (string, error) {
|
func (a *App) GetBlocksWithBoardID(boardID string) ([]model.Block, error) {
|
||||||
return a.store.GetRootID(c, blockID)
|
return a.store.GetBlocksWithBoardID(boardID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetParentID(c store.Container, blockID string) (string, error) {
|
func (a *App) PatchBlock(blockID string, blockPatch *model.BlockPatch, modifiedByID string) error {
|
||||||
return a.store.GetParentID(c, blockID)
|
oldBlock, err := a.store.GetBlock(blockID)
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) PatchBlock(c store.Container, blockID string, blockPatch *model.BlockPatch, modifiedByID string) error {
|
|
||||||
oldBlock, err := a.store.GetBlock(c, blockID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.store.PatchBlock(c, blockID, blockPatch, modifiedByID)
|
board, err := a.store.GetBoard(oldBlock.BoardID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.store.PatchBlock(blockID, blockPatch, modifiedByID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
a.metrics.IncrementBlocksPatched(1)
|
a.metrics.IncrementBlocksPatched(1)
|
||||||
block, err := a.store.GetBlock(c, blockID)
|
block, err := a.store.GetBlock(blockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
a.wsAdapter.BroadcastBlockChange(c.WorkspaceID, *block)
|
a.blockChangeNotifier.Enqueue(func() error {
|
||||||
go func() {
|
// broadcast on websocket
|
||||||
|
a.wsAdapter.BroadcastBlockChange(board.TeamID, *block)
|
||||||
|
|
||||||
|
// broadcast on webhooks
|
||||||
a.webhook.NotifyUpdate(*block)
|
a.webhook.NotifyUpdate(*block)
|
||||||
a.notifyBlockChanged(notify.Update, c, block, oldBlock, modifiedByID)
|
|
||||||
}()
|
// send notifications
|
||||||
|
a.notifyBlockChanged(notify.Update, block, oldBlock, modifiedByID)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) PatchBlocks(c store.Container, blockPatches *model.BlockPatchBatch, modifiedByID string) error {
|
func (a *App) PatchBlocks(teamID string, blockPatches *model.BlockPatchBatch, modifiedByID string) error {
|
||||||
oldBlocks := make([]model.Block, 0, len(blockPatches.BlockIDs))
|
oldBlocks := make([]model.Block, 0, len(blockPatches.BlockIDs))
|
||||||
for _, blockID := range blockPatches.BlockIDs {
|
for _, blockID := range blockPatches.BlockIDs {
|
||||||
oldBlock, err := a.store.GetBlock(c, blockID)
|
oldBlock, err := a.store.GetBlock(blockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
oldBlocks = append(oldBlocks, *oldBlock)
|
oldBlocks = append(oldBlocks, *oldBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := a.store.PatchBlocks(c, blockPatches, modifiedByID)
|
err := a.store.PatchBlocks(blockPatches, modifiedByID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
a.metrics.IncrementBlocksPatched(len(oldBlocks))
|
a.blockChangeNotifier.Enqueue(func() error {
|
||||||
for i, blockID := range blockPatches.BlockIDs {
|
a.metrics.IncrementBlocksPatched(len(oldBlocks))
|
||||||
newBlock, err := a.store.GetBlock(c, blockID)
|
for i, blockID := range blockPatches.BlockIDs {
|
||||||
if err != nil {
|
newBlock, err := a.store.GetBlock(blockID)
|
||||||
return nil
|
if err != nil {
|
||||||
}
|
return nil
|
||||||
a.wsAdapter.BroadcastBlockChange(c.WorkspaceID, *newBlock)
|
}
|
||||||
go func(currentIndex int) {
|
a.wsAdapter.BroadcastBlockChange(teamID, *newBlock)
|
||||||
a.webhook.NotifyUpdate(*newBlock)
|
a.webhook.NotifyUpdate(*newBlock)
|
||||||
a.notifyBlockChanged(notify.Update, c, newBlock, &oldBlocks[currentIndex], modifiedByID)
|
a.notifyBlockChanged(notify.Update, newBlock, &oldBlocks[i], modifiedByID)
|
||||||
}(i)
|
}
|
||||||
}
|
return nil
|
||||||
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) InsertBlock(c store.Container, block model.Block, modifiedByID string) error {
|
func (a *App) InsertBlock(block model.Block, modifiedByID string) error {
|
||||||
err := a.store.InsertBlock(c, &block, modifiedByID)
|
board, bErr := a.store.GetBoard(block.BoardID)
|
||||||
|
if bErr != nil {
|
||||||
|
return bErr
|
||||||
|
}
|
||||||
|
|
||||||
|
err := a.store.InsertBlock(&block, modifiedByID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.wsAdapter.BroadcastBlockChange(c.WorkspaceID, block)
|
a.blockChangeNotifier.Enqueue(func() error {
|
||||||
a.metrics.IncrementBlocksInserted(1)
|
a.wsAdapter.BroadcastBlockChange(board.TeamID, block)
|
||||||
go func() {
|
a.metrics.IncrementBlocksInserted(1)
|
||||||
a.webhook.NotifyUpdate(block)
|
a.webhook.NotifyUpdate(block)
|
||||||
a.notifyBlockChanged(notify.Add, c, &block, nil, modifiedByID)
|
a.notifyBlockChanged(notify.Add, &block, nil, modifiedByID)
|
||||||
}()
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) InsertBlocks(c store.Container, blocks []model.Block, modifiedByID string, allowNotifications bool) ([]model.Block, error) {
|
func (a *App) InsertBlocks(blocks []model.Block, modifiedByID string, allowNotifications bool) ([]model.Block, error) {
|
||||||
|
if len(blocks) == 0 {
|
||||||
|
return []model.Block{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// all blocks must belong to the same board
|
||||||
|
boardID := blocks[0].BoardID
|
||||||
|
for _, block := range blocks {
|
||||||
|
if block.BoardID != boardID {
|
||||||
|
return nil, ErrBlocksFromMultipleBoards
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
board, err := a.store.GetBoard(boardID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
needsNotify := make([]model.Block, 0, len(blocks))
|
needsNotify := make([]model.Block, 0, len(blocks))
|
||||||
for i := range blocks {
|
for i := range blocks {
|
||||||
err := a.store.InsertBlock(c, &blocks[i], modifiedByID)
|
err := a.store.InsertBlock(&blocks[i], modifiedByID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
blocks[i].WorkspaceID = c.WorkspaceID
|
|
||||||
needsNotify = append(needsNotify, blocks[i])
|
needsNotify = append(needsNotify, blocks[i])
|
||||||
|
|
||||||
a.wsAdapter.BroadcastBlockChange(c.WorkspaceID, blocks[i])
|
a.wsAdapter.BroadcastBlockChange(board.TeamID, blocks[i])
|
||||||
a.metrics.IncrementBlocksInserted(1)
|
a.metrics.IncrementBlocksInserted(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
a.blockChangeNotifier.Enqueue(func() error {
|
||||||
for _, b := range needsNotify {
|
for _, b := range needsNotify {
|
||||||
block := b
|
block := b
|
||||||
a.webhook.NotifyUpdate(block)
|
a.webhook.NotifyUpdate(block)
|
||||||
if allowNotifications {
|
if allowNotifications {
|
||||||
a.notifyBlockChanged(notify.Add, c, &block, nil, modifiedByID)
|
a.notifyBlockChanged(notify.Add, &block, nil, modifiedByID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
return blocks, nil
|
return blocks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) CopyCardFiles(sourceBoardID string, destWorkspaceID string, blocks []model.Block) error {
|
func (a *App) CopyCardFiles(sourceBoardID string, blocks []model.Block) error {
|
||||||
// Images attached in cards have a path comprising the card's board ID.
|
// Images attached in cards have a path comprising the card's board ID.
|
||||||
// When we create a template from this board, we need to copy the files
|
// When we create a template from this board, we need to copy the files
|
||||||
// with the new board ID in path.
|
// with the new board ID in path.
|
||||||
|
@ -139,7 +194,7 @@ func (a *App) CopyCardFiles(sourceBoardID string, destWorkspaceID string, blocks
|
||||||
// template) to fail to load.
|
// template) to fail to load.
|
||||||
|
|
||||||
// look up ID of source board, which may be different than the blocks.
|
// look up ID of source board, which may be different than the blocks.
|
||||||
board, err := a.GetBlockByID(store.Container{}, sourceBoardID)
|
board, err := a.GetBlockByID(sourceBoardID)
|
||||||
if err != nil || board == nil {
|
if err != nil || board == nil {
|
||||||
return fmt.Errorf("cannot fetch board %s for CopyCardFiles: %w", sourceBoardID, err)
|
return fmt.Errorf("cannot fetch board %s for CopyCardFiles: %w", sourceBoardID, err)
|
||||||
}
|
}
|
||||||
|
@ -153,8 +208,8 @@ func (a *App) CopyCardFiles(sourceBoardID string, destWorkspaceID string, blocks
|
||||||
ext := filepath.Ext(fileName.(string))
|
ext := filepath.Ext(fileName.(string))
|
||||||
destFilename := utils.NewID(utils.IDTypeNone) + ext
|
destFilename := utils.NewID(utils.IDTypeNone) + ext
|
||||||
|
|
||||||
sourceFilePath := filepath.Join(board.WorkspaceID, sourceBoardID, fileName.(string))
|
sourceFilePath := filepath.Join(sourceBoardID, fileName.(string))
|
||||||
destinationFilePath := filepath.Join(destWorkspaceID, block.RootID, destFilename)
|
destinationFilePath := filepath.Join(block.BoardID, destFilename)
|
||||||
|
|
||||||
a.logger.Debug(
|
a.logger.Debug(
|
||||||
"Copying card file",
|
"Copying card file",
|
||||||
|
@ -179,24 +234,26 @@ func (a *App) CopyCardFiles(sourceBoardID string, destWorkspaceID string, blocks
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetSubTree(c store.Container, blockID string, levels int) ([]model.Block, error) {
|
func (a *App) GetSubTree(boardID, blockID string, levels int, opts model.QuerySubtreeOptions) ([]model.Block, error) {
|
||||||
// Only 2 or 3 levels are supported for now
|
// Only 2 or 3 levels are supported for now
|
||||||
if levels >= 3 {
|
if levels >= 3 {
|
||||||
return a.store.GetSubTree3(c, blockID, model.QuerySubtreeOptions{})
|
return a.store.GetSubTree3(boardID, blockID, opts)
|
||||||
}
|
}
|
||||||
return a.store.GetSubTree2(c, blockID, model.QuerySubtreeOptions{})
|
|
||||||
|
return a.store.GetSubTree2(boardID, blockID, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetAllBlocks(c store.Container) ([]model.Block, error) {
|
func (a *App) GetBlockByID(blockID string) (*model.Block, error) {
|
||||||
return a.store.GetAllBlocks(c)
|
return a.store.GetBlock(blockID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetBlockByID(c store.Container, blockID string) (*model.Block, error) {
|
func (a *App) DeleteBlock(blockID string, modifiedBy string) error {
|
||||||
return a.store.GetBlock(c, blockID)
|
block, err := a.store.GetBlock(blockID)
|
||||||
}
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) DeleteBlock(c store.Container, blockID string, modifiedBy string) error {
|
board, err := a.store.GetBoard(block.BoardID)
|
||||||
block, err := a.store.GetBlock(c, blockID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -206,7 +263,7 @@ func (a *App) DeleteBlock(c store.Container, blockID string, modifiedBy string)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.store.DeleteBlock(c, blockID, modifiedBy)
|
err = a.store.DeleteBlock(blockID, modifiedBy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -214,7 +271,7 @@ func (a *App) DeleteBlock(c store.Container, blockID string, modifiedBy string)
|
||||||
if block.Type == model.TypeImage {
|
if block.Type == model.TypeImage {
|
||||||
fileName, fileIDExists := block.Fields["fileId"]
|
fileName, fileIDExists := block.Fields["fileId"]
|
||||||
if fileName, fileIDIsString := fileName.(string); fileIDExists && fileIDIsString {
|
if fileName, fileIDIsString := fileName.(string); fileIDExists && fileIDIsString {
|
||||||
filePath := filepath.Join(block.WorkspaceID, block.RootID, fileName)
|
filePath := filepath.Join(block.BoardID, fileName)
|
||||||
err = a.filesBackend.RemoveFile(filePath)
|
err = a.filesBackend.RemoveFile(filePath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -225,31 +282,32 @@ func (a *App) DeleteBlock(c store.Container, blockID string, modifiedBy string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a.wsAdapter.BroadcastBlockDelete(c.WorkspaceID, blockID, block.ParentID)
|
a.blockChangeNotifier.Enqueue(func() error {
|
||||||
a.metrics.IncrementBlocksDeleted(1)
|
a.wsAdapter.BroadcastBlockDelete(board.TeamID, blockID, block.BoardID)
|
||||||
go func() {
|
a.metrics.IncrementBlocksDeleted(1)
|
||||||
a.notifyBlockChanged(notify.Delete, c, block, block, modifiedBy)
|
a.notifyBlockChanged(notify.Delete, block, block, modifiedBy)
|
||||||
}()
|
return nil
|
||||||
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) UndeleteBlock(c store.Container, blockID string, modifiedBy string) error {
|
func (a *App) UndeleteBlock(blockID string, modifiedBy string) error {
|
||||||
blocks, err := a.store.GetBlockHistory(c, blockID, model.QueryBlockHistoryOptions{Limit: 1, Descending: true})
|
blocks, err := a.store.GetBlockHistory(blockID, model.QueryBlockHistoryOptions{Limit: 1, Descending: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(blocks) == 0 {
|
if len(blocks) == 0 {
|
||||||
// deleting non-existing block not considered an error
|
// undeleting non-existing block not considered an error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.store.UndeleteBlock(c, blockID, modifiedBy)
|
err = a.store.UndeleteBlock(blockID, modifiedBy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
block, err := a.store.GetBlock(c, blockID)
|
block, err := a.store.GetBlock(blockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -259,12 +317,19 @@ func (a *App) UndeleteBlock(c store.Container, blockID string, modifiedBy string
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
a.wsAdapter.BroadcastBlockChange(c.WorkspaceID, *block)
|
board, err := a.store.GetBoard(block.BoardID)
|
||||||
a.metrics.IncrementBlocksInserted(1)
|
if err != nil {
|
||||||
go func() {
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.blockChangeNotifier.Enqueue(func() error {
|
||||||
|
a.wsAdapter.BroadcastBlockChange(board.TeamID, *block)
|
||||||
|
a.metrics.IncrementBlocksInserted(1)
|
||||||
a.webhook.NotifyUpdate(*block)
|
a.webhook.NotifyUpdate(*block)
|
||||||
a.notifyBlockChanged(notify.Add, c, block, nil, modifiedBy)
|
a.notifyBlockChanged(notify.Add, block, nil, modifiedBy)
|
||||||
}()
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,13 +337,17 @@ func (a *App) GetBlockCountsByType() (map[string]int64, error) {
|
||||||
return a.store.GetBlockCountsByType()
|
return a.store.GetBlockCountsByType()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) notifyBlockChanged(action notify.Action, c store.Container, block *model.Block, oldBlock *model.Block, modifiedByID string) {
|
func (a *App) GetBlocksForBoard(boardID string) ([]model.Block, error) {
|
||||||
|
return a.store.GetBlocksForBoard(boardID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) notifyBlockChanged(action notify.Action, block *model.Block, oldBlock *model.Block, modifiedByID string) {
|
||||||
if a.notifications == nil {
|
if a.notifications == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// find card and board for the changed block.
|
// find card and board for the changed block.
|
||||||
board, card, err := a.store.GetBoardAndCard(c, block)
|
board, card, err := a.getBoardAndCard(block)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Error("Error notifying for block change; cannot determine board or card", mlog.Err(err))
|
a.logger.Error("Error notifying for block change; cannot determine board or card", mlog.Err(err))
|
||||||
return
|
return
|
||||||
|
@ -286,7 +355,7 @@ func (a *App) notifyBlockChanged(action notify.Action, c store.Container, block
|
||||||
|
|
||||||
evt := notify.BlockChangeEvent{
|
evt := notify.BlockChangeEvent{
|
||||||
Action: action,
|
Action: action,
|
||||||
Workspace: c.WorkspaceID,
|
TeamID: board.TeamID,
|
||||||
Board: board,
|
Board: board,
|
||||||
Card: card,
|
Card: card,
|
||||||
BlockChanged: block,
|
BlockChanged: block,
|
||||||
|
@ -295,3 +364,35 @@ func (a *App) notifyBlockChanged(action notify.Action, c store.Container, block
|
||||||
}
|
}
|
||||||
a.notifications.BlockChanged(evt)
|
a.notifications.BlockChanged(evt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxSearchDepth = 50
|
||||||
|
)
|
||||||
|
|
||||||
|
// getBoardAndCard returns the first parent of type `card` its board for the specified block.
|
||||||
|
// `board` and/or `card` may return nil without error if the block does not belong to a board or card.
|
||||||
|
func (a *App) getBoardAndCard(block *model.Block) (board *model.Board, card *model.Block, err error) {
|
||||||
|
board, err = a.store.GetBoard(block.BoardID)
|
||||||
|
if err != nil {
|
||||||
|
return board, card, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var count int // don't let invalid blocks hierarchy cause infinite loop.
|
||||||
|
iter := block
|
||||||
|
for {
|
||||||
|
count++
|
||||||
|
if card == nil && iter.Type == model.TypeCard {
|
||||||
|
card = iter
|
||||||
|
}
|
||||||
|
|
||||||
|
if iter.ParentID == "" || (board != nil && card != nil) || count > maxSearchDepth {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
iter, err = a.store.GetBlock(iter.ParentID)
|
||||||
|
if err != nil || iter == nil {
|
||||||
|
return board, card, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return board, card, nil
|
||||||
|
}
|
||||||
|
|
|
@ -3,10 +3,9 @@ package app
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
st "github.com/mattermost/focalboard/server/services/store"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,47 +17,28 @@ func (be blockError) Error() string {
|
||||||
return be.msg
|
return be.msg
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetParentID(t *testing.T) {
|
|
||||||
th, tearDown := SetupTestHelper(t)
|
|
||||||
defer tearDown()
|
|
||||||
|
|
||||||
container := st.Container{
|
|
||||||
WorkspaceID: "0",
|
|
||||||
}
|
|
||||||
t.Run("success query", func(t *testing.T) {
|
|
||||||
th.Store.EXPECT().GetParentID(gomock.Eq(container), gomock.Eq("test-id")).Return("test-parent-id", nil)
|
|
||||||
result, err := th.App.GetParentID(container, "test-id")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "test-parent-id", result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("fail query", func(t *testing.T) {
|
|
||||||
th.Store.EXPECT().GetParentID(gomock.Eq(container), gomock.Eq("test-id")).Return("", blockError{"block-not-found"})
|
|
||||||
_, err := th.App.GetParentID(container, "test-id")
|
|
||||||
require.Error(t, err)
|
|
||||||
require.ErrorIs(t, err, blockError{"block-not-found"})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInsertBlock(t *testing.T) {
|
func TestInsertBlock(t *testing.T) {
|
||||||
th, tearDown := SetupTestHelper(t)
|
th, tearDown := SetupTestHelper(t)
|
||||||
defer tearDown()
|
defer tearDown()
|
||||||
|
|
||||||
container := st.Container{
|
|
||||||
WorkspaceID: "0",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("success scenerio", func(t *testing.T) {
|
t.Run("success scenerio", func(t *testing.T) {
|
||||||
block := model.Block{}
|
boardID := testBoardID
|
||||||
th.Store.EXPECT().InsertBlock(gomock.Eq(container), gomock.Eq(&block), gomock.Eq("user-id-1")).Return(nil)
|
block := model.Block{BoardID: boardID}
|
||||||
err := th.App.InsertBlock(container, block, "user-id-1")
|
board := &model.Board{ID: boardID}
|
||||||
|
th.Store.EXPECT().GetBoard(boardID).Return(board, nil)
|
||||||
|
th.Store.EXPECT().InsertBlock(&block, "user-id-1").Return(nil)
|
||||||
|
th.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil)
|
||||||
|
err := th.App.InsertBlock(block, "user-id-1")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("error scenerio", func(t *testing.T) {
|
t.Run("error scenerio", func(t *testing.T) {
|
||||||
block := model.Block{}
|
boardID := testBoardID
|
||||||
th.Store.EXPECT().InsertBlock(gomock.Eq(container), gomock.Eq(&block), gomock.Eq("user-id-1")).Return(blockError{"error"})
|
block := model.Block{BoardID: boardID}
|
||||||
err := th.App.InsertBlock(container, block, "user-id-1")
|
board := &model.Board{ID: boardID}
|
||||||
|
th.Store.EXPECT().GetBoard(boardID).Return(board, nil)
|
||||||
|
th.Store.EXPECT().InsertBlock(&block, "user-id-1").Return(blockError{"error"})
|
||||||
|
err := th.App.InsertBlock(block, "user-id-1")
|
||||||
require.Error(t, err, "error")
|
require.Error(t, err, "error")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -67,20 +47,17 @@ func TestPatchBlocks(t *testing.T) {
|
||||||
th, tearDown := SetupTestHelper(t)
|
th, tearDown := SetupTestHelper(t)
|
||||||
defer tearDown()
|
defer tearDown()
|
||||||
|
|
||||||
container := st.Container{
|
|
||||||
WorkspaceID: "0",
|
|
||||||
}
|
|
||||||
t.Run("patchBlocks success scenerio", func(t *testing.T) {
|
t.Run("patchBlocks success scenerio", func(t *testing.T) {
|
||||||
blockPatches := model.BlockPatchBatch{}
|
blockPatches := model.BlockPatchBatch{}
|
||||||
th.Store.EXPECT().PatchBlocks(gomock.Eq(container), gomock.Eq(&blockPatches), gomock.Eq("user-id-1")).Return(nil)
|
th.Store.EXPECT().PatchBlocks(gomock.Eq(&blockPatches), gomock.Eq("user-id-1")).Return(nil)
|
||||||
err := th.App.PatchBlocks(container, &blockPatches, "user-id-1")
|
err := th.App.PatchBlocks("team-id", &blockPatches, "user-id-1")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("patchBlocks error scenerio", func(t *testing.T) {
|
t.Run("patchBlocks error scenerio", func(t *testing.T) {
|
||||||
blockPatches := model.BlockPatchBatch{}
|
blockPatches := model.BlockPatchBatch{}
|
||||||
th.Store.EXPECT().PatchBlocks(gomock.Eq(container), gomock.Eq(&blockPatches), gomock.Eq("user-id-1")).Return(blockError{"error"})
|
th.Store.EXPECT().PatchBlocks(gomock.Eq(&blockPatches), gomock.Eq("user-id-1")).Return(blockError{"error"})
|
||||||
err := th.App.PatchBlocks(container, &blockPatches, "user-id-1")
|
err := th.App.PatchBlocks("team-id", &blockPatches, "user-id-1")
|
||||||
require.Error(t, err, "error")
|
require.Error(t, err, "error")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -89,27 +66,32 @@ func TestDeleteBlock(t *testing.T) {
|
||||||
th, tearDown := SetupTestHelper(t)
|
th, tearDown := SetupTestHelper(t)
|
||||||
defer tearDown()
|
defer tearDown()
|
||||||
|
|
||||||
container := st.Container{
|
|
||||||
WorkspaceID: "0",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("success scenerio", func(t *testing.T) {
|
t.Run("success scenerio", func(t *testing.T) {
|
||||||
|
boardID := testBoardID
|
||||||
|
board := &model.Board{ID: boardID}
|
||||||
block := model.Block{
|
block := model.Block{
|
||||||
ID: "block-id",
|
ID: "block-id",
|
||||||
|
BoardID: board.ID,
|
||||||
}
|
}
|
||||||
th.Store.EXPECT().GetBlock(gomock.Eq(container), gomock.Eq("block-id")).Return(&block, nil)
|
th.Store.EXPECT().GetBlock(gomock.Eq("block-id")).Return(&block, nil)
|
||||||
th.Store.EXPECT().DeleteBlock(gomock.Eq(container), gomock.Eq("block-id"), gomock.Eq("user-id-1")).Return(nil)
|
th.Store.EXPECT().DeleteBlock(gomock.Eq("block-id"), gomock.Eq("user-id-1")).Return(nil)
|
||||||
err := th.App.DeleteBlock(container, "block-id", "user-id-1")
|
th.Store.EXPECT().GetBoard(gomock.Eq(testBoardID)).Return(board, nil)
|
||||||
|
th.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil)
|
||||||
|
err := th.App.DeleteBlock("block-id", "user-id-1")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("error scenerio", func(t *testing.T) {
|
t.Run("error scenerio", func(t *testing.T) {
|
||||||
|
boardID := testBoardID
|
||||||
|
board := &model.Board{ID: boardID}
|
||||||
block := model.Block{
|
block := model.Block{
|
||||||
ID: "block-id",
|
ID: "block-id",
|
||||||
|
BoardID: board.ID,
|
||||||
}
|
}
|
||||||
th.Store.EXPECT().GetBlock(gomock.Eq(container), gomock.Eq("block-id")).Return(&block, nil)
|
th.Store.EXPECT().GetBlock(gomock.Eq("block-id")).Return(&block, nil)
|
||||||
th.Store.EXPECT().DeleteBlock(gomock.Eq(container), gomock.Eq("block-id"), gomock.Eq("user-id-1")).Return(blockError{"error"})
|
th.Store.EXPECT().DeleteBlock(gomock.Eq("block-id"), gomock.Eq("user-id-1")).Return(blockError{"error"})
|
||||||
err := th.App.DeleteBlock(container, "block-id", "user-id-1")
|
th.Store.EXPECT().GetBoard(gomock.Eq(testBoardID)).Return(board, nil)
|
||||||
|
err := th.App.DeleteBlock("block-id", "user-id-1")
|
||||||
require.Error(t, err, "error")
|
require.Error(t, err, "error")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -118,22 +100,22 @@ func TestUndeleteBlock(t *testing.T) {
|
||||||
th, tearDown := SetupTestHelper(t)
|
th, tearDown := SetupTestHelper(t)
|
||||||
defer tearDown()
|
defer tearDown()
|
||||||
|
|
||||||
container := st.Container{
|
|
||||||
WorkspaceID: "0",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("success scenerio", func(t *testing.T) {
|
t.Run("success scenerio", func(t *testing.T) {
|
||||||
|
boardID := testBoardID
|
||||||
|
board := &model.Board{ID: boardID}
|
||||||
block := model.Block{
|
block := model.Block{
|
||||||
ID: "block-id",
|
ID: "block-id",
|
||||||
|
BoardID: board.ID,
|
||||||
}
|
}
|
||||||
th.Store.EXPECT().GetBlockHistory(
|
th.Store.EXPECT().GetBlockHistory(
|
||||||
gomock.Eq(container),
|
|
||||||
gomock.Eq("block-id"),
|
gomock.Eq("block-id"),
|
||||||
gomock.Eq(model.QueryBlockHistoryOptions{Limit: 1, Descending: true}),
|
gomock.Eq(model.QueryBlockHistoryOptions{Limit: 1, Descending: true}),
|
||||||
).Return([]model.Block{block}, nil)
|
).Return([]model.Block{block}, nil)
|
||||||
th.Store.EXPECT().UndeleteBlock(gomock.Eq(container), gomock.Eq("block-id"), gomock.Eq("user-id-1")).Return(nil)
|
th.Store.EXPECT().UndeleteBlock(gomock.Eq("block-id"), gomock.Eq("user-id-1")).Return(nil)
|
||||||
th.Store.EXPECT().GetBlock(gomock.Eq(container), gomock.Eq("block-id")).Return(&block, nil)
|
th.Store.EXPECT().GetBlock(gomock.Eq("block-id")).Return(&block, nil)
|
||||||
err := th.App.UndeleteBlock(container, "block-id", "user-id-1")
|
th.Store.EXPECT().GetBoard(boardID).Return(board, nil)
|
||||||
|
th.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil)
|
||||||
|
err := th.App.UndeleteBlock("block-id", "user-id-1")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -142,13 +124,12 @@ func TestUndeleteBlock(t *testing.T) {
|
||||||
ID: "block-id",
|
ID: "block-id",
|
||||||
}
|
}
|
||||||
th.Store.EXPECT().GetBlockHistory(
|
th.Store.EXPECT().GetBlockHistory(
|
||||||
gomock.Eq(container),
|
|
||||||
gomock.Eq("block-id"),
|
gomock.Eq("block-id"),
|
||||||
gomock.Eq(model.QueryBlockHistoryOptions{Limit: 1, Descending: true}),
|
gomock.Eq(model.QueryBlockHistoryOptions{Limit: 1, Descending: true}),
|
||||||
).Return([]model.Block{block}, nil)
|
).Return([]model.Block{block}, nil)
|
||||||
th.Store.EXPECT().UndeleteBlock(gomock.Eq(container), gomock.Eq("block-id"), gomock.Eq("user-id-1")).Return(blockError{"error"})
|
th.Store.EXPECT().UndeleteBlock(gomock.Eq("block-id"), gomock.Eq("user-id-1")).Return(blockError{"error"})
|
||||||
th.Store.EXPECT().GetBlock(gomock.Eq(container), gomock.Eq("block-id")).Return(&block, nil)
|
th.Store.EXPECT().GetBlock(gomock.Eq("block-id")).Return(&block, nil)
|
||||||
err := th.App.UndeleteBlock(container, "block-id", "user-id-1")
|
err := th.App.UndeleteBlock("block-id", "user-id-1")
|
||||||
require.Error(t, err, "error")
|
require.Error(t, err, "error")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
254
server/app/boards.go
Normal file
254
server/app/boards.go
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/mattermost/focalboard/server/model"
|
||||||
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBoardMemberIsLastAdmin = errors.New("cannot leave a board with no admins")
|
||||||
|
ErrNewBoardCannotHaveID = errors.New("new board cannot have an ID")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *App) GetBoard(boardID string) (*model.Board, error) {
|
||||||
|
board, err := a.store.GetBoard(boardID)
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return board, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) DuplicateBoard(boardID, userID, toTeam string, asTemplate bool) (*model.BoardsAndBlocks, []*model.BoardMember, error) {
|
||||||
|
bab, members, err := a.store.DuplicateBoard(boardID, userID, toTeam, asTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
teamID := ""
|
||||||
|
for _, board := range bab.Boards {
|
||||||
|
teamID = board.TeamID
|
||||||
|
a.wsAdapter.BroadcastBoardChange(teamID, board)
|
||||||
|
}
|
||||||
|
for _, block := range bab.Blocks {
|
||||||
|
a.wsAdapter.BroadcastBlockChange(teamID, block)
|
||||||
|
}
|
||||||
|
for _, member := range members {
|
||||||
|
a.wsAdapter.BroadcastMemberChange(teamID, member.BoardID, member)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return bab, members, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetBoardsForUserAndTeam(userID, teamID string) ([]*model.Board, error) {
|
||||||
|
return a.store.GetBoardsForUserAndTeam(userID, teamID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetTemplateBoards(teamID string) ([]*model.Board, error) {
|
||||||
|
return a.store.GetTemplateBoards(teamID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) CreateBoard(board *model.Board, userID string, addMember bool) (*model.Board, error) {
|
||||||
|
if board.ID != "" {
|
||||||
|
return nil, ErrNewBoardCannotHaveID
|
||||||
|
}
|
||||||
|
board.ID = utils.NewID(utils.IDTypeBoard)
|
||||||
|
|
||||||
|
var newBoard *model.Board
|
||||||
|
var member *model.BoardMember
|
||||||
|
var err error
|
||||||
|
if addMember {
|
||||||
|
newBoard, member, err = a.store.InsertBoardWithAdmin(board, userID)
|
||||||
|
} else {
|
||||||
|
newBoard, err = a.store.InsertBoard(board, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
a.wsAdapter.BroadcastBoardChange(newBoard.TeamID, newBoard)
|
||||||
|
|
||||||
|
if addMember {
|
||||||
|
a.wsAdapter.BroadcastMemberChange(newBoard.TeamID, newBoard.ID, member)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return newBoard, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) PatchBoard(patch *model.BoardPatch, boardID, userID string) (*model.Board, error) {
|
||||||
|
updatedBoard, err := a.store.PatchBoard(boardID, patch, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
a.wsAdapter.BroadcastBoardChange(updatedBoard.TeamID, updatedBoard)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return updatedBoard, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) DeleteBoard(boardID, userID string) error {
|
||||||
|
board, err := a.store.GetBoard(boardID)
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.store.DeleteBoard(boardID, userID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
a.wsAdapter.BroadcastBoardDelete(board.TeamID, boardID)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetMembersForBoard(boardID string) ([]*model.BoardMember, error) {
|
||||||
|
return a.store.GetMembersForBoard(boardID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetMembersForUser(userID string) ([]*model.BoardMember, error) {
|
||||||
|
return a.store.GetMembersForUser(userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) AddMemberToBoard(member *model.BoardMember) (*model.BoardMember, error) {
|
||||||
|
board, err := a.store.GetBoard(member.BoardID)
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
existingMembership, err := a.store.GetMemberForBoard(member.BoardID, member.UserID)
|
||||||
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if existingMembership != nil {
|
||||||
|
return existingMembership, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newMember, err := a.store.SaveMember(member)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
a.wsAdapter.BroadcastMemberChange(board.TeamID, member.BoardID, member)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return newMember, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) UpdateBoardMember(member *model.BoardMember) (*model.BoardMember, error) {
|
||||||
|
board, bErr := a.store.GetBoard(member.BoardID)
|
||||||
|
if errors.Is(bErr, sql.ErrNoRows) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if bErr != nil {
|
||||||
|
return nil, bErr
|
||||||
|
}
|
||||||
|
|
||||||
|
oldMember, err := a.store.GetMemberForBoard(member.BoardID, member.UserID)
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we're updating an admin, we need to check that there is at
|
||||||
|
// least still another admin on the board
|
||||||
|
if oldMember.SchemeAdmin && !member.SchemeAdmin {
|
||||||
|
isLastAdmin, err2 := a.isLastAdmin(member.UserID, member.BoardID)
|
||||||
|
if err2 != nil {
|
||||||
|
return nil, err2
|
||||||
|
}
|
||||||
|
if isLastAdmin {
|
||||||
|
return nil, ErrBoardMemberIsLastAdmin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newMember, err := a.store.SaveMember(member)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
a.wsAdapter.BroadcastMemberChange(board.TeamID, member.BoardID, member)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return newMember, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) isLastAdmin(userID, boardID string) (bool, error) {
|
||||||
|
members, err := a.store.GetMembersForBoard(boardID)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range members {
|
||||||
|
if m.SchemeAdmin && m.UserID != userID {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) DeleteBoardMember(boardID, userID string) error {
|
||||||
|
board, bErr := a.store.GetBoard(boardID)
|
||||||
|
if errors.Is(bErr, sql.ErrNoRows) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if bErr != nil {
|
||||||
|
return bErr
|
||||||
|
}
|
||||||
|
|
||||||
|
oldMember, err := a.store.GetMemberForBoard(boardID, userID)
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we're removing an admin, we need to check that there is at
|
||||||
|
// least still another admin on the board
|
||||||
|
if oldMember.SchemeAdmin {
|
||||||
|
isLastAdmin, err := a.isLastAdmin(userID, boardID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if isLastAdmin {
|
||||||
|
return ErrBoardMemberIsLastAdmin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.store.DeleteMember(boardID, userID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
a.wsAdapter.BroadcastMemberDelete(board.TeamID, boardID, userID)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) SearchBoardsForUserAndTeam(term, userID, teamID string) ([]*model.Board, error) {
|
||||||
|
return a.store.SearchBoardsForUserAndTeam(term, userID, teamID)
|
||||||
|
}
|
126
server/app/boards_and_blocks.go
Normal file
126
server/app/boards_and_blocks.go
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mattermost/focalboard/server/model"
|
||||||
|
"github.com/mattermost/focalboard/server/services/notify"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *App) CreateBoardsAndBlocks(bab *model.BoardsAndBlocks, userID string, addMember bool) (*model.BoardsAndBlocks, error) {
|
||||||
|
var newBab *model.BoardsAndBlocks
|
||||||
|
var members []*model.BoardMember
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if addMember {
|
||||||
|
newBab, members, err = a.store.CreateBoardsAndBlocksWithAdmin(bab, userID)
|
||||||
|
} else {
|
||||||
|
newBab, err = a.store.CreateBoardsAndBlocks(bab, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// all new boards should belong to the same team
|
||||||
|
teamID := newBab.Boards[0].TeamID
|
||||||
|
|
||||||
|
// This can be synchronous because this action is not common
|
||||||
|
for _, board := range newBab.Boards {
|
||||||
|
a.wsAdapter.BroadcastBoardChange(teamID, board)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, block := range newBab.Blocks {
|
||||||
|
b := block
|
||||||
|
a.wsAdapter.BroadcastBlockChange(teamID, b)
|
||||||
|
a.metrics.IncrementBlocksInserted(1)
|
||||||
|
a.webhook.NotifyUpdate(b)
|
||||||
|
a.notifyBlockChanged(notify.Add, &b, nil, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if addMember {
|
||||||
|
for _, member := range members {
|
||||||
|
a.wsAdapter.BroadcastMemberChange(teamID, member.BoardID, member)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newBab, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) PatchBoardsAndBlocks(pbab *model.PatchBoardsAndBlocks, userID string) (*model.BoardsAndBlocks, error) {
|
||||||
|
oldBlocksMap := map[string]*model.Block{}
|
||||||
|
for _, blockID := range pbab.BlockIDs {
|
||||||
|
block, err := a.store.GetBlock(blockID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
oldBlocksMap[blockID] = block
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, err := a.store.PatchBoardsAndBlocks(pbab, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.blockChangeNotifier.Enqueue(func() error {
|
||||||
|
teamID := bab.Boards[0].TeamID
|
||||||
|
|
||||||
|
for _, block := range bab.Blocks {
|
||||||
|
oldBlock, ok := oldBlocksMap[block.ID]
|
||||||
|
if !ok {
|
||||||
|
a.logger.Error("Error notifying for block change on patch boards and blocks; cannot get old block", mlog.String("blockID", block.ID))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
b := block
|
||||||
|
a.metrics.IncrementBlocksPatched(1)
|
||||||
|
a.wsAdapter.BroadcastBlockChange(teamID, b)
|
||||||
|
a.webhook.NotifyUpdate(b)
|
||||||
|
a.notifyBlockChanged(notify.Update, &b, oldBlock, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, board := range bab.Boards {
|
||||||
|
a.wsAdapter.BroadcastBoardChange(board.TeamID, board)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return bab, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) DeleteBoardsAndBlocks(dbab *model.DeleteBoardsAndBlocks, userID string) error {
|
||||||
|
firstBoard, err := a.store.GetBoard(dbab.Boards[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need the block entity to notify of the block changes, so we
|
||||||
|
// fetch and store the blocks first
|
||||||
|
blocks := []*model.Block{}
|
||||||
|
for _, blockID := range dbab.Blocks {
|
||||||
|
block, err := a.store.GetBlock(blockID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
blocks = append(blocks, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.store.DeleteBoardsAndBlocks(dbab, userID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.blockChangeNotifier.Enqueue(func() error {
|
||||||
|
for _, block := range blocks {
|
||||||
|
a.wsAdapter.BroadcastBlockDelete(firstBoard.TeamID, block.ID, block.BoardID)
|
||||||
|
a.metrics.IncrementBlocksDeleted(1)
|
||||||
|
a.notifyBlockChanged(notify.Update, block, block, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, boardID := range dbab.Boards {
|
||||||
|
a.wsAdapter.BroadcastBoardDelete(firstBoard.TeamID, boardID)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
103
server/app/category.go
Normal file
103
server/app/category.go
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/mattermost/focalboard/server/model"
|
||||||
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrorCategoryPermissionDenied = errors.New("category doesn't belong to user")
|
||||||
|
ErrorCategoryDeleted = errors.New("category is deleted")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *App) CreateCategory(category *model.Category) (*model.Category, error) {
|
||||||
|
category.Hydrate()
|
||||||
|
if err := category.IsValid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.store.CreateCategory(*category); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
createdCategory, err := a.store.GetCategory(category.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
a.wsAdapter.BroadcastCategoryChange(*createdCategory)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return createdCategory, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) UpdateCategory(category *model.Category) (*model.Category, error) {
|
||||||
|
// verify if category belongs to the user
|
||||||
|
existingCategory, err := a.store.GetCategory(category.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if existingCategory.DeleteAt != 0 {
|
||||||
|
return nil, ErrorCategoryDeleted
|
||||||
|
}
|
||||||
|
|
||||||
|
if existingCategory.UserID != category.UserID {
|
||||||
|
return nil, ErrorCategoryPermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
category.UpdateAt = utils.GetMillis()
|
||||||
|
if err = category.IsValid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = a.store.UpdateCategory(*category); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedCategory, err := a.store.GetCategory(category.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
a.wsAdapter.BroadcastCategoryChange(*updatedCategory)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return updatedCategory, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) DeleteCategory(categoryID, userID, teamID string) (*model.Category, error) {
|
||||||
|
existingCategory, err := a.store.GetCategory(categoryID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// category is already deleted. This avoids
|
||||||
|
// overriding the original deleted at timestamp
|
||||||
|
if existingCategory.DeleteAt != 0 {
|
||||||
|
return existingCategory, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify if category belongs to the user
|
||||||
|
if existingCategory.UserID != userID {
|
||||||
|
return nil, ErrorCategoryPermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = a.store.DeleteCategory(categoryID, userID, teamID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
deletedCategory, err := a.store.GetCategory(categoryID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
a.wsAdapter.BroadcastCategoryChange(*deletedCategory)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return deletedCategory, nil
|
||||||
|
}
|
26
server/app/category_blocks.go
Normal file
26
server/app/category_blocks.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import "github.com/mattermost/focalboard/server/model"
|
||||||
|
|
||||||
|
func (a *App) GetUserCategoryBlocks(userID, teamID string) ([]model.CategoryBlocks, error) {
|
||||||
|
return a.store.GetUserCategoryBlocks(userID, teamID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) AddUpdateUserCategoryBlock(teamID, userID, categoryID, blockID string) error {
|
||||||
|
err := a.store.AddUpdateCategoryBlock(userID, categoryID, blockID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
a.wsAdapter.BroadcastCategoryBlockChange(
|
||||||
|
teamID,
|
||||||
|
userID,
|
||||||
|
model.BlockCategoryWebsocketData{
|
||||||
|
BlockID: blockID,
|
||||||
|
CategoryID: categoryID,
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
|
||||||
"github.com/wiggin77/merror"
|
"github.com/wiggin77/merror"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
|
@ -18,10 +17,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) ExportArchive(w io.Writer, opt model.ExportArchiveOptions) (errs error) {
|
func (a *App) ExportArchive(w io.Writer, opt model.ExportArchiveOptions) (errs error) {
|
||||||
container := store.Container{
|
boards, err := a.getBoardsForArchive(opt.BoardIDs)
|
||||||
WorkspaceID: opt.WorkspaceID,
|
|
||||||
}
|
|
||||||
boards, err := a.getBoardsForArchive(container, opt.BoardIDs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -71,7 +67,7 @@ func (a *App) writeArchiveVersion(zw *zip.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeArchiveBoard writes a single board to the archive in a zip directory.
|
// writeArchiveBoard writes a single board to the archive in a zip directory.
|
||||||
func (a *App) writeArchiveBoard(zw *zip.Writer, board model.Block, opt model.ExportArchiveOptions) error {
|
func (a *App) writeArchiveBoard(zw *zip.Writer, board model.Board, opt model.ExportArchiveOptions) error {
|
||||||
// create a directory per board
|
// create a directory per board
|
||||||
w, err := zw.Create(board.ID + "/board.jsonl")
|
w, err := zw.Create(board.ID + "/board.jsonl")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -79,18 +75,14 @@ func (a *App) writeArchiveBoard(zw *zip.Writer, board model.Block, opt model.Exp
|
||||||
}
|
}
|
||||||
|
|
||||||
// write the board block first
|
// write the board block first
|
||||||
if err = a.writeArchiveBlockLine(w, board); err != nil {
|
if err = a.writeArchiveBoardLine(w, board); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var files []string
|
var files []string
|
||||||
container := store.Container{
|
|
||||||
WorkspaceID: opt.WorkspaceID,
|
|
||||||
}
|
|
||||||
|
|
||||||
// write the board's blocks
|
// write the board's blocks
|
||||||
// TODO: paginate this
|
// TODO: paginate this
|
||||||
blocks, err := a.GetBlocksWithRootID(container, board.ID)
|
blocks, err := a.GetBlocksWithBoardID(board.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -143,6 +135,32 @@ func (a *App) writeArchiveBlockLine(w io.Writer, block model.Block) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeArchiveBlockLine writes a single block to the archive.
|
||||||
|
func (a *App) writeArchiveBoardLine(w io.Writer, board model.Board) error {
|
||||||
|
b, err := json.Marshal(&board)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
line := model.ArchiveLine{
|
||||||
|
Type: "board",
|
||||||
|
Data: b,
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err = json.Marshal(&line)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// jsonl files need a newline
|
||||||
|
_, err = w.Write(newline)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// writeArchiveFile writes a single file to the archive.
|
// writeArchiveFile writes a single file to the archive.
|
||||||
func (a *App) writeArchiveFile(zw *zip.Writer, filename string, boardID string, opt model.ExportArchiveOptions) error {
|
func (a *App) writeArchiveFile(zw *zip.Writer, filename string, boardID string, opt model.ExportArchiveOptions) error {
|
||||||
dest, err := zw.Create(boardID + "/" + filename)
|
dest, err := zw.Create(boardID + "/" + filename)
|
||||||
|
@ -150,12 +168,12 @@ func (a *App) writeArchiveFile(zw *zip.Writer, filename string, boardID string,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
src, err := a.GetFileReader(opt.WorkspaceID, boardID, filename)
|
src, err := a.GetFileReader(opt.TeamID, boardID, filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// just log this; image file is missing but we'll still export an equivalent board
|
// just log this; image file is missing but we'll still export an equivalent board
|
||||||
a.logger.Error("image file missing for export",
|
a.logger.Error("image file missing for export",
|
||||||
mlog.String("filename", filename),
|
mlog.String("filename", filename),
|
||||||
mlog.String("workspace_id", opt.WorkspaceID),
|
mlog.String("team_id", opt.TeamID),
|
||||||
mlog.String("board_id", boardID),
|
mlog.String("board_id", boardID),
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
|
@ -168,27 +186,25 @@ func (a *App) writeArchiveFile(zw *zip.Writer, filename string, boardID string,
|
||||||
|
|
||||||
// getBoardsForArchive fetches all the specified boards, or all boards in the workspace/team
|
// getBoardsForArchive fetches all the specified boards, or all boards in the workspace/team
|
||||||
// if `boardIDs` is empty.
|
// if `boardIDs` is empty.
|
||||||
func (a *App) getBoardsForArchive(container store.Container, boardIDs []string) ([]model.Block, error) {
|
func (a *App) getBoardsForArchive(boardIDs []string) ([]model.Board, error) {
|
||||||
if len(boardIDs) == 0 {
|
if len(boardIDs) == 0 {
|
||||||
boards, err := a.GetBlocks(container, "", model.TypeBoard)
|
// TODO: implement this
|
||||||
if err != nil {
|
// boards, err := a.GetAllBoards("", "board")
|
||||||
return nil, fmt.Errorf("could not fetch all boards: %w", err)
|
// if err != nil {
|
||||||
}
|
// return nil, fmt.Errorf("could not fetch all boards: %w", err)
|
||||||
return boards, nil
|
// }
|
||||||
|
// return boards, nil
|
||||||
|
return []model.Board{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
boards := make([]model.Block, 0, len(boardIDs))
|
boards := make([]model.Board, 0, len(boardIDs))
|
||||||
|
|
||||||
for _, id := range boardIDs {
|
for _, id := range boardIDs {
|
||||||
b, err := a.GetBlockByID(container, id)
|
b, err := a.GetBoard(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not fetch board %s: %w", id, err)
|
return nil, fmt.Errorf("could not fetch board %s: %w", id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.Type != model.TypeBoard {
|
|
||||||
return nil, fmt.Errorf("block %s is not a board: %w", b.ID, model.ErrInvalidBoardBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
boards = append(boards, *b)
|
boards = append(boards, *b)
|
||||||
}
|
}
|
||||||
return boards, nil
|
return boards, nil
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/filestore"
|
"github.com/mattermost/mattermost-server/v6/shared/filestore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) SaveFile(reader io.Reader, workspaceID, rootID, filename string) (string, error) {
|
func (a *App) SaveFile(reader io.Reader, teamID, rootID, filename string) (string, error) {
|
||||||
// NOTE: File extension includes the dot
|
// NOTE: File extension includes the dot
|
||||||
fileExtension := strings.ToLower(filepath.Ext(filename))
|
fileExtension := strings.ToLower(filepath.Ext(filename))
|
||||||
if fileExtension == ".jpeg" {
|
if fileExtension == ".jpeg" {
|
||||||
|
@ -21,7 +21,7 @@ func (a *App) SaveFile(reader io.Reader, workspaceID, rootID, filename string) (
|
||||||
}
|
}
|
||||||
|
|
||||||
createdFilename := fmt.Sprintf(`%s%s`, utils.NewID(utils.IDTypeNone), fileExtension)
|
createdFilename := fmt.Sprintf(`%s%s`, utils.NewID(utils.IDTypeNone), fileExtension)
|
||||||
filePath := filepath.Join(workspaceID, rootID, createdFilename)
|
filePath := filepath.Join(teamID, rootID, createdFilename)
|
||||||
|
|
||||||
_, appErr := a.filesBackend.WriteFile(reader, filePath)
|
_, appErr := a.filesBackend.WriteFile(reader, filePath)
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
|
@ -31,14 +31,14 @@ func (a *App) SaveFile(reader io.Reader, workspaceID, rootID, filename string) (
|
||||||
return createdFilename, nil
|
return createdFilename, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetFileReader(workspaceID, rootID, filename string) (filestore.ReadCloseSeeker, error) {
|
func (a *App) GetFileReader(teamID, rootID, filename string) (filestore.ReadCloseSeeker, error) {
|
||||||
filePath := filepath.Join(workspaceID, rootID, filename)
|
filePath := filepath.Join(teamID, rootID, filename)
|
||||||
exists, err := a.filesBackend.FileExists(filePath)
|
exists, err := a.filesBackend.FileExists(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// FIXUP: Check the deprecated old location
|
// FIXUP: Check the deprecated old location
|
||||||
if workspaceID == "0" && !exists {
|
if teamID == "0" && !exists {
|
||||||
oldExists, err2 := a.filesBackend.FileExists(filename)
|
oldExists, err2 := a.filesBackend.FileExists(filename)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
return nil, err2
|
return nil, err2
|
||||||
|
|
|
@ -5,16 +5,17 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/plugin/plugintest/mock"
|
"github.com/mattermost/mattermost-server/v6/plugin/plugintest/mock"
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/filestore"
|
"github.com/mattermost/mattermost-server/v6/shared/filestore"
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/filestore/mocks"
|
"github.com/mattermost/mattermost-server/v6/shared/filestore/mocks"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
testFileName = "temp-file-name"
|
testFileName = "temp-file-name"
|
||||||
testRootID = "test-root-id"
|
testBoardID = "test-board-id"
|
||||||
testFilePath = "1/test-root-id/temp-file-name"
|
testFilePath = "1/test-board-id/temp-file-name"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestError struct{}
|
type TestError struct{}
|
||||||
|
@ -45,7 +46,7 @@ func TestGetFileReader(t *testing.T) {
|
||||||
|
|
||||||
mockedFileBackend.On("Reader", testFilePath).Return(readerFunc, readerErrorFunc)
|
mockedFileBackend.On("Reader", testFilePath).Return(readerFunc, readerErrorFunc)
|
||||||
mockedFileBackend.On("FileExists", testFilePath).Return(fileExistsFunc, fileExistsErrorFunc)
|
mockedFileBackend.On("FileExists", testFilePath).Return(fileExistsFunc, fileExistsErrorFunc)
|
||||||
actual, _ := th.App.GetFileReader("1", testRootID, testFileName)
|
actual, _ := th.App.GetFileReader("1", testBoardID, testFileName)
|
||||||
assert.Equal(t, mockedReadCloseSeek, actual)
|
assert.Equal(t, mockedReadCloseSeek, actual)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -71,7 +72,7 @@ func TestGetFileReader(t *testing.T) {
|
||||||
|
|
||||||
mockedFileBackend.On("Reader", testFilePath).Return(readerFunc, readerErrorFunc)
|
mockedFileBackend.On("Reader", testFilePath).Return(readerFunc, readerErrorFunc)
|
||||||
mockedFileBackend.On("FileExists", testFilePath).Return(fileExistsFunc, fileExistsErrorFunc)
|
mockedFileBackend.On("FileExists", testFilePath).Return(fileExistsFunc, fileExistsErrorFunc)
|
||||||
actual, err := th.App.GetFileReader("1", testRootID, testFileName)
|
actual, err := th.App.GetFileReader("1", testBoardID, testFileName)
|
||||||
assert.Error(t, err, mockedError)
|
assert.Error(t, err, mockedError)
|
||||||
assert.Nil(t, actual)
|
assert.Nil(t, actual)
|
||||||
})
|
})
|
||||||
|
@ -98,13 +99,13 @@ func TestGetFileReader(t *testing.T) {
|
||||||
|
|
||||||
mockedFileBackend.On("Reader", testFilePath).Return(readerFunc, readerErrorFunc)
|
mockedFileBackend.On("Reader", testFilePath).Return(readerFunc, readerErrorFunc)
|
||||||
mockedFileBackend.On("FileExists", testFilePath).Return(fileExistsFunc, fileExistsErrorFunc)
|
mockedFileBackend.On("FileExists", testFilePath).Return(fileExistsFunc, fileExistsErrorFunc)
|
||||||
actual, err := th.App.GetFileReader("1", testRootID, testFileName)
|
actual, err := th.App.GetFileReader("1", testBoardID, testFileName)
|
||||||
assert.Error(t, err, mockedError)
|
assert.Error(t, err, mockedError)
|
||||||
assert.Nil(t, actual)
|
assert.Nil(t, actual)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should move file from old filepath to new filepath, if file doesnot exists in new filepath and workspace id is 0", func(t *testing.T) {
|
t.Run("should move file from old filepath to new filepath, if file doesnot exists in new filepath and workspace id is 0", func(t *testing.T) {
|
||||||
filePath := "0/test-root-id/temp-file-name"
|
filePath := "0/test-board-id/temp-file-name"
|
||||||
workspaceid := "0"
|
workspaceid := "0"
|
||||||
mockedFileBackend := &mocks.FileBackend{}
|
mockedFileBackend := &mocks.FileBackend{}
|
||||||
th.App.filesBackend = mockedFileBackend
|
th.App.filesBackend = mockedFileBackend
|
||||||
|
@ -134,12 +135,12 @@ func TestGetFileReader(t *testing.T) {
|
||||||
mockedFileBackend.On("MoveFile", testFileName, filePath).Return(moveFileFunc)
|
mockedFileBackend.On("MoveFile", testFileName, filePath).Return(moveFileFunc)
|
||||||
mockedFileBackend.On("Reader", filePath).Return(readerFunc, readerErrorFunc)
|
mockedFileBackend.On("Reader", filePath).Return(readerFunc, readerErrorFunc)
|
||||||
|
|
||||||
actual, _ := th.App.GetFileReader(workspaceid, testRootID, testFileName)
|
actual, _ := th.App.GetFileReader(workspaceid, testBoardID, testFileName)
|
||||||
assert.Equal(t, mockedReadCloseSeek, actual)
|
assert.Equal(t, mockedReadCloseSeek, actual)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should return file reader, if file doesnot exists in new filepath and old file path", func(t *testing.T) {
|
t.Run("should return file reader, if file doesnot exists in new filepath and old file path", func(t *testing.T) {
|
||||||
filePath := "0/test-root-id/temp-file-name"
|
filePath := "0/test-board-id/temp-file-name"
|
||||||
fileName := testFileName
|
fileName := testFileName
|
||||||
workspaceid := "0"
|
workspaceid := "0"
|
||||||
mockedFileBackend := &mocks.FileBackend{}
|
mockedFileBackend := &mocks.FileBackend{}
|
||||||
|
@ -170,7 +171,7 @@ func TestGetFileReader(t *testing.T) {
|
||||||
mockedFileBackend.On("MoveFile", fileName, filePath).Return(moveFileFunc)
|
mockedFileBackend.On("MoveFile", fileName, filePath).Return(moveFileFunc)
|
||||||
mockedFileBackend.On("Reader", filePath).Return(readerFunc, readerErrorFunc)
|
mockedFileBackend.On("Reader", filePath).Return(readerFunc, readerErrorFunc)
|
||||||
|
|
||||||
actual, _ := th.App.GetFileReader(workspaceid, testRootID, testFileName)
|
actual, _ := th.App.GetFileReader(workspaceid, testBoardID, testFileName)
|
||||||
assert.Equal(t, mockedReadCloseSeek, actual)
|
assert.Equal(t, mockedReadCloseSeek, actual)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -186,7 +187,7 @@ func TestSaveFile(t *testing.T) {
|
||||||
writeFileFunc := func(reader io.Reader, path string) int64 {
|
writeFileFunc := func(reader io.Reader, path string) int64 {
|
||||||
paths := strings.Split(path, "/")
|
paths := strings.Split(path, "/")
|
||||||
assert.Equal(t, "1", paths[0])
|
assert.Equal(t, "1", paths[0])
|
||||||
assert.Equal(t, testRootID, paths[1])
|
assert.Equal(t, testBoardID, paths[1])
|
||||||
fileName = paths[2]
|
fileName = paths[2]
|
||||||
return int64(10)
|
return int64(10)
|
||||||
}
|
}
|
||||||
|
@ -196,7 +197,7 @@ func TestSaveFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
mockedFileBackend.On("WriteFile", mockedReadCloseSeek, mock.Anything).Return(writeFileFunc, writeFileErrorFunc)
|
mockedFileBackend.On("WriteFile", mockedReadCloseSeek, mock.Anything).Return(writeFileFunc, writeFileErrorFunc)
|
||||||
actual, err := th.App.SaveFile(mockedReadCloseSeek, "1", testRootID, fileName)
|
actual, err := th.App.SaveFile(mockedReadCloseSeek, "1", testBoardID, fileName)
|
||||||
assert.Equal(t, fileName, actual)
|
assert.Equal(t, fileName, actual)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
})
|
})
|
||||||
|
@ -209,7 +210,7 @@ func TestSaveFile(t *testing.T) {
|
||||||
writeFileFunc := func(reader io.Reader, path string) int64 {
|
writeFileFunc := func(reader io.Reader, path string) int64 {
|
||||||
paths := strings.Split(path, "/")
|
paths := strings.Split(path, "/")
|
||||||
assert.Equal(t, "1", paths[0])
|
assert.Equal(t, "1", paths[0])
|
||||||
assert.Equal(t, "test-root-id", paths[1])
|
assert.Equal(t, "test-board-id", paths[1])
|
||||||
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
|
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
|
||||||
return int64(10)
|
return int64(10)
|
||||||
}
|
}
|
||||||
|
@ -219,7 +220,7 @@ func TestSaveFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
mockedFileBackend.On("WriteFile", mockedReadCloseSeek, mock.Anything).Return(writeFileFunc, writeFileErrorFunc)
|
mockedFileBackend.On("WriteFile", mockedReadCloseSeek, mock.Anything).Return(writeFileFunc, writeFileErrorFunc)
|
||||||
actual, err := th.App.SaveFile(mockedReadCloseSeek, "1", "test-root-id", fileName)
|
actual, err := th.App.SaveFile(mockedReadCloseSeek, "1", "test-board-id", fileName)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.NotNil(t, actual)
|
assert.NotNil(t, actual)
|
||||||
})
|
})
|
||||||
|
@ -233,7 +234,7 @@ func TestSaveFile(t *testing.T) {
|
||||||
writeFileFunc := func(reader io.Reader, path string) int64 {
|
writeFileFunc := func(reader io.Reader, path string) int64 {
|
||||||
paths := strings.Split(path, "/")
|
paths := strings.Split(path, "/")
|
||||||
assert.Equal(t, "1", paths[0])
|
assert.Equal(t, "1", paths[0])
|
||||||
assert.Equal(t, "test-root-id", paths[1])
|
assert.Equal(t, "test-board-id", paths[1])
|
||||||
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
|
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
|
||||||
return int64(10)
|
return int64(10)
|
||||||
}
|
}
|
||||||
|
@ -243,7 +244,7 @@ func TestSaveFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
mockedFileBackend.On("WriteFile", mockedReadCloseSeek, mock.Anything).Return(writeFileFunc, writeFileErrorFunc)
|
mockedFileBackend.On("WriteFile", mockedReadCloseSeek, mock.Anything).Return(writeFileFunc, writeFileErrorFunc)
|
||||||
actual, err := th.App.SaveFile(mockedReadCloseSeek, "1", "test-root-id", fileName)
|
actual, err := th.App.SaveFile(mockedReadCloseSeek, "1", "test-board-id", fileName)
|
||||||
assert.Equal(t, "", actual)
|
assert.Equal(t, "", actual)
|
||||||
assert.Equal(t, "unable to store the file in the files storage: Mocked File backend error", err.Error())
|
assert.Equal(t, "unable to store the file in the files storage: Mocked File backend error", err.Error())
|
||||||
})
|
})
|
||||||
|
|
|
@ -29,11 +29,10 @@ func SetupTestHelper(t *testing.T) (*TestHelper, func()) {
|
||||||
defer ctrl.Finish()
|
defer ctrl.Finish()
|
||||||
cfg := config.Configuration{}
|
cfg := config.Configuration{}
|
||||||
store := mockstore.NewMockStore(ctrl)
|
store := mockstore.NewMockStore(ctrl)
|
||||||
|
auth := auth.New(&cfg, store, nil)
|
||||||
auth := auth.New(&cfg, store)
|
|
||||||
logger := mlog.CreateConsoleTestLogger(false, mlog.LvlDebug)
|
logger := mlog.CreateConsoleTestLogger(false, mlog.LvlDebug)
|
||||||
sessionToken := "TESTTOKEN"
|
sessionToken := "TESTTOKEN"
|
||||||
wsserver := ws.NewServer(auth, sessionToken, false, logger)
|
wsserver := ws.NewServer(auth, sessionToken, false, logger, store)
|
||||||
webhook := webhook.NewClient(&cfg, logger)
|
webhook := webhook.NewClient(&cfg, logger)
|
||||||
metricsService := metrics.NewMetrics(metrics.InstanceInfo{})
|
metricsService := metrics.NewMetrics(metrics.InstanceInfo{})
|
||||||
|
|
||||||
|
@ -49,6 +48,7 @@ func SetupTestHelper(t *testing.T) (*TestHelper, func()) {
|
||||||
app2 := New(&cfg, wsserver, appServices)
|
app2 := New(&cfg, wsserver, appServices)
|
||||||
|
|
||||||
tearDown := func() {
|
tearDown := func() {
|
||||||
|
app2.Shutdown()
|
||||||
if logger != nil {
|
if logger != nil {
|
||||||
_ = logger.Shutdown()
|
_ = logger.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import (
|
||||||
"github.com/krolaw/zipstream"
|
"github.com/krolaw/zipstream"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
|
||||||
"github.com/mattermost/focalboard/server/utils"
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
|
@ -84,7 +83,7 @@ func (a *App) ImportArchive(r io.Reader, opt model.ImportArchiveOptions) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// save file with original filename so it matches name in image block.
|
// save file with original filename so it matches name in image block.
|
||||||
filePath := filepath.Join(opt.WorkspaceID, boardID, filename)
|
filePath := filepath.Join(opt.TeamID, boardID, filename)
|
||||||
_, err := a.filesBackend.WriteFile(zr, filePath)
|
_, err := a.filesBackend.WriteFile(zr, filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot import file %s for board %s: %w", filename, dir, err)
|
return fmt.Errorf("cannot import file %s for board %s: %w", filename, dir, err)
|
||||||
|
@ -103,7 +102,10 @@ func (a *App) ImportArchive(r io.Reader, opt model.ImportArchiveOptions) error {
|
||||||
func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (string, error) {
|
func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (string, error) {
|
||||||
// TODO: Stream this once `model.GenerateBlockIDs` can take a stream of blocks.
|
// TODO: Stream this once `model.GenerateBlockIDs` can take a stream of blocks.
|
||||||
// We don't want to load the whole file in memory, even though it's a single board.
|
// We don't want to load the whole file in memory, even though it's a single board.
|
||||||
blocks := make([]model.Block, 0, 10)
|
boardsAndBlocks := &model.BoardsAndBlocks{
|
||||||
|
Blocks: make([]model.Block, 0, 10),
|
||||||
|
Boards: make([]*model.Board, 0, 10),
|
||||||
|
}
|
||||||
lineReader := bufio.NewReader(r)
|
lineReader := bufio.NewReader(r)
|
||||||
|
|
||||||
userID := opt.ModifiedBy
|
userID := opt.ModifiedBy
|
||||||
|
@ -137,7 +139,16 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str
|
||||||
}
|
}
|
||||||
block.ModifiedBy = userID
|
block.ModifiedBy = userID
|
||||||
block.UpdateAt = now
|
block.UpdateAt = now
|
||||||
blocks = append(blocks, block)
|
boardsAndBlocks.Blocks = append(boardsAndBlocks.Blocks, block)
|
||||||
|
case "board":
|
||||||
|
var board model.Board
|
||||||
|
if err2 := json.Unmarshal(archiveLine.Data, &board); err2 != nil {
|
||||||
|
return "", fmt.Errorf("invalid block in archive line %d: %w", lineNum, err2)
|
||||||
|
}
|
||||||
|
board.ModifiedBy = userID
|
||||||
|
board.UpdateAt = now
|
||||||
|
board.TeamID = opt.TeamID
|
||||||
|
boardsAndBlocks.Boards = append(boardsAndBlocks.Boards, &board)
|
||||||
default:
|
default:
|
||||||
return "", model.NewErrUnsupportedArchiveLineType(lineNum, archiveLine.Type)
|
return "", model.NewErrUnsupportedArchiveLineType(lineNum, archiveLine.Type)
|
||||||
}
|
}
|
||||||
|
@ -154,36 +165,33 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str
|
||||||
}
|
}
|
||||||
|
|
||||||
modInfoCache := make(map[string]interface{})
|
modInfoCache := make(map[string]interface{})
|
||||||
modBlocks := make([]model.Block, 0, len(blocks))
|
modBoards := make([]*model.Board, 0, len(boardsAndBlocks.Boards))
|
||||||
for _, block := range blocks {
|
for _, board := range boardsAndBlocks.Boards {
|
||||||
b := block
|
b := *board
|
||||||
if opt.BlockModifier != nil && !opt.BlockModifier(&b, modInfoCache) {
|
if opt.BoardModifier != nil && !opt.BoardModifier(&b, modInfoCache) {
|
||||||
a.logger.Debug("skipping insert block per block modifier",
|
a.logger.Debug("skipping insert block per block modifier",
|
||||||
mlog.String("blockID", block.ID),
|
mlog.String("blockID", board.ID),
|
||||||
mlog.String("block_type", block.Type.String()),
|
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
modBlocks = append(modBlocks, b)
|
modBoards = append(modBoards, &b)
|
||||||
}
|
|
||||||
|
|
||||||
blocks = model.GenerateBlockIDs(modBlocks, a.logger)
|
|
||||||
|
|
||||||
container := store.Container{
|
|
||||||
WorkspaceID: opt.WorkspaceID,
|
|
||||||
}
|
}
|
||||||
|
boardsAndBlocks.Boards = modBoards
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
blocks, err = a.InsertBlocks(container, blocks, opt.ModifiedBy, false)
|
boardsAndBlocks, err = model.GenerateBoardsAndBlocksIDs(boardsAndBlocks, a.logger)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error inserting archive blocks: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
boardsAndBlocks, err = a.CreateBoardsAndBlocks(boardsAndBlocks, opt.ModifiedBy, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("error inserting archive blocks: %w", err)
|
return "", fmt.Errorf("error inserting archive blocks: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// find new board id
|
// find new board id
|
||||||
for _, block := range blocks {
|
for _, board := range boardsAndBlocks.Boards {
|
||||||
if block.Type == model.TypeBoard {
|
return board.ID, nil
|
||||||
return block.ID, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("missing board in archive: %w", model.ErrInvalidBoardBlock)
|
return "", fmt.Errorf("missing board in archive: %w", model.ErrInvalidBoardBlock)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import "github.com/mattermost/mattermost-server/v6/shared/mlog"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
|
)
|
||||||
|
|
||||||
// initialize is called when the App is first created.
|
// initialize is called when the App is first created.
|
||||||
func (a *App) initialize(skipTemplateInit bool) {
|
func (a *App) initialize(skipTemplateInit bool) {
|
||||||
|
@ -10,3 +14,13 @@ func (a *App) initialize(skipTemplateInit bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) Shutdown() {
|
||||||
|
if a.blockChangeNotifier != nil {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), blockChangeNotifierShutdownTimeout)
|
||||||
|
defer cancel()
|
||||||
|
if !a.blockChangeNotifier.Shutdown(ctx) {
|
||||||
|
a.logger.Warn("blockChangeNotifier shutdown timed out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -21,17 +20,12 @@ const (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errUnableToFindWelcomeBoard = errors.New("unable to find welcome board in newly created blocks")
|
errUnableToFindWelcomeBoard = errors.New("unable to find welcome board in newly created blocks")
|
||||||
|
errCannotCreateBoard = errors.New("new board wasn't created")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) PrepareOnboardingTour(userID string) (string, string, error) {
|
func (a *App) PrepareOnboardingTour(userID string, teamID string) (string, string, error) {
|
||||||
// create a private workspace for the user
|
|
||||||
workspaceID, err := a.store.CreatePrivateWorkspace(userID)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy the welcome board into this workspace
|
// copy the welcome board into this workspace
|
||||||
boardID, err := a.createWelcomeBoard(userID, workspaceID)
|
boardID, err := a.createWelcomeBoard(userID, teamID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
@ -48,18 +42,18 @@ func (a *App) PrepareOnboardingTour(userID string) (string, string, error) {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return workspaceID, boardID, nil
|
return teamID, boardID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) getOnboardingBoardID() (string, error) {
|
func (a *App) getOnboardingBoardID() (string, error) {
|
||||||
blocks, err := a.store.GetDefaultTemplateBlocks()
|
boards, err := a.store.GetTemplateBoards(globalTeamID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var onboardingBoardID string
|
var onboardingBoardID string
|
||||||
for _, block := range blocks {
|
for _, block := range boards {
|
||||||
if block.Type == model.TypeBoard && block.Title == WelcomeBoardTitle {
|
if block.Title == WelcomeBoardTitle {
|
||||||
onboardingBoardID = block.ID
|
onboardingBoardID = block.ID
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -72,42 +66,20 @@ func (a *App) getOnboardingBoardID() (string, error) {
|
||||||
return onboardingBoardID, nil
|
return onboardingBoardID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) createWelcomeBoard(userID, workspaceID string) (string, error) {
|
func (a *App) createWelcomeBoard(userID, teamID string) (string, error) {
|
||||||
onboardingBoardID, err := a.getOnboardingBoardID()
|
onboardingBoardID, err := a.getOnboardingBoardID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
blocks, err := a.GetSubTree(store.Container{WorkspaceID: "0"}, onboardingBoardID, 3)
|
bab, _, err := a.DuplicateBoard(onboardingBoardID, userID, teamID, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
blocks = model.GenerateBlockIDs(blocks, a.logger)
|
if len(bab.Boards) != 1 {
|
||||||
|
return "", errCannotCreateBoard
|
||||||
if errUpdateFileIDs := a.CopyCardFiles(onboardingBoardID, workspaceID, blocks); errUpdateFileIDs != nil {
|
|
||||||
return "", errUpdateFileIDs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// we're copying from a global template, so we need to set the
|
return bab.Boards[0].ID, nil
|
||||||
// `isTemplate` flag to false on the board
|
|
||||||
var welcomeBoardID string
|
|
||||||
for i := range blocks {
|
|
||||||
if blocks[i].Type == model.TypeBoard {
|
|
||||||
blocks[i].Fields["isTemplate"] = false
|
|
||||||
|
|
||||||
if blocks[i].Title == WelcomeBoardTitle {
|
|
||||||
welcomeBoardID = blocks[i].ID
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
model.StampModificationMetadata(userID, blocks, nil)
|
|
||||||
_, err = a.InsertBlocks(store.Container{WorkspaceID: workspaceID}, blocks, userID, false)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return welcomeBoardID, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,44 +3,32 @@ package app
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testTeamID = "team_id"
|
||||||
|
)
|
||||||
|
|
||||||
func TestPrepareOnboardingTour(t *testing.T) {
|
func TestPrepareOnboardingTour(t *testing.T) {
|
||||||
th, tearDown := SetupTestHelper(t)
|
th, tearDown := SetupTestHelper(t)
|
||||||
defer tearDown()
|
defer tearDown()
|
||||||
|
|
||||||
t.Run("base case", func(t *testing.T) {
|
t.Run("base case", func(t *testing.T) {
|
||||||
welcomeBoard := model.Block{
|
teamID := testTeamID
|
||||||
ID: "block_id_1",
|
userID := "user_id_1"
|
||||||
Type: model.TypeBoard,
|
welcomeBoard := model.Board{
|
||||||
Title: "Welcome to Boards!",
|
ID: "board_id_1",
|
||||||
Fields: map[string]interface{}{
|
Title: "Welcome to Boards!",
|
||||||
"isTemplate": true,
|
TeamID: "0",
|
||||||
},
|
IsTemplate: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
blocks := []model.Block{welcomeBoard}
|
th.Store.EXPECT().GetTemplateBoards("0").Return([]*model.Board{&welcomeBoard}, nil)
|
||||||
th.Store.EXPECT().GetDefaultTemplateBlocks().Return(blocks, nil)
|
th.Store.EXPECT().DuplicateBoard(welcomeBoard.ID, userID, teamID, false).Return(&model.BoardsAndBlocks{Boards: []*model.Board{&welcomeBoard}},
|
||||||
|
nil, nil)
|
||||||
th.Store.EXPECT().GetSubTree3(
|
th.Store.EXPECT().GetMembersForBoard(welcomeBoard.ID).Return([]*model.BoardMember{}, nil)
|
||||||
store.Container{WorkspaceID: "0"},
|
|
||||||
"block_id_1",
|
|
||||||
gomock.Any(),
|
|
||||||
).Return([]model.Block{welcomeBoard}, nil)
|
|
||||||
|
|
||||||
th.Store.EXPECT().InsertBlock(
|
|
||||||
store.Container{WorkspaceID: "workspace_id_1"},
|
|
||||||
gomock.Any(),
|
|
||||||
"user_id_1",
|
|
||||||
).Return(nil)
|
|
||||||
|
|
||||||
th.Store.EXPECT().GetBlock(gomock.Any(), "block_id_1").Return(&welcomeBoard, nil)
|
|
||||||
|
|
||||||
th.Store.EXPECT().CreatePrivateWorkspace("user_id_1").Return("workspace_id_1", nil)
|
|
||||||
|
|
||||||
userPropPatch := model.UserPropPatch{
|
userPropPatch := model.UserPropPatch{
|
||||||
UpdatedFields: map[string]string{
|
UpdatedFields: map[string]string{
|
||||||
|
@ -50,11 +38,11 @@ func TestPrepareOnboardingTour(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
th.Store.EXPECT().PatchUserProps("user_id_1", userPropPatch).Return(nil)
|
th.Store.EXPECT().PatchUserProps(userID, userPropPatch).Return(nil)
|
||||||
|
|
||||||
workspaceID, boardID, err := th.App.PrepareOnboardingTour("user_id_1")
|
teamID, boardID, err := th.App.PrepareOnboardingTour(userID, teamID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "workspace_id_1", workspaceID)
|
assert.Equal(t, testTeamID, teamID)
|
||||||
assert.NotEmpty(t, boardID)
|
assert.NotEmpty(t, boardID)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -64,88 +52,41 @@ func TestCreateWelcomeBoard(t *testing.T) {
|
||||||
defer tearDown()
|
defer tearDown()
|
||||||
|
|
||||||
t.Run("base case", func(t *testing.T) {
|
t.Run("base case", func(t *testing.T) {
|
||||||
welcomeBoard := model.Block{
|
teamID := testTeamID
|
||||||
ID: "block_id_1",
|
userID := "user_id_1"
|
||||||
Type: model.TypeBoard,
|
welcomeBoard := model.Board{
|
||||||
Title: "Welcome to Boards!",
|
ID: "board_id_1",
|
||||||
Fields: map[string]interface{}{
|
Title: "Welcome to Boards!",
|
||||||
"isTemplate": true,
|
TeamID: "0",
|
||||||
},
|
IsTemplate: true,
|
||||||
}
|
}
|
||||||
|
th.Store.EXPECT().GetTemplateBoards("0").Return([]*model.Board{&welcomeBoard}, nil)
|
||||||
|
th.Store.EXPECT().DuplicateBoard(welcomeBoard.ID, userID, teamID, false).
|
||||||
|
Return(&model.BoardsAndBlocks{Boards: []*model.Board{&welcomeBoard}}, nil, nil)
|
||||||
|
th.Store.EXPECT().GetMembersForBoard(welcomeBoard.ID).Return([]*model.BoardMember{}, nil)
|
||||||
|
|
||||||
blocks := []model.Block{welcomeBoard}
|
boardID, err := th.App.createWelcomeBoard(userID, teamID)
|
||||||
th.Store.EXPECT().GetDefaultTemplateBlocks().Return(blocks, nil)
|
|
||||||
|
|
||||||
th.Store.EXPECT().GetSubTree3(
|
|
||||||
store.Container{WorkspaceID: "0"},
|
|
||||||
"block_id_1",
|
|
||||||
gomock.Any(),
|
|
||||||
).Return([]model.Block{welcomeBoard}, nil)
|
|
||||||
|
|
||||||
th.Store.EXPECT().InsertBlock(
|
|
||||||
store.Container{WorkspaceID: "workspace_id_1"},
|
|
||||||
gomock.Any(),
|
|
||||||
"user_id_1",
|
|
||||||
).Return(nil)
|
|
||||||
|
|
||||||
th.Store.EXPECT().GetBlock(gomock.Any(), "block_id_1").Return(&welcomeBoard, nil)
|
|
||||||
|
|
||||||
boardID, err := th.App.createWelcomeBoard("user_id_1", "workspace_id_1")
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.NotEmpty(t, boardID)
|
assert.NotEmpty(t, boardID)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("template doesn't contain a board", func(t *testing.T) {
|
t.Run("template doesn't contain a board", func(t *testing.T) {
|
||||||
welcomeBoard := model.Block{
|
teamID := testTeamID
|
||||||
ID: "block_id_1",
|
th.Store.EXPECT().GetTemplateBoards("0").Return([]*model.Board{}, nil)
|
||||||
Type: model.TypeComment,
|
boardID, err := th.App.createWelcomeBoard("user_id_1", teamID)
|
||||||
Title: "Welcome to Boards!",
|
|
||||||
}
|
|
||||||
blocks := []model.Block{welcomeBoard}
|
|
||||||
th.Store.EXPECT().GetDefaultTemplateBlocks().Return(blocks, nil)
|
|
||||||
|
|
||||||
th.Store.EXPECT().GetSubTree3(
|
|
||||||
store.Container{WorkspaceID: "0"},
|
|
||||||
"buixxjic3xjfkieees4iafdrznc",
|
|
||||||
gomock.Any(),
|
|
||||||
).Return([]model.Block{welcomeBoard}, nil)
|
|
||||||
|
|
||||||
th.Store.EXPECT().InsertBlock(
|
|
||||||
store.Container{WorkspaceID: "workspace_id_1"},
|
|
||||||
gomock.Any(),
|
|
||||||
"user_id_1",
|
|
||||||
).Return(nil)
|
|
||||||
|
|
||||||
boardID, err := th.App.createWelcomeBoard("user_id_1", "workspace_id_1")
|
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Empty(t, boardID)
|
assert.Empty(t, boardID)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("template doesn't contain the welcome board", func(t *testing.T) {
|
t.Run("template doesn't contain the welcome board", func(t *testing.T) {
|
||||||
welcomeBoard := model.Block{
|
teamID := testTeamID
|
||||||
ID: "block_id_1",
|
welcomeBoard := model.Board{
|
||||||
Type: model.TypeBoard,
|
ID: "board_id_1",
|
||||||
Title: "Jean luc Picard",
|
Title: "Other template",
|
||||||
Fields: map[string]interface{}{
|
TeamID: teamID,
|
||||||
"isTemplate": true,
|
IsTemplate: true,
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
th.Store.EXPECT().GetTemplateBoards("0").Return([]*model.Board{&welcomeBoard}, nil)
|
||||||
blocks := []model.Block{welcomeBoard}
|
|
||||||
th.Store.EXPECT().GetDefaultTemplateBlocks().Return(blocks, nil)
|
|
||||||
|
|
||||||
th.Store.EXPECT().GetSubTree3(
|
|
||||||
store.Container{WorkspaceID: "0"},
|
|
||||||
"buixxjic3xjfkieees4iafdrznc",
|
|
||||||
gomock.Any(),
|
|
||||||
).Return([]model.Block{welcomeBoard}, nil)
|
|
||||||
|
|
||||||
th.Store.EXPECT().InsertBlock(
|
|
||||||
store.Container{WorkspaceID: "workspace_id_1"},
|
|
||||||
gomock.Any(),
|
|
||||||
"user_id_1",
|
|
||||||
).Return(nil)
|
|
||||||
|
|
||||||
boardID, err := th.App.createWelcomeBoard("user_id_1", "workspace_id_1")
|
boardID, err := th.App.createWelcomeBoard("user_id_1", "workspace_id_1")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Empty(t, boardID)
|
assert.Empty(t, boardID)
|
||||||
|
@ -157,24 +98,13 @@ func TestGetOnboardingBoardID(t *testing.T) {
|
||||||
defer tearDown()
|
defer tearDown()
|
||||||
|
|
||||||
t.Run("base case", func(t *testing.T) {
|
t.Run("base case", func(t *testing.T) {
|
||||||
board := model.Block{
|
welcomeBoard := model.Board{
|
||||||
ID: "board_id_1",
|
ID: "board_id_1",
|
||||||
Type: model.TypeBoard,
|
Title: "Welcome to Boards!",
|
||||||
Title: "Welcome to Boards!",
|
TeamID: "0",
|
||||||
|
IsTemplate: true,
|
||||||
}
|
}
|
||||||
|
th.Store.EXPECT().GetTemplateBoards("0").Return([]*model.Board{&welcomeBoard}, nil)
|
||||||
card := model.Block{
|
|
||||||
ID: "card_id_1",
|
|
||||||
Type: model.TypeCard,
|
|
||||||
ParentID: board.ID,
|
|
||||||
}
|
|
||||||
|
|
||||||
blocks := []model.Block{
|
|
||||||
board,
|
|
||||||
card,
|
|
||||||
}
|
|
||||||
|
|
||||||
th.Store.EXPECT().GetDefaultTemplateBlocks().Return(blocks, nil)
|
|
||||||
|
|
||||||
onboardingBoardID, err := th.App.getOnboardingBoardID()
|
onboardingBoardID, err := th.App.getOnboardingBoardID()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -182,9 +112,7 @@ func TestGetOnboardingBoardID(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("no blocks found", func(t *testing.T) {
|
t.Run("no blocks found", func(t *testing.T) {
|
||||||
blocks := []model.Block{}
|
th.Store.EXPECT().GetTemplateBoards("0").Return([]*model.Board{}, nil)
|
||||||
|
|
||||||
th.Store.EXPECT().GetDefaultTemplateBlocks().Return(blocks, nil)
|
|
||||||
|
|
||||||
onboardingBoardID, err := th.App.getOnboardingBoardID()
|
onboardingBoardID, err := th.App.getOnboardingBoardID()
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
@ -192,24 +120,13 @@ func TestGetOnboardingBoardID(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("onboarding board doesn't exists", func(t *testing.T) {
|
t.Run("onboarding board doesn't exists", func(t *testing.T) {
|
||||||
board := model.Block{
|
welcomeBoard := model.Board{
|
||||||
ID: "board_id_1",
|
ID: "board_id_1",
|
||||||
Type: model.TypeBoard,
|
Title: "Other template",
|
||||||
Title: "Some board title",
|
TeamID: "0",
|
||||||
|
IsTemplate: true,
|
||||||
}
|
}
|
||||||
|
th.Store.EXPECT().GetTemplateBoards("0").Return([]*model.Board{&welcomeBoard}, nil)
|
||||||
card := model.Block{
|
|
||||||
ID: "card_id_1",
|
|
||||||
Type: model.TypeCard,
|
|
||||||
ParentID: board.ID,
|
|
||||||
}
|
|
||||||
|
|
||||||
blocks := []model.Block{
|
|
||||||
board,
|
|
||||||
card,
|
|
||||||
}
|
|
||||||
|
|
||||||
th.Store.EXPECT().GetDefaultTemplateBlocks().Return(blocks, nil)
|
|
||||||
|
|
||||||
onboardingBoardID, err := th.App.getOnboardingBoardID()
|
onboardingBoardID, err := th.App.getOnboardingBoardID()
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
|
@ -5,11 +5,10 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) GetSharing(c store.Container, rootID string) (*model.Sharing, error) {
|
func (a *App) GetSharing(boardID string) (*model.Sharing, error) {
|
||||||
sharing, err := a.store.GetSharing(c, rootID)
|
sharing, err := a.store.GetSharing(boardID)
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -19,6 +18,6 @@ func (a *App) GetSharing(c store.Container, rootID string) (*model.Sharing, erro
|
||||||
return sharing, nil
|
return sharing, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) UpsertSharing(c store.Container, sharing model.Sharing) error {
|
func (a *App) UpsertSharing(sharing model.Sharing) error {
|
||||||
return a.store.UpsertSharing(c, sharing)
|
return a.store.UpsertSharing(sharing)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,7 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
st "github.com/mattermost/focalboard/server/services/store"
|
|
||||||
"github.com/mattermost/focalboard/server/utils"
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -16,10 +14,6 @@ func TestGetSharing(t *testing.T) {
|
||||||
th, tearDown := SetupTestHelper(t)
|
th, tearDown := SetupTestHelper(t)
|
||||||
defer tearDown()
|
defer tearDown()
|
||||||
|
|
||||||
container := st.Container{
|
|
||||||
WorkspaceID: utils.NewID(utils.IDTypeWorkspace),
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("should get a sharing successfully", func(t *testing.T) {
|
t.Run("should get a sharing successfully", func(t *testing.T) {
|
||||||
want := &model.Sharing{
|
want := &model.Sharing{
|
||||||
ID: utils.NewID(utils.IDTypeBlock),
|
ID: utils.NewID(utils.IDTypeBlock),
|
||||||
|
@ -28,9 +22,9 @@ func TestGetSharing(t *testing.T) {
|
||||||
ModifiedBy: "otherid",
|
ModifiedBy: "otherid",
|
||||||
UpdateAt: utils.GetMillis(),
|
UpdateAt: utils.GetMillis(),
|
||||||
}
|
}
|
||||||
th.Store.EXPECT().GetSharing(gomock.Eq(container), gomock.Eq("test-id")).Return(want, nil)
|
th.Store.EXPECT().GetSharing("test-id").Return(want, nil)
|
||||||
|
|
||||||
result, err := th.App.GetSharing(container, "test-id")
|
result, err := th.App.GetSharing("test-id")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, result, want)
|
require.Equal(t, result, want)
|
||||||
|
@ -38,11 +32,11 @@ func TestGetSharing(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should fail to get a sharing", func(t *testing.T) {
|
t.Run("should fail to get a sharing", func(t *testing.T) {
|
||||||
th.Store.EXPECT().GetSharing(gomock.Eq(container), gomock.Eq("test-id")).Return(
|
th.Store.EXPECT().GetSharing("test-id").Return(
|
||||||
nil,
|
nil,
|
||||||
errors.New("sharing not found"),
|
errors.New("sharing not found"),
|
||||||
)
|
)
|
||||||
result, err := th.App.GetSharing(container, "test-id")
|
result, err := th.App.GetSharing("test-id")
|
||||||
|
|
||||||
require.Nil(t, result)
|
require.Nil(t, result)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
@ -50,11 +44,11 @@ func TestGetSharing(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should return a tuple of nil", func(t *testing.T) {
|
t.Run("should return a tuple of nil", func(t *testing.T) {
|
||||||
th.Store.EXPECT().GetSharing(gomock.Eq(container), gomock.Eq("test-id")).Return(
|
th.Store.EXPECT().GetSharing("test-id").Return(
|
||||||
nil,
|
nil,
|
||||||
sql.ErrNoRows,
|
sql.ErrNoRows,
|
||||||
)
|
)
|
||||||
result, err := th.App.GetSharing(container, "test-id")
|
result, err := th.App.GetSharing("test-id")
|
||||||
|
|
||||||
require.Nil(t, result)
|
require.Nil(t, result)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -65,9 +59,6 @@ func TestUpsertSharing(t *testing.T) {
|
||||||
th, tearDown := SetupTestHelper(t)
|
th, tearDown := SetupTestHelper(t)
|
||||||
defer tearDown()
|
defer tearDown()
|
||||||
|
|
||||||
container := st.Container{
|
|
||||||
WorkspaceID: utils.NewID(utils.IDTypeWorkspace),
|
|
||||||
}
|
|
||||||
sharing := model.Sharing{
|
sharing := model.Sharing{
|
||||||
ID: utils.NewID(utils.IDTypeBlock),
|
ID: utils.NewID(utils.IDTypeBlock),
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
|
@ -77,15 +68,15 @@ func TestUpsertSharing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("should success to upsert sharing", func(t *testing.T) {
|
t.Run("should success to upsert sharing", func(t *testing.T) {
|
||||||
th.Store.EXPECT().UpsertSharing(gomock.Eq(container), gomock.Eq(sharing)).Return(nil)
|
th.Store.EXPECT().UpsertSharing(sharing).Return(nil)
|
||||||
err := th.App.UpsertSharing(container, sharing)
|
err := th.App.UpsertSharing(sharing)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should fail to upsert a sharing", func(t *testing.T) {
|
t.Run("should fail to upsert a sharing", func(t *testing.T) {
|
||||||
th.Store.EXPECT().UpsertSharing(gomock.Eq(container), gomock.Eq(sharing)).Return(errors.New("sharing not found"))
|
th.Store.EXPECT().UpsertSharing(sharing).Return(errors.New("sharing not found"))
|
||||||
err := th.App.UpsertSharing(container, sharing)
|
err := th.App.UpsertSharing(sharing)
|
||||||
|
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Equal(t, "sharing not found", err.Error())
|
require.Equal(t, "sharing not found", err.Error())
|
||||||
|
|
|
@ -2,41 +2,40 @@ package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
|
||||||
"github.com/mattermost/focalboard/server/utils"
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) CreateSubscription(c store.Container, sub *model.Subscription) (*model.Subscription, error) {
|
func (a *App) CreateSubscription(sub *model.Subscription) (*model.Subscription, error) {
|
||||||
sub, err := a.store.CreateSubscription(c, sub)
|
sub, err := a.store.CreateSubscription(sub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
a.notifySubscriptionChanged(c, sub)
|
a.notifySubscriptionChanged(sub)
|
||||||
|
|
||||||
return sub, nil
|
return sub, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) DeleteSubscription(c store.Container, blockID string, subscriberID string) (*model.Subscription, error) {
|
func (a *App) DeleteSubscription(blockID string, subscriberID string) (*model.Subscription, error) {
|
||||||
sub, err := a.store.GetSubscription(c, blockID, subscriberID)
|
sub, err := a.store.GetSubscription(blockID, subscriberID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := a.store.DeleteSubscription(c, blockID, subscriberID); err != nil {
|
if err := a.store.DeleteSubscription(blockID, subscriberID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sub.DeleteAt = utils.GetMillis()
|
sub.DeleteAt = utils.GetMillis()
|
||||||
a.notifySubscriptionChanged(c, sub)
|
a.notifySubscriptionChanged(sub)
|
||||||
|
|
||||||
return sub, nil
|
return sub, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetSubscriptions(c store.Container, subscriberID string) ([]*model.Subscription, error) {
|
func (a *App) GetSubscriptions(subscriberID string) ([]*model.Subscription, error) {
|
||||||
return a.store.GetSubscriptions(c, subscriberID)
|
return a.store.GetSubscriptions(subscriberID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) notifySubscriptionChanged(c store.Container, subscription *model.Subscription) {
|
func (a *App) notifySubscriptionChanged(subscription *model.Subscription) {
|
||||||
if a.notifications == nil {
|
if a.notifications == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
a.notifications.BroadcastSubscriptionChange(c.WorkspaceID, subscription)
|
a.notifications.BroadcastSubscriptionChange(subscription)
|
||||||
}
|
}
|
||||||
|
|
68
server/app/teams.go
Normal file
68
server/app/teams.go
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/mattermost/focalboard/server/model"
|
||||||
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *App) GetRootTeam() (*model.Team, error) {
|
||||||
|
teamID := "0"
|
||||||
|
team, _ := a.store.GetTeam(teamID)
|
||||||
|
if team == nil {
|
||||||
|
team = &model.Team{
|
||||||
|
ID: teamID,
|
||||||
|
SignupToken: utils.NewID(utils.IDTypeToken),
|
||||||
|
}
|
||||||
|
err := a.store.UpsertTeamSignupToken(*team)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("Unable to initialize team", mlog.Err(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
team, err = a.store.GetTeam(teamID)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("Unable to get initialized team", mlog.Err(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.logger.Info("initialized team")
|
||||||
|
}
|
||||||
|
|
||||||
|
return team, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetTeam(id string) (*model.Team, error) {
|
||||||
|
team, err := a.store.GetTeam(id)
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return team, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetTeamsForUser(userID string) ([]*model.Team, error) {
|
||||||
|
return a.store.GetTeamsForUser(userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) DoesUserHaveTeamAccess(userID string, teamID string) bool {
|
||||||
|
return a.auth.DoesUserHaveTeamAccess(userID, teamID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) UpsertTeamSettings(team model.Team) error {
|
||||||
|
return a.store.UpsertTeamSettings(team)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) UpsertTeamSignupToken(team model.Team) error {
|
||||||
|
return a.store.UpsertTeamSignupToken(team)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetTeamCount() (int64, error) {
|
||||||
|
return a.store.GetTeamCount()
|
||||||
|
}
|
152
server/app/teams_test.go
Normal file
152
server/app/teams_test.go
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/mattermost/focalboard/server/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errInvalidTeam = errors.New("invalid team id")
|
||||||
|
|
||||||
|
var mockTeam = &model.Team{
|
||||||
|
ID: "mock-team-id",
|
||||||
|
Title: "MockTeam",
|
||||||
|
}
|
||||||
|
|
||||||
|
var errUpsertSignupToken = errors.New("upsert error")
|
||||||
|
|
||||||
|
func TestGetRootTeam(t *testing.T) {
|
||||||
|
var newRootTeam = &model.Team{
|
||||||
|
ID: "0",
|
||||||
|
Title: "NewRootTeam",
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
title string
|
||||||
|
teamToReturnBeforeUpsert *model.Team
|
||||||
|
teamToReturnAfterUpsert *model.Team
|
||||||
|
isError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"Success, Return new root team, when root team returned by mockstore is nil",
|
||||||
|
nil,
|
||||||
|
newRootTeam,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Success, Return existing root team, when root team returned by mockstore is notnil",
|
||||||
|
newRootTeam,
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Fail, Return nil, when root team returned by mockstore is nil, and upsert new root team fails",
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.title, func(t *testing.T) {
|
||||||
|
th, tearDown := SetupTestHelper(t)
|
||||||
|
defer tearDown()
|
||||||
|
th.Store.EXPECT().GetTeam("0").Return(tc.teamToReturnBeforeUpsert, nil)
|
||||||
|
th.Store.EXPECT().UpsertTeamSignupToken(gomock.Any()).DoAndReturn(
|
||||||
|
func(arg0 model.Team) error {
|
||||||
|
if tc.isError {
|
||||||
|
return errUpsertSignupToken
|
||||||
|
}
|
||||||
|
th.Store.EXPECT().GetTeam("0").Return(tc.teamToReturnAfterUpsert, nil)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
rootTeam, err := th.App.GetRootTeam()
|
||||||
|
|
||||||
|
if tc.isError {
|
||||||
|
require.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NotNil(t, rootTeam.ID)
|
||||||
|
assert.NotNil(t, rootTeam.SignupToken)
|
||||||
|
assert.Equal(t, "", rootTeam.ModifiedBy)
|
||||||
|
assert.Equal(t, int64(0), rootTeam.UpdateAt)
|
||||||
|
assert.Equal(t, "NewRootTeam", rootTeam.Title)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, rootTeam)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTeam(t *testing.T) {
|
||||||
|
th, tearDown := SetupTestHelper(t)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
title string
|
||||||
|
teamID string
|
||||||
|
isError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"Success, Return new root team, when team returned by mockstore is not nil",
|
||||||
|
"mock-team-id",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Success, Return nil, when get team returns an sql error",
|
||||||
|
"team-not-available-id",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Fail, Return nil, when get team by mockstore returns an error",
|
||||||
|
"invalid-team-id",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
th.Store.EXPECT().GetTeam("mock-team-id").Return(mockTeam, nil)
|
||||||
|
th.Store.EXPECT().GetTeam("invalid-team-id").Return(nil, errInvalidTeam)
|
||||||
|
th.Store.EXPECT().GetTeam("team-not-available-id").Return(nil, sql.ErrNoRows)
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.title, func(t *testing.T) {
|
||||||
|
t.Log(tc.title)
|
||||||
|
team, err := th.App.GetTeam(tc.teamID)
|
||||||
|
|
||||||
|
if tc.isError {
|
||||||
|
require.Error(t, err)
|
||||||
|
} else if tc.teamID != "team-not-available-id" {
|
||||||
|
assert.NotNil(t, team.ID)
|
||||||
|
assert.NotNil(t, team.SignupToken)
|
||||||
|
assert.Equal(t, "mock-team-id", team.ID)
|
||||||
|
assert.Equal(t, "", team.ModifiedBy)
|
||||||
|
assert.Equal(t, int64(0), team.UpdateAt)
|
||||||
|
assert.Equal(t, "MockTeam", team.Title)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, team)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTeamOperations(t *testing.T) {
|
||||||
|
th, tearDown := SetupTestHelper(t)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
th.Store.EXPECT().UpsertTeamSettings(*mockTeam).Return(nil)
|
||||||
|
th.Store.EXPECT().UpsertTeamSignupToken(*mockTeam).Return(nil)
|
||||||
|
th.Store.EXPECT().GetTeamCount().Return(int64(10), nil)
|
||||||
|
|
||||||
|
errUpsertTeamSettings := th.App.UpsertTeamSettings(*mockTeam)
|
||||||
|
assert.NoError(t, errUpsertTeamSettings)
|
||||||
|
|
||||||
|
errUpsertTeamSignupToken := th.App.UpsertTeamSignupToken(*mockTeam)
|
||||||
|
assert.NoError(t, errUpsertTeamSignupToken)
|
||||||
|
|
||||||
|
count, errGetTeamCount := th.App.GetTeamCount()
|
||||||
|
assert.NoError(t, errGetTeamCount)
|
||||||
|
assert.Equal(t, int64(10), count)
|
||||||
|
}
|
Binary file not shown.
|
@ -14,21 +14,26 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultTemplateVersion = 2
|
defaultTemplateVersion = 2
|
||||||
|
globalTeamID = "0"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed templates.boardarchive
|
//go:embed templates.boardarchive
|
||||||
var defTemplates []byte
|
var defTemplates []byte
|
||||||
|
|
||||||
// initializeTemplates imports default templates if the blocks table is empty.
|
func (a *App) InitTemplates() error {
|
||||||
|
return a.initializeTemplates()
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializeTemplates imports default templates if the boards table is empty.
|
||||||
func (a *App) initializeTemplates() error {
|
func (a *App) initializeTemplates() error {
|
||||||
blocks, err := a.store.GetDefaultTemplateBlocks()
|
boards, err := a.store.GetTemplateBoards(globalTeamID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot initialize templates: %w", err)
|
return fmt.Errorf("cannot initialize templates: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.logger.Debug("Fetched template blocks", mlog.Int("count", len(blocks)))
|
a.logger.Debug("Fetched template boards", mlog.Int("count", len(boards)))
|
||||||
|
|
||||||
isNeeded, reason := a.isInitializationNeeded(blocks)
|
isNeeded, reason := a.isInitializationNeeded(boards)
|
||||||
if !isNeeded {
|
if !isNeeded {
|
||||||
a.logger.Debug("Template import not needed, skipping")
|
a.logger.Debug("Template import not needed, skipping")
|
||||||
return nil
|
return nil
|
||||||
|
@ -36,67 +41,55 @@ func (a *App) initializeTemplates() error {
|
||||||
|
|
||||||
a.logger.Debug("Importing new default templates", mlog.String("reason", reason))
|
a.logger.Debug("Importing new default templates", mlog.String("reason", reason))
|
||||||
|
|
||||||
if err := a.store.RemoveDefaultTemplates(blocks); err != nil {
|
// Remove in case of newer Templates
|
||||||
return fmt.Errorf("cannot remove old templates: %w", err)
|
if err = a.store.RemoveDefaultTemplates(boards); err != nil {
|
||||||
|
return fmt.Errorf("cannot remove old template boards: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r := bytes.NewReader(defTemplates)
|
r := bytes.NewReader(defTemplates)
|
||||||
|
|
||||||
opt := model.ImportArchiveOptions{
|
opt := model.ImportArchiveOptions{
|
||||||
WorkspaceID: "0",
|
TeamID: globalTeamID,
|
||||||
ModifiedBy: "system",
|
ModifiedBy: "system",
|
||||||
BlockModifier: fixTemplateBlock,
|
BoardModifier: fixTemplateBoard,
|
||||||
}
|
}
|
||||||
|
if err = a.ImportArchive(r, opt); err != nil {
|
||||||
return a.ImportArchive(r, opt)
|
return fmt.Errorf("cannot initialize global templates for team %s: %w", globalTeamID, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isInitializationNeeded returns true if the blocks table contains no default templates,
|
// isInitializationNeeded returns true if the blocks table contains no default templates,
|
||||||
// or contains at least one default template with an old version number.
|
// or contains at least one default template with an old version number.
|
||||||
func (a *App) isInitializationNeeded(blocks []model.Block) (bool, string) {
|
func (a *App) isInitializationNeeded(boards []*model.Board) (bool, string) {
|
||||||
if len(blocks) == 0 {
|
if len(boards) == 0 {
|
||||||
return true, "no default templates found"
|
return true, "no default templates found"
|
||||||
}
|
}
|
||||||
|
|
||||||
// look for any template blocks with the wrong version number (or no version #).
|
// look for any built-in template boards with the wrong version number (or no version #).
|
||||||
for _, block := range blocks {
|
for _, board := range boards {
|
||||||
v, ok := block.Fields["templateVer"]
|
// if not built-in board...skip
|
||||||
if !ok {
|
if board.CreatedBy != "system" {
|
||||||
return true, "block missing templateVer"
|
continue
|
||||||
}
|
}
|
||||||
version, ok := v.(float64)
|
if board.TemplateVersion < defaultTemplateVersion {
|
||||||
if !ok {
|
return true, "template_version too old"
|
||||||
return true, "templateVer NaN"
|
|
||||||
}
|
|
||||||
if version < defaultTemplateVersion {
|
|
||||||
return true, "templateVer too old"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixTemplateBlock fixes a block to be inserted as part of a template.
|
// fixTemplateBoard fixes a board to be inserted as part of a template.
|
||||||
func fixTemplateBlock(block *model.Block, cache map[string]interface{}) bool {
|
func fixTemplateBoard(board *model.Board, cache map[string]interface{}) bool {
|
||||||
// cache contains ids of skipped blocks. Ensure their children are skipped as well.
|
|
||||||
if _, ok := cache[block.ParentID]; ok {
|
|
||||||
cache[block.ID] = struct{}{}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter out template blocks; we only want the non-template
|
// filter out template blocks; we only want the non-template
|
||||||
// blocks which we will turn into default template blocks.
|
// blocks which we will turn into default template blocks.
|
||||||
if b, ok := block.Fields["isTemplate"]; ok {
|
if board.IsTemplate {
|
||||||
if val, ok := b.(bool); ok && val {
|
cache[board.ID] = struct{}{}
|
||||||
cache[block.ID] = struct{}{}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove '(NEW)' from title & force template flag
|
// remove '(NEW)' from title & force template flag
|
||||||
if block.Type == model.TypeBoard {
|
board.Title = strings.ReplaceAll(board.Title, "(NEW)", "")
|
||||||
block.Title = strings.ReplaceAll(block.Title, "(NEW)", "")
|
board.IsTemplate = true
|
||||||
block.Fields["isTemplate"] = true
|
board.TemplateVersion = defaultTemplateVersion
|
||||||
block.Fields["templateVer"] = defaultTemplateVersion
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,12 @@ package app
|
||||||
|
|
||||||
import "github.com/mattermost/focalboard/server/model"
|
import "github.com/mattermost/focalboard/server/model"
|
||||||
|
|
||||||
func (a *App) GetWorkspaceUsers(workspaceID string) ([]*model.User, error) {
|
func (a *App) GetTeamUsers(teamID string) ([]*model.User, error) {
|
||||||
return a.store.GetUsersByWorkspace(workspaceID)
|
return a.store.GetUsersByTeam(teamID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) SearchTeamUsers(teamID string, searchQuery string) ([]*model.User, error) {
|
||||||
|
return a.store.SearchUsersByTeam(teamID, searchQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) UpdateUserConfig(userID string, patch model.UserPropPatch) (map[string]interface{}, error) {
|
func (a *App) UpdateUserConfig(userID string, patch model.UserPropPatch) (map[string]interface{}, error) {
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/model"
|
|
||||||
"github.com/mattermost/focalboard/server/utils"
|
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (a *App) GetRootWorkspace() (*model.Workspace, error) {
|
|
||||||
workspaceID := "0"
|
|
||||||
workspace, _ := a.store.GetWorkspace(workspaceID)
|
|
||||||
if workspace == nil {
|
|
||||||
workspace = &model.Workspace{
|
|
||||||
ID: workspaceID,
|
|
||||||
SignupToken: utils.NewID(utils.IDTypeToken),
|
|
||||||
}
|
|
||||||
err := a.store.UpsertWorkspaceSignupToken(*workspace)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Error("Unable to initialize workspace", mlog.Err(err))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
workspace, err = a.store.GetWorkspace(workspaceID)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Error("Unable to get initialized workspace", mlog.Err(err))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
a.logger.Info("initialized workspace")
|
|
||||||
}
|
|
||||||
|
|
||||||
return workspace, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) GetWorkspace(id string) (*model.Workspace, error) {
|
|
||||||
workspace, err := a.store.GetWorkspace(id)
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return workspace, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) DoesUserHaveWorkspaceAccess(userID string, workspaceID string) bool {
|
|
||||||
return a.auth.DoesUserHaveWorkspaceAccess(userID, workspaceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) UpsertWorkspaceSettings(workspace model.Workspace) error {
|
|
||||||
return a.store.UpsertWorkspaceSettings(workspace)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) UpsertWorkspaceSignupToken(workspace model.Workspace) error {
|
|
||||||
return a.store.UpsertWorkspaceSignupToken(workspace)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) GetWorkspaceCount() (int64, error) {
|
|
||||||
return a.store.GetWorkspaceCount()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) GetUserWorkspaces(userID string) ([]model.UserWorkspace, error) {
|
|
||||||
return a.store.GetUserWorkspaces(userID)
|
|
||||||
}
|
|
|
@ -1,165 +0,0 @@
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/mattermost/focalboard/server/model"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errInvalidWorkspace = errors.New("invalid workspace id")
|
|
||||||
|
|
||||||
var mockWorkspace = &model.Workspace{
|
|
||||||
ID: "mock-workspace-id",
|
|
||||||
Title: "MockWorkspace",
|
|
||||||
}
|
|
||||||
|
|
||||||
var mockUserWorkspaces = []model.UserWorkspace{
|
|
||||||
{
|
|
||||||
ID: "mock-user-workspace-id",
|
|
||||||
Title: "MockUserWorkspace",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var errUpsertSignupToken = errors.New("upsert error")
|
|
||||||
|
|
||||||
func TestGetRootWorkspace(t *testing.T) {
|
|
||||||
var newRootWorkspace = &model.Workspace{
|
|
||||||
ID: "0",
|
|
||||||
Title: "NewRootWorkspace",
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
title string
|
|
||||||
workSpaceToReturnBeforeUpsert *model.Workspace
|
|
||||||
workSpaceToReturnAfterUpsert *model.Workspace
|
|
||||||
isError bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"Success, Return new root workspace, when root workspace returned by mockstore is nil",
|
|
||||||
nil,
|
|
||||||
newRootWorkspace,
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Success, Return existing root workspace, when root workspace returned by mockstore is notnil",
|
|
||||||
newRootWorkspace,
|
|
||||||
nil,
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Fail, Return nil, when root workspace returned by mockstore is nil, and upsert new root workspace fails",
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, eachTestacase := range testCases {
|
|
||||||
t.Run(eachTestacase.title, func(t *testing.T) {
|
|
||||||
th, tearDown := SetupTestHelper(t)
|
|
||||||
defer tearDown()
|
|
||||||
t.Log(eachTestacase.title)
|
|
||||||
th.Store.EXPECT().GetWorkspace("0").Return(eachTestacase.workSpaceToReturnBeforeUpsert, nil)
|
|
||||||
th.Store.EXPECT().UpsertWorkspaceSignupToken(gomock.Any()).DoAndReturn(
|
|
||||||
func(arg0 model.Workspace) error {
|
|
||||||
if eachTestacase.isError {
|
|
||||||
return errUpsertSignupToken
|
|
||||||
}
|
|
||||||
th.Store.EXPECT().GetWorkspace("0").Return(eachTestacase.workSpaceToReturnAfterUpsert, nil)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
rootWorkSpace, err := th.App.GetRootWorkspace()
|
|
||||||
|
|
||||||
if eachTestacase.isError {
|
|
||||||
require.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NotNil(t, rootWorkSpace.ID)
|
|
||||||
assert.NotNil(t, rootWorkSpace.SignupToken)
|
|
||||||
assert.Equal(t, "", rootWorkSpace.ModifiedBy)
|
|
||||||
assert.Equal(t, int64(0), rootWorkSpace.UpdateAt)
|
|
||||||
assert.Equal(t, "NewRootWorkspace", rootWorkSpace.Title)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, rootWorkSpace)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetWorkspace(t *testing.T) {
|
|
||||||
th, tearDown := SetupTestHelper(t)
|
|
||||||
defer tearDown()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
title string
|
|
||||||
workspaceID string
|
|
||||||
isError bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"Success, Return new root workspace, when workspace returned by mockstore is not nil",
|
|
||||||
"mock-workspace-id",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Success, Return nil, when get workspace returns an sql error",
|
|
||||||
"workspace-not-available-id",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Fail, Return nil, when get workspace by mockstore retruns an error",
|
|
||||||
"invalid-workspace-id",
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
th.Store.EXPECT().GetWorkspace("mock-workspace-id").Return(mockWorkspace, nil)
|
|
||||||
th.Store.EXPECT().GetWorkspace("invalid-workspace-id").Return(nil, errInvalidWorkspace)
|
|
||||||
th.Store.EXPECT().GetWorkspace("workspace-not-available-id").Return(nil, sql.ErrNoRows)
|
|
||||||
for _, eachTestacase := range testCases {
|
|
||||||
t.Run(eachTestacase.title, func(t *testing.T) {
|
|
||||||
t.Log(eachTestacase.title)
|
|
||||||
workSpace, err := th.App.GetWorkspace(eachTestacase.workspaceID)
|
|
||||||
|
|
||||||
if eachTestacase.isError {
|
|
||||||
require.Error(t, err)
|
|
||||||
} else if eachTestacase.workspaceID != "workspace-not-available-id" {
|
|
||||||
assert.NotNil(t, workSpace.ID)
|
|
||||||
assert.NotNil(t, workSpace.SignupToken)
|
|
||||||
assert.Equal(t, "mock-workspace-id", workSpace.ID)
|
|
||||||
assert.Equal(t, "", workSpace.ModifiedBy)
|
|
||||||
assert.Equal(t, int64(0), workSpace.UpdateAt)
|
|
||||||
assert.Equal(t, "MockWorkspace", workSpace.Title)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, workSpace)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWorkspaceOperations(t *testing.T) {
|
|
||||||
th, tearDown := SetupTestHelper(t)
|
|
||||||
defer tearDown()
|
|
||||||
|
|
||||||
th.Store.EXPECT().UpsertWorkspaceSettings(*mockWorkspace).Return(nil)
|
|
||||||
th.Store.EXPECT().UpsertWorkspaceSignupToken(*mockWorkspace).Return(nil)
|
|
||||||
th.Store.EXPECT().GetWorkspaceCount().Return(int64(10), nil)
|
|
||||||
th.Store.EXPECT().GetUserWorkspaces("mock-user-id").Return(mockUserWorkspaces, nil)
|
|
||||||
|
|
||||||
errUpsertWorkspaceSettings := th.App.UpsertWorkspaceSettings(*mockWorkspace)
|
|
||||||
assert.NoError(t, errUpsertWorkspaceSettings)
|
|
||||||
|
|
||||||
errUpsertWorkspaceSignupToken := th.App.UpsertWorkspaceSignupToken(*mockWorkspace)
|
|
||||||
assert.NoError(t, errUpsertWorkspaceSignupToken)
|
|
||||||
|
|
||||||
count, errGetWorkspaceCount := th.App.GetWorkspaceCount()
|
|
||||||
assert.NoError(t, errGetWorkspaceCount)
|
|
||||||
assert.Equal(t, int64(10), count)
|
|
||||||
|
|
||||||
userWorkSpace, errGetUserWorkSpace := th.App.GetUserWorkspaces("mock-user-id")
|
|
||||||
assert.NoError(t, errGetUserWorkSpace)
|
|
||||||
assert.NotNil(t, userWorkSpace)
|
|
||||||
}
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/services/config"
|
"github.com/mattermost/focalboard/server/services/config"
|
||||||
|
"github.com/mattermost/focalboard/server/services/permissions"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
"github.com/mattermost/focalboard/server/services/store"
|
||||||
"github.com/mattermost/focalboard/server/utils"
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -13,19 +14,20 @@ import (
|
||||||
|
|
||||||
type AuthInterface interface {
|
type AuthInterface interface {
|
||||||
GetSession(token string) (*model.Session, error)
|
GetSession(token string) (*model.Session, error)
|
||||||
IsValidReadToken(c store.Container, blockID string, readToken string) (bool, error)
|
IsValidReadToken(boardID string, readToken string) (bool, error)
|
||||||
DoesUserHaveWorkspaceAccess(userID string, workspaceID string) bool
|
DoesUserHaveTeamAccess(userID string, teamID string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth authenticates sessions.
|
// Auth authenticates sessions.
|
||||||
type Auth struct {
|
type Auth struct {
|
||||||
config *config.Configuration
|
config *config.Configuration
|
||||||
store store.Store
|
store store.Store
|
||||||
|
permissions permissions.PermissionsService
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new Auth.
|
// New returns a new Auth.
|
||||||
func New(config *config.Configuration, store store.Store) *Auth {
|
func New(config *config.Configuration, store store.Store, permissions permissions.PermissionsService) *Auth {
|
||||||
return &Auth{config: config, store: store}
|
return &Auth{config: config, store: store, permissions: permissions}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSession Get a user active session and refresh the session if needed.
|
// GetSession Get a user active session and refresh the session if needed.
|
||||||
|
@ -44,14 +46,9 @@ func (a *Auth) GetSession(token string) (*model.Session, error) {
|
||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValidReadToken validates the read token for a block.
|
// IsValidReadToken validates the read token for a board.
|
||||||
func (a *Auth) IsValidReadToken(c store.Container, blockID string, readToken string) (bool, error) {
|
func (a *Auth) IsValidReadToken(boardID string, readToken string) (bool, error) {
|
||||||
rootID, err := a.store.GetRootID(c, blockID)
|
sharing, err := a.store.GetSharing(boardID)
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sharing, err := a.store.GetSharing(c, rootID)
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
@ -59,17 +56,13 @@ func (a *Auth) IsValidReadToken(c store.Container, blockID string, readToken str
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if sharing != nil && (sharing.ID == rootID && sharing.Enabled && sharing.Token == readToken) {
|
if sharing != nil && (sharing.ID == boardID && sharing.Enabled && sharing.Token == readToken) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) DoesUserHaveWorkspaceAccess(userID string, workspaceID string) bool {
|
func (a *Auth) DoesUserHaveTeamAccess(userID string, teamID string) bool {
|
||||||
hasAccess, err := a.store.HasWorkspaceAccess(userID, workspaceID)
|
return a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam)
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return hasAccess
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/services/config"
|
"github.com/mattermost/focalboard/server/services/config"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
"github.com/mattermost/focalboard/server/services/permissions/localpermissions"
|
||||||
|
mockpermissions "github.com/mattermost/focalboard/server/services/permissions/mocks"
|
||||||
"github.com/mattermost/focalboard/server/services/store/mockstore"
|
"github.com/mattermost/focalboard/server/services/store/mockstore"
|
||||||
"github.com/mattermost/focalboard/server/utils"
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -31,14 +32,19 @@ var mockSession = &model.Session{
|
||||||
func setupTestHelper(t *testing.T) *TestHelper {
|
func setupTestHelper(t *testing.T) *TestHelper {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
defer ctrl.Finish()
|
defer ctrl.Finish()
|
||||||
|
ctrlPermissions := gomock.NewController(t)
|
||||||
|
defer ctrlPermissions.Finish()
|
||||||
cfg := config.Configuration{}
|
cfg := config.Configuration{}
|
||||||
mockStore := mockstore.NewMockStore(ctrl)
|
mockStore := mockstore.NewMockStore(ctrl)
|
||||||
newAuth := New(&cfg, mockStore)
|
mockPermissions := mockpermissions.NewMockStore(ctrlPermissions)
|
||||||
|
logger, err := mlog.NewLogger()
|
||||||
|
require.NoError(t, err)
|
||||||
|
newAuth := New(&cfg, mockStore, localpermissions.New(mockPermissions, logger))
|
||||||
|
|
||||||
// called during default template setup for every test
|
// called during default template setup for every test
|
||||||
mockStore.EXPECT().GetDefaultTemplateBlocks().AnyTimes()
|
mockStore.EXPECT().GetTemplateBoards(gomock.Any()).AnyTimes()
|
||||||
mockStore.EXPECT().RemoveDefaultTemplates(gomock.Any()).AnyTimes()
|
mockStore.EXPECT().RemoveDefaultTemplates(gomock.Any()).AnyTimes()
|
||||||
mockStore.EXPECT().InsertBlock(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
|
mockStore.EXPECT().InsertBlock(gomock.Any(), gomock.Any()).AnyTimes()
|
||||||
|
|
||||||
return &TestHelper{
|
return &TestHelper{
|
||||||
Auth: newAuth,
|
Auth: newAuth,
|
||||||
|
@ -83,55 +89,57 @@ func TestGetSession(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsValidReadToken(t *testing.T) {
|
func TestIsValidReadToken(t *testing.T) {
|
||||||
th := setupTestHelper(t)
|
// ToDo: reimplement
|
||||||
|
|
||||||
validBlockID := "testBlockID"
|
// th := setupTestHelper(t)
|
||||||
mockContainer := store.Container{
|
|
||||||
WorkspaceID: "testWorkspaceID",
|
|
||||||
}
|
|
||||||
validReadToken := "testReadToken"
|
|
||||||
mockSharing := model.Sharing{
|
|
||||||
ID: "testRootID",
|
|
||||||
Enabled: true,
|
|
||||||
Token: validReadToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
testcases := []struct {
|
// validBlockID := "testBlockID"
|
||||||
title string
|
// mockContainer := store.Container{
|
||||||
container store.Container
|
// TeamID: "testTeamID",
|
||||||
blockID string
|
// }
|
||||||
readToken string
|
// validReadToken := "testReadToken"
|
||||||
isError bool
|
// mockSharing := model.Sharing{
|
||||||
isSuccess bool
|
// ID: "testRootID",
|
||||||
}{
|
// Enabled: true,
|
||||||
{"fail, error GetRootID", mockContainer, "badBlock", "", true, false},
|
// Token: validReadToken,
|
||||||
{"fail, rootID not found", mockContainer, "goodBlockID", "", false, false},
|
// }
|
||||||
{"fail, sharing throws error", mockContainer, "goodBlockID2", "", true, false},
|
|
||||||
{"fail, bad readToken", mockContainer, validBlockID, "invalidReadToken", false, false},
|
|
||||||
{"success", mockContainer, validBlockID, validReadToken, false, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
th.Store.EXPECT().GetRootID(gomock.Eq(mockContainer), "badBlock").Return("", errors.New("invalid block"))
|
// testcases := []struct {
|
||||||
th.Store.EXPECT().GetRootID(gomock.Eq(mockContainer), "goodBlockID").Return("rootNotFound", nil)
|
// title string
|
||||||
th.Store.EXPECT().GetRootID(gomock.Eq(mockContainer), "goodBlockID2").Return("rootError", nil)
|
// container store.Container
|
||||||
th.Store.EXPECT().GetRootID(gomock.Eq(mockContainer), validBlockID).Return("testRootID", nil).Times(2)
|
// blockID string
|
||||||
th.Store.EXPECT().GetSharing(gomock.Eq(mockContainer), "rootNotFound").Return(nil, sql.ErrNoRows)
|
// readToken string
|
||||||
th.Store.EXPECT().GetSharing(gomock.Eq(mockContainer), "rootError").Return(nil, errors.New("another error"))
|
// isError bool
|
||||||
th.Store.EXPECT().GetSharing(gomock.Eq(mockContainer), "testRootID").Return(&mockSharing, nil).Times(2)
|
// isSuccess bool
|
||||||
|
// }{
|
||||||
|
// {"fail, error GetRootID", mockContainer, "badBlock", "", true, false},
|
||||||
|
// {"fail, rootID not found", mockContainer, "goodBlockID", "", false, false},
|
||||||
|
// {"fail, sharing throws error", mockContainer, "goodBlockID2", "", true, false},
|
||||||
|
// {"fail, bad readToken", mockContainer, validBlockID, "invalidReadToken", false, false},
|
||||||
|
// {"success", mockContainer, validBlockID, validReadToken, false, true},
|
||||||
|
// }
|
||||||
|
|
||||||
for _, test := range testcases {
|
// th.Store.EXPECT().GetRootID(gomock.Eq(mockContainer), "badBlock").Return("", errors.New("invalid block"))
|
||||||
t.Run(test.title, func(t *testing.T) {
|
// th.Store.EXPECT().GetRootID(gomock.Eq(mockContainer), "goodBlockID").Return("rootNotFound", nil)
|
||||||
success, err := th.Auth.IsValidReadToken(test.container, test.blockID, test.readToken)
|
// th.Store.EXPECT().GetRootID(gomock.Eq(mockContainer), "goodBlockID2").Return("rootError", nil)
|
||||||
if test.isError {
|
// th.Store.EXPECT().GetRootID(gomock.Eq(mockContainer), validBlockID).Return("testRootID", nil).Times(2)
|
||||||
require.Error(t, err)
|
// th.Store.EXPECT().GetSharing(gomock.Eq(mockContainer), "rootNotFound").Return(nil, sql.ErrNoRows)
|
||||||
} else {
|
// th.Store.EXPECT().GetSharing(gomock.Eq(mockContainer), "rootError").Return(nil, errors.New("another error"))
|
||||||
require.NoError(t, err)
|
// th.Store.EXPECT().GetSharing(gomock.Eq(mockContainer), "testRootID").Return(&mockSharing, nil).Times(2)
|
||||||
}
|
|
||||||
if test.isSuccess {
|
// for _, test := range testcases {
|
||||||
require.True(t, success)
|
// t.Run(test.title, func(t *testing.T) {
|
||||||
} else {
|
// success, err := th.Auth.IsValidReadToken(test.container, test.blockID, test.readToken)
|
||||||
require.False(t, success)
|
// if test.isError {
|
||||||
}
|
// require.Error(t, err)
|
||||||
})
|
// } else {
|
||||||
}
|
// require.NoError(t, err)
|
||||||
|
// }
|
||||||
|
// if test.isSuccess {
|
||||||
|
// require.True(t, success)
|
||||||
|
// } else {
|
||||||
|
// require.False(t, success)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
model "github.com/mattermost/focalboard/server/model"
|
model "github.com/mattermost/focalboard/server/model"
|
||||||
store "github.com/mattermost/focalboard/server/services/store"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockAuthInterface is a mock of AuthInterface interface.
|
// MockAuthInterface is a mock of AuthInterface interface.
|
||||||
|
@ -35,18 +34,18 @@ func (m *MockAuthInterface) EXPECT() *MockAuthInterfaceMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoesUserHaveWorkspaceAccess mocks base method.
|
// DoesUserHaveTeamAccess mocks base method.
|
||||||
func (m *MockAuthInterface) DoesUserHaveWorkspaceAccess(arg0, arg1 string) bool {
|
func (m *MockAuthInterface) DoesUserHaveTeamAccess(arg0, arg1 string) bool {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "DoesUserHaveWorkspaceAccess", arg0, arg1)
|
ret := m.ctrl.Call(m, "DoesUserHaveTeamAccess", arg0, arg1)
|
||||||
ret0, _ := ret[0].(bool)
|
ret0, _ := ret[0].(bool)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoesUserHaveWorkspaceAccess indicates an expected call of DoesUserHaveWorkspaceAccess.
|
// DoesUserHaveTeamAccess indicates an expected call of DoesUserHaveTeamAccess.
|
||||||
func (mr *MockAuthInterfaceMockRecorder) DoesUserHaveWorkspaceAccess(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockAuthInterfaceMockRecorder) DoesUserHaveTeamAccess(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DoesUserHaveWorkspaceAccess", reflect.TypeOf((*MockAuthInterface)(nil).DoesUserHaveWorkspaceAccess), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DoesUserHaveTeamAccess", reflect.TypeOf((*MockAuthInterface)(nil).DoesUserHaveTeamAccess), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSession mocks base method.
|
// GetSession mocks base method.
|
||||||
|
@ -65,16 +64,16 @@ func (mr *MockAuthInterfaceMockRecorder) GetSession(arg0 interface{}) *gomock.Ca
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValidReadToken mocks base method.
|
// IsValidReadToken mocks base method.
|
||||||
func (m *MockAuthInterface) IsValidReadToken(arg0 store.Container, arg1, arg2 string) (bool, error) {
|
func (m *MockAuthInterface) IsValidReadToken(arg0, arg1 string) (bool, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "IsValidReadToken", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "IsValidReadToken", arg0, arg1)
|
||||||
ret0, _ := ret[0].(bool)
|
ret0, _ := ret[0].(bool)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValidReadToken indicates an expected call of IsValidReadToken.
|
// IsValidReadToken indicates an expected call of IsValidReadToken.
|
||||||
func (mr *MockAuthInterfaceMockRecorder) IsValidReadToken(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockAuthInterfaceMockRecorder) IsValidReadToken(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsValidReadToken", reflect.TypeOf((*MockAuthInterface)(nil).IsValidReadToken), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsValidReadToken", reflect.TypeOf((*MockAuthInterface)(nil).IsValidReadToken), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,8 +101,8 @@ func (c *Client) DoAPIPut(url, data string) (*http.Response, error) {
|
||||||
return c.DoAPIRequest(http.MethodPut, c.APIURL+url, data, "")
|
return c.DoAPIRequest(http.MethodPut, c.APIURL+url, data, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) DoAPIDelete(url string) (*http.Response, error) {
|
func (c *Client) DoAPIDelete(url string, data string) (*http.Response, error) {
|
||||||
return c.DoAPIRequest(http.MethodDelete, c.APIURL+url, "", "")
|
return c.DoAPIRequest(http.MethodDelete, c.APIURL+url, data, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) DoAPIRequest(method, url, data, etag string) (*http.Response, error) {
|
func (c *Client) DoAPIRequest(method, url, data, etag string) (*http.Response, error) {
|
||||||
|
@ -152,20 +152,50 @@ func (c *Client) doAPIRequestReader(method, url string, data io.Reader, _ /* eta
|
||||||
return rp, nil
|
return rp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetBlocksRoute() string {
|
func (c *Client) GetTeamRoute(teamID string) string {
|
||||||
return "/workspaces/0/blocks"
|
return fmt.Sprintf("%s/%s", c.GetTeamsRoute(), teamID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetBlockRoute(id string) string {
|
func (c *Client) GetTeamsRoute() string {
|
||||||
return fmt.Sprintf("%s/%s", c.GetBlocksRoute(), id)
|
return "/teams"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetSubtreeRoute(id string) string {
|
func (c *Client) GetBlockRoute(boardID, blockID string) string {
|
||||||
return fmt.Sprintf("%s/subtree", c.GetBlockRoute(id))
|
return fmt.Sprintf("%s/%s", c.GetBlocksRoute(boardID), blockID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetBlocks() ([]model.Block, *Response) {
|
func (c *Client) GetSubtreeRoute(boardID, blockID string) string {
|
||||||
r, err := c.DoAPIGet(c.GetBlocksRoute(), "")
|
return fmt.Sprintf("%s/subtree", c.GetBlockRoute(boardID, blockID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetBoardsRoute() string {
|
||||||
|
return "/boards"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetBoardRoute(boardID string) string {
|
||||||
|
return fmt.Sprintf("%s/%s", c.GetBoardsRoute(), boardID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetBlocksRoute(boardID string) string {
|
||||||
|
return fmt.Sprintf("%s/blocks", c.GetBoardRoute(boardID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetBoardsAndBlocksRoute() string {
|
||||||
|
return "/boards-and-blocks"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetTeam(teamID string) (*model.Team, *Response) {
|
||||||
|
r, err := c.DoAPIGet(c.GetTeamRoute(teamID), "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return model.TeamFromJSON(r.Body), BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetBlocksForBoard(boardID string) ([]model.Block, *Response) {
|
||||||
|
r, err := c.DoAPIGet(c.GetBlocksRoute(boardID), "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
@ -174,8 +204,8 @@ func (c *Client) GetBlocks() ([]model.Block, *Response) {
|
||||||
return model.BlocksFromJSON(r.Body), BuildResponse(r)
|
return model.BlocksFromJSON(r.Body), BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) PatchBlock(blockID string, blockPatch *model.BlockPatch) (bool, *Response) {
|
func (c *Client) PatchBlock(boardID, blockID string, blockPatch *model.BlockPatch) (bool, *Response) {
|
||||||
r, err := c.DoAPIPatch(c.GetBlockRoute(blockID), toJSON(blockPatch))
|
r, err := c.DoAPIPatch(c.GetBlockRoute(boardID, blockID), toJSON(blockPatch))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, BuildErrorResponse(r, err)
|
return false, BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
@ -184,8 +214,46 @@ func (c *Client) PatchBlock(blockID string, blockPatch *model.BlockPatch) (bool,
|
||||||
return true, BuildResponse(r)
|
return true, BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) InsertBlocks(blocks []model.Block) ([]model.Block, *Response) {
|
func (c *Client) DuplicateBoard(boardID string, asTemplate bool, teamID string) (bool, *Response) {
|
||||||
r, err := c.DoAPIPost(c.GetBlocksRoute(), toJSON(blocks))
|
queryParams := "?asTemplate=false&"
|
||||||
|
if asTemplate {
|
||||||
|
queryParams = "?asTemplate=true"
|
||||||
|
}
|
||||||
|
r, err := c.DoAPIPost(c.GetBoardRoute(boardID)+"/duplicate"+queryParams, "")
|
||||||
|
if err != nil {
|
||||||
|
return false, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return true, BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DuplicateBlock(boardID, blockID string, asTemplate bool) (bool, *Response) {
|
||||||
|
queryParams := "?asTemplate=false"
|
||||||
|
if asTemplate {
|
||||||
|
queryParams = "?asTemplate=true"
|
||||||
|
}
|
||||||
|
r, err := c.DoAPIPost(c.GetBlockRoute(boardID, blockID)+"/duplicate"+queryParams, "")
|
||||||
|
if err != nil {
|
||||||
|
return false, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return true, BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UndeleteBlock(boardID, blockID string) (bool, *Response) {
|
||||||
|
r, err := c.DoAPIPost(c.GetBlockRoute(boardID, blockID)+"/undelete", "")
|
||||||
|
if err != nil {
|
||||||
|
return false, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return true, BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) InsertBlocks(boardID string, blocks []model.Block) ([]model.Block, *Response) {
|
||||||
|
r, err := c.DoAPIPost(c.GetBlocksRoute(boardID), toJSON(blocks))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
@ -194,8 +262,8 @@ func (c *Client) InsertBlocks(blocks []model.Block) ([]model.Block, *Response) {
|
||||||
return model.BlocksFromJSON(r.Body), BuildResponse(r)
|
return model.BlocksFromJSON(r.Body), BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) DeleteBlock(blockID string) (bool, *Response) {
|
func (c *Client) DeleteBlock(boardID, blockID string) (bool, *Response) {
|
||||||
r, err := c.DoAPIDelete(c.GetBlockRoute(blockID))
|
r, err := c.DoAPIDelete(c.GetBlockRoute(boardID, blockID), "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, BuildErrorResponse(r, err)
|
return false, BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
@ -204,18 +272,8 @@ func (c *Client) DeleteBlock(blockID string) (bool, *Response) {
|
||||||
return true, BuildResponse(r)
|
return true, BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) UndeleteBlock(blockID string) (bool, *Response) {
|
func (c *Client) GetSubtree(boardID, blockID string) ([]model.Block, *Response) {
|
||||||
r, err := c.DoAPIPost(c.GetBlockRoute(blockID)+"/undelete", "")
|
r, err := c.DoAPIGet(c.GetSubtreeRoute(boardID, blockID), "")
|
||||||
if err != nil {
|
|
||||||
return false, BuildErrorResponse(r, err)
|
|
||||||
}
|
|
||||||
defer closeBody(r)
|
|
||||||
|
|
||||||
return true, BuildResponse(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) GetSubtree(blockID string) ([]model.Block, *Response) {
|
|
||||||
r, err := c.DoAPIGet(c.GetSubtreeRoute(blockID), "")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
@ -224,14 +282,45 @@ func (c *Client) GetSubtree(blockID string) ([]model.Block, *Response) {
|
||||||
return model.BlocksFromJSON(r.Body), BuildResponse(r)
|
return model.BlocksFromJSON(r.Body), BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Boards and blocks.
|
||||||
|
func (c *Client) CreateBoardsAndBlocks(bab *model.BoardsAndBlocks) (*model.BoardsAndBlocks, *Response) {
|
||||||
|
r, err := c.DoAPIPost(c.GetBoardsAndBlocksRoute(), toJSON(bab))
|
||||||
|
if err != nil {
|
||||||
|
return nil, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return model.BoardsAndBlocksFromJSON(r.Body), BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PatchBoardsAndBlocks(pbab *model.PatchBoardsAndBlocks) (*model.BoardsAndBlocks, *Response) {
|
||||||
|
r, err := c.DoAPIPatch(c.GetBoardsAndBlocksRoute(), toJSON(pbab))
|
||||||
|
if err != nil {
|
||||||
|
return nil, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return model.BoardsAndBlocksFromJSON(r.Body), BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeleteBoardsAndBlocks(dbab *model.DeleteBoardsAndBlocks) (bool, *Response) {
|
||||||
|
r, err := c.DoAPIDelete(c.GetBoardsAndBlocksRoute(), toJSON(dbab))
|
||||||
|
if err != nil {
|
||||||
|
return false, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return true, BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
// Sharing
|
// Sharing
|
||||||
|
|
||||||
func (c *Client) GetSharingRoute(rootID string) string {
|
func (c *Client) GetSharingRoute(boardID string) string {
|
||||||
return fmt.Sprintf("/workspaces/0/sharing/%s", rootID)
|
return fmt.Sprintf("%s/sharing", c.GetBoardRoute(boardID))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetSharing(rootID string) (*model.Sharing, *Response) {
|
func (c *Client) GetSharing(boardID string) (*model.Sharing, *Response) {
|
||||||
r, err := c.DoAPIGet(c.GetSharingRoute(rootID), "")
|
r, err := c.DoAPIGet(c.GetSharingRoute(boardID), "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
@ -241,7 +330,7 @@ func (c *Client) GetSharing(rootID string) (*model.Sharing, *Response) {
|
||||||
return &sharing, BuildResponse(r)
|
return &sharing, BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) PostSharing(sharing model.Sharing) (bool, *Response) {
|
func (c *Client) PostSharing(sharing *model.Sharing) (bool, *Response) {
|
||||||
r, err := c.DoAPIPost(c.GetSharingRoute(sharing.ID), toJSON(sharing))
|
r, err := c.DoAPIPost(c.GetSharingRoute(sharing.ID), toJSON(sharing))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, BuildErrorResponse(r, err)
|
return false, BuildErrorResponse(r, err)
|
||||||
|
@ -338,11 +427,116 @@ func (c *Client) UserChangePassword(id string, data *api.ChangePasswordRequest)
|
||||||
return true, BuildResponse(r)
|
return true, BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetWorkspaceUploadFileRoute(workspaceID, rootID string) string {
|
func (c *Client) CreateBoard(board *model.Board) (*model.Board, *Response) {
|
||||||
return fmt.Sprintf("/workspaces/%s/%s/files", workspaceID, rootID)
|
r, err := c.DoAPIPost(c.GetBoardsRoute(), toJSON(board))
|
||||||
|
if err != nil {
|
||||||
|
return nil, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return model.BoardFromJSON(r.Body), BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) WorkspaceUploadFile(workspaceID, rootID string, data io.Reader) (*api.FileUploadResponse, *Response) {
|
func (c *Client) PatchBoard(boardID string, patch *model.BoardPatch) (*model.Board, *Response) {
|
||||||
|
r, err := c.DoAPIPatch(c.GetBoardRoute(boardID), toJSON(patch))
|
||||||
|
if err != nil {
|
||||||
|
return nil, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return model.BoardFromJSON(r.Body), BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeleteBoard(boardID string) (bool, *Response) {
|
||||||
|
r, err := c.DoAPIDelete(c.GetBoardRoute(boardID), "")
|
||||||
|
if err != nil {
|
||||||
|
return false, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return true, BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetBoard(boardID, readToken string) (*model.Board, *Response) {
|
||||||
|
url := c.GetBoardRoute(boardID)
|
||||||
|
if readToken != "" {
|
||||||
|
url += fmt.Sprintf("?read_token=%s", readToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := c.DoAPIGet(url, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return model.BoardFromJSON(r.Body), BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetBoardsForTeam(teamID string) ([]*model.Board, *Response) {
|
||||||
|
r, err := c.DoAPIGet(c.GetTeamRoute(teamID)+"/boards", "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return model.BoardsFromJSON(r.Body), BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SearchBoardsForTeam(teamID, term string) ([]*model.Board, *Response) {
|
||||||
|
r, err := c.DoAPIGet(c.GetTeamRoute(teamID)+"/boards/search?q="+term, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return model.BoardsFromJSON(r.Body), BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetMembersForBoard(boardID string) ([]*model.BoardMember, *Response) {
|
||||||
|
r, err := c.DoAPIGet(c.GetBoardRoute(boardID)+"/members", "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return model.BoardMembersFromJSON(r.Body), BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AddMemberToBoard(member *model.BoardMember) (*model.BoardMember, *Response) {
|
||||||
|
r, err := c.DoAPIPost(c.GetBoardRoute(member.BoardID)+"/members", toJSON(member))
|
||||||
|
if err != nil {
|
||||||
|
return nil, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return model.BoardMemberFromJSON(r.Body), BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UpdateBoardMember(member *model.BoardMember) (*model.BoardMember, *Response) {
|
||||||
|
r, err := c.DoAPIPut(c.GetBoardRoute(member.BoardID)+"/members/"+member.UserID, toJSON(member))
|
||||||
|
if err != nil {
|
||||||
|
return nil, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return model.BoardMemberFromJSON(r.Body), BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeleteBoardMember(member *model.BoardMember) (bool, *Response) {
|
||||||
|
r, err := c.DoAPIDelete(c.GetBoardRoute(member.BoardID)+"/members/"+member.UserID, "")
|
||||||
|
if err != nil {
|
||||||
|
return false, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
return true, BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetTeamUploadFileRoute(teamID, boardID string) string {
|
||||||
|
return fmt.Sprintf("%s/%s/files", c.GetTeamRoute(teamID), boardID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) TeamUploadFile(teamID, boardID string, data io.Reader) (*api.FileUploadResponse, *Response) {
|
||||||
body := &bytes.Buffer{}
|
body := &bytes.Buffer{}
|
||||||
writer := multipart.NewWriter(body)
|
writer := multipart.NewWriter(body)
|
||||||
part, err := writer.CreateFormFile(api.UploadFormFileKey, "file")
|
part, err := writer.CreateFormFile(api.UploadFormFileKey, "file")
|
||||||
|
@ -358,7 +552,7 @@ func (c *Client) WorkspaceUploadFile(workspaceID, rootID string, data io.Reader)
|
||||||
r.Header.Add("Content-Type", writer.FormDataContentType())
|
r.Header.Add("Content-Type", writer.FormDataContentType())
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := c.doAPIRequestReader(http.MethodPost, c.APIURL+c.GetWorkspaceUploadFileRoute(workspaceID, rootID), body, "", opt)
|
r, err := c.doAPIRequestReader(http.MethodPost, c.APIURL+c.GetTeamUploadFileRoute(teamID, boardID), body, "", opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
@ -372,12 +566,12 @@ func (c *Client) WorkspaceUploadFile(workspaceID, rootID string, data io.Reader)
|
||||||
return fileUploadResponse, BuildResponse(r)
|
return fileUploadResponse, BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetSubscriptionsRoute(workspaceID string) string {
|
func (c *Client) GetSubscriptionsRoute() string {
|
||||||
return fmt.Sprintf("/workspaces/%s/subscriptions", workspaceID)
|
return "/subscriptions"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) CreateSubscription(workspaceID string, sub *model.Subscription) (*model.Subscription, *Response) {
|
func (c *Client) CreateSubscription(sub *model.Subscription) (*model.Subscription, *Response) {
|
||||||
r, err := c.DoAPIPost(c.GetSubscriptionsRoute(workspaceID), toJSON(&sub))
|
r, err := c.DoAPIPost(c.GetSubscriptionsRoute(), toJSON(&sub))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
@ -390,10 +584,10 @@ func (c *Client) CreateSubscription(workspaceID string, sub *model.Subscription)
|
||||||
return subNew, BuildResponse(r)
|
return subNew, BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) DeleteSubscription(workspaceID string, blockID string, subscriberID string) *Response {
|
func (c *Client) DeleteSubscription(blockID string, subscriberID string) *Response {
|
||||||
url := fmt.Sprintf("%s/%s/%s", c.GetSubscriptionsRoute(workspaceID), blockID, subscriberID)
|
url := fmt.Sprintf("%s/%s/%s", c.GetSubscriptionsRoute(), blockID, subscriberID)
|
||||||
|
|
||||||
r, err := c.DoAPIDelete(url)
|
r, err := c.DoAPIDelete(url, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return BuildErrorResponse(r, err)
|
return BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
@ -402,8 +596,8 @@ func (c *Client) DeleteSubscription(workspaceID string, blockID string, subscrib
|
||||||
return BuildResponse(r)
|
return BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetSubscriptions(workspaceID string, subscriberID string) ([]*model.Subscription, *Response) {
|
func (c *Client) GetSubscriptions(subscriberID string) ([]*model.Subscription, *Response) {
|
||||||
url := fmt.Sprintf("%s/%s", c.GetSubscriptionsRoute(workspaceID), subscriberID)
|
url := fmt.Sprintf("%s/%s", c.GetSubscriptionsRoute(), subscriberID)
|
||||||
|
|
||||||
r, err := c.DoAPIGet(url, "")
|
r, err := c.DoAPIGet(url, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -5,7 +5,6 @@ go 1.16
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/squirrel v1.5.0
|
github.com/Masterminds/squirrel v1.5.0
|
||||||
github.com/go-sql-driver/mysql v1.6.0
|
github.com/go-sql-driver/mysql v1.6.0
|
||||||
github.com/golang-migrate/migrate/v4 v4.14.1
|
|
||||||
github.com/golang/mock v1.5.0
|
github.com/golang/mock v1.5.0
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
|
@ -14,6 +13,7 @@ require (
|
||||||
github.com/magiconair/properties v1.8.5 // indirect
|
github.com/magiconair/properties v1.8.5 // indirect
|
||||||
github.com/mattermost/mattermost-plugin-api v0.0.21
|
github.com/mattermost/mattermost-plugin-api v0.0.21
|
||||||
github.com/mattermost/mattermost-server/v6 v6.0.0-20210913141218-bb659d03fde0
|
github.com/mattermost/mattermost-server/v6 v6.0.0-20210913141218-bb659d03fde0
|
||||||
|
github.com/mattermost/morph v0.0.0-20220222074146-cff3f12ff131
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||||
github.com/oklog/run v1.1.0
|
github.com/oklog/run v1.1.0
|
||||||
|
|
158
server/go.sum
158
server/go.sum
|
@ -47,7 +47,6 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy
|
||||||
git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||||
github.com/Azure/azure-sdk-for-go v26.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
github.com/Azure/azure-sdk-for-go v26.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||||
github.com/Azure/go-autorest v11.5.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
github.com/Azure/go-autorest v11.5.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||||
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
@ -68,7 +67,6 @@ github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0
|
||||||
github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8=
|
github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8=
|
||||||
github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
||||||
github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
|
github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
|
||||||
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA=
|
|
||||||
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/PaulARoy/azurestoragecache v0.0.0-20170906084534-3c249a3ba788/go.mod h1:lY1dZd8HBzJ10eqKERHn3CU59tfhzcAVb2c0ZhIWSOk=
|
github.com/PaulARoy/azurestoragecache v0.0.0-20170906084534-3c249a3ba788/go.mod h1:lY1dZd8HBzJ10eqKERHn3CU59tfhzcAVb2c0ZhIWSOk=
|
||||||
|
@ -161,7 +159,6 @@ github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE
|
||||||
github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=
|
github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=
|
||||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||||
github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||||
github.com/containerd/containerd v1.4.1 h1:pASeJT3R3YyVn+94qEPk0SnU1OQ20Jd/T+SPKy9xehY=
|
|
||||||
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
|
@ -185,6 +182,7 @@ github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ1
|
||||||
github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
|
github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
|
||||||
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
|
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
|
||||||
github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
|
github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
|
||||||
|
github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpTVGlwA=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -195,19 +193,14 @@ github.com/dgryski/dgoogauth v0.0.0-20190221195224-5a805980a5f3/go.mod h1:hEfFau
|
||||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
github.com/dhui/dktest v0.3.3 h1:DBuH/9GFaWbDRa42qsut/hbQu+srAQ0rPWnUoiGX7CA=
|
|
||||||
github.com/dhui/dktest v0.3.3/go.mod h1:EML9sP4sqJELHn4jV7B0TY8oF6077nk83/tz7M56jcQ=
|
github.com/dhui/dktest v0.3.3/go.mod h1:EML9sP4sqJELHn4jV7B0TY8oF6077nk83/tz7M56jcQ=
|
||||||
github.com/die-net/lrucache v0.0.0-20181227122439-19a39ef22a11/go.mod h1:ew0MSjCVDdtGMjF3kzLK9hwdgF5mOE8SbYVF3Rc7mkU=
|
github.com/die-net/lrucache v0.0.0-20181227122439-19a39ef22a11/go.mod h1:ew0MSjCVDdtGMjF3kzLK9hwdgF5mOE8SbYVF3Rc7mkU=
|
||||||
github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
|
github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible h1:iWPIG7pWIsCwT6ZtHnTUpoVMnete7O/pzd9HFE3+tn8=
|
|
||||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
|
||||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
||||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||||
|
@ -233,6 +226,7 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqL
|
||||||
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
|
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
|
||||||
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
|
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
|
||||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
|
github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
|
||||||
|
@ -290,9 +284,7 @@ github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhD
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
|
||||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||||
github.com/golang-migrate/migrate/v4 v4.14.1 h1:qmRd/rNGjM1r3Ve5gHd5ZplytrD02UcItYNxJ3iUHHE=
|
|
||||||
github.com/golang-migrate/migrate/v4 v4.14.1/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0=
|
github.com/golang-migrate/migrate/v4 v4.14.1/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0=
|
||||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
|
@ -346,6 +338,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||||
|
@ -366,8 +359,9 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
|
||||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
|
||||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
@ -403,7 +397,6 @@ github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1p
|
||||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
|
@ -416,7 +409,6 @@ github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP
|
||||||
github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4=
|
github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4=
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
github.com/hashicorp/go-plugin v1.4.2 h1:yFvG3ufXXpqiMiZx9HLcaK3XbIqQ1WJFR/F1a2CuVw0=
|
github.com/hashicorp/go-plugin v1.4.2 h1:yFvG3ufXXpqiMiZx9HLcaK3XbIqQ1WJFR/F1a2CuVw0=
|
||||||
github.com/hashicorp/go-plugin v1.4.2/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
|
github.com/hashicorp/go-plugin v1.4.2/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
|
||||||
|
@ -513,6 +505,8 @@ github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYb
|
||||||
github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
|
github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
|
||||||
github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
|
github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
|
||||||
github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
|
github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
@ -564,6 +558,7 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
||||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||||
|
@ -592,6 +587,8 @@ github.com/mattermost/mattermost-plugin-api v0.0.21/go.mod h1:qz19Y+5HLbjtzY2RZ6
|
||||||
github.com/mattermost/mattermost-server/v6 v6.0.0-20210901153517-42e75fad4dae/go.mod h1:kmxJuVgpX13Th+e5L1ZsBs4aq+ETmmDg9joo5r4cIw8=
|
github.com/mattermost/mattermost-server/v6 v6.0.0-20210901153517-42e75fad4dae/go.mod h1:kmxJuVgpX13Th+e5L1ZsBs4aq+ETmmDg9joo5r4cIw8=
|
||||||
github.com/mattermost/mattermost-server/v6 v6.0.0-20210913141218-bb659d03fde0 h1:A7TCgCGF9JmAHBQv9qGm5SfPYTAl8dOXy/u6lCSV8ow=
|
github.com/mattermost/mattermost-server/v6 v6.0.0-20210913141218-bb659d03fde0 h1:A7TCgCGF9JmAHBQv9qGm5SfPYTAl8dOXy/u6lCSV8ow=
|
||||||
github.com/mattermost/mattermost-server/v6 v6.0.0-20210913141218-bb659d03fde0/go.mod h1:TUSk5lYJmwfTKTJLXR0eAsjJNlKkWzS5aGZegXG0J08=
|
github.com/mattermost/mattermost-server/v6 v6.0.0-20210913141218-bb659d03fde0/go.mod h1:TUSk5lYJmwfTKTJLXR0eAsjJNlKkWzS5aGZegXG0J08=
|
||||||
|
github.com/mattermost/morph v0.0.0-20220222074146-cff3f12ff131 h1:agJMxBP8LV0nyV90PZ/BHmmjNyvzTWqR20wLwiXHx14=
|
||||||
|
github.com/mattermost/morph v0.0.0-20220222074146-cff3f12ff131/go.mod h1:jxM3g1bx+k2Thz7jofcHguBS8TZn5Pc+o5MGmORObhw=
|
||||||
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs=
|
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||||
|
@ -616,6 +613,7 @@ github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh
|
||||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||||
|
@ -658,7 +656,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|
||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||||
|
@ -705,9 +702,7 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
||||||
github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
|
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
|
||||||
github.com/oov/psd v0.0.0-20210618170533-9fb823ddb631/go.mod h1:GHI1bnmAcbp96z6LNfBJvtrjxhaXGkbsk967utPlvL8=
|
github.com/oov/psd v0.0.0-20210618170533-9fb823ddb631/go.mod h1:GHI1bnmAcbp96z6LNfBJvtrjxhaXGkbsk967utPlvL8=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
|
||||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
|
@ -782,6 +777,7 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
github.com/reflog/dateconstraints v0.2.1/go.mod h1:Ax8AxTBcJc3E/oVS2hd2j7RDM/5MDtuPwuR7lIHtPLo=
|
github.com/reflog/dateconstraints v0.2.1/go.mod h1:Ax8AxTBcJc3E/oVS2hd2j7RDM/5MDtuPwuR7lIHtPLo=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
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-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
@ -1063,6 +1059,7 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
@ -1203,6 +1200,7 @@ golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -1213,8 +1211,10 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
|
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw=
|
||||||
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -1287,9 +1287,11 @@ golang.org/x/tools v0.0.0-20200817023811-d00afeaade8f/go.mod h1:njjCfa9FT2d7l9Bc
|
||||||
golang.org/x/tools v0.0.0-20200818005847-188abfa75333/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200818005847-188abfa75333/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
golang.org/x/tools v0.1.4 h1:cVngSRcfgyZCzys3KYOpCFa+4dqX/Oub9tAq00ttGVs=
|
||||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@ -1454,17 +1456,139 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
|
||||||
|
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||||
modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg=
|
modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg=
|
||||||
|
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U=
|
||||||
|
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||||
|
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
|
||||||
|
modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
|
||||||
|
modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
|
||||||
|
modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
|
||||||
|
modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw=
|
||||||
|
modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ=
|
||||||
|
modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c=
|
||||||
|
modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo=
|
||||||
|
modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg=
|
||||||
|
modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I=
|
||||||
|
modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs=
|
||||||
|
modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8=
|
||||||
|
modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE=
|
||||||
|
modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk=
|
||||||
|
modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w=
|
||||||
|
modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE=
|
||||||
|
modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8=
|
||||||
|
modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc=
|
||||||
|
modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU=
|
||||||
|
modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE=
|
||||||
|
modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk=
|
||||||
|
modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI=
|
||||||
|
modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE=
|
||||||
|
modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg=
|
||||||
|
modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74=
|
||||||
|
modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU=
|
||||||
|
modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
|
||||||
|
modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
|
||||||
|
modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
|
||||||
|
modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
|
||||||
|
modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
|
||||||
|
modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
|
||||||
|
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
|
||||||
|
modernc.org/ccgo/v3 v3.12.84/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
|
||||||
|
modernc.org/ccgo/v3 v3.12.86/go.mod h1:dN7S26DLTgVSni1PVA3KxxHTcykyDurf3OgUzNqTSrU=
|
||||||
|
modernc.org/ccgo/v3 v3.12.88/go.mod h1:0MFzUHIuSIthpVZyMWiFYMwjiFnhrN5MkvBrUwON+ZM=
|
||||||
|
modernc.org/ccgo/v3 v3.12.90/go.mod h1:obhSc3CdivCRpYZmrvO88TXlW0NvoSVvdh/ccRjJYko=
|
||||||
|
modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzqHA=
|
||||||
|
modernc.org/ccgo/v3 v3.12.95 h1:Ym2JG2G3P4IyZqjTTojHTl7qO0RysXeGSYPSoKPSBxc=
|
||||||
|
modernc.org/ccgo/v3 v3.12.95/go.mod h1:ZcLyvtocXYi8uF+9Ebm3G8EF8HNY5hGomBqthDp4eC8=
|
||||||
|
modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA=
|
||||||
|
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||||
modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8=
|
modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8=
|
||||||
modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw=
|
modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw=
|
||||||
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
|
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
|
||||||
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
|
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
|
||||||
|
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||||
|
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||||
modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM=
|
modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM=
|
||||||
|
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||||
|
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
|
||||||
|
modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
|
||||||
|
modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
|
||||||
|
modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
|
||||||
|
modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE=
|
||||||
|
modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso=
|
||||||
|
modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8=
|
||||||
|
modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8=
|
||||||
|
modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I=
|
||||||
|
modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk=
|
||||||
|
modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY=
|
||||||
|
modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE=
|
||||||
|
modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg=
|
||||||
|
modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM=
|
||||||
|
modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg=
|
||||||
|
modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo=
|
||||||
|
modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8=
|
||||||
|
modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ=
|
||||||
|
modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA=
|
||||||
|
modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM=
|
||||||
|
modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg=
|
||||||
|
modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE=
|
||||||
|
modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM=
|
||||||
|
modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU=
|
||||||
|
modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
|
||||||
|
modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
|
||||||
|
modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
|
||||||
|
modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
|
||||||
|
modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
|
||||||
|
modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
|
||||||
|
modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
|
||||||
|
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
|
||||||
|
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
|
||||||
|
modernc.org/libc v1.11.88/go.mod h1:h3oIVe8dxmTcchcFuCcJ4nAWaoiwzKCdv82MM0oiIdQ=
|
||||||
|
modernc.org/libc v1.11.90/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
|
||||||
|
modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
|
||||||
|
modernc.org/libc v1.11.99/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
|
||||||
|
modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
|
||||||
|
modernc.org/libc v1.11.104 h1:gxoa5b3HPo7OzD4tKZjgnwXk/w//u1oovvjSMP3Q96Q=
|
||||||
|
modernc.org/libc v1.11.104/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
|
||||||
modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8=
|
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.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
|
||||||
|
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
|
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
|
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/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
||||||
|
modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
|
||||||
|
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
|
||||||
|
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
|
||||||
|
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||||
modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY=
|
modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY=
|
||||||
modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k=
|
modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k=
|
||||||
|
modernc.org/sqlite v1.14.3 h1:psrTwgpEujgWEP3FNdsC9yNh5tSeA77U0GeWhHH4XmQ=
|
||||||
|
modernc.org/sqlite v1.14.3/go.mod h1:xMpicS1i2MJ4C8+Ap0vYBqTwYfpFvdnPE6brbFOtV2Y=
|
||||||
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
|
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/tcl v1.9.2 h1:YA87dFLOsR2KqMka371a2Xgr+YsyUwo7OmHVSv/kztw=
|
||||||
|
modernc.org/tcl v1.9.2/go.mod h1:aw7OnlIoiuJgu1gwbTZtrKnGpDqH9wyH++jZcxdqNsg=
|
||||||
|
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
||||||
|
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
|
modernc.org/z v1.2.20 h1:DyboxM1sJR2NB803j2StnbnL6jcQXz273OhHDGu8dGk=
|
||||||
|
modernc.org/z v1.2.20/go.mod h1:zU9FiF4PbHdOTUxw+IF8j7ArBMRPsHgq10uVPt6xTzo=
|
||||||
modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4=
|
modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
|
|
|
@ -11,40 +11,38 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetBlocks(t *testing.T) {
|
func TestGetBlocks(t *testing.T) {
|
||||||
th := SetupTestHelper().InitBasic()
|
th := SetupTestHelperWithToken(t).Start()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
board := th.CreateBoard("team-id", model.BoardTypeOpen)
|
||||||
require.NoError(t, resp.Error)
|
|
||||||
initialCount := len(blocks)
|
|
||||||
|
|
||||||
initialID1 := utils.NewID(utils.IDTypeBlock)
|
initialID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
initialID2 := utils.NewID(utils.IDTypeBlock)
|
initialID2 := utils.NewID(utils.IDTypeBlock)
|
||||||
newBlocks := []model.Block{
|
newBlocks := []model.Block{
|
||||||
{
|
{
|
||||||
ID: initialID1,
|
ID: initialID1,
|
||||||
RootID: initialID1,
|
BoardID: board.ID,
|
||||||
CreateAt: 1,
|
CreateAt: 1,
|
||||||
UpdateAt: 1,
|
UpdateAt: 1,
|
||||||
Type: model.TypeBoard,
|
Type: model.TypeCard,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: initialID2,
|
ID: initialID2,
|
||||||
RootID: initialID2,
|
BoardID: board.ID,
|
||||||
CreateAt: 1,
|
CreateAt: 1,
|
||||||
UpdateAt: 1,
|
UpdateAt: 1,
|
||||||
Type: model.TypeBoard,
|
Type: model.TypeCard,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
newBlocks, resp = th.Client.InsertBlocks(newBlocks)
|
newBlocks, resp := th.Client.InsertBlocks(board.ID, newBlocks)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, newBlocks, 2)
|
require.Len(t, newBlocks, 2)
|
||||||
blockID1 := newBlocks[0].ID
|
blockID1 := newBlocks[0].ID
|
||||||
blockID2 := newBlocks[1].ID
|
blockID2 := newBlocks[1].ID
|
||||||
|
|
||||||
blocks, resp = th.Client.GetBlocks()
|
blocks, resp := th.Client.GetBlocksForBoard(board.ID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, blocks, initialCount+2)
|
require.Len(t, blocks, 2)
|
||||||
|
|
||||||
blockIDs := make([]string, len(blocks))
|
blockIDs := make([]string, len(blocks))
|
||||||
for i, b := range blocks {
|
for i, b := range blocks {
|
||||||
|
@ -55,12 +53,10 @@ func TestGetBlocks(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPostBlock(t *testing.T) {
|
func TestPostBlock(t *testing.T) {
|
||||||
th := SetupTestHelper().InitBasic()
|
th := SetupTestHelperWithToken(t).Start()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
board := th.CreateBoard("team-id", model.BoardTypeOpen)
|
||||||
require.NoError(t, resp.Error)
|
|
||||||
initialCount := len(blocks)
|
|
||||||
|
|
||||||
var blockID1 string
|
var blockID1 string
|
||||||
var blockID2 string
|
var blockID2 string
|
||||||
|
@ -70,21 +66,21 @@ func TestPostBlock(t *testing.T) {
|
||||||
initialID1 := utils.NewID(utils.IDTypeBlock)
|
initialID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
block := model.Block{
|
block := model.Block{
|
||||||
ID: initialID1,
|
ID: initialID1,
|
||||||
RootID: initialID1,
|
BoardID: board.ID,
|
||||||
CreateAt: 1,
|
CreateAt: 1,
|
||||||
UpdateAt: 1,
|
UpdateAt: 1,
|
||||||
Type: model.TypeBoard,
|
Type: model.TypeCard,
|
||||||
Title: "New title",
|
Title: "New title",
|
||||||
}
|
}
|
||||||
|
|
||||||
newBlocks, resp := th.Client.InsertBlocks([]model.Block{block})
|
newBlocks, resp := th.Client.InsertBlocks(board.ID, []model.Block{block})
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, newBlocks, 1)
|
require.Len(t, newBlocks, 1)
|
||||||
blockID1 = newBlocks[0].ID
|
blockID1 = newBlocks[0].ID
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
blocks, resp := th.Client.GetBlocksForBoard(board.ID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, blocks, initialCount+1)
|
require.Len(t, blocks, 1)
|
||||||
|
|
||||||
blockIDs := make([]string, len(blocks))
|
blockIDs := make([]string, len(blocks))
|
||||||
for i, b := range blocks {
|
for i, b := range blocks {
|
||||||
|
@ -99,21 +95,21 @@ func TestPostBlock(t *testing.T) {
|
||||||
newBlocks := []model.Block{
|
newBlocks := []model.Block{
|
||||||
{
|
{
|
||||||
ID: initialID2,
|
ID: initialID2,
|
||||||
RootID: initialID2,
|
BoardID: board.ID,
|
||||||
CreateAt: 1,
|
CreateAt: 1,
|
||||||
UpdateAt: 1,
|
UpdateAt: 1,
|
||||||
Type: model.TypeBoard,
|
Type: model.TypeCard,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: initialID3,
|
ID: initialID3,
|
||||||
RootID: initialID3,
|
BoardID: board.ID,
|
||||||
CreateAt: 1,
|
CreateAt: 1,
|
||||||
UpdateAt: 1,
|
UpdateAt: 1,
|
||||||
Type: model.TypeBoard,
|
Type: model.TypeCard,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
newBlocks, resp := th.Client.InsertBlocks(newBlocks)
|
newBlocks, resp := th.Client.InsertBlocks(board.ID, newBlocks)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, newBlocks, 2)
|
require.Len(t, newBlocks, 2)
|
||||||
blockID2 = newBlocks[0].ID
|
blockID2 = newBlocks[0].ID
|
||||||
|
@ -121,9 +117,9 @@ func TestPostBlock(t *testing.T) {
|
||||||
require.NotEqual(t, initialID2, blockID2)
|
require.NotEqual(t, initialID2, blockID2)
|
||||||
require.NotEqual(t, initialID3, blockID3)
|
require.NotEqual(t, initialID3, blockID3)
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
blocks, resp := th.Client.GetBlocksForBoard(board.ID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, blocks, initialCount+3)
|
require.Len(t, blocks, 3)
|
||||||
|
|
||||||
blockIDs := make([]string, len(blocks))
|
blockIDs := make([]string, len(blocks))
|
||||||
for i, b := range blocks {
|
for i, b := range blocks {
|
||||||
|
@ -137,22 +133,22 @@ func TestPostBlock(t *testing.T) {
|
||||||
t.Run("Update a block should not be possible through the insert endpoint", func(t *testing.T) {
|
t.Run("Update a block should not be possible through the insert endpoint", func(t *testing.T) {
|
||||||
block := model.Block{
|
block := model.Block{
|
||||||
ID: blockID1,
|
ID: blockID1,
|
||||||
RootID: blockID1,
|
BoardID: board.ID,
|
||||||
CreateAt: 1,
|
CreateAt: 1,
|
||||||
UpdateAt: 20,
|
UpdateAt: 20,
|
||||||
Type: model.TypeBoard,
|
Type: model.TypeCard,
|
||||||
Title: "Updated title",
|
Title: "Updated title",
|
||||||
}
|
}
|
||||||
|
|
||||||
newBlocks, resp := th.Client.InsertBlocks([]model.Block{block})
|
newBlocks, resp := th.Client.InsertBlocks(board.ID, []model.Block{block})
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, newBlocks, 1)
|
require.Len(t, newBlocks, 1)
|
||||||
blockID4 := newBlocks[0].ID
|
blockID4 := newBlocks[0].ID
|
||||||
require.NotEqual(t, blockID1, blockID4)
|
require.NotEqual(t, blockID1, blockID4)
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
blocks, resp := th.Client.GetBlocksForBoard(board.ID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, blocks, initialCount+4)
|
require.Len(t, blocks, 4)
|
||||||
|
|
||||||
var block4 model.Block
|
var block4 model.Block
|
||||||
for _, b := range blocks {
|
for _, b := range blocks {
|
||||||
|
@ -166,42 +162,41 @@ func TestPostBlock(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPatchBlock(t *testing.T) {
|
func TestPatchBlock(t *testing.T) {
|
||||||
th := SetupTestHelper().InitBasic()
|
th := SetupTestHelperWithToken(t).Start()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
initialID := utils.NewID(utils.IDTypeBlock)
|
initialID := utils.NewID(utils.IDTypeBlock)
|
||||||
|
|
||||||
|
board := th.CreateBoard("team-id", model.BoardTypeOpen)
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
block := model.Block{
|
block := model.Block{
|
||||||
ID: initialID,
|
ID: initialID,
|
||||||
RootID: initialID,
|
BoardID: board.ID,
|
||||||
CreateAt: 1,
|
CreateAt: 1,
|
||||||
UpdateAt: 1,
|
UpdateAt: 1,
|
||||||
Type: model.TypeBoard,
|
Type: model.TypeCard,
|
||||||
Title: "New title",
|
Title: "New title",
|
||||||
Fields: map[string]interface{}{"test": "test value", "test2": "test value 2"},
|
Fields: map[string]interface{}{"test": "test value", "test2": "test value 2"},
|
||||||
}
|
}
|
||||||
|
|
||||||
newBlocks, resp := th.Client.InsertBlocks([]model.Block{block})
|
newBlocks, resp := th.Client.InsertBlocks(board.ID, []model.Block{block})
|
||||||
require.NoError(t, resp.Error)
|
th.CheckOK(resp)
|
||||||
require.Len(t, newBlocks, 1)
|
require.Len(t, newBlocks, 1)
|
||||||
blockID := newBlocks[0].ID
|
blockID := newBlocks[0].ID
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
|
||||||
require.NoError(t, resp.Error)
|
|
||||||
initialCount := len(blocks)
|
|
||||||
|
|
||||||
t.Run("Patch a block basic field", func(t *testing.T) {
|
t.Run("Patch a block basic field", func(t *testing.T) {
|
||||||
newTitle := "Updated title"
|
newTitle := "Updated title"
|
||||||
blockPatch := &model.BlockPatch{
|
blockPatch := &model.BlockPatch{
|
||||||
Title: &newTitle,
|
Title: &newTitle,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, resp := th.Client.PatchBlock(blockID, blockPatch)
|
_, resp := th.Client.PatchBlock(board.ID, blockID, blockPatch)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
blocks, resp := th.Client.GetBlocksForBoard(board.ID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, blocks, initialCount)
|
require.Len(t, blocks, 1)
|
||||||
|
|
||||||
var updatedBlock model.Block
|
var updatedBlock model.Block
|
||||||
for _, b := range blocks {
|
for _, b := range blocks {
|
||||||
|
@ -221,12 +216,12 @@ func TestPatchBlock(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, resp := th.Client.PatchBlock(blockID, blockPatch)
|
_, resp := th.Client.PatchBlock(board.ID, blockID, blockPatch)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
blocks, resp := th.Client.GetBlocksForBoard(board.ID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, blocks, initialCount)
|
require.Len(t, blocks, 1)
|
||||||
|
|
||||||
var updatedBlock model.Block
|
var updatedBlock model.Block
|
||||||
for _, b := range blocks {
|
for _, b := range blocks {
|
||||||
|
@ -244,12 +239,12 @@ func TestPatchBlock(t *testing.T) {
|
||||||
DeletedFields: []string{"test", "test3", "test100"},
|
DeletedFields: []string{"test", "test3", "test100"},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, resp := th.Client.PatchBlock(blockID, blockPatch)
|
_, resp := th.Client.PatchBlock(board.ID, blockID, blockPatch)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
blocks, resp := th.Client.GetBlocksForBoard(board.ID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, blocks, initialCount)
|
require.Len(t, blocks, 1)
|
||||||
|
|
||||||
var updatedBlock model.Block
|
var updatedBlock model.Block
|
||||||
for _, b := range blocks {
|
for _, b := range blocks {
|
||||||
|
@ -265,35 +260,34 @@ func TestPatchBlock(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteBlock(t *testing.T) {
|
func TestDeleteBlock(t *testing.T) {
|
||||||
th := SetupTestHelper().InitBasic()
|
th := SetupTestHelperWithToken(t).Start()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
board := th.CreateBoard("team-id", model.BoardTypeOpen)
|
||||||
require.NoError(t, resp.Error)
|
time.Sleep(10 * time.Millisecond)
|
||||||
initialCount := len(blocks)
|
|
||||||
|
|
||||||
var blockID string
|
var blockID string
|
||||||
t.Run("Create a block", func(t *testing.T) {
|
t.Run("Create a block", func(t *testing.T) {
|
||||||
initialID := utils.NewID(utils.IDTypeBlock)
|
initialID := utils.NewID(utils.IDTypeBlock)
|
||||||
block := model.Block{
|
block := model.Block{
|
||||||
ID: initialID,
|
ID: initialID,
|
||||||
RootID: initialID,
|
BoardID: board.ID,
|
||||||
CreateAt: 1,
|
CreateAt: 1,
|
||||||
UpdateAt: 1,
|
UpdateAt: 1,
|
||||||
Type: model.TypeBoard,
|
Type: model.TypeCard,
|
||||||
Title: "New title",
|
Title: "New title",
|
||||||
}
|
}
|
||||||
|
|
||||||
newBlocks, resp := th.Client.InsertBlocks([]model.Block{block})
|
newBlocks, resp := th.Client.InsertBlocks(board.ID, []model.Block{block})
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, newBlocks, 1)
|
require.Len(t, newBlocks, 1)
|
||||||
require.NotZero(t, newBlocks[0].ID)
|
require.NotZero(t, newBlocks[0].ID)
|
||||||
require.NotEqual(t, initialID, newBlocks[0].ID)
|
require.NotEqual(t, initialID, newBlocks[0].ID)
|
||||||
blockID = newBlocks[0].ID
|
blockID = newBlocks[0].ID
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
blocks, resp := th.Client.GetBlocksForBoard(board.ID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, blocks, initialCount+1)
|
require.Len(t, blocks, 1)
|
||||||
|
|
||||||
blockIDs := make([]string, len(blocks))
|
blockIDs := make([]string, len(blocks))
|
||||||
for i, b := range blocks {
|
for i, b := range blocks {
|
||||||
|
@ -307,43 +301,45 @@ func TestDeleteBlock(t *testing.T) {
|
||||||
// id,insert_at on block history
|
// id,insert_at on block history
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
_, resp := th.Client.DeleteBlock(blockID)
|
_, resp := th.Client.DeleteBlock(board.ID, blockID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
blocks, resp := th.Client.GetBlocksForBoard(board.ID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, blocks, initialCount)
|
require.Empty(t, blocks)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUndeleteBlock(t *testing.T) {
|
func TestUndeleteBlock(t *testing.T) {
|
||||||
th := SetupTestHelper().InitBasic()
|
th := SetupTestHelper(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
board := th.CreateBoard("team-id", model.BoardTypeOpen)
|
||||||
|
|
||||||
|
blocks, resp := th.Client.GetBlocksForBoard(board.ID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
initialCount := len(blocks)
|
initialCount := len(blocks)
|
||||||
|
|
||||||
var blockID string
|
var blockID string
|
||||||
t.Run("Create a block", func(t *testing.T) {
|
t.Run("Create a block", func(t *testing.T) {
|
||||||
initialID := utils.NewID(utils.IDTypeBlock)
|
initialID := utils.NewID(utils.IDTypeBoard)
|
||||||
block := model.Block{
|
block := model.Block{
|
||||||
ID: initialID,
|
ID: initialID,
|
||||||
RootID: initialID,
|
BoardID: board.ID,
|
||||||
CreateAt: 1,
|
CreateAt: 1,
|
||||||
UpdateAt: 1,
|
UpdateAt: 1,
|
||||||
Type: model.TypeBoard,
|
Type: model.TypeBoard,
|
||||||
Title: "New title",
|
Title: "New title",
|
||||||
}
|
}
|
||||||
|
|
||||||
newBlocks, resp := th.Client.InsertBlocks([]model.Block{block})
|
newBlocks, resp := th.Client.InsertBlocks(board.ID, []model.Block{block})
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, newBlocks, 1)
|
require.Len(t, newBlocks, 1)
|
||||||
require.NotZero(t, newBlocks[0].ID)
|
require.NotZero(t, newBlocks[0].ID)
|
||||||
require.NotEqual(t, initialID, newBlocks[0].ID)
|
require.NotEqual(t, initialID, newBlocks[0].ID)
|
||||||
blockID = newBlocks[0].ID
|
blockID = newBlocks[0].ID
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
blocks, resp := th.Client.GetBlocksForBoard(board.ID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, blocks, initialCount+1)
|
require.Len(t, blocks, initialCount+1)
|
||||||
|
|
||||||
|
@ -359,10 +355,10 @@ func TestUndeleteBlock(t *testing.T) {
|
||||||
// id,insert_at on block history
|
// id,insert_at on block history
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
_, resp := th.Client.DeleteBlock(blockID)
|
_, resp := th.Client.DeleteBlock(board.ID, blockID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
blocks, resp := th.Client.GetBlocksForBoard(board.ID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, blocks, initialCount)
|
require.Len(t, blocks, initialCount)
|
||||||
})
|
})
|
||||||
|
@ -372,10 +368,10 @@ func TestUndeleteBlock(t *testing.T) {
|
||||||
// id,insert_at on block history
|
// id,insert_at on block history
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
_, resp := th.Client.UndeleteBlock(blockID)
|
_, resp := th.Client.UndeleteBlock(board.ID, blockID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
blocks, resp := th.Client.GetBlocksForBoard(board.ID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, blocks, initialCount+1)
|
require.Len(t, blocks, initialCount+1)
|
||||||
})
|
})
|
||||||
|
@ -384,12 +380,10 @@ func TestUndeleteBlock(t *testing.T) {
|
||||||
func TestGetSubtree(t *testing.T) {
|
func TestGetSubtree(t *testing.T) {
|
||||||
t.Skip("TODO: fix flaky test")
|
t.Skip("TODO: fix flaky test")
|
||||||
|
|
||||||
th := SetupTestHelper().InitBasic()
|
th := SetupTestHelperWithToken(t).Start()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
board := th.CreateBoard("team-id", model.BoardTypeOpen)
|
||||||
require.NoError(t, resp.Error)
|
|
||||||
initialCount := len(blocks)
|
|
||||||
|
|
||||||
parentBlockID := utils.NewID(utils.IDTypeBlock)
|
parentBlockID := utils.NewID(utils.IDTypeBlock)
|
||||||
childBlockID1 := utils.NewID(utils.IDTypeBlock)
|
childBlockID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
|
@ -399,14 +393,14 @@ func TestGetSubtree(t *testing.T) {
|
||||||
newBlocks := []model.Block{
|
newBlocks := []model.Block{
|
||||||
{
|
{
|
||||||
ID: parentBlockID,
|
ID: parentBlockID,
|
||||||
RootID: parentBlockID,
|
BoardID: board.ID,
|
||||||
CreateAt: 1,
|
CreateAt: 1,
|
||||||
UpdateAt: 1,
|
UpdateAt: 1,
|
||||||
Type: model.TypeBoard,
|
Type: model.TypeCard,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: childBlockID1,
|
ID: childBlockID1,
|
||||||
RootID: parentBlockID,
|
BoardID: board.ID,
|
||||||
ParentID: parentBlockID,
|
ParentID: parentBlockID,
|
||||||
CreateAt: 2,
|
CreateAt: 2,
|
||||||
UpdateAt: 2,
|
UpdateAt: 2,
|
||||||
|
@ -414,7 +408,7 @@ func TestGetSubtree(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: childBlockID2,
|
ID: childBlockID2,
|
||||||
RootID: parentBlockID,
|
BoardID: board.ID,
|
||||||
ParentID: parentBlockID,
|
ParentID: parentBlockID,
|
||||||
CreateAt: 2,
|
CreateAt: 2,
|
||||||
UpdateAt: 2,
|
UpdateAt: 2,
|
||||||
|
@ -422,12 +416,12 @@ func TestGetSubtree(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, resp := th.Client.InsertBlocks(newBlocks)
|
_, resp := th.Client.InsertBlocks(board.ID, newBlocks)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
|
|
||||||
blocks, resp := th.Client.GetBlocks()
|
blocks, resp := th.Client.GetBlocksForBoard(board.ID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, blocks, initialCount+1) // GetBlocks returns root blocks (null ParentID)
|
require.Len(t, blocks, 1) // GetBlocks returns root blocks (null ParentID)
|
||||||
|
|
||||||
blockIDs := make([]string, len(blocks))
|
blockIDs := make([]string, len(blocks))
|
||||||
for i, b := range blocks {
|
for i, b := range blocks {
|
||||||
|
@ -437,7 +431,7 @@ func TestGetSubtree(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Get subtree for parent ID", func(t *testing.T) {
|
t.Run("Get subtree for parent ID", func(t *testing.T) {
|
||||||
blocks, resp := th.Client.GetSubtree(parentBlockID)
|
blocks, resp := th.Client.GetSubtree(board.ID, parentBlockID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, blocks, 3)
|
require.Len(t, blocks, 3)
|
||||||
|
|
||||||
|
|
1270
server/integrationtests/board_test.go
Normal file
1270
server/integrationtests/board_test.go
Normal file
File diff suppressed because it is too large
Load diff
816
server/integrationtests/boards_and_blocks_test.go
Normal file
816
server/integrationtests/boards_and_blocks_test.go
Normal file
|
@ -0,0 +1,816 @@
|
||||||
|
package integrationtests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mattermost/focalboard/server/model"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateBoardsAndBlocks(t *testing.T) {
|
||||||
|
teamID := testTeamID
|
||||||
|
|
||||||
|
t.Run("a non authenticated user should be rejected", func(t *testing.T) {
|
||||||
|
th := SetupTestHelper(t).Start()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
newBab := &model.BoardsAndBlocks{
|
||||||
|
Boards: []*model.Board{},
|
||||||
|
Blocks: []model.Block{},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.CreateBoardsAndBlocks(newBab)
|
||||||
|
th.CheckUnauthorized(resp)
|
||||||
|
require.Nil(t, bab)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid boards and blocks", func(t *testing.T) {
|
||||||
|
th := SetupTestHelper(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
t.Run("no boards", func(t *testing.T) {
|
||||||
|
newBab := &model.BoardsAndBlocks{
|
||||||
|
Boards: []*model.Board{},
|
||||||
|
Blocks: []model.Block{
|
||||||
|
{ID: "block-id", BoardID: "board-id", Type: model.TypeCard},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.CreateBoardsAndBlocks(newBab)
|
||||||
|
th.CheckBadRequest(resp)
|
||||||
|
require.Nil(t, bab)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no blocks", func(t *testing.T) {
|
||||||
|
newBab := &model.BoardsAndBlocks{
|
||||||
|
Boards: []*model.Board{
|
||||||
|
{ID: "board-id", TeamID: teamID, Type: model.BoardTypePrivate},
|
||||||
|
},
|
||||||
|
Blocks: []model.Block{},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.CreateBoardsAndBlocks(newBab)
|
||||||
|
th.CheckBadRequest(resp)
|
||||||
|
require.Nil(t, bab)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("blocks from nonexistent boards", func(t *testing.T) {
|
||||||
|
newBab := &model.BoardsAndBlocks{
|
||||||
|
Boards: []*model.Board{
|
||||||
|
{ID: "board-id", TeamID: teamID, Type: model.BoardTypePrivate},
|
||||||
|
},
|
||||||
|
Blocks: []model.Block{
|
||||||
|
{ID: "block-id", BoardID: "nonexistent-board-id", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.CreateBoardsAndBlocks(newBab)
|
||||||
|
th.CheckBadRequest(resp)
|
||||||
|
require.Nil(t, bab)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("boards with no IDs", func(t *testing.T) {
|
||||||
|
newBab := &model.BoardsAndBlocks{
|
||||||
|
Boards: []*model.Board{
|
||||||
|
{ID: "board-id", TeamID: teamID, Type: model.BoardTypePrivate},
|
||||||
|
{TeamID: teamID, Type: model.BoardTypePrivate},
|
||||||
|
},
|
||||||
|
Blocks: []model.Block{
|
||||||
|
{ID: "block-id", BoardID: "board-id", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.CreateBoardsAndBlocks(newBab)
|
||||||
|
th.CheckBadRequest(resp)
|
||||||
|
require.Nil(t, bab)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("boards from different teams", func(t *testing.T) {
|
||||||
|
newBab := &model.BoardsAndBlocks{
|
||||||
|
Boards: []*model.Board{
|
||||||
|
{ID: "board-id-1", TeamID: "team-id-1", Type: model.BoardTypePrivate},
|
||||||
|
{ID: "board-id-2", TeamID: "team-id-2", Type: model.BoardTypePrivate},
|
||||||
|
},
|
||||||
|
Blocks: []model.Block{
|
||||||
|
{ID: "block-id", BoardID: "board-id-1", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.CreateBoardsAndBlocks(newBab)
|
||||||
|
th.CheckBadRequest(resp)
|
||||||
|
require.Nil(t, bab)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("creating boards and blocks", func(t *testing.T) {
|
||||||
|
newBab := &model.BoardsAndBlocks{
|
||||||
|
Boards: []*model.Board{
|
||||||
|
{ID: "board-id-1", Title: "public board", TeamID: teamID, Type: model.BoardTypeOpen},
|
||||||
|
{ID: "board-id-2", Title: "private board", TeamID: teamID, Type: model.BoardTypePrivate},
|
||||||
|
},
|
||||||
|
Blocks: []model.Block{
|
||||||
|
{ID: "block-id-1", Title: "block 1", BoardID: "board-id-1", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},
|
||||||
|
{ID: "block-id-2", Title: "block 2", BoardID: "board-id-2", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.CreateBoardsAndBlocks(newBab)
|
||||||
|
th.CheckOK(resp)
|
||||||
|
require.NotNil(t, bab)
|
||||||
|
|
||||||
|
require.Len(t, bab.Boards, 2)
|
||||||
|
require.Len(t, bab.Blocks, 2)
|
||||||
|
|
||||||
|
// board 1 should have been created with a new ID, and its
|
||||||
|
// block should be there too
|
||||||
|
boardsTermPublic, resp := th.Client.SearchBoardsForTeam(teamID, "public")
|
||||||
|
th.CheckOK(resp)
|
||||||
|
require.Len(t, boardsTermPublic, 1)
|
||||||
|
board1 := boardsTermPublic[0]
|
||||||
|
require.Equal(t, "public board", board1.Title)
|
||||||
|
require.Equal(t, model.BoardTypeOpen, board1.Type)
|
||||||
|
require.NotEqual(t, "board-id-1", board1.ID)
|
||||||
|
blocks1, err := th.Server.App().GetBlocksForBoard(board1.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, blocks1, 1)
|
||||||
|
require.Equal(t, "block 1", blocks1[0].Title)
|
||||||
|
|
||||||
|
// board 1 should have been created with a new ID, and its
|
||||||
|
// block should be there too
|
||||||
|
boardsTermPrivate, resp := th.Client.SearchBoardsForTeam(teamID, "private")
|
||||||
|
th.CheckOK(resp)
|
||||||
|
require.Len(t, boardsTermPrivate, 1)
|
||||||
|
board2 := boardsTermPrivate[0]
|
||||||
|
require.Equal(t, "private board", board2.Title)
|
||||||
|
require.Equal(t, model.BoardTypePrivate, board2.Type)
|
||||||
|
require.NotEqual(t, "board-id-2", board2.ID)
|
||||||
|
blocks2, err := th.Server.App().GetBlocksForBoard(board2.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, blocks2, 1)
|
||||||
|
require.Equal(t, "block 2", blocks2[0].Title)
|
||||||
|
|
||||||
|
// user should be an admin of both newly created boards
|
||||||
|
user1 := th.GetUser1()
|
||||||
|
members1, err := th.Server.App().GetMembersForBoard(board1.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, members1, 1)
|
||||||
|
require.Equal(t, user1.ID, members1[0].UserID)
|
||||||
|
members2, err := th.Server.App().GetMembersForBoard(board2.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, members2, 1)
|
||||||
|
require.Equal(t, user1.ID, members2[0].UserID)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPatchBoardsAndBlocks(t *testing.T) {
|
||||||
|
teamID := "team-id"
|
||||||
|
|
||||||
|
t.Run("a non authenticated user should be rejected", func(t *testing.T) {
|
||||||
|
th := SetupTestHelper(t).Start()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
pbab := &model.PatchBoardsAndBlocks{}
|
||||||
|
|
||||||
|
bab, resp := th.Client.PatchBoardsAndBlocks(pbab)
|
||||||
|
th.CheckUnauthorized(resp)
|
||||||
|
require.Nil(t, bab)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid patch boards and blocks", func(t *testing.T) {
|
||||||
|
th := SetupTestHelper(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
userID := th.GetUser1().ID
|
||||||
|
initialTitle := "initial title 1"
|
||||||
|
newTitle := "new title 1"
|
||||||
|
|
||||||
|
newBoard1 := &model.Board{
|
||||||
|
Title: initialTitle,
|
||||||
|
TeamID: teamID,
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
board1, err := th.Server.App().CreateBoard(newBoard1, userID, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board1)
|
||||||
|
|
||||||
|
newBoard2 := &model.Board{
|
||||||
|
Title: initialTitle,
|
||||||
|
TeamID: teamID,
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
board2, err := th.Server.App().CreateBoard(newBoard2, userID, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board2)
|
||||||
|
|
||||||
|
newBlock1 := model.Block{
|
||||||
|
ID: "block-id-1",
|
||||||
|
BoardID: board1.ID,
|
||||||
|
Title: initialTitle,
|
||||||
|
}
|
||||||
|
require.NoError(t, th.Server.App().InsertBlock(newBlock1, userID))
|
||||||
|
block1, err := th.Server.App().GetBlockByID("block-id-1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, block1)
|
||||||
|
|
||||||
|
newBlock2 := model.Block{
|
||||||
|
ID: "block-id-2",
|
||||||
|
BoardID: board2.ID,
|
||||||
|
Title: initialTitle,
|
||||||
|
}
|
||||||
|
require.NoError(t, th.Server.App().InsertBlock(newBlock2, userID))
|
||||||
|
block2, err := th.Server.App().GetBlockByID("block-id-2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, block2)
|
||||||
|
|
||||||
|
t.Run("no board IDs", func(t *testing.T) {
|
||||||
|
pbab := &model.PatchBoardsAndBlocks{
|
||||||
|
BoardIDs: []string{},
|
||||||
|
BoardPatches: []*model.BoardPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
BlockIDs: []string{block1.ID, block2.ID},
|
||||||
|
BlockPatches: []*model.BlockPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.PatchBoardsAndBlocks(pbab)
|
||||||
|
th.CheckBadRequest(resp)
|
||||||
|
require.Nil(t, bab)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("missmatch board IDs and patches", func(t *testing.T) {
|
||||||
|
pbab := &model.PatchBoardsAndBlocks{
|
||||||
|
BoardIDs: []string{board1.ID, board2.ID},
|
||||||
|
BoardPatches: []*model.BoardPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
BlockIDs: []string{block1.ID, block2.ID},
|
||||||
|
BlockPatches: []*model.BlockPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.PatchBoardsAndBlocks(pbab)
|
||||||
|
th.CheckBadRequest(resp)
|
||||||
|
require.Nil(t, bab)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no block IDs", func(t *testing.T) {
|
||||||
|
pbab := &model.PatchBoardsAndBlocks{
|
||||||
|
BoardIDs: []string{board1.ID, board2.ID},
|
||||||
|
BoardPatches: []*model.BoardPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
BlockIDs: []string{},
|
||||||
|
BlockPatches: []*model.BlockPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.PatchBoardsAndBlocks(pbab)
|
||||||
|
th.CheckBadRequest(resp)
|
||||||
|
require.Nil(t, bab)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("missmatch block IDs and patches", func(t *testing.T) {
|
||||||
|
pbab := &model.PatchBoardsAndBlocks{
|
||||||
|
BoardIDs: []string{board1.ID, board2.ID},
|
||||||
|
BoardPatches: []*model.BoardPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
BlockIDs: []string{block1.ID, block2.ID},
|
||||||
|
BlockPatches: []*model.BlockPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.PatchBoardsAndBlocks(pbab)
|
||||||
|
th.CheckBadRequest(resp)
|
||||||
|
require.Nil(t, bab)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("block that doesn't belong to any board", func(t *testing.T) {
|
||||||
|
pbab := &model.PatchBoardsAndBlocks{
|
||||||
|
BoardIDs: []string{board1.ID},
|
||||||
|
BoardPatches: []*model.BoardPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
BlockIDs: []string{block1.ID, "board-id-2"},
|
||||||
|
BlockPatches: []*model.BlockPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.PatchBoardsAndBlocks(pbab)
|
||||||
|
th.CheckBadRequest(resp)
|
||||||
|
require.Nil(t, bab)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("if the user doesn't have permissions for one of the boards, nothing should be updated", func(t *testing.T) {
|
||||||
|
th := SetupTestHelper(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
userID := th.GetUser1().ID
|
||||||
|
initialTitle := "initial title 2"
|
||||||
|
newTitle := "new title 2"
|
||||||
|
|
||||||
|
newBoard1 := &model.Board{
|
||||||
|
Title: initialTitle,
|
||||||
|
TeamID: teamID,
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
board1, err := th.Server.App().CreateBoard(newBoard1, userID, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board1)
|
||||||
|
|
||||||
|
newBoard2 := &model.Board{
|
||||||
|
Title: initialTitle,
|
||||||
|
TeamID: teamID,
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
board2, err := th.Server.App().CreateBoard(newBoard2, userID, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board2)
|
||||||
|
|
||||||
|
newBlock1 := model.Block{
|
||||||
|
ID: "block-id-1",
|
||||||
|
BoardID: board1.ID,
|
||||||
|
Title: initialTitle,
|
||||||
|
}
|
||||||
|
require.NoError(t, th.Server.App().InsertBlock(newBlock1, userID))
|
||||||
|
block1, err := th.Server.App().GetBlockByID("block-id-1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, block1)
|
||||||
|
|
||||||
|
newBlock2 := model.Block{
|
||||||
|
ID: "block-id-2",
|
||||||
|
BoardID: board2.ID,
|
||||||
|
Title: initialTitle,
|
||||||
|
}
|
||||||
|
require.NoError(t, th.Server.App().InsertBlock(newBlock2, userID))
|
||||||
|
block2, err := th.Server.App().GetBlockByID("block-id-2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, block2)
|
||||||
|
|
||||||
|
pbab := &model.PatchBoardsAndBlocks{
|
||||||
|
BoardIDs: []string{board1.ID, board2.ID},
|
||||||
|
BoardPatches: []*model.BoardPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
BlockIDs: []string{block1.ID, block2.ID},
|
||||||
|
BlockPatches: []*model.BlockPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.PatchBoardsAndBlocks(pbab)
|
||||||
|
th.CheckForbidden(resp)
|
||||||
|
require.Nil(t, bab)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("boards belonging to different teams should be rejected", func(t *testing.T) {
|
||||||
|
th := SetupTestHelper(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
userID := th.GetUser1().ID
|
||||||
|
initialTitle := "initial title 3"
|
||||||
|
newTitle := "new title 3"
|
||||||
|
|
||||||
|
newBoard1 := &model.Board{
|
||||||
|
Title: initialTitle,
|
||||||
|
TeamID: teamID,
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
board1, err := th.Server.App().CreateBoard(newBoard1, userID, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board1)
|
||||||
|
|
||||||
|
newBoard2 := &model.Board{
|
||||||
|
Title: initialTitle,
|
||||||
|
TeamID: "different-team-id",
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
board2, err := th.Server.App().CreateBoard(newBoard2, userID, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board2)
|
||||||
|
|
||||||
|
newBlock1 := model.Block{
|
||||||
|
ID: "block-id-1",
|
||||||
|
BoardID: board1.ID,
|
||||||
|
Title: initialTitle,
|
||||||
|
}
|
||||||
|
require.NoError(t, th.Server.App().InsertBlock(newBlock1, userID))
|
||||||
|
block1, err := th.Server.App().GetBlockByID("block-id-1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, block1)
|
||||||
|
|
||||||
|
newBlock2 := model.Block{
|
||||||
|
ID: "block-id-2",
|
||||||
|
BoardID: board2.ID,
|
||||||
|
Title: initialTitle,
|
||||||
|
}
|
||||||
|
require.NoError(t, th.Server.App().InsertBlock(newBlock2, userID))
|
||||||
|
block2, err := th.Server.App().GetBlockByID("block-id-2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, block2)
|
||||||
|
|
||||||
|
pbab := &model.PatchBoardsAndBlocks{
|
||||||
|
BoardIDs: []string{board1.ID, board2.ID},
|
||||||
|
BoardPatches: []*model.BoardPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
BlockIDs: []string{block1.ID, "board-id-2"},
|
||||||
|
BlockPatches: []*model.BlockPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.PatchBoardsAndBlocks(pbab)
|
||||||
|
th.CheckBadRequest(resp)
|
||||||
|
require.Nil(t, bab)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("patches should be rejected if one is invalid", func(t *testing.T) {
|
||||||
|
th := SetupTestHelper(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
userID := th.GetUser1().ID
|
||||||
|
initialTitle := "initial title 4"
|
||||||
|
newTitle := "new title 4"
|
||||||
|
|
||||||
|
newBoard1 := &model.Board{
|
||||||
|
Title: initialTitle,
|
||||||
|
TeamID: teamID,
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
board1, err := th.Server.App().CreateBoard(newBoard1, userID, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board1)
|
||||||
|
|
||||||
|
newBoard2 := &model.Board{
|
||||||
|
Title: initialTitle,
|
||||||
|
TeamID: teamID,
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
board2, err := th.Server.App().CreateBoard(newBoard2, userID, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board2)
|
||||||
|
|
||||||
|
newBlock1 := model.Block{
|
||||||
|
ID: "block-id-1",
|
||||||
|
BoardID: board1.ID,
|
||||||
|
Title: initialTitle,
|
||||||
|
}
|
||||||
|
require.NoError(t, th.Server.App().InsertBlock(newBlock1, userID))
|
||||||
|
block1, err := th.Server.App().GetBlockByID("block-id-1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, block1)
|
||||||
|
|
||||||
|
newBlock2 := model.Block{
|
||||||
|
ID: "block-id-2",
|
||||||
|
BoardID: board2.ID,
|
||||||
|
Title: initialTitle,
|
||||||
|
}
|
||||||
|
require.NoError(t, th.Server.App().InsertBlock(newBlock2, userID))
|
||||||
|
block2, err := th.Server.App().GetBlockByID("block-id-2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, block2)
|
||||||
|
|
||||||
|
var invalidPatchType model.BoardType = "invalid"
|
||||||
|
invalidPatch := &model.BoardPatch{Type: &invalidPatchType}
|
||||||
|
|
||||||
|
pbab := &model.PatchBoardsAndBlocks{
|
||||||
|
BoardIDs: []string{board1.ID, board2.ID},
|
||||||
|
BoardPatches: []*model.BoardPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
invalidPatch,
|
||||||
|
},
|
||||||
|
BlockIDs: []string{block1.ID, "board-id-2"},
|
||||||
|
BlockPatches: []*model.BlockPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.PatchBoardsAndBlocks(pbab)
|
||||||
|
th.CheckBadRequest(resp)
|
||||||
|
require.Nil(t, bab)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("patches should be rejected if there is a block that doesn't belong to the boards being patched", func(t *testing.T) {
|
||||||
|
th := SetupTestHelper(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
userID := th.GetUser1().ID
|
||||||
|
initialTitle := "initial title"
|
||||||
|
newTitle := "new title"
|
||||||
|
|
||||||
|
newBoard1 := &model.Board{
|
||||||
|
Title: initialTitle,
|
||||||
|
TeamID: teamID,
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
board1, err := th.Server.App().CreateBoard(newBoard1, userID, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board1)
|
||||||
|
|
||||||
|
newBoard2 := &model.Board{
|
||||||
|
Title: initialTitle,
|
||||||
|
TeamID: teamID,
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
board2, err := th.Server.App().CreateBoard(newBoard2, userID, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board2)
|
||||||
|
|
||||||
|
newBlock1 := model.Block{
|
||||||
|
ID: "block-id-1",
|
||||||
|
BoardID: board1.ID,
|
||||||
|
Title: initialTitle,
|
||||||
|
}
|
||||||
|
require.NoError(t, th.Server.App().InsertBlock(newBlock1, userID))
|
||||||
|
block1, err := th.Server.App().GetBlockByID("block-id-1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, block1)
|
||||||
|
|
||||||
|
newBlock2 := model.Block{
|
||||||
|
ID: "block-id-2",
|
||||||
|
BoardID: board2.ID,
|
||||||
|
Title: initialTitle,
|
||||||
|
}
|
||||||
|
require.NoError(t, th.Server.App().InsertBlock(newBlock2, userID))
|
||||||
|
block2, err := th.Server.App().GetBlockByID("block-id-2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, block2)
|
||||||
|
|
||||||
|
pbab := &model.PatchBoardsAndBlocks{
|
||||||
|
BoardIDs: []string{board1.ID},
|
||||||
|
BoardPatches: []*model.BoardPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
BlockIDs: []string{block1.ID, block2.ID},
|
||||||
|
BlockPatches: []*model.BlockPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.PatchBoardsAndBlocks(pbab)
|
||||||
|
th.CheckBadRequest(resp)
|
||||||
|
require.Nil(t, bab)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("patches should be applied if they're valid and they're related", func(t *testing.T) {
|
||||||
|
th := SetupTestHelper(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
userID := th.GetUser1().ID
|
||||||
|
initialTitle := "initial title"
|
||||||
|
newTitle := "new title"
|
||||||
|
|
||||||
|
newBoard1 := &model.Board{
|
||||||
|
Title: initialTitle,
|
||||||
|
TeamID: teamID,
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
board1, err := th.Server.App().CreateBoard(newBoard1, userID, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board1)
|
||||||
|
|
||||||
|
newBoard2 := &model.Board{
|
||||||
|
Title: initialTitle,
|
||||||
|
TeamID: teamID,
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
board2, err := th.Server.App().CreateBoard(newBoard2, userID, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board2)
|
||||||
|
|
||||||
|
newBlock1 := model.Block{
|
||||||
|
ID: "block-id-1",
|
||||||
|
BoardID: board1.ID,
|
||||||
|
Title: initialTitle,
|
||||||
|
}
|
||||||
|
require.NoError(t, th.Server.App().InsertBlock(newBlock1, userID))
|
||||||
|
block1, err := th.Server.App().GetBlockByID("block-id-1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, block1)
|
||||||
|
|
||||||
|
newBlock2 := model.Block{
|
||||||
|
ID: "block-id-2",
|
||||||
|
BoardID: board2.ID,
|
||||||
|
Title: initialTitle,
|
||||||
|
}
|
||||||
|
require.NoError(t, th.Server.App().InsertBlock(newBlock2, userID))
|
||||||
|
block2, err := th.Server.App().GetBlockByID("block-id-2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, block2)
|
||||||
|
|
||||||
|
pbab := &model.PatchBoardsAndBlocks{
|
||||||
|
BoardIDs: []string{board1.ID, board2.ID},
|
||||||
|
BoardPatches: []*model.BoardPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
BlockIDs: []string{block1.ID, block2.ID},
|
||||||
|
BlockPatches: []*model.BlockPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, resp := th.Client.PatchBoardsAndBlocks(pbab)
|
||||||
|
th.CheckOK(resp)
|
||||||
|
require.NotNil(t, bab)
|
||||||
|
require.Len(t, bab.Boards, 2)
|
||||||
|
require.Len(t, bab.Blocks, 2)
|
||||||
|
|
||||||
|
// ensure that the entities have been updated
|
||||||
|
rBoard1, err := th.Server.App().GetBoard(board1.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, newTitle, rBoard1.Title)
|
||||||
|
rBlock1, err := th.Server.App().GetBlockByID(block1.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, newTitle, rBlock1.Title)
|
||||||
|
|
||||||
|
rBoard2, err := th.Server.App().GetBoard(board2.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, newTitle, rBoard2.Title)
|
||||||
|
rBlock2, err := th.Server.App().GetBlockByID(block2.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, newTitle, rBlock2.Title)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteBoardsAndBlocks(t *testing.T) {
|
||||||
|
teamID := "team-id"
|
||||||
|
|
||||||
|
t.Run("a non authenticated user should be rejected", func(t *testing.T) {
|
||||||
|
th := SetupTestHelper(t).Start()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
dbab := &model.DeleteBoardsAndBlocks{}
|
||||||
|
|
||||||
|
success, resp := th.Client.DeleteBoardsAndBlocks(dbab)
|
||||||
|
th.CheckUnauthorized(resp)
|
||||||
|
require.False(t, success)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid delete boards and blocks", func(t *testing.T) {
|
||||||
|
th := SetupTestHelper(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
// a board is required for the permission checks
|
||||||
|
newBoard := &model.Board{
|
||||||
|
TeamID: teamID,
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
board, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board)
|
||||||
|
|
||||||
|
t.Run("no boards", func(t *testing.T) {
|
||||||
|
dbab := &model.DeleteBoardsAndBlocks{
|
||||||
|
Blocks: []string{"block-id-1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
success, resp := th.Client.DeleteBoardsAndBlocks(dbab)
|
||||||
|
th.CheckBadRequest(resp)
|
||||||
|
require.False(t, success)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no blocks", func(t *testing.T) {
|
||||||
|
dbab := &model.DeleteBoardsAndBlocks{
|
||||||
|
Boards: []string{board.ID},
|
||||||
|
}
|
||||||
|
|
||||||
|
success, resp := th.Client.DeleteBoardsAndBlocks(dbab)
|
||||||
|
th.CheckBadRequest(resp)
|
||||||
|
require.False(t, success)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("boards from different teams", func(t *testing.T) {
|
||||||
|
newOtherTeamsBoard := &model.Board{
|
||||||
|
TeamID: "another-team-id",
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
otherTeamsBoard, err := th.Server.App().CreateBoard(newOtherTeamsBoard, th.GetUser1().ID, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board)
|
||||||
|
|
||||||
|
dbab := &model.DeleteBoardsAndBlocks{
|
||||||
|
Boards: []string{board.ID, otherTeamsBoard.ID},
|
||||||
|
Blocks: []string{"block-id-1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
success, resp := th.Client.DeleteBoardsAndBlocks(dbab)
|
||||||
|
th.CheckBadRequest(resp)
|
||||||
|
require.False(t, success)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("if the user has no permissions to one of the boards, nothing should be deleted", func(t *testing.T) {
|
||||||
|
th := SetupTestHelper(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
// the user is an admin of the first board
|
||||||
|
newBoard1 := &model.Board{
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
board1, err := th.Server.App().CreateBoard(newBoard1, th.GetUser1().ID, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board1)
|
||||||
|
|
||||||
|
// but not of the second
|
||||||
|
newBoard2 := &model.Board{
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
board2, err := th.Server.App().CreateBoard(newBoard2, th.GetUser1().ID, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board2)
|
||||||
|
|
||||||
|
dbab := &model.DeleteBoardsAndBlocks{
|
||||||
|
Boards: []string{board1.ID, board2.ID},
|
||||||
|
Blocks: []string{"block-id-1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
success, resp := th.Client.DeleteBoardsAndBlocks(dbab)
|
||||||
|
th.CheckForbidden(resp)
|
||||||
|
require.False(t, success)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("all boards and blocks should be deleted if the request is correct", func(t *testing.T) {
|
||||||
|
th := SetupTestHelper(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
newBab := &model.BoardsAndBlocks{
|
||||||
|
Boards: []*model.Board{
|
||||||
|
{ID: "board-id-1", Title: "public board", TeamID: teamID, Type: model.BoardTypeOpen},
|
||||||
|
{ID: "board-id-2", Title: "private board", TeamID: teamID, Type: model.BoardTypePrivate},
|
||||||
|
},
|
||||||
|
Blocks: []model.Block{
|
||||||
|
{ID: "block-id-1", Title: "block 1", BoardID: "board-id-1", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},
|
||||||
|
{ID: "block-id-2", Title: "block 2", BoardID: "board-id-2", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bab, err := th.Server.App().CreateBoardsAndBlocks(newBab, th.GetUser1().ID, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, bab.Boards, 2)
|
||||||
|
require.Len(t, bab.Blocks, 2)
|
||||||
|
|
||||||
|
// ensure that the entities have been successfully created
|
||||||
|
board1, err := th.Server.App().GetBoard("board-id-1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board1)
|
||||||
|
block1, err := th.Server.App().GetBlockByID("block-id-1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, block1)
|
||||||
|
|
||||||
|
board2, err := th.Server.App().GetBoard("board-id-2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board2)
|
||||||
|
block2, err := th.Server.App().GetBlockByID("block-id-2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, block2)
|
||||||
|
|
||||||
|
// call the API to delete boards and blocks
|
||||||
|
dbab := &model.DeleteBoardsAndBlocks{
|
||||||
|
Boards: []string{"board-id-1", "board-id-2"},
|
||||||
|
Blocks: []string{"block-id-1", "block-id-2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
success, resp := th.Client.DeleteBoardsAndBlocks(dbab)
|
||||||
|
th.CheckOK(resp)
|
||||||
|
require.True(t, success)
|
||||||
|
|
||||||
|
// ensure that the entities have been successfully deleted
|
||||||
|
board1, err = th.Server.App().GetBoard("board-id-1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, board1)
|
||||||
|
block1, err = th.Server.App().GetBlockByID("block-id-1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, block1)
|
||||||
|
|
||||||
|
board2, err = th.Server.App().GetBoard("board-id-2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, board2)
|
||||||
|
block2, err = th.Server.App().GetBlockByID("block-id-2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, block2)
|
||||||
|
})
|
||||||
|
}
|
|
@ -4,19 +4,30 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/api"
|
"github.com/mattermost/focalboard/server/api"
|
||||||
"github.com/mattermost/focalboard/server/client"
|
"github.com/mattermost/focalboard/server/client"
|
||||||
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/server"
|
"github.com/mattermost/focalboard/server/server"
|
||||||
"github.com/mattermost/focalboard/server/services/config"
|
"github.com/mattermost/focalboard/server/services/config"
|
||||||
|
"github.com/mattermost/focalboard/server/services/permissions/localpermissions"
|
||||||
"github.com/mattermost/focalboard/server/services/store/sqlstore"
|
"github.com/mattermost/focalboard/server/services/store/sqlstore"
|
||||||
"github.com/mattermost/focalboard/server/utils"
|
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
user1Username = "user1"
|
||||||
|
user2Username = "user2"
|
||||||
|
password = "Pa$$word"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestHelper struct {
|
type TestHelper struct {
|
||||||
|
T *testing.T
|
||||||
Server *server.Server
|
Server *server.Server
|
||||||
Client *client.Client
|
Client *client.Client
|
||||||
Client2 *client.Client
|
Client2 *client.Client
|
||||||
|
@ -80,11 +91,14 @@ func newTestServer(singleUserToken string) *server.Server {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
permissionsService := localpermissions.New(db, logger)
|
||||||
|
|
||||||
params := server.Params{
|
params := server.Params{
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
SingleUserToken: singleUserToken,
|
SingleUserToken: singleUserToken,
|
||||||
DBStore: db,
|
DBStore: db,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
|
PermissionsService: permissionsService,
|
||||||
}
|
}
|
||||||
|
|
||||||
srv, err := server.New(params)
|
srv, err := server.New(params)
|
||||||
|
@ -95,23 +109,26 @@ func newTestServer(singleUserToken string) *server.Server {
|
||||||
return srv
|
return srv
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupTestHelper() *TestHelper {
|
func SetupTestHelperWithToken(t *testing.T) *TestHelper {
|
||||||
sessionToken := "TESTTOKEN"
|
sessionToken := "TESTTOKEN"
|
||||||
th := &TestHelper{}
|
th := &TestHelper{T: t}
|
||||||
th.Server = newTestServer(sessionToken)
|
th.Server = newTestServer(sessionToken)
|
||||||
th.Client = client.NewClient(th.Server.Config().ServerRoot, sessionToken)
|
th.Client = client.NewClient(th.Server.Config().ServerRoot, sessionToken)
|
||||||
|
th.Client2 = client.NewClient(th.Server.Config().ServerRoot, sessionToken)
|
||||||
return th
|
return th
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupTestHelperWithoutToken() *TestHelper {
|
func SetupTestHelper(t *testing.T) *TestHelper {
|
||||||
th := &TestHelper{}
|
th := &TestHelper{T: t}
|
||||||
th.Server = newTestServer("")
|
th.Server = newTestServer("")
|
||||||
th.Client = client.NewClient(th.Server.Config().ServerRoot, "")
|
th.Client = client.NewClient(th.Server.Config().ServerRoot, "")
|
||||||
th.Client2 = client.NewClient(th.Server.Config().ServerRoot, "")
|
th.Client2 = client.NewClient(th.Server.Config().ServerRoot, "")
|
||||||
return th
|
return th
|
||||||
}
|
}
|
||||||
|
|
||||||
func (th *TestHelper) InitBasic() *TestHelper {
|
// Start starts the test server and ensures that it's correctly
|
||||||
|
// responding to requests before returning.
|
||||||
|
func (th *TestHelper) Start() *TestHelper {
|
||||||
go func() {
|
go func() {
|
||||||
if err := th.Server.Start(); err != nil {
|
if err := th.Server.Start(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -144,51 +161,28 @@ func (th *TestHelper) InitBasic() *TestHelper {
|
||||||
return th
|
return th
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrRegisterFail = errors.New("register failed")
|
// InitBasic starts the test server and initializes the clients of the
|
||||||
|
// helper, registering them and logging them into the system.
|
||||||
|
func (th *TestHelper) InitBasic() *TestHelper {
|
||||||
|
th.Start()
|
||||||
|
|
||||||
func (th *TestHelper) InitUsers(username1 string, username2 string) error {
|
// user1
|
||||||
workspace, err := th.Server.App().GetRootWorkspace()
|
th.RegisterAndLogin(th.Client, user1Username, "user1@sample.com", password, "")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
clients := []*client.Client{th.Client, th.Client2}
|
// get token
|
||||||
usernames := []string{username1, username2}
|
team, resp := th.Client.GetTeam("0")
|
||||||
|
th.CheckOK(resp)
|
||||||
|
require.NotNil(th.T, team)
|
||||||
|
require.NotNil(th.T, team.SignupToken)
|
||||||
|
|
||||||
for i, client := range clients {
|
// user2
|
||||||
// register a new user
|
th.RegisterAndLogin(th.Client2, user2Username, "user2@sample.com", password, team.SignupToken)
|
||||||
password := utils.NewID(utils.IDTypeNone)
|
|
||||||
registerRequest := &api.RegisterRequest{
|
|
||||||
Username: usernames[i],
|
|
||||||
Email: usernames[i] + "@example.com",
|
|
||||||
Password: password,
|
|
||||||
Token: workspace.SignupToken,
|
|
||||||
}
|
|
||||||
success, resp := client.Register(registerRequest)
|
|
||||||
if resp.Error != nil {
|
|
||||||
return resp.Error
|
|
||||||
}
|
|
||||||
if !success {
|
|
||||||
return ErrRegisterFail
|
|
||||||
}
|
|
||||||
|
|
||||||
// login
|
return th
|
||||||
loginRequest := &api.LoginRequest{
|
|
||||||
Type: "normal",
|
|
||||||
Username: registerRequest.Username,
|
|
||||||
Email: registerRequest.Email,
|
|
||||||
Password: registerRequest.Password,
|
|
||||||
}
|
|
||||||
data, resp := client.Login(loginRequest)
|
|
||||||
if resp.Error != nil {
|
|
||||||
return resp.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
client.Token = data.Token
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrRegisterFail = errors.New("register failed")
|
||||||
|
|
||||||
func (th *TestHelper) TearDown() {
|
func (th *TestHelper) TearDown() {
|
||||||
defer func() { _ = th.Server.Logger().Shutdown() }()
|
defer func() { _ = th.Server.Logger().Shutdown() }()
|
||||||
|
|
||||||
|
@ -198,4 +192,96 @@ func (th *TestHelper) TearDown() {
|
||||||
}
|
}
|
||||||
|
|
||||||
os.RemoveAll(th.Server.Config().FilesPath)
|
os.RemoveAll(th.Server.Config().FilesPath)
|
||||||
|
|
||||||
|
if err := os.Remove(th.Server.Config().DBConfigString); err == nil {
|
||||||
|
th.Server.Logger().Debug("Removed test database", mlog.String("file", th.Server.Config().DBConfigString))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) RegisterAndLogin(client *client.Client, username, email, password, token string) {
|
||||||
|
req := &api.RegisterRequest{
|
||||||
|
Username: username,
|
||||||
|
Email: email,
|
||||||
|
Password: password,
|
||||||
|
Token: token,
|
||||||
|
}
|
||||||
|
|
||||||
|
success, resp := th.Client.Register(req)
|
||||||
|
th.CheckOK(resp)
|
||||||
|
require.True(th.T, success)
|
||||||
|
|
||||||
|
th.Login(client, username, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) Login(client *client.Client, username, password string) {
|
||||||
|
req := &api.LoginRequest{
|
||||||
|
Type: "normal",
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
data, resp := client.Login(req)
|
||||||
|
th.CheckOK(resp)
|
||||||
|
require.NotNil(th.T, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) Login1() {
|
||||||
|
th.Login(th.Client, user1Username, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) Login2() {
|
||||||
|
th.Login(th.Client2, user2Username, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) Logout(client *client.Client) {
|
||||||
|
client.Token = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) Me(client *client.Client) *model.User {
|
||||||
|
user, resp := client.GetMe()
|
||||||
|
th.CheckOK(resp)
|
||||||
|
require.NotNil(th.T, user)
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) CreateBoard(teamID string, boardType model.BoardType) *model.Board {
|
||||||
|
newBoard := &model.Board{
|
||||||
|
TeamID: teamID,
|
||||||
|
Type: boardType,
|
||||||
|
}
|
||||||
|
board, resp := th.Client.CreateBoard(newBoard)
|
||||||
|
th.CheckOK(resp)
|
||||||
|
return board
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) GetUser1() *model.User {
|
||||||
|
return th.Me(th.Client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) GetUser2() *model.User {
|
||||||
|
return th.Me(th.Client2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) CheckOK(r *client.Response) {
|
||||||
|
require.Equal(th.T, http.StatusOK, r.StatusCode)
|
||||||
|
require.NoError(th.T, r.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) CheckBadRequest(r *client.Response) {
|
||||||
|
require.Equal(th.T, http.StatusBadRequest, r.StatusCode)
|
||||||
|
require.Error(th.T, r.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) CheckNotFound(r *client.Response) {
|
||||||
|
require.Equal(th.T, http.StatusNotFound, r.StatusCode)
|
||||||
|
require.Error(th.T, r.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) CheckUnauthorized(r *client.Response) {
|
||||||
|
require.Equal(th.T, http.StatusUnauthorized, r.StatusCode)
|
||||||
|
require.Error(th.T, r.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) CheckForbidden(r *client.Response) {
|
||||||
|
require.Equal(th.T, http.StatusForbidden, r.StatusCode)
|
||||||
|
require.Error(th.T, r.Error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package integrationtests
|
package integrationtests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
|
@ -9,60 +10,84 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSharing(t *testing.T) {
|
func TestSharing(t *testing.T) {
|
||||||
th := SetupTestHelper().InitBasic()
|
th := SetupTestHelper(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
rootID := utils.NewID(utils.IDTypeBlock)
|
var boardID string
|
||||||
token := utils.NewID(utils.IDTypeToken)
|
token := utils.NewID(utils.IDTypeToken)
|
||||||
|
|
||||||
|
t.Run("an unauthenticated client should not be able to get a sharing", func(t *testing.T) {
|
||||||
|
th.Logout(th.Client)
|
||||||
|
|
||||||
|
sharing, resp := th.Client.GetSharing("board-id")
|
||||||
|
th.CheckUnauthorized(resp)
|
||||||
|
require.Nil(t, sharing)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("Check no initial sharing", func(t *testing.T) {
|
t.Run("Check no initial sharing", func(t *testing.T) {
|
||||||
sharing, resp := th.Client.GetSharing(rootID)
|
th.Login1()
|
||||||
require.NoError(t, resp.Error)
|
|
||||||
require.Empty(t, sharing.ID)
|
teamID := "0"
|
||||||
require.False(t, sharing.Enabled)
|
newBoard := &model.Board{
|
||||||
|
TeamID: teamID,
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
}
|
||||||
|
|
||||||
|
board, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, board)
|
||||||
|
boardID = board.ID
|
||||||
|
|
||||||
|
s, err := th.Server.App().GetSharing(boardID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, s)
|
||||||
|
|
||||||
|
sharing, resp := th.Client.GetSharing(boardID)
|
||||||
|
th.CheckNotFound(resp)
|
||||||
|
require.Nil(t, sharing)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("POST sharing, config = false", func(t *testing.T) {
|
t.Run("POST sharing, config = false", func(t *testing.T) {
|
||||||
sharing := model.Sharing{
|
sharing := model.Sharing{
|
||||||
ID: rootID,
|
ID: boardID,
|
||||||
Token: token,
|
Token: token,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
UpdateAt: 1,
|
UpdateAt: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
// it will fail with default config
|
// it will fail with default config
|
||||||
success, resp := th.Client.PostSharing(sharing)
|
success, resp := th.Client.PostSharing(&sharing)
|
||||||
require.False(t, success)
|
require.False(t, success)
|
||||||
require.Error(t, resp.Error)
|
require.Error(t, resp.Error)
|
||||||
|
|
||||||
t.Run("GET sharing", func(t *testing.T) {
|
t.Run("GET sharing", func(t *testing.T) {
|
||||||
sharing, resp := th.Client.GetSharing(rootID)
|
sharing, resp := th.Client.GetSharing(boardID)
|
||||||
// Expect no error, but no Id returned
|
// Expect not found error
|
||||||
require.NoError(t, resp.Error)
|
require.Error(t, resp.Error)
|
||||||
require.NotNil(t, sharing)
|
require.Equal(t, resp.StatusCode, http.StatusNotFound)
|
||||||
require.Equal(t, "", sharing.ID)
|
require.Nil(t, sharing)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("POST sharing, config = true", func(t *testing.T) {
|
t.Run("POST sharing, config = true", func(t *testing.T) {
|
||||||
th.Server.Config().EnablePublicSharedBoards = true
|
th.Server.Config().EnablePublicSharedBoards = true
|
||||||
sharing := model.Sharing{
|
sharing := model.Sharing{
|
||||||
ID: rootID,
|
ID: boardID,
|
||||||
Token: token,
|
Token: token,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
UpdateAt: 1,
|
UpdateAt: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
// it will succeed with updated config
|
// it will succeed with updated config
|
||||||
success, resp := th.Client.PostSharing(sharing)
|
success, resp := th.Client.PostSharing(&sharing)
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
|
|
||||||
t.Run("GET sharing", func(t *testing.T) {
|
t.Run("GET sharing", func(t *testing.T) {
|
||||||
sharing, resp := th.Client.GetSharing(rootID)
|
sharing, resp := th.Client.GetSharing(boardID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.NotNil(t, sharing)
|
require.NotNil(t, sharing)
|
||||||
require.Equal(t, sharing.ID, rootID)
|
require.Equal(t, sharing.ID, boardID)
|
||||||
require.True(t, sharing.Enabled)
|
require.True(t, sharing.Enabled)
|
||||||
require.Equal(t, sharing.Token, token)
|
require.Equal(t, sharing.Token, token)
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,13 +6,12 @@ import (
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/client"
|
"github.com/mattermost/focalboard/server/client"
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
|
||||||
"github.com/mattermost/focalboard/server/utils"
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createTestSubscriptions(client *client.Client, num int, workspaceID string) ([]*model.Subscription, string, error) {
|
func createTestSubscriptions(client *client.Client, num int) ([]*model.Subscription, string, error) {
|
||||||
newSubs := make([]*model.Subscription, 0, num)
|
newSubs := make([]*model.Subscription, 0, num)
|
||||||
|
|
||||||
user, resp := client.GetMe()
|
user, resp := client.GetMe()
|
||||||
|
@ -20,29 +19,27 @@ func createTestSubscriptions(client *client.Client, num int, workspaceID string)
|
||||||
return nil, "", fmt.Errorf("cannot get current user: %w", resp.Error)
|
return nil, "", fmt.Errorf("cannot get current user: %w", resp.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
board := model.Block{
|
board := &model.Board{
|
||||||
ID: utils.NewID(utils.IDTypeBoard),
|
TeamID: "0",
|
||||||
RootID: workspaceID,
|
Type: model.BoardTypeOpen,
|
||||||
CreateAt: 1,
|
CreateAt: 1,
|
||||||
UpdateAt: 1,
|
UpdateAt: 1,
|
||||||
Type: model.TypeBoard,
|
|
||||||
}
|
}
|
||||||
boards, resp := client.InsertBlocks([]model.Block{board})
|
board, resp = client.CreateBoard(board)
|
||||||
if resp.Error != nil {
|
if resp.Error != nil {
|
||||||
return nil, "", fmt.Errorf("cannot insert test board block: %w", resp.Error)
|
return nil, "", fmt.Errorf("cannot insert test board block: %w", resp.Error)
|
||||||
}
|
}
|
||||||
board = boards[0]
|
|
||||||
|
|
||||||
for n := 0; n < num; n++ {
|
for n := 0; n < num; n++ {
|
||||||
newBlock := model.Block{
|
newBlock := model.Block{
|
||||||
ID: utils.NewID(utils.IDTypeCard),
|
ID: utils.NewID(utils.IDTypeCard),
|
||||||
RootID: board.ID,
|
BoardID: board.ID,
|
||||||
CreateAt: 1,
|
CreateAt: 1,
|
||||||
UpdateAt: 1,
|
UpdateAt: 1,
|
||||||
Type: model.TypeCard,
|
Type: model.TypeCard,
|
||||||
}
|
}
|
||||||
|
|
||||||
newBlocks, resp := client.InsertBlocks([]model.Block{newBlock})
|
newBlocks, resp := client.InsertBlocks(board.ID, []model.Block{newBlock})
|
||||||
if resp.Error != nil {
|
if resp.Error != nil {
|
||||||
return nil, "", fmt.Errorf("cannot insert test card block: %w", resp.Error)
|
return nil, "", fmt.Errorf("cannot insert test card block: %w", resp.Error)
|
||||||
}
|
}
|
||||||
|
@ -51,12 +48,11 @@ func createTestSubscriptions(client *client.Client, num int, workspaceID string)
|
||||||
sub := &model.Subscription{
|
sub := &model.Subscription{
|
||||||
BlockType: newBlock.Type,
|
BlockType: newBlock.Type,
|
||||||
BlockID: newBlock.ID,
|
BlockID: newBlock.ID,
|
||||||
WorkspaceID: workspaceID,
|
|
||||||
SubscriberType: model.SubTypeUser,
|
SubscriberType: model.SubTypeUser,
|
||||||
SubscriberID: user.ID,
|
SubscriberID: user.ID,
|
||||||
}
|
}
|
||||||
|
|
||||||
subNew, resp := client.CreateSubscription(workspaceID, sub)
|
subNew, resp := client.CreateSubscription(sub)
|
||||||
if resp.Error != nil {
|
if resp.Error != nil {
|
||||||
return nil, "", resp.Error
|
return nil, "", resp.Error
|
||||||
}
|
}
|
||||||
|
@ -66,20 +62,16 @@ func createTestSubscriptions(client *client.Client, num int, workspaceID string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateSubscription(t *testing.T) {
|
func TestCreateSubscription(t *testing.T) {
|
||||||
th := SetupTestHelper().InitBasic()
|
th := SetupTestHelper(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
container := store.Container{
|
|
||||||
WorkspaceID: utils.NewID(utils.IDTypeWorkspace),
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Create valid subscription", func(t *testing.T) {
|
t.Run("Create valid subscription", func(t *testing.T) {
|
||||||
subs, userID, err := createTestSubscriptions(th.Client, 5, container.WorkspaceID)
|
subs, userID, err := createTestSubscriptions(th.Client, 5)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, subs, 5)
|
require.Len(t, subs, 5)
|
||||||
|
|
||||||
// fetch the newly created subscriptions and compare
|
// fetch the newly created subscriptions and compare
|
||||||
subsFound, resp := th.Client.GetSubscriptions(container.WorkspaceID, userID)
|
subsFound, resp := th.Client.GetSubscriptions(userID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, subsFound, 5)
|
require.Len(t, subsFound, 5)
|
||||||
assert.ElementsMatch(t, subs, subsFound)
|
assert.ElementsMatch(t, subs, subsFound)
|
||||||
|
@ -90,47 +82,38 @@ func TestCreateSubscription(t *testing.T) {
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
|
|
||||||
sub := &model.Subscription{
|
sub := &model.Subscription{
|
||||||
WorkspaceID: container.WorkspaceID,
|
|
||||||
SubscriberID: user.ID,
|
SubscriberID: user.ID,
|
||||||
}
|
}
|
||||||
_, resp = th.Client.CreateSubscription(container.WorkspaceID, sub)
|
_, resp = th.Client.CreateSubscription(sub)
|
||||||
require.Error(t, resp.Error)
|
require.Error(t, resp.Error)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Create subscription for another user", func(t *testing.T) {
|
t.Run("Create subscription for another user", func(t *testing.T) {
|
||||||
sub := &model.Subscription{
|
sub := &model.Subscription{
|
||||||
WorkspaceID: container.WorkspaceID,
|
|
||||||
SubscriberID: utils.NewID(utils.IDTypeUser),
|
SubscriberID: utils.NewID(utils.IDTypeUser),
|
||||||
}
|
}
|
||||||
_, resp := th.Client.CreateSubscription(container.WorkspaceID, sub)
|
_, resp := th.Client.CreateSubscription(sub)
|
||||||
require.Error(t, resp.Error)
|
require.Error(t, resp.Error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetSubscriptions(t *testing.T) {
|
func TestGetSubscriptions(t *testing.T) {
|
||||||
th := SetupTestHelperWithoutToken().InitBasic()
|
th := SetupTestHelper(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
err := th.InitUsers("user1", "user2")
|
|
||||||
require.NoError(t, err, "failed to init users")
|
|
||||||
|
|
||||||
container := store.Container{
|
|
||||||
WorkspaceID: utils.NewID(utils.IDTypeWorkspace),
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Get subscriptions for user", func(t *testing.T) {
|
t.Run("Get subscriptions for user", func(t *testing.T) {
|
||||||
mySubs, user1ID, err := createTestSubscriptions(th.Client, 5, container.WorkspaceID)
|
mySubs, user1ID, err := createTestSubscriptions(th.Client, 5)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, mySubs, 5)
|
require.Len(t, mySubs, 5)
|
||||||
|
|
||||||
// create more subscriptions with different user
|
// create more subscriptions with different user
|
||||||
otherSubs, _, err := createTestSubscriptions(th.Client2, 10, container.WorkspaceID)
|
otherSubs, _, err := createTestSubscriptions(th.Client2, 10)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, otherSubs, 10)
|
require.Len(t, otherSubs, 10)
|
||||||
|
|
||||||
// fetch the newly created subscriptions for current user, making sure only
|
// fetch the newly created subscriptions for current user, making sure only
|
||||||
// the ones created for the current user are returned.
|
// the ones created for the current user are returned.
|
||||||
subsFound, resp := th.Client.GetSubscriptions(container.WorkspaceID, user1ID)
|
subsFound, resp := th.Client.GetSubscriptions(user1ID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, subsFound, 5)
|
require.Len(t, subsFound, 5)
|
||||||
assert.ElementsMatch(t, mySubs, subsFound)
|
assert.ElementsMatch(t, mySubs, subsFound)
|
||||||
|
@ -138,23 +121,19 @@ func TestGetSubscriptions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteSubscription(t *testing.T) {
|
func TestDeleteSubscription(t *testing.T) {
|
||||||
th := SetupTestHelper().InitBasic()
|
th := SetupTestHelper(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
container := store.Container{
|
|
||||||
WorkspaceID: utils.NewID(utils.IDTypeWorkspace),
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Delete valid subscription", func(t *testing.T) {
|
t.Run("Delete valid subscription", func(t *testing.T) {
|
||||||
subs, userID, err := createTestSubscriptions(th.Client, 3, container.WorkspaceID)
|
subs, userID, err := createTestSubscriptions(th.Client, 3)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, subs, 3)
|
require.Len(t, subs, 3)
|
||||||
|
|
||||||
resp := th.Client.DeleteSubscription(container.WorkspaceID, subs[1].BlockID, userID)
|
resp := th.Client.DeleteSubscription(subs[1].BlockID, userID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
|
|
||||||
// fetch the subscriptions and ensure the list is correct
|
// fetch the subscriptions and ensure the list is correct
|
||||||
subsFound, resp := th.Client.GetSubscriptions(container.WorkspaceID, userID)
|
subsFound, resp := th.Client.GetSubscriptions(userID)
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.Len(t, subsFound, 2)
|
require.Len(t, subsFound, 2)
|
||||||
|
|
||||||
|
@ -167,7 +146,7 @@ func TestDeleteSubscription(t *testing.T) {
|
||||||
user, resp := th.Client.GetMe()
|
user, resp := th.Client.GetMe()
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
|
|
||||||
resp = th.Client.DeleteSubscription(container.WorkspaceID, "bogus", user.ID)
|
resp = th.Client.DeleteSubscription("bogus", user.ID)
|
||||||
require.Error(t, resp.Error)
|
require.Error(t, resp.Error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/api"
|
"github.com/mattermost/focalboard/server/api"
|
||||||
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/utils"
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -16,7 +17,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUserRegister(t *testing.T) {
|
func TestUserRegister(t *testing.T) {
|
||||||
th := SetupTestHelperWithoutToken().InitBasic()
|
th := SetupTestHelper(t).Start()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
// register
|
// register
|
||||||
|
@ -29,14 +30,14 @@ func TestUserRegister(t *testing.T) {
|
||||||
require.NoError(t, resp.Error)
|
require.NoError(t, resp.Error)
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// register again will failed
|
// register again will fail
|
||||||
success, resp = th.Client.Register(registerRequest)
|
success, resp = th.Client.Register(registerRequest)
|
||||||
require.Error(t, resp.Error)
|
require.Error(t, resp.Error)
|
||||||
require.False(t, success)
|
require.False(t, success)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserLogin(t *testing.T) {
|
func TestUserLogin(t *testing.T) {
|
||||||
th := SetupTestHelperWithoutToken().InitBasic()
|
th := SetupTestHelper(t).Start()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
t.Run("with nonexist user", func(t *testing.T) {
|
t.Run("with nonexist user", func(t *testing.T) {
|
||||||
|
@ -78,7 +79,7 @@ func TestUserLogin(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetMe(t *testing.T) {
|
func TestGetMe(t *testing.T) {
|
||||||
th := SetupTestHelperWithoutToken().InitBasic()
|
th := SetupTestHelper(t).Start()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
t.Run("not login yet", func(t *testing.T) {
|
t.Run("not login yet", func(t *testing.T) {
|
||||||
|
@ -120,7 +121,7 @@ func TestGetMe(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetUser(t *testing.T) {
|
func TestGetUser(t *testing.T) {
|
||||||
th := SetupTestHelperWithoutToken().InitBasic()
|
th := SetupTestHelper(t).Start()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
// register
|
// register
|
||||||
|
@ -165,7 +166,7 @@ func TestGetUser(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserChangePassword(t *testing.T) {
|
func TestUserChangePassword(t *testing.T) {
|
||||||
th := SetupTestHelperWithoutToken().InitBasic()
|
th := SetupTestHelper(t).Start()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
// register
|
// register
|
||||||
|
@ -210,30 +211,58 @@ func randomBytes(t *testing.T, n int) []byte {
|
||||||
return bb
|
return bb
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWorkspaceUploadFile(t *testing.T) {
|
func TestTeamUploadFile(t *testing.T) {
|
||||||
t.Run("no permission", func(t *testing.T) { // native auth, but not login
|
t.Run("no permission", func(t *testing.T) { // native auth, but not login
|
||||||
th := SetupTestHelperWithoutToken().InitBasic()
|
th := SetupTestHelper(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
workspaceID := "0"
|
teamID := "0"
|
||||||
rootID := utils.NewID(utils.IDTypeBlock)
|
boardID := utils.NewID(utils.IDTypeBoard)
|
||||||
data := randomBytes(t, 1024)
|
data := randomBytes(t, 1024)
|
||||||
result, resp := th.Client.WorkspaceUploadFile(workspaceID, rootID, bytes.NewReader(data))
|
result, resp := th.Client.TeamUploadFile(teamID, boardID, bytes.NewReader(data))
|
||||||
require.Error(t, resp.Error)
|
require.Error(t, resp.Error)
|
||||||
require.Nil(t, result)
|
require.Nil(t, result)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("success", func(t *testing.T) { // single token auth
|
t.Run("a board admin should be able to update a file", func(t *testing.T) { // single token auth
|
||||||
th := SetupTestHelper().InitBasic()
|
th := SetupTestHelper(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
workspaceID := "0"
|
teamID := "0"
|
||||||
rootID := utils.NewID(utils.IDTypeBlock)
|
newBoard := &model.Board{
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
TeamID: teamID,
|
||||||
|
}
|
||||||
|
board, resp := th.Client.CreateBoard(newBoard)
|
||||||
|
th.CheckOK(resp)
|
||||||
|
require.NotNil(t, board)
|
||||||
|
|
||||||
data := randomBytes(t, 1024)
|
data := randomBytes(t, 1024)
|
||||||
result, resp := th.Client.WorkspaceUploadFile(workspaceID, rootID, bytes.NewReader(data))
|
result, resp := th.Client.TeamUploadFile(teamID, board.ID, bytes.NewReader(data))
|
||||||
require.NoError(t, resp.Error)
|
th.CheckOK(resp)
|
||||||
require.NotNil(t, result)
|
require.NotNil(t, result)
|
||||||
require.NotEmpty(t, result.FileID)
|
require.NotEmpty(t, result.FileID)
|
||||||
// TODO get the uploaded file
|
// TODO get the uploaded file
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("user that doesn't belong to the board should not be able to upload a file", func(t *testing.T) {
|
||||||
|
th := SetupTestHelper(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
teamID := "0"
|
||||||
|
newBoard := &model.Board{
|
||||||
|
Type: model.BoardTypeOpen,
|
||||||
|
TeamID: teamID,
|
||||||
|
}
|
||||||
|
board, resp := th.Client.CreateBoard(newBoard)
|
||||||
|
th.CheckOK(resp)
|
||||||
|
require.NotNil(t, board)
|
||||||
|
|
||||||
|
data := randomBytes(t, 1024)
|
||||||
|
|
||||||
|
// a user that doesn't belong to the board tries to upload the file
|
||||||
|
result, resp := th.Client2.TeamUploadFile(teamID, board.ID, bytes.NewReader(data))
|
||||||
|
th.CheckForbidden(resp)
|
||||||
|
require.Nil(t, result)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/server"
|
"github.com/mattermost/focalboard/server/server"
|
||||||
"github.com/mattermost/focalboard/server/services/config"
|
"github.com/mattermost/focalboard/server/services/config"
|
||||||
|
"github.com/mattermost/focalboard/server/services/permissions/localpermissions"
|
||||||
)
|
)
|
||||||
import (
|
import (
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
|
@ -145,11 +146,14 @@ func main() {
|
||||||
logger.Fatal("server.NewStore ERROR", mlog.Err(err))
|
logger.Fatal("server.NewStore ERROR", mlog.Err(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
permissionsService := localpermissions.New(db, logger)
|
||||||
|
|
||||||
params := server.Params{
|
params := server.Params{
|
||||||
Cfg: config,
|
Cfg: config,
|
||||||
SingleUserToken: singleUserToken,
|
SingleUserToken: singleUserToken,
|
||||||
DBStore: db,
|
DBStore: db,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
|
PermissionsService: permissionsService,
|
||||||
}
|
}
|
||||||
|
|
||||||
server, err := server.New(params)
|
server, err := server.New(params)
|
||||||
|
@ -233,11 +237,14 @@ func startServer(webPath string, filesPath string, port int, singleUserToken, db
|
||||||
logger.Fatal("server.NewStore ERROR", mlog.Err(err))
|
logger.Fatal("server.NewStore ERROR", mlog.Err(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
permissionsService := localpermissions.New(db, logger)
|
||||||
|
|
||||||
params := server.Params{
|
params := server.Params{
|
||||||
Cfg: config,
|
Cfg: config,
|
||||||
SingleUserToken: singleUserToken,
|
SingleUserToken: singleUserToken,
|
||||||
DBStore: db,
|
DBStore: db,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
|
PermissionsService: permissionsService,
|
||||||
}
|
}
|
||||||
|
|
||||||
pServer, err = server.New(params)
|
pServer, err = server.New(params)
|
||||||
|
|
|
@ -19,10 +19,6 @@ type Block struct {
|
||||||
// required: false
|
// required: false
|
||||||
ParentID string `json:"parentId"`
|
ParentID string `json:"parentId"`
|
||||||
|
|
||||||
// The id for this block's root block
|
|
||||||
// required: true
|
|
||||||
RootID string `json:"rootId"`
|
|
||||||
|
|
||||||
// The id for user who created this block
|
// The id for user who created this block
|
||||||
// required: true
|
// required: true
|
||||||
CreatedBy string `json:"createdBy"`
|
CreatedBy string `json:"createdBy"`
|
||||||
|
@ -59,9 +55,13 @@ type Block struct {
|
||||||
// required: false
|
// required: false
|
||||||
DeleteAt int64 `json:"deleteAt"`
|
DeleteAt int64 `json:"deleteAt"`
|
||||||
|
|
||||||
// The workspace id that the block belongs to
|
// Deprecated. The workspace id that the block belongs to
|
||||||
|
// required: false
|
||||||
|
WorkspaceID string `json:"-"`
|
||||||
|
|
||||||
|
// The board id that the block belongs to
|
||||||
// required: true
|
// required: true
|
||||||
WorkspaceID string `json:"workspaceId"`
|
BoardID string `json:"boardId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlockPatch is a patch for modify blocks
|
// BlockPatch is a patch for modify blocks
|
||||||
|
@ -71,10 +71,6 @@ type BlockPatch struct {
|
||||||
// required: false
|
// required: false
|
||||||
ParentID *string `json:"parentId"`
|
ParentID *string `json:"parentId"`
|
||||||
|
|
||||||
// The id for this block's root block
|
|
||||||
// required: false
|
|
||||||
RootID *string `json:"rootId"`
|
|
||||||
|
|
||||||
// The schema version of this block
|
// The schema version of this block
|
||||||
// required: false
|
// required: false
|
||||||
Schema *int64 `json:"schema"`
|
Schema *int64 `json:"schema"`
|
||||||
|
@ -94,6 +90,10 @@ type BlockPatch struct {
|
||||||
// The block removed fields
|
// The block removed fields
|
||||||
// required: false
|
// required: false
|
||||||
DeletedFields []string `json:"deletedFields"`
|
DeletedFields []string `json:"deletedFields"`
|
||||||
|
|
||||||
|
// The board id that the block belongs to
|
||||||
|
// required: false
|
||||||
|
BoardID *string `json:"boardId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlockPatchBatch is a batch of IDs and patches for modify blocks
|
// BlockPatchBatch is a batch of IDs and patches for modify blocks
|
||||||
|
@ -106,11 +106,11 @@ type BlockPatchBatch struct {
|
||||||
BlockPatches []BlockPatch `json:"block_patches"`
|
BlockPatches []BlockPatch `json:"block_patches"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlockModifier is a callback that can modify each block during an import.
|
// BoardModifier is a callback that can modify each board during an import.
|
||||||
// A cache of arbitrary data will be passed for each call and any changes
|
// A cache of arbitrary data will be passed for each call and any changes
|
||||||
// to the cache will be preserved for the next call.
|
// to the cache will be preserved for the next call.
|
||||||
// Return true to import the block or false to skip import.
|
// Return true to import the block or false to skip import.
|
||||||
type BlockModifier func(block *Block, cache map[string]interface{}) bool
|
type BoardModifier func(board *Board, cache map[string]interface{}) bool
|
||||||
|
|
||||||
func BlocksFromJSON(data io.Reader) []Block {
|
func BlocksFromJSON(data io.Reader) []Block {
|
||||||
var blocks []Block
|
var blocks []Block
|
||||||
|
@ -123,12 +123,12 @@ func (b Block) LogClone() interface{} {
|
||||||
return struct {
|
return struct {
|
||||||
ID string
|
ID string
|
||||||
ParentID string
|
ParentID string
|
||||||
RootID string
|
BoardID string
|
||||||
Type BlockType
|
Type BlockType
|
||||||
}{
|
}{
|
||||||
ID: b.ID,
|
ID: b.ID,
|
||||||
ParentID: b.ParentID,
|
ParentID: b.ParentID,
|
||||||
RootID: b.RootID,
|
BoardID: b.BoardID,
|
||||||
Type: b.Type,
|
Type: b.Type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,8 +139,8 @@ func (p *BlockPatch) Patch(block *Block) *Block {
|
||||||
block.ParentID = *p.ParentID
|
block.ParentID = *p.ParentID
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.RootID != nil {
|
if p.BoardID != nil {
|
||||||
block.RootID = *p.RootID
|
block.BoardID = *p.BoardID
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Schema != nil {
|
if p.Schema != nil {
|
||||||
|
|
|
@ -20,61 +20,61 @@ func TestGenerateBlockIDs(t *testing.T) {
|
||||||
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
||||||
|
|
||||||
require.NotEqual(t, blockID, blocks[0].ID)
|
require.NotEqual(t, blockID, blocks[0].ID)
|
||||||
require.Zero(t, blocks[0].RootID)
|
require.Zero(t, blocks[0].BoardID)
|
||||||
require.Zero(t, blocks[0].ParentID)
|
require.Zero(t, blocks[0].ParentID)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Should generate a new ID for a single block with references", func(t *testing.T) {
|
t.Run("Should generate a new ID for a single block with references", func(t *testing.T) {
|
||||||
blockID := utils.NewID(utils.IDTypeBlock)
|
blockID := utils.NewID(utils.IDTypeBlock)
|
||||||
rootID := utils.NewID(utils.IDTypeBlock)
|
boardID := utils.NewID(utils.IDTypeBlock)
|
||||||
parentID := utils.NewID(utils.IDTypeBlock)
|
parentID := utils.NewID(utils.IDTypeBlock)
|
||||||
blocks := []Block{{ID: blockID, RootID: rootID, ParentID: parentID}}
|
blocks := []Block{{ID: blockID, BoardID: boardID, ParentID: parentID}}
|
||||||
|
|
||||||
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
||||||
|
|
||||||
require.NotEqual(t, blockID, blocks[0].ID)
|
require.NotEqual(t, blockID, blocks[0].ID)
|
||||||
require.Equal(t, rootID, blocks[0].RootID)
|
require.Equal(t, boardID, blocks[0].BoardID)
|
||||||
require.Equal(t, parentID, blocks[0].ParentID)
|
require.Equal(t, parentID, blocks[0].ParentID)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Should generate IDs and link multiple blocks with existing references", func(t *testing.T) {
|
t.Run("Should generate IDs and link multiple blocks with existing references", func(t *testing.T) {
|
||||||
blockID1 := utils.NewID(utils.IDTypeBlock)
|
blockID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
rootID1 := utils.NewID(utils.IDTypeBlock)
|
boardID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
parentID1 := utils.NewID(utils.IDTypeBlock)
|
parentID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
block1 := Block{ID: blockID1, RootID: rootID1, ParentID: parentID1}
|
block1 := Block{ID: blockID1, BoardID: boardID1, ParentID: parentID1}
|
||||||
|
|
||||||
blockID2 := utils.NewID(utils.IDTypeBlock)
|
blockID2 := utils.NewID(utils.IDTypeBlock)
|
||||||
rootID2 := blockID1
|
boardID2 := blockID1
|
||||||
parentID2 := utils.NewID(utils.IDTypeBlock)
|
parentID2 := utils.NewID(utils.IDTypeBlock)
|
||||||
block2 := Block{ID: blockID2, RootID: rootID2, ParentID: parentID2}
|
block2 := Block{ID: blockID2, BoardID: boardID2, ParentID: parentID2}
|
||||||
|
|
||||||
blocks := []Block{block1, block2}
|
blocks := []Block{block1, block2}
|
||||||
|
|
||||||
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
||||||
|
|
||||||
require.NotEqual(t, blockID1, blocks[0].ID)
|
require.NotEqual(t, blockID1, blocks[0].ID)
|
||||||
require.Equal(t, rootID1, blocks[0].RootID)
|
require.Equal(t, boardID1, blocks[0].BoardID)
|
||||||
require.Equal(t, parentID1, blocks[0].ParentID)
|
require.Equal(t, parentID1, blocks[0].ParentID)
|
||||||
|
|
||||||
require.NotEqual(t, blockID2, blocks[1].ID)
|
require.NotEqual(t, blockID2, blocks[1].ID)
|
||||||
require.NotEqual(t, rootID2, blocks[1].RootID)
|
require.NotEqual(t, boardID2, blocks[1].BoardID)
|
||||||
require.Equal(t, parentID2, blocks[1].ParentID)
|
require.Equal(t, parentID2, blocks[1].ParentID)
|
||||||
|
|
||||||
// blockID1 was referenced, so it should still be after the ID
|
// blockID1 was referenced, so it should still be after the ID
|
||||||
// changes
|
// changes
|
||||||
require.Equal(t, blocks[0].ID, blocks[1].RootID)
|
require.Equal(t, blocks[0].ID, blocks[1].BoardID)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Should generate new IDs but not modify nonexisting references", func(t *testing.T) {
|
t.Run("Should generate new IDs but not modify nonexisting references", func(t *testing.T) {
|
||||||
blockID1 := utils.NewID(utils.IDTypeBlock)
|
blockID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
rootID1 := ""
|
boardID1 := ""
|
||||||
parentID1 := utils.NewID(utils.IDTypeBlock)
|
parentID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
block1 := Block{ID: blockID1, RootID: rootID1, ParentID: parentID1}
|
block1 := Block{ID: blockID1, BoardID: boardID1, ParentID: parentID1}
|
||||||
|
|
||||||
blockID2 := utils.NewID(utils.IDTypeBlock)
|
blockID2 := utils.NewID(utils.IDTypeBlock)
|
||||||
rootID2 := utils.NewID(utils.IDTypeBlock)
|
boardID2 := utils.NewID(utils.IDTypeBlock)
|
||||||
parentID2 := ""
|
parentID2 := ""
|
||||||
block2 := Block{ID: blockID2, RootID: rootID2, ParentID: parentID2}
|
block2 := Block{ID: blockID2, BoardID: boardID2, ParentID: parentID2}
|
||||||
|
|
||||||
blocks := []Block{block1, block2}
|
blocks := []Block{block1, block2}
|
||||||
|
|
||||||
|
@ -82,37 +82,37 @@ func TestGenerateBlockIDs(t *testing.T) {
|
||||||
|
|
||||||
// only the IDs should have changed
|
// only the IDs should have changed
|
||||||
require.NotEqual(t, blockID1, blocks[0].ID)
|
require.NotEqual(t, blockID1, blocks[0].ID)
|
||||||
require.Zero(t, blocks[0].RootID)
|
require.Zero(t, blocks[0].BoardID)
|
||||||
require.Equal(t, parentID1, blocks[0].ParentID)
|
require.Equal(t, parentID1, blocks[0].ParentID)
|
||||||
|
|
||||||
require.NotEqual(t, blockID2, blocks[1].ID)
|
require.NotEqual(t, blockID2, blocks[1].ID)
|
||||||
require.Equal(t, rootID2, blocks[1].RootID)
|
require.Equal(t, boardID2, blocks[1].BoardID)
|
||||||
require.Zero(t, blocks[1].ParentID)
|
require.Zero(t, blocks[1].ParentID)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Should modify correctly multiple blocks with existing and nonexisting references", func(t *testing.T) {
|
t.Run("Should modify correctly multiple blocks with existing and nonexisting references", func(t *testing.T) {
|
||||||
blockID1 := utils.NewID(utils.IDTypeBlock)
|
blockID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
rootID1 := utils.NewID(utils.IDTypeBlock)
|
boardID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
parentID1 := utils.NewID(utils.IDTypeBlock)
|
parentID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
block1 := Block{ID: blockID1, RootID: rootID1, ParentID: parentID1}
|
block1 := Block{ID: blockID1, BoardID: boardID1, ParentID: parentID1}
|
||||||
|
|
||||||
// linked to 1
|
// linked to 1
|
||||||
blockID2 := utils.NewID(utils.IDTypeBlock)
|
blockID2 := utils.NewID(utils.IDTypeBlock)
|
||||||
rootID2 := blockID1
|
boardID2 := blockID1
|
||||||
parentID2 := utils.NewID(utils.IDTypeBlock)
|
parentID2 := utils.NewID(utils.IDTypeBlock)
|
||||||
block2 := Block{ID: blockID2, RootID: rootID2, ParentID: parentID2}
|
block2 := Block{ID: blockID2, BoardID: boardID2, ParentID: parentID2}
|
||||||
|
|
||||||
// linked to 2
|
// linked to 2
|
||||||
blockID3 := utils.NewID(utils.IDTypeBlock)
|
blockID3 := utils.NewID(utils.IDTypeBlock)
|
||||||
rootID3 := blockID2
|
boardID3 := blockID2
|
||||||
parentID3 := utils.NewID(utils.IDTypeBlock)
|
parentID3 := utils.NewID(utils.IDTypeBlock)
|
||||||
block3 := Block{ID: blockID3, RootID: rootID3, ParentID: parentID3}
|
block3 := Block{ID: blockID3, BoardID: boardID3, ParentID: parentID3}
|
||||||
|
|
||||||
// linked to 1
|
// linked to 1
|
||||||
blockID4 := utils.NewID(utils.IDTypeBlock)
|
blockID4 := utils.NewID(utils.IDTypeBlock)
|
||||||
rootID4 := blockID1
|
boardID4 := blockID1
|
||||||
parentID4 := utils.NewID(utils.IDTypeBlock)
|
parentID4 := utils.NewID(utils.IDTypeBlock)
|
||||||
block4 := Block{ID: blockID4, RootID: rootID4, ParentID: parentID4}
|
block4 := Block{ID: blockID4, BoardID: boardID4, ParentID: parentID4}
|
||||||
|
|
||||||
// blocks are shuffled
|
// blocks are shuffled
|
||||||
blocks := []Block{block4, block2, block1, block3}
|
blocks := []Block{block4, block2, block1, block3}
|
||||||
|
@ -121,44 +121,44 @@ func TestGenerateBlockIDs(t *testing.T) {
|
||||||
|
|
||||||
// block 1
|
// block 1
|
||||||
require.NotEqual(t, blockID1, blocks[2].ID)
|
require.NotEqual(t, blockID1, blocks[2].ID)
|
||||||
require.Equal(t, rootID1, blocks[2].RootID)
|
require.Equal(t, boardID1, blocks[2].BoardID)
|
||||||
require.Equal(t, parentID1, blocks[2].ParentID)
|
require.Equal(t, parentID1, blocks[2].ParentID)
|
||||||
|
|
||||||
// block 2
|
// block 2
|
||||||
require.NotEqual(t, blockID2, blocks[1].ID)
|
require.NotEqual(t, blockID2, blocks[1].ID)
|
||||||
require.NotEqual(t, rootID2, blocks[1].RootID)
|
require.NotEqual(t, boardID2, blocks[1].BoardID)
|
||||||
require.Equal(t, blocks[2].ID, blocks[1].RootID) // link to 1
|
require.Equal(t, blocks[2].ID, blocks[1].BoardID) // link to 1
|
||||||
require.Equal(t, parentID2, blocks[1].ParentID)
|
require.Equal(t, parentID2, blocks[1].ParentID)
|
||||||
|
|
||||||
// block 3
|
// block 3
|
||||||
require.NotEqual(t, blockID3, blocks[3].ID)
|
require.NotEqual(t, blockID3, blocks[3].ID)
|
||||||
require.NotEqual(t, rootID3, blocks[3].RootID)
|
require.NotEqual(t, boardID3, blocks[3].BoardID)
|
||||||
require.Equal(t, blocks[1].ID, blocks[3].RootID) // link to 2
|
require.Equal(t, blocks[1].ID, blocks[3].BoardID) // link to 2
|
||||||
require.Equal(t, parentID3, blocks[3].ParentID)
|
require.Equal(t, parentID3, blocks[3].ParentID)
|
||||||
|
|
||||||
// block 4
|
// block 4
|
||||||
require.NotEqual(t, blockID4, blocks[0].ID)
|
require.NotEqual(t, blockID4, blocks[0].ID)
|
||||||
require.NotEqual(t, rootID4, blocks[0].RootID)
|
require.NotEqual(t, boardID4, blocks[0].BoardID)
|
||||||
require.Equal(t, blocks[2].ID, blocks[0].RootID) // link to 1
|
require.Equal(t, blocks[2].ID, blocks[0].BoardID) // link to 1
|
||||||
require.Equal(t, parentID4, blocks[0].ParentID)
|
require.Equal(t, parentID4, blocks[0].ParentID)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Should update content order", func(t *testing.T) {
|
t.Run("Should update content order", func(t *testing.T) {
|
||||||
blockID1 := utils.NewID(utils.IDTypeBlock)
|
blockID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
rootID1 := utils.NewID(utils.IDTypeBlock)
|
boardID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
parentID1 := utils.NewID(utils.IDTypeBlock)
|
parentID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
block1 := Block{
|
block1 := Block{
|
||||||
ID: blockID1,
|
ID: blockID1,
|
||||||
RootID: rootID1,
|
BoardID: boardID1,
|
||||||
ParentID: parentID1,
|
ParentID: parentID1,
|
||||||
}
|
}
|
||||||
|
|
||||||
blockID2 := utils.NewID(utils.IDTypeBlock)
|
blockID2 := utils.NewID(utils.IDTypeBlock)
|
||||||
rootID2 := utils.NewID(utils.IDTypeBlock)
|
boardID2 := utils.NewID(utils.IDTypeBlock)
|
||||||
parentID2 := utils.NewID(utils.IDTypeBlock)
|
parentID2 := utils.NewID(utils.IDTypeBlock)
|
||||||
block2 := Block{
|
block2 := Block{
|
||||||
ID: blockID2,
|
ID: blockID2,
|
||||||
RootID: rootID2,
|
BoardID: boardID2,
|
||||||
ParentID: parentID2,
|
ParentID: parentID2,
|
||||||
Fields: map[string]interface{}{
|
Fields: map[string]interface{}{
|
||||||
"contentOrder": []interface{}{
|
"contentOrder": []interface{}{
|
||||||
|
@ -172,11 +172,11 @@ func TestGenerateBlockIDs(t *testing.T) {
|
||||||
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
||||||
|
|
||||||
require.NotEqual(t, blockID1, blocks[0].ID)
|
require.NotEqual(t, blockID1, blocks[0].ID)
|
||||||
require.Equal(t, rootID1, blocks[0].RootID)
|
require.Equal(t, boardID1, blocks[0].BoardID)
|
||||||
require.Equal(t, parentID1, blocks[0].ParentID)
|
require.Equal(t, parentID1, blocks[0].ParentID)
|
||||||
|
|
||||||
require.NotEqual(t, blockID2, blocks[1].ID)
|
require.NotEqual(t, blockID2, blocks[1].ID)
|
||||||
require.Equal(t, rootID2, blocks[1].RootID)
|
require.Equal(t, boardID2, blocks[1].BoardID)
|
||||||
require.Equal(t, parentID2, blocks[1].ParentID)
|
require.Equal(t, parentID2, blocks[1].ParentID)
|
||||||
|
|
||||||
// since block 1 was referenced in block 2,
|
// since block 1 was referenced in block 2,
|
||||||
|
@ -189,35 +189,35 @@ func TestGenerateBlockIDs(t *testing.T) {
|
||||||
|
|
||||||
t.Run("Should update content order when it contain slices", func(t *testing.T) {
|
t.Run("Should update content order when it contain slices", func(t *testing.T) {
|
||||||
blockID1 := utils.NewID(utils.IDTypeBlock)
|
blockID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
rootID1 := utils.NewID(utils.IDTypeBlock)
|
boardID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
parentID1 := utils.NewID(utils.IDTypeBlock)
|
parentID1 := utils.NewID(utils.IDTypeBlock)
|
||||||
block1 := Block{
|
block1 := Block{
|
||||||
ID: blockID1,
|
ID: blockID1,
|
||||||
RootID: rootID1,
|
BoardID: boardID1,
|
||||||
ParentID: parentID1,
|
ParentID: parentID1,
|
||||||
}
|
}
|
||||||
|
|
||||||
blockID2 := utils.NewID(utils.IDTypeBlock)
|
blockID2 := utils.NewID(utils.IDTypeBlock)
|
||||||
block2 := Block{
|
block2 := Block{
|
||||||
ID: blockID2,
|
ID: blockID2,
|
||||||
RootID: rootID1,
|
BoardID: boardID1,
|
||||||
ParentID: parentID1,
|
ParentID: parentID1,
|
||||||
}
|
}
|
||||||
|
|
||||||
blockID3 := utils.NewID(utils.IDTypeBlock)
|
blockID3 := utils.NewID(utils.IDTypeBlock)
|
||||||
block3 := Block{
|
block3 := Block{
|
||||||
ID: blockID3,
|
ID: blockID3,
|
||||||
RootID: rootID1,
|
BoardID: boardID1,
|
||||||
ParentID: parentID1,
|
ParentID: parentID1,
|
||||||
}
|
}
|
||||||
|
|
||||||
blockID4 := utils.NewID(utils.IDTypeBlock)
|
blockID4 := utils.NewID(utils.IDTypeBlock)
|
||||||
rootID2 := utils.NewID(utils.IDTypeBlock)
|
boardID2 := utils.NewID(utils.IDTypeBlock)
|
||||||
parentID2 := utils.NewID(utils.IDTypeBlock)
|
parentID2 := utils.NewID(utils.IDTypeBlock)
|
||||||
|
|
||||||
block4 := Block{
|
block4 := Block{
|
||||||
ID: blockID4,
|
ID: blockID4,
|
||||||
RootID: rootID2,
|
BoardID: boardID2,
|
||||||
ParentID: parentID2,
|
ParentID: parentID2,
|
||||||
Fields: map[string]interface{}{
|
Fields: map[string]interface{}{
|
||||||
"contentOrder": []interface{}{
|
"contentOrder": []interface{}{
|
||||||
|
@ -235,11 +235,11 @@ func TestGenerateBlockIDs(t *testing.T) {
|
||||||
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
|
||||||
|
|
||||||
require.NotEqual(t, blockID1, blocks[0].ID)
|
require.NotEqual(t, blockID1, blocks[0].ID)
|
||||||
require.Equal(t, rootID1, blocks[0].RootID)
|
require.Equal(t, boardID1, blocks[0].BoardID)
|
||||||
require.Equal(t, parentID1, blocks[0].ParentID)
|
require.Equal(t, parentID1, blocks[0].ParentID)
|
||||||
|
|
||||||
require.NotEqual(t, blockID4, blocks[3].ID)
|
require.NotEqual(t, blockID4, blocks[3].ID)
|
||||||
require.Equal(t, rootID2, blocks[3].RootID)
|
require.Equal(t, boardID2, blocks[3].BoardID)
|
||||||
require.Equal(t, parentID2, blocks[3].ParentID)
|
require.Equal(t, parentID2, blocks[3].ParentID)
|
||||||
|
|
||||||
// since block 1 was referenced in block 2,
|
// since block 1 was referenced in block 2,
|
||||||
|
|
|
@ -20,8 +20,8 @@ func GenerateBlockIDs(blocks []Block, logger *mlog.Logger) []Block {
|
||||||
blockIDs[block.ID] = block.Type
|
blockIDs[block.ID] = block.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := referenceIDs[block.RootID]; !ok {
|
if _, ok := referenceIDs[block.BoardID]; !ok {
|
||||||
referenceIDs[block.RootID] = true
|
referenceIDs[block.BoardID] = true
|
||||||
}
|
}
|
||||||
if _, ok := referenceIDs[block.ParentID]; !ok {
|
if _, ok := referenceIDs[block.ParentID]; !ok {
|
||||||
referenceIDs[block.ParentID] = true
|
referenceIDs[block.ParentID] = true
|
||||||
|
@ -81,7 +81,7 @@ func GenerateBlockIDs(blocks []Block, logger *mlog.Logger) []Block {
|
||||||
newBlocks := make([]Block, len(blocks))
|
newBlocks := make([]Block, len(blocks))
|
||||||
for i, block := range blocks {
|
for i, block := range blocks {
|
||||||
block.ID = getExistingOrNewID(block.ID)
|
block.ID = getExistingOrNewID(block.ID)
|
||||||
block.RootID = getExistingOrOldID(block.RootID)
|
block.BoardID = getExistingOrOldID(block.BoardID)
|
||||||
block.ParentID = getExistingOrOldID(block.ParentID)
|
block.ParentID = getExistingOrOldID(block.ParentID)
|
||||||
|
|
||||||
blockMod := block
|
blockMod := block
|
||||||
|
|
304
server/model/board.go
Normal file
304
server/model/board.go
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BoardType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
BoardTypeOpen BoardType = "O"
|
||||||
|
BoardTypePrivate BoardType = "P"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Board groups a set of blocks and its layout
|
||||||
|
// swagger:model
|
||||||
|
type Board struct {
|
||||||
|
// The ID for the board
|
||||||
|
// required: true
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
// The ID of the team that the board belongs to
|
||||||
|
// required: true
|
||||||
|
TeamID string `json:"teamId"`
|
||||||
|
|
||||||
|
// The ID of the channel that the board was created from
|
||||||
|
// required: false
|
||||||
|
ChannelID string `json:"channelId"`
|
||||||
|
|
||||||
|
// The ID of the user that created the board
|
||||||
|
// required: true
|
||||||
|
CreatedBy string `json:"createdBy"`
|
||||||
|
|
||||||
|
// The ID of the last user that updated the board
|
||||||
|
// required: true
|
||||||
|
ModifiedBy string `json:"modifiedBy"`
|
||||||
|
|
||||||
|
// The type of the board
|
||||||
|
// required: true
|
||||||
|
Type BoardType `json:"type"`
|
||||||
|
|
||||||
|
// The title of the board
|
||||||
|
// required: false
|
||||||
|
Title string `json:"title"`
|
||||||
|
|
||||||
|
// The description of the board
|
||||||
|
// required: false
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// The icon of the board
|
||||||
|
// required: false
|
||||||
|
Icon string `json:"icon"`
|
||||||
|
|
||||||
|
// Indicates if the board shows the description on the interface
|
||||||
|
// required: false
|
||||||
|
ShowDescription bool `json:"showDescription"`
|
||||||
|
|
||||||
|
// Marks the template boards
|
||||||
|
// required: false
|
||||||
|
IsTemplate bool `json:"isTemplate"`
|
||||||
|
|
||||||
|
// Marks the template boards
|
||||||
|
// required: false
|
||||||
|
TemplateVersion int `json:"templateVersion"`
|
||||||
|
|
||||||
|
// The properties of the board
|
||||||
|
// required: false
|
||||||
|
Properties map[string]interface{} `json:"properties"`
|
||||||
|
|
||||||
|
// The properties of the board cards
|
||||||
|
// required: false
|
||||||
|
CardProperties []map[string]interface{} `json:"cardProperties"`
|
||||||
|
|
||||||
|
// The calculations on the board's cards
|
||||||
|
// required: false
|
||||||
|
ColumnCalculations map[string]interface{} `json:"columnCalculations"`
|
||||||
|
|
||||||
|
// The creation time
|
||||||
|
// required: true
|
||||||
|
CreateAt int64 `json:"createAt"`
|
||||||
|
|
||||||
|
// The last modified time
|
||||||
|
// required: true
|
||||||
|
UpdateAt int64 `json:"updateAt"`
|
||||||
|
|
||||||
|
// The deleted time. Set to indicate this block is deleted
|
||||||
|
// required: false
|
||||||
|
DeleteAt int64 `json:"deleteAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoardPatch is a patch for modify boards
|
||||||
|
// swagger:model
|
||||||
|
type BoardPatch struct {
|
||||||
|
// The type of the board
|
||||||
|
// required: false
|
||||||
|
Type *BoardType `json:"type"`
|
||||||
|
|
||||||
|
// The title of the board
|
||||||
|
// required: false
|
||||||
|
Title *string `json:"title"`
|
||||||
|
|
||||||
|
// The description of the board
|
||||||
|
// required: false
|
||||||
|
Description *string `json:"description"`
|
||||||
|
|
||||||
|
// The icon of the board
|
||||||
|
// required: false
|
||||||
|
Icon *string `json:"icon"`
|
||||||
|
|
||||||
|
// Indicates if the board shows the description on the interface
|
||||||
|
// required: false
|
||||||
|
ShowDescription *bool `json:"showDescription"`
|
||||||
|
|
||||||
|
// The board updated properties
|
||||||
|
// required: false
|
||||||
|
UpdatedProperties map[string]interface{} `json:"updatedProperties"`
|
||||||
|
|
||||||
|
// The board removed properties
|
||||||
|
// required: false
|
||||||
|
DeletedProperties []string `json:"deletedProperties"`
|
||||||
|
|
||||||
|
// The board updated card properties
|
||||||
|
// required: false
|
||||||
|
UpdatedCardProperties []map[string]interface{} `json:"updatedCardProperties"`
|
||||||
|
|
||||||
|
// The board removed card properties
|
||||||
|
// required: false
|
||||||
|
DeletedCardProperties []string `json:"deletedCardProperties"`
|
||||||
|
|
||||||
|
// The board updated column calculations
|
||||||
|
// required: false
|
||||||
|
UpdatedColumnCalculations map[string]interface{} `json:"updatedColumnCalculations"`
|
||||||
|
|
||||||
|
// The board deleted column calculations
|
||||||
|
// required: false
|
||||||
|
DeletedColumnCalculations []string `json:"deletedColumnCalculations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoardMember stores the information of the membership of a user on a board
|
||||||
|
// swagger:model
|
||||||
|
type BoardMember struct {
|
||||||
|
// The ID of the board
|
||||||
|
// required: true
|
||||||
|
BoardID string `json:"boardId"`
|
||||||
|
|
||||||
|
// The ID of the user
|
||||||
|
// required: true
|
||||||
|
UserID string `json:"userId"`
|
||||||
|
|
||||||
|
// The independent roles of the user on the board
|
||||||
|
// required: false
|
||||||
|
Roles string `json:"roles"`
|
||||||
|
|
||||||
|
// Marks the user as an admin of the board
|
||||||
|
// required: true
|
||||||
|
SchemeAdmin bool `json:"schemeAdmin"`
|
||||||
|
|
||||||
|
// Marks the user as an editor of the board
|
||||||
|
// required: true
|
||||||
|
SchemeEditor bool `json:"schemeEditor"`
|
||||||
|
|
||||||
|
// Marks the user as an commenter of the board
|
||||||
|
// required: true
|
||||||
|
SchemeCommenter bool `json:"schemeCommenter"`
|
||||||
|
|
||||||
|
// Marks the user as an viewer of the board
|
||||||
|
// required: true
|
||||||
|
SchemeViewer bool `json:"schemeViewer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func BoardFromJSON(data io.Reader) *Board {
|
||||||
|
var board *Board
|
||||||
|
_ = json.NewDecoder(data).Decode(&board)
|
||||||
|
return board
|
||||||
|
}
|
||||||
|
|
||||||
|
func BoardsFromJSON(data io.Reader) []*Board {
|
||||||
|
var boards []*Board
|
||||||
|
_ = json.NewDecoder(data).Decode(&boards)
|
||||||
|
return boards
|
||||||
|
}
|
||||||
|
|
||||||
|
func BoardMemberFromJSON(data io.Reader) *BoardMember {
|
||||||
|
var boardMember *BoardMember
|
||||||
|
_ = json.NewDecoder(data).Decode(&boardMember)
|
||||||
|
return boardMember
|
||||||
|
}
|
||||||
|
|
||||||
|
func BoardMembersFromJSON(data io.Reader) []*BoardMember {
|
||||||
|
var boardMembers []*BoardMember
|
||||||
|
_ = json.NewDecoder(data).Decode(&boardMembers)
|
||||||
|
return boardMembers
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch returns an updated version of the board.
|
||||||
|
func (p *BoardPatch) Patch(board *Board) *Board {
|
||||||
|
if p.Type != nil {
|
||||||
|
board.Type = *p.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Title != nil {
|
||||||
|
board.Title = *p.Title
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Description != nil {
|
||||||
|
board.Description = *p.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Icon != nil {
|
||||||
|
board.Icon = *p.Icon
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.ShowDescription != nil {
|
||||||
|
board.ShowDescription = *p.ShowDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, property := range p.UpdatedProperties {
|
||||||
|
board.Properties[key] = property
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range p.DeletedProperties {
|
||||||
|
delete(board.Properties, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.UpdatedCardProperties) != 0 || len(p.DeletedCardProperties) != 0 {
|
||||||
|
// first we accumulate all properties indexed by ID
|
||||||
|
cardPropertyMap := map[string]map[string]interface{}{}
|
||||||
|
for _, prop := range board.CardProperties {
|
||||||
|
id, ok := prop["id"].(string)
|
||||||
|
if !ok {
|
||||||
|
// bad property, skipping
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cardPropertyMap[id] = prop
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are properties marked for removal, we delete them
|
||||||
|
for _, propertyID := range p.DeletedCardProperties {
|
||||||
|
delete(cardPropertyMap, propertyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are properties marked for update, we replace the
|
||||||
|
// existing ones or add them
|
||||||
|
for _, newprop := range p.UpdatedCardProperties {
|
||||||
|
id, ok := newprop["id"].(string)
|
||||||
|
if !ok {
|
||||||
|
// bad new property, skipping
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cardPropertyMap[id] = newprop
|
||||||
|
}
|
||||||
|
|
||||||
|
// and finally we flatten and save the updated properties
|
||||||
|
newCardProperties := []map[string]interface{}{}
|
||||||
|
for _, p := range cardPropertyMap {
|
||||||
|
newCardProperties = append(newCardProperties, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
board.CardProperties = newCardProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, columnCalculation := range p.UpdatedColumnCalculations {
|
||||||
|
board.ColumnCalculations[key] = columnCalculation
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range p.DeletedColumnCalculations {
|
||||||
|
delete(board.ColumnCalculations, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return board
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsBoardTypeValid(t BoardType) bool {
|
||||||
|
return t == BoardTypeOpen || t == BoardTypePrivate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BoardPatch) IsValid() error {
|
||||||
|
if p.Type != nil && !IsBoardTypeValid(*p.Type) {
|
||||||
|
return InvalidBoardErr{"invalid-board-type"}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvalidBoardErr struct {
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ibe InvalidBoardErr) Error() string {
|
||||||
|
return ibe.msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Board) IsValid() error {
|
||||||
|
if b.TeamID == "" {
|
||||||
|
return InvalidBoardErr{"empty-team-id"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !IsBoardTypeValid(b.Type) {
|
||||||
|
return InvalidBoardErr{"invalid-board-type"}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
164
server/model/boards_and_blocks.go
Normal file
164
server/model/boards_and_blocks.go
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNoBoardsInBoardsAndBlocks = errors.New("at least one board is required")
|
||||||
|
var ErrNoBlocksInBoardsAndBlocks = errors.New("at least one block is required")
|
||||||
|
var ErrNoTeamInBoardsAndBlocks = errors.New("team ID cannot be empty")
|
||||||
|
var ErrBoardIDsAndPatchesMissmatchInBoardsAndBlocks = errors.New("board ids and patches need to match")
|
||||||
|
var ErrBlockIDsAndPatchesMissmatchInBoardsAndBlocks = errors.New("block ids and patches need to match")
|
||||||
|
|
||||||
|
type BlockDoesntBelongToAnyBoardErr struct {
|
||||||
|
blockID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e BlockDoesntBelongToAnyBoardErr) Error() string {
|
||||||
|
return fmt.Sprintf("block %s doesn't belong to any board", e.blockID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoardsAndBlocks is used to operate over boards and blocks at the
|
||||||
|
// same time
|
||||||
|
// swagger:model
|
||||||
|
type BoardsAndBlocks struct {
|
||||||
|
// The boards
|
||||||
|
// required: false
|
||||||
|
Boards []*Board `json:"boards"`
|
||||||
|
|
||||||
|
// The blocks
|
||||||
|
// required: false
|
||||||
|
Blocks []Block `json:"blocks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bab *BoardsAndBlocks) IsValid() error {
|
||||||
|
if len(bab.Boards) == 0 {
|
||||||
|
return ErrNoBoardsInBoardsAndBlocks
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(bab.Blocks) == 0 {
|
||||||
|
return ErrNoBlocksInBoardsAndBlocks
|
||||||
|
}
|
||||||
|
|
||||||
|
boardsMap := map[string]bool{}
|
||||||
|
for _, board := range bab.Boards {
|
||||||
|
boardsMap[board.ID] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, block := range bab.Blocks {
|
||||||
|
if _, ok := boardsMap[block.BoardID]; !ok {
|
||||||
|
return BlockDoesntBelongToAnyBoardErr{block.ID}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBoardsAndBlocks is used to list the boards and blocks to
|
||||||
|
// delete on a request
|
||||||
|
// swagger:model
|
||||||
|
type DeleteBoardsAndBlocks struct {
|
||||||
|
// The boards
|
||||||
|
// required: true
|
||||||
|
Boards []string `json:"boards"`
|
||||||
|
|
||||||
|
// The blocks
|
||||||
|
// required: true
|
||||||
|
Blocks []string `json:"blocks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbab *DeleteBoardsAndBlocks) IsValid() error {
|
||||||
|
if len(dbab.Boards) == 0 {
|
||||||
|
return ErrNoBoardsInBoardsAndBlocks
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dbab.Blocks) == 0 {
|
||||||
|
return ErrNoBlocksInBoardsAndBlocks
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatchBoardsAndBlocks is used to patch multiple boards and blocks on
|
||||||
|
// a single request
|
||||||
|
// swagger:model
|
||||||
|
type PatchBoardsAndBlocks struct {
|
||||||
|
// The board IDs to patch
|
||||||
|
// required: true
|
||||||
|
BoardIDs []string `json:"boardIDs"`
|
||||||
|
|
||||||
|
// The board patches
|
||||||
|
// required: true
|
||||||
|
BoardPatches []*BoardPatch `json:"boardPatches"`
|
||||||
|
|
||||||
|
// The block IDs to patch
|
||||||
|
// required: true
|
||||||
|
BlockIDs []string `json:"blockIDs"`
|
||||||
|
|
||||||
|
// The block patches
|
||||||
|
// required: true
|
||||||
|
BlockPatches []*BlockPatch `json:"blockPatches"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbab *PatchBoardsAndBlocks) IsValid() error {
|
||||||
|
if len(dbab.BoardIDs) == 0 {
|
||||||
|
return ErrNoBoardsInBoardsAndBlocks
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dbab.BoardIDs) != len(dbab.BoardPatches) {
|
||||||
|
return ErrBoardIDsAndPatchesMissmatchInBoardsAndBlocks
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dbab.BlockIDs) == 0 {
|
||||||
|
return ErrNoBlocksInBoardsAndBlocks
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dbab.BlockIDs) != len(dbab.BlockPatches) {
|
||||||
|
return ErrBlockIDsAndPatchesMissmatchInBoardsAndBlocks
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateBoardsAndBlocksIDs(bab *BoardsAndBlocks, logger *mlog.Logger) (*BoardsAndBlocks, error) {
|
||||||
|
if err := bab.IsValid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
blocksByBoard := map[string][]Block{}
|
||||||
|
for _, block := range bab.Blocks {
|
||||||
|
blocksByBoard[block.BoardID] = append(blocksByBoard[block.BoardID], block)
|
||||||
|
}
|
||||||
|
|
||||||
|
boards := []*Board{}
|
||||||
|
blocks := []Block{}
|
||||||
|
for _, board := range bab.Boards {
|
||||||
|
newID := utils.NewID(utils.IDTypeBoard)
|
||||||
|
for _, block := range blocksByBoard[board.ID] {
|
||||||
|
block.BoardID = newID
|
||||||
|
blocks = append(blocks, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
board.ID = newID
|
||||||
|
boards = append(boards, board)
|
||||||
|
}
|
||||||
|
|
||||||
|
newBab := &BoardsAndBlocks{
|
||||||
|
Boards: boards,
|
||||||
|
Blocks: GenerateBlockIDs(blocks, logger),
|
||||||
|
}
|
||||||
|
|
||||||
|
return newBab, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BoardsAndBlocksFromJSON(data io.Reader) *BoardsAndBlocks {
|
||||||
|
var bab *BoardsAndBlocks
|
||||||
|
_ = json.NewDecoder(data).Decode(&bab)
|
||||||
|
return bab
|
||||||
|
}
|
264
server/model/boards_and_blocks_test.go
Normal file
264
server/model/boards_and_blocks_test.go
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsValidBoardsAndBlocks(t *testing.T) {
|
||||||
|
t.Run("no boards", func(t *testing.T) {
|
||||||
|
bab := &BoardsAndBlocks{
|
||||||
|
Blocks: []Block{
|
||||||
|
{ID: "block-id-1", BoardID: "board-id-1", Type: TypeCard},
|
||||||
|
{ID: "block-id-2", BoardID: "board-id-2", Type: TypeCard},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.ErrorIs(t, bab.IsValid(), ErrNoBoardsInBoardsAndBlocks)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no blocks", func(t *testing.T) {
|
||||||
|
bab := &BoardsAndBlocks{
|
||||||
|
Boards: []*Board{
|
||||||
|
{ID: "board-id-1", Type: BoardTypeOpen},
|
||||||
|
{ID: "board-id-2", Type: BoardTypePrivate},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.ErrorIs(t, bab.IsValid(), ErrNoBlocksInBoardsAndBlocks)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("block that doesn't belong to the boards", func(t *testing.T) {
|
||||||
|
bab := &BoardsAndBlocks{
|
||||||
|
Boards: []*Board{
|
||||||
|
{ID: "board-id-1", Type: BoardTypeOpen},
|
||||||
|
{ID: "board-id-2", Type: BoardTypePrivate},
|
||||||
|
},
|
||||||
|
Blocks: []Block{
|
||||||
|
{ID: "block-id-1", BoardID: "board-id-1", Type: TypeCard},
|
||||||
|
{ID: "block-id-3", BoardID: "board-id-3", Type: TypeCard},
|
||||||
|
{ID: "block-id-2", BoardID: "board-id-2", Type: TypeCard},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.ErrorIs(t, bab.IsValid(), BlockDoesntBelongToAnyBoardErr{"block-id-3"})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid boards and blocks", func(t *testing.T) {
|
||||||
|
bab := &BoardsAndBlocks{
|
||||||
|
Boards: []*Board{
|
||||||
|
{ID: "board-id-1", Type: BoardTypeOpen},
|
||||||
|
{ID: "board-id-2", Type: BoardTypePrivate},
|
||||||
|
},
|
||||||
|
Blocks: []Block{
|
||||||
|
{ID: "block-id-1", BoardID: "board-id-1", Type: TypeCard},
|
||||||
|
{ID: "block-id-3", BoardID: "board-id-2", Type: TypeCard},
|
||||||
|
{ID: "block-id-2", BoardID: "board-id-2", Type: TypeCard},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, bab.IsValid())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateBoardsAndBlocksIDs(t *testing.T) {
|
||||||
|
logger, err := mlog.NewLogger()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
getBlockByType := func(blocks []Block, blockType BlockType) Block {
|
||||||
|
for _, b := range blocks {
|
||||||
|
if b.Type == blockType {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Block{}
|
||||||
|
}
|
||||||
|
|
||||||
|
getBoardByTitle := func(boards []*Board, title string) *Board {
|
||||||
|
for _, b := range boards {
|
||||||
|
if b.Title == title {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("invalid boards and blocks", func(t *testing.T) {
|
||||||
|
bab := &BoardsAndBlocks{
|
||||||
|
Blocks: []Block{
|
||||||
|
{ID: "block-id-1", BoardID: "board-id-1", Type: TypeCard},
|
||||||
|
{ID: "block-id-2", BoardID: "board-id-2", Type: TypeCard},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rBab, err := GenerateBoardsAndBlocksIDs(bab, logger)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Nil(t, rBab)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("correctly generates IDs for all the boards and links the blocks to them, with new IDs too", func(t *testing.T) {
|
||||||
|
bab := &BoardsAndBlocks{
|
||||||
|
Boards: []*Board{
|
||||||
|
{ID: "board-id-1", Type: BoardTypeOpen, Title: "board1"},
|
||||||
|
{ID: "board-id-2", Type: BoardTypePrivate, Title: "board2"},
|
||||||
|
{ID: "board-id-3", Type: BoardTypeOpen, Title: "board3"},
|
||||||
|
},
|
||||||
|
Blocks: []Block{
|
||||||
|
{ID: "block-id-1", BoardID: "board-id-1", Type: TypeCard},
|
||||||
|
{ID: "block-id-2", BoardID: "board-id-2", Type: TypeView},
|
||||||
|
{ID: "block-id-3", BoardID: "board-id-2", Type: TypeText},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rBab, err := GenerateBoardsAndBlocksIDs(bab, logger)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, rBab)
|
||||||
|
|
||||||
|
// all boards and blocks should have refreshed their IDs, and
|
||||||
|
// blocks should be correctly linked to the new board IDs
|
||||||
|
board1 := getBoardByTitle(rBab.Boards, "board1")
|
||||||
|
require.NotNil(t, board1)
|
||||||
|
require.NotEmpty(t, board1.ID)
|
||||||
|
require.NotEqual(t, "board-id-1", board1.ID)
|
||||||
|
board2 := getBoardByTitle(rBab.Boards, "board2")
|
||||||
|
require.NotNil(t, board2)
|
||||||
|
require.NotEmpty(t, board2.ID)
|
||||||
|
require.NotEqual(t, "board-id-2", board2.ID)
|
||||||
|
board3 := getBoardByTitle(rBab.Boards, "board3")
|
||||||
|
require.NotNil(t, board3)
|
||||||
|
require.NotEmpty(t, board3.ID)
|
||||||
|
require.NotEqual(t, "board-id-3", board3.ID)
|
||||||
|
|
||||||
|
block1 := getBlockByType(rBab.Blocks, TypeCard)
|
||||||
|
require.NotNil(t, block1)
|
||||||
|
require.NotEmpty(t, block1.ID)
|
||||||
|
require.NotEqual(t, "block-id-1", block1.ID)
|
||||||
|
require.Equal(t, board1.ID, block1.BoardID)
|
||||||
|
block2 := getBlockByType(rBab.Blocks, TypeView)
|
||||||
|
require.NotNil(t, block2)
|
||||||
|
require.NotEmpty(t, block2.ID)
|
||||||
|
require.NotEqual(t, "block-id-2", block2.ID)
|
||||||
|
require.Equal(t, board2.ID, block2.BoardID)
|
||||||
|
block3 := getBlockByType(rBab.Blocks, TypeText)
|
||||||
|
require.NotNil(t, block3)
|
||||||
|
require.NotEmpty(t, block3.ID)
|
||||||
|
require.NotEqual(t, "block-id-3", block3.ID)
|
||||||
|
require.Equal(t, board2.ID, block3.BoardID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsValidPatchBoardsAndBlocks(t *testing.T) {
|
||||||
|
newTitle := "new title"
|
||||||
|
newDescription := "new description"
|
||||||
|
var schema int64 = 1
|
||||||
|
|
||||||
|
t.Run("no board ids", func(t *testing.T) {
|
||||||
|
pbab := &PatchBoardsAndBlocks{
|
||||||
|
BoardIDs: []string{},
|
||||||
|
BlockIDs: []string{"block-id-1"},
|
||||||
|
BlockPatches: []*BlockPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Schema: &schema},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.ErrorIs(t, pbab.IsValid(), ErrNoBoardsInBoardsAndBlocks)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("missmatch board IDs and patches", func(t *testing.T) {
|
||||||
|
pbab := &PatchBoardsAndBlocks{
|
||||||
|
BoardIDs: []string{"board-id-1", "board-id-2"},
|
||||||
|
BoardPatches: []*BoardPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
BlockIDs: []string{"block-id-1"},
|
||||||
|
BlockPatches: []*BlockPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.ErrorIs(t, pbab.IsValid(), ErrBoardIDsAndPatchesMissmatchInBoardsAndBlocks)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no block ids", func(t *testing.T) {
|
||||||
|
pbab := &PatchBoardsAndBlocks{
|
||||||
|
BoardIDs: []string{"board-id-1", "board-id-2"},
|
||||||
|
BoardPatches: []*BoardPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Description: &newDescription},
|
||||||
|
},
|
||||||
|
BlockIDs: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.ErrorIs(t, pbab.IsValid(), ErrNoBlocksInBoardsAndBlocks)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("missmatch block IDs and patches", func(t *testing.T) {
|
||||||
|
pbab := &PatchBoardsAndBlocks{
|
||||||
|
BoardIDs: []string{"board-id-1", "board-id-2"},
|
||||||
|
BoardPatches: []*BoardPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Description: &newDescription},
|
||||||
|
},
|
||||||
|
BlockIDs: []string{"block-id-1"},
|
||||||
|
BlockPatches: []*BlockPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Schema: &schema},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.ErrorIs(t, pbab.IsValid(), ErrBlockIDsAndPatchesMissmatchInBoardsAndBlocks)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid", func(t *testing.T) {
|
||||||
|
pbab := &PatchBoardsAndBlocks{
|
||||||
|
BoardIDs: []string{"board-id-1", "board-id-2"},
|
||||||
|
BoardPatches: []*BoardPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
{Description: &newDescription},
|
||||||
|
},
|
||||||
|
BlockIDs: []string{"block-id-1"},
|
||||||
|
BlockPatches: []*BlockPatch{
|
||||||
|
{Title: &newTitle},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, pbab.IsValid())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsValidDeleteBoardsAndBlocks(t *testing.T) {
|
||||||
|
/*
|
||||||
|
TODO fix this
|
||||||
|
t.Run("no board ids", func(t *testing.T) {
|
||||||
|
dbab := &DeleteBoardsAndBlocks{
|
||||||
|
TeamID: "team-id",
|
||||||
|
Blocks: []string{"block-id-1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.ErrorIs(t, dbab.IsValid(), NoBoardsInBoardsAndBlocksErr)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no block ids", func(t *testing.T) {
|
||||||
|
dbab := &DeleteBoardsAndBlocks{
|
||||||
|
TeamID: "team-id",
|
||||||
|
Boards: []string{"board-id-1", "board-id-2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.ErrorIs(t, dbab.IsValid(), NoBlocksInBoardsAndBlocksErr)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid", func(t *testing.T) {
|
||||||
|
dbab := &DeleteBoardsAndBlocks{
|
||||||
|
TeamID: "team-id",
|
||||||
|
Boards: []string{"board-id-1", "board-id-2"},
|
||||||
|
Blocks: []string{"block-id-1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, dbab.IsValid())
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
}
|
57
server/model/category.go
Normal file
57
server/model/category.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Category struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
UserID string `json:"userID"`
|
||||||
|
TeamID string `json:"teamID"`
|
||||||
|
CreateAt int64 `json:"createAt"`
|
||||||
|
UpdateAt int64 `json:"updateAt"`
|
||||||
|
DeleteAt int64 `json:"deleteAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Category) Hydrate() {
|
||||||
|
c.ID = utils.NewID(utils.IDTypeNone)
|
||||||
|
c.CreateAt = utils.GetMillis()
|
||||||
|
c.UpdateAt = c.CreateAt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Category) IsValid() error {
|
||||||
|
if strings.TrimSpace(c.ID) == "" {
|
||||||
|
return newErrInvalidCategory("category ID cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(c.Name) == "" {
|
||||||
|
return newErrInvalidCategory("category name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(c.UserID) == "" {
|
||||||
|
return newErrInvalidCategory("category user ID cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(c.TeamID) == "" {
|
||||||
|
return newErrInvalidCategory("category team id ID cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrInvalidCategory struct {
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newErrInvalidCategory(msg string) *ErrInvalidCategory {
|
||||||
|
return &ErrInvalidCategory{
|
||||||
|
msg: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrInvalidCategory) Error() string {
|
||||||
|
return e.msg
|
||||||
|
}
|
11
server/model/category_blocks.go
Normal file
11
server/model/category_blocks.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
type CategoryBlocks struct {
|
||||||
|
Category
|
||||||
|
BlockIDs []string `json:"blockIDs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlockCategoryWebsocketData struct {
|
||||||
|
BlockID string `json:"blockID"`
|
||||||
|
CategoryID string `json:"categoryID"`
|
||||||
|
}
|
7
server/model/database.go
Normal file
7
server/model/database.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
const (
|
||||||
|
SqliteDBType = "sqlite3"
|
||||||
|
PostgresDBType = "postgres"
|
||||||
|
MysqlDBType = "mysql"
|
||||||
|
)
|
|
@ -33,7 +33,7 @@ type ArchiveLine struct {
|
||||||
// ExportArchiveOptions provides options when exporting one or more boards
|
// ExportArchiveOptions provides options when exporting one or more boards
|
||||||
// to an archive.
|
// to an archive.
|
||||||
type ExportArchiveOptions struct {
|
type ExportArchiveOptions struct {
|
||||||
WorkspaceID string
|
TeamID string
|
||||||
|
|
||||||
// BoardIDs is the list of boards to include in the archive.
|
// BoardIDs is the list of boards to include in the archive.
|
||||||
// Empty slice means export all boards from workspace/team.
|
// Empty slice means export all boards from workspace/team.
|
||||||
|
@ -42,9 +42,9 @@ type ExportArchiveOptions struct {
|
||||||
|
|
||||||
// ImportArchiveOptions provides options when importing an archive.
|
// ImportArchiveOptions provides options when importing an archive.
|
||||||
type ImportArchiveOptions struct {
|
type ImportArchiveOptions struct {
|
||||||
WorkspaceID string
|
TeamID string
|
||||||
ModifiedBy string
|
ModifiedBy string
|
||||||
BlockModifier BlockModifier
|
BoardModifier BoardModifier
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrUnsupportedArchiveVersion is an error returned when trying to import an
|
// ErrUnsupportedArchiveVersion is an error returned when trying to import an
|
||||||
|
|
|
@ -18,10 +18,6 @@ type NotificationHint struct {
|
||||||
// required: true
|
// required: true
|
||||||
BlockID string `json:"block_id"`
|
BlockID string `json:"block_id"`
|
||||||
|
|
||||||
// WorkspaceID is id of workspace the block belongs to
|
|
||||||
// required: true
|
|
||||||
WorkspaceID string `json:"workspace_id"`
|
|
||||||
|
|
||||||
// ModifiedByID is the id of the user who made the block change
|
// ModifiedByID is the id of the user who made the block change
|
||||||
ModifiedByID string `json:"modified_by_id"`
|
ModifiedByID string `json:"modified_by_id"`
|
||||||
|
|
||||||
|
@ -41,9 +37,6 @@ func (s *NotificationHint) IsValid() error {
|
||||||
if s.BlockID == "" {
|
if s.BlockID == "" {
|
||||||
return ErrInvalidNotificationHint{"missing block id"}
|
return ErrInvalidNotificationHint{"missing block id"}
|
||||||
}
|
}
|
||||||
if s.WorkspaceID == "" {
|
|
||||||
return ErrInvalidNotificationHint{"missing workspace id"}
|
|
||||||
}
|
|
||||||
if s.BlockType == "" {
|
if s.BlockType == "" {
|
||||||
return ErrInvalidNotificationHint{"missing block type"}
|
return ErrInvalidNotificationHint{"missing block type"}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +50,6 @@ func (s *NotificationHint) Copy() *NotificationHint {
|
||||||
return &NotificationHint{
|
return &NotificationHint{
|
||||||
BlockType: s.BlockType,
|
BlockType: s.BlockType,
|
||||||
BlockID: s.BlockID,
|
BlockID: s.BlockID,
|
||||||
WorkspaceID: s.WorkspaceID,
|
|
||||||
ModifiedByID: s.ModifiedByID,
|
ModifiedByID: s.ModifiedByID,
|
||||||
CreateAt: s.CreateAt,
|
CreateAt: s.CreateAt,
|
||||||
NotifyAt: s.NotifyAt,
|
NotifyAt: s.NotifyAt,
|
||||||
|
@ -68,14 +60,12 @@ func (s *NotificationHint) LogClone() interface{} {
|
||||||
return struct {
|
return struct {
|
||||||
BlockType BlockType `json:"block_type"`
|
BlockType BlockType `json:"block_type"`
|
||||||
BlockID string `json:"block_id"`
|
BlockID string `json:"block_id"`
|
||||||
WorkspaceID string `json:"workspace_id"`
|
|
||||||
ModifiedByID string `json:"modified_by_id"`
|
ModifiedByID string `json:"modified_by_id"`
|
||||||
CreateAt string `json:"create_at"`
|
CreateAt string `json:"create_at"`
|
||||||
NotifyAt string `json:"notify_at"`
|
NotifyAt string `json:"notify_at"`
|
||||||
}{
|
}{
|
||||||
BlockType: s.BlockType,
|
BlockType: s.BlockType,
|
||||||
BlockID: s.BlockID,
|
BlockID: s.BlockID,
|
||||||
WorkspaceID: s.WorkspaceID,
|
|
||||||
ModifiedByID: s.ModifiedByID,
|
ModifiedByID: s.ModifiedByID,
|
||||||
CreateAt: utils.TimeFromMillis(s.CreateAt).Format(time.StampMilli),
|
CreateAt: utils.TimeFromMillis(s.CreateAt).Format(time.StampMilli),
|
||||||
NotifyAt: utils.TimeFromMillis(s.NotifyAt).Format(time.StampMilli),
|
NotifyAt: utils.TimeFromMillis(s.NotifyAt).Format(time.StampMilli),
|
||||||
|
|
19
server/model/permission.go
Normal file
19
server/model/permission.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
PermissionViewTeam = mmModel.PermissionViewTeam
|
||||||
|
PermissionViewMembers = mmModel.PermissionViewMembers
|
||||||
|
PermissionCreatePublicChannel = mmModel.PermissionCreatePublicChannel
|
||||||
|
PermissionCreatePrivateChannel = mmModel.PermissionCreatePrivateChannel
|
||||||
|
PermissionManageBoardType = &mmModel.Permission{Id: "manage_board_type", Name: "", Description: "", Scope: ""}
|
||||||
|
PermissionDeleteBoard = &mmModel.Permission{Id: "delete_board", Name: "", Description: "", Scope: ""}
|
||||||
|
PermissionViewBoard = &mmModel.Permission{Id: "view_board", Name: "", Description: "", Scope: ""}
|
||||||
|
PermissionManageBoardRoles = &mmModel.Permission{Id: "manage_board_roles", Name: "", Description: "", Scope: ""}
|
||||||
|
PermissionShareBoard = &mmModel.Permission{Id: "share_board", Name: "", Description: "", Scope: ""}
|
||||||
|
PermissionManageBoardCards = &mmModel.Permission{Id: "manage_board_cards", Name: "", Description: "", Scope: ""}
|
||||||
|
PermissionManageBoardProperties = &mmModel.Permission{Id: "manage_board_properties", Name: "", Description: "", Scope: ""}
|
||||||
|
)
|
|
@ -147,30 +147,10 @@ func (pd PropDef) ParseDate(s string) (string, error) {
|
||||||
// schema for all cards within the board.
|
// schema for all cards within the board.
|
||||||
// The result is provided as a map for quick lookup, and the original order is
|
// The result is provided as a map for quick lookup, and the original order is
|
||||||
// preserved via the `Index` field.
|
// preserved via the `Index` field.
|
||||||
func ParsePropertySchema(board *Block) (PropSchema, error) {
|
func ParsePropertySchema(board *Board) (PropSchema, error) {
|
||||||
if board == nil || board.Type != TypeBoard {
|
|
||||||
return nil, ErrInvalidBoardBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
schema := make(map[string]PropDef)
|
schema := make(map[string]PropDef)
|
||||||
|
|
||||||
// cardProperties contains a slice of maps (untyped at this point).
|
for i, prop := range board.CardProperties {
|
||||||
cardPropsIface, ok := board.Fields["cardProperties"]
|
|
||||||
if !ok {
|
|
||||||
return schema, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cardProps, ok := cardPropsIface.([]interface{})
|
|
||||||
if !ok || len(cardProps) == 0 {
|
|
||||||
return schema, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, cp := range cardProps {
|
|
||||||
prop, ok := cp.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrInvalidPropSchema
|
|
||||||
}
|
|
||||||
|
|
||||||
pd := PropDef{
|
pd := PropDef{
|
||||||
ID: getMapString("id", prop),
|
ID: getMapString("id", prop),
|
||||||
Index: i,
|
Index: i,
|
||||||
|
|
|
@ -13,14 +13,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_parsePropertySchema(t *testing.T) {
|
func Test_parsePropertySchema(t *testing.T) {
|
||||||
board := &Block{
|
board := &Board{
|
||||||
ID: utils.NewID(utils.IDTypeBoard),
|
ID: utils.NewID(utils.IDTypeBoard),
|
||||||
Type: TypeBoard,
|
Title: "Test Board",
|
||||||
Title: "Test Board",
|
TeamID: utils.NewID(utils.IDTypeTeam),
|
||||||
WorkspaceID: utils.NewID(utils.IDTypeWorkspace),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := json.Unmarshal([]byte(fieldsExample), &board.Fields)
|
err := json.Unmarshal([]byte(cardPropertiesExample), &board.CardProperties)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Run("parse schema", func(t *testing.T) {
|
t.Run("parse schema", func(t *testing.T) {
|
||||||
|
@ -46,93 +45,74 @@ func Test_parsePropertySchema(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
fieldsExample = `
|
cardPropertiesExample = `[
|
||||||
{
|
{
|
||||||
"cardProperties":[
|
"id":"7c212e78-9345-4c60-81b5-0b0e37ce463f",
|
||||||
{
|
"name":"Type",
|
||||||
"id":"7c212e78-9345-4c60-81b5-0b0e37ce463f",
|
"options":[
|
||||||
"name":"Type",
|
{
|
||||||
"options":[
|
"color":"propColorYellow",
|
||||||
{
|
"id":"31da50ca-f1a9-4d21-8636-17dc387c1a23",
|
||||||
"color":"propColorYellow",
|
"value":"Ad Hoc"
|
||||||
"id":"31da50ca-f1a9-4d21-8636-17dc387c1a23",
|
},
|
||||||
"value":"Ad Hoc"
|
{
|
||||||
},
|
"color":"propColorBlue",
|
||||||
{
|
"id":"def6317c-ec11-410d-8a6b-ea461320f392",
|
||||||
"color":"propColorBlue",
|
"value":"Standup"
|
||||||
"id":"def6317c-ec11-410d-8a6b-ea461320f392",
|
},
|
||||||
"value":"Standup"
|
{
|
||||||
},
|
"color":"propColorPurple",
|
||||||
{
|
"id":"700f83f8-6a41-46cd-87e2-53e0d0b12cc7",
|
||||||
"color":"propColorPurple",
|
"value":"Weekly Sync"
|
||||||
"id":"700f83f8-6a41-46cd-87e2-53e0d0b12cc7",
|
}
|
||||||
"value":"Weekly Sync"
|
],
|
||||||
}
|
"type":"select"
|
||||||
],
|
},
|
||||||
"type":"select"
|
{
|
||||||
},
|
"id":"13d2394a-eb5e-4f22-8c22-6515ec41c4a4",
|
||||||
{
|
"name":"Summary",
|
||||||
"id":"13d2394a-eb5e-4f22-8c22-6515ec41c4a4",
|
"options":[],
|
||||||
"name":"Summary",
|
"type":"text"
|
||||||
"options":[
|
},
|
||||||
|
{
|
||||||
],
|
"id":"566cd860-bbae-4bcd-86a8-7df4db2ba15c",
|
||||||
"type":"text"
|
"name":"Color",
|
||||||
},
|
"options":[
|
||||||
{
|
{
|
||||||
"id":"566cd860-bbae-4bcd-86a8-7df4db2ba15c",
|
"color":"propColorDefault",
|
||||||
"name":"Color",
|
"id":"efb0c783-f9ea-4938-8b86-9cf425296cd1",
|
||||||
"options":[
|
"value":"RED"
|
||||||
{
|
},
|
||||||
"color":"propColorDefault",
|
{
|
||||||
"id":"efb0c783-f9ea-4938-8b86-9cf425296cd1",
|
"color":"propColorDefault",
|
||||||
"value":"RED"
|
"id":"2f100e13-e7c4-4ab6-81c9-a17baf98b311",
|
||||||
},
|
"value":"GREEN"
|
||||||
{
|
},
|
||||||
"color":"propColorDefault",
|
{
|
||||||
"id":"2f100e13-e7c4-4ab6-81c9-a17baf98b311",
|
"color":"propColorDefault",
|
||||||
"value":"GREEN"
|
"id":"a05bdc80-bd90-45b0-8805-a7e77a4884be",
|
||||||
},
|
"value":"BLUE"
|
||||||
{
|
}
|
||||||
"color":"propColorDefault",
|
],
|
||||||
"id":"a05bdc80-bd90-45b0-8805-a7e77a4884be",
|
"type":"select"
|
||||||
"value":"BLUE"
|
},
|
||||||
}
|
{
|
||||||
],
|
"id":"aawg1s8rxq8o1bbksxmsmpsdd3r",
|
||||||
"type":"select"
|
"name":"MyTextProp",
|
||||||
},
|
"options":[],
|
||||||
{
|
"type":"text"
|
||||||
"id":"aawg1s8rxq8o1bbksxmsmpsdd3r",
|
},
|
||||||
"name":"MyTextProp",
|
{
|
||||||
"options":[
|
"id":"awdwfigo4kse63bdfp56mzhip6w",
|
||||||
|
"name":"MyCheckBox",
|
||||||
],
|
"options":[],
|
||||||
"type":"text"
|
"type":"checkbox"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id":"awdwfigo4kse63bdfp56mzhip6w",
|
"id":"a8spou7if43eo1rqzb9qeq488so",
|
||||||
"name":"MyCheckBox",
|
"name":"MyDate",
|
||||||
"options":[
|
"options":[],
|
||||||
|
"type":"date"
|
||||||
],
|
}
|
||||||
"type":"checkbox"
|
]`
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":"a8spou7if43eo1rqzb9qeq488so",
|
|
||||||
"name":"MyDate",
|
|
||||||
"options":[
|
|
||||||
|
|
||||||
],
|
|
||||||
"type":"date"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"columnCalculations":[
|
|
||||||
|
|
||||||
],
|
|
||||||
"description":"",
|
|
||||||
"icon":"🗒️",
|
|
||||||
"isTemplate":false,
|
|
||||||
"showDescription":false
|
|
||||||
}
|
|
||||||
`
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -31,10 +31,6 @@ type Subscription struct {
|
||||||
// required: true
|
// required: true
|
||||||
BlockID string `json:"blockId"`
|
BlockID string `json:"blockId"`
|
||||||
|
|
||||||
// WorkspaceID is id of the workspace the block belongs to
|
|
||||||
// required: true
|
|
||||||
WorkspaceID string `json:"workspaceId"`
|
|
||||||
|
|
||||||
// SubscriberType is the type of the entity (e.g. user, channel) that is subscribing
|
// SubscriberType is the type of the entity (e.g. user, channel) that is subscribing
|
||||||
// required: true
|
// required: true
|
||||||
SubscriberType SubscriberType `json:"subscriberType"`
|
SubscriberType SubscriberType `json:"subscriberType"`
|
||||||
|
@ -63,9 +59,6 @@ func (s *Subscription) IsValid() error {
|
||||||
if s.BlockID == "" {
|
if s.BlockID == "" {
|
||||||
return ErrInvalidSubscription{"missing block id"}
|
return ErrInvalidSubscription{"missing block id"}
|
||||||
}
|
}
|
||||||
if s.WorkspaceID == "" {
|
|
||||||
return ErrInvalidSubscription{"missing workspace id"}
|
|
||||||
}
|
|
||||||
if s.BlockType == "" {
|
if s.BlockType == "" {
|
||||||
return ErrInvalidSubscription{"missing block type"}
|
return ErrInvalidSubscription{"missing block type"}
|
||||||
}
|
}
|
||||||
|
|
46
server/model/team.go
Normal file
46
server/model/team.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Team is information global to a team
|
||||||
|
// swagger:model
|
||||||
|
type Team struct {
|
||||||
|
// ID of the team
|
||||||
|
// required: true
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
// Title of the team
|
||||||
|
// required: false
|
||||||
|
Title string `json:"title"`
|
||||||
|
|
||||||
|
// Token required to register new users
|
||||||
|
// required: true
|
||||||
|
SignupToken string `json:"signupToken"`
|
||||||
|
|
||||||
|
// Team settings
|
||||||
|
// required: false
|
||||||
|
Settings map[string]interface{} `json:"settings"`
|
||||||
|
|
||||||
|
// ID of user who last modified this
|
||||||
|
// required: true
|
||||||
|
ModifiedBy string `json:"modifiedBy"`
|
||||||
|
|
||||||
|
// Updated time
|
||||||
|
// required: true
|
||||||
|
UpdateAt int64 `json:"updateAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TeamFromJSON(data io.Reader) *Team {
|
||||||
|
var team *Team
|
||||||
|
_ = json.NewDecoder(data).Decode(&team)
|
||||||
|
return team
|
||||||
|
}
|
||||||
|
|
||||||
|
func TeamsFromJSON(data io.Reader) []*Team {
|
||||||
|
var teams []*Team
|
||||||
|
_ = json.NewDecoder(data).Decode(&teams)
|
||||||
|
return teams
|
||||||
|
}
|
|
@ -57,6 +57,8 @@ type User struct {
|
||||||
IsBot bool `json:"is_bot"`
|
IsBot bool `json:"is_bot"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserPropPatch is a user property patch
|
||||||
|
// swagger:model
|
||||||
type UserPropPatch struct {
|
type UserPropPatch struct {
|
||||||
// The user prop updated fields
|
// The user prop updated fields
|
||||||
// required: false
|
// required: false
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
// Workspace is information global to a workspace
|
|
||||||
// swagger:model
|
|
||||||
type Workspace struct {
|
|
||||||
// ID of the workspace
|
|
||||||
// required: true
|
|
||||||
ID string `json:"id"`
|
|
||||||
|
|
||||||
// Title of the workspace
|
|
||||||
// required: false
|
|
||||||
Title string `json:"title"`
|
|
||||||
|
|
||||||
// Token required to register new users
|
|
||||||
// required: true
|
|
||||||
SignupToken string `json:"signupToken"`
|
|
||||||
|
|
||||||
// Workspace settings
|
|
||||||
// required: false
|
|
||||||
Settings map[string]interface{} `json:"settings"`
|
|
||||||
|
|
||||||
// ID of user who last modified this
|
|
||||||
// required: true
|
|
||||||
ModifiedBy string `json:"modifiedBy"`
|
|
||||||
|
|
||||||
// Updated time
|
|
||||||
// required: true
|
|
||||||
UpdateAt int64 `json:"updateAt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserWorkspace is a summary of a single association between
|
|
||||||
// a user and a workspace
|
|
||||||
// swagger:model
|
|
||||||
type UserWorkspace struct {
|
|
||||||
// ID of the workspace
|
|
||||||
// required: true
|
|
||||||
ID string `json:"id"`
|
|
||||||
|
|
||||||
// Title of the workspace
|
|
||||||
// required: false
|
|
||||||
Title string `json:"title"`
|
|
||||||
|
|
||||||
// Number of boards in the workspace
|
|
||||||
BoardCount int `json:"boardCount"`
|
|
||||||
}
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/services/config"
|
"github.com/mattermost/focalboard/server/services/config"
|
||||||
"github.com/mattermost/focalboard/server/services/notify"
|
"github.com/mattermost/focalboard/server/services/notify"
|
||||||
|
"github.com/mattermost/focalboard/server/services/permissions"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
"github.com/mattermost/focalboard/server/services/store"
|
||||||
"github.com/mattermost/focalboard/server/ws"
|
"github.com/mattermost/focalboard/server/ws"
|
||||||
|
|
||||||
|
@ -12,13 +13,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Params struct {
|
type Params struct {
|
||||||
Cfg *config.Configuration
|
Cfg *config.Configuration
|
||||||
SingleUserToken string
|
SingleUserToken string
|
||||||
DBStore store.Store
|
DBStore store.Store
|
||||||
Logger *mlog.Logger
|
Logger *mlog.Logger
|
||||||
ServerID string
|
ServerID string
|
||||||
WSAdapter ws.Adapter
|
WSAdapter ws.Adapter
|
||||||
NotifyBackends []notify.Backend
|
NotifyBackends []notify.Backend
|
||||||
|
PermissionsService permissions.PermissionsService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Params) CheckValid() error {
|
func (p Params) CheckValid() error {
|
||||||
|
@ -33,6 +35,10 @@ func (p Params) CheckValid() error {
|
||||||
if p.Logger == nil {
|
if p.Logger == nil {
|
||||||
return ErrServerParam{name: "Logger", issue: "cannot be nil"}
|
return ErrServerParam{name: "Logger", issue: "cannot be nil"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.PermissionsService == nil {
|
||||||
|
return ErrServerParam{name: "Permissions", issue: "cannot be nil"}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,12 +74,12 @@ func New(params Params) (*Server, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticator := auth.New(params.Cfg, params.DBStore)
|
authenticator := auth.New(params.Cfg, params.DBStore, params.PermissionsService)
|
||||||
|
|
||||||
// if no ws adapter is provided, we spin up a websocket server
|
// if no ws adapter is provided, we spin up a websocket server
|
||||||
wsAdapter := params.WSAdapter
|
wsAdapter := params.WSAdapter
|
||||||
if wsAdapter == nil {
|
if wsAdapter == nil {
|
||||||
wsAdapter = ws.NewServer(authenticator, params.SingleUserToken, params.Cfg.AuthMode == MattermostAuthMod, params.Logger)
|
wsAdapter = ws.NewServer(authenticator, params.SingleUserToken, params.Cfg.AuthMode == MattermostAuthMod, params.Logger, params.DBStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
filesBackendSettings := filestore.FileBackendSettings{}
|
filesBackendSettings := filestore.FileBackendSettings{}
|
||||||
|
@ -137,18 +137,19 @@ func New(params Params) (*Server, error) {
|
||||||
Metrics: metricsService,
|
Metrics: metricsService,
|
||||||
Notifications: notificationService,
|
Notifications: notificationService,
|
||||||
Logger: params.Logger,
|
Logger: params.Logger,
|
||||||
|
Permissions: params.PermissionsService,
|
||||||
}
|
}
|
||||||
app := app.New(params.Cfg, wsAdapter, appServices)
|
app := app.New(params.Cfg, wsAdapter, appServices)
|
||||||
|
|
||||||
focalboardAPI := api.NewAPI(app, params.SingleUserToken, params.Cfg.AuthMode, params.Logger, auditService)
|
focalboardAPI := api.NewAPI(app, params.SingleUserToken, params.Cfg.AuthMode, params.PermissionsService, params.Logger, auditService)
|
||||||
|
|
||||||
// Local router for admin APIs
|
// Local router for admin APIs
|
||||||
localRouter := mux.NewRouter()
|
localRouter := mux.NewRouter()
|
||||||
focalboardAPI.RegisterAdminRoutes(localRouter)
|
focalboardAPI.RegisterAdminRoutes(localRouter)
|
||||||
|
|
||||||
// Init workspace
|
// Init team
|
||||||
if _, err := app.GetRootWorkspace(); err != nil {
|
if _, err := app.GetRootTeam(); err != nil {
|
||||||
params.Logger.Error("Unable to get root workspace", mlog.Err(err))
|
params.Logger.Error("Unable to get root team", mlog.Err(err))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,6 +203,11 @@ func New(params Params) (*Server, error) {
|
||||||
|
|
||||||
server.initHandlers()
|
server.initHandlers()
|
||||||
|
|
||||||
|
if err := app.InitTemplates(); err != nil {
|
||||||
|
params.Logger.Error("Unable initialize team templates", mlog.Err(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &server, nil
|
return &server, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,13 +278,13 @@ func (s *Server) Start() error {
|
||||||
for blockType, count := range blockCounts {
|
for blockType, count := range blockCounts {
|
||||||
s.metricsService.ObserveBlockCount(blockType, count)
|
s.metricsService.ObserveBlockCount(blockType, count)
|
||||||
}
|
}
|
||||||
workspaceCount, err := s.store.GetWorkspaceCount()
|
teamCount, err := s.store.GetTeamCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Error updating metrics", mlog.String("group", "workspaces"), mlog.Err(err))
|
s.logger.Error("Error updating metrics", mlog.String("group", "teams"), mlog.Err(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.logger.Log(mlog.LvlFBMetrics, "Workspace metrics collected", mlog.Int64("workspace_count", workspaceCount))
|
s.logger.Log(mlog.LvlFBMetrics, "Team metrics collected", mlog.Int64("team_count", teamCount))
|
||||||
s.metricsService.ObserveWorkspaceCount(workspaceCount)
|
s.metricsService.ObserveTeamCount(teamCount)
|
||||||
}
|
}
|
||||||
// metricsUpdater() Calling this immediately causes integration unit tests to fail.
|
// metricsUpdater() Calling this immediately causes integration unit tests to fail.
|
||||||
s.metricsUpdaterTask = scheduler.CreateRecurringTask("updateMetrics", metricsUpdater, updateMetricsTaskFrequency)
|
s.metricsUpdaterTask = scheduler.CreateRecurringTask("updateMetrics", metricsUpdater, updateMetricsTaskFrequency)
|
||||||
|
@ -336,6 +342,8 @@ func (s *Server) Shutdown() error {
|
||||||
s.logger.Warn("Error occurred when shutting down notification service", mlog.Err(err))
|
s.logger.Warn("Error occurred when shutting down notification service", mlog.Err(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.app.Shutdown()
|
||||||
|
|
||||||
defer s.logger.Info("Server.Shutdown")
|
defer s.logger.Info("Server.Shutdown")
|
||||||
|
|
||||||
return s.store.Shutdown()
|
return s.store.Shutdown()
|
||||||
|
@ -472,13 +480,13 @@ func initTelemetry(opts telemetryOptions) *telemetry.Service {
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
})
|
})
|
||||||
telemetryService.RegisterTracker("workspaces", func() (telemetry.Tracker, error) {
|
telemetryService.RegisterTracker("teams", func() (telemetry.Tracker, error) {
|
||||||
count, err := opts.app.GetWorkspaceCount()
|
count, err := opts.app.GetTeamCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
m := map[string]interface{}{
|
m := map[string]interface{}{
|
||||||
"workspaces": count,
|
"teams": count,
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,15 +7,15 @@ import (
|
||||||
const (
|
const (
|
||||||
DefMaxQueueSize = 1000
|
DefMaxQueueSize = 1000
|
||||||
|
|
||||||
KeyAPIPath = "api_path"
|
KeyAPIPath = "api_path"
|
||||||
KeyEvent = "event"
|
KeyEvent = "event"
|
||||||
KeyStatus = "status"
|
KeyStatus = "status"
|
||||||
KeyUserID = "user_id"
|
KeyUserID = "user_id"
|
||||||
KeySessionID = "session_id"
|
KeySessionID = "session_id"
|
||||||
KeyClient = "client"
|
KeyClient = "client"
|
||||||
KeyIPAddress = "ip_address"
|
KeyIPAddress = "ip_address"
|
||||||
KeyClusterID = "cluster_id"
|
KeyClusterID = "cluster_id"
|
||||||
KeyWorkspaceID = "workspace_id"
|
KeyTeamID = "team_id"
|
||||||
|
|
||||||
Success = "success"
|
Success = "success"
|
||||||
Attempt = "attempt"
|
Attempt = "attempt"
|
||||||
|
|
|
@ -2,7 +2,6 @@ package auth
|
||||||
|
|
||||||
import "regexp"
|
import "regexp"
|
||||||
|
|
||||||
//nolint:lll
|
|
||||||
var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||||
|
|
||||||
// IsEmailValid checks if the email provided passes the required structure and length.
|
// IsEmailValid checks if the email provided passes the required structure and length.
|
||||||
|
|
|
@ -8,10 +8,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MetricsNamespace = "focalboard"
|
MetricsNamespace = "focalboard"
|
||||||
MetricsSubsystemBlocks = "blocks"
|
MetricsSubsystemBlocks = "blocks"
|
||||||
MetricsSubsystemWorkspaces = "workspaces"
|
MetricsSubsystemTeams = "teams"
|
||||||
MetricsSubsystemSystem = "system"
|
MetricsSubsystemSystem = "system"
|
||||||
|
|
||||||
MetricsCloudInstallationLabel = "installationId"
|
MetricsCloudInstallationLabel = "installationId"
|
||||||
)
|
)
|
||||||
|
@ -38,8 +38,8 @@ type Metrics struct {
|
||||||
blocksPatchedCount prometheus.Counter
|
blocksPatchedCount prometheus.Counter
|
||||||
blocksDeletedCount prometheus.Counter
|
blocksDeletedCount prometheus.Counter
|
||||||
|
|
||||||
blockCount *prometheus.GaugeVec
|
blockCount *prometheus.GaugeVec
|
||||||
workspaceCount prometheus.Gauge
|
teamCount prometheus.Gauge
|
||||||
|
|
||||||
blockLastActivity prometheus.Gauge
|
blockLastActivity prometheus.Gauge
|
||||||
}
|
}
|
||||||
|
@ -143,14 +143,14 @@ func NewMetrics(info InstanceInfo) *Metrics {
|
||||||
}, []string{"BlockType"})
|
}, []string{"BlockType"})
|
||||||
m.registry.MustRegister(m.blockCount)
|
m.registry.MustRegister(m.blockCount)
|
||||||
|
|
||||||
m.workspaceCount = prometheus.NewGauge(prometheus.GaugeOpts{
|
m.teamCount = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
Namespace: MetricsNamespace,
|
Namespace: MetricsNamespace,
|
||||||
Subsystem: MetricsSubsystemWorkspaces,
|
Subsystem: MetricsSubsystemTeams,
|
||||||
Name: "workspaces_total",
|
Name: "teams_total",
|
||||||
Help: "Total number of workspaces.",
|
Help: "Total number of teams.",
|
||||||
ConstLabels: additionalLabels,
|
ConstLabels: additionalLabels,
|
||||||
})
|
})
|
||||||
m.registry.MustRegister(m.workspaceCount)
|
m.registry.MustRegister(m.teamCount)
|
||||||
|
|
||||||
m.blockLastActivity = prometheus.NewGauge(prometheus.GaugeOpts{
|
m.blockLastActivity = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
Namespace: MetricsNamespace,
|
Namespace: MetricsNamespace,
|
||||||
|
@ -209,8 +209,8 @@ func (m *Metrics) ObserveBlockCount(blockType string, count int64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Metrics) ObserveWorkspaceCount(count int64) {
|
func (m *Metrics) ObserveTeamCount(count int64) {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
m.workspaceCount.Set(float64(count))
|
m.teamCount.Set(float64(count))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,6 @@ import (
|
||||||
// SubscriptionDelivery provides an interface for delivering subscription notifications to other systems, such as
|
// SubscriptionDelivery provides an interface for delivering subscription notifications to other systems, such as
|
||||||
// channels server via plugin API.
|
// channels server via plugin API.
|
||||||
type SubscriptionDelivery interface {
|
type SubscriptionDelivery interface {
|
||||||
SubscriptionDeliverSlackAttachments(workspaceID string, subscriberID string, subscriberType model.SubscriberType,
|
SubscriptionDeliverSlackAttachments(subscriberID string, subscriberType model.SubscriberType,
|
||||||
attachments []*mm_model.SlackAttachment) error
|
attachments []*mm_model.SlackAttachment) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,13 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Diff represents a difference between two versions of a block.
|
// Diff represents a difference between two versions of a block.
|
||||||
type Diff struct {
|
type Diff struct {
|
||||||
Board *model.Block
|
Board *model.Board
|
||||||
Card *model.Block
|
Card *model.Block
|
||||||
Authors StringMap
|
Authors StringMap
|
||||||
|
|
||||||
|
@ -40,16 +39,15 @@ type PropDiff struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type SchemaDiff struct {
|
type SchemaDiff struct {
|
||||||
Board *model.Block
|
Board *model.Board
|
||||||
|
|
||||||
OldPropDef *model.PropDef
|
OldPropDef *model.PropDef
|
||||||
NewPropDef *model.PropDef
|
NewPropDef *model.PropDef
|
||||||
}
|
}
|
||||||
|
|
||||||
type diffGenerator struct {
|
type diffGenerator struct {
|
||||||
container store.Container
|
board *model.Board
|
||||||
board *model.Block
|
card *model.Block
|
||||||
card *model.Block
|
|
||||||
|
|
||||||
store Store
|
store Store
|
||||||
hint *model.NotificationHint
|
hint *model.NotificationHint
|
||||||
|
@ -63,7 +61,7 @@ func (dg *diffGenerator) generateDiffs() ([]*Diff, error) {
|
||||||
Limit: 1,
|
Limit: 1,
|
||||||
Descending: true,
|
Descending: true,
|
||||||
}
|
}
|
||||||
blocks, err := dg.store.GetBlockHistory(dg.container, dg.hint.BlockID, opts)
|
blocks, err := dg.store.GetBlockHistory(dg.hint.BlockID, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not get block for notification: %w", err)
|
return nil, fmt.Errorf("could not get block for notification: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -84,7 +82,10 @@ func (dg *diffGenerator) generateDiffs() ([]*Diff, error) {
|
||||||
|
|
||||||
switch block.Type {
|
switch block.Type {
|
||||||
case model.TypeBoard:
|
case model.TypeBoard:
|
||||||
return dg.generateDiffsForBoard(block, schema)
|
dg.logger.Warn("generateDiffs for board skipped", mlog.String("block_id", block.ID))
|
||||||
|
// TODO: Fix this
|
||||||
|
// return dg.generateDiffsForBoard(block, schema)
|
||||||
|
return nil, nil
|
||||||
case model.TypeCard:
|
case model.TypeCard:
|
||||||
diff, err := dg.generateDiffsForCard(block, schema)
|
diff, err := dg.generateDiffsForCard(block, schema)
|
||||||
if err != nil || diff == nil {
|
if err != nil || diff == nil {
|
||||||
|
@ -100,27 +101,29 @@ func (dg *diffGenerator) generateDiffs() ([]*Diff, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dg *diffGenerator) generateDiffsForBoard(board *model.Block, schema model.PropSchema) ([]*Diff, error) {
|
// TODO: fix this
|
||||||
|
/*
|
||||||
|
func (dg *diffGenerator) generateDiffsForBoard(board *model.Board, schema model.PropSchema) ([]*Diff, error) {
|
||||||
opts := model.QuerySubtreeOptions{
|
opts := model.QuerySubtreeOptions{
|
||||||
AfterUpdateAt: dg.lastNotifyAt,
|
AfterUpdateAt: dg.lastNotifyAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
// find all child blocks of the board that updated since last notify.
|
find all child blocks of the board that updated since last notify.
|
||||||
blocks, err := dg.store.GetSubTree2(dg.container, board.ID, opts)
|
blocks, err := dg.store.GetSubTree2(board.ID, board.ID, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not get subtree for board %s: %w", board.ID, err)
|
return nil, fmt.Errorf("could not get subtree for board %s: %w", board.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var diffs []*Diff
|
var diffs []*Diff
|
||||||
|
|
||||||
// generate diff for board title change or description
|
generate diff for board title change or description
|
||||||
boardDiff, err := dg.generateDiffForBlock(board, schema)
|
boardDiff, err := dg.generateDiffForBlock(board, schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not generate diff for board %s: %w", board.ID, err)
|
return nil, fmt.Errorf("could not generate diff for board %s: %w", board.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if boardDiff != nil {
|
if boardDiff != nil {
|
||||||
// TODO: phase 2 feature (generate schema diffs and add to board diff) goes here.
|
TODO: phase 2 feature (generate schema diffs and add to board diff) goes here.
|
||||||
diffs = append(diffs, boardDiff)
|
diffs = append(diffs, boardDiff)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +139,7 @@ func (dg *diffGenerator) generateDiffsForBoard(board *model.Block, schema model.
|
||||||
}
|
}
|
||||||
return diffs, nil
|
return diffs, nil
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func (dg *diffGenerator) generateDiffsForCard(card *model.Block, schema model.PropSchema) (*Diff, error) {
|
func (dg *diffGenerator) generateDiffsForCard(card *model.Block, schema model.PropSchema) (*Diff, error) {
|
||||||
// generate diff for card title change and properties.
|
// generate diff for card title change and properties.
|
||||||
|
@ -148,7 +152,7 @@ func (dg *diffGenerator) generateDiffsForCard(card *model.Block, schema model.Pr
|
||||||
opts := model.QuerySubtreeOptions{
|
opts := model.QuerySubtreeOptions{
|
||||||
AfterUpdateAt: dg.lastNotifyAt,
|
AfterUpdateAt: dg.lastNotifyAt,
|
||||||
}
|
}
|
||||||
blocks, err := dg.store.GetSubTree2(dg.container, card.ID, opts)
|
blocks, err := dg.store.GetSubTree2(card.BoardID, card.ID, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not get subtree for card %s: %w", card.ID, err)
|
return nil, fmt.Errorf("could not get subtree for card %s: %w", card.ID, err)
|
||||||
}
|
}
|
||||||
|
@ -214,7 +218,7 @@ func (dg *diffGenerator) generateDiffForBlock(newBlock *model.Block, schema mode
|
||||||
Limit: 1,
|
Limit: 1,
|
||||||
Descending: true,
|
Descending: true,
|
||||||
}
|
}
|
||||||
history, err := dg.store.GetBlockHistory(dg.container, newBlock.ID, opts)
|
history, err := dg.store.GetBlockHistory(newBlock.ID, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not get block history for block %s: %w", newBlock.ID, err)
|
return nil, fmt.Errorf("could not get block history for block %s: %w", newBlock.ID, err)
|
||||||
}
|
}
|
||||||
|
@ -237,7 +241,7 @@ func (dg *diffGenerator) generateDiffForBlock(newBlock *model.Block, schema mode
|
||||||
AfterUpdateAt: dg.lastNotifyAt,
|
AfterUpdateAt: dg.lastNotifyAt,
|
||||||
Descending: true,
|
Descending: true,
|
||||||
}
|
}
|
||||||
chgBlocks, err := dg.store.GetBlockHistory(dg.container, newBlock.ID, opts)
|
chgBlocks, err := dg.store.GetBlockHistory(newBlock.ID, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting block history for block %s: %w", newBlock.ID, err)
|
return nil, fmt.Errorf("error getting block history for block %s: %w", newBlock.ID, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ var (
|
||||||
// DiffConvOpts provides options when converting diffs to slack attachments.
|
// DiffConvOpts provides options when converting diffs to slack attachments.
|
||||||
type DiffConvOpts struct {
|
type DiffConvOpts struct {
|
||||||
Language string
|
Language string
|
||||||
MakeCardLink func(block *model.Block, board *model.Block, card *model.Block) string
|
MakeCardLink func(block *model.Block, board *model.Board, card *model.Block) string
|
||||||
Logger *mlog.Logger
|
Logger *mlog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ func getTemplate(name string, opts DiffConvOpts, def string) (*template.Template
|
||||||
t = template.New(key)
|
t = template.New(key)
|
||||||
|
|
||||||
if opts.MakeCardLink == nil {
|
if opts.MakeCardLink == nil {
|
||||||
opts.MakeCardLink = func(block *model.Block, _ *model.Block, _ *model.Block) string {
|
opts.MakeCardLink = func(block *model.Block, _ *model.Board, _ *model.Block) string {
|
||||||
return fmt.Sprintf("`%s`", block.Title)
|
return fmt.Sprintf("`%s`", block.Title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,6 +160,7 @@ func cardDiff2SlackAttachment(cardDiff *Diff, opts DiffConvOpts) (*mm_model.Slac
|
||||||
mlog.String("card_id", cardDiff.Card.ID),
|
mlog.String("card_id", cardDiff.Card.ID),
|
||||||
mlog.String("new_block_id", cardDiff.NewBlock.ID),
|
mlog.String("new_block_id", cardDiff.NewBlock.ID),
|
||||||
mlog.String("old_block_id", cardDiff.OldBlock.ID),
|
mlog.String("old_block_id", cardDiff.OldBlock.ID),
|
||||||
|
mlog.Int("childDiffs", len(cardDiff.Diffs)),
|
||||||
)
|
)
|
||||||
|
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
|
|
|
@ -144,12 +144,8 @@ func (n *notifier) notify() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *notifier) notifySubscribers(hint *model.NotificationHint) error {
|
func (n *notifier) notifySubscribers(hint *model.NotificationHint) error {
|
||||||
c := store.Container{
|
|
||||||
WorkspaceID: hint.WorkspaceID,
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the subscriber list
|
// get the subscriber list
|
||||||
subs, err := n.store.GetSubscribersForBlock(c, hint.BlockID)
|
subs, err := n.store.GetSubscribersForBlock(hint.BlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -162,7 +158,7 @@ func (n *notifier) notifySubscribers(hint *model.NotificationHint) error {
|
||||||
oldestNotifiedAt := subs[0].NotifiedAt
|
oldestNotifiedAt := subs[0].NotifiedAt
|
||||||
|
|
||||||
// need the block's board and card.
|
// need the block's board and card.
|
||||||
board, card, err := n.store.GetBoardAndCardByID(c, hint.BlockID)
|
board, card, err := n.store.GetBoardAndCardByID(hint.BlockID)
|
||||||
if err != nil || board == nil || card == nil {
|
if err != nil || board == nil || card == nil {
|
||||||
return fmt.Errorf("could not get board & card for block %s: %w", hint.BlockID, err)
|
return fmt.Errorf("could not get board & card for block %s: %w", hint.BlockID, err)
|
||||||
}
|
}
|
||||||
|
@ -175,7 +171,6 @@ func (n *notifier) notifySubscribers(hint *model.NotificationHint) error {
|
||||||
)
|
)
|
||||||
|
|
||||||
dg := &diffGenerator{
|
dg := &diffGenerator{
|
||||||
container: c,
|
|
||||||
board: board,
|
board: board,
|
||||||
card: card,
|
card: card,
|
||||||
store: n.store,
|
store: n.store,
|
||||||
|
@ -204,8 +199,8 @@ func (n *notifier) notifySubscribers(hint *model.NotificationHint) error {
|
||||||
|
|
||||||
opts := DiffConvOpts{
|
opts := DiffConvOpts{
|
||||||
Language: "en", // TODO: use correct language with i18n available on server.
|
Language: "en", // TODO: use correct language with i18n available on server.
|
||||||
MakeCardLink: func(block *model.Block, board *model.Block, card *model.Block) string {
|
MakeCardLink: func(block *model.Block, board *model.Board, card *model.Block) string {
|
||||||
return fmt.Sprintf("[%s](%s)", block.Title, utils.MakeCardLink(n.serverRoot, board.WorkspaceID, board.ID, card.ID))
|
return fmt.Sprintf("[%s](%s)", block.Title, utils.MakeCardLink(n.serverRoot, board.TeamID, board.ID, card.ID))
|
||||||
},
|
},
|
||||||
Logger: n.logger,
|
Logger: n.logger,
|
||||||
}
|
}
|
||||||
|
@ -236,7 +231,7 @@ func (n *notifier) notifySubscribers(hint *model.NotificationHint) error {
|
||||||
mlog.String("subscriber_type", string(sub.SubscriberType)),
|
mlog.String("subscriber_type", string(sub.SubscriberType)),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err = n.delivery.SubscriptionDeliverSlackAttachments(hint.WorkspaceID, sub.SubscriberID, sub.SubscriberType, attachments); err != nil {
|
if err = n.delivery.SubscriptionDeliverSlackAttachments(sub.SubscriberID, sub.SubscriberType, attachments); err != nil {
|
||||||
merr.Append(fmt.Errorf("cannot deliver notification to subscriber %s [%s]: %w",
|
merr.Append(fmt.Errorf("cannot deliver notification to subscriber %s [%s]: %w",
|
||||||
sub.SubscriberID, sub.SubscriberType, err))
|
sub.SubscriberID, sub.SubscriberType, err))
|
||||||
}
|
}
|
||||||
|
@ -262,7 +257,7 @@ func (n *notifier) notifySubscribers(hint *model.NotificationHint) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the last notified_at for all subscribers since we at least attempted to notify all of them.
|
// update the last notified_at for all subscribers since we at least attempted to notify all of them.
|
||||||
err = dg.store.UpdateSubscribersNotifiedAt(dg.container, dg.hint.BlockID, notifiedAt)
|
err = dg.store.UpdateSubscribersNotifiedAt(dg.hint.BlockID, notifiedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
merr.Append(fmt.Errorf("could not update subscribers notified_at for block %s: %w", dg.hint.BlockID, err))
|
merr.Append(fmt.Errorf("could not update subscribers notified_at for block %s: %w", dg.hint.BlockID, err))
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,21 +7,20 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Store interface {
|
type Store interface {
|
||||||
GetBlock(c store.Container, blockID string) (*model.Block, error)
|
GetBlock(blockID string) (*model.Block, error)
|
||||||
GetBlockHistory(c store.Container, blockID string, opts model.QueryBlockHistoryOptions) ([]model.Block, error)
|
GetBlockHistory(blockID string, opts model.QueryBlockHistoryOptions) ([]model.Block, error)
|
||||||
GetSubTree2(c store.Container, blockID string, opts model.QuerySubtreeOptions) ([]model.Block, error)
|
GetSubTree2(boardID, blockID string, opts model.QuerySubtreeOptions) ([]model.Block, error)
|
||||||
GetBoardAndCardByID(c store.Container, blockID string) (board *model.Block, card *model.Block, err error)
|
GetBoardAndCardByID(blockID string) (board *model.Board, card *model.Block, err error)
|
||||||
|
|
||||||
GetUserByID(userID string) (*model.User, error)
|
GetUserByID(userID string) (*model.User, error)
|
||||||
|
|
||||||
CreateSubscription(c store.Container, sub *model.Subscription) (*model.Subscription, error)
|
CreateSubscription(sub *model.Subscription) (*model.Subscription, error)
|
||||||
GetSubscribersForBlock(c store.Container, blockID string) ([]*model.Subscriber, error)
|
GetSubscribersForBlock(blockID string) ([]*model.Subscriber, error)
|
||||||
GetSubscribersCountForBlock(c store.Container, blockID string) (int, error)
|
GetSubscribersCountForBlock(blockID string) (int, error)
|
||||||
UpdateSubscribersNotifiedAt(c store.Container, blockID string, notifyAt int64) error
|
UpdateSubscribersNotifiedAt(blockID string, notifyAt int64) error
|
||||||
|
|
||||||
UpsertNotificationHint(hint *model.NotificationHint, notificationFreq time.Duration) (*model.NotificationHint, error)
|
UpsertNotificationHint(hint *model.NotificationHint, notificationFreq time.Duration) (*model.NotificationHint, error)
|
||||||
GetNextNotificationHint(remove bool) (*model.NotificationHint, error)
|
GetNextNotificationHint(remove bool) (*model.NotificationHint, error)
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/services/notify"
|
"github.com/mattermost/focalboard/server/services/notify"
|
||||||
"github.com/mattermost/focalboard/server/services/store"
|
|
||||||
"github.com/mattermost/focalboard/server/ws"
|
"github.com/mattermost/focalboard/server/ws"
|
||||||
"github.com/wiggin77/merror"
|
"github.com/wiggin77/merror"
|
||||||
|
|
||||||
|
@ -77,8 +76,6 @@ func (b *Backend) getBlockUpdateFreq(blockType model.BlockType) time.Duration {
|
||||||
switch blockType {
|
switch blockType {
|
||||||
case model.TypeCard:
|
case model.TypeCard:
|
||||||
return time.Second * time.Duration(b.notifyFreqCardSeconds)
|
return time.Second * time.Duration(b.notifyFreqCardSeconds)
|
||||||
case model.TypeBoard:
|
|
||||||
return time.Second * time.Duration(b.notifyFreqBoardSeconds)
|
|
||||||
default:
|
default:
|
||||||
return defBlockNotificationFreq
|
return defBlockNotificationFreq
|
||||||
}
|
}
|
||||||
|
@ -95,35 +92,30 @@ func (b *Backend) BlockChanged(evt notify.BlockChangeEvent) error {
|
||||||
merr := merror.New()
|
merr := merror.New()
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
c := store.Container{
|
|
||||||
WorkspaceID: evt.Workspace,
|
|
||||||
}
|
|
||||||
|
|
||||||
// if new card added, automatically subscribe the author.
|
// if new card added, automatically subscribe the author.
|
||||||
if evt.Action == notify.Add && evt.BlockChanged.Type == model.TypeCard {
|
if evt.Action == notify.Add && evt.BlockChanged.Type == model.TypeCard {
|
||||||
sub := &model.Subscription{
|
sub := &model.Subscription{
|
||||||
BlockType: model.TypeCard,
|
BlockType: model.TypeCard,
|
||||||
BlockID: evt.BlockChanged.ID,
|
BlockID: evt.BlockChanged.ID,
|
||||||
WorkspaceID: evt.Workspace,
|
|
||||||
SubscriberType: model.SubTypeUser,
|
SubscriberType: model.SubTypeUser,
|
||||||
SubscriberID: evt.ModifiedByID,
|
SubscriberID: evt.ModifiedByID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if sub, err = b.store.CreateSubscription(c, sub); err != nil {
|
if _, err = b.store.CreateSubscription(sub); err != nil {
|
||||||
b.logger.Warn("Cannot subscribe card author to card",
|
b.logger.Warn("Cannot subscribe card author to card",
|
||||||
mlog.String("card_id", evt.BlockChanged.ID),
|
mlog.String("card_id", evt.BlockChanged.ID),
|
||||||
mlog.Err(err),
|
mlog.Err(err),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
b.wsAdapter.BroadcastSubscriptionChange(c.WorkspaceID, sub)
|
b.wsAdapter.BroadcastSubscriptionChange(evt.TeamID, sub)
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify board subscribers
|
// notify board subscribers
|
||||||
subs, err := b.store.GetSubscribersForBlock(c, evt.Board.ID)
|
subs, err := b.store.GetSubscribersForBlock(evt.Board.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
merr.Append(fmt.Errorf("cannot fetch subscribers for board %s: %w", evt.Board.ID, err))
|
merr.Append(fmt.Errorf("cannot fetch subscribers for board %s: %w", evt.Board.ID, err))
|
||||||
}
|
}
|
||||||
if err = b.notifySubscribers(subs, evt.Board, evt.ModifiedByID); err != nil {
|
if err = b.notifySubscribers(subs, evt.Board.ID, model.TypeBoard, evt.ModifiedByID); err != nil {
|
||||||
merr.Append(fmt.Errorf("cannot notify board subscribers for board %s: %w", evt.Board.ID, err))
|
merr.Append(fmt.Errorf("cannot notify board subscribers for board %s: %w", evt.Board.ID, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,21 +124,21 @@ func (b *Backend) BlockChanged(evt notify.BlockChangeEvent) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify card subscribers
|
// notify card subscribers
|
||||||
subs, err = b.store.GetSubscribersForBlock(c, evt.Card.ID)
|
subs, err = b.store.GetSubscribersForBlock(evt.Card.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
merr.Append(fmt.Errorf("cannot fetch subscribers for card %s: %w", evt.Card.ID, err))
|
merr.Append(fmt.Errorf("cannot fetch subscribers for card %s: %w", evt.Card.ID, err))
|
||||||
}
|
}
|
||||||
if err = b.notifySubscribers(subs, evt.Card, evt.ModifiedByID); err != nil {
|
if err = b.notifySubscribers(subs, evt.Card.ID, model.TypeCard, evt.ModifiedByID); err != nil {
|
||||||
merr.Append(fmt.Errorf("cannot notify card subscribers for card %s: %w", evt.Card.ID, err))
|
merr.Append(fmt.Errorf("cannot notify card subscribers for card %s: %w", evt.Card.ID, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify block subscribers (if/when other types can be subscribed to)
|
// notify block subscribers (if/when other types can be subscribed to)
|
||||||
if evt.Board.ID != evt.BlockChanged.ID && evt.Card.ID != evt.BlockChanged.ID {
|
if evt.Board.ID != evt.BlockChanged.ID && evt.Card.ID != evt.BlockChanged.ID {
|
||||||
subs, err := b.store.GetSubscribersForBlock(c, evt.BlockChanged.ID)
|
subs, err := b.store.GetSubscribersForBlock(evt.BlockChanged.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
merr.Append(fmt.Errorf("cannot fetch subscribers for block %s: %w", evt.BlockChanged.ID, err))
|
merr.Append(fmt.Errorf("cannot fetch subscribers for block %s: %w", evt.BlockChanged.ID, err))
|
||||||
}
|
}
|
||||||
if err := b.notifySubscribers(subs, evt.BlockChanged, evt.ModifiedByID); err != nil {
|
if err := b.notifySubscribers(subs, evt.BlockChanged.ID, evt.BlockChanged.Type, evt.ModifiedByID); err != nil {
|
||||||
merr.Append(fmt.Errorf("cannot notify block subscribers for block %s: %w", evt.BlockChanged.ID, err))
|
merr.Append(fmt.Errorf("cannot notify block subscribers for block %s: %w", evt.BlockChanged.ID, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,24 +146,26 @@ func (b *Backend) BlockChanged(evt notify.BlockChangeEvent) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// notifySubscribers triggers a change notification for subscribers by writing a notification hint to the database.
|
// notifySubscribers triggers a change notification for subscribers by writing a notification hint to the database.
|
||||||
func (b *Backend) notifySubscribers(subs []*model.Subscriber, block *model.Block, modifiedByID string) error {
|
func (b *Backend) notifySubscribers(subs []*model.Subscriber, blockID string, idType model.BlockType, modifiedByID string) error {
|
||||||
if len(subs) == 0 {
|
if len(subs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
hint := &model.NotificationHint{
|
hint := &model.NotificationHint{
|
||||||
BlockType: block.Type,
|
BlockType: idType,
|
||||||
BlockID: block.ID,
|
BlockID: blockID,
|
||||||
WorkspaceID: block.WorkspaceID,
|
|
||||||
ModifiedByID: modifiedByID,
|
ModifiedByID: modifiedByID,
|
||||||
}
|
}
|
||||||
|
|
||||||
hint, err := b.store.UpsertNotificationHint(hint, b.getBlockUpdateFreq(block.Type))
|
hint, err := b.store.UpsertNotificationHint(hint, b.getBlockUpdateFreq(idType))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot upsert notification hint: %w", err)
|
return fmt.Errorf("cannot upsert notification hint: %w", err)
|
||||||
}
|
}
|
||||||
|
if err := b.notifier.onNotifyHint(hint); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return b.notifier.onNotifyHint(hint)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnMention satisfies the `MentionListener` interface and is called whenever a @mention notification
|
// OnMention satisfies the `MentionListener` interface and is called whenever a @mention notification
|
||||||
|
@ -188,17 +182,13 @@ func (b *Backend) OnMention(userID string, evt notify.BlockChangeEvent) {
|
||||||
sub := &model.Subscription{
|
sub := &model.Subscription{
|
||||||
BlockType: model.TypeCard,
|
BlockType: model.TypeCard,
|
||||||
BlockID: evt.Card.ID,
|
BlockID: evt.Card.ID,
|
||||||
WorkspaceID: evt.Workspace,
|
|
||||||
SubscriberType: model.SubTypeUser,
|
SubscriberType: model.SubTypeUser,
|
||||||
SubscriberID: userID,
|
SubscriberID: userID,
|
||||||
}
|
}
|
||||||
|
|
||||||
c := store.Container{
|
|
||||||
WorkspaceID: evt.Workspace,
|
|
||||||
}
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if sub, err = b.store.CreateSubscription(c, sub); err != nil {
|
if sub, err = b.store.CreateSubscription(sub); err != nil {
|
||||||
b.logger.Warn("Cannot subscribe mentioned user to card",
|
b.logger.Warn("Cannot subscribe mentioned user to card",
|
||||||
mlog.String("user_id", userID),
|
mlog.String("user_id", userID),
|
||||||
mlog.String("card_id", evt.Card.ID),
|
mlog.String("card_id", evt.Card.ID),
|
||||||
|
@ -206,7 +196,7 @@ func (b *Backend) OnMention(userID string, evt notify.BlockChangeEvent) {
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b.wsAdapter.BroadcastSubscriptionChange(c.WorkspaceID, sub)
|
b.wsAdapter.BroadcastSubscriptionChange(evt.TeamID, sub)
|
||||||
|
|
||||||
b.logger.Debug("Subscribed mentioned user to card",
|
b.logger.Debug("Subscribed mentioned user to card",
|
||||||
mlog.String("user_id", userID),
|
mlog.String("user_id", userID),
|
||||||
|
|
|
@ -14,13 +14,7 @@ import (
|
||||||
|
|
||||||
// MentionDeliver notifies a user they have been mentioned in a block.
|
// MentionDeliver notifies a user they have been mentioned in a block.
|
||||||
func (pd *PluginDelivery) MentionDeliver(mentionUsername string, extract string, evt notify.BlockChangeEvent) (string, error) {
|
func (pd *PluginDelivery) MentionDeliver(mentionUsername string, extract string, evt notify.BlockChangeEvent) (string, error) {
|
||||||
// determine which team the workspace is associated with
|
member, err := teamMemberFromUsername(pd.api, mentionUsername, evt.TeamID)
|
||||||
teamID, err := pd.getTeamID(evt)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("cannot determine teamID for block change notification: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
member, err := teamMemberFromUsername(pd.api, mentionUsername, teamID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isErrNotFound(err) {
|
if isErrNotFound(err) {
|
||||||
// not really an error; could just be someone typed "@sometext"
|
// not really an error; could just be someone typed "@sometext"
|
||||||
|
@ -30,16 +24,6 @@ func (pd *PluginDelivery) MentionDeliver(mentionUsername string, extract string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that user is a member of the channel
|
|
||||||
_, err = pd.api.GetChannelMember(evt.Workspace, member.UserId)
|
|
||||||
if err != nil {
|
|
||||||
if pd.api.IsErrNotFound(err) {
|
|
||||||
// mentioned user is not a member of the channel; fail silently.
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("cannot fetch channel member for user %s: %w", member.UserId, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
author, err := pd.api.GetUserByID(evt.ModifiedByID)
|
author, err := pd.api.GetUserByID(evt.ModifiedByID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("cannot find user: %w", err)
|
return "", fmt.Errorf("cannot find user: %w", err)
|
||||||
|
@ -49,7 +33,7 @@ func (pd *PluginDelivery) MentionDeliver(mentionUsername string, extract string,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("cannot get direct channel: %w", err)
|
return "", fmt.Errorf("cannot get direct channel: %w", err)
|
||||||
}
|
}
|
||||||
link := utils.MakeCardLink(pd.serverRoot, evt.Workspace, evt.Board.ID, evt.Card.ID)
|
link := utils.MakeCardLink(pd.serverRoot, evt.Board.TeamID, evt.Board.ID, evt.Card.ID)
|
||||||
|
|
||||||
post := &model.Post{
|
post := &model.Post{
|
||||||
UserId: pd.botID,
|
UserId: pd.botID,
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
package plugindelivery
|
package plugindelivery
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/services/notify"
|
"github.com/mattermost/focalboard/server/services/notify"
|
||||||
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
|
|
||||||
mm_model "github.com/mattermost/mattermost-server/v6/model"
|
mm_model "github.com/mattermost/mattermost-server/v6/model"
|
||||||
)
|
)
|
||||||
|
@ -52,11 +55,32 @@ func New(botID string, serverRoot string, api PluginAPI) *PluginDelivery {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pd *PluginDelivery) getTeamID(evt notify.BlockChangeEvent) (string, error) {
|
func (pd *PluginDelivery) Deliver(mentionUsername string, extract string, evt notify.BlockChangeEvent) error {
|
||||||
// for now, the workspace ID is also the channel ID
|
member, err := teamMemberFromUsername(pd.api, mentionUsername, evt.TeamID)
|
||||||
channel, err := pd.api.GetChannelByID(evt.Workspace)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
if isErrNotFound(err) {
|
||||||
|
// not really an error; could just be someone typed "@sometext"
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("cannot lookup mentioned user: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return channel.TeamId, nil
|
|
||||||
|
author, err := pd.api.GetUserByID(evt.ModifiedByID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot find user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, err := pd.api.GetDirectChannel(member.UserId, pd.botID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot get direct channel: %w", err)
|
||||||
|
}
|
||||||
|
link := utils.MakeCardLink(pd.serverRoot, evt.TeamID, evt.Board.ID, evt.Card.ID)
|
||||||
|
|
||||||
|
post := &mm_model.Post{
|
||||||
|
UserId: pd.botID,
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: formatMessage(author.Username, extract, evt.Card.Title, link, evt.BlockChanged),
|
||||||
|
}
|
||||||
|
return pd.api.CreatePost(post)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,10 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// SubscriptionDeliverSlashAttachments notifies a user that changes were made to a block they are subscribed to.
|
// SubscriptionDeliverSlashAttachments notifies a user that changes were made to a block they are subscribed to.
|
||||||
func (pd *PluginDelivery) SubscriptionDeliverSlackAttachments(workspaceID string, subscriberID string, subscriptionType model.SubscriberType,
|
func (pd *PluginDelivery) SubscriptionDeliverSlackAttachments(subscriberID string, subscriptionType model.SubscriberType,
|
||||||
attachments []*mm_model.SlackAttachment) error {
|
attachments []*mm_model.SlackAttachment) error {
|
||||||
// check subscriber is member of channel
|
// check subscriber is member of channel
|
||||||
_, err := pd.api.GetChannelMember(workspaceID, subscriberID)
|
_, err := pd.api.GetUserByID(subscriberID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if pd.api.IsErrNotFound(err) {
|
if pd.api.IsErrNotFound(err) {
|
||||||
// subscriber is not a member of the channel; fail silently.
|
// subscriber is not a member of the channel; fail silently.
|
||||||
|
|
|
@ -22,8 +22,8 @@ const (
|
||||||
|
|
||||||
type BlockChangeEvent struct {
|
type BlockChangeEvent struct {
|
||||||
Action Action
|
Action Action
|
||||||
Workspace string
|
TeamID string
|
||||||
Board *model.Block
|
Board *model.Board
|
||||||
Card *model.Block
|
Card *model.Block
|
||||||
BlockChanged *model.Block
|
BlockChanged *model.Block
|
||||||
BlockOld *model.Block
|
BlockOld *model.Block
|
||||||
|
@ -31,7 +31,7 @@ type BlockChangeEvent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubscriptionChangeNotifier interface {
|
type SubscriptionChangeNotifier interface {
|
||||||
BroadcastSubscriptionChange(workspaceID string, subscription *model.Subscription)
|
BroadcastSubscriptionChange(subscription *model.Subscription)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backend provides an interface for sending notifications.
|
// Backend provides an interface for sending notifications.
|
||||||
|
@ -113,7 +113,7 @@ func (s *Service) BlockChanged(evt BlockChangeEvent) {
|
||||||
|
|
||||||
// BroadcastSubscriptionChange sends a websocket message with details of the changed subscription to all
|
// BroadcastSubscriptionChange sends a websocket message with details of the changed subscription to all
|
||||||
// connected users in the workspace.
|
// connected users in the workspace.
|
||||||
func (s *Service) BroadcastSubscriptionChange(workspaceID string, subscription *model.Subscription) {
|
func (s *Service) BroadcastSubscriptionChange(subscription *model.Subscription) {
|
||||||
s.mux.RLock()
|
s.mux.RLock()
|
||||||
backends := make([]Backend, len(s.backends))
|
backends := make([]Backend, len(s.backends))
|
||||||
copy(backends, s.backends)
|
copy(backends, s.backends)
|
||||||
|
@ -122,11 +122,10 @@ func (s *Service) BroadcastSubscriptionChange(workspaceID string, subscription *
|
||||||
for _, backend := range backends {
|
for _, backend := range backends {
|
||||||
if scn, ok := backend.(SubscriptionChangeNotifier); ok {
|
if scn, ok := backend.(SubscriptionChangeNotifier); ok {
|
||||||
s.logger.Debug("Delivering subscription change notification",
|
s.logger.Debug("Delivering subscription change notification",
|
||||||
mlog.String("workspace_id", workspaceID),
|
|
||||||
mlog.String("block_id", subscription.BlockID),
|
mlog.String("block_id", subscription.BlockID),
|
||||||
mlog.String("subscriber_id", subscription.SubscriberID),
|
mlog.String("subscriber_id", subscription.SubscriberID),
|
||||||
)
|
)
|
||||||
scn.BroadcastSubscriptionChange(workspaceID, subscription)
|
scn.BroadcastSubscriptionChange(subscription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
61
server/services/permissions/localpermissions/helpers_test.go
Normal file
61
server/services/permissions/localpermissions/helpers_test.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package localpermissions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mattermost/focalboard/server/model"
|
||||||
|
permissionsMocks "github.com/mattermost/focalboard/server/services/permissions/mocks"
|
||||||
|
|
||||||
|
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestHelper struct {
|
||||||
|
t *testing.T
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
store *permissionsMocks.MockStore
|
||||||
|
permissions *Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetupTestHelper(t *testing.T) *TestHelper {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
mockStore := permissionsMocks.NewMockStore(ctrl)
|
||||||
|
|
||||||
|
return &TestHelper{
|
||||||
|
t: t,
|
||||||
|
ctrl: ctrl,
|
||||||
|
store: mockStore,
|
||||||
|
permissions: New(mockStore, nil),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) checkBoardPermissions(roleName string, member *model.BoardMember, hasPermissionTo, hasNotPermissionTo []*mmModel.Permission) {
|
||||||
|
for _, p := range hasPermissionTo {
|
||||||
|
th.t.Run(roleName+" "+p.Id, func(t *testing.T) {
|
||||||
|
th.store.EXPECT().
|
||||||
|
GetMemberForBoard(member.BoardID, member.UserID).
|
||||||
|
Return(member, nil).
|
||||||
|
Times(1)
|
||||||
|
|
||||||
|
hasPermission := th.permissions.HasPermissionToBoard(member.UserID, member.BoardID, p)
|
||||||
|
assert.True(t, hasPermission)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range hasNotPermissionTo {
|
||||||
|
th.t.Run(roleName+" "+p.Id, func(t *testing.T) {
|
||||||
|
th.store.EXPECT().
|
||||||
|
GetMemberForBoard(member.BoardID, member.UserID).
|
||||||
|
Return(member, nil).
|
||||||
|
Times(1)
|
||||||
|
|
||||||
|
hasPermission := th.permissions.HasPermissionToBoard(member.UserID, member.BoardID, p)
|
||||||
|
assert.False(t, hasPermission)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package localpermissions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/mattermost/focalboard/server/model"
|
||||||
|
"github.com/mattermost/focalboard/server/services/permissions"
|
||||||
|
|
||||||
|
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
store permissions.Store
|
||||||
|
logger *mlog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(store permissions.Store, logger *mlog.Logger) *Service {
|
||||||
|
return &Service{
|
||||||
|
store: store,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) HasPermissionToTeam(userID, teamID string, permission *mmModel.Permission) bool {
|
||||||
|
if userID == "" || teamID == "" || permission == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) HasPermissionToBoard(userID, boardID string, permission *mmModel.Permission) bool {
|
||||||
|
if userID == "" || boardID == "" || permission == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
member, err := s.store.GetMemberForBoard(boardID, userID)
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("error getting member for board",
|
||||||
|
mlog.String("boardID", boardID),
|
||||||
|
mlog.String("userID", userID),
|
||||||
|
mlog.Err(err),
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch permission {
|
||||||
|
case model.PermissionManageBoardType, model.PermissionDeleteBoard, model.PermissionManageBoardRoles, model.PermissionShareBoard:
|
||||||
|
return member.SchemeAdmin
|
||||||
|
case model.PermissionManageBoardCards, model.PermissionManageBoardProperties:
|
||||||
|
return member.SchemeAdmin || member.SchemeEditor
|
||||||
|
case model.PermissionViewBoard:
|
||||||
|
return member.SchemeAdmin || member.SchemeEditor || member.SchemeCommenter || member.SchemeViewer
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package localpermissions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mattermost/focalboard/server/model"
|
||||||
|
|
||||||
|
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHasPermissionToTeam(t *testing.T) {
|
||||||
|
th := SetupTestHelper(t)
|
||||||
|
|
||||||
|
t.Run("empty input should always unauthorize", func(t *testing.T) {
|
||||||
|
assert.False(t, th.permissions.HasPermissionToTeam("", "team-id", model.PermissionManageBoardCards))
|
||||||
|
assert.False(t, th.permissions.HasPermissionToTeam("user-id", "", model.PermissionManageBoardCards))
|
||||||
|
assert.False(t, th.permissions.HasPermissionToTeam("user-id", "team-id", nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("all users have all permissions on teams", func(t *testing.T) {
|
||||||
|
hasPermission := th.permissions.HasPermissionToTeam("user-id", "team-id", model.PermissionManageBoardCards)
|
||||||
|
assert.True(t, hasPermission)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHasPermissionToBoard(t *testing.T) {
|
||||||
|
th := SetupTestHelper(t)
|
||||||
|
|
||||||
|
t.Run("empty input should always unauthorize", func(t *testing.T) {
|
||||||
|
assert.False(t, th.permissions.HasPermissionToBoard("", "board-id", model.PermissionManageBoardCards))
|
||||||
|
assert.False(t, th.permissions.HasPermissionToBoard("user-id", "", model.PermissionManageBoardCards))
|
||||||
|
assert.False(t, th.permissions.HasPermissionToBoard("user-id", "board-id", nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nonexistent user", func(t *testing.T) {
|
||||||
|
userID := "user-id"
|
||||||
|
boardID := "board-id"
|
||||||
|
|
||||||
|
th.store.EXPECT().
|
||||||
|
GetMemberForBoard(boardID, userID).
|
||||||
|
Return(nil, sql.ErrNoRows).
|
||||||
|
Times(1)
|
||||||
|
|
||||||
|
hasPermission := th.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards)
|
||||||
|
assert.False(t, hasPermission)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("board admin", func(t *testing.T) {
|
||||||
|
member := &model.BoardMember{
|
||||||
|
UserID: "user-id",
|
||||||
|
BoardID: "board-id",
|
||||||
|
SchemeAdmin: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
hasPermissionTo := []*mmModel.Permission{
|
||||||
|
model.PermissionManageBoardType,
|
||||||
|
model.PermissionDeleteBoard,
|
||||||
|
model.PermissionManageBoardRoles,
|
||||||
|
model.PermissionShareBoard,
|
||||||
|
model.PermissionManageBoardCards,
|
||||||
|
model.PermissionViewBoard,
|
||||||
|
model.PermissionManageBoardProperties,
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNotPermissionTo := []*mmModel.Permission{}
|
||||||
|
|
||||||
|
th.checkBoardPermissions("admin", member, hasPermissionTo, hasNotPermissionTo)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("board editor", func(t *testing.T) {
|
||||||
|
member := &model.BoardMember{
|
||||||
|
UserID: "user-id",
|
||||||
|
BoardID: "board-id",
|
||||||
|
SchemeEditor: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
hasPermissionTo := []*mmModel.Permission{
|
||||||
|
model.PermissionManageBoardCards,
|
||||||
|
model.PermissionViewBoard,
|
||||||
|
model.PermissionManageBoardProperties,
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNotPermissionTo := []*mmModel.Permission{
|
||||||
|
model.PermissionManageBoardType,
|
||||||
|
model.PermissionDeleteBoard,
|
||||||
|
model.PermissionManageBoardRoles,
|
||||||
|
model.PermissionShareBoard,
|
||||||
|
}
|
||||||
|
|
||||||
|
th.checkBoardPermissions("editor", member, hasPermissionTo, hasNotPermissionTo)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("board commenter", func(t *testing.T) {
|
||||||
|
member := &model.BoardMember{
|
||||||
|
UserID: "user-id",
|
||||||
|
BoardID: "board-id",
|
||||||
|
SchemeCommenter: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
hasPermissionTo := []*mmModel.Permission{
|
||||||
|
model.PermissionViewBoard,
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNotPermissionTo := []*mmModel.Permission{
|
||||||
|
model.PermissionManageBoardType,
|
||||||
|
model.PermissionDeleteBoard,
|
||||||
|
model.PermissionManageBoardRoles,
|
||||||
|
model.PermissionShareBoard,
|
||||||
|
model.PermissionManageBoardCards,
|
||||||
|
model.PermissionManageBoardProperties,
|
||||||
|
}
|
||||||
|
|
||||||
|
th.checkBoardPermissions("commenter", member, hasPermissionTo, hasNotPermissionTo)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("board viewer", func(t *testing.T) {
|
||||||
|
member := &model.BoardMember{
|
||||||
|
UserID: "user-id",
|
||||||
|
BoardID: "board-id",
|
||||||
|
SchemeViewer: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
hasPermissionTo := []*mmModel.Permission{
|
||||||
|
model.PermissionViewBoard,
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNotPermissionTo := []*mmModel.Permission{
|
||||||
|
model.PermissionManageBoardType,
|
||||||
|
model.PermissionDeleteBoard,
|
||||||
|
model.PermissionManageBoardRoles,
|
||||||
|
model.PermissionShareBoard,
|
||||||
|
model.PermissionManageBoardCards,
|
||||||
|
model.PermissionManageBoardProperties,
|
||||||
|
}
|
||||||
|
|
||||||
|
th.checkBoardPermissions("viewer", member, hasPermissionTo, hasNotPermissionTo)
|
||||||
|
})
|
||||||
|
}
|
86
server/services/permissions/mmpermissions/helpers_test.go
Normal file
86
server/services/permissions/mmpermissions/helpers_test.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package mmpermissions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mattermost/focalboard/server/model"
|
||||||
|
mmpermissionsMocks "github.com/mattermost/focalboard/server/services/permissions/mmpermissions/mocks"
|
||||||
|
permissionsMocks "github.com/mattermost/focalboard/server/services/permissions/mocks"
|
||||||
|
|
||||||
|
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestHelper struct {
|
||||||
|
t *testing.T
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
store *permissionsMocks.MockStore
|
||||||
|
api *mmpermissionsMocks.MockAPI
|
||||||
|
permissions *Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetupTestHelper(t *testing.T) *TestHelper {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
mockStore := permissionsMocks.NewMockStore(ctrl)
|
||||||
|
mockAPI := mmpermissionsMocks.NewMockAPI(ctrl)
|
||||||
|
|
||||||
|
return &TestHelper{
|
||||||
|
t: t,
|
||||||
|
ctrl: ctrl,
|
||||||
|
store: mockStore,
|
||||||
|
api: mockAPI,
|
||||||
|
permissions: New(mockStore, mockAPI),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) checkBoardPermissions(roleName string, member *model.BoardMember, teamID string,
|
||||||
|
hasPermissionTo, hasNotPermissionTo []*mmModel.Permission) {
|
||||||
|
for _, p := range hasPermissionTo {
|
||||||
|
th.t.Run(roleName+" "+p.Id, func(t *testing.T) {
|
||||||
|
th.store.EXPECT().
|
||||||
|
GetBoard(member.BoardID).
|
||||||
|
Return(&model.Board{ID: member.BoardID, TeamID: teamID}, nil).
|
||||||
|
Times(1)
|
||||||
|
|
||||||
|
th.api.EXPECT().
|
||||||
|
HasPermissionToTeam(member.UserID, teamID, model.PermissionViewTeam).
|
||||||
|
Return(true).
|
||||||
|
Times(1)
|
||||||
|
|
||||||
|
th.store.EXPECT().
|
||||||
|
GetMemberForBoard(member.BoardID, member.UserID).
|
||||||
|
Return(member, nil).
|
||||||
|
Times(1)
|
||||||
|
|
||||||
|
hasPermission := th.permissions.HasPermissionToBoard(member.UserID, member.BoardID, p)
|
||||||
|
assert.True(t, hasPermission)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range hasNotPermissionTo {
|
||||||
|
th.t.Run(roleName+" "+p.Id, func(t *testing.T) {
|
||||||
|
th.store.EXPECT().
|
||||||
|
GetBoard(member.BoardID).
|
||||||
|
Return(&model.Board{ID: member.BoardID, TeamID: teamID}, nil).
|
||||||
|
Times(1)
|
||||||
|
|
||||||
|
th.api.EXPECT().
|
||||||
|
HasPermissionToTeam(member.UserID, teamID, model.PermissionViewTeam).
|
||||||
|
Return(true).
|
||||||
|
Times(1)
|
||||||
|
|
||||||
|
th.store.EXPECT().
|
||||||
|
GetMemberForBoard(member.BoardID, member.UserID).
|
||||||
|
Return(member, nil).
|
||||||
|
Times(1)
|
||||||
|
|
||||||
|
hasPermission := th.permissions.HasPermissionToBoard(member.UserID, member.BoardID, p)
|
||||||
|
assert.False(t, hasPermission)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue