Browse Source

Swagger info corrections (#9441)

* use numbers and not http.Status___ enum

* fix test

* add many missing swagger responses

* code format

* Deletion Sould return 204 ...

* error handling improvements

* if special error type ... then add it to swagger too

* one smal nit

* invalidTopicsError is []string

* valid swagger specification 2.0
 - if you add responses swagger can tell you if you do it right :+1:

* use ctx.InternalServerError

* Revert "use numbers and not http.Status___ enum"

This reverts commit b1ff386e24.

* use http.Status* enum everywhere
tags/v1.11.0-rc1
6543 4 years ago
parent
commit
2848c5eb8f
52 changed files with 1262 additions and 648 deletions
  1. 7
    0
      modules/context/api.go
  2. 9
    5
      routers/api/v1/admin/org.go
  3. 5
    0
      routers/api/v1/admin/repo.go
  4. 28
    19
      routers/api/v1/admin/user.go
  5. 29
    28
      routers/api/v1/api.go
  6. 8
    6
      routers/api/v1/misc/markdown.go
  7. 3
    1
      routers/api/v1/misc/swagger.go
  8. 3
    1
      routers/api/v1/misc/version.go
  9. 10
    5
      routers/api/v1/org/hook.go
  10. 29
    15
      routers/api/v1/org/member.go
  11. 18
    11
      routers/api/v1/org/org.go
  12. 65
    38
      routers/api/v1/org/team.go
  13. 2
    0
      routers/api/v1/repo/blob.go
  14. 12
    8
      routers/api/v1/repo/branch.go
  15. 28
    17
      routers/api/v1/repo/collaborators.go
  16. 4
    3
      routers/api/v1/repo/commits.go
  17. 10
    0
      routers/api/v1/repo/file.go
  18. 18
    8
      routers/api/v1/repo/fork.go
  19. 16
    10
      routers/api/v1/repo/git_hook.go
  20. 5
    3
      routers/api/v1/repo/git_ref.go
  21. 15
    8
      routers/api/v1/repo/hook.go
  22. 53
    35
      routers/api/v1/repo/issue.go
  23. 41
    24
      routers/api/v1/repo/issue_comment.go
  24. 42
    25
      routers/api/v1/repo/issue_label.go
  25. 48
    27
      routers/api/v1/repo/issue_reaction.go
  26. 22
    17
      routers/api/v1/repo/issue_stopwatch.go
  27. 17
    12
      routers/api/v1/repo/issue_subscription.go
  28. 31
    18
      routers/api/v1/repo/issue_tracked_time.go
  29. 26
    17
      routers/api/v1/repo/key.go
  30. 16
    10
      routers/api/v1/repo/label.go
  31. 16
    10
      routers/api/v1/repo/milestone.go
  32. 65
    47
      routers/api/v1/repo/pull.go
  33. 27
    18
      routers/api/v1/repo/release.go
  34. 21
    16
      routers/api/v1/repo/release_attachment.go
  35. 58
    43
      routers/api/v1/repo/repo.go
  36. 5
    2
      routers/api/v1/repo/star.go
  37. 24
    12
      routers/api/v1/repo/status.go
  38. 5
    2
      routers/api/v1/repo/subscriber.go
  39. 5
    2
      routers/api/v1/repo/tag.go
  40. 25
    23
      routers/api/v1/repo/topic.go
  41. 7
    3
      routers/api/v1/repo/tree.go
  42. 11
    6
      routers/api/v1/user/app.go
  43. 16
    9
      routers/api/v1/user/email.go
  44. 18
    8
      routers/api/v1/user/follower.go
  45. 18
    11
      routers/api/v1/user/gpg_key.go
  46. 16
    9
      routers/api/v1/user/key.go
  47. 11
    6
      routers/api/v1/user/repo.go
  48. 16
    9
      routers/api/v1/user/star.go
  49. 9
    6
      routers/api/v1/user/user.go
  50. 16
    9
      routers/api/v1/user/watch.go
  51. 16
    16
      routers/api/v1/utils/hook.go
  52. 237
    10
      templates/swagger/v1_json.tmpl

+ 7
- 0
modules/context/api.go View File

URL string `json:"url"` URL string `json:"url"`
} }


// APIInvalidTopicsError is error format response to invalid topics
// swagger:response invalidTopicsError
type APIInvalidTopicsError struct {
Topics []string `json:"invalidTopics"`
Message string `json:"message"`
}

//APIEmpty is an empty response //APIEmpty is an empty response
// swagger:response empty // swagger:response empty
type APIEmpty struct{} type APIEmpty struct{}

+ 9
- 5
routers/api/v1/admin/org.go View File

package admin package admin


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"

u := user.GetUserByParams(ctx) u := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
if models.IsErrUserAlreadyExist(err) || if models.IsErrUserAlreadyExist(err) ||
models.IsErrNameReserved(err) || models.IsErrNameReserved(err) ||
models.IsErrNamePatternNotAllowed(err) { models.IsErrNamePatternNotAllowed(err) {
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(500, "CreateOrganization", err)
ctx.Error(http.StatusInternalServerError, "CreateOrganization", err)
} }
return return
} }


ctx.JSON(201, convert.ToOrganization(org))
ctx.JSON(http.StatusCreated, convert.ToOrganization(org))
} }


//GetAllOrgs API for getting information of all the organizations //GetAllOrgs API for getting information of all the organizations
// "$ref": "#/responses/OrganizationList" // "$ref": "#/responses/OrganizationList"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"

users, _, err := models.SearchUsers(&models.SearchUserOptions{ users, _, err := models.SearchUsers(&models.SearchUserOptions{
Type: models.UserTypeOrganization, Type: models.UserTypeOrganization,
OrderBy: models.SearchOrderByAlphabetically, OrderBy: models.SearchOrderByAlphabetically,
Private: true, Private: true,
}) })
if err != nil { if err != nil {
ctx.Error(500, "SearchOrganizations", err)
ctx.Error(http.StatusInternalServerError, "SearchOrganizations", err)
return return
} }
orgs := make([]*api.Organization, len(users)) orgs := make([]*api.Organization, len(users))
for i := range users { for i := range users {
orgs[i] = convert.ToOrganization(users[i]) orgs[i] = convert.ToOrganization(users[i])
} }
ctx.JSON(200, &orgs)
ctx.JSON(http.StatusOK, &orgs)
} }

+ 5
- 0
routers/api/v1/admin/repo.go View File

// "$ref": "#/responses/Repository" // "$ref": "#/responses/Repository"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "409":
// "$ref": "#/responses/error"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"

owner := user.GetUserByParams(ctx) owner := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return

+ 28
- 19
routers/api/v1/admin/user.go View File



import ( import (
"errors" "errors"
"net/http"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
source, err := models.GetLoginSourceByID(sourceID) source, err := models.GetLoginSourceByID(sourceID)
if err != nil { if err != nil {
if models.IsErrLoginSourceNotExist(err) { if models.IsErrLoginSourceNotExist(err) {
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(500, "GetLoginSourceByID", err)
ctx.Error(http.StatusInternalServerError, "GetLoginSourceByID", err)
} }
return return
} }
// "$ref": "#/responses/User" // "$ref": "#/responses/User"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "400":
// "$ref": "#/responses/error"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"

u := &models.User{ u := &models.User{
Name: form.Username, Name: form.Username,
FullName: form.FullName, FullName: form.FullName,
} }
if !password.IsComplexEnough(form.Password) { if !password.IsComplexEnough(form.Password) {
err := errors.New("PasswordComplexity") err := errors.New("PasswordComplexity")
ctx.Error(400, "PasswordComplexity", err)
ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
return return
} }
if err := models.CreateUser(u); err != nil { if err := models.CreateUser(u); err != nil {
models.IsErrEmailAlreadyUsed(err) || models.IsErrEmailAlreadyUsed(err) ||
models.IsErrNameReserved(err) || models.IsErrNameReserved(err) ||
models.IsErrNamePatternNotAllowed(err) { models.IsErrNamePatternNotAllowed(err) {
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(500, "CreateUser", err)
ctx.Error(http.StatusInternalServerError, "CreateUser", err)
} }
return return
} }
if form.SendNotify { if form.SendNotify {
mailer.SendRegisterNotifyMail(ctx.Locale, u) mailer.SendRegisterNotifyMail(ctx.Locale, u)
} }
ctx.JSON(201, convert.ToUser(u, ctx.IsSigned, ctx.User.IsAdmin))
ctx.JSON(http.StatusCreated, convert.ToUser(u, ctx.IsSigned, ctx.User.IsAdmin))
} }


// EditUser api for modifying a user's information // EditUser api for modifying a user's information
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"

u := user.GetUserByParams(ctx) u := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
if len(form.Password) > 0 { if len(form.Password) > 0 {
if !password.IsComplexEnough(form.Password) { if !password.IsComplexEnough(form.Password) {
err := errors.New("PasswordComplexity") err := errors.New("PasswordComplexity")
ctx.Error(400, "PasswordComplexity", err)
ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
return return
} }
var err error var err error
if u.Salt, err = models.GetUserSalt(); err != nil { if u.Salt, err = models.GetUserSalt(); err != nil {
ctx.Error(500, "UpdateUser", err)
ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
return return
} }
u.HashPassword(form.Password) u.HashPassword(form.Password)


if err := models.UpdateUser(u); err != nil { if err := models.UpdateUser(u); err != nil {
if models.IsErrEmailAlreadyUsed(err) { if models.IsErrEmailAlreadyUsed(err) {
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(500, "UpdateUser", err)
ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
} }
return return
} }
log.Trace("Account profile updated by admin (%s): %s", ctx.User.Name, u.Name) log.Trace("Account profile updated by admin (%s): %s", ctx.User.Name, u.Name)


ctx.JSON(200, convert.ToUser(u, ctx.IsSigned, ctx.User.IsAdmin))
ctx.JSON(http.StatusOK, convert.ToUser(u, ctx.IsSigned, ctx.User.IsAdmin))
} }


// DeleteUser api for deleting a user // DeleteUser api for deleting a user
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"

u := user.GetUserByParams(ctx) u := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
if err := models.DeleteUser(u); err != nil { if err := models.DeleteUser(u); err != nil {
if models.IsErrUserOwnRepos(err) || if models.IsErrUserOwnRepos(err) ||
models.IsErrUserHasOrgs(err) { models.IsErrUserHasOrgs(err) {
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(500, "DeleteUser", err)
ctx.Error(http.StatusInternalServerError, "DeleteUser", err)
} }
return return
} }
log.Trace("Account deleted by admin(%s): %s", ctx.User.Name, u.Name) log.Trace("Account deleted by admin(%s): %s", ctx.User.Name, u.Name)


ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


// CreatePublicKey api for creating a public key to a user // CreatePublicKey api for creating a public key to a user
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"

u := user.GetUserByParams(ctx) u := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"

u := user.GetUserByParams(ctx) u := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
if models.IsErrKeyNotExist(err) { if models.IsErrKeyNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else if models.IsErrKeyAccessDenied(err) { } else if models.IsErrKeyAccessDenied(err) {
ctx.Error(403, "", "You do not have access to this key")
ctx.Error(http.StatusForbidden, "", "You do not have access to this key")
} else { } else {
ctx.Error(500, "DeleteUserPublicKey", err)
ctx.Error(http.StatusInternalServerError, "DeleteUserPublicKey", err)
} }
return return
} }
log.Trace("Key deleted by admin(%s): %s", ctx.User.Name, u.Name) log.Trace("Key deleted by admin(%s): %s", ctx.User.Name, u.Name)


ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


//GetAllUsers API for getting information of all the users //GetAllUsers API for getting information of all the users
// "$ref": "#/responses/UserList" // "$ref": "#/responses/UserList"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"

users, _, err := models.SearchUsers(&models.SearchUserOptions{ users, _, err := models.SearchUsers(&models.SearchUserOptions{
Type: models.UserTypeIndividual, Type: models.UserTypeIndividual,
OrderBy: models.SearchOrderByAlphabetically, OrderBy: models.SearchOrderByAlphabetically,
PageSize: -1, PageSize: -1,
}) })
if err != nil { if err != nil {
ctx.Error(500, "GetAllUsers", err)
ctx.Error(http.StatusInternalServerError, "GetAllUsers", err)
return return
} }


results[i] = convert.ToUser(users[i], ctx.IsSigned, ctx.User.IsAdmin) results[i] = convert.ToUser(users[i], ctx.IsSigned, ctx.User.IsAdmin)
} }


ctx.JSON(200, &results)
ctx.JSON(http.StatusOK, &results)
} }

+ 29
- 28
routers/api/v1/api.go View File

package v1 package v1


import ( import (
"net/http"
"strings" "strings"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetUserByName", err)
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
} }
return return
} }
log.Trace("Sudo from (%s) to: %s", ctx.User.Name, user.Name) log.Trace("Sudo from (%s) to: %s", ctx.User.Name, user.Name)
ctx.User = user ctx.User = user
} else { } else {
ctx.JSON(403, map[string]string{
ctx.JSON(http.StatusForbidden, map[string]string{
"message": "Only administrators allowed to sudo.", "message": "Only administrators allowed to sudo.",
}) })
return return
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetUserByName", err)
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
} }
return return
} }
} else if models.IsErrRepoRedirectNotExist(err) { } else if models.IsErrRepoRedirectNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "LookupRepoRedirect", err)
ctx.Error(http.StatusInternalServerError, "LookupRepoRedirect", err)
} }
} else { } else {
ctx.Error(500, "GetRepositoryByName", err)
ctx.Error(http.StatusInternalServerError, "GetRepositoryByName", err)
} }
return return
} }


ctx.Repo.Permission, err = models.GetUserRepoPermission(repo, ctx.User) ctx.Repo.Permission, err = models.GetUserRepoPermission(repo, ctx.User)
if err != nil { if err != nil {
ctx.Error(500, "GetUserRepoPermission", err)
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
return return
} }


ctx.RequireCSRF() ctx.RequireCSRF()
return return
} }
ctx.Context.Error(401)
ctx.Context.Error(http.StatusUnauthorized)
} }
} }


func reqBasicAuth() macaron.Handler { func reqBasicAuth() macaron.Handler {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
if !ctx.Context.IsBasicAuth { if !ctx.Context.IsBasicAuth {
ctx.Context.Error(401)
ctx.Context.Error(http.StatusUnauthorized)
return return
} }
ctx.CheckForOTP() ctx.CheckForOTP()
func reqSiteAdmin() macaron.Handler { func reqSiteAdmin() macaron.Handler {
return func(ctx *context.Context) { return func(ctx *context.Context) {
if !ctx.IsUserSiteAdmin() { if !ctx.IsUserSiteAdmin() {
ctx.Error(403)
ctx.Error(http.StatusForbidden)
return return
} }
} }
func reqOwner() macaron.Handler { func reqOwner() macaron.Handler {
return func(ctx *context.Context) { return func(ctx *context.Context) {
if !ctx.IsUserRepoOwner() && !ctx.IsUserSiteAdmin() { if !ctx.IsUserRepoOwner() && !ctx.IsUserSiteAdmin() {
ctx.Error(403)
ctx.Error(http.StatusForbidden)
return return
} }
} }
func reqAdmin() macaron.Handler { func reqAdmin() macaron.Handler {
return func(ctx *context.Context) { return func(ctx *context.Context) {
if !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { if !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
ctx.Error(403)
ctx.Error(http.StatusForbidden)
return return
} }
} }
func reqRepoWriter(unitTypes ...models.UnitType) macaron.Handler { func reqRepoWriter(unitTypes ...models.UnitType) macaron.Handler {
return func(ctx *context.Context) { return func(ctx *context.Context) {
if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
ctx.Error(403)
ctx.Error(http.StatusForbidden)
return return
} }
} }
func reqRepoReader(unitType models.UnitType) macaron.Handler { func reqRepoReader(unitType models.UnitType) macaron.Handler {
return func(ctx *context.Context) { return func(ctx *context.Context) {
if !ctx.IsUserRepoReaderSpecific(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { if !ctx.IsUserRepoReaderSpecific(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
ctx.Error(403)
ctx.Error(http.StatusForbidden)
return return
} }
} }
func reqAnyRepoReader() macaron.Handler { func reqAnyRepoReader() macaron.Handler {
return func(ctx *context.Context) { return func(ctx *context.Context) {
if !ctx.IsUserRepoReaderAny() && !ctx.IsUserSiteAdmin() { if !ctx.IsUserRepoReaderAny() && !ctx.IsUserSiteAdmin() {
ctx.Error(403)
ctx.Error(http.StatusForbidden)
return return
} }
} }
} else if ctx.Org.Team != nil { } else if ctx.Org.Team != nil {
orgID = ctx.Org.Team.OrgID orgID = ctx.Org.Team.OrgID
} else { } else {
ctx.Error(500, "", "reqOrgOwnership: unprepared context")
ctx.Error(http.StatusInternalServerError, "", "reqOrgOwnership: unprepared context")
return return
} }


isOwner, err := models.IsOrganizationOwner(orgID, ctx.User.ID) isOwner, err := models.IsOrganizationOwner(orgID, ctx.User.ID)
if err != nil { if err != nil {
ctx.Error(500, "IsOrganizationOwner", err)
ctx.Error(http.StatusInternalServerError, "IsOrganizationOwner", err)
return return
} else if !isOwner { } else if !isOwner {
if ctx.Org.Organization != nil { if ctx.Org.Organization != nil {
ctx.Error(403, "", "Must be an organization owner")
ctx.Error(http.StatusForbidden, "", "Must be an organization owner")
} else { } else {
ctx.NotFound() ctx.NotFound()
} }
return return
} }
if ctx.Org.Team == nil { if ctx.Org.Team == nil {
ctx.Error(500, "", "reqTeamMembership: unprepared context")
ctx.Error(http.StatusInternalServerError, "", "reqTeamMembership: unprepared context")
return return
} }


var orgID = ctx.Org.Team.OrgID var orgID = ctx.Org.Team.OrgID
isOwner, err := models.IsOrganizationOwner(orgID, ctx.User.ID) isOwner, err := models.IsOrganizationOwner(orgID, ctx.User.ID)
if err != nil { if err != nil {
ctx.Error(500, "IsOrganizationOwner", err)
ctx.Error(http.StatusInternalServerError, "IsOrganizationOwner", err)
return return
} else if isOwner { } else if isOwner {
return return
} }


if isTeamMember, err := models.IsTeamMember(orgID, ctx.Org.Team.ID, ctx.User.ID); err != nil { if isTeamMember, err := models.IsTeamMember(orgID, ctx.Org.Team.ID, ctx.User.ID); err != nil {
ctx.Error(500, "IsTeamMember", err)
ctx.Error(http.StatusInternalServerError, "IsTeamMember", err)
return return
} else if !isTeamMember { } else if !isTeamMember {
isOrgMember, err := models.IsOrganizationMember(orgID, ctx.User.ID) isOrgMember, err := models.IsOrganizationMember(orgID, ctx.User.ID)
if err != nil { if err != nil {
ctx.Error(500, "IsOrganizationMember", err)
ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
} else if isOrgMember { } else if isOrgMember {
ctx.Error(403, "", "Must be a team member")
ctx.Error(http.StatusForbidden, "", "Must be a team member")
} else { } else {
ctx.NotFound() ctx.NotFound()
} }
} else if ctx.Org.Team != nil { } else if ctx.Org.Team != nil {
orgID = ctx.Org.Team.OrgID orgID = ctx.Org.Team.OrgID
} else { } else {
ctx.Error(500, "", "reqOrgMembership: unprepared context")
ctx.Error(http.StatusInternalServerError, "", "reqOrgMembership: unprepared context")
return return
} }


if isMember, err := models.IsOrganizationMember(orgID, ctx.User.ID); err != nil { if isMember, err := models.IsOrganizationMember(orgID, ctx.User.ID); err != nil {
ctx.Error(500, "IsOrganizationMember", err)
ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
return return
} else if !isMember { } else if !isMember {
if ctx.Org.Organization != nil { if ctx.Org.Organization != nil {
ctx.Error(403, "", "Must be an organization member")
ctx.Error(http.StatusForbidden, "", "Must be an organization member")
} else { } else {
ctx.NotFound() ctx.NotFound()
} }
func reqGitHook() macaron.Handler { func reqGitHook() macaron.Handler {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
if !ctx.User.CanEditGitHook() { if !ctx.User.CanEditGitHook() {
ctx.Error(403, "", "must be allowed to edit Git hooks")
ctx.Error(http.StatusForbidden, "", "must be allowed to edit Git hooks")
return return
} }
} }
if models.IsErrOrgNotExist(err) { if models.IsErrOrgNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetOrgByName", err)
ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
} }
return return
} }
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetTeamById", err)
ctx.Error(http.StatusInternalServerError, "GetTeamById", err)
} }
return return
} }

+ 8
- 6
routers/api/v1/misc/markdown.go View File

// "$ref": "#/responses/MarkdownRender" // "$ref": "#/responses/MarkdownRender"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"

if ctx.HasAPIError() { if ctx.HasAPIError() {
ctx.Error(422, "", ctx.GetErrMsg())
ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
return return
} }


if form.Wiki { if form.Wiki {
_, err := ctx.Write([]byte(markdown.RenderWiki(md, urlPrefix, meta))) _, err := ctx.Write([]byte(markdown.RenderWiki(md, urlPrefix, meta)))
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "", err)
ctx.InternalServerError(err)
return return
} }
} else { } else {
_, err := ctx.Write(markdown.Render(md, urlPrefix, meta)) _, err := ctx.Write(markdown.Render(md, urlPrefix, meta))
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "", err)
ctx.InternalServerError(err)
return return
} }
} }
default: default:
_, err := ctx.Write(markdown.RenderRaw([]byte(form.Text), "", false)) _, err := ctx.Write(markdown.RenderRaw([]byte(form.Text), "", false))
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "", err)
ctx.InternalServerError(err)
return return
} }
} }
// "$ref": "#/responses/MarkdownRender" // "$ref": "#/responses/MarkdownRender"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"

body, err := ctx.Req.Body().Bytes() body, err := ctx.Req.Body().Bytes()
if err != nil { if err != nil {
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
return return
} }
_, err = ctx.Write(markdown.RenderRaw(body, "", false)) _, err = ctx.Write(markdown.RenderRaw(body, "", false))
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "", err)
ctx.InternalServerError(err)
return return
} }
} }

+ 3
- 1
routers/api/v1/misc/swagger.go View File

package misc package misc


import ( import (
"net/http"

"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
) )
// Swagger render swagger-ui page with v1 json // Swagger render swagger-ui page with v1 json
func Swagger(ctx *context.Context) { func Swagger(ctx *context.Context) {
ctx.Data["APIJSONVersion"] = "v1" ctx.Data["APIJSONVersion"] = "v1"
ctx.HTML(200, tplSwagger)
ctx.HTML(http.StatusOK, tplSwagger)
} }

+ 3
- 1
routers/api/v1/misc/version.go View File

package misc package misc


import ( import (
"net/http"

"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/ServerVersion" // "$ref": "#/responses/ServerVersion"
ctx.JSON(200, &structs.ServerVersion{Version: setting.AppVer})
ctx.JSON(http.StatusOK, &structs.ServerVersion{Version: setting.AppVer})
} }

+ 10
- 5
routers/api/v1/org/hook.go View File

package org package org


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/HookList" // "$ref": "#/responses/HookList"

org := ctx.Org.Organization org := ctx.Org.Organization
orgHooks, err := models.GetWebhooksByOrgID(org.ID) orgHooks, err := models.GetWebhooksByOrgID(org.ID)
if err != nil { if err != nil {
ctx.Error(500, "GetWebhooksByOrgID", err)
ctx.Error(http.StatusInternalServerError, "GetWebhooksByOrgID", err)
return return
} }
hooks := make([]*api.Hook, len(orgHooks)) hooks := make([]*api.Hook, len(orgHooks))
for i, hook := range orgHooks { for i, hook := range orgHooks {
hooks[i] = convert.ToHook(org.HomeLink(), hook) hooks[i] = convert.ToHook(org.HomeLink(), hook)
} }
ctx.JSON(200, hooks)
ctx.JSON(http.StatusOK, hooks)
} }


// GetHook get an organization's hook by id // GetHook get an organization's hook by id
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Hook" // "$ref": "#/responses/Hook"

org := ctx.Org.Organization org := ctx.Org.Organization
hookID := ctx.ParamsInt64(":id") hookID := ctx.ParamsInt64(":id")
hook, err := utils.GetOrgHook(ctx, org.ID, hookID) hook, err := utils.GetOrgHook(ctx, org.ID, hookID)
if err != nil { if err != nil {
return return
} }
ctx.JSON(200, convert.ToHook(org.HomeLink(), hook))
ctx.JSON(http.StatusOK, convert.ToHook(org.HomeLink(), hook))
} }


// CreateHook create a hook for an organization // CreateHook create a hook for an organization
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"

org := ctx.Org.Organization org := ctx.Org.Organization
hookID := ctx.ParamsInt64(":id") hookID := ctx.ParamsInt64(":id")
if err := models.DeleteWebhookByOrgID(org.ID, hookID); err != nil { if err := models.DeleteWebhookByOrgID(org.ID, hookID); err != nil {
if models.IsErrWebhookNotExist(err) { if models.IsErrWebhookNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "DeleteWebhookByOrgID", err)
ctx.Error(http.StatusInternalServerError, "DeleteWebhookByOrgID", err)
} }
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 29
- 15
routers/api/v1/org/member.go View File



import ( import (
"fmt" "fmt"
"net/http"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
PublicOnly: publicOnly, PublicOnly: publicOnly,
}) })
if err != nil { if err != nil {
ctx.Error(500, "GetUsersByIDs", err)
ctx.Error(http.StatusInternalServerError, "GetUsersByIDs", err)
return return
} }


for i, member := range members { for i, member := range members {
apiMembers[i] = convert.ToUser(member, ctx.IsSigned, ctx.User != nil && ctx.User.IsAdmin) apiMembers[i] = convert.ToUser(member, ctx.IsSigned, ctx.User != nil && ctx.User.IsAdmin)
} }
ctx.JSON(200, apiMembers)
ctx.JSON(http.StatusOK, apiMembers)
} }


// ListMembers list an organization's members // ListMembers list an organization's members
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/UserList" // "$ref": "#/responses/UserList"

