You've already forked focalboard
mirror of
https://github.com/mattermost/focalboard.git
synced 2025-07-03 23:30:29 +02:00
Merge branch 'main' into private-templates
This commit is contained in:
@ -89,6 +89,7 @@ func (a *API) RegisterRoutes(r *mux.Router) {
|
||||
apiv1.HandleFunc("/boards/{boardID}/blocks/{blockID}/undelete", a.sessionRequired(a.handleUndeleteBlock)).Methods("POST")
|
||||
apiv1.HandleFunc("/boards/{boardID}/blocks/{blockID}/subtree", a.attachSession(a.handleGetSubTree, false)).Methods("GET")
|
||||
apiv1.HandleFunc("/boards/{boardID}/blocks/{blockID}/duplicate", a.attachSession(a.handleDuplicateBlock, false)).Methods("POST")
|
||||
apiv1.HandleFunc("/boards/{boardID}/metadata", a.sessionRequired(a.handleGetBoardMetadata)).Methods("GET")
|
||||
|
||||
// Import&Export APIs
|
||||
apiv1.HandleFunc("/boards/{boardID}/blocks/export", a.sessionRequired(a.handleExport)).Methods("GET")
|
||||
@ -99,6 +100,7 @@ func (a *API) RegisterRoutes(r *mux.Router) {
|
||||
apiv1.HandleFunc("/boards/{boardID}/members", a.sessionRequired(a.handleAddMember)).Methods("POST")
|
||||
apiv1.HandleFunc("/boards/{boardID}/members/{userID}", a.sessionRequired(a.handleUpdateMember)).Methods("PUT")
|
||||
apiv1.HandleFunc("/boards/{boardID}/members/{userID}", a.sessionRequired(a.handleDeleteMember)).Methods("DELETE")
|
||||
apiv1.HandleFunc("/boards/{boardID}/join", a.sessionRequired(a.handleJoinBoard)).Methods("POST")
|
||||
|
||||
// Sharing APIs
|
||||
apiv1.HandleFunc("/boards/{boardID}/sharing", a.sessionRequired(a.handlePostSharing)).Methods("POST")
|
||||
@ -2968,6 +2970,81 @@ func (a *API) handleDuplicateBlock(w http.ResponseWriter, r *http.Request) {
|
||||
auditRec.Success()
|
||||
}
|
||||
|
||||
func (a *API) handleGetBoardMetadata(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:operation GET /api/v1/boards/{boardID}/metadata getBoardMetadata
|
||||
//
|
||||
// Returns a board's metadata
|
||||
//
|
||||
// ---
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: boardID
|
||||
// in: path
|
||||
// description: Board ID
|
||||
// required: true
|
||||
// type: string
|
||||
// security:
|
||||
// - BearerAuth: []
|
||||
// responses:
|
||||
// '200':
|
||||
// description: success
|
||||
// schema:
|
||||
// "$ref": "#/definitions/BoardMetadata"
|
||||
// '404':
|
||||
// description: board not found
|
||||
// '501':
|
||||
// description: required license not found
|
||||
// default:
|
||||
// description: internal error
|
||||
// schema:
|
||||
// "$ref": "#/definitions/ErrorResponse"
|
||||
|
||||
boardID := mux.Vars(r)["boardID"]
|
||||
userID := getUserID(r)
|
||||
|
||||
board, boardMetadata, err := a.app.GetBoardMetadata(boardID)
|
||||
if errors.Is(err, app.ErrInsufficientLicense) {
|
||||
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "", err)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
if board == nil || boardMetadata == nil {
|
||||
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if board.Type == model.BoardTypePrivate {
|
||||
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {
|
||||
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {
|
||||
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
auditRec := a.makeAuditRecord(r, "getBoardMetadata", audit.Fail)
|
||||
defer a.audit.LogRecord(audit.LevelRead, auditRec)
|
||||
auditRec.AddMeta("boardID", boardID)
|
||||
|
||||
data, err := json.Marshal(boardMetadata)
|
||||
if err != nil {
|
||||
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
// response
|
||||
jsonBytesResponse(w, http.StatusOK, data)
|
||||
|
||||
auditRec.Success()
|
||||
}
|
||||
|
||||
func (a *API) handleSearchBoards(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:operation GET /api/v1/teams/{teamID}/boards/search searchBoards
|
||||
//
|
||||
@ -3214,6 +3291,98 @@ func (a *API) handleAddMember(w http.ResponseWriter, r *http.Request) {
|
||||
auditRec.Success()
|
||||
}
|
||||
|
||||
func (a *API) handleJoinBoard(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:operation POST /boards/{boardID}/join joinBoard
|
||||
//
|
||||
// Become a member of a board
|
||||
//
|
||||
// ---
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: boardID
|
||||
// in: path
|
||||
// description: Board ID
|
||||
// required: true
|
||||
// type: string
|
||||
// security:
|
||||
// - BearerAuth: []
|
||||
// responses:
|
||||
// '200':
|
||||
// description: success
|
||||
// schema:
|
||||
// $ref: '#/definitions/BoardMember'
|
||||
// '404':
|
||||
// description: board not found
|
||||
// '503':
|
||||
// description: access denied
|
||||
// default:
|
||||
// description: internal error
|
||||
// schema:
|
||||
// "$ref": "#/definitions/ErrorResponse"
|
||||
|
||||
userID := getUserID(r)
|
||||
if userID == "" {
|
||||
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil)
|
||||
return
|
||||
}
|
||||
|
||||
boardID := mux.Vars(r)["boardID"]
|
||||
board, err := a.app.GetBoard(boardID)
|
||||
if err != nil {
|
||||
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
if board == nil {
|
||||
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
|
||||
return
|
||||
}
|
||||
if board.Type != model.BoardTypeOpen {
|
||||
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {
|
||||
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// currently all memberships are created as editors by default
|
||||
// TODO: Support different public roles
|
||||
newBoardMember := &model.BoardMember{
|
||||
UserID: userID,
|
||||
BoardID: boardID,
|
||||
SchemeEditor: true,
|
||||
}
|
||||
|
||||
auditRec := a.makeAuditRecord(r, "joinBoard", audit.Fail)
|
||||
defer a.audit.LogRecord(audit.LevelModify, auditRec)
|
||||
auditRec.AddMeta("boardID", boardID)
|
||||
auditRec.AddMeta("addedUserID", userID)
|
||||
|
||||
member, err := a.app.AddMemberToBoard(newBoardMember)
|
||||
if err != nil {
|
||||
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
a.logger.Debug("AddMember",
|
||||
mlog.String("boardID", board.ID),
|
||||
mlog.String("addedUserID", userID),
|
||||
)
|
||||
|
||||
data, err := json.Marshal(member)
|
||||
if err != nil {
|
||||
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
// response
|
||||
jsonBytesResponse(w, http.StatusOK, data)
|
||||
|
||||
auditRec.Success()
|
||||
}
|
||||
|
||||
func (a *API) handleUpdateMember(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:operation PUT /boards/{boardID}/members/{userID} updateMember
|
||||
//
|
||||
|
Reference in New Issue
Block a user