main

mattermost/focalboard

Last updated at: 29/12/2023 09:47

search.go

TLDR

This file, search.go, is part of the Demo Projects project and contains the implementation of search related API endpoints for boards and channels. It includes methods for handling searches of boards, channels, linkable boards, and all boards.

Methods

handleSearchMyChannels

This method handles a GET request to /teams/{teamID}/channels and returns the user's available channels. It requires authentication and the parameters teamID (Team ID) and search (string to filter channels list) are optional.

handleSearchBoards

This method handles a GET request to /teams/{teamID}/boards/search and returns the boards that match a search term in the specified team. It requires authentication and the parameters teamID (Team ID), q (the search term), and field (the field to search on) are required.

handleSearchLinkableBoards

This method handles a GET request to /teams/{teamID}/boards/search/linkable and returns the boards that match a search term in the specified team and the user has permission to manage members. It requires authentication and the parameters teamID (Team ID) and q (the search term) are required.

handleSearchAllBoards

This method handles a GET request to /boards/search and returns the boards that match a search term. It requires authentication and the parameter q (the search term) is required.

Classes

package api

import (
	"encoding/json"
	"net/http"

	"github.com/gorilla/mux"
	"github.com/mattermost/focalboard/server/model"
	"github.com/mattermost/focalboard/server/services/audit"

	"github.com/mattermost/mattermost-server/v6/shared/mlog"
)

func (a *API) registerSearchRoutes(r *mux.Router) {
	r.HandleFunc("/teams/{teamID}/channels", a.sessionRequired(a.handleSearchMyChannels)).Methods("GET")
	r.HandleFunc("/teams/{teamID}/boards/search", a.sessionRequired(a.handleSearchBoards)).Methods("GET")
	r.HandleFunc("/teams/{teamID}/boards/search/linkable", a.sessionRequired(a.handleSearchLinkableBoards)).Methods("GET")
	r.HandleFunc("/boards/search", a.sessionRequired(a.handleSearchAllBoards)).Methods("GET")
}

func (a *API) handleSearchMyChannels(w http.ResponseWriter, r *http.Request) {
	// swagger:operation GET /teams/{teamID}/channels searchMyChannels
	//
	// Returns the user available channels
	//
	// ---
	// produces:
	// - application/json
	// parameters:
	// - name: teamID
	//   in: path
	//   description: Team ID
	//   required: true
	//   type: string
	// - name: search
	//   in: query
	//   description: string to filter channels list
	//   required: false
	//   type: string
	// security:
	// - BearerAuth: []
	// responses:
	//   '200':
	//     description: success
	//     schema:
	//       type: array
	//       items:
	//         "$ref": "#/definitions/Channel"
	//   default:
	//     description: internal error
	//     schema:
	//       "$ref": "#/definitions/ErrorResponse"

	if !a.MattermostAuth {
		a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in standalone mode"))
		return
	}

	query := r.URL.Query()
	searchQuery := query.Get("search")

	teamID := mux.Vars(r)["teamID"]
	userID := getUserID(r)

	if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
		a.errorResponse(w, r, model.NewErrPermission("access denied to team"))
		return
	}

	auditRec := a.makeAuditRecord(r, "searchMyChannels", audit.Fail)
	defer a.audit.LogRecord(audit.LevelRead, auditRec)
	auditRec.AddMeta("teamID", teamID)

	channels, err := a.app.SearchUserChannels(teamID, userID, searchQuery)
	if err != nil {
		a.errorResponse(w, r, err)
		return
	}

	a.logger.Debug("GetUserChannels",
		mlog.String("teamID", teamID),
		mlog.Int("channelsCount", len(channels)),
	)

	data, err := json.Marshal(channels)
	if err != nil {
		a.errorResponse(w, r, err)
		return
	}

	// response
	jsonBytesResponse(w, http.StatusOK, data)

	auditRec.AddMeta("channelsCount", len(channels))
	auditRec.Success()
}

func (a *API) handleSearchBoards(w http.ResponseWriter, r *http.Request) {
	// swagger:operation GET /teams/{teamID}/boards/search searchBoards
	//
	// Returns the boards that match with a search term in the team
	//
	// ---
	// produces:
	// - application/json
	// parameters:
	// - name: teamID
	//   in: path
	//   description: Team ID
	//   required: true
	//   type: string
	// - name: q
	//   in: query
	//   description: The search term. Must have at least one character
	//   required: true
	//   type: string
	// - name: field
	//   in: query
	//   description: The field to search on for search term. Can be `title`, `property_name`. Defaults to `title`
	//   required: false
	//   type: string
	// security:
	// - BearerAuth: []
	// responses:
	//   '200':
	//     description: success
	//     schema:
	//       type: array
	//       items:
	//         "$ref": "#/definitions/Board"
	//   default:
	//     description: internal error
	//     schema:
	//       "$ref": "#/definitions/ErrorResponse"

	var err error
	teamID := mux.Vars(r)["teamID"]
	term := r.URL.Query().Get("q")
	searchFieldText := r.URL.Query().Get("field")
	searchField := model.BoardSearchFieldTitle
	if searchFieldText != "" {
		searchField, err = model.BoardSearchFieldFromString(searchFieldText)
		if err != nil {
			a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
			return
		}
	}
	userID := getUserID(r)

	if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
		a.errorResponse(w, r, model.NewErrPermission("access denied to team"))
		return
	}

	if len(term) == 0 {
		jsonStringResponse(w, http.StatusOK, "[]")
		return
	}

	auditRec := a.makeAuditRecord(r, "searchBoards", audit.Fail)
	defer a.audit.LogRecord(audit.LevelRead, auditRec)
	auditRec.AddMeta("teamID", teamID)

	isGuest, err := a.userIsGuest(userID)
	if err != nil {
		a.errorResponse(w, r, err)
		return
	}

	// retrieve boards list
	boards, err := a.app.SearchBoardsForUser(term, searchField, userID, !isGuest)
	if err != nil {
		a.errorResponse(w, r, err)
		return
	}

	a.logger.Debug("SearchBoards",
		mlog.String("teamID", teamID),
		mlog.Int("boardsCount", len(boards)),
	)

	data, err := json.Marshal(boards)
	if err != nil {
		a.errorResponse(w, r, err)
		return
	}

	// response
	jsonBytesResponse(w, http.StatusOK, data)

	auditRec.AddMeta("boardsCount", len(boards))
	auditRec.Success()
}