publicOnly := true publicOnly := true
if ctx.User != nil { if ctx.User != nil {
isMember, err := ctx.Org.Organization.IsOrgMember(ctx.User.ID) isMember, err := ctx.Org.Organization.IsOrgMember(ctx.User.ID)
if err != nil { if err != nil {
ctx.Error(500, "IsOrgMember", err)
ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
return return
} }
publicOnly = !isMember publicOnly = !isMember
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/UserList" // "$ref": "#/responses/UserList"

listMembers(ctx, true) listMembers(ctx, true)
} }


// responses: // responses:
// "204": // "204":
// description: user is a member // description: user is a member
// "302":
// description: redirection to /orgs/{org}/public_members/{username}
// "404": // "404":
// description: user is not a member // description: user is not a member

userToCheck := user.GetUserByParams(ctx) userToCheck := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
if ctx.User != nil { if ctx.User != nil {
userIsMember, err := ctx.Org.Organization.IsOrgMember(ctx.User.ID) userIsMember, err := ctx.Org.Organization.IsOrgMember(ctx.User.ID)
if err != nil { if err != nil {
ctx.Error(500, "IsOrgMember", err)
ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
return return
} else if userIsMember { } else if userIsMember {
userToCheckIsMember, err := ctx.Org.Organization.IsOrgMember(userToCheck.ID) userToCheckIsMember, err := ctx.Org.Organization.IsOrgMember(userToCheck.ID)
if err != nil { if err != nil {
ctx.Error(500, "IsOrgMember", err)
ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
} else if userToCheckIsMember { } else if userToCheckIsMember {
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} else { } else {
ctx.NotFound() ctx.NotFound()
} }
// description: user is a public member // description: user is a public member
// "404": // "404":
// description: user is not a public member // description: user is not a public member

userToCheck := user.GetUserByParams(ctx) userToCheck := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
if userToCheck.IsPublicMember(ctx.Org.Organization.ID) { if userToCheck.IsPublicMember(ctx.Org.Organization.ID) {
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} else { } else {
ctx.NotFound() ctx.NotFound()
} }
// responses: // responses:
// "204": // "204":
// description: membership publicized // description: membership publicized
// "403":
// "$ref": "#/responses/forbidden"

userToPublicize := user.GetUserByParams(ctx) userToPublicize := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
if userToPublicize.ID != ctx.User.ID { if userToPublicize.ID != ctx.User.ID {
ctx.Error(403, "", "Cannot publicize another member")
ctx.Error(http.StatusForbidden, "", "Cannot publicize another member")
return return
} }
err := models.ChangeOrgUserStatus(ctx.Org.Organization.ID, userToPublicize.ID, true) err := models.ChangeOrgUserStatus(ctx.Org.Organization.ID, userToPublicize.ID, true)
if err != nil { if err != nil {
ctx.Error(500, "ChangeOrgUserStatus", err)
ctx.Error(http.StatusInternalServerError, "ChangeOrgUserStatus", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


// ConcealMember make a member's membership not public // ConcealMember make a member's membership not public
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"

userToConceal := user.GetUserByParams(ctx) userToConceal := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
if userToConceal.ID != ctx.User.ID { if userToConceal.ID != ctx.User.ID {
ctx.Error(403, "", "Cannot conceal another member")
ctx.Error(http.StatusForbidden, "", "Cannot conceal another member")
return return
} }
err := models.ChangeOrgUserStatus(ctx.Org.Organization.ID, userToConceal.ID, false) err := models.ChangeOrgUserStatus(ctx.Org.Organization.ID, userToConceal.ID, false)
if err != nil { if err != nil {
ctx.Error(500, "ChangeOrgUserStatus", err)
ctx.Error(http.StatusInternalServerError, "ChangeOrgUserStatus", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


// DeleteMember remove a member from an organization // DeleteMember remove a member from an organization
// responses: // responses:
// "204": // "204":
// description: member removed // description: member removed

member := user.GetUserByParams(ctx) member := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
if err := ctx.Org.Organization.RemoveMember(member.ID); err != nil { if err := ctx.Org.Organization.RemoveMember(member.ID); err != nil {
ctx.Error(500, "RemoveMember", err)
ctx.Error(http.StatusInternalServerError, "RemoveMember", err)
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 18
- 11
routers/api/v1/org/org.go View File

package org package org


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"


func listUserOrgs(ctx *context.APIContext, u *models.User, all bool) { func listUserOrgs(ctx *context.APIContext, u *models.User, all bool) {
if err := u.GetOrganizations(all); err != nil { if err := u.GetOrganizations(all); err != nil {
ctx.Error(500, "GetOrganizations", err)
ctx.Error(http.StatusInternalServerError, "GetOrganizations", err)
return return
} }


for i := range u.Orgs { for i := range u.Orgs {
apiOrgs[i] = convert.ToOrganization(u.Orgs[i]) apiOrgs[i] = convert.ToOrganization(u.Orgs[i])
} }
ctx.JSON(200, &apiOrgs)
ctx.JSON(http.StatusOK, &apiOrgs)
} }


// ListMyOrgs list all my orgs // ListMyOrgs list all my orgs
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/OrganizationList" // "$ref": "#/responses/OrganizationList"

listUserOrgs(ctx, ctx.User, true) listUserOrgs(ctx, ctx.User, true)
} }


// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/OrganizationList" // "$ref": "#/responses/OrganizationList"

u := user.GetUserByParams(ctx) u := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"


if !ctx.User.CanCreateOrganization() { if !ctx.User.CanCreateOrganization() {
ctx.Error(403, "Create organization not allowed", nil)
ctx.Error(http.StatusForbidden, "Create organization not allowed", nil)
return return
} }


if models.IsErrUserAlreadyExist(err) || if models.IsErrUserAlreadyExist(err) ||
models.IsErrNameReserved(err) || models.IsErrNameReserved(err) ||
models.IsErrNamePatternNotAllowed(err) { models.IsErrNamePatternNotAllowed(err) {
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(500, "CreateOrganization", err)
ctx.Error(http.StatusInternalServerError, "CreateOrganization", err)
} }
return return
} }


ctx.JSON(201, convert.ToOrganization(org))
ctx.JSON(http.StatusCreated, convert.ToOrganization(org))
} }


// Get get an organization // Get get an organization
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Organization" // "$ref": "#/responses/Organization"

if !models.HasOrgVisible(ctx.Org.Organization, ctx.User) { if !models.HasOrgVisible(ctx.Org.Organization, ctx.User) {
ctx.NotFound("HasOrgVisible", nil) ctx.NotFound("HasOrgVisible", nil)
return return
} }
ctx.JSON(200, convert.ToOrganization(ctx.Org.Organization))
ctx.JSON(http.StatusOK, convert.ToOrganization(ctx.Org.Organization))
} }


// Edit change an organization's information // Edit change an organization's information
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Organization" // "$ref": "#/responses/Organization"

org := ctx.Org.Organization org := ctx.Org.Organization
org.FullName = form.FullName org.FullName = form.FullName
org.Description = form.Description org.Description = form.Description
org.Visibility = api.VisibilityModes[form.Visibility] org.Visibility = api.VisibilityModes[form.Visibility]
} }
if err := models.UpdateUserCols(org, "full_name", "description", "website", "location", "visibility"); err != nil { if err := models.UpdateUserCols(org, "full_name", "description", "website", "location", "visibility"); err != nil {
ctx.Error(500, "EditOrganization", err)
ctx.Error(http.StatusInternalServerError, "EditOrganization", err)
return return
} }


ctx.JSON(200, convert.ToOrganization(org))
ctx.JSON(http.StatusOK, convert.ToOrganization(org))
} }


//Delete an organization //Delete an organization
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"

if err := models.DeleteOrganization(ctx.Org.Organization); err != nil { if err := models.DeleteOrganization(ctx.Org.Organization); err != nil {
ctx.Error(500, "DeleteOrganization", err)
ctx.Error(http.StatusInternalServerError, "DeleteOrganization", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 65
- 38
routers/api/v1/org/team.go View File

package org package org


import ( import (
"net/http"
"strings" "strings"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/TeamList" // "$ref": "#/responses/TeamList"

org := ctx.Org.Organization org := ctx.Org.Organization
if err := org.GetTeams(); err != nil { if err := org.GetTeams(); err != nil {
ctx.Error(500, "GetTeams", err)
ctx.Error(http.StatusInternalServerError, "GetTeams", err)
return return
} }


apiTeams := make([]*api.Team, len(org.Teams)) apiTeams := make([]*api.Team, len(org.Teams))
for i := range org.Teams { for i := range org.Teams {
if err := org.Teams[i].GetUnits(); err != nil { if err := org.Teams[i].GetUnits(); err != nil {
ctx.Error(500, "GetUnits", err)
ctx.Error(http.StatusInternalServerError, "GetUnits", err)
return return
} }


apiTeams[i] = convert.ToTeam(org.Teams[i]) apiTeams[i] = convert.ToTeam(org.Teams[i])
} }
ctx.JSON(200, apiTeams)
ctx.JSON(http.StatusOK, apiTeams)
} }


// ListUserTeams list all the teams a user belongs to // ListUserTeams list all the teams a user belongs to
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/TeamList" // "$ref": "#/responses/TeamList"

teams, err := models.GetUserTeams(ctx.User.ID) teams, err := models.GetUserTeams(ctx.User.ID)
if err != nil { if err != nil {
ctx.Error(500, "GetUserTeams", err)
ctx.Error(http.StatusInternalServerError, "GetUserTeams", err)
return return
} }


if !ok { if !ok {
org, err := models.GetUserByID(teams[i].OrgID) org, err := models.GetUserByID(teams[i].OrgID)
if err != nil { if err != nil {
ctx.Error(500, "GetUserByID", err)
ctx.Error(http.StatusInternalServerError, "GetUserByID", err)
return return
} }
apiOrg = convert.ToOrganization(org) apiOrg = convert.ToOrganization(org)
apiTeams[i] = convert.ToTeam(teams[i]) apiTeams[i] = convert.ToTeam(teams[i])
apiTeams[i].Organization = apiOrg apiTeams[i].Organization = apiOrg
} }
ctx.JSON(200, apiTeams)
ctx.JSON(http.StatusOK, apiTeams)
} }


// GetTeam api for get a team // GetTeam api for get a team
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Team" // "$ref": "#/responses/Team"
ctx.JSON(200, convert.ToTeam(ctx.Org.Team))

ctx.JSON(http.StatusOK, convert.ToTeam(ctx.Org.Team))
} }


// CreateTeam api for create a team // CreateTeam api for create a team
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/Team" // "$ref": "#/responses/Team"
// "422":
// "$ref": "#/responses/validationError"

team := &models.Team{ team := &models.Team{
OrgID: ctx.Org.Organization.ID, OrgID: ctx.Org.Organization.ID,
Name: form.Name, Name: form.Name,


if err := models.NewTeam(team); err != nil { if err := models.NewTeam(team); err != nil {
if models.IsErrTeamAlreadyExist(err) { if models.IsErrTeamAlreadyExist(err) {
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(500, "NewTeam", err)
ctx.Error(http.StatusInternalServerError, "NewTeam", err)
} }
return return
} }


ctx.JSON(201, convert.ToTeam(team))
ctx.JSON(http.StatusCreated, convert.ToTeam(team))
} }


// EditTeam api for edit a team // EditTeam api for edit a team
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Team" // "$ref": "#/responses/Team"

team := ctx.Org.Team team := ctx.Org.Team
team.Description = form.Description team.Description = form.Description
unitTypes := models.FindUnitTypes(form.Units...) unitTypes := models.FindUnitTypes(form.Units...)
} }


if err := models.UpdateTeam(team, isAuthChanged, isIncludeAllChanged); err != nil { if err := models.UpdateTeam(team, isAuthChanged, isIncludeAllChanged); err != nil {
ctx.Error(500, "EditTeam", err)
ctx.Error(http.StatusInternalServerError, "EditTeam", err)
return return
} }
ctx.JSON(200, convert.ToTeam(team))
ctx.JSON(http.StatusOK, convert.ToTeam(team))
} }


// DeleteTeam api for delete a team // DeleteTeam api for delete a team
// responses: // responses:
// "204": // "204":
// description: team deleted // description: team deleted

if err := models.DeleteTeam(ctx.Org.Team); err != nil { if err := models.DeleteTeam(ctx.Org.Team); err != nil {
ctx.Error(500, "DeleteTeam", err)
ctx.Error(http.StatusInternalServerError, "DeleteTeam", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


// GetTeamMembers api for get a team's members // GetTeamMembers api for get a team's members
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/UserList" // "$ref": "#/responses/UserList"

isMember, err := models.IsOrganizationMember(ctx.Org.Team.OrgID, ctx.User.ID) isMember, err := models.IsOrganizationMember(ctx.Org.Team.OrgID, ctx.User.ID)
if err != nil { if err != nil {
ctx.Error(500, "IsOrganizationMember", err)
ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
return return
} else if !isMember { } else if !isMember {
ctx.NotFound() ctx.NotFound()
} }
team := ctx.Org.Team team := ctx.Org.Team
if err := team.GetMembers(); err != nil { if err := team.GetMembers(); err != nil {
ctx.Error(500, "GetTeamMembers", err)
ctx.Error(http.StatusInternalServerError, "GetTeamMembers", err)
return return
} }
members := make([]*api.User, len(team.Members)) members := make([]*api.User, len(team.Members))
for i, member := range team.Members { for i, member := range team.Members {
members[i] = convert.ToUser(member, ctx.IsSigned, ctx.User.IsAdmin) members[i] = convert.ToUser(member, ctx.IsSigned, ctx.User.IsAdmin)
} }
ctx.JSON(200, members)
ctx.JSON(http.StatusOK, members)
} }


// GetTeamMember api for get a particular member of team // GetTeamMember api for get a particular member of team
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/User" // "$ref": "#/responses/User"
// "404":
// "$ref": "#/responses/notFound"

u := user.GetUserByParams(ctx) u := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
teamID := ctx.ParamsInt64("teamid") teamID := ctx.ParamsInt64("teamid")
isTeamMember, err := models.IsUserInTeams(u.ID, []int64{teamID}) isTeamMember, err := models.IsUserInTeams(u.ID, []int64{teamID})
if err != nil { if err != nil {
ctx.Error(500, "IsUserInTeams", err)
ctx.Error(http.StatusInternalServerError, "IsUserInTeams", err)
return return
} else if !isTeamMember { } else if !isTeamMember {
ctx.NotFound() ctx.NotFound()
return return
} }
ctx.JSON(200, convert.ToUser(u, ctx.IsSigned, ctx.User.IsAdmin))
ctx.JSON(http.StatusOK, convert.ToUser(u, ctx.IsSigned, ctx.User.IsAdmin))
} }


// AddTeamMember api for add a member to a team // AddTeamMember api for add a member to a team
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"

u := user.GetUserByParams(ctx) u := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
if err := ctx.Org.Team.AddMember(u.ID); err != nil { if err := ctx.Org.Team.AddMember(u.ID); err != nil {
ctx.Error(500, "AddMember", err)
ctx.Error(http.StatusInternalServerError, "AddMember", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


// RemoveTeamMember api for remove one member from a team // RemoveTeamMember api for remove one member from a team
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"

u := user.GetUserByParams(ctx) u := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }


if err := ctx.Org.Team.RemoveMember(u.ID); err != nil { if err := ctx.Org.Team.RemoveMember(u.ID); err != nil {
ctx.Error(500, "RemoveMember", err)
ctx.Error(http.StatusInternalServerError, "RemoveMember", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


// GetTeamRepos api for get a team's repos // GetTeamRepos api for get a team's repos
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/RepositoryList" // "$ref": "#/responses/RepositoryList"

team := ctx.Org.Team team := ctx.Org.Team
if err := team.GetRepositories(); err != nil { if err := team.GetRepositories(); err != nil {
ctx.Error(500, "GetTeamRepos", err)
ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
} }
repos := make([]*api.Repository, len(team.Repos)) repos := make([]*api.Repository, len(team.Repos))
for i, repo := range team.Repos { for i, repo := range team.Repos {
access, err := models.AccessLevel(ctx.User, repo) access, err := models.AccessLevel(ctx.User, repo)
if err != nil { if err != nil {
ctx.Error(500, "GetTeamRepos", err)
ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
return return
} }
repos[i] = repo.APIFormat(access) repos[i] = repo.APIFormat(access)
} }
ctx.JSON(200, repos)
ctx.JSON(http.StatusOK, repos)
} }


// getRepositoryByParams get repository by a team's organization ID and repo name // getRepositoryByParams get repository by a team's organization ID and repo name
if models.IsErrRepoNotExist(err) { if models.IsErrRepoNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetRepositoryByName", err)
ctx.Error(http.StatusInternalServerError, "GetRepositoryByName", err)
} }
return nil return nil
} }
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"

repo := getRepositoryByParams(ctx) repo := getRepositoryByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
if access, err := models.AccessLevel(ctx.User, repo); err != nil { if access, err := models.AccessLevel(ctx.User, repo); err != nil {
ctx.Error(500, "AccessLevel", err)
ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
return return
} else if access < models.AccessModeAdmin { } else if access < models.AccessModeAdmin {
ctx.Error(403, "", "Must have admin-level access to the repository")
ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository")
return return
} }
if err := ctx.Org.Team.AddRepository(repo); err != nil { if err := ctx.Org.Team.AddRepository(repo); err != nil {
ctx.Error(500, "AddRepository", err)
ctx.Error(http.StatusInternalServerError, "AddRepository", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


// RemoveTeamRepository api for removing a repository from a team // RemoveTeamRepository api for removing a repository from a team
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"

repo := getRepositoryByParams(ctx) repo := getRepositoryByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
if access, err := models.AccessLevel(ctx.User, repo); err != nil { if access, err := models.AccessLevel(ctx.User, repo); err != nil {
ctx.Error(500, "AccessLevel", err)
ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
return return
} else if access < models.AccessModeAdmin { } else if access < models.AccessModeAdmin {
ctx.Error(403, "", "Must have admin-level access to the repository")
ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository")
return return
} }
if err := ctx.Org.Team.RemoveRepository(repo.ID); err != nil { if err := ctx.Org.Team.RemoveRepository(repo.ID); err != nil {
ctx.Error(500, "RemoveRepository", err)
ctx.Error(http.StatusInternalServerError, "RemoveRepository", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


// SearchTeam api for searching teams // SearchTeam api for searching teams
// type: array // type: array
// items: // items:
// "$ref": "#/definitions/Team" // "$ref": "#/definitions/Team"

opts := &models.SearchTeamOptions{ opts := &models.SearchTeamOptions{
UserID: ctx.User.ID, UserID: ctx.User.ID,
Keyword: strings.TrimSpace(ctx.Query("q")), Keyword: strings.TrimSpace(ctx.Query("q")),
teams, _, err := models.SearchTeam(opts) teams, _, err := models.SearchTeam(opts)
if err != nil { if err != nil {
log.Error("SearchTeam failed: %v", err) log.Error("SearchTeam failed: %v", err)
ctx.JSON(500, map[string]interface{}{
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"ok": false, "ok": false,
"error": "SearchTeam internal failure", "error": "SearchTeam internal failure",
}) })
for i := range teams { for i := range teams {
if err := teams[i].GetUnits(); err != nil { if err := teams[i].GetUnits(); err != nil {
log.Error("Team GetUnits failed: %v", err) log.Error("Team GetUnits failed: %v", err)
ctx.JSON(500, map[string]interface{}{
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"ok": false, "ok": false,
"error": "SearchTeam failed to get units", "error": "SearchTeam failed to get units",
}) })
apiTeams[i] = convert.ToTeam(teams[i]) apiTeams[i] = convert.ToTeam(teams[i])
} }


ctx.JSON(200, map[string]interface{}{
ctx.JSON(http.StatusOK, map[string]interface{}{
"ok": true, "ok": true,
"data": apiTeams, "data": apiTeams,
}) })

+ 2
- 0
routers/api/v1/repo/blob.go View File

// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/GitBlobResponse" // "$ref": "#/responses/GitBlobResponse"
// "400":
// "$ref": "#/responses/error"


sha := ctx.Params("sha") sha := ctx.Params("sha")
if len(sha) == 0 { if len(sha) == 0 {

+ 12
- 8
routers/api/v1/repo/branch.go View File

package repo package repo


import ( import (
"net/http"

"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Branch" // "$ref": "#/responses/Branch"

if ctx.Repo.TreePath != "" { if ctx.Repo.TreePath != "" {
// if TreePath != "", then URL contained extra slashes // if TreePath != "", then URL contained extra slashes
// (i.e. "master/subbranch" instead of "master"), so branch does // (i.e. "master/subbranch" instead of "master"), so branch does
if git.IsErrBranchNotExist(err) { if git.IsErrBranchNotExist(err) {
ctx.NotFound(err) ctx.NotFound(err)
} else { } else {
ctx.Error(500, "GetBranch", err)
ctx.Error(http.StatusInternalServerError, "GetBranch", err)
} }
return return
} }


c, err := branch.GetCommit() c, err := branch.GetCommit()
if err != nil { if err != nil {
ctx.Error(500, "GetCommit", err)
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
return return
} }


branchProtection, err := ctx.Repo.Repository.GetBranchProtection(ctx.Repo.BranchName) branchProtection, err := ctx.Repo.Repository.GetBranchProtection(ctx.Repo.BranchName)
if err != nil { if err != nil {
ctx.Error(500, "GetBranchProtection", err)
ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
return return
} }


ctx.JSON(200, convert.ToBranch(ctx.Repo.Repository, branch, c, branchProtection, ctx.User))
ctx.JSON(http.StatusOK, convert.ToBranch(ctx.Repo.Repository, branch, c, branchProtection, ctx.User))
} }


// ListBranches list all the branches of a repository // ListBranches list all the branches of a repository
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/BranchList" // "$ref": "#/responses/BranchList"

branches, err := ctx.Repo.Repository.GetBranches() branches, err := ctx.Repo.Repository.GetBranches()
if err != nil { if err != nil {
ctx.Error(500, "GetBranches", err)
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
return return
} }


for i := range branches { for i := range branches {
c, err := branches[i].GetCommit() c, err := branches[i].GetCommit()
if err != nil { if err != nil {
ctx.Error(500, "GetCommit", err)
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
return return
} }
branchProtection, err := ctx.Repo.Repository.GetBranchProtection(branches[i].Name) branchProtection, err := ctx.Repo.Repository.GetBranchProtection(branches[i].Name)
if err != nil { if err != nil {
ctx.Error(500, "GetBranchProtection", err)
ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
return return
} }
apiBranches[i] = convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.User) apiBranches[i] = convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.User)
} }


ctx.JSON(200, &apiBranches)
ctx.JSON(http.StatusOK, &apiBranches)
} }

+ 28
- 17
routers/api/v1/repo/collaborators.go View File



import ( import (
"errors" "errors"
"net/http"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/UserList" // "$ref": "#/responses/UserList"

collaborators, err := ctx.Repo.Repository.GetCollaborators() collaborators, err := ctx.Repo.Repository.GetCollaborators()
if err != nil { if err != nil {
ctx.Error(500, "ListCollaborators", err)
ctx.Error(http.StatusInternalServerError, "ListCollaborators", err)
return return
} }
users := make([]*api.User, len(collaborators)) users := make([]*api.User, len(collaborators))
for i, collaborator := range collaborators { for i, collaborator := range collaborators {
users[i] = convert.ToUser(collaborator.User, ctx.IsSigned, ctx.User != nil && ctx.User.IsAdmin) users[i] = convert.ToUser(collaborator.User, ctx.IsSigned, ctx.User != nil && ctx.User.IsAdmin)
} }
ctx.JSON(200, users)
ctx.JSON(http.StatusOK, users)
} }


// IsCollaborator check if a user is a collaborator of a repository // IsCollaborator check if a user is a collaborator of a repository
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "404": // "404":
// "$ref": "#/responses/empty"
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"

user, err := models.GetUserByName(ctx.Params(":collaborator")) user, err := models.GetUserByName(ctx.Params(":collaborator"))
if err != nil { if err != nil {
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(500, "GetUserByName", err)
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
} }
return return
} }
isColab, err := ctx.Repo.Repository.IsCollaborator(user.ID) isColab, err := ctx.Repo.Repository.IsCollaborator(user.ID)
if err != nil { if err != nil {
ctx.Error(500, "IsCollaborator", err)
ctx.Error(http.StatusInternalServerError, "IsCollaborator", err)
return return
} }
if isColab { if isColab {
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} else { } else {
ctx.NotFound() ctx.NotFound()
} }
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "422":
// "$ref": "#/responses/validationError"

collaborator, err := models.GetUserByName(ctx.Params(":collaborator")) collaborator, err := models.GetUserByName(ctx.Params(":collaborator"))
if err != nil { if err != nil {
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(500, "GetUserByName", err)
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
} }
return return
} }


if !collaborator.IsActive { if !collaborator.IsActive {
ctx.Error(500, "InactiveCollaborator", errors.New("collaborator's account is inactive"))
ctx.Error(http.StatusInternalServerError, "InactiveCollaborator", errors.New("collaborator's account is inactive"))
return return
} }


if err := ctx.Repo.Repository.AddCollaborator(collaborator); err != nil { if err := ctx.Repo.Repository.AddCollaborator(collaborator); err != nil {
ctx.Error(500, "AddCollaborator", err)
ctx.Error(http.StatusInternalServerError, "AddCollaborator", err)
return return
} }


if form.Permission != nil { if form.Permission != nil {
if err := ctx.Repo.Repository.ChangeCollaborationAccessMode(collaborator.ID, models.ParseAccessMode(*form.Permission)); err != nil { if err := ctx.Repo.Repository.ChangeCollaborationAccessMode(collaborator.ID, models.ParseAccessMode(*form.Permission)); err != nil {
ctx.Error(500, "ChangeCollaborationAccessMode", err)
ctx.Error(http.StatusInternalServerError, "ChangeCollaborationAccessMode", err)
return return
} }
} }


ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


// DeleteCollaborator delete a collaborator from a repository // DeleteCollaborator delete a collaborator from a repository
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "422":
// "$ref": "#/responses/validationError"

collaborator, err := models.GetUserByName(ctx.Params(":collaborator")) collaborator, err := models.GetUserByName(ctx.Params(":collaborator"))
if err != nil { if err != nil {
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(500, "GetUserByName", err)
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
} }
return return
} }


