From b3fbd37e992cf3f9f42f49818087c67d464000eb Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 17 Jun 2021 16:02:34 +0200 Subject: [PATCH] [API] expose repo.GetReviewers() & repo.GetAssignees() (#16168) * API: expose repo.GetReviewers() & repo.GetAssignees() * Add tests * fix unrelated swagger query type --- integrations/api_repo_test.go | 28 +++++++++++ modules/convert/user.go | 9 ++++ routers/api/v1/api.go | 2 + routers/api/v1/notify/repo.go | 2 +- routers/api/v1/notify/user.go | 2 +- routers/api/v1/repo/collaborators.go | 60 ++++++++++++++++++++++++ routers/api/v1/user/user.go | 8 +--- templates/swagger/v1_json.tmpl | 70 +++++++++++++++++++++++++++- 8 files changed, 170 insertions(+), 11 deletions(-) diff --git a/integrations/api_repo_test.go b/integrations/api_repo_test.go index 1ca4575508..98c9fb6ec7 100644 --- a/integrations/api_repo_test.go +++ b/integrations/api_repo_test.go @@ -494,3 +494,31 @@ func TestAPIRepoTransfer(t *testing.T) { repo = models.AssertExistsAndLoadBean(t, &models.Repository{ID: repo.ID}).(*models.Repository) _ = models.DeleteRepository(user, repo.OwnerID, repo.ID) } + +func TestAPIRepoGetReviewers(t *testing.T) { + defer prepareTestEnv(t)() + user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/reviewers?token=%s", user.Name, repo.Name, token) + resp := session.MakeRequest(t, req, http.StatusOK) + var reviewers []*api.User + DecodeJSON(t, resp, &reviewers) + assert.Len(t, reviewers, 4) +} + +func TestAPIRepoGetAssignees(t *testing.T) { + defer prepareTestEnv(t)() + user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees?token=%s", user.Name, repo.Name, token) + resp := session.MakeRequest(t, req, http.StatusOK) + var assignees []*api.User + DecodeJSON(t, resp, &assignees) + assert.Len(t, assignees, 1) +} diff --git a/modules/convert/user.go b/modules/convert/user.go index c588f5f2f0..07a4efd41a 100644 --- a/modules/convert/user.go +++ b/modules/convert/user.go @@ -25,6 +25,15 @@ func ToUser(user, doer *models.User) *api.User { return toUser(user, signed, authed) } +// ToUsers convert list of models.User to list of api.User +func ToUsers(doer *models.User, users []*models.User) []*api.User { + result := make([]*api.User, len(users)) + for i := range users { + result[i] = ToUser(users[i], doer) + } + return result +} + // ToUserWithAccessMode convert models.User to api.User // AccessMode is not none show add some more information func ToUserWithAccessMode(user *models.User, accessMode models.AccessMode) *api.User { diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index acee6329af..0b47953e58 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -746,6 +746,8 @@ func Routes() *web.Route { Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator). Delete(reqAdmin(), repo.DeleteCollaborator) }, reqToken()) + m.Get("/assignees", reqToken(), reqAnyRepoReader(), repo.GetAssignees) + m.Get("/reviewers", reqToken(), reqAnyRepoReader(), repo.GetReviewers) m.Group("/teams", func() { m.Get("", reqAnyRepoReader(), repo.ListTeams) m.Combo("/{team}").Get(reqAnyRepoReader(), repo.IsTeam). diff --git a/routers/api/v1/notify/repo.go b/routers/api/v1/notify/repo.go index 4deb16a227..af55d1d49c 100644 --- a/routers/api/v1/notify/repo.go +++ b/routers/api/v1/notify/repo.go @@ -65,7 +65,7 @@ func ListRepoNotifications(ctx *context.APIContext) { // - name: all // in: query // description: If true, show notifications marked as read. Default value is false - // type: string + // type: boolean // - name: status-types // in: query // description: "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread & pinned" diff --git a/routers/api/v1/notify/user.go b/routers/api/v1/notify/user.go index 1ff62622b0..475a541bdc 100644 --- a/routers/api/v1/notify/user.go +++ b/routers/api/v1/notify/user.go @@ -27,7 +27,7 @@ func ListNotifications(ctx *context.APIContext) { // - name: all // in: query // description: If true, show notifications marked as read. Default value is false - // type: string + // type: boolean // - name: status-types // in: query // description: "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread & pinned." diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go index d0936019fa..078af1f6ff 100644 --- a/routers/api/v1/repo/collaborators.go +++ b/routers/api/v1/repo/collaborators.go @@ -221,3 +221,63 @@ func DeleteCollaborator(ctx *context.APIContext) { } ctx.Status(http.StatusNoContent) } + +// GetReviewers return all users that can be requested to review in this repo +func GetReviewers(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/reviewers repository repoGetReviewers + // --- + // summary: Return all users that can be requested to review in this repo + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/UserList" + + reviewers, err := ctx.Repo.Repository.GetReviewers(ctx.User.ID, 0) + if err != nil { + ctx.Error(http.StatusInternalServerError, "ListCollaborators", err) + return + } + ctx.JSON(http.StatusOK, convert.ToUsers(ctx.User, reviewers)) +} + +// GetAssignees return all users that have write access and can be assigned to issues +func GetAssignees(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/assignees repository repoGetAssignees + // --- + // summary: Return all users that have write access and can be assigned to issues + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/UserList" + + assignees, err := ctx.Repo.Repository.GetAssignees() + if err != nil { + ctx.Error(http.StatusInternalServerError, "ListCollaborators", err) + return + } + ctx.JSON(http.StatusOK, convert.ToUsers(ctx.User, assignees)) +} diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index 6e811bf0f8..4adae532fd 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -13,7 +13,6 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" - api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/routers/api/v1/utils" ) @@ -73,18 +72,13 @@ func Search(ctx *context.APIContext) { return } - results := make([]*api.User, len(users)) - for i := range users { - results[i] = convert.ToUser(users[i], ctx.User) - } - ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", maxResults)) ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") ctx.JSON(http.StatusOK, map[string]interface{}{ "ok": true, - "data": results, + "data": convert.ToUsers(ctx.User, users), }) } diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 18b870517e..9b0d07ebfc 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -630,7 +630,7 @@ "operationId": "notifyGetList", "parameters": [ { - "type": "string", + "type": "boolean", "description": "If true, show notifications marked as read. Default value is false", "name": "all", "in": "query" @@ -2277,6 +2277,39 @@ } } }, + "/repos/{owner}/{repo}/assignees": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Return all users that have write access and can be assigned to issues", + "operationId": "repoGetAssignees", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/UserList" + } + } + } + }, "/repos/{owner}/{repo}/branch_protections": { "get": { "produces": [ @@ -6844,7 +6877,7 @@ "required": true }, { - "type": "string", + "type": "boolean", "description": "If true, show notifications marked as read. Default value is false", "name": "all", "in": "query" @@ -8629,6 +8662,39 @@ } } }, + "/repos/{owner}/{repo}/reviewers": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Return all users that can be requested to review in this repo", + "operationId": "repoGetReviewers", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/UserList" + } + } + } + }, "/repos/{owner}/{repo}/signing-key.gpg": { "get": { "produces": [