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

fixes for shared boards (#2581)

* fixes for shared boards

* fixes from code review

* update for personal server

* fixes for cypress tests

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
Scott Bishel 2022-03-25 10:46:58 -06:00 committed by GitHub
parent 4d88f525ae
commit 33557093b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 73 additions and 40 deletions

View File

@ -81,7 +81,7 @@ func (a *API) RegisterRoutes(r *mux.Router) {
apiv1.HandleFunc("/boards/{boardID}", a.sessionRequired(a.handlePatchBoard)).Methods("PATCH")
apiv1.HandleFunc("/boards/{boardID}", a.sessionRequired(a.handleDeleteBoard)).Methods("DELETE")
apiv1.HandleFunc("/boards/{boardID}/duplicate", a.sessionRequired(a.handleDuplicateBoard)).Methods("POST")
apiv1.HandleFunc("/boards/{boardID}/blocks", a.sessionRequired(a.handleGetBlocks)).Methods("GET")
apiv1.HandleFunc("/boards/{boardID}/blocks", a.attachSession(a.handleGetBlocks, false)).Methods("GET")
apiv1.HandleFunc("/boards/{boardID}/blocks", a.sessionRequired(a.handlePostBlocks)).Methods("POST")
apiv1.HandleFunc("/boards/{boardID}/blocks", a.sessionRequired(a.handlePatchBlocks)).Methods("PATCH")
apiv1.HandleFunc("/boards/{boardID}/blocks/{blockID}", a.sessionRequired(a.handleDeleteBlock)).Methods("DELETE")
@ -288,15 +288,17 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
return
}
if board.IsTemplate {
if !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board template"})
return
}
} else {
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"})
return
if !a.hasValidReadTokenForBoard(r, boardID) {
if board.IsTemplate {
if !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board template"})
return
}
} else {
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"})
return
}
}
}
@ -3495,7 +3497,6 @@ func (a *API) handleCreateBoardsAndBlocks(w http.ResponseWriter, r *http.Request
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create private boards"})
return
}
auditRec := a.makeAuditRecord(r, "createBoardsAndBlocks", audit.Fail)
defer a.audit.LogRecord(audit.LevelModify, auditRec)
auditRec.AddMeta("teamID", teamID)

View File