if err := ctx.Repo.Repository.DeleteCollaboration(collaborator.ID); err != nil { if err := ctx.Repo.Repository.DeleteCollaboration(collaborator.ID); err != nil {
ctx.Error(500, "DeleteCollaboration", err)
ctx.Error(http.StatusInternalServerError, "DeleteCollaboration", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 4
- 3
routers/api/v1/repo/commits.go View File



import ( import (
"math" "math"
"net/http"
"strconv" "strconv"
"time" "time"


return return
} }


ctx.JSON(200, json)
ctx.JSON(http.StatusOK, json)
} }


// GetAllCommits get all commits via // GetAllCommits get all commits via
// "$ref": "#/responses/EmptyRepository" // "$ref": "#/responses/EmptyRepository"


if ctx.Repo.Repository.IsEmpty { if ctx.Repo.Repository.IsEmpty {
ctx.JSON(409, api.APIError{
ctx.JSON(http.StatusConflict, api.APIError{
Message: "Git Repository is empty.", Message: "Git Repository is empty.",
URL: setting.API.SwaggerURL, URL: setting.API.SwaggerURL,
}) })
ctx.Header().Set("X-PageCount", strconv.Itoa(pageCount)) ctx.Header().Set("X-PageCount", strconv.Itoa(pageCount))
ctx.Header().Set("X-HasMore", strconv.FormatBool(page < pageCount)) ctx.Header().Set("X-HasMore", strconv.FormatBool(page < pageCount))


ctx.JSON(200, &apiCommits)
ctx.JSON(http.StatusOK, &apiCommits)
} }


func toCommit(ctx *context.APIContext, repo *models.Repository, commit *git.Commit, userCache map[string]*models.User) (*api.Commit, error) { func toCommit(ctx *context.APIContext, repo *models.Repository, commit *git.Commit, userCache map[string]*models.User) (*api.Commit, error) {

+ 10
- 0
routers/api/v1/repo/file.go View File

// responses: // responses:
// 200: // 200:
// description: success // description: success
// "404":
// "$ref": "#/responses/notFound"

if ctx.Repo.Repository.IsEmpty { if ctx.Repo.Repository.IsEmpty {
ctx.NotFound() ctx.NotFound()
return return
// responses: // responses:
// 200: // 200:
// description: success // description: success
// "404":
// "$ref": "#/responses/notFound"

repoPath := models.RepoPath(ctx.Params(":username"), ctx.Params(":reponame")) repoPath := models.RepoPath(ctx.Params(":username"), ctx.Params(":reponame"))
gitRepo, err := git.OpenRepository(repoPath) gitRepo, err := git.OpenRepository(repoPath)
if err != nil { if err != nil {
// responses: // responses:
// 200: // 200:
// description: success // description: success
// "404":
// "$ref": "#/responses/notFound"

ec, err := ctx.Repo.GetEditorconfig() ec, err := ctx.Repo.GetEditorconfig()
if err != nil { if err != nil {
if git.IsErrNotExist(err) { if git.IsErrNotExist(err) {
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/FileDeleteResponse" // "$ref": "#/responses/FileDeleteResponse"

if !CanWriteFiles(ctx.Repo) { if !CanWriteFiles(ctx.Repo) {
ctx.Error(http.StatusInternalServerError, "DeleteFile", models.ErrUserDoesNotHaveAccessToRepo{ ctx.Error(http.StatusInternalServerError, "DeleteFile", models.ErrUserDoesNotHaveAccessToRepo{
UserID: ctx.User.ID, UserID: ctx.User.ID,

+ 18
- 8
routers/api/v1/repo/fork.go View File

package repo package repo


import ( import (
"fmt"
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/RepositoryList" // "$ref": "#/responses/RepositoryList"

forks, err := ctx.Repo.Repository.GetForks() forks, err := ctx.Repo.Repository.GetForks()
if err != nil { if err != nil {
ctx.Error(500, "GetForks", err)
ctx.Error(http.StatusInternalServerError, "GetForks", err)
return return
} }
apiForks := make([]*api.Repository, len(forks)) apiForks := make([]*api.Repository, len(forks))
for i, fork := range forks { for i, fork := range forks {
access, err := models.AccessLevel(ctx.User, fork) access, err := models.AccessLevel(ctx.User, fork)
if err != nil { if err != nil {
ctx.Error(500, "AccessLevel", err)
ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
return return
} }
apiForks[i] = fork.APIFormat(access) apiForks[i] = fork.APIFormat(access)
} }
ctx.JSON(200, apiForks)
ctx.JSON(http.StatusOK, apiForks)
} }


// CreateFork create a fork of a repo // CreateFork create a fork of a repo
// responses: // responses:
// "202": // "202":
// "$ref": "#/responses/Repository" // "$ref": "#/responses/Repository"
// "403":
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"

repo := ctx.Repo.Repository repo := ctx.Repo.Repository
var forker *models.User // user/org that will own the fork var forker *models.User // user/org that will own the fork
if form.Organization == nil { if form.Organization == nil {
org, err := models.GetOrgByName(*form.Organization) org, err := models.GetOrgByName(*form.Organization)
if err != nil { if err != nil {
if models.IsErrOrgNotExist(err) { if models.IsErrOrgNotExist(err) {
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(500, "GetOrgByName", err)
ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
} }
return return
} }
ctx.ServerError("IsOrgMember", err) ctx.ServerError("IsOrgMember", err)
return return
} else if !isMember { } else if !isMember {
ctx.Status(403)
ctx.Error(http.StatusForbidden, "isMemberNot", fmt.Sprintf("User is no Member of Organisation '%s'", org.Name))
return return
} }
forker = org forker = org


fork, err := repo_service.ForkRepository(ctx.User, forker, repo, repo.Name, repo.Description) fork, err := repo_service.ForkRepository(ctx.User, forker, repo, repo.Name, repo.Description)
if err != nil { if err != nil {
ctx.Error(500, "ForkRepository", err)
ctx.Error(http.StatusInternalServerError, "ForkRepository", err)
return return
} }


ctx.JSON(202, fork.APIFormat(models.AccessModeOwner))
//TODO change back to 201
ctx.JSON(http.StatusAccepted, fork.APIFormat(models.AccessModeOwner))
} }

+ 16
- 10
routers/api/v1/repo/git_hook.go View File

package repo package repo


import ( import (
"net/http"

"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/GitHookList" // "$ref": "#/responses/GitHookList"

hooks, err := ctx.Repo.GitRepo.Hooks() hooks, err := ctx.Repo.GitRepo.Hooks()
if err != nil { if err != nil {
ctx.Error(500, "Hooks", err)
ctx.Error(http.StatusInternalServerError, "Hooks", err)
return return
} }


for i := range hooks { for i := range hooks {
apiHooks[i] = convert.ToGitHook(hooks[i]) apiHooks[i] = convert.ToGitHook(hooks[i])
} }
ctx.JSON(200, &apiHooks)
ctx.JSON(http.StatusOK, &apiHooks)
} }


// GetGitHook get a repo's Git hook by id // GetGitHook get a repo's Git hook by id
// "$ref": "#/responses/GitHook" // "$ref": "#/responses/GitHook"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"

hookID := ctx.Params(":id") hookID := ctx.Params(":id")
hook, err := ctx.Repo.GitRepo.GetHook(hookID) hook, err := ctx.Repo.GitRepo.GetHook(hookID)
if err != nil { if err != nil {
if err == git.ErrNotValidHook { if err == git.ErrNotValidHook {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetHook", err)
ctx.Error(http.StatusInternalServerError, "GetHook", err)
} }
return return
} }
ctx.JSON(200, convert.ToGitHook(hook))
ctx.JSON(http.StatusOK, convert.ToGitHook(hook))
} }


// EditGitHook modify a Git hook of a repository // EditGitHook modify a Git hook of a repository
// "$ref": "#/responses/GitHook" // "$ref": "#/responses/GitHook"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"

hookID := ctx.Params(":id") hookID := ctx.Params(":id")
hook, err := ctx.Repo.GitRepo.GetHook(hookID) hook, err := ctx.Repo.GitRepo.GetHook(hookID)
if err != nil { if err != nil {
if err == git.ErrNotValidHook { if err == git.ErrNotValidHook {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetHook", err)
ctx.Error(http.StatusInternalServerError, "GetHook", err)
} }
return return
} }


hook.Content = form.Content hook.Content = form.Content
if err = hook.Update(); err != nil { if err = hook.Update(); err != nil {
ctx.Error(500, "hook.Update", err)
ctx.Error(http.StatusInternalServerError, "hook.Update", err)
return return
} }


ctx.JSON(200, convert.ToGitHook(hook))
ctx.JSON(http.StatusOK, convert.ToGitHook(hook))
} }


// DeleteGitHook delete a Git hook of a repository // DeleteGitHook delete a Git hook of a repository
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"

hookID := ctx.Params(":id") hookID := ctx.Params(":id")
hook, err := ctx.Repo.GitRepo.GetHook(hookID) hook, err := ctx.Repo.GitRepo.GetHook(hookID)
if err != nil { if err != nil {
if err == git.ErrNotValidHook { if err == git.ErrNotValidHook {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetHook", err)
ctx.Error(http.StatusInternalServerError, "GetHook", err)
} }
return return
} }


hook.Content = "" hook.Content = ""
if err = hook.Update(); err != nil { if err = hook.Update(); err != nil {
ctx.Error(500, "hook.Update", err)
ctx.Error(http.StatusInternalServerError, "hook.Update", err)
return return
} }


ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 5
- 3
routers/api/v1/repo/git_ref.go View File

package repo package repo


import ( import (
"net/http"

"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
func getGitRefsInternal(ctx *context.APIContext, filter string) { func getGitRefsInternal(ctx *context.APIContext, filter string) {
refs, lastMethodName, err := getGitRefs(ctx, filter) refs, lastMethodName, err := getGitRefs(ctx, filter)
if err != nil { if err != nil {
ctx.Error(500, lastMethodName, err)
ctx.Error(http.StatusInternalServerError, lastMethodName, err)
return return
} }


} }
// If single reference is found and it matches filter exactly return it as object // If single reference is found and it matches filter exactly return it as object
if len(apiRefs) == 1 && apiRefs[0].Ref == filter { if len(apiRefs) == 1 && apiRefs[0].Ref == filter {
ctx.JSON(200, &apiRefs[0])
ctx.JSON(http.StatusOK, &apiRefs[0])
return return
} }
ctx.JSON(200, &apiRefs)
ctx.JSON(http.StatusOK, &apiRefs)
} }

+ 15
- 8
routers/api/v1/repo/hook.go View File

package repo package repo


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/HookList" // "$ref": "#/responses/HookList"

hooks, err := models.GetWebhooksByRepoID(ctx.Repo.Repository.ID) hooks, err := models.GetWebhooksByRepoID(ctx.Repo.Repository.ID)
if err != nil { if err != nil {
ctx.Error(500, "GetWebhooksByRepoID", err)
ctx.Error(http.StatusInternalServerError, "GetWebhooksByRepoID", err)
return return
} }


for i := range hooks { for i := range hooks {
apiHooks[i] = convert.ToHook(ctx.Repo.RepoLink, hooks[i]) apiHooks[i] = convert.ToHook(ctx.Repo.RepoLink, hooks[i])
} }
ctx.JSON(200, &apiHooks)
ctx.JSON(http.StatusOK, &apiHooks)
} }


// GetHook get a repo's hook by id // GetHook get a repo's hook by id
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Hook" // "$ref": "#/responses/Hook"
// "404":
// "$ref": "#/responses/notFound"

repo := ctx.Repo repo := ctx.Repo
hookID := ctx.ParamsInt64(":id") hookID := ctx.ParamsInt64(":id")
hook, err := utils.GetRepoHook(ctx, repo.Repository.ID, hookID) hook, err := utils.GetRepoHook(ctx, repo.Repository.ID, hookID)
if err != nil { if err != nil {
return return
} }
ctx.JSON(200, convert.ToHook(repo.RepoLink, hook))
ctx.JSON(http.StatusOK, convert.ToHook(repo.RepoLink, hook))
} }


// TestHook tests a hook // TestHook tests a hook
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"

if ctx.Repo.Commit == nil { if ctx.Repo.Commit == nil {
// if repo does not have any commits, then don't send a webhook // if repo does not have any commits, then don't send a webhook
ctx.Status(204)
ctx.Status(http.StatusNoContent)
return return
} }


Pusher: convert.ToUser(ctx.User, ctx.IsSigned, false), Pusher: convert.ToUser(ctx.User, ctx.IsSigned, false),
Sender: convert.ToUser(ctx.User, ctx.IsSigned, false), Sender: convert.ToUser(ctx.User, ctx.IsSigned, false),
}); err != nil { }); err != nil {
ctx.Error(500, "PrepareWebhook: ", err)
ctx.Error(http.StatusInternalServerError, "PrepareWebhook: ", err)
return return
} }


ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


// CreateHook create a hook for a repository // CreateHook create a hook for a repository
if models.IsErrWebhookNotExist(err) { if models.IsErrWebhookNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "DeleteWebhookByRepoID", err)
ctx.Error(http.StatusInternalServerError, "DeleteWebhookByRepoID", err)
} }
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 53
- 35
routers/api/v1/repo/issue.go View File

// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/IssueList" // "$ref": "#/responses/IssueList"

var isClosed util.OptionalBool var isClosed util.OptionalBool
switch ctx.Query("state") { switch ctx.Query("state") {
case "closed": case "closed":
OrderBy: models.SearchOrderByRecentUpdated, OrderBy: models.SearchOrderByRecentUpdated,
}) })
if err != nil { if err != nil {
ctx.Error(500, "SearchRepositoryByName", err)
ctx.Error(http.StatusInternalServerError, "SearchRepositoryByName", err)
return return
} }


if splitted := strings.Split(labels, ","); labels != "" && len(splitted) > 0 { if splitted := strings.Split(labels, ","); labels != "" && len(splitted) > 0 {
labelIDs, err = models.GetLabelIDsInReposByNames(repoIDs, splitted) labelIDs, err = models.GetLabelIDsInReposByNames(repoIDs, splitted)
if err != nil { if err != nil {
ctx.Error(500, "GetLabelIDsInRepoByNames", err)
ctx.Error(http.StatusInternalServerError, "GetLabelIDsInRepoByNames", err)
return return
} }
} }
} }


if err != nil { if err != nil {
ctx.Error(500, "Issues", err)
ctx.Error(http.StatusInternalServerError, "Issues", err)
return return
} }


} }


ctx.SetLinkHeader(issueCount, setting.UI.IssuePagingNum) ctx.SetLinkHeader(issueCount, setting.UI.IssuePagingNum)
ctx.JSON(200, &apiIssues)
ctx.JSON(http.StatusOK, &apiIssues)
} }


// ListIssues list the issues of a repository // ListIssues list the issues of a repository
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/IssueList" // "$ref": "#/responses/IssueList"

var isClosed util.OptionalBool var isClosed util.OptionalBool
switch ctx.Query("state") { switch ctx.Query("state") {
case "closed": case "closed":
if splitted := strings.Split(ctx.Query("labels"), ","); len(splitted) > 0 { if splitted := strings.Split(ctx.Query("labels"), ","); len(splitted) > 0 {
labelIDs, err = models.GetLabelIDsInRepoByNames(ctx.Repo.Repository.ID, splitted) labelIDs, err = models.GetLabelIDsInRepoByNames(ctx.Repo.Repository.ID, splitted)
if err != nil { if err != nil {
ctx.Error(500, "GetLabelIDsInRepoByNames", err)
ctx.Error(http.StatusInternalServerError, "GetLabelIDsInRepoByNames", err)
return return
} }
} }
} }


if err != nil { if err != nil {
ctx.Error(500, "Issues", err)
ctx.Error(http.StatusInternalServerError, "Issues", err)
return return
} }


} }


ctx.SetLinkHeader(ctx.Repo.Repository.NumIssues, setting.UI.IssuePagingNum) ctx.SetLinkHeader(ctx.Repo.Repository.NumIssues, setting.UI.IssuePagingNum)
ctx.JSON(200, &apiIssues)
ctx.JSON(http.StatusOK, &apiIssues)
} }


// GetIssue get an issue of a repository // GetIssue get an issue of a repository
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Issue" // "$ref": "#/responses/Issue"
// "404":
// "$ref": "#/responses/notFound"

issue, err := models.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
} }
return return
} }
ctx.JSON(200, issue.APIFormat())
ctx.JSON(http.StatusOK, issue.APIFormat())
} }


// CreateIssue create an issue of a repository // CreateIssue create an issue of a repository
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/Issue" // "$ref": "#/responses/Issue"
// "403":
// "$ref": "#/responses/forbidden"
// "412":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"


var deadlineUnix timeutil.TimeStamp var deadlineUnix timeutil.TimeStamp
if form.Deadline != nil && ctx.Repo.CanWrite(models.UnitTypeIssues) { if form.Deadline != nil && ctx.Repo.CanWrite(models.UnitTypeIssues) {
assigneeIDs, err = models.MakeIDsFromAPIAssigneesToAdd(form.Assignee, form.Assignees) assigneeIDs, err = models.MakeIDsFromAPIAssigneesToAdd(form.Assignee, form.Assignees)
if err != nil { if err != nil {
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
} else { } else {
ctx.Error(500, "AddAssigneeByName", err)
ctx.Error(http.StatusInternalServerError, "AddAssigneeByName", err)
} }
return return
} }
for _, aID := range assigneeIDs { for _, aID := range assigneeIDs {
assignee, err := models.GetUserByID(aID) assignee, err := models.GetUserByID(aID)
if err != nil { if err != nil {
ctx.Error(500, "GetUserByID", err)
ctx.Error(http.StatusInternalServerError, "GetUserByID", err)
return return
} }


valid, err := models.CanBeAssigned(assignee, ctx.Repo.Repository, false) valid, err := models.CanBeAssigned(assignee, ctx.Repo.Repository, false)
if err != nil { if err != nil {
ctx.Error(500, "canBeAssigned", err)
ctx.Error(http.StatusInternalServerError, "canBeAssigned", err)
return return
} }
if !valid { if !valid {
ctx.Error(422, "canBeAssigned", models.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: ctx.Repo.Repository.Name})
ctx.Error(http.StatusUnprocessableEntity, "canBeAssigned", models.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: ctx.Repo.Repository.Name})
return return
} }
} }


if err := issue_service.NewIssue(ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs); err != nil { if err := issue_service.NewIssue(ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs); err != nil {
if models.IsErrUserDoesNotHaveAccessToRepo(err) { if models.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(400, "UserDoesNotHaveAccessToRepo", err)
ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err)
return return
} }
ctx.Error(500, "NewIssue", err)
ctx.Error(http.StatusInternalServerError, "NewIssue", err)
return return
} }


ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies") ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
return return
} }
ctx.Error(500, "ChangeStatus", err)
ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
return return
} }
} }
// Refetch from database to assign some automatic values // Refetch from database to assign some automatic values
issue, err = models.GetIssueByID(issue.ID) issue, err = models.GetIssueByID(issue.ID)
if err != nil { if err != nil {
ctx.Error(500, "GetIssueByID", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByID", err)
return return
} }
ctx.JSON(201, issue.APIFormat())
ctx.JSON(http.StatusCreated, issue.APIFormat())
} }


// EditIssue modify an issue of a repository // EditIssue modify an issue of a repository
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/Issue" // "$ref": "#/responses/Issue"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "412":
// "$ref": "#/responses/error"

issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
} }
return return
} }


err = issue.LoadAttributes() err = issue.LoadAttributes()
if err != nil { if err != nil {
ctx.Error(500, "LoadAttributes", err)
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return return
} }


if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypeIssues) { if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypeIssues) {
ctx.Status(403)
ctx.Status(http.StatusForbidden)
return return
} }


} }


if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil { if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil {
ctx.Error(500, "UpdateIssueDeadline", err)
ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err)
return return
} }
issue.DeadlineUnix = deadlineUnix issue.DeadlineUnix = deadlineUnix


err = issue_service.UpdateAssignees(issue, oneAssignee, form.Assignees, ctx.User) err = issue_service.UpdateAssignees(issue, oneAssignee, form.Assignees, ctx.User)
if err != nil { if err != nil {
ctx.Error(500, "UpdateAssignees", err)
ctx.Error(http.StatusInternalServerError, "UpdateAssignees", err)
return return
} }
} }
oldMilestoneID := issue.MilestoneID oldMilestoneID := issue.MilestoneID
issue.MilestoneID = *form.Milestone issue.MilestoneID = *form.Milestone
if err = issue_service.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil { if err = issue_service.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil {
ctx.Error(500, "ChangeMilestoneAssign", err)
ctx.Error(http.StatusInternalServerError, "ChangeMilestoneAssign", err)
return return
} }
} }


if err = models.UpdateIssue(issue); err != nil { if err = models.UpdateIssue(issue); err != nil {
ctx.Error(500, "UpdateIssue", err)
ctx.Error(http.StatusInternalServerError, "UpdateIssue", err)
return return
} }
if form.State != nil { if form.State != nil {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies") ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
return return
} }
ctx.Error(500, "ChangeStatus", err)
ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
return return
} }
} }
// Refetch from database to assign some automatic values // Refetch from database to assign some automatic values
issue, err = models.GetIssueByID(issue.ID) issue, err = models.GetIssueByID(issue.ID)
if err != nil { if err != nil {
ctx.Error(500, "GetIssueByID", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByID", err)
return return
} }
ctx.JSON(201, issue.APIFormat())
ctx.JSON(http.StatusCreated, issue.APIFormat())
} }


// UpdateIssueDeadline updates an issue deadline // UpdateIssueDeadline updates an issue deadline
// "201": // "201":
// "$ref": "#/responses/IssueDeadline" // "$ref": "#/responses/IssueDeadline"
// "403": // "403":
// description: Not repo writer
// "$ref": "#/responses/forbidden"
// "404": // "404":
// description: Issue not found
// "$ref": "#/responses/notFound"


issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
} }
return return
} }


if !ctx.Repo.CanWrite(models.UnitTypeIssues) { if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
ctx.Status(403)
ctx.Error(http.StatusForbidden, "", "Not repo writer")
return return
} }


} }


if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil { if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil {
ctx.Error(500, "UpdateIssueDeadline", err)
ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err)
return return
} }


ctx.JSON(201, api.IssueDeadline{Deadline: &deadline})
ctx.JSON(http.StatusCreated, api.IssueDeadline{Deadline: &deadline})
} }

+ 41
- 24
routers/api/v1/repo/issue_comment.go View File



import ( import (
"errors" "errors"
"net/http"
"time" "time"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/CommentList" // "$ref": "#/responses/CommentList"

var since time.Time var since time.Time
if len(ctx.Query("since")) > 0 { if len(ctx.Query("since")) > 0 {
since, _ = time.Parse(time.RFC3339, ctx.Query("since")) since, _ = time.Parse(time.RFC3339, ctx.Query("since"))
// comments,err:=models.GetCommentsByIssueIDSince(, since) // comments,err:=models.GetCommentsByIssueIDSince(, since)
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
ctx.Error(500, "GetRawIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
return return
} }
issue.Repo = ctx.Repo.Repository issue.Repo = ctx.Repo.Repository
Type: models.CommentTypeComment, Type: models.CommentTypeComment,
}) })
if err != nil { if err != nil {
ctx.Error(500, "FindComments", err)
ctx.Error(http.StatusInternalServerError, "FindComments", err)
return return
} }


if err := models.CommentList(comments).LoadPosters(); err != nil { if err := models.CommentList(comments).LoadPosters(); err != nil {
ctx.Error(500, "LoadPosters", err)
ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
return return
} }


comment.Issue = issue comment.Issue = issue
apiComments[i] = comments[i].APIFormat() apiComments[i] = comments[i].APIFormat()
} }
ctx.JSON(200, &apiComments)
ctx.JSON(http.StatusOK, &apiComments)
} }


// ListRepoIssueComments returns all issue-comments for a repo // ListRepoIssueComments returns all issue-comments for a repo
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/CommentList" // "$ref": "#/responses/CommentList"

var since time.Time var since time.Time
if len(ctx.Query("since")) > 0 { if len(ctx.Query("since")) > 0 {
since, _ = time.Parse(time.RFC3339, ctx.Query("since")) since, _ = time.Parse(time.RFC3339, ctx.Query("since"))
Type: models.CommentTypeComment, Type: models.CommentTypeComment,
}) })
if err != nil { if err != nil {
ctx.Error(500, "FindComments", err)
ctx.Error(http.StatusInternalServerError, "FindComments", err)
return return
} }


if err = models.CommentList(comments).LoadPosters(); err != nil { if err = models.CommentList(comments).LoadPosters(); err != nil {
ctx.Error(500, "LoadPosters", err)
ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
return return
} }


apiComments := make([]*api.Comment, len(comments)) apiComments := make([]*api.Comment, len(comments))
if err := models.CommentList(comments).LoadIssues(); err != nil { if err := models.CommentList(comments).LoadIssues(); err != nil {
ctx.Error(500, "LoadIssues", err)
ctx.Error(http.StatusInternalServerError, "LoadIssues", err)
return return
} }
if err := models.CommentList(comments).LoadPosters(); err != nil { if err := models.CommentList(comments).LoadPosters(); err != nil {
ctx.Error(500, "LoadPosters", err)
ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
return return
} }
if _, err := models.CommentList(comments).Issues().LoadRepositories(); err != nil { if _, err := models.CommentList(comments).Issues().LoadRepositories(); err != nil {
ctx.Error(500, "LoadRepositories", err)
ctx.Error(http.StatusInternalServerError, "LoadRepositories", err)
return return
} }
for i := range comments { for i := range comments {
apiComments[i] = comments[i].APIFormat() apiComments[i] = comments[i].APIFormat()
} }
ctx.JSON(200, &apiComments)
ctx.JSON(http.StatusOK, &apiComments)
} }


// CreateIssueComment create a comment for an issue // CreateIssueComment create a comment for an issue
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/Comment" // "$ref": "#/responses/Comment"
// "403":
// "$ref": "#/responses/forbidden"

issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
ctx.Error(500, "GetIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
return return
} }


