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:
parent
4d88f525ae
commit
33557093b0
@ -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)
|
||||
|
@ -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(),
|
||||
|
@ -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(() => {
|
||||
|
@ -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
|
||||
|
@ -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) => {
|
||||
|
@ -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) {
|
||||
|
@ -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] || []
|
||||
|
@ -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] || []
|
||||
|
@ -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}
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user