@ -261,7 +261,11 @@ class OctoClient {
}
async getAllBlocks(boardID: string): Promise<Block[]> {
const path = `/api/v1/boards/${boardID}/blocks?all=true`
let path = `/api/v1/boards/${boardID}/blocks?all=true`
const readToken = Utils.getReadToken()
if (readToken) {
path += `&read_token=${readToken}`
}
return this.getBlocksWithPath(path)
}
@ -610,7 +614,11 @@ class OctoClient {
}
async getBoard(boardID: string): Promise<Board | undefined> {
const path = `/api/v1/boards/${boardID}`
let path = `/api/v1/boards/${boardID}`
const readToken = Utils.getReadToken()
if (readToken) {
path += `?read_token=${readToken}`
}
const response = await fetch(this.getBaseURL() + path, {
method: 'GET',
headers: this.headers(),

View File

@ -80,13 +80,10 @@ const BoardPage = (props: Props): JSX.Element => {
return initialLoad
}, [props.readonly])
const loadOrJoinBoard = useCallback(async (userId: string, boardTeamId: string, boardId: string, viewId: string) => {
// set the active board if we're able to pick one
dispatch(setCurrentBoard(boardId))
const loadOrJoinBoard = useCallback(async (userId: string, boardTeamId: string, boardId: string) => {
// and fetch its data
const result: any = await dispatch(loadBoardData(boardId))
if (result.payload.blocks.length === 0) {
if (result.payload.blocks.length === 0 && userId) {
const member = await octoClient.createBoardMember({userId, boardId})
if (!member) {
UserSettings.setLastBoardID(boardTeamId, null)
@ -101,23 +98,28 @@ const BoardPage = (props: Props): JSX.Element => {
teamId: boardTeamId,
boardId,
}))
// and set it as most recently viewed board
UserSettings.setLastBoardID(boardTeamId, boardId)
if (viewId && viewId !== '0') {
dispatch(setCurrentView(viewId))
UserSettings.setLastViewId(boardId, viewId)
}
}, [])
useEffect(() => {
dispatch(loadAction(match.params.boardId))
if (match.params.boardId && me) {
loadOrJoinBoard(me.id, teamId, match.params.boardId, match.params.viewId)
if (match.params.boardId) {
// set the active board
dispatch(setCurrentBoard(match.params.boardId))
// and set it as most recently viewed board
UserSettings.setLastBoardID(teamId, match.params.boardId)
if (match.params.viewId && match.params.viewId !== '0') {
dispatch(setCurrentView(match.params.viewId))
UserSettings.setLastViewId(match.params.boardId, match.params.viewId)
}
if (!props.readonly && me) {
loadOrJoinBoard(me.id, teamId, match.params.boardId)
}
}
}, [teamId, match.params.boardId, match.params.viewId])
}, [teamId, match.params.boardId, match.params.viewId, me?.id])
if (props.readonly) {
useEffect(() => {

View File

@ -8,6 +8,7 @@ import {
useRouteMatch,
useHistory,
generatePath,
useLocation,
} from 'react-router-dom'
import {createBrowserHistory, History} from 'history'
@ -70,16 +71,21 @@ function HomeToCurrentTeam(props: {path: string, exact: boolean}) {
function WorkspaceToTeamRedirect() {
const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string, workspaceId?: string}>()
const queryParams = new URLSearchParams(useLocation().search)
const history = useHistory()
useEffect(() => {
octoClient.getBoard(match.params.boardId).then((board) => {
if (board) {
history.replace(generatePath('/team/:teamId/:boardId?/:viewId?/:cardId?', {
let newPath = generatePath(match.path.replace('/workspace/:workspaceId', '/team/:teamId'), {
teamId: board?.teamId,
boardId: board?.id,
viewId: match.params.viewId,
cardId: match.params.cardId,
}))
})
if (queryParams) {
newPath += '?' + queryParams
}
history.replace(newPath)
}
})
}, [])
@ -159,9 +165,10 @@ const FocalboardRouter = (props: Props): JSX.Element => {
<ChangePasswordPage/>
</FBRoute>}
<FBRoute path='/shared/:boardId?/:viewId?/:cardId?'>
<FBRoute path={['/team/:teamId/shared/:boardId?/:viewId?/:cardId?', '/shared/:boardId?/:viewId?/:cardId?']}>
<BoardPage readonly={true}/>
</FBRoute>
<FBRoute
loginRequired={true}
path='/board/:boardId?/:viewId?/:cardId?'
@ -171,7 +178,7 @@ const FocalboardRouter = (props: Props): JSX.Element => {
>
<BoardPage/>
</FBRoute>
<FBRoute path={['/workspace/:workspaceId/:boardId?/:viewId?/:cardId?', '/workspace/:workspaceId/shared/:boardId?/:viewId?/:cardId?']}>
<FBRoute path={['/workspace/:workspaceId/shared/:boardId?/:viewId?/:cardId?', '/workspace/:workspaceId/:boardId?/:viewId?/:cardId?']}>
<WorkspaceToTeamRedirect/>
</FBRoute>
<FBRoute

View File

@ -7,7 +7,7 @@ import {default as client} from '../octoClient'
import {Board, BoardMember} from '../blocks/board'
import {IUser} from '../user'
import {initialLoad, loadBoardData} from './initialLoad'
import {initialLoad, initialReadOnlyLoad, loadBoardData} from './initialLoad'
import {addBoardUsers} from './users'
@ -147,6 +147,17 @@ const boardsSlice = createSlice({
builder.addCase(loadBoardData.rejected, (state) => {
state.loadingBoard = false
})
builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {
state.boards = {}
state.templates = {}
if (action.payload.board) {
if (action.payload.board.isTemplate) {
state.templates[action.payload.board.id] = action.payload.board
} else {
state.boards[action.payload.board.id] = action.payload.board
}
}
})
builder.addCase(initialLoad.fulfilled, (state, action) => {
state.boards = {}
action.payload.boards.forEach((board) => {

View File

@ -59,7 +59,7 @@ const cardsSlice = createSlice({
builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {
state.cards = {}
state.templates = {}
for (const block of action.payload) {
for (const block of action.payload.blocks) {
if (block.type === 'card' && block.fields.isTemplate) {
state.templates[block.id] = block as Card
} else if (block.type === 'card' && !block.fields.isTemplate) {

View File

@ -53,7 +53,7 @@ const commentsSlice = createSlice({
builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {
state.comments = {}
state.commentsByCard = {}
for (const block of action.payload) {
for (const block of action.payload.blocks) {
if (block.type === 'comment') {
state.comments[block.id] = block as CommentBlock
state.commentsByCard[block.parentId] = state.commentsByCard[block.parentId] || []

View File

@ -54,7 +54,7 @@ const contentsSlice = createSlice({
builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {
state.contents = {}
state.contentsByCard = {}
for (const block of action.payload) {
for (const block of action.payload.blocks) {
if (block.type !== 'board' && block.type !== 'view' && block.type !== 'comment') {
state.contents[block.id] = block as ContentBlock
state.contentsByCard[block.parentId] = state.contentsByCard[block.parentId] || []

View File

@ -37,8 +37,12 @@ export const initialLoad = createAsyncThunk(
export const initialReadOnlyLoad = createAsyncThunk(
'initialReadOnlyLoad',
async (boardId: string) => {
const blocks = client.getSubtree(boardId, 3)
return blocks
const [board, blocks] = await Promise.all([
client.getBoard(boardId),
client.getAllBlocks(boardId),
])
return {board, blocks}
},
)

View File

@ -79,7 +79,7 @@ const viewsSlice = createSlice({
extraReducers: (builder) => {
builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {
state.views = {}
for (const block of action.payload) {
for (const block of action.payload.blocks) {
if (block.type === 'view') {
state.views[block.id] = block as BoardView
}