if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin { if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
ctx.Error(403, "CreateIssueComment", errors.New(ctx.Tr("repo.issues.comment_on_locked")))
ctx.Error(http.StatusForbidden, "CreateIssueComment", errors.New(ctx.Tr("repo.issues.comment_on_locked")))
return return
} }


comment, err := comment_service.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Body, nil) comment, err := comment_service.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Body, nil)
if err != nil { if err != nil {
ctx.Error(500, "CreateIssueComment", err)
ctx.Error(http.StatusInternalServerError, "CreateIssueComment", err)
return return
} }


ctx.JSON(201, comment.APIFormat())
ctx.JSON(http.StatusCreated, comment.APIFormat())
} }


// EditIssueComment modify a comment of an issue // EditIssueComment modify a comment of an issue
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Comment" // "$ref": "#/responses/Comment"
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"

editIssueComment(ctx, form) editIssueComment(ctx, form)
} }


if models.IsErrCommentNotExist(err) { if models.IsErrCommentNotExist(err) {
ctx.NotFound(err) ctx.NotFound(err)
} else { } else {
ctx.Error(500, "GetCommentByID", err)
ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
} }
return return
} }


if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.IsAdmin()) { if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
ctx.Status(403)
ctx.Status(http.StatusForbidden)
return return
} else if comment.Type != models.CommentTypeComment { } else if comment.Type != models.CommentTypeComment {
ctx.Status(204)
ctx.Status(http.StatusNoContent)
return return
} }


oldContent := comment.Content oldContent := comment.Content
comment.Content = form.Body comment.Content = form.Body
if err := comment_service.UpdateComment(comment, ctx.User, oldContent); err != nil { if err := comment_service.UpdateComment(comment, ctx.User, oldContent); err != nil {
ctx.Error(500, "UpdateComment", err)
ctx.Error(http.StatusInternalServerError, "UpdateComment", err)
return return
} }


ctx.JSON(200, comment.APIFormat())
ctx.JSON(http.StatusOK, comment.APIFormat())
} }


// DeleteIssueComment delete a comment from an issue // DeleteIssueComment delete a comment from an issue
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"

deleteIssueComment(ctx) deleteIssueComment(ctx)
} }


// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"

deleteIssueComment(ctx) deleteIssueComment(ctx)
} }


if models.IsErrCommentNotExist(err) { if models.IsErrCommentNotExist(err) {
ctx.NotFound(err) ctx.NotFound(err)
} else { } else {
ctx.Error(500, "GetCommentByID", err)
ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
} }
return return
} }


if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.IsAdmin()) { if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
ctx.Status(403)
ctx.Status(http.StatusForbidden)
return return
} else if comment.Type != models.CommentTypeComment { } else if comment.Type != models.CommentTypeComment {
ctx.Status(204)
ctx.Status(http.StatusNoContent)
return return
} }


if err = comment_service.DeleteComment(comment, ctx.User); err != nil { if err = comment_service.DeleteComment(comment, ctx.User); err != nil {
ctx.Error(500, "DeleteCommentByID", err)
ctx.Error(http.StatusInternalServerError, "DeleteCommentByID", err)
return return
} }


ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 42
- 25
routers/api/v1/repo/issue_label.go View File

package repo package repo


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
// "$ref": "#/responses/LabelList" // "$ref": "#/responses/LabelList"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"

issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
} }
return return
} }


if err := issue.LoadAttributes(); err != nil { if err := issue.LoadAttributes(); err != nil {
ctx.Error(500, "LoadAttributes", err)
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return return
} }


for i := range issue.Labels { for i := range issue.Labels {
apiLabels[i] = issue.Labels[i].APIFormat() apiLabels[i] = issue.Labels[i].APIFormat()
} }
ctx.JSON(200, &apiLabels)
ctx.JSON(http.StatusOK, &apiLabels)
} }


// AddIssueLabels add labels for an issue // AddIssueLabels add labels for an issue
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/LabelList" // "$ref": "#/responses/LabelList"
// "403":
// "$ref": "#/responses/forbidden"

issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
} }
return return
} }


if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
ctx.Status(403)
ctx.Status(http.StatusForbidden)
return return
} }


labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels) labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels)
if err != nil { if err != nil {
ctx.Error(500, "GetLabelsInRepoByIDs", err)
ctx.Error(http.StatusInternalServerError, "GetLabelsInRepoByIDs", err)
return return
} }


if err = issue_service.AddLabels(issue, ctx.User, labels); err != nil { if err = issue_service.AddLabels(issue, ctx.User, labels); err != nil {
ctx.Error(500, "AddLabels", err)
ctx.Error(http.StatusInternalServerError, "AddLabels", err)
return return
} }


labels, err = models.GetLabelsByIssueID(issue.ID) labels, err = models.GetLabelsByIssueID(issue.ID)
if err != nil { if err != nil {
ctx.Error(500, "GetLabelsByIssueID", err)
ctx.Error(http.StatusInternalServerError, "GetLabelsByIssueID", err)
return return
} }


for i := range labels { for i := range labels {
apiLabels[i] = labels[i].APIFormat() apiLabels[i] = labels[i].APIFormat()
} }
ctx.JSON(200, &apiLabels)
ctx.JSON(http.StatusOK, &apiLabels)
} }


// DeleteIssueLabel delete a label for an issue // DeleteIssueLabel delete a label for an issue
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"

issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
} }
return return
} }


if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
ctx.Status(403)
ctx.Status(http.StatusForbidden)
return return
} }


label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrLabelNotExist(err) { if models.IsErrLabelNotExist(err) {
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(500, "GetLabelInRepoByID", err)
ctx.Error(http.StatusInternalServerError, "GetLabelInRepoByID", err)
} }
return return
} }


if err := models.DeleteIssueLabel(issue, label, ctx.User); err != nil { if err := models.DeleteIssueLabel(issue, label, ctx.User); err != nil {
ctx.Error(500, "DeleteIssueLabel", err)
ctx.Error(http.StatusInternalServerError, "DeleteIssueLabel", err)
return return
} }


ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


// ReplaceIssueLabels replace labels for an issue // ReplaceIssueLabels replace labels for an issue
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/LabelList" // "$ref": "#/responses/LabelList"
// "403":
// "$ref": "#/responses/forbidden"

issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
} }
return return
} }


if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
ctx.Status(403)
ctx.Status(http.StatusForbidden)
return return
} }


labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels) labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels)
if err != nil { if err != nil {
ctx.Error(500, "GetLabelsInRepoByIDs", err)
ctx.Error(http.StatusInternalServerError, "GetLabelsInRepoByIDs", err)
return return
} }


if err := issue.ReplaceLabels(labels, ctx.User); err != nil { if err := issue.ReplaceLabels(labels, ctx.User); err != nil {
ctx.Error(500, "ReplaceLabels", err)
ctx.Error(http.StatusInternalServerError, "ReplaceLabels", err)
return return
} }


labels, err = models.GetLabelsByIssueID(issue.ID) labels, err = models.GetLabelsByIssueID(issue.ID)
if err != nil { if err != nil {
ctx.Error(500, "GetLabelsByIssueID", err)
ctx.Error(http.StatusInternalServerError, "GetLabelsByIssueID", err)
return return
} }


for i := range labels { for i := range labels {
apiLabels[i] = labels[i].APIFormat() apiLabels[i] = labels[i].APIFormat()
} }
ctx.JSON(200, &apiLabels)
ctx.JSON(http.StatusOK, &apiLabels)
} }


// ClearIssueLabels delete all the labels for an issue // ClearIssueLabels delete all the labels for an issue
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"

issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
} }
return return
} }


if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
ctx.Status(403)
ctx.Status(http.StatusForbidden)
return return
} }


if err := issue_service.ClearLabels(issue, ctx.User); err != nil { if err := issue_service.ClearLabels(issue, ctx.User); err != nil {
ctx.Error(500, "ClearLabels", err)
ctx.Error(http.StatusInternalServerError, "ClearLabels", err)
return return
} }


ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 48
- 27
routers/api/v1/repo/issue_reaction.go View File



import ( import (
"errors" "errors"
"net/http"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/ReactionResponseList" // "$ref": "#/responses/ReactionResponseList"
// "403":
// "$ref": "#/responses/forbidden"

comment, err := models.GetCommentByID(ctx.ParamsInt64(":id")) comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrCommentNotExist(err) { if models.IsErrCommentNotExist(err) {
ctx.NotFound(err) ctx.NotFound(err)
} else { } else {
ctx.Error(500, "GetCommentByID", err)
ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
} }
return return
} }


if !ctx.Repo.CanRead(models.UnitTypeIssues) && !ctx.User.IsAdmin { if !ctx.Repo.CanRead(models.UnitTypeIssues) && !ctx.User.IsAdmin {
ctx.Error(403, "GetIssueCommentReactions", errors.New("no permission to get reactions"))
ctx.Error(http.StatusForbidden, "GetIssueCommentReactions", errors.New("no permission to get reactions"))
return return
} }


reactions, err := models.FindCommentReactions(comment) reactions, err := models.FindCommentReactions(comment)
if err != nil { if err != nil {
ctx.Error(500, "FindIssueReactions", err)
ctx.Error(http.StatusInternalServerError, "FindIssueReactions", err)
return return
} }
_, err = reactions.LoadUsers() _, err = reactions.LoadUsers()
if err != nil { if err != nil {
ctx.Error(500, "ReactionList.LoadUsers()", err)
ctx.Error(http.StatusInternalServerError, "ReactionList.LoadUsers()", err)
return return
} }


}) })
} }


ctx.JSON(200, result)
ctx.JSON(http.StatusOK, result)
} }


// PostIssueCommentReaction add a reaction to a comment of a issue // PostIssueCommentReaction add a reaction to a comment of a issue
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/ReactionResponse" // "$ref": "#/responses/ReactionResponse"
// "403":
// "$ref": "#/responses/forbidden"

changeIssueCommentReaction(ctx, form, true) changeIssueCommentReaction(ctx, form, true)
} }


// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"

changeIssueCommentReaction(ctx, form, false) changeIssueCommentReaction(ctx, form, false)
} }


if models.IsErrCommentNotExist(err) { if models.IsErrCommentNotExist(err) {
ctx.NotFound(err) ctx.NotFound(err)
} else { } else {
ctx.Error(500, "GetCommentByID", err)
ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
} }
return return
} }


err = comment.LoadIssue() err = comment.LoadIssue()
if err != nil { if err != nil {
ctx.Error(500, "comment.LoadIssue() failed", err)
ctx.Error(http.StatusInternalServerError, "comment.LoadIssue() failed", err)
} }


if comment.Issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin { if comment.Issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
ctx.Error(403, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
ctx.Error(http.StatusForbidden, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
return return
} }


reaction, err := models.CreateCommentReaction(ctx.User, comment.Issue, comment, form.Reaction) reaction, err := models.CreateCommentReaction(ctx.User, comment.Issue, comment, form.Reaction)
if err != nil { if err != nil {
if models.IsErrForbiddenIssueReaction(err) { if models.IsErrForbiddenIssueReaction(err) {
ctx.Error(403, err.Error(), err)
ctx.Error(http.StatusForbidden, err.Error(), err)
} else { } else {
ctx.Error(500, "CreateCommentReaction", err)
ctx.Error(http.StatusInternalServerError, "CreateCommentReaction", err)
} }
return return
} }
_, err = reaction.LoadUser() _, err = reaction.LoadUser()
if err != nil { if err != nil {
ctx.Error(500, "Reaction.LoadUser()", err)
ctx.Error(http.StatusInternalServerError, "Reaction.LoadUser()", err)
return return
} }


ctx.JSON(201, api.ReactionResponse{
ctx.JSON(http.StatusCreated, api.ReactionResponse{
User: reaction.User.APIFormat(), User: reaction.User.APIFormat(),
Reaction: reaction.Type, Reaction: reaction.Type,
Created: reaction.CreatedUnix.AsTime(), Created: reaction.CreatedUnix.AsTime(),
// DeleteIssueCommentReaction part // DeleteIssueCommentReaction part
err = models.DeleteCommentReaction(ctx.User, comment.Issue, comment, form.Reaction) err = models.DeleteCommentReaction(ctx.User, comment.Issue, comment, form.Reaction)
if err != nil { if err != nil {
ctx.Error(500, "DeleteCommentReaction", err)
ctx.Error(http.StatusInternalServerError, "DeleteCommentReaction", err)
return return
} }
ctx.Status(200)
//ToDo respond 204
ctx.Status(http.StatusOK)
} }
} }


// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/ReactionResponseList" // "$ref": "#/responses/ReactionResponseList"
// "403":
// "$ref": "#/responses/forbidden"

issue, err := models.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
} }
return return
} }


if !ctx.Repo.CanRead(models.UnitTypeIssues) && !ctx.User.IsAdmin { if !ctx.Repo.CanRead(models.UnitTypeIssues) && !ctx.User.IsAdmin {
ctx.Error(403, "GetIssueReactions", errors.New("no permission to get reactions"))
ctx.Error(http.StatusForbidden, "GetIssueReactions", errors.New("no permission to get reactions"))
return return
} }


reactions, err := models.FindIssueReactions(issue) reactions, err := models.FindIssueReactions(issue)
if err != nil { if err != nil {
ctx.Error(500, "FindIssueReactions", err)
ctx.Error(http.StatusInternalServerError, "FindIssueReactions", err)
return return
} }
_, err = reactions.LoadUsers() _, err = reactions.LoadUsers()
if err != nil { if err != nil {
ctx.Error(500, "ReactionList.LoadUsers()", err)
ctx.Error(http.StatusInternalServerError, "ReactionList.LoadUsers()", err)
return return
} }


}) })
} }


ctx.JSON(200, result)
ctx.JSON(http.StatusOK, result)
} }


// PostIssueReaction add a reaction to a comment of a issue // PostIssueReaction add a reaction to a comment of a issue
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/ReactionResponse" // "$ref": "#/responses/ReactionResponse"
// "403":
// "$ref": "#/responses/forbidden"

changeIssueReaction(ctx, form, true) changeIssueReaction(ctx, form, true)
} }


// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"

changeIssueReaction(ctx, form, false) changeIssueReaction(ctx, form, false)
} }


if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
} }
return return
} }


if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin { if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
ctx.Error(403, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
ctx.Error(http.StatusForbidden, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
return return
} }


reaction, err := models.CreateIssueReaction(ctx.User, issue, form.Reaction) reaction, err := models.CreateIssueReaction(ctx.User, issue, form.Reaction)
if err != nil { if err != nil {
if models.IsErrForbiddenIssueReaction(err) { if models.IsErrForbiddenIssueReaction(err) {
ctx.Error(403, err.Error(), err)
ctx.Error(http.StatusForbidden, err.Error(), err)
} else { } else {
ctx.Error(500, "CreateCommentReaction", err)
ctx.Error(http.StatusInternalServerError, "CreateCommentReaction", err)
} }
return return
} }
_, err = reaction.LoadUser() _, err = reaction.LoadUser()
if err != nil { if err != nil {
ctx.Error(500, "Reaction.LoadUser()", err)
ctx.Error(http.StatusInternalServerError, "Reaction.LoadUser()", err)
return return
} }


ctx.JSON(201, api.ReactionResponse{
ctx.JSON(http.StatusCreated, api.ReactionResponse{
User: reaction.User.APIFormat(), User: reaction.User.APIFormat(),
Reaction: reaction.Type, Reaction: reaction.Type,
Created: reaction.CreatedUnix.AsTime(), Created: reaction.CreatedUnix.AsTime(),
// DeleteIssueReaction part // DeleteIssueReaction part
err = models.DeleteIssueReaction(ctx.User, issue, form.Reaction) err = models.DeleteIssueReaction(ctx.User, issue, form.Reaction)
if err != nil { if err != nil {
ctx.Error(500, "DeleteIssueReaction", err)
ctx.Error(http.StatusInternalServerError, "DeleteIssueReaction", err)
return return
} }
ctx.Status(200)
//ToDo respond 204
ctx.Status(http.StatusOK)
} }
} }

+ 22
- 17
routers/api/v1/repo/issue_stopwatch.go View File

package repo package repo


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
) )
// "403": // "403":
// description: Not repo writer, user does not have rights to toggle stopwatch // description: Not repo writer, user does not have rights to toggle stopwatch
// "404": // "404":
// description: Issue not found
// "$ref": "#/responses/notFound"
// "409": // "409":
// description: Cannot start a stopwatch again if it already exists // description: Cannot start a stopwatch again if it already exists

issue, err := prepareIssueStopwatch(ctx, false) issue, err := prepareIssueStopwatch(ctx, false)
if err != nil { if err != nil {
return return
} }


if err := models.CreateOrStopIssueStopwatch(ctx.User, issue); err != nil { if err := models.CreateOrStopIssueStopwatch(ctx.User, issue); err != nil {
ctx.Error(500, "CreateOrStopIssueStopwatch", err)
ctx.Error(http.StatusInternalServerError, "CreateOrStopIssueStopwatch", err)
return return
} }


ctx.Status(201)
ctx.Status(http.StatusCreated)
} }


// StopIssueStopwatch stops a stopwatch for the given issue. // StopIssueStopwatch stops a stopwatch for the given issue.
// "403": // "403":
// description: Not repo writer, user does not have rights to toggle stopwatch // description: Not repo writer, user does not have rights to toggle stopwatch
// "404": // "404":
// description: Issue not found
// "$ref": "#/responses/notFound"
// "409": // "409":
// description: Cannot stop a non existent stopwatch // description: Cannot stop a non existent stopwatch

issue, err := prepareIssueStopwatch(ctx, true) issue, err := prepareIssueStopwatch(ctx, true)
if err != nil { if err != nil {
return return
} }


if err := models.CreateOrStopIssueStopwatch(ctx.User, issue); err != nil { if err := models.CreateOrStopIssueStopwatch(ctx.User, issue); err != nil {
ctx.Error(500, "CreateOrStopIssueStopwatch", err)
ctx.Error(http.StatusInternalServerError, "CreateOrStopIssueStopwatch", err)
return return
} }


ctx.Status(201)
ctx.Status(http.StatusCreated)
} }


// DeleteIssueStopwatch delete a specific stopwatch // DeleteIssueStopwatch delete a specific stopwatch
// "403": // "403":
// description: Not repo writer, user does not have rights to toggle stopwatch // description: Not repo writer, user does not have rights to toggle stopwatch
// "404": // "404":
// description: Issue not found
// "$ref": "#/responses/notFound"
// "409": // "409":
// description: Cannot cancel a non existent stopwatch // description: Cannot cancel a non existent stopwatch

issue, err := prepareIssueStopwatch(ctx, true) issue, err := prepareIssueStopwatch(ctx, true)
if err != nil { if err != nil {
return return
} }


if err := models.CancelStopwatch(ctx.User, issue); err != nil { if err := models.CancelStopwatch(ctx.User, issue); err != nil {
ctx.Error(500, "CancelStopwatch", err)
ctx.Error(http.StatusInternalServerError, "CancelStopwatch", err)
return return
} }


ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


func prepareIssueStopwatch(ctx *context.APIContext, shouldExist bool) (*models.Issue, error) { func prepareIssueStopwatch(ctx *context.APIContext, shouldExist bool) (*models.Issue, error) {
if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
} }


return nil, err return nil, err
} }


if !ctx.Repo.CanWrite(models.UnitTypeIssues) { if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
ctx.Status(403)
ctx.Status(http.StatusForbidden)
return nil, err return nil, err
} }


if !ctx.Repo.CanUseTimetracker(issue, ctx.User) { if !ctx.Repo.CanUseTimetracker(issue, ctx.User) {
ctx.Status(403)
ctx.Status(http.StatusForbidden)
return nil, err return nil, err
} }


if models.StopwatchExists(ctx.User.ID, issue.ID) != shouldExist { if models.StopwatchExists(ctx.User.ID, issue.ID) != shouldExist {
if shouldExist { if shouldExist {
ctx.Error(409, "StopwatchExists", "cannot stop/cancel a non existent stopwatch")
ctx.Error(http.StatusConflict, "StopwatchExists", "cannot stop/cancel a non existent stopwatch")
} else { } else {
ctx.Error(409, "StopwatchExists", "cannot start a stopwatch again if it already exists")
ctx.Error(http.StatusConflict, "StopwatchExists", "cannot start a stopwatch again if it already exists")
} }
return nil, err return nil, err
} }


sws, err := models.GetUserStopwatches(ctx.User.ID) sws, err := models.GetUserStopwatches(ctx.User.ID)
if err != nil { if err != nil {
ctx.Error(500, "GetUserStopwatches", err)
ctx.Error(http.StatusInternalServerError, "GetUserStopwatches", err)
return return
} }


apiSWs, err := sws.APIFormat() apiSWs, err := sws.APIFormat()
if err != nil { if err != nil {
ctx.Error(500, "APIFormat", err)
ctx.Error(http.StatusInternalServerError, "APIFormat", err)
return return
} }


ctx.JSON(200, apiSWs)
ctx.JSON(http.StatusOK, apiSWs)
} }

+ 17
- 12
routers/api/v1/repo/issue_subscription.go View File

package repo package repo


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
) )
// "304": // "304":
// description: User can only subscribe itself if he is no admin // description: User can only subscribe itself if he is no admin
// "404": // "404":
// description: Issue not found
// "$ref": "#/responses/notFound"

setIssueSubscription(ctx, true) setIssueSubscription(ctx, true)
} }


// "304": // "304":
// description: User can only subscribe itself if he is no admin // description: User can only subscribe itself if he is no admin
// "404": // "404":
// description: Issue not found
// "$ref": "#/responses/notFound"

setIssueSubscription(ctx, false) setIssueSubscription(ctx, false)
} }


if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
} }


return return
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetUserByName", err)
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
} }


return return


//only admin and user for itself can change subscription //only admin and user for itself can change subscription
if user.ID != ctx.User.ID && !ctx.User.IsAdmin { if user.ID != ctx.User.ID && !ctx.User.IsAdmin {
ctx.Error(403, "User", nil)
ctx.Error(http.StatusForbidden, "User", nil)
return return
} }


if err := models.CreateOrUpdateIssueWatch(user.ID, issue.ID, watch); err != nil { if err := models.CreateOrUpdateIssueWatch(user.ID, issue.ID, watch); err != nil {
ctx.Error(500, "CreateOrUpdateIssueWatch", err)
ctx.Error(http.StatusInternalServerError, "CreateOrUpdateIssueWatch", err)
return return
} }


ctx.Status(201)
ctx.Status(http.StatusCreated)
} }


// GetIssueSubscribers return subscribers of an issue // GetIssueSubscribers return subscribers of an issue
// "200": // "200":
// "$ref": "#/responses/UserList" // "$ref": "#/responses/UserList"
// "404": // "404":
// description: Issue not found
// "$ref": "#/responses/notFound"

issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
} }


return return


iwl, err := models.GetIssueWatchers(issue.ID) iwl, err := models.GetIssueWatchers(issue.ID)
if err != nil { if err != nil {
ctx.Error(500, "GetIssueWatchers", err)
ctx.Error(http.StatusInternalServerError, "GetIssueWatchers", err)
return return
} }


users, err := iwl.LoadWatchUsers() users, err := iwl.LoadWatchUsers()
if err != nil { if err != nil {
ctx.Error(500, "LoadWatchUsers", err)
ctx.Error(http.StatusInternalServerError, "LoadWatchUsers", err)
return return
} }


ctx.JSON(200, users.APIFormat())
ctx.JSON(http.StatusOK, users.APIFormat())
} }

+ 31
- 18
routers/api/v1/repo/issue_tracked_time.go View File

package repo package repo


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/TrackedTimeList" // "$ref": "#/responses/TrackedTimeList"
// "404":
// "$ref": "#/responses/notFound"

if !ctx.Repo.Repository.IsTimetrackerEnabled() { if !ctx.Repo.Repository.IsTimetrackerEnabled() {
ctx.NotFound("Timetracker is disabled") ctx.NotFound("Timetracker is disabled")
return return
if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {
ctx.NotFound(err) ctx.NotFound(err)
} else { } else {
ctx.Error(500, "GetIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
} }
return return
} }


trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{IssueID: issue.ID}) trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{IssueID: issue.ID})
if err != nil { if err != nil {
ctx.Error(500, "GetTrackedTimesByIssue", err)
ctx.Error(http.StatusInternalServerError, "GetTrackedTimesByIssue", err)
return return
} }
apiTrackedTimes := trackedTimesToAPIFormat(trackedTimes) apiTrackedTimes := trackedTimesToAPIFormat(trackedTimes)
ctx.JSON(200, &apiTrackedTimes)
ctx.JSON(http.StatusOK, &apiTrackedTimes)
} }


// AddTime adds time manual to the given issue // AddTime adds time manual to the given issue
// "400": // "400":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "403": // "403":
// "$ref": "#/responses/error"
// "$ref": "#/responses/forbidden"

issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {
ctx.NotFound(err) ctx.NotFound(err)
} else { } else {
ctx.Error(500, "GetIssueByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
} }
return return
} }


if !ctx.Repo.CanUseTimetracker(issue, ctx.User) { if !ctx.Repo.CanUseTimetracker(issue, ctx.User) {
if !ctx.Repo.Repository.IsTimetrackerEnabled() { if !ctx.Repo.Repository.IsTimetrackerEnabled() {
ctx.JSON(400, struct{ Message string }{Message: "time tracking disabled"})
ctx.Error(http.StatusBadRequest, "", "time tracking disabled")
return return
} }
ctx.Status(403)
ctx.Status(http.StatusForbidden)
return return
} }
trackedTime, err := models.AddTime(ctx.User, issue, form.Time) trackedTime, err := models.AddTime(ctx.User, issue, form.Time)
if err != nil { if err != nil {
ctx.Error(500, "AddTime", err)
ctx.Error(http.StatusInternalServerError, "AddTime", err)
return return
} }
ctx.JSON(200, trackedTime.APIFormat())
ctx.JSON(http.StatusOK, trackedTime.APIFormat())
} }


// ListTrackedTimesByUser lists all tracked times of the user // ListTrackedTimesByUser lists all tracked times of the user
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/TrackedTimeList" // "$ref": "#/responses/TrackedTimeList"
// "400":
// "$ref": "#/responses/error"