func (a *API) handleSearchLinkableBoards(w http.ResponseWriter, r *http.Request) {
	// swagger:operation GET /teams/{teamID}/boards/search/linkable searchLinkableBoards
	//
	// Returns the boards that match with a search term in the team and the
	// user has permission to manage members
	//
	// ---
	// produces:
	// - application/json
	// parameters:
	// - name: teamID
	//   in: path
	//   description: Team ID
	//   required: true
	//   type: string
	// - name: q
	//   in: query
	//   description: The search term. Must have at least one character
	//   required: true
	//   type: string
	// security:
	// - BearerAuth: []
	// responses:
	//   '200':
	//     description: success
	//     schema:
	//       type: array
	//       items:
	//         "$ref": "#/definitions/Board"
	//   default:
	//     description: internal error
	//     schema:
	//       "$ref": "#/definitions/ErrorResponse"

	if !a.MattermostAuth {
		a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in standalone mode"))
		return
	}

	teamID := mux.Vars(r)["teamID"]
	term := r.URL.Query().Get("q")
	userID := getUserID(r)

	if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
		a.errorResponse(w, r, model.NewErrPermission("access denied to team"))
		return
	}

	if len(term) == 0 {
		jsonStringResponse(w, http.StatusOK, "[]")
		return
	}

	auditRec := a.makeAuditRecord(r, "searchLinkableBoards", audit.Fail)
	defer a.audit.LogRecord(audit.LevelRead, auditRec)
	auditRec.AddMeta("teamID", teamID)

	// retrieve boards list
	boards, err := a.app.SearchBoardsForUserInTeam(teamID, term, userID)
	if err != nil {
		a.errorResponse(w, r, err)
		return
	}

	linkableBoards := []*model.Board{}
	for _, board := range boards {
		if a.permissions.HasPermissionToBoard(userID, board.ID, model.PermissionManageBoardRoles) {
			linkableBoards = append(linkableBoards, board)
		}
	}

	a.logger.Debug("SearchLinkableBoards",
		mlog.String("teamID", teamID),
		mlog.Int("boardsCount", len(linkableBoards)),
	)

	data, err := json.Marshal(linkableBoards)
	if err != nil {
		a.errorResponse(w, r, err)
		return
	}

	// response
	jsonBytesResponse(w, http.StatusOK, data)

	auditRec.AddMeta("boardsCount", len(linkableBoards))
	auditRec.Success()
}

func (a *API) handleSearchAllBoards(w http.ResponseWriter, r *http.Request) {
	// swagger:operation GET /boards/search searchAllBoards
	//
	// Returns the boards that match with a search term
	//
	// ---
	// produces:
	// - application/json
	// parameters:
	// - name: q
	//   in: query
	//   description: The search term. Must have at least one character
	//   required: true
	//   type: string
	// security:
	// - BearerAuth: []
	// responses:
	//   '200':
	//     description: success
	//     schema:
	//       type: array
	//       items:
	//         "$ref": "#/definitions/Board"
	//   default:
	//     description: internal error
	//     schema:
	//       "$ref": "#/definitions/ErrorResponse"

	term := r.URL.Query().Get("q")
	userID := getUserID(r)

	if len(term) == 0 {
		jsonStringResponse(w, http.StatusOK, "[]")
		return
	}

	auditRec := a.makeAuditRecord(r, "searchAllBoards", audit.Fail)
	defer a.audit.LogRecord(audit.LevelRead, auditRec)

	isGuest, err := a.userIsGuest(userID)
	if err != nil {
		a.errorResponse(w, r, err)
		return
	}

	// retrieve boards list
	boards, err := a.app.SearchBoardsForUser(term, model.BoardSearchFieldTitle, userID, !isGuest)
	if err != nil {
		a.errorResponse(w, r, err)
		return
	}

	a.logger.Debug("SearchAllBoards",
		mlog.Int("boardsCount", len(boards)),
	)

	data, err := json.Marshal(boards)
	if err != nil {
		a.errorResponse(w, r, err)
		return
	}

	// response
	jsonBytesResponse(w, http.StatusOK, data)

	auditRec.AddMeta("boardsCount", len(boards))
	auditRec.Success()
}