diff --git a/server/api/api.go b/server/api/api.go index b833f2429..7b33c509a 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -76,6 +76,7 @@ func (a *API) RegisterRoutes(r *mux.Router) { apiv1.HandleFunc("/teams/{teamID}/boards", a.sessionRequired(a.handleGetBoards)).Methods("GET") apiv1.HandleFunc("/teams/{teamID}/boards/search", a.sessionRequired(a.handleSearchBoards)).Methods("GET") apiv1.HandleFunc("/teams/{teamID}/templates", a.sessionRequired(a.handleGetTemplates)).Methods("GET") + apiv1.HandleFunc("/templates", a.sessionRequired(a.handleGetDefaultTemplates)).Methods("GET") apiv1.HandleFunc("/boards", a.sessionRequired(a.handleCreateBoard)).Methods("POST") apiv1.HandleFunc("/boards/{boardID}", a.attachSession(a.handleGetBoard, false)).Methods("GET") apiv1.HandleFunc("/boards/{boardID}", a.sessionRequired(a.handlePatchBoard)).Methods("PATCH") @@ -2184,7 +2185,7 @@ func (a *API) handleGetTemplates(w http.ResponseWriter, r *http.Request) { auditRec.AddMeta("teamID", teamID) // retrieve boards list - boards, err := a.app.GetTemplateBoards(teamID) + boards, err := a.app.GetTemplateBoards(teamID, userID) if err != nil { a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) return @@ -2208,6 +2209,62 @@ func (a *API) handleGetTemplates(w http.ResponseWriter, r *http.Request) { auditRec.Success() } +func (a *API) handleGetDefaultTemplates(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /api/v1/templates getDefaultTemplates + // + // Returns default templates + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // items: + // "$ref": "#/definitions/Board" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + auditRec := a.makeAuditRecord(r, "getDefaultTemplates", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + + // retrieve boards list + boards, err := a.app.GetDefaultTemplates() + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("GetDefaultTemplates", + mlog.Int("boardsCount", len(boards)), + ) + + data, err := json.Marshal(boards) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.AddMeta("templatesCount", len(boards)) + auditRec.Success() +} + // subscriptions func (a *API) handleCreateSubscription(w http.ResponseWriter, r *http.Request) { diff --git a/server/app/boards.go b/server/app/boards.go index 6fc0b76bb..202c4298a 100644 --- a/server/app/boards.go +++ b/server/app/boards.go @@ -49,8 +49,12 @@ func (a *App) GetBoardsForUserAndTeam(userID, teamID string) ([]*model.Board, er return a.store.GetBoardsForUserAndTeam(userID, teamID) } -func (a *App) GetTemplateBoards(teamID string) ([]*model.Board, error) { - return a.store.GetTemplateBoards(teamID) +func (a *App) GetTemplateBoards(teamID, userID string) ([]*model.Board, error) { + return a.store.GetTemplateBoards(teamID, userID) +} + +func (a *App) GetDefaultTemplates() ([]*model.Board, error) { + return a.store.GetDefaultTemplates() } func (a *App) CreateBoard(board *model.Board, userID string, addMember bool) (*model.Board, error) { diff --git a/server/app/onboarding.go b/server/app/onboarding.go index 369e32227..6f517432f 100644 --- a/server/app/onboarding.go +++ b/server/app/onboarding.go @@ -46,7 +46,7 @@ func (a *App) PrepareOnboardingTour(userID string, teamID string) (string, strin } func (a *App) getOnboardingBoardID() (string, error) { - boards, err := a.store.GetTemplateBoards(globalTeamID) + boards, err := a.store.GetDefaultTemplates() if err != nil { return "", err } diff --git a/server/app/templates.go b/server/app/templates.go index da28e7567..7d23b7ce5 100644 --- a/server/app/templates.go +++ b/server/app/templates.go @@ -26,7 +26,7 @@ func (a *App) InitTemplates() error { // initializeTemplates imports default templates if the boards table is empty. func (a *App) initializeTemplates() error { - boards, err := a.store.GetTemplateBoards(globalTeamID) + boards, err := a.store.GetDefaultTemplates() if err != nil { return fmt.Errorf("cannot initialize templates: %w", err) } diff --git a/server/services/store/mockstore/mockstore.go b/server/services/store/mockstore/mockstore.go index 68dc50af3..ded8dbed7 100644 --- a/server/services/store/mockstore/mockstore.go +++ b/server/services/store/mockstore/mockstore.go @@ -535,6 +535,21 @@ func (mr *MockStoreMockRecorder) GetCategory(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCategory", reflect.TypeOf((*MockStore)(nil).GetCategory), arg0) } +// GetDefaultTemplates mocks base method. +func (m *MockStore) GetDefaultTemplates() ([]*model.Board, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDefaultTemplates") + ret0, _ := ret[0].([]*model.Board) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDefaultTemplates indicates an expected call of GetDefaultTemplates. +func (mr *MockStoreMockRecorder) GetDefaultTemplates() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultTemplates", reflect.TypeOf((*MockStore)(nil).GetDefaultTemplates)) +} + // GetMemberForBoard mocks base method. func (m *MockStore) GetMemberForBoard(arg0, arg1 string) (*model.BoardMember, error) { m.ctrl.T.Helper() @@ -821,18 +836,18 @@ func (mr *MockStoreMockRecorder) GetTeamsForUser(arg0 interface{}) *gomock.Call } // GetTemplateBoards mocks base method. -func (m *MockStore) GetTemplateBoards(arg0 string) ([]*model.Board, error) { +func (m *MockStore) GetTemplateBoards(arg0, arg1 string) ([]*model.Board, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetTemplateBoards", arg0) + ret := m.ctrl.Call(m, "GetTemplateBoards", arg0, arg1) ret0, _ := ret[0].([]*model.Board) ret1, _ := ret[1].(error) return ret0, ret1 } // GetTemplateBoards indicates an expected call of GetTemplateBoards. -func (mr *MockStoreMockRecorder) GetTemplateBoards(arg0 interface{}) *gomock.Call { +func (mr *MockStoreMockRecorder) GetTemplateBoards(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTemplateBoards", reflect.TypeOf((*MockStore)(nil).GetTemplateBoards), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTemplateBoards", reflect.TypeOf((*MockStore)(nil).GetTemplateBoards), arg0, arg1) } // GetUserByEmail mocks base method. diff --git a/server/services/store/sqlstore/migrations/000017_add_teams_and_boards.up.sql b/server/services/store/sqlstore/migrations/000017_add_teams_and_boards.up.sql index 2ed63ba57..74286beb4 100644 --- a/server/services/store/sqlstore/migrations/000017_add_teams_and_boards.up.sql +++ b/server/services/store/sqlstore/migrations/000017_add_teams_and_boards.up.sql @@ -367,12 +367,10 @@ INSERT INTO {{.prefix}}board_members ( SELECT B.Id, CM.UserId, CM.Roles, (CM.UserId=B.created_by) OR CM.SchemeAdmin, CM.SchemeUser, FALSE, CM.SchemeGuest FROM {{.prefix}}boards AS B INNER JOIN ChannelMembers as CM ON CM.ChannelId=B.channel_id - WHERE NOT B.is_template ); {{else}} {{- /* if we're in personal server or desktop, create memberships for everyone */ -}} INSERT INTO {{.prefix}}board_members SELECT B.id, U.id, '', B.created_by=U.id, TRUE, FALSE, FALSE FROM {{.prefix}}boards AS B, {{.prefix}}users AS U - WHERE NOT B.is_template; {{end}} diff --git a/server/services/store/sqlstore/public_methods.go b/server/services/store/sqlstore/public_methods.go index 8861bbbf3..c3231dc27 100644 --- a/server/services/store/sqlstore/public_methods.go +++ b/server/services/store/sqlstore/public_methods.go @@ -319,6 +319,11 @@ func (s *SQLStore) GetCategory(id string) (*model.Category, error) { } +func (s *SQLStore) GetDefaultTemplates() ([]*model.Board, error) { + return s.getDefaultTemplates(s.db) + +} + func (s *SQLStore) GetMemberForBoard(boardID string, userID string) (*model.BoardMember, error) { return s.getMemberForBoard(s.db, boardID, userID) @@ -414,8 +419,8 @@ func (s *SQLStore) GetTeamsForUser(userID string) ([]*model.Team, error) { } -func (s *SQLStore) GetTemplateBoards(teamID string) ([]*model.Board, error) { - return s.getTemplateBoards(s.db, teamID) +func (s *SQLStore) GetTemplateBoards(teamID string, userID string) ([]*model.Board, error) { + return s.getTemplateBoards(s.db, teamID, userID) } diff --git a/server/services/store/sqlstore/templates.go b/server/services/store/sqlstore/templates.go index a58ba15b4..f8354ce68 100644 --- a/server/services/store/sqlstore/templates.go +++ b/server/services/store/sqlstore/templates.go @@ -55,11 +55,11 @@ func (s *SQLStore) removeDefaultTemplates(db sq.BaseRunner, boards []*model.Boar } // getDefaultTemplateBoards fetches all template blocks . -func (s *SQLStore) getTemplateBoards(db sq.BaseRunner, teamID string) ([]*model.Board, error) { +func (s *SQLStore) getDefaultTemplates(db sq.BaseRunner) ([]*model.Board, error) { query := s.getQueryBuilder(db). Select(boardFields("")...). From(s.tablePrefix + "boards"). - Where(sq.Eq{"coalesce(team_id, '0')": teamID}). + Where(sq.Eq{"team_id": "0"}). Where(sq.Eq{"is_template": true}) rows, err := query.Query() @@ -71,3 +71,27 @@ func (s *SQLStore) getTemplateBoards(db sq.BaseRunner, teamID string) ([]*model. return s.boardsFromRows(rows) } + +// getDefaultTemplateBoards fetches all template blocks . +func (s *SQLStore) getTemplateBoards(db sq.BaseRunner, teamID, userID string) ([]*model.Board, error) { + query := s.getQueryBuilder(db). + Select(boardFields("")...). + From(s.tablePrefix+"boards as b"). + Join(s.tablePrefix+"board_members as bm on b.id = bm.board_id and b.team_id = ? and bm.user_id = ?", teamID, userID). + Where(sq.Eq{"is_template": true}) + + rows, err := query.Query() + if err != nil { + s.logger.Error(`getTemplateBoards ERROR`, mlog.Err(err)) + return nil, err + } + defer s.CloseRows(rows) + + userTemplates, err := s.boardsFromRows(rows) + if err != nil { + return nil, err + } + + return userTemplates, nil + +} diff --git a/server/services/store/store.go b/server/services/store/store.go index 28b63eb03..7bb82c25b 100644 --- a/server/services/store/store.go +++ b/server/services/store/store.go @@ -124,8 +124,9 @@ type Store interface { GetNotificationHint(blockID string) (*model.NotificationHint, error) GetNextNotificationHint(remove bool) (*model.NotificationHint, error) + GetDefaultTemplates() ([]*model.Board, error) RemoveDefaultTemplates(boards []*model.Board) error - GetTemplateBoards(teamID string) ([]*model.Board, error) + GetTemplateBoards(teamID, userID string) ([]*model.Board, error) DBType() string diff --git a/webapp/src/octoClient.ts b/webapp/src/octoClient.ts index 6b67fc7e9..aa3e5196b 100644 --- a/webapp/src/octoClient.ts +++ b/webapp/src/octoClient.ts @@ -596,6 +596,15 @@ class OctoClient { return this.getBoardsWithPath(path) } + async getDefaultTemplates(): Promise { + const path = '/api/v1/templates' + const response = await fetch(this.getBaseURL() + path, {headers: this.headers()}) + if (response.status !== 200) { + return [] + } + return (await this.getJson(response, [])) as Board[] + } + // Boards // ToDo: . // - goal? make the interface show boards & blocks for boards diff --git a/webapp/src/store/globalTemplates.ts b/webapp/src/store/globalTemplates.ts index b7b0ae2ec..3dc5ead82 100644 --- a/webapp/src/store/globalTemplates.ts +++ b/webapp/src/store/globalTemplates.ts @@ -13,7 +13,7 @@ import {RootState} from './index' export const fetchGlobalTemplates = createAsyncThunk( 'globalTemplates/fetch', async () => { - const templates = await client.getTeamTemplates('0') + const templates = await client.getDefaultTemplates() return templates.sort((a, b) => a.title.localeCompare(b.title)) }, )