if !ctx.Repo.Repository.IsTimetrackerEnabled() { if !ctx.Repo.Repository.IsTimetrackerEnabled() {
ctx.JSON(400, struct{ Message string }{Message: "time tracking disabled"})
ctx.Error(http.StatusBadRequest, "", "time tracking disabled")
return return
} }
user, err := models.GetUserByName(ctx.Params(":timetrackingusername")) user, err := models.GetUserByName(ctx.Params(":timetrackingusername"))
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.NotFound(err) ctx.NotFound(err)
} else { } else {
ctx.Error(500, "GetUserByName", err)
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
} }
return return
} }
UserID: user.ID, UserID: user.ID,
RepositoryID: ctx.Repo.Repository.ID}) RepositoryID: ctx.Repo.Repository.ID})
if err != nil { if err != nil {
ctx.Error(500, "GetTrackedTimesByUser", err)
ctx.Error(http.StatusInternalServerError, "GetTrackedTimesByUser", err)
return return
} }
apiTrackedTimes := trackedTimesToAPIFormat(trackedTimes) apiTrackedTimes := trackedTimesToAPIFormat(trackedTimes)
ctx.JSON(200, &apiTrackedTimes)
ctx.JSON(http.StatusOK, &apiTrackedTimes)
} }


// ListTrackedTimesByRepository lists all tracked times of the repository // ListTrackedTimesByRepository lists all tracked times of the repository
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/TrackedTimeList" // "$ref": "#/responses/TrackedTimeList"
// "400":
// "$ref": "#/responses/error"

if !ctx.Repo.Repository.IsTimetrackerEnabled() { if !ctx.Repo.Repository.IsTimetrackerEnabled() {
ctx.JSON(400, struct{ Message string }{Message: "time tracking disabled"})
ctx.Error(http.StatusBadRequest, "", "time tracking disabled")
return return
} }
trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{ trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{
RepositoryID: ctx.Repo.Repository.ID}) RepositoryID: ctx.Repo.Repository.ID})
if err != nil { if err != nil {
ctx.Error(500, "GetTrackedTimesByUser", err)
ctx.Error(http.StatusInternalServerError, "GetTrackedTimesByUser", err)
return return
} }
apiTrackedTimes := trackedTimesToAPIFormat(trackedTimes) apiTrackedTimes := trackedTimesToAPIFormat(trackedTimes)
ctx.JSON(200, &apiTrackedTimes)
ctx.JSON(http.StatusOK, &apiTrackedTimes)
} }


// ListMyTrackedTimes lists all tracked times of the current user // ListMyTrackedTimes lists all tracked times of the current user
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/TrackedTimeList" // "$ref": "#/responses/TrackedTimeList"

trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{UserID: ctx.User.ID}) trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{UserID: ctx.User.ID})
if err != nil { if err != nil {
ctx.Error(500, "GetTrackedTimesByUser", err)
ctx.Error(http.StatusInternalServerError, "GetTrackedTimesByUser", err)
return return
} }
apiTrackedTimes := trackedTimesToAPIFormat(trackedTimes) apiTrackedTimes := trackedTimesToAPIFormat(trackedTimes)
ctx.JSON(200, &apiTrackedTimes)
ctx.JSON(http.StatusOK, &apiTrackedTimes)
} }

+ 26
- 17
routers/api/v1/repo/key.go View File



import ( import (
"fmt" "fmt"
"net/http"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/DeployKeyList" // "$ref": "#/responses/DeployKeyList"

var keys []*models.DeployKey var keys []*models.DeployKey
var err error var err error


} }


if err != nil { if err != nil {
ctx.Error(500, "ListDeployKeys", err)
ctx.Error(http.StatusInternalServerError, "ListDeployKeys", err)
return return
} }


apiKeys := make([]*api.DeployKey, len(keys)) apiKeys := make([]*api.DeployKey, len(keys))
for i := range keys { for i := range keys {
if err = keys[i].GetContent(); err != nil { if err = keys[i].GetContent(); err != nil {
ctx.Error(500, "GetContent", err)
ctx.Error(http.StatusInternalServerError, "GetContent", err)
return return
} }
apiKeys[i] = convert.ToDeployKey(apiLink, keys[i]) apiKeys[i] = convert.ToDeployKey(apiLink, keys[i])
} }
} }


ctx.JSON(200, &apiKeys)
ctx.JSON(http.StatusOK, &apiKeys)
} }


// GetDeployKey get a deploy key by id // GetDeployKey get a deploy key by id
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/DeployKey" // "$ref": "#/responses/DeployKey"

key, err := models.GetDeployKeyByID(ctx.ParamsInt64(":id")) key, err := models.GetDeployKeyByID(ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrDeployKeyNotExist(err) { if models.IsErrDeployKeyNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetDeployKeyByID", err)
ctx.Error(http.StatusInternalServerError, "GetDeployKeyByID", err)
} }
return return
} }


if err = key.GetContent(); err != nil { if err = key.GetContent(); err != nil {
ctx.Error(500, "GetContent", err)
ctx.Error(http.StatusInternalServerError, "GetContent", err)
return return
} }


if ctx.User.IsAdmin || ((ctx.Repo.Repository.ID == key.RepoID) && (ctx.User.ID == ctx.Repo.Owner.ID)) { if ctx.User.IsAdmin || ((ctx.Repo.Repository.ID == key.RepoID) && (ctx.User.ID == ctx.Repo.Owner.ID)) {
apiKey, _ = appendPrivateInformation(apiKey, key, ctx.Repo.Repository) apiKey, _ = appendPrivateInformation(apiKey, key, ctx.Repo.Repository)
} }
ctx.JSON(200, apiKey)
ctx.JSON(http.StatusOK, apiKey)
} }


// HandleCheckKeyStringError handle check key error // HandleCheckKeyStringError handle check key error
func HandleCheckKeyStringError(ctx *context.APIContext, err error) { func HandleCheckKeyStringError(ctx *context.APIContext, err error) {
if models.IsErrSSHDisabled(err) { if models.IsErrSSHDisabled(err) {
ctx.Error(422, "", "SSH is disabled")
ctx.Error(http.StatusUnprocessableEntity, "", "SSH is disabled")
} else if models.IsErrKeyUnableVerify(err) { } else if models.IsErrKeyUnableVerify(err) {
ctx.Error(422, "", "Unable to verify key content")
ctx.Error(http.StatusUnprocessableEntity, "", "Unable to verify key content")
} else { } else {
ctx.Error(422, "", fmt.Errorf("Invalid key content: %v", err))
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid key content: %v", err))
} }
} }


func HandleAddKeyError(ctx *context.APIContext, err error) { func HandleAddKeyError(ctx *context.APIContext, err error) {
switch { switch {
case models.IsErrDeployKeyAlreadyExist(err): case models.IsErrDeployKeyAlreadyExist(err):
ctx.Error(422, "", "This key has already been added to this repository")
ctx.Error(http.StatusUnprocessableEntity, "", "This key has already been added to this repository")
case models.IsErrKeyAlreadyExist(err): case models.IsErrKeyAlreadyExist(err):
ctx.Error(422, "", "Key content has been used as non-deploy key")
ctx.Error(http.StatusUnprocessableEntity, "", "Key content has been used as non-deploy key")
case models.IsErrKeyNameAlreadyUsed(err): case models.IsErrKeyNameAlreadyUsed(err):
ctx.Error(422, "", "Key title has been used")
ctx.Error(http.StatusUnprocessableEntity, "", "Key title has been used")
default: default:
ctx.Error(500, "AddKey", err)
ctx.Error(http.StatusInternalServerError, "AddKey", err)
} }
} }


// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/DeployKey" // "$ref": "#/responses/DeployKey"
// "422":
// "$ref": "#/responses/validationError"

content, err := models.CheckPublicKeyString(form.Key) content, err := models.CheckPublicKeyString(form.Key)
if err != nil { if err != nil {
HandleCheckKeyStringError(ctx, err) HandleCheckKeyStringError(ctx, err)


key.Content = content key.Content = content
apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name) apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name)
ctx.JSON(201, convert.ToDeployKey(apiLink, key))
ctx.JSON(http.StatusCreated, convert.ToDeployKey(apiLink, key))
} }


// DeleteDeploykey delete deploy key for a repository // DeleteDeploykey delete deploy key for a repository
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"

if err := models.DeleteDeployKey(ctx.User, ctx.ParamsInt64(":id")); err != nil { if err := models.DeleteDeployKey(ctx.User, ctx.ParamsInt64(":id")); err != nil {
if models.IsErrKeyAccessDenied(err) { if models.IsErrKeyAccessDenied(err) {
ctx.Error(403, "", "You do not have access to this key")
ctx.Error(http.StatusForbidden, "", "You do not have access to this key")
} else { } else {
ctx.Error(500, "DeleteDeployKey", err)
ctx.Error(http.StatusInternalServerError, "DeleteDeployKey", err)
} }
return return
} }


ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 16
- 10
routers/api/v1/repo/label.go View File

package repo package repo


import ( import (
"net/http"
"strconv" "strconv"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/LabelList" // "$ref": "#/responses/LabelList"

labels, err := models.GetLabelsByRepoID(ctx.Repo.Repository.ID, ctx.Query("sort")) labels, err := models.GetLabelsByRepoID(ctx.Repo.Repository.ID, ctx.Query("sort"))
if err != nil { if err != nil {
ctx.Error(500, "GetLabelsByRepoID", err)
ctx.Error(http.StatusInternalServerError, "GetLabelsByRepoID", err)
return return
} }


for i := range labels { for i := range labels {
apiLabels[i] = labels[i].APIFormat() apiLabels[i] = labels[i].APIFormat()
} }
ctx.JSON(200, &apiLabels)
ctx.JSON(http.StatusOK, &apiLabels)
} }


// GetLabel get label by repository and label id // GetLabel get label by repository and label id
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Label" // "$ref": "#/responses/Label"

var ( var (
label *models.Label label *models.Label
err error err error
if models.IsErrLabelNotExist(err) { if models.IsErrLabelNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetLabelByRepoID", err)
ctx.Error(http.StatusInternalServerError, "GetLabelByRepoID", err)
} }
return return
} }


ctx.JSON(200, label.APIFormat())
ctx.JSON(http.StatusOK, label.APIFormat())
} }


// CreateLabel create a label for a repository // CreateLabel create a label for a repository
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/Label" // "$ref": "#/responses/Label"

label := &models.Label{ label := &models.Label{
Name: form.Name, Name: form.Name,
Color: form.Color, Color: form.Color,
Description: form.Description, Description: form.Description,
} }
if err := models.NewLabel(label); err != nil { if err := models.NewLabel(label); err != nil {
ctx.Error(500, "NewLabel", err)
ctx.Error(http.StatusInternalServerError, "NewLabel", err)
return return
} }
ctx.JSON(201, label.APIFormat())
ctx.JSON(http.StatusCreated, label.APIFormat())
} }


// EditLabel modify a label for a repository // EditLabel modify a label for a repository
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Label" // "$ref": "#/responses/Label"

label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrLabelNotExist(err) { if models.IsErrLabelNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetLabelByRepoID", err)
ctx.Error(http.StatusInternalServerError, "GetLabelByRepoID", err)
} }
return return
} }
ctx.ServerError("UpdateLabel", err) ctx.ServerError("UpdateLabel", err)
return return
} }
ctx.JSON(200, label.APIFormat())
ctx.JSON(http.StatusOK, label.APIFormat())
} }


// DeleteLabel delete a label for a repository // DeleteLabel delete a label for a repository
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"

if err := models.DeleteLabel(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")); err != nil { if err := models.DeleteLabel(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")); err != nil {
ctx.Error(500, "DeleteLabel", err)
ctx.Error(http.StatusInternalServerError, "DeleteLabel", err)
return return
} }


ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 16
- 10
routers/api/v1/repo/milestone.go View File

package repo package repo


import ( import (
"net/http"
"time" "time"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/MilestoneList" // "$ref": "#/responses/MilestoneList"

milestones, err := models.GetMilestonesByRepoID(ctx.Repo.Repository.ID, api.StateType(ctx.Query("state"))) milestones, err := models.GetMilestonesByRepoID(ctx.Repo.Repository.ID, api.StateType(ctx.Query("state")))
if err != nil { if err != nil {
ctx.Error(500, "GetMilestonesByRepoID", err)
ctx.Error(http.StatusInternalServerError, "GetMilestonesByRepoID", err)
return return
} }


for i := range milestones { for i := range milestones {
apiMilestones[i] = milestones[i].APIFormat() apiMilestones[i] = milestones[i].APIFormat()
} }
ctx.JSON(200, &apiMilestones)
ctx.JSON(http.StatusOK, &apiMilestones)
} }


// GetMilestone get a milestone for a repository // GetMilestone get a milestone for a repository
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Milestone" // "$ref": "#/responses/Milestone"

milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrMilestoneNotExist(err) { if models.IsErrMilestoneNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetMilestoneByRepoID", err)
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
} }
return return
} }
ctx.JSON(200, milestone.APIFormat())
ctx.JSON(http.StatusOK, milestone.APIFormat())
} }


// CreateMilestone create a milestone for a repository // CreateMilestone create a milestone for a repository
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/Milestone" // "$ref": "#/responses/Milestone"

if form.Deadline == nil { if form.Deadline == nil {
defaultDeadline, _ := time.ParseInLocation("2006-01-02", "9999-12-31", time.Local) defaultDeadline, _ := time.ParseInLocation("2006-01-02", "9999-12-31", time.Local)
form.Deadline = &defaultDeadline form.Deadline = &defaultDeadline
} }


if err := models.NewMilestone(milestone); err != nil { if err := models.NewMilestone(milestone); err != nil {
ctx.Error(500, "NewMilestone", err)
ctx.Error(http.StatusInternalServerError, "NewMilestone", err)
return return
} }
ctx.JSON(201, milestone.APIFormat())
ctx.JSON(http.StatusCreated, milestone.APIFormat())
} }


// EditMilestone modify a milestone for a repository // EditMilestone modify a milestone for a repository
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Milestone" // "$ref": "#/responses/Milestone"

milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrMilestoneNotExist(err) { if models.IsErrMilestoneNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetMilestoneByRepoID", err)
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
} }
return return
} }
ctx.ServerError("UpdateMilestone", err) ctx.ServerError("UpdateMilestone", err)
return return
} }
ctx.JSON(200, milestone.APIFormat())
ctx.JSON(http.StatusOK, milestone.APIFormat())
} }


// DeleteMilestone delete a milestone for a repository // DeleteMilestone delete a milestone for a repository
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"

if err := models.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")); err != nil { if err := models.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")); err != nil {
ctx.Error(500, "DeleteMilestoneByRepoID", err)
ctx.Error(http.StatusInternalServerError, "DeleteMilestoneByRepoID", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 65
- 47
routers/api/v1/repo/pull.go View File

// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/PullRequestList" // "$ref": "#/responses/PullRequestList"

prs, maxResults, err := models.PullRequests(ctx.Repo.Repository.ID, &models.PullRequestsOptions{ prs, maxResults, err := models.PullRequests(ctx.Repo.Repository.ID, &models.PullRequestsOptions{
Page: ctx.QueryInt("page"), Page: ctx.QueryInt("page"),
State: ctx.QueryTrim("state"), State: ctx.QueryTrim("state"),
}) })


if err != nil { if err != nil {
ctx.Error(500, "PullRequests", err)
ctx.Error(http.StatusInternalServerError, "PullRequests", err)
return return
} }


apiPrs := make([]*api.PullRequest, len(prs)) apiPrs := make([]*api.PullRequest, len(prs))
for i := range prs { for i := range prs {
if err = prs[i].LoadIssue(); err != nil { if err = prs[i].LoadIssue(); err != nil {
ctx.Error(500, "LoadIssue", err)
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
return return
} }
if err = prs[i].LoadAttributes(); err != nil { if err = prs[i].LoadAttributes(); err != nil {
ctx.Error(500, "LoadAttributes", err)
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return return
} }
if err = prs[i].GetBaseRepo(); err != nil { if err = prs[i].GetBaseRepo(); err != nil {
ctx.Error(500, "GetBaseRepo", err)
ctx.Error(http.StatusInternalServerError, "GetBaseRepo", err)
return return
} }
if err = prs[i].GetHeadRepo(); err != nil { if err = prs[i].GetHeadRepo(); err != nil {
ctx.Error(500, "GetHeadRepo", err)
ctx.Error(http.StatusInternalServerError, "GetHeadRepo", err)
return return
} }
apiPrs[i] = prs[i].APIFormat() apiPrs[i] = prs[i].APIFormat()
} }


ctx.SetLinkHeader(int(maxResults), models.ItemsPerPage) ctx.SetLinkHeader(int(maxResults), models.ItemsPerPage)
ctx.JSON(200, &apiPrs)
ctx.JSON(http.StatusOK, &apiPrs)
} }


// GetPullRequest returns a single PR based on index // GetPullRequest returns a single PR based on index
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/PullRequest" // "$ref": "#/responses/PullRequest"

pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrPullRequestNotExist(err) { if models.IsErrPullRequestNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetPullRequestByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
} }
return return
} }


if err = pr.GetBaseRepo(); err != nil { if err = pr.GetBaseRepo(); err != nil {
ctx.Error(500, "GetBaseRepo", err)
ctx.Error(http.StatusInternalServerError, "GetBaseRepo", err)
return return
} }
if err = pr.GetHeadRepo(); err != nil { if err = pr.GetHeadRepo(); err != nil {
ctx.Error(500, "GetHeadRepo", err)
ctx.Error(http.StatusInternalServerError, "GetHeadRepo", err)
return return
} }
ctx.JSON(200, pr.APIFormat())
ctx.JSON(http.StatusOK, pr.APIFormat())
} }


// CreatePullRequest does what it says // CreatePullRequest does what it says
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/PullRequest" // "$ref": "#/responses/PullRequest"
// "409":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"

var ( var (
repo = ctx.Repo.Repository repo = ctx.Repo.Repository
labelIDs []int64 labelIDs []int64
existingPr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch) existingPr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch)
if err != nil { if err != nil {
if !models.IsErrPullRequestNotExist(err) { if !models.IsErrPullRequestNotExist(err) {
ctx.Error(500, "GetUnmergedPullRequest", err)
ctx.Error(http.StatusInternalServerError, "GetUnmergedPullRequest", err)
return return
} }
} else { } else {
HeadBranch: existingPr.HeadBranch, HeadBranch: existingPr.HeadBranch,
BaseBranch: existingPr.BaseBranch, BaseBranch: existingPr.BaseBranch,
} }
ctx.Error(409, "GetUnmergedPullRequest", err)
ctx.Error(http.StatusConflict, "GetUnmergedPullRequest", err)
return return
} }


if len(form.Labels) > 0 { if len(form.Labels) > 0 {
labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels) labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels)
if err != nil { if err != nil {
ctx.Error(500, "GetLabelsInRepoByIDs", err)
ctx.Error(http.StatusInternalServerError, "GetLabelsInRepoByIDs", err)
return return
} }


if models.IsErrMilestoneNotExist(err) { if models.IsErrMilestoneNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetMilestoneByRepoID", err)
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
} }
return return
} }
assigneeIDs, err := models.MakeIDsFromAPIAssigneesToAdd(form.Assignee, form.Assignees) assigneeIDs, err := models.MakeIDsFromAPIAssigneesToAdd(form.Assignee, form.Assignees)
if err != nil { if err != nil {
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
} else { } else {
ctx.Error(500, "AddAssigneeByName", err)
ctx.Error(http.StatusInternalServerError, "AddAssigneeByName", err)
} }
return return
} }
for _, aID := range assigneeIDs { for _, aID := range assigneeIDs {
assignee, err := models.GetUserByID(aID) assignee, err := models.GetUserByID(aID)
if err != nil { if err != nil {
ctx.Error(500, "GetUserByID", err)
ctx.Error(http.StatusInternalServerError, "GetUserByID", err)
return return
} }


valid, err := models.CanBeAssigned(assignee, repo, true) valid, err := models.CanBeAssigned(assignee, repo, true)
if err != nil { if err != nil {
ctx.Error(500, "canBeAssigned", err)
ctx.Error(http.StatusInternalServerError, "canBeAssigned", err)
return return
} }
if !valid { if !valid {
ctx.Error(422, "canBeAssigned", models.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: repo.Name})
ctx.Error(http.StatusUnprocessableEntity, "canBeAssigned", models.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: repo.Name})
return return
} }
} }


if err := pull_service.NewPullRequest(repo, prIssue, labelIDs, []string{}, pr, assigneeIDs); err != nil { if err := pull_service.NewPullRequest(repo, prIssue, labelIDs, []string{}, pr, assigneeIDs); err != nil {
if models.IsErrUserDoesNotHaveAccessToRepo(err) { if models.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(400, "UserDoesNotHaveAccessToRepo", err)
ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err)
return return
} }
ctx.Error(500, "NewPullRequest", err)
ctx.Error(http.StatusInternalServerError, "NewPullRequest", err)
return return
} }


notification.NotifyNewPullRequest(pr) notification.NotifyNewPullRequest(pr)


log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID) log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID)
ctx.JSON(201, pr.APIFormat())
ctx.JSON(http.StatusCreated, pr.APIFormat())
} }


// EditPullRequest does what it says // EditPullRequest does what it says
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/PullRequest" // "$ref": "#/responses/PullRequest"
// "403":
// "$ref": "#/responses/forbidden"
// "412":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"

pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrPullRequestNotExist(err) { if models.IsErrPullRequestNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetPullRequestByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
} }
return return
} }
issue.Repo = ctx.Repo.Repository issue.Repo = ctx.Repo.Repository


if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypePullRequests) { if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypePullRequests) {
ctx.Status(403)
ctx.Status(http.StatusForbidden)
return return
} }


} }


if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil { if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil {
ctx.Error(500, "UpdateIssueDeadline", err)
ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err)
return return
} }
issue.DeadlineUnix = deadlineUnix issue.DeadlineUnix = deadlineUnix
err = issue_service.UpdateAssignees(issue, form.Assignee, form.Assignees, ctx.User) err = issue_service.UpdateAssignees(issue, form.Assignee, form.Assignees, ctx.User)
if err != nil { if err != nil {
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
} else { } else {
ctx.Error(500, "UpdateAssignees", err)
ctx.Error(http.StatusInternalServerError, "UpdateAssignees", err)
} }
return return
} }
oldMilestoneID := issue.MilestoneID oldMilestoneID := issue.MilestoneID
issue.MilestoneID = form.Milestone issue.MilestoneID = form.Milestone
if err = issue_service.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil { if err = issue_service.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil {
ctx.Error(500, "ChangeMilestoneAssign", err)
ctx.Error(http.StatusInternalServerError, "ChangeMilestoneAssign", err)
return return
} }
} }
if ctx.Repo.CanWrite(models.UnitTypePullRequests) && form.Labels != nil { if ctx.Repo.CanWrite(models.UnitTypePullRequests) && form.Labels != nil {
labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels) labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels)
if err != nil { if err != nil {
ctx.Error(500, "GetLabelsInRepoByIDsError", err)
ctx.Error(http.StatusInternalServerError, "GetLabelsInRepoByIDsError", err)
return return
} }
if err = issue.ReplaceLabels(labels, ctx.User); err != nil { if err = issue.ReplaceLabels(labels, ctx.User); err != nil {
ctx.Error(500, "ReplaceLabelsError", err)
ctx.Error(http.StatusInternalServerError, "ReplaceLabelsError", err)
return return
} }
} }


if err = models.UpdateIssue(issue); err != nil { if err = models.UpdateIssue(issue); err != nil {
ctx.Error(500, "UpdateIssue", err)
ctx.Error(http.StatusInternalServerError, "UpdateIssue", err)
return return
} }
if form.State != nil { if form.State != nil {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies") ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
return return
} }
ctx.Error(500, "ChangeStatus", err)
ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
return return
} }
} }
if models.IsErrPullRequestNotExist(err) { if models.IsErrPullRequestNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetPullRequestByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
} }
return return
} }


// TODO this should be 200, not 201 // TODO this should be 200, not 201
ctx.JSON(201, pr.APIFormat())
ctx.JSON(http.StatusCreated, pr.APIFormat())
} }


// IsPullRequestMerged checks if a PR exists given an index // IsPullRequestMerged checks if a PR exists given an index
// description: pull request has been merged // description: pull request has been merged
// "404": // "404":
// description: pull request has not been merged // description: pull request has not been merged

pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrPullRequestNotExist(err) { if models.IsErrPullRequestNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetPullRequestByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
} }
return return
} }


if pr.HasMerged { if pr.HasMerged {
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }
ctx.NotFound() ctx.NotFound()
} }
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "405": // "405":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "409":
// "$ref": "#/responses/error"

pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrPullRequestNotExist(err) { if models.IsErrPullRequestNotExist(err) {
ctx.NotFound("GetPullRequestByIndex", err) ctx.NotFound("GetPullRequestByIndex", err)
} else { } else {
ctx.Error(500, "GetPullRequestByIndex", err)
ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
} }
return return
} }
if ctx.IsSigned { if ctx.IsSigned {
// Update issue-user. // Update issue-user.
if err = pr.Issue.ReadBy(ctx.User.ID); err != nil { if err = pr.Issue.ReadBy(ctx.User.ID); err != nil {
ctx.Error(500, "ReadBy", err)
ctx.Error(http.StatusInternalServerError, "ReadBy", err)
return return
} }
} }
} }


if !pr.CanAutoMerge() || pr.HasMerged || pr.IsWorkInProgress() { if !pr.CanAutoMerge() || pr.HasMerged || pr.IsWorkInProgress() {
ctx.Status(405)
ctx.Status(http.StatusMethodNotAllowed)
return return
} }


isPass, err := pull_service.IsPullCommitStatusPass(pr) isPass, err := pull_service.IsPullCommitStatusPass(pr)
if err != nil { if err != nil {
ctx.Error(500, "IsPullCommitStatusPass", err)
ctx.Error(http.StatusInternalServerError, "IsPullCommitStatusPass", err)
return return
} }


if !isPass && !ctx.IsUserRepoAdmin() { if !isPass && !ctx.IsUserRepoAdmin() {
ctx.Status(405)
ctx.Status(http.StatusMethodNotAllowed)
return return
} }




if err := pull_service.Merge(pr, ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil { if err := pull_service.Merge(pr, ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil {
if models.IsErrInvalidMergeStyle(err) { if models.IsErrInvalidMergeStyle(err) {
ctx.Status(405)
ctx.Status(http.StatusMethodNotAllowed)
return return
} else if models.IsErrMergeConflicts(err) { } else if models.IsErrMergeConflicts(err) {
conflictError := err.(models.ErrMergeConflicts) conflictError := err.(models.ErrMergeConflicts)
conflictError := err.(models.ErrMergeUnrelatedHistories) conflictError := err.(models.ErrMergeUnrelatedHistories)
ctx.JSON(http.StatusConflict, conflictError) ctx.JSON(http.StatusConflict, conflictError)
} else if models.IsErrMergePushOutOfDate(err) { } else if models.IsErrMergePushOutOfDate(err) {
ctx.Status(http.StatusConflict)
ctx.Error(http.StatusConflict, "Merge", "merge push out of date")
return return
} }
ctx.Error(500, "Merge", err)
ctx.Error(http.StatusInternalServerError, "Merge", err)
return return
} }


log.Trace("Pull request merged: %d", pr.ID) log.Trace("Pull request merged: %d", pr.ID)
ctx.Status(200)
ctx.Status(http.StatusOK)
} }


func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*models.User, *models.Repository, *git.Repository, *git.CompareInfo, string, string) { func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*models.User, *models.Repository, *git.Repository, *git.CompareInfo, string, string) {
} else { } else {
headGitRepo, err = git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name)) headGitRepo, err = git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name))
if err != nil { if err != nil {
ctx.Error(500, "OpenRepository", err)
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return nil, nil, nil, nil, "", "" return nil, nil, nil, nil, "", ""
} }
} }
compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch) compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch)
if err != nil { if err != nil {
headGitRepo.Close() headGitRepo.Close()
ctx.Error(500, "GetCompareInfo", err)
ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err)
return nil, nil, nil, nil, "", "" return nil, nil, nil, nil, "", ""
} }



+ 27
- 18
routers/api/v1/repo/release.go View File

package repo package repo


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Release" // "$ref": "#/responses/Release"

id := ctx.ParamsInt64(":id") id := ctx.ParamsInt64(":id")
release, err := models.GetReleaseByID(id) release, err := models.GetReleaseByID(id)
if err != nil { if err != nil {
ctx.Error(500, "GetReleaseByID", err)
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
return return
} }
if release.RepoID != ctx.Repo.Repository.ID { if release.RepoID != ctx.Repo.Repository.ID {
return return
} }
if err := release.LoadAttributes(); err != nil { if err := release.LoadAttributes(); err != nil {
ctx.Error(500, "LoadAttributes", err)
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return return
} }
ctx.JSON(200, release.APIFormat())
ctx.JSON(http.StatusOK, release.APIFormat())
} }


func getPagesInfo(ctx *context.APIContext) (int, int) { func getPagesInfo(ctx *context.APIContext) (int, int) {
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/ReleaseList" // "$ref": "#/responses/ReleaseList"

page, limit := getPagesInfo(ctx) page, limit := getPagesInfo(ctx)
releases, err := models.GetReleasesByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{ releases, err := models.GetReleasesByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
IncludeDrafts: ctx.Repo.AccessMode >= models.AccessModeWrite, IncludeDrafts: ctx.Repo.AccessMode >= models.AccessModeWrite,
IncludeTags: false, IncludeTags: false,
}, page, limit) }, page, limit)
if err != nil { if err != nil {
ctx.Error(500, "GetReleasesByRepoID", err)
ctx.Error(http.StatusInternalServerError, "GetReleasesByRepoID", err)
return return
} }
rels := make([]*api.Release, len(releases)) rels := make([]*api.Release, len(releases))
for i, release := range releases { for i, release := range releases {
if err := release.LoadAttributes(); err != nil { if err := release.LoadAttributes(); err != nil {
ctx.Error(500, "LoadAttributes", err)
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return return
} }
rels[i] = release.APIFormat() rels[i] = release.APIFormat()
} }
ctx.JSON(200, rels)
ctx.JSON(http.StatusOK, rels)
} }


// CreateRelease create a release // CreateRelease create a release
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/Release" // "$ref": "#/responses/Release"
// "409":
// "$ref": "#/responses/error"

rel, err := models.GetRelease(ctx.Repo.Repository.ID, form.TagName) rel, err := models.GetRelease(ctx.Repo.Repository.ID, form.TagName)
if err != nil { if err != nil {
if !models.IsErrReleaseNotExist(err) { if !models.IsErrReleaseNotExist(err) {
} }
if err := releaseservice.CreateRelease(ctx.Repo.GitRepo, rel, nil); err != nil { if err := releaseservice.CreateRelease(ctx.Repo.GitRepo, rel, nil); err != nil {
if models.IsErrReleaseAlreadyExist(err) { if models.IsErrReleaseAlreadyExist(err) {
ctx.Status(409)
ctx.Error(http.StatusConflict, "ReleaseAlreadyExist", err)
} else { } else {
ctx.Error(500, "CreateRelease", err)
ctx.Error(http.StatusInternalServerError, "CreateRelease", err)
} }
return return
} }
} else { } else {
if !rel.IsTag { if !rel.IsTag {
ctx.Status(409)
ctx.Error(http.StatusConflict, "GetRelease", "Release is has no Tag")
return return
} }


return return
} }
} }
ctx.JSON(201, rel.APIFormat())
ctx.JSON(http.StatusCreated, rel.APIFormat())
} }


// EditRelease edit a release // EditRelease edit a release
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Release" // "$ref": "#/responses/Release"

id := ctx.ParamsInt64(":id") id := ctx.ParamsInt64(":id")
rel, err := models.GetReleaseByID(id) rel, err := models.GetReleaseByID(id)
if err != nil && !models.IsErrReleaseNotExist(err) { if err != nil && !models.IsErrReleaseNotExist(err) {
ctx.Error(500, "GetReleaseByID", err)
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
return return
} }
if err != nil && models.IsErrReleaseNotExist(err) || if err != nil && models.IsErrReleaseNotExist(err) ||
rel.IsPrerelease = *form.IsPrerelease rel.IsPrerelease = *form.IsPrerelease
} }
if err := releaseservice.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, nil); err != nil { if err := releaseservice.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, nil); err != nil {
ctx.Error(500, "UpdateRelease", err)
ctx.Error(http.StatusInternalServerError, "UpdateRelease", err)
return return
} }


rel, err = models.GetReleaseByID(id) rel, err = models.GetReleaseByID(id)
if err != nil { if err != nil {
ctx.Error(500, "GetReleaseByID", err)
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
return return
} }
if err := rel.LoadAttributes(); err != nil { if err := rel.LoadAttributes(); err != nil {
ctx.Error(500, "LoadAttributes", err)
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return return
} }
ctx.JSON(200, rel.APIFormat())
ctx.JSON(http.StatusOK, rel.APIFormat())
} }


// DeleteRelease delete a release from a repository // DeleteRelease delete a release from a repository
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"

id := ctx.ParamsInt64(":id") id := ctx.ParamsInt64(":id")
rel, err := models.GetReleaseByID(id) rel, err := models.GetReleaseByID(id)
if err != nil && !models.IsErrReleaseNotExist(err) { if err != nil && !models.IsErrReleaseNotExist(err) {
ctx.Error(500, "GetReleaseByID", err)
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
return return
} }
if err != nil && models.IsErrReleaseNotExist(err) || if err != nil && models.IsErrReleaseNotExist(err) ||
return return
} }
if err := releaseservice.DeleteReleaseByID(id, ctx.User, false); err != nil { if err := releaseservice.DeleteReleaseByID(id, ctx.User, false); err != nil {
ctx.Error(500, "DeleteReleaseByID", err)
ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 21
- 16
routers/api/v1/repo/release_attachment.go View File

package repo package repo


import ( import (
"net/http"
"strings" "strings"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Attachment" // "$ref": "#/responses/Attachment"

releaseID := ctx.ParamsInt64(":id") releaseID := ctx.ParamsInt64(":id")
attachID := ctx.ParamsInt64(":asset") attachID := ctx.ParamsInt64(":asset")
attach, err := models.GetAttachmentByID(attachID) attach, err := models.GetAttachmentByID(attachID)
if err != nil { if err != nil {
ctx.Error(500, "GetAttachmentByID", err)
ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err)
return return
} }
if attach.ReleaseID != releaseID { if attach.ReleaseID != releaseID {
return return
} }
// FIXME Should prove the existence of the given repo, but results in unnecessary database requests // FIXME Should prove the existence of the given repo, but results in unnecessary database requests
ctx.JSON(200, attach.APIFormat())
ctx.JSON(http.StatusOK, attach.APIFormat())
} }


// ListReleaseAttachments lists all attachments of the release // ListReleaseAttachments lists all attachments of the release
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/AttachmentList" // "$ref": "#/responses/AttachmentList"

releaseID := ctx.ParamsInt64(":id") releaseID := ctx.ParamsInt64(":id")
release, err := models.GetReleaseByID(releaseID) release, err := models.GetReleaseByID(releaseID)
if err != nil { if err != nil {
ctx.Error(500, "GetReleaseByID", err)
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
return return
} }
if release.RepoID != ctx.Repo.Repository.ID { if release.RepoID != ctx.Repo.Repository.ID {
return return
} }
if err := release.LoadAttributes(); err != nil { if err := release.LoadAttributes(); err != nil {
ctx.Error(500, "LoadAttributes", err)
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return return
} }
ctx.JSON(200, release.APIFormat().Attachments)
ctx.JSON(http.StatusOK, release.APIFormat().Attachments)
} }


// CreateReleaseAttachment creates an attachment and saves the given file // CreateReleaseAttachment creates an attachment and saves the given file
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/Attachment" // "$ref": "#/responses/Attachment"
// "400":
// "$ref": "#/responses/error"


// Check if attachments are enabled // Check if attachments are enabled
if !setting.AttachmentEnabled { if !setting.AttachmentEnabled {
releaseID := ctx.ParamsInt64(":id") releaseID := ctx.ParamsInt64(":id")
release, err := models.GetReleaseByID(releaseID) release, err := models.GetReleaseByID(releaseID)
if err != nil { if err != nil {
ctx.Error(500, "GetReleaseByID", err)
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
return return
} }


// Get uploaded file from request // Get uploaded file from request
file, header, err := ctx.GetFile("attachment") file, header, err := ctx.GetFile("attachment")
if err != nil { if err != nil {
ctx.Error(500, "GetFile", err)
ctx.Error(http.StatusInternalServerError, "GetFile", err)
return return
} }
defer file.Close() defer file.Close()
// Check if the filetype is allowed by the settings // Check if the filetype is allowed by the settings
err = upload.VerifyAllowedContentType(buf, strings.Split(setting.AttachmentAllowedTypes, ",")) err = upload.VerifyAllowedContentType(buf, strings.Split(setting.AttachmentAllowedTypes, ","))
if err != nil { if err != nil {
ctx.Error(400, "DetectContentType", err)
ctx.Error(http.StatusBadRequest, "DetectContentType", err)
return return
} }


ReleaseID: release.ID, ReleaseID: release.ID,
}, buf, file) }, buf, file)
if err != nil { if err != nil {
ctx.Error(500, "NewAttachment", err)
ctx.Error(http.StatusInternalServerError, "NewAttachment", err)
return return
} }


ctx.JSON(201, attach.APIFormat())
ctx.JSON(http.StatusCreated, attach.APIFormat())
} }


// EditReleaseAttachment updates the given attachment // EditReleaseAttachment updates the given attachment
attachID := ctx.ParamsInt64(":asset") attachID := ctx.ParamsInt64(":asset")
attach, err := models.GetAttachmentByID(attachID) attach, err := models.GetAttachmentByID(attachID)
if err != nil { if err != nil {
ctx.Error(500, "GetAttachmentByID", err)
ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err)
return return
} }
if attach.ReleaseID != releaseID { if attach.ReleaseID != releaseID {
} }


if err := models.UpdateAttachment(attach); err != nil { if err := models.UpdateAttachment(attach); err != nil {
ctx.Error(500, "UpdateAttachment", attach)
ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach)
} }
ctx.JSON(201, attach.APIFormat())
ctx.JSON(http.StatusCreated, attach.APIFormat())
} }


// DeleteReleaseAttachment delete a given attachment // DeleteReleaseAttachment delete a given attachment
attachID := ctx.ParamsInt64(":asset") attachID := ctx.ParamsInt64(":asset")
attach, err := models.GetAttachmentByID(attachID) attach, err := models.GetAttachmentByID(attachID)
if err != nil { if err != nil {
ctx.Error(500, "GetAttachmentByID", err)
ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err)
return return
} }
if attach.ReleaseID != releaseID { if attach.ReleaseID != releaseID {
// FIXME Should prove the existence of the given repo, but results in unnecessary database requests // FIXME Should prove the existence of the given repo, but results in unnecessary database requests


if err := models.DeleteAttachment(attach, true); err != nil { if err := models.DeleteAttachment(attach, true); err != nil {
ctx.Error(500, "DeleteAttachment", err)
ctx.Error(http.StatusInternalServerError, "DeleteAttachment", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 58
- 43
routers/api/v1/repo/repo.go View File

// "$ref": "#/responses/SearchResults" // "$ref": "#/responses/SearchResults"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"

opts := &models.SearchRepoOptions{ opts := &models.SearchRepoOptions{
Keyword: strings.Trim(ctx.Query("q"), " "), Keyword: strings.Trim(ctx.Query("q"), " "),
OwnerID: ctx.QueryInt64("uid"), OwnerID: ctx.QueryInt64("uid"),
var err error var err error
repos, count, err := models.SearchRepository(opts) repos, count, err := models.SearchRepository(opts)
if err != nil { if err != nil {
ctx.JSON(500, api.SearchError{
ctx.JSON(http.StatusInternalServerError, api.SearchError{
OK: false, OK: false,
Error: err.Error(), Error: err.Error(),
}) })
results := make([]*api.Repository, len(repos)) results := make([]*api.Repository, len(repos))
for i, repo := range repos { for i, repo := range repos {
if err = repo.GetOwner(); err != nil { if err = repo.GetOwner(); err != nil {
ctx.JSON(500, api.SearchError{
ctx.JSON(http.StatusInternalServerError, api.SearchError{
OK: false, OK: false,
Error: err.Error(), Error: err.Error(),
}) })
} }
accessMode, err := models.AccessLevel(ctx.User, repo) accessMode, err := models.AccessLevel(ctx.User, repo)
if err != nil { if err != nil {
ctx.JSON(500, api.SearchError{
ctx.JSON(http.StatusInternalServerError, api.SearchError{
OK: false, OK: false,
Error: err.Error(), Error: err.Error(),
}) })


ctx.SetLinkHeader(int(count), setting.API.MaxResponseItems) ctx.SetLinkHeader(int(count), setting.API.MaxResponseItems)
ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", count)) ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", count))
ctx.JSON(200, api.SearchResults{
ctx.JSON(http.StatusOK, api.SearchResults{
OK: true, OK: true,
Data: results, Data: results,
}) })
}) })
if err != nil { if err != nil {
if models.IsErrRepoAlreadyExist(err) { if models.IsErrRepoAlreadyExist(err) {
ctx.Error(409, "", "The repository with the same name already exists.")
ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
} else if models.IsErrNameReserved(err) || } else if models.IsErrNameReserved(err) ||
models.IsErrNamePatternNotAllowed(err) { models.IsErrNamePatternNotAllowed(err) {
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(500, "CreateRepository", err)
ctx.Error(http.StatusInternalServerError, "CreateRepository", err)
} }
return return
} }


ctx.JSON(201, repo.APIFormat(models.AccessModeOwner))
ctx.JSON(http.StatusCreated, repo.APIFormat(models.AccessModeOwner))
} }


// Create one repository of mine // Create one repository of mine
// description: The repository with the same name already exists. // description: The repository with the same name already exists.
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"

if ctx.User.IsOrganization() { if ctx.User.IsOrganization() {
// Shouldn't reach this condition, but just in case. // Shouldn't reach this condition, but just in case.
ctx.Error(422, "", "not allowed creating repository for organization")
ctx.Error(http.StatusUnprocessableEntity, "", "not allowed creating repository for organization")
return return
} }
CreateUserRepo(ctx, ctx.User, opt) CreateUserRepo(ctx, ctx.User, opt)
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"

org, err := models.GetOrgByName(ctx.Params(":org")) org, err := models.GetOrgByName(ctx.Params(":org"))
if err != nil { if err != nil {
if models.IsErrOrgNotExist(err) { if models.IsErrOrgNotExist(err) {
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(500, "GetOrgByName", err)
ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
} }
return return
} }
ctx.ServerError("CanCreateOrgRepo", err) ctx.ServerError("CanCreateOrgRepo", err)
return return
} else if !canCreate { } else if !canCreate {
ctx.Error(403, "", "Given user is not allowed to create repository in organization.")
ctx.Error(http.StatusForbidden, "", "Given user is not allowed to create repository in organization.")
return return
} }
} }
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/Repository" // "$ref": "#/responses/Repository"
// "403":
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"

ctxUser := ctx.User ctxUser := ctx.User
// Not equal means context user is an organization, // Not equal means context user is an organization,
// or is another user/organization if current user is admin. // or is another user/organization if current user is admin.
org, err := models.GetUserByID(form.UID) org, err := models.GetUserByID(form.UID)
if err != nil { if err != nil {
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(500, "GetUserByID", err)
ctx.Error(http.StatusInternalServerError, "GetUserByID", err)
} }
return return
} }
} }


if ctx.HasError() { if ctx.HasError() {
ctx.Error(422, "", ctx.GetErrMsg())
ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
return return
} }


if !ctx.User.IsAdmin { if !ctx.User.IsAdmin {
if !ctxUser.IsOrganization() && ctx.User.ID != ctxUser.ID { if !ctxUser.IsOrganization() && ctx.User.ID != ctxUser.ID {
ctx.Error(403, "", "Given user is not an organization.")
ctx.Error(http.StatusForbidden, "", "Given user is not an organization.")
return return
} }


// Check ownership of organization. // Check ownership of organization.
isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID) isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID)
if err != nil { if err != nil {
ctx.Error(500, "IsOwnedBy", err)
ctx.Error(http.StatusInternalServerError, "IsOwnedBy", err)
return return
} else if !isOwner { } else if !isOwner {
ctx.Error(403, "", "Given user is not owner of organization.")
ctx.Error(http.StatusForbidden, "", "Given user is not owner of organization.")
return return
} }
} }
addrErr := err.(models.ErrInvalidCloneAddr) addrErr := err.(models.ErrInvalidCloneAddr)
switch { switch {
case addrErr.IsURLError: case addrErr.IsURLError:
ctx.Error(422, "", err)
ctx.Error(http.StatusUnprocessableEntity, "", err)
case addrErr.IsPermissionDenied: case addrErr.IsPermissionDenied:
ctx.Error(422, "", "You are not allowed to import local repositories.")
ctx.Error(http.StatusUnprocessableEntity, "", "You are not allowed to import local repositories.")
case addrErr.IsInvalidPath: case addrErr.IsInvalidPath:
ctx.Error(422, "", "Invalid local path, it does not exist or not a directory.")
ctx.Error(http.StatusUnprocessableEntity, "", "Invalid local path, it does not exist or not a directory.")
default: default:
ctx.Error(500, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error())
ctx.Error(http.StatusInternalServerError, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error())
} }
} else { } else {
ctx.Error(500, "ParseRemoteAddr", err)
ctx.Error(http.StatusInternalServerError, "ParseRemoteAddr", err)
} }
return return
} }
} }


log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName) log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
ctx.JSON(201, repo.APIFormat(models.AccessModeAdmin))
ctx.JSON(http.StatusCreated, repo.APIFormat(models.AccessModeAdmin))
} }


func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteAddr string, err error) { func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteAddr string, err error) {
switch { switch {
case models.IsErrRepoAlreadyExist(err): case models.IsErrRepoAlreadyExist(err):
ctx.Error(409, "", "The repository with the same name already exists.")
ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
case migrations.IsRateLimitError(err): case migrations.IsRateLimitError(err):
ctx.Error(422, "", "Remote visit addressed rate limitation.")
ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit addressed rate limitation.")
case migrations.IsTwoFactorAuthError(err): case migrations.IsTwoFactorAuthError(err):
ctx.Error(422, "", "Remote visit required two factors authentication.")
ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit required two factors authentication.")
case models.IsErrReachLimitOfRepo(err): case models.IsErrReachLimitOfRepo(err):
ctx.Error(422, "", fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))
case models.IsErrNameReserved(err): case models.IsErrNameReserved(err):
ctx.Error(422, "", fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))
case models.IsErrNamePatternNotAllowed(err): case models.IsErrNamePatternNotAllowed(err):
ctx.Error(422, "", fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern))
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern))
default: default:
err = util.URLSanitizedError(err, remoteAddr) err = util.URLSanitizedError(err, remoteAddr)
if strings.Contains(err.Error(), "Authentication failed") || if strings.Contains(err.Error(), "Authentication failed") ||
strings.Contains(err.Error(), "Bad credentials") || strings.Contains(err.Error(), "Bad credentials") ||
strings.Contains(err.Error(), "could not read Username") { strings.Contains(err.Error(), "could not read Username") {
ctx.Error(422, "", fmt.Sprintf("Authentication failed: %v.", err))
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Authentication failed: %v.", err))
} else if strings.Contains(err.Error(), "fatal:") { } else if strings.Contains(err.Error(), "fatal:") {
ctx.Error(422, "", fmt.Sprintf("Migration failed: %v.", err))
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Migration failed: %v.", err))
} else { } else {
ctx.Error(500, "MigrateRepository", err)
ctx.Error(http.StatusInternalServerError, "MigrateRepository", err)
} }
} }
} }
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Repository" // "$ref": "#/responses/Repository"
ctx.JSON(200, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode))

ctx.JSON(http.StatusOK, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode))
} }


// GetByID returns a single Repository // GetByID returns a single Repository
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Repository" // "$ref": "#/responses/Repository"

repo, err := models.GetRepositoryByID(ctx.ParamsInt64(":id")) repo, err := models.GetRepositoryByID(ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrRepoNotExist(err) { if models.IsErrRepoNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetRepositoryByID", err)
ctx.Error(http.StatusInternalServerError, "GetRepositoryByID", err)
} }
return return
} }


perm, err := models.GetUserRepoPermission(repo, ctx.User) perm, err := models.GetUserRepoPermission(repo, ctx.User)
if err != nil { if err != nil {
ctx.Error(500, "AccessLevel", err)
ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
return return
} else if !perm.HasAccess() { } else if !perm.HasAccess() {
ctx.NotFound() ctx.NotFound()
return return
} }
ctx.JSON(200, repo.APIFormat(perm.AccessMode))
ctx.JSON(http.StatusOK, repo.APIFormat(perm.AccessMode))
} }


// Edit edit repository properties // Edit edit repository properties
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"

if err := updateBasicProperties(ctx, opts); err != nil { if err := updateBasicProperties(ctx, opts); err != nil {
return return
} }
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"

owner := ctx.Repo.Owner owner := ctx.Repo.Owner
repo := ctx.Repo.Repository repo := ctx.Repo.Repository


canDelete, err := repo.CanUserDelete(ctx.User) canDelete, err := repo.CanUserDelete(ctx.User)
if err != nil { if err != nil {
ctx.Error(500, "CanUserDelete", err)
ctx.Error(http.StatusInternalServerError, "CanUserDelete", err)
return return
} else if !canDelete { } else if !canDelete {
ctx.Error(403, "", "Given user is not owner of organization.")
ctx.Error(http.StatusForbidden, "", "Given user is not owner of organization.")
return return
} }


if err := repo_service.DeleteRepository(ctx.User, repo); err != nil { if err := repo_service.DeleteRepository(ctx.User, repo); err != nil {
ctx.Error(500, "DeleteRepository", err)
ctx.Error(http.StatusInternalServerError, "DeleteRepository", err)
return return
} }


log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name) log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name)
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


// MirrorSync adds a mirrored repository to the sync queue // MirrorSync adds a mirrored repository to the sync queue
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"

repo := ctx.Repo.Repository repo := ctx.Repo.Repository


if !ctx.Repo.CanWrite(models.UnitTypeCode) { if !ctx.Repo.CanWrite(models.UnitTypeCode) {
ctx.Error(403, "MirrorSync", "Must have write access")
ctx.Error(http.StatusForbidden, "MirrorSync", "Must have write access")
} }


mirror_service.StartToMirror(repo.ID) mirror_service.StartToMirror(repo.ID)


ctx.Status(200)
ctx.Status(http.StatusOK)
} }

+ 5
- 2
routers/api/v1/repo/star.go View File

package repo package repo


import ( import (
"net/http"

"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/UserList" // "$ref": "#/responses/UserList"

stargazers, err := ctx.Repo.Repository.GetStargazers(-1) stargazers, err := ctx.Repo.Repository.GetStargazers(-1)
if err != nil { if err != nil {
ctx.Error(500, "GetStargazers", err)
ctx.Error(http.StatusInternalServerError, "GetStargazers", err)
return return
} }
users := make([]*api.User, len(stargazers)) users := make([]*api.User, len(stargazers))
for i, stargazer := range stargazers { for i, stargazer := range stargazers {
users[i] = convert.ToUser(stargazer, ctx.IsSigned, ctx.User != nil && ctx.User.IsAdmin) users[i] = convert.ToUser(stargazer, ctx.IsSigned, ctx.User != nil && ctx.User.IsAdmin)
} }
ctx.JSON(200, users)
ctx.JSON(http.StatusOK, users)
} }

+ 24
- 12
routers/api/v1/repo/status.go View File



import ( import (
"fmt" "fmt"
"net/http"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/Status" // "$ref": "#/responses/Status"
// "400":
// "$ref": "#/responses/error"

sha := ctx.Params("sha") sha := ctx.Params("sha")
if len(sha) == 0 { if len(sha) == 0 {
ctx.Error(400, "sha not given", nil)
ctx.Error(http.StatusBadRequest, "sha not given", nil)
return return
} }
status := &models.CommitStatus{ status := &models.CommitStatus{
Context: form.Context, Context: form.Context,
} }
if err := repofiles.CreateCommitStatus(ctx.Repo.Repository, ctx.User, sha, status); err != nil { if err := repofiles.CreateCommitStatus(ctx.Repo.Repository, ctx.User, sha, status); err != nil {
ctx.Error(500, "CreateCommitStatus", err)
ctx.Error(http.StatusInternalServerError, "CreateCommitStatus", err)
return return
} }


ctx.JSON(201, status.APIFormat())
ctx.JSON(http.StatusCreated, status.APIFormat())
} }


// GetCommitStatuses returns all statuses for any given commit hash // GetCommitStatuses returns all statuses for any given commit hash
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/StatusList" // "$ref": "#/responses/StatusList"
// "400":
// "$ref": "#/responses/error"

getCommitStatuses(ctx, ctx.Params("sha")) getCommitStatuses(ctx, ctx.Params("sha"))
} }


// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/StatusList" // "$ref": "#/responses/StatusList"
// "400":
// "$ref": "#/responses/error"


filter := ctx.Params("ref") filter := ctx.Params("ref")
if len(filter) == 0 { if len(filter) == 0 {
ctx.Error(400, "ref not given", nil)
ctx.Error(http.StatusBadRequest, "ref not given", nil)
return return
} }


for _, reftype := range []string{"heads", "tags"} { //Search branches and tags for _, reftype := range []string{"heads", "tags"} { //Search branches and tags
refSHA, lastMethodName, err := searchRefCommitByType(ctx, reftype, filter) refSHA, lastMethodName, err := searchRefCommitByType(ctx, reftype, filter)
if err != nil { if err != nil {
ctx.Error(500, lastMethodName, err)
ctx.Error(http.StatusInternalServerError, lastMethodName, err)
return return
} }
if refSHA != "" { if refSHA != "" {


func getCommitStatuses(ctx *context.APIContext, sha string) { func getCommitStatuses(ctx *context.APIContext, sha string) {
if len(sha) == 0 { if len(sha) == 0 {
ctx.Error(400, "ref/sha not given", nil)
ctx.Error(http.StatusBadRequest, "ref/sha not given", nil)
return return
} }
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
State: ctx.QueryTrim("state"), State: ctx.QueryTrim("state"),
}) })
if err != nil { if err != nil {
ctx.Error(500, "GetCommitStatuses", fmt.Errorf("GetCommitStatuses[%s, %s, %d]: %v", repo.FullName(), sha, ctx.QueryInt("page"), err))
ctx.Error(http.StatusInternalServerError, "GetCommitStatuses", fmt.Errorf("GetCommitStatuses[%s, %s, %d]: %v", repo.FullName(), sha, ctx.QueryInt("page"), err))
return return
} }


apiStatuses = append(apiStatuses, status.APIFormat()) apiStatuses = append(apiStatuses, status.APIFormat())
} }


ctx.JSON(200, apiStatuses)
ctx.JSON(http.StatusOK, apiStatuses)
} }


type combinedCommitStatus struct { type combinedCommitStatus struct {
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Status" // "$ref": "#/responses/Status"
// "400":
// "$ref": "#/responses/error"

sha := ctx.Params("ref") sha := ctx.Params("ref")
if len(sha) == 0 { if len(sha) == 0 {
ctx.Error(400, "ref/sha not given", nil)
ctx.Error(http.StatusBadRequest, "ref/sha not given", nil)
return return
} }
repo := ctx.Repo.Repository repo := ctx.Repo.Repository


statuses, err := models.GetLatestCommitStatus(repo, sha, page) statuses, err := models.GetLatestCommitStatus(repo, sha, page)
if err != nil { if err != nil {
ctx.Error(500, "GetLatestCommitStatus", fmt.Errorf("GetLatestCommitStatus[%s, %s, %d]: %v", repo.FullName(), sha, page, err))
ctx.Error(http.StatusInternalServerError, "GetLatestCommitStatus", fmt.Errorf("GetLatestCommitStatus[%s, %s, %d]: %v", repo.FullName(), sha, page, err))
return return
} }


if len(statuses) == 0 { if len(statuses) == 0 {
ctx.Status(200)
ctx.Status(http.StatusOK)
return return
} }


} }
} }


ctx.JSON(200, retStatus)
ctx.JSON(http.StatusOK, retStatus)
} }

+ 5
- 2
routers/api/v1/repo/subscriber.go View File

package repo package repo


import ( import (
"net/http"

"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/UserList" // "$ref": "#/responses/UserList"

subscribers, err := ctx.Repo.Repository.GetWatchers(0) subscribers, err := ctx.Repo.Repository.GetWatchers(0)
if err != nil { if err != nil {
ctx.Error(500, "GetWatchers", err)
ctx.Error(http.StatusInternalServerError, "GetWatchers", err)
return return
} }
users := make([]*api.User, len(subscribers)) users := make([]*api.User, len(subscribers))
for i, subscriber := range subscribers { for i, subscriber := range subscribers {
users[i] = convert.ToUser(subscriber, ctx.IsSigned, ctx.User != nil && ctx.User.IsAdmin) users[i] = convert.ToUser(subscriber, ctx.IsSigned, ctx.User != nil && ctx.User.IsAdmin)
} }
ctx.JSON(200, users)
ctx.JSON(http.StatusOK, users)
} }

+ 5
- 2
routers/api/v1/repo/tag.go View File

// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/TagList" // "$ref": "#/responses/TagList"

tags, err := ctx.Repo.GitRepo.GetTagInfos() tags, err := ctx.Repo.GitRepo.GetTagInfos()
if err != nil { if err != nil {
ctx.Error(500, "GetTags", err)
ctx.Error(http.StatusInternalServerError, "GetTags", err)
return return
} }


apiTags[i] = convert.ToTag(ctx.Repo.Repository, tags[i]) apiTags[i] = convert.ToTag(ctx.Repo.Repository, tags[i])
} }


ctx.JSON(200, &apiTags)
ctx.JSON(http.StatusOK, &apiTags)
} }


// GetTag get the tag of a repository. // GetTag get the tag of a repository.
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/AnnotatedTag" // "$ref": "#/responses/AnnotatedTag"
// "400":
// "$ref": "#/responses/error"


sha := ctx.Params("sha") sha := ctx.Params("sha")
if len(sha) == 0 { if len(sha) == 0 {

+ 25
- 23
routers/api/v1/repo/topic.go View File

}) })
if err != nil { if err != nil {
log.Error("ListTopics failed: %v", err) log.Error("ListTopics failed: %v", err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"message": "ListTopics failed.",
})
ctx.InternalServerError(err)
return return
} }


// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "422":
// "$ref": "#/responses/invalidTopicsError"


topicNames := form.Topics topicNames := form.Topics
validTopics, invalidTopics := models.SanitizeAndValidateTopics(topicNames) validTopics, invalidTopics := models.SanitizeAndValidateTopics(topicNames)
err := models.SaveTopics(ctx.Repo.Repository.ID, validTopics...) err := models.SaveTopics(ctx.Repo.Repository.ID, validTopics...)
if err != nil { if err != nil {
log.Error("SaveTopics failed: %v", err) log.Error("SaveTopics failed: %v", err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"message": "Save topics failed.",
})
ctx.InternalServerError(err)
return return
} }


// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "422":
// "$ref": "#/responses/invalidTopicsError"


topicName := strings.TrimSpace(strings.ToLower(ctx.Params(":topic"))) topicName := strings.TrimSpace(strings.ToLower(ctx.Params(":topic")))


if !models.ValidateTopic(topicName) { if !models.ValidateTopic(topicName) {
ctx.Error(http.StatusUnprocessableEntity, "", "Topic name is invalid")
ctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{
"invalidTopics": topicName,
"message": "Topic name is invalid",
})
return return
} }


}) })
if err != nil { if err != nil {
log.Error("AddTopic failed: %v", err) log.Error("AddTopic failed: %v", err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"message": "ListTopics failed.",
})
ctx.InternalServerError(err)
return return
} }
if len(topics) >= 25 { if len(topics) >= 25 {
_, err = models.AddTopic(ctx.Repo.Repository.ID, topicName) _, err = models.AddTopic(ctx.Repo.Repository.ID, topicName)
if err != nil { if err != nil {
log.Error("AddTopic failed: %v", err) log.Error("AddTopic failed: %v", err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"message": "AddTopic failed.",
})
ctx.InternalServerError(err)
return return
} }


// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "422":
// "$ref": "#/responses/invalidTopicsError"

topicName := strings.TrimSpace(strings.ToLower(ctx.Params(":topic"))) topicName := strings.TrimSpace(strings.ToLower(ctx.Params(":topic")))


if !models.ValidateTopic(topicName) { if !models.ValidateTopic(topicName) {
ctx.Error(http.StatusUnprocessableEntity, "", "Topic name is invalid")
ctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{
"invalidTopics": topicName,
"message": "Topic name is invalid",
})
return return
} }


topic, err := models.DeleteTopic(ctx.Repo.Repository.ID, topicName) topic, err := models.DeleteTopic(ctx.Repo.Repository.ID, topicName)
if err != nil { if err != nil {
log.Error("DeleteTopic failed: %v", err) log.Error("DeleteTopic failed: %v", err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"message": "DeleteTopic failed.",
})
ctx.InternalServerError(err)
return return
} }


// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/TopicListResponse" // "$ref": "#/responses/TopicListResponse"
// "403":
// "$ref": "#/responses/forbidden"

if ctx.User == nil { if ctx.User == nil {
ctx.JSON(http.StatusForbidden, map[string]interface{}{
"message": "Only owners could change the topics.",
})
ctx.Error(http.StatusForbidden, "UserIsNil", "Only owners could change the topics.")
return return
} }


}) })
if err != nil { if err != nil {
log.Error("SearchTopics failed: %v", err) log.Error("SearchTopics failed: %v", err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"message": "Search topics failed.",
})
ctx.InternalServerError(err)
return return
} }



+ 7
- 3
routers/api/v1/repo/tree.go View File

package repo package repo


import ( import (
"net/http"

"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/repofiles" "code.gitea.io/gitea/modules/repofiles"
) )
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/GitTreeResponse" // "$ref": "#/responses/GitTreeResponse"
// "400":
// "$ref": "#/responses/error"


sha := ctx.Params(":sha") sha := ctx.Params(":sha")
if len(sha) == 0 { if len(sha) == 0 {
ctx.Error(400, "", "sha not provided")
ctx.Error(http.StatusBadRequest, "", "sha not provided")
return return
} }
if tree, err := repofiles.GetTreeBySHA(ctx.Repo.Repository, sha, ctx.QueryInt("page"), ctx.QueryInt("per_page"), ctx.QueryBool("recursive")); err != nil { if tree, err := repofiles.GetTreeBySHA(ctx.Repo.Repository, sha, ctx.QueryInt("page"), ctx.QueryInt("per_page"), ctx.QueryBool("recursive")); err != nil {
ctx.Error(400, "", err.Error())
ctx.Error(http.StatusBadRequest, "", err.Error())
} else { } else {
ctx.JSON(200, tree)
ctx.JSON(http.StatusOK, tree)
} }
} }

+ 11
- 6
routers/api/v1/user/app.go View File

package user package user


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/AccessTokenList" // "$ref": "#/responses/AccessTokenList"

tokens, err := models.ListAccessTokens(ctx.User.ID) tokens, err := models.ListAccessTokens(ctx.User.ID)
if err != nil { if err != nil {
ctx.Error(500, "ListAccessTokens", err)
ctx.Error(http.StatusInternalServerError, "ListAccessTokens", err)
return return
} }


TokenLastEight: tokens[i].TokenLastEight, TokenLastEight: tokens[i].TokenLastEight,
} }
} }
ctx.JSON(200, &apiTokens)
ctx.JSON(http.StatusOK, &apiTokens)
} }


// CreateAccessToken create access tokens // CreateAccessToken create access tokens
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/AccessToken" // "$ref": "#/responses/AccessToken"

t := &models.AccessToken{ t := &models.AccessToken{
UID: ctx.User.ID, UID: ctx.User.ID,
Name: form.Name, Name: form.Name,
} }
if err := models.NewAccessToken(t); err != nil { if err := models.NewAccessToken(t); err != nil {
ctx.Error(500, "NewAccessToken", err)
ctx.Error(http.StatusInternalServerError, "NewAccessToken", err)
return return
} }
ctx.JSON(201, &api.AccessToken{
ctx.JSON(http.StatusCreated, &api.AccessToken{
Name: t.Name, Name: t.Name,
Token: t.Token, Token: t.Token,
ID: t.ID, ID: t.ID,
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"

tokenID := ctx.ParamsInt64(":id") tokenID := ctx.ParamsInt64(":id")
if err := models.DeleteAccessTokenByID(tokenID, ctx.User.ID); err != nil { if err := models.DeleteAccessTokenByID(tokenID, ctx.User.ID); err != nil {
if models.IsErrAccessTokenNotExist(err) { if models.IsErrAccessTokenNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "DeleteAccessTokenByID", err)
ctx.Error(http.StatusInternalServerError, "DeleteAccessTokenByID", err)
} }
return return
} }


ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 16
- 9
routers/api/v1/user/email.go View File

package user package user


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/EmailList" // "$ref": "#/responses/EmailList"

emails, err := models.GetEmailAddresses(ctx.User.ID) emails, err := models.GetEmailAddresses(ctx.User.ID)
if err != nil { if err != nil {
ctx.Error(500, "GetEmailAddresses", err)
ctx.Error(http.StatusInternalServerError, "GetEmailAddresses", err)
return return
} }
apiEmails := make([]*api.Email, len(emails)) apiEmails := make([]*api.Email, len(emails))
for i := range emails { for i := range emails {
apiEmails[i] = convert.ToEmail(emails[i]) apiEmails[i] = convert.ToEmail(emails[i])
} }
ctx.JSON(200, &apiEmails)
ctx.JSON(http.StatusOK, &apiEmails)
} }


// AddEmail add an email address // AddEmail add an email address
// responses: // responses:
// '201': // '201':
// "$ref": "#/responses/EmailList" // "$ref": "#/responses/EmailList"
// "422":
// "$ref": "#/responses/validationError"

if len(form.Emails) == 0 { if len(form.Emails) == 0 {
ctx.Status(422)
ctx.Error(http.StatusUnprocessableEntity, "", "Email list empty")
return return
} }




if err := models.AddEmailAddresses(emails); err != nil { if err := models.AddEmailAddresses(emails); err != nil {
if models.IsErrEmailAlreadyUsed(err) { if models.IsErrEmailAlreadyUsed(err) {
ctx.Error(422, "", "Email address has been used: "+err.(models.ErrEmailAlreadyUsed).Email)
ctx.Error(http.StatusUnprocessableEntity, "", "Email address has been used: "+err.(models.ErrEmailAlreadyUsed).Email)
} else { } else {
ctx.Error(500, "AddEmailAddresses", err)
ctx.Error(http.StatusInternalServerError, "AddEmailAddresses", err)
} }
return return
} }
for i := range emails { for i := range emails {
apiEmails[i] = convert.ToEmail(emails[i]) apiEmails[i] = convert.ToEmail(emails[i])
} }
ctx.JSON(201, &apiEmails)
ctx.JSON(http.StatusCreated, &apiEmails)
} }


// DeleteEmail delete email // DeleteEmail delete email
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"

if len(form.Emails) == 0 { if len(form.Emails) == 0 {
ctx.Status(204)
ctx.Status(http.StatusNoContent)
return return
} }


} }


if err := models.DeleteEmailAddresses(emails); err != nil { if err := models.DeleteEmailAddresses(emails); err != nil {
ctx.Error(500, "DeleteEmailAddresses", err)
ctx.Error(http.StatusInternalServerError, "DeleteEmailAddresses", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 18
- 8
routers/api/v1/user/follower.go View File

package user package user


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
for i := range users { for i := range users {
apiUsers[i] = convert.ToUser(users[i], ctx.IsSigned, ctx.User != nil && ctx.User.IsAdmin) apiUsers[i] = convert.ToUser(users[i], ctx.IsSigned, ctx.User != nil && ctx.User.IsAdmin)
} }
ctx.JSON(200, &apiUsers)
ctx.JSON(http.StatusOK, &apiUsers)
} }


func listUserFollowers(ctx *context.APIContext, u *models.User) { func listUserFollowers(ctx *context.APIContext, u *models.User) {
users, err := u.GetFollowers(ctx.QueryInt("page")) users, err := u.GetFollowers(ctx.QueryInt("page"))
if err != nil { if err != nil {
ctx.Error(500, "GetUserFollowers", err)
ctx.Error(http.StatusInternalServerError, "GetUserFollowers", err)
return return
} }
responseAPIUsers(ctx, users) responseAPIUsers(ctx, users)
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/UserList" // "$ref": "#/responses/UserList"

listUserFollowers(ctx, ctx.User) listUserFollowers(ctx, ctx.User)
} }


// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/UserList" // "$ref": "#/responses/UserList"

u := GetUserByParams(ctx) u := GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
func listUserFollowing(ctx *context.APIContext, u *models.User) { func listUserFollowing(ctx *context.APIContext, u *models.User) {
users, err := u.GetFollowing(ctx.QueryInt("page")) users, err := u.GetFollowing(ctx.QueryInt("page"))
if err != nil { if err != nil {
ctx.Error(500, "GetFollowing", err)
ctx.Error(http.StatusInternalServerError, "GetFollowing", err)
return return
} }
responseAPIUsers(ctx, users) responseAPIUsers(ctx, users)
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/UserList" // "$ref": "#/responses/UserList"

listUserFollowing(ctx, ctx.User) listUserFollowing(ctx, ctx.User)
} }


// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/UserList" // "$ref": "#/responses/UserList"

u := GetUserByParams(ctx) u := GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return


func checkUserFollowing(ctx *context.APIContext, u *models.User, followID int64) { func checkUserFollowing(ctx *context.APIContext, u *models.User, followID int64) {
if u.IsFollowing(followID) { if u.IsFollowing(followID) {
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} else { } else {
ctx.NotFound() ctx.NotFound()
} }
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"

target := GetUserByParams(ctx) target := GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"

u := GetUserByParams(ctx) u := GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"

target := GetUserByParams(ctx) target := GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
if err := models.FollowUser(ctx.User.ID, target.ID); err != nil { if err := models.FollowUser(ctx.User.ID, target.ID); err != nil {
ctx.Error(500, "FollowUser", err)
ctx.Error(http.StatusInternalServerError, "FollowUser", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


// Unfollow unfollow a user // Unfollow unfollow a user
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"

target := GetUserByParams(ctx) target := GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
if err := models.UnfollowUser(ctx.User.ID, target.ID); err != nil { if err := models.UnfollowUser(ctx.User.ID, target.ID); err != nil {
ctx.Error(500, "UnfollowUser", err)
ctx.Error(http.StatusInternalServerError, "UnfollowUser", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 18
- 11
routers/api/v1/user/gpg_key.go View File

package user package user


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
func listGPGKeys(ctx *context.APIContext, uid int64) { func listGPGKeys(ctx *context.APIContext, uid int64) {
keys, err := models.ListGPGKeys(uid) keys, err := models.ListGPGKeys(uid)
if err != nil { if err != nil {
ctx.Error(500, "ListGPGKeys", err)
ctx.Error(http.StatusInternalServerError, "ListGPGKeys", err)
return return
} }


apiKeys[i] = convert.ToGPGKey(keys[i]) apiKeys[i] = convert.ToGPGKey(keys[i])
} }


ctx.JSON(200, &apiKeys)
ctx.JSON(http.StatusOK, &apiKeys)
} }


//ListGPGKeys get the GPG key list of a user //ListGPGKeys get the GPG key list of a user
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/GPGKeyList" // "$ref": "#/responses/GPGKeyList"

user := GetUserByParams(ctx) user := GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/GPGKeyList" // "$ref": "#/responses/GPGKeyList"

listGPGKeys(ctx, ctx.User.ID) listGPGKeys(ctx, ctx.User.ID)
} }


// "$ref": "#/responses/GPGKey" // "$ref": "#/responses/GPGKey"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"

key, err := models.GetGPGKeyByID(ctx.ParamsInt64(":id")) key, err := models.GetGPGKeyByID(ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrGPGKeyNotExist(err) { if models.IsErrGPGKeyNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetGPGKeyByID", err)
ctx.Error(http.StatusInternalServerError, "GetGPGKeyByID", err)
} }
return return
} }
ctx.JSON(200, convert.ToGPGKey(key))
ctx.JSON(http.StatusOK, convert.ToGPGKey(key))
} }


// CreateUserGPGKey creates new GPG key to given user by ID. // CreateUserGPGKey creates new GPG key to given user by ID.
HandleAddGPGKeyError(ctx, err) HandleAddGPGKeyError(ctx, err)
return return
} }
ctx.JSON(201, convert.ToGPGKey(key))
ctx.JSON(http.StatusCreated, convert.ToGPGKey(key))
} }


// swagger:parameters userCurrentPostGPGKey // swagger:parameters userCurrentPostGPGKey
// "$ref": "#/responses/GPGKey" // "$ref": "#/responses/GPGKey"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"

CreateUserGPGKey(ctx, form, ctx.User.ID) CreateUserGPGKey(ctx, form, ctx.User.ID)
} }


// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"

if err := models.DeleteGPGKey(ctx.User, ctx.ParamsInt64(":id")); err != nil { if err := models.DeleteGPGKey(ctx.User, ctx.ParamsInt64(":id")); err != nil {
if models.IsErrGPGKeyAccessDenied(err) { if models.IsErrGPGKeyAccessDenied(err) {
ctx.Error(403, "", "You do not have access to this key")
ctx.Error(http.StatusForbidden, "", "You do not have access to this key")
} else { } else {
ctx.Error(500, "DeleteGPGKey", err)
ctx.Error(http.StatusInternalServerError, "DeleteGPGKey", err)
} }
return return
} }


ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


// HandleAddGPGKeyError handle add GPGKey error // HandleAddGPGKeyError handle add GPGKey error
func HandleAddGPGKeyError(ctx *context.APIContext, err error) { func HandleAddGPGKeyError(ctx *context.APIContext, err error) {
switch { switch {
case models.IsErrGPGKeyAccessDenied(err): case models.IsErrGPGKeyAccessDenied(err):
ctx.Error(422, "", "You do not have access to this GPG key")
ctx.Error(http.StatusUnprocessableEntity, "", "You do not have access to this GPG key")
case models.IsErrGPGKeyIDAlreadyUsed(err): case models.IsErrGPGKeyIDAlreadyUsed(err):
ctx.Error(422, "", "A key with the same id already exists")
ctx.Error(http.StatusUnprocessableEntity, "", "A key with the same id already exists")
default: default:
ctx.Error(500, "AddGPGKey", err)
ctx.Error(http.StatusInternalServerError, "AddGPGKey", err)
} }
} }

+ 16
- 9
routers/api/v1/user/key.go View File

package user package user


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetUserByName", err)
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
} }
return nil return nil
} }
} }


if err != nil { if err != nil {
ctx.Error(500, "ListPublicKeys", err)
ctx.Error(http.StatusInternalServerError, "ListPublicKeys", err)
return return
} }


} }
} }


ctx.JSON(200, &apiKeys)
ctx.JSON(http.StatusOK, &apiKeys)
} }


// ListMyPublicKeys list all of the authenticated user's public keys // ListMyPublicKeys list all of the authenticated user's public keys
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/PublicKeyList" // "$ref": "#/responses/PublicKeyList"

listPublicKeys(ctx, ctx.User) listPublicKeys(ctx, ctx.User)
} }


// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/PublicKeyList" // "$ref": "#/responses/PublicKeyList"

user := GetUserByParams(ctx) user := GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
// "$ref": "#/responses/PublicKey" // "$ref": "#/responses/PublicKey"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"

key, err := models.GetPublicKeyByID(ctx.ParamsInt64(":id")) key, err := models.GetPublicKeyByID(ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrKeyNotExist(err) { if models.IsErrKeyNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetPublicKeyByID", err)
ctx.Error(http.StatusInternalServerError, "GetPublicKeyByID", err)
} }
return return
} }
if ctx.User.IsAdmin || ctx.User.ID == key.OwnerID { if ctx.User.IsAdmin || ctx.User.ID == key.OwnerID {
apiKey, _ = appendPrivateInformation(apiKey, key, ctx.User) apiKey, _ = appendPrivateInformation(apiKey, key, ctx.User)
} }
ctx.JSON(200, apiKey)
ctx.JSON(http.StatusOK, apiKey)
} }


// CreateUserPublicKey creates new public key to given user by ID. // CreateUserPublicKey creates new public key to given user by ID.
if ctx.User.IsAdmin || ctx.User.ID == key.OwnerID { if ctx.User.IsAdmin || ctx.User.ID == key.OwnerID {
apiKey, _ = appendPrivateInformation(apiKey, key, ctx.User) apiKey, _ = appendPrivateInformation(apiKey, key, ctx.User)
} }
ctx.JSON(201, apiKey)
ctx.JSON(http.StatusCreated, apiKey)
} }


// CreatePublicKey create one public key for me // CreatePublicKey create one public key for me
// "$ref": "#/responses/PublicKey" // "$ref": "#/responses/PublicKey"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"

CreateUserPublicKey(ctx, form, ctx.User.ID) CreateUserPublicKey(ctx, form, ctx.User.ID)
} }


// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"

if err := models.DeletePublicKey(ctx.User, ctx.ParamsInt64(":id")); err != nil { if err := models.DeletePublicKey(ctx.User, ctx.ParamsInt64(":id")); err != nil {
if models.IsErrKeyNotExist(err) { if models.IsErrKeyNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else if models.IsErrKeyAccessDenied(err) { } else if models.IsErrKeyAccessDenied(err) {
ctx.Error(403, "", "You do not have access to this key")
ctx.Error(http.StatusForbidden, "", "You do not have access to this key")
} else { } else {
ctx.Error(500, "DeletePublicKey", err)
ctx.Error(http.StatusInternalServerError, "DeletePublicKey", err)
} }
return return
} }


ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 11
- 6
routers/api/v1/user/repo.go View File

package user package user


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
func listUserRepos(ctx *context.APIContext, u *models.User, private bool) { func listUserRepos(ctx *context.APIContext, u *models.User, private bool) {
repos, err := models.GetUserRepositories(u.ID, private, 1, u.NumRepos, "") repos, err := models.GetUserRepositories(u.ID, private, 1, u.NumRepos, "")
if err != nil { if err != nil {
ctx.Error(500, "GetUserRepositories", err)
ctx.Error(http.StatusInternalServerError, "GetUserRepositories", err)
return return
} }


for i := range repos { for i := range repos {
access, err := models.AccessLevel(ctx.User, repos[i]) access, err := models.AccessLevel(ctx.User, repos[i])
if err != nil { if err != nil {
ctx.Error(500, "AccessLevel", err)
ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
return return
} }
if ctx.IsSigned && ctx.User.IsAdmin || access >= models.AccessModeRead { if ctx.IsSigned && ctx.User.IsAdmin || access >= models.AccessModeRead {
apiRepos = append(apiRepos, repos[i].APIFormat(access)) apiRepos = append(apiRepos, repos[i].APIFormat(access))
} }
} }
ctx.JSON(200, &apiRepos)
ctx.JSON(http.StatusOK, &apiRepos)
} }


// ListUserRepos - list the repos owned by the given user. // ListUserRepos - list the repos owned by the given user.
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/RepositoryList" // "$ref": "#/responses/RepositoryList"

user := GetUserByParams(ctx) user := GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/RepositoryList" // "$ref": "#/responses/RepositoryList"

ownRepos, err := models.GetUserRepositories(ctx.User.ID, true, 1, ctx.User.NumRepos, "") ownRepos, err := models.GetUserRepositories(ctx.User.ID, true, 1, ctx.User.NumRepos, "")
if err != nil { if err != nil {
ctx.Error(500, "GetUserRepositories", err)
ctx.Error(http.StatusInternalServerError, "GetUserRepositories", err)
return return
} }
accessibleReposMap, err := ctx.User.GetRepositoryAccesses() accessibleReposMap, err := ctx.User.GetRepositoryAccesses()
if err != nil { if err != nil {
ctx.Error(500, "GetRepositoryAccesses", err)
ctx.Error(http.StatusInternalServerError, "GetRepositoryAccesses", err)
return return
} }


apiRepos[i] = repo.APIFormat(access) apiRepos[i] = repo.APIFormat(access)
i++ i++
} }
ctx.JSON(200, &apiRepos)
ctx.JSON(http.StatusOK, &apiRepos)
} }


// ListOrgRepos - list the repositories of an organization. // ListOrgRepos - list the repositories of an organization.
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/RepositoryList" // "$ref": "#/responses/RepositoryList"

listUserRepos(ctx, ctx.Org.Organization, ctx.IsSigned) listUserRepos(ctx, ctx.Org.Organization, ctx.IsSigned)
} }

+ 16
- 9
routers/api/v1/user/star.go View File

package user package user


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/RepositoryList" // "$ref": "#/responses/RepositoryList"

user := GetUserByParams(ctx) user := GetUserByParams(ctx)
private := user.ID == ctx.User.ID private := user.ID == ctx.User.ID
repos, err := getStarredRepos(user, private) repos, err := getStarredRepos(user, private)
if err != nil { if err != nil {
ctx.Error(500, "getStarredRepos", err)
ctx.Error(http.StatusInternalServerError, "getStarredRepos", err)
} }
ctx.JSON(200, &repos)
ctx.JSON(http.StatusOK, &repos)
} }


// GetMyStarredRepos returns the repos that the authenticated user has starred // GetMyStarredRepos returns the repos that the authenticated user has starred
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/RepositoryList" // "$ref": "#/responses/RepositoryList"

repos, err := getStarredRepos(ctx.User, true) repos, err := getStarredRepos(ctx.User, true)
if err != nil { if err != nil {
ctx.Error(500, "getStarredRepos", err)
ctx.Error(http.StatusInternalServerError, "getStarredRepos", err)
} }
ctx.JSON(200, &repos)
ctx.JSON(http.StatusOK, &repos)
} }


// IsStarring returns whether the authenticated is starring the repo // IsStarring returns whether the authenticated is starring the repo
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"

if models.IsStaring(ctx.User.ID, ctx.Repo.Repository.ID) { if models.IsStaring(ctx.User.ID, ctx.Repo.Repository.ID) {
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} else { } else {
ctx.NotFound() ctx.NotFound()
} }
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"

err := models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, true) err := models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, true)
if err != nil { if err != nil {
ctx.Error(500, "StarRepo", err)
ctx.Error(http.StatusInternalServerError, "StarRepo", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


// Unstar the repo specified in the APIContext, as the authenticated user // Unstar the repo specified in the APIContext, as the authenticated user
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"

err := models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, false) err := models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, false)
if err != nil { if err != nil {
ctx.Error(500, "StarRepo", err)
ctx.Error(http.StatusInternalServerError, "StarRepo", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }

+ 9
- 6
routers/api/v1/user/user.go View File

// type: array // type: array
// items: // items:
// "$ref": "#/definitions/User" // "$ref": "#/definitions/User"

opts := &models.SearchUserOptions{ opts := &models.SearchUserOptions{
Keyword: strings.Trim(ctx.Query("q"), " "), Keyword: strings.Trim(ctx.Query("q"), " "),
UID: com.StrTo(ctx.Query("uid")).MustInt64(), UID: com.StrTo(ctx.Query("uid")).MustInt64(),


users, _, err := models.SearchUsers(opts) users, _, err := models.SearchUsers(opts)
if err != nil { if err != nil {
ctx.JSON(500, map[string]interface{}{
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"ok": false, "ok": false,
"error": err.Error(), "error": err.Error(),
}) })
results[i] = convert.ToUser(users[i], ctx.IsSigned, ctx.User != nil && ctx.User.IsAdmin) results[i] = convert.ToUser(users[i], ctx.IsSigned, ctx.User != nil && ctx.User.IsAdmin)
} }


ctx.JSON(200, map[string]interface{}{
ctx.JSON(http.StatusOK, map[string]interface{}{
"ok": true, "ok": true,
"data": results, "data": results,
}) })
// "$ref": "#/responses/User" // "$ref": "#/responses/User"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"

u, err := models.GetUserByName(ctx.Params(":username")) u, err := models.GetUserByName(ctx.Params(":username"))
if err != nil { if err != nil {
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetUserByName", err)
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
} }
return return
} }


ctx.JSON(200, convert.ToUser(u, ctx.IsSigned, ctx.User != nil && (ctx.User.ID == u.ID || ctx.User.IsAdmin)))
ctx.JSON(http.StatusOK, convert.ToUser(u, ctx.IsSigned, ctx.User != nil && (ctx.User.ID == u.ID || ctx.User.IsAdmin)))
} }


// GetAuthenticatedUser get current user's information // GetAuthenticatedUser get current user's information
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/User" // "$ref": "#/responses/User"
ctx.JSON(200, convert.ToUser(ctx.User, ctx.IsSigned, ctx.User != nil))

ctx.JSON(http.StatusOK, convert.ToUser(ctx.User, ctx.IsSigned, ctx.User != nil))
} }


// GetUserHeatmapData is the handler to get a users heatmap // GetUserHeatmapData is the handler to get a users heatmap
ctx.Error(http.StatusInternalServerError, "GetUserHeatmapDataByUser", err) ctx.Error(http.StatusInternalServerError, "GetUserHeatmapDataByUser", err)
return return
} }
ctx.JSON(200, heatmap)
ctx.JSON(http.StatusOK, heatmap)
} }

+ 16
- 9
routers/api/v1/user/watch.go View File

package user package user


import ( import (
"net/http"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/RepositoryList" // "$ref": "#/responses/RepositoryList"

user := GetUserByParams(ctx) user := GetUserByParams(ctx)
private := user.ID == ctx.User.ID private := user.ID == ctx.User.ID
repos, err := getWatchedRepos(user, private) repos, err := getWatchedRepos(user, private)
if err != nil { if err != nil {
ctx.Error(500, "getWatchedRepos", err)
ctx.Error(http.StatusInternalServerError, "getWatchedRepos", err)
} }
ctx.JSON(200, &repos)
ctx.JSON(http.StatusOK, &repos)
} }


// GetMyWatchedRepos returns the repos that the authenticated user is watching // GetMyWatchedRepos returns the repos that the authenticated user is watching
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/RepositoryList" // "$ref": "#/responses/RepositoryList"

repos, err := getWatchedRepos(ctx.User, true) repos, err := getWatchedRepos(ctx.User, true)
if err != nil { if err != nil {
ctx.Error(500, "getWatchedRepos", err)
ctx.Error(http.StatusInternalServerError, "getWatchedRepos", err)
} }
ctx.JSON(200, &repos)
ctx.JSON(http.StatusOK, &repos)
} }


// IsWatching returns whether the authenticated user is watching the repo // IsWatching returns whether the authenticated user is watching the repo
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/WatchInfo" // "$ref": "#/responses/WatchInfo"

if models.IsWatching(ctx.User.ID, ctx.Repo.Repository.ID) { if models.IsWatching(ctx.User.ID, ctx.Repo.Repository.ID) {
ctx.JSON(200, api.WatchInfo{
ctx.JSON(http.StatusOK, api.WatchInfo{
Subscribed: true, Subscribed: true,
Ignored: false, Ignored: false,
Reason: nil, Reason: nil,
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/WatchInfo" // "$ref": "#/responses/WatchInfo"

err := models.WatchRepo(ctx.User.ID, ctx.Repo.Repository.ID, true) err := models.WatchRepo(ctx.User.ID, ctx.Repo.Repository.ID, true)
if err != nil { if err != nil {
ctx.Error(500, "WatchRepo", err)
ctx.Error(http.StatusInternalServerError, "WatchRepo", err)
return return
} }
ctx.JSON(200, api.WatchInfo{
ctx.JSON(http.StatusOK, api.WatchInfo{
Subscribed: true, Subscribed: true,
Ignored: false, Ignored: false,
Reason: nil, Reason: nil,
// responses: // responses:
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"

err := models.WatchRepo(ctx.User.ID, ctx.Repo.Repository.ID, false) err := models.WatchRepo(ctx.User.ID, ctx.Repo.Repository.ID, false)
if err != nil { if err != nil {
ctx.Error(500, "UnwatchRepo", err)
ctx.Error(http.StatusInternalServerError, "UnwatchRepo", err)
return return
} }
ctx.Status(204)
ctx.Status(http.StatusNoContent)
} }


// subscriptionURL returns the URL of the subscription API endpoint of a repo // subscriptionURL returns the URL of the subscription API endpoint of a repo

+ 16
- 16
routers/api/v1/utils/hook.go View File

if models.IsErrWebhookNotExist(err) { if models.IsErrWebhookNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetWebhookByOrgID", err)
ctx.Error(http.StatusInternalServerError, "GetWebhookByOrgID", err)
} }
return nil, err return nil, err
} }
if models.IsErrWebhookNotExist(err) { if models.IsErrWebhookNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(500, "GetWebhookByID", err)
ctx.Error(http.StatusInternalServerError, "GetWebhookByID", err)
} }
return nil, err return nil, err
} }
// write the appropriate error to `ctx`. Return whether the form is valid // write the appropriate error to `ctx`. Return whether the form is valid
func CheckCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption) bool { func CheckCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption) bool {
if !models.IsValidHookTaskType(form.Type) { if !models.IsValidHookTaskType(form.Type) {
ctx.Error(422, "", "Invalid hook type")
ctx.Error(http.StatusUnprocessableEntity, "", "Invalid hook type")
return false return false
} }
for _, name := range []string{"url", "content_type"} { for _, name := range []string{"url", "content_type"} {
if _, ok := form.Config[name]; !ok { if _, ok := form.Config[name]; !ok {
ctx.Error(422, "", "Missing config option: "+name)
ctx.Error(http.StatusUnprocessableEntity, "", "Missing config option: "+name)
return false return false
} }
} }
if !models.IsValidHookContentType(form.Config["content_type"]) { if !models.IsValidHookContentType(form.Config["content_type"]) {
ctx.Error(422, "", "Invalid content type")
ctx.Error(http.StatusUnprocessableEntity, "", "Invalid content type")
return false return false
} }
return true return true
if w.HookTaskType == models.SLACK { if w.HookTaskType == models.SLACK {
channel, ok := form.Config["channel"] channel, ok := form.Config["channel"]
if !ok { if !ok {
ctx.Error(422, "", "Missing config option: channel")
ctx.Error(http.StatusUnprocessableEntity, "", "Missing config option: channel")
return nil, false return nil, false
} }


if !utils.IsValidSlackChannel(channel) { if !utils.IsValidSlackChannel(channel) {
ctx.Error(400, "", "Invalid slack channel name")
ctx.Error(http.StatusBadRequest, "", "Invalid slack channel name")
return nil, false return nil, false
} }


Color: form.Config["color"], Color: form.Config["color"],
}) })
if err != nil { if err != nil {
ctx.Error(500, "slack: JSON marshal failed", err)
ctx.Error(http.StatusInternalServerError, "slack: JSON marshal failed", err)
return nil, false return nil, false
} }
w.Meta = string(meta) w.Meta = string(meta)
} }


if err := w.UpdateEvent(); err != nil { if err := w.UpdateEvent(); err != nil {
ctx.Error(500, "UpdateEvent", err)
ctx.Error(http.StatusInternalServerError, "UpdateEvent", err)
return nil, false return nil, false
} else if err := models.CreateWebhook(w); err != nil { } else if err := models.CreateWebhook(w); err != nil {
ctx.Error(500, "CreateWebhook", err)
ctx.Error(http.StatusInternalServerError, "CreateWebhook", err)
return nil, false return nil, false
} }
return w, true return w, true
if err != nil { if err != nil {
return return
} }
ctx.JSON(200, convert.ToHook(org.HomeLink(), updated))
ctx.JSON(http.StatusOK, convert.ToHook(org.HomeLink(), updated))
} }


// EditRepoHook edit webhook `w` according to `form`. Writes to `ctx` accordingly // EditRepoHook edit webhook `w` according to `form`. Writes to `ctx` accordingly
if err != nil { if err != nil {
return return
} }
ctx.JSON(200, convert.ToHook(repo.RepoLink, updated))
ctx.JSON(http.StatusOK, convert.ToHook(repo.RepoLink, updated))
} }


// editHook edit the webhook `w` according to `form`. If an error occurs, write // editHook edit the webhook `w` according to `form`. If an error occurs, write
} }
if ct, ok := form.Config["content_type"]; ok { if ct, ok := form.Config["content_type"]; ok {
if !models.IsValidHookContentType(ct) { if !models.IsValidHookContentType(ct) {
ctx.Error(422, "", "Invalid content type")
ctx.Error(http.StatusUnprocessableEntity, "", "Invalid content type")
return false return false
} }
w.ContentType = models.ToHookContentType(ct) w.ContentType = models.ToHookContentType(ct)
Color: form.Config["color"], Color: form.Config["color"],
}) })
if err != nil { if err != nil {
ctx.Error(500, "slack: JSON marshal failed", err)
ctx.Error(http.StatusInternalServerError, "slack: JSON marshal failed", err)
return false return false
} }
w.Meta = string(meta) w.Meta = string(meta)
w.BranchFilter = form.BranchFilter w.BranchFilter = form.BranchFilter


if err := w.UpdateEvent(); err != nil { if err := w.UpdateEvent(); err != nil {
ctx.Error(500, "UpdateEvent", err)
ctx.Error(http.StatusInternalServerError, "UpdateEvent", err)
return false return false
} }


} }


if err := models.UpdateWebhook(w); err != nil { if err := models.UpdateWebhook(w); err != nil {
ctx.Error(500, "UpdateWebhook", err)
ctx.Error(http.StatusInternalServerError, "UpdateWebhook", err)
return false return false
} }
return true return true

+ 237
- 10
templates/swagger/v1_json.tmpl View File

"201": { "201": {
"$ref": "#/responses/User" "$ref": "#/responses/User"
}, },
"400": {
"$ref": "#/responses/error"
},
"403": { "403": {
"$ref": "#/responses/forbidden" "$ref": "#/responses/forbidden"
}, },
"403": { "403": {
"$ref": "#/responses/forbidden" "$ref": "#/responses/forbidden"
}, },
"404": {
"$ref": "#/responses/notFound"
},
"409": {
"$ref": "#/responses/error"
},
"422": { "422": {
"$ref": "#/responses/validationError" "$ref": "#/responses/validationError"
} }
"204": { "204": {
"description": "user is a member" "description": "user is a member"
}, },
"302": {
"description": "redirection to /orgs/{org}/public_members/{username}"
},
"404": { "404": {
"description": "user is not a member" "description": "user is not a member"
} }
"responses": { "responses": {
"204": { "204": {
"description": "membership publicized" "description": "membership publicized"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
}, },
"responses": { "responses": {
"204": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
} }
"responses": { "responses": {
"201": { "201": {
"$ref": "#/responses/Team" "$ref": "#/responses/Team"
},
"422": {
"$ref": "#/responses/validationError"
} }
} }
} }
"responses": { "responses": {
"201": { "201": {
"$ref": "#/responses/Repository" "$ref": "#/responses/Repository"
},
"403": {
"$ref": "#/responses/forbidden"
},
"422": {
"$ref": "#/responses/validationError"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"description": "success" "description": "success"
},
"404": {
"$ref": "#/responses/notFound"
} }
} }
} }
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
}, },
"404": { "404": {
"$ref": "#/responses/empty"
"$ref": "#/responses/notFound"
},
"422": {
"$ref": "#/responses/validationError"
} }
} }
}, },
"responses": { "responses": {
"204": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"422": {
"$ref": "#/responses/validationError"
} }
} }
}, },
"responses": { "responses": {
"204": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"422": {
"$ref": "#/responses/validationError"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/Status" "$ref": "#/responses/Status"
},
"400": {
"$ref": "#/responses/error"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"description": "success" "description": "success"
},
"404": {
"$ref": "#/responses/notFound"
} }
} }
} }
"responses": { "responses": {
"202": { "202": {
"$ref": "#/responses/Repository" "$ref": "#/responses/Repository"
},
"403": {
"$ref": "#/responses/forbidden"
},
"422": {
"$ref": "#/responses/validationError"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/GitBlobResponse" "$ref": "#/responses/GitBlobResponse"
},
"400": {
"$ref": "#/responses/error"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/AnnotatedTag" "$ref": "#/responses/AnnotatedTag"
},
"400": {
"$ref": "#/responses/error"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/GitTreeResponse" "$ref": "#/responses/GitTreeResponse"
},
"400": {
"$ref": "#/responses/error"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/Hook" "$ref": "#/responses/Hook"
},
"404": {
"$ref": "#/responses/notFound"
} }
} }
}, },
"responses": { "responses": {
"201": { "201": {
"$ref": "#/responses/Issue" "$ref": "#/responses/Issue"
},
"403": {
"$ref": "#/responses/forbidden"
},
"412": {
"$ref": "#/responses/error"
},
"422": {
"$ref": "#/responses/validationError"
} }
} }
} }
"responses": { "responses": {
"204": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
}, },
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/ReactionResponseList" "$ref": "#/responses/ReactionResponseList"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
}, },
"responses": { "responses": {
"201": { "201": {
"$ref": "#/responses/ReactionResponse" "$ref": "#/responses/ReactionResponse"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
}, },
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/TrackedTimeList" "$ref": "#/responses/TrackedTimeList"
},
"404": {
"$ref": "#/responses/notFound"
} }
} }
}, },
"$ref": "#/responses/error" "$ref": "#/responses/error"
}, },
"403": { "403": {
"$ref": "#/responses/error"
"$ref": "#/responses/forbidden"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/Issue" "$ref": "#/responses/Issue"
},
"404": {
"$ref": "#/responses/notFound"
} }
} }
}, },
"responses": { "responses": {
"201": { "201": {
"$ref": "#/responses/Issue" "$ref": "#/responses/Issue"
},
"403": {
"$ref": "#/responses/forbidden"
},
"404": {
"$ref": "#/responses/notFound"
},
"412": {
"$ref": "#/responses/error"
} }
} }
} }
"responses": { "responses": {
"201": { "201": {
"$ref": "#/responses/Comment" "$ref": "#/responses/Comment"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
} }
"responses": { "responses": {
"204": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
}, },
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/Comment" "$ref": "#/responses/Comment"
},
"204": {
"$ref": "#/responses/empty"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
} }
"$ref": "#/responses/IssueDeadline" "$ref": "#/responses/IssueDeadline"
}, },
"403": { "403": {
"description": "Not repo writer"
"$ref": "#/responses/forbidden"
}, },
"404": { "404": {
"description": "Issue not found"
"$ref": "#/responses/notFound"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/LabelList" "$ref": "#/responses/LabelList"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
}, },
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/LabelList" "$ref": "#/responses/LabelList"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
}, },
"responses": { "responses": {
"204": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
} }
"responses": { "responses": {
"204": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"403": {
"$ref": "#/responses/forbidden"
},
"422": {
"$ref": "#/responses/validationError"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/ReactionResponseList" "$ref": "#/responses/ReactionResponseList"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
}, },
"responses": { "responses": {
"201": { "201": {
"$ref": "#/responses/ReactionResponse" "$ref": "#/responses/ReactionResponse"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
}, },
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
} }
"description": "Not repo writer, user does not have rights to toggle stopwatch" "description": "Not repo writer, user does not have rights to toggle stopwatch"
}, },
"404": { "404": {
"description": "Issue not found"
"$ref": "#/responses/notFound"
}, },
"409": { "409": {
"description": "Cannot cancel a non existent stopwatch" "description": "Cannot cancel a non existent stopwatch"
"description": "Not repo writer, user does not have rights to toggle stopwatch" "description": "Not repo writer, user does not have rights to toggle stopwatch"
}, },
"404": { "404": {
"description": "Issue not found"
"$ref": "#/responses/notFound"
}, },
"409": { "409": {
"description": "Cannot start a stopwatch again if it already exists" "description": "Cannot start a stopwatch again if it already exists"
"description": "Not repo writer, user does not have rights to toggle stopwatch" "description": "Not repo writer, user does not have rights to toggle stopwatch"
}, },
"404": { "404": {
"description": "Issue not found"
"$ref": "#/responses/notFound"
}, },
"409": { "409": {
"description": "Cannot stop a non existent stopwatch" "description": "Cannot stop a non existent stopwatch"
"$ref": "#/responses/UserList" "$ref": "#/responses/UserList"
}, },
"404": { "404": {
"description": "Issue not found"
"$ref": "#/responses/notFound"
} }
} }
} }
"description": "User can only subscribe itself if he is no admin" "description": "User can only subscribe itself if he is no admin"
}, },
"404": { "404": {
"description": "Issue not found"
"$ref": "#/responses/notFound"
} }
} }
}, },
"description": "User can only subscribe itself if he is no admin" "description": "User can only subscribe itself if he is no admin"
}, },
"404": { "404": {
"description": "Issue not found"
"$ref": "#/responses/notFound"
} }
} }
} }
"responses": { "responses": {
"201": { "201": {
"$ref": "#/responses/DeployKey" "$ref": "#/responses/DeployKey"
},
"422": {
"$ref": "#/responses/validationError"
} }
} }
} }
"responses": { "responses": {
"204": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
} }
"responses": { "responses": {
"201": { "201": {
"$ref": "#/responses/PullRequest" "$ref": "#/responses/PullRequest"
},
"409": {
"$ref": "#/responses/error"
},
"422": {
"$ref": "#/responses/validationError"
} }
} }
} }
"responses": { "responses": {
"201": { "201": {
"$ref": "#/responses/PullRequest" "$ref": "#/responses/PullRequest"
},
"403": {
"$ref": "#/responses/forbidden"
},
"412": {
"$ref": "#/responses/error"
},
"422": {
"$ref": "#/responses/validationError"
} }
} }
} }
}, },
"405": { "405": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"409": {
"$ref": "#/responses/error"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"description": "success" "description": "success"
},
"404": {
"$ref": "#/responses/notFound"
} }
} }
} }
"responses": { "responses": {
"201": { "201": {
"$ref": "#/responses/Release" "$ref": "#/responses/Release"
},
"409": {
"$ref": "#/responses/error"
} }
} }
} }
"responses": { "responses": {
"201": { "201": {
"$ref": "#/responses/Attachment" "$ref": "#/responses/Attachment"
},
"400": {
"$ref": "#/responses/error"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/StatusList" "$ref": "#/responses/StatusList"
},
"400": {
"$ref": "#/responses/error"
} }
} }
}, },
"responses": { "responses": {
"201": { "201": {
"$ref": "#/responses/Status" "$ref": "#/responses/Status"
},
"400": {
"$ref": "#/responses/error"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/TrackedTimeList" "$ref": "#/responses/TrackedTimeList"
},
"400": {
"$ref": "#/responses/error"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/TrackedTimeList" "$ref": "#/responses/TrackedTimeList"
},
"400": {
"$ref": "#/responses/error"
} }
} }
} }
"responses": { "responses": {
"204": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"422": {
"$ref": "#/responses/invalidTopicsError"
} }
} }
} }
"responses": { "responses": {
"204": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"422": {
"$ref": "#/responses/invalidTopicsError"
} }
} }
}, },
"responses": { "responses": {
"204": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"422": {
"$ref": "#/responses/invalidTopicsError"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/User" "$ref": "#/responses/User"
},
"404": {
"$ref": "#/responses/notFound"
} }
} }
}, },
"responses": { "responses": {
"204": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"404": {
"$ref": "#/responses/notFound"
} }
} }
}, },
"responses": { "responses": {
"204": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"404": {
"$ref": "#/responses/notFound"
} }
} }
} }
"responses": { "responses": {
"204": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
}, },
"responses": { "responses": {
"204": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/TopicListResponse" "$ref": "#/responses/TopicListResponse"
},
"403": {
"$ref": "#/responses/forbidden"
} }
} }
} }
"responses": { "responses": {
"201": { "201": {
"$ref": "#/responses/EmailList" "$ref": "#/responses/EmailList"
},
"422": {
"$ref": "#/responses/validationError"
} }
} }
}, },
} }
} }
}, },
"invalidTopicsError": {
"description": "APIInvalidTopicsError is error format response to invalid topics",
"headers": {
"invalidTopics": {
"type": "array",
"items": {
"type": "string"
}
},
"message": {
"type": "string"
}
}
},
"notFound": { "notFound": {
"description": "APINotFound is a not found empty response" "description": "APINotFound is a not found empty response"
}, },

Loading…
Cancel
Save