This commit is contained in:
@@ -6,7 +6,7 @@ package activitypub
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
func NotImplemented(ctx *context.APIContext) {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/routers/api/v1/shared"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"gitea.dev/routers/api/v1/shared"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// ListWorkflowJobs Lists all jobs
|
||||
@@ -29,6 +29,14 @@ func ListWorkflowJobs(ctx *context.APIContext) {
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// - name: sort
|
||||
// in: query
|
||||
// description: sort jobs by attribute. Supported values are "id". Default is "id"
|
||||
// type: string
|
||||
// - name: order
|
||||
// in: query
|
||||
// description: sort order, either "asc" (ascending) or "desc" (descending). Default is "asc"
|
||||
// type: string
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/WorkflowJobsList"
|
||||
@@ -36,8 +44,10 @@ func ListWorkflowJobs(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
shared.ListJobs(ctx, 0, 0, 0)
|
||||
shared.ListJobs(ctx, 0, 0, 0, nil)
|
||||
}
|
||||
|
||||
// ListWorkflowRuns Lists all runs
|
||||
@@ -89,5 +99,5 @@ func ListWorkflowRuns(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
shared.ListRuns(ctx, 0, 0)
|
||||
shared.ListRuns(ctx, 0, 0, "")
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ package admin
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/gitrepo"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
)
|
||||
|
||||
// ListUnadoptedRepositories lists the unadopted repositories that match the provided names
|
||||
|
||||
@@ -6,12 +6,12 @@ package admin
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/cron"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/cron"
|
||||
)
|
||||
|
||||
// ListCronTasks api for getting cron tasks
|
||||
|
||||
@@ -6,11 +6,11 @@ package admin
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// GetAllEmails
|
||||
|
||||
@@ -7,15 +7,15 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/webhook"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
webhook_service "code.gitea.io/gitea/services/webhook"
|
||||
"gitea.dev/models/webhook"
|
||||
"gitea.dev/modules/optional"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
webhook_service "gitea.dev/services/webhook"
|
||||
)
|
||||
|
||||
// ListHooks list system's webhooks
|
||||
|
||||
@@ -7,14 +7,14 @@ package admin
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/organization"
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// CreateOrg api for create organization
|
||||
@@ -48,7 +48,7 @@ func CreateOrg(ctx *context.APIContext) {
|
||||
|
||||
visibility := api.VisibleTypePublic
|
||||
if form.Visibility != "" {
|
||||
visibility = api.VisibilityModes[form.Visibility]
|
||||
visibility = api.VisibilityModes[string(form.Visibility)]
|
||||
}
|
||||
|
||||
org := &organization.Organization{
|
||||
@@ -67,7 +67,7 @@ func CreateOrg(ctx *context.APIContext) {
|
||||
db.IsErrNameReserved(err) ||
|
||||
db.IsErrNameCharsNotAllowed(err) ||
|
||||
db.IsErrNamePatternNotAllowed(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/repo"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/repo"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// CreateRepo api for creating a repository
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/routers/api/v1/shared"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"gitea.dev/routers/api/v1/shared"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
|
||||
|
||||
@@ -9,27 +9,27 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
org_model "code.gitea.io/gitea/models/organization"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/auth/password"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/user"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"code.gitea.io/gitea/services/mailer"
|
||||
user_service "code.gitea.io/gitea/services/user"
|
||||
asymkey_model "gitea.dev/models/asymkey"
|
||||
"gitea.dev/models/auth"
|
||||
"gitea.dev/models/db"
|
||||
org_model "gitea.dev/models/organization"
|
||||
packages_model "gitea.dev/models/packages"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/auth/password"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/optional"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/user"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
asymkey_service "gitea.dev/services/asymkey"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
"gitea.dev/services/mailer"
|
||||
user_service "gitea.dev/services/user"
|
||||
)
|
||||
|
||||
func parseAuthSource(ctx *context.APIContext, u *user_model.User, sourceID int64) {
|
||||
@@ -40,7 +40,7 @@ func parseAuthSource(ctx *context.APIContext, u *user_model.User, sourceID int64
|
||||
source, err := auth.GetSourceByID(ctx, sourceID)
|
||||
if err != nil {
|
||||
if auth.IsErrSourceNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -97,14 +97,12 @@ func CreateUser(ctx *context.APIContext) {
|
||||
|
||||
if u.LoginType == auth.Plain {
|
||||
if len(form.Password) < setting.MinPasswordLength {
|
||||
err := errors.New("PasswordIsRequired")
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, "PasswordIsRequired")
|
||||
return
|
||||
}
|
||||
|
||||
if !password.IsComplexEnough(form.Password) {
|
||||
err := errors.New("PasswordComplexity")
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, "PasswordComplexity")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -112,7 +110,7 @@ func CreateUser(ctx *context.APIContext) {
|
||||
if password.IsErrIsPwnedRequest(err) {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
ctx.APIError(http.StatusBadRequest, errors.New("PasswordPwned"))
|
||||
ctx.APIError(http.StatusBadRequest, "PasswordPwned")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -123,7 +121,7 @@ func CreateUser(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
if form.Visibility != "" {
|
||||
visibility := api.VisibilityModes[form.Visibility]
|
||||
visibility := api.VisibilityModes[string(form.Visibility)]
|
||||
overwriteDefault.Visibility = &visibility
|
||||
}
|
||||
|
||||
@@ -143,7 +141,7 @@ func CreateUser(ctx *context.APIContext) {
|
||||
user_model.IsErrEmailCharIsNotSupported(err) ||
|
||||
user_model.IsErrEmailInvalid(err) ||
|
||||
db.IsErrNamePatternNotAllowed(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -196,7 +194,7 @@ func EditUser(ctx *context.APIContext) {
|
||||
|
||||
authOpts := &user_service.UpdateAuthOptions{
|
||||
LoginSource: optional.FromNonDefault(form.SourceID),
|
||||
LoginName: optional.Some(form.LoginName),
|
||||
LoginName: optional.FromPtr(form.LoginName),
|
||||
Password: optional.FromNonDefault(form.Password),
|
||||
MustChangePassword: optional.FromPtr(form.MustChangePassword),
|
||||
ProhibitLogin: optional.FromPtr(form.ProhibitLogin),
|
||||
@@ -204,11 +202,11 @@ func EditUser(ctx *context.APIContext) {
|
||||
if err := user_service.UpdateAuth(ctx, ctx.ContextUser, authOpts); err != nil {
|
||||
switch {
|
||||
case errors.Is(err, password.ErrMinLength):
|
||||
ctx.APIError(http.StatusBadRequest, fmt.Errorf("password must be at least %d characters", setting.MinPasswordLength))
|
||||
ctx.APIError(http.StatusBadRequest, fmt.Sprintf("password must be at least %d characters", setting.MinPasswordLength))
|
||||
case errors.Is(err, password.ErrComplexity):
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
case errors.Is(err, password.ErrIsPwned), password.IsErrIsPwnedRequest(err):
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
default:
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -222,9 +220,9 @@ func EditUser(ctx *context.APIContext) {
|
||||
if !user_model.IsEmailDomainAllowed(*form.Email) {
|
||||
err = fmt.Errorf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", *form.Email)
|
||||
}
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
case user_model.IsErrEmailAlreadyUsed(err):
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
default:
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -239,7 +237,7 @@ func EditUser(ctx *context.APIContext) {
|
||||
Description: optional.FromPtr(form.Description),
|
||||
IsActive: optional.FromPtr(form.Active),
|
||||
IsAdmin: user_service.UpdateOptionFieldFromPtr(form.Admin),
|
||||
Visibility: optional.FromMapLookup(api.VisibilityModes, form.Visibility),
|
||||
Visibility: optional.FromMapLookup(api.VisibilityModes, string(form.Visibility)),
|
||||
AllowGitHook: optional.FromPtr(form.AllowGitHook),
|
||||
AllowImportLocal: optional.FromPtr(form.AllowImportLocal),
|
||||
MaxRepoCreation: optional.FromPtr(form.MaxRepoCreation),
|
||||
@@ -249,7 +247,7 @@ func EditUser(ctx *context.APIContext) {
|
||||
|
||||
if err := user_service.UpdateUser(ctx, ctx.ContextUser, opts); err != nil {
|
||||
if user_model.IsErrDeleteLastAdminUser(err) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -289,13 +287,13 @@ func DeleteUser(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
if ctx.ContextUser.IsOrganization() {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("%s is an organization not a user", ctx.ContextUser.Name))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "target is an organization but not user")
|
||||
return
|
||||
}
|
||||
|
||||
// admin should not delete themself
|
||||
if ctx.ContextUser.ID == ctx.Doer.ID {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, errors.New("you cannot delete yourself"))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "you cannot delete yourself")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -304,7 +302,7 @@ func DeleteUser(ctx *context.APIContext) {
|
||||
org_model.IsErrUserHasOrgs(err) ||
|
||||
packages_model.IsErrUserOwnPackages(err) ||
|
||||
user_model.IsErrDeleteLastAdminUser(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -464,35 +462,18 @@ func SearchUsers(ctx *context.APIContext) {
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
orderBy := db.SearchOrderByAlphabetically
|
||||
sortMode := ctx.FormString("sort")
|
||||
if len(sortMode) > 0 {
|
||||
sortOrder := ctx.FormString("order")
|
||||
if len(sortOrder) == 0 {
|
||||
sortOrder = "asc"
|
||||
}
|
||||
if searchModeMap, ok := user_model.AdminUserOrderByMap[sortOrder]; ok {
|
||||
if order, ok := searchModeMap[sortMode]; ok {
|
||||
orderBy = order
|
||||
} else {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort mode: \"%s\"", sortMode))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort order: \"%s\"", sortOrder))
|
||||
return
|
||||
}
|
||||
orderBy, ok := utils.ResolveSortOrder(ctx, user_model.AdminUserOrderByMap, db.SearchOrderByAlphabetically)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var visible []api.VisibleType
|
||||
visibilityParam := ctx.FormString("visibility")
|
||||
if len(visibilityParam) > 0 {
|
||||
if visibility, ok := api.VisibilityModes[visibilityParam]; ok {
|
||||
visible = []api.VisibleType{visibility}
|
||||
} else {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid visibility: \"%s\"", visibilityParam))
|
||||
return
|
||||
}
|
||||
if visibility, ok := api.VisibilityModes[visibilityParam]; ok {
|
||||
visible = []api.VisibleType{visibility}
|
||||
} else if visibilityParam != "" {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "invalid visibility")
|
||||
return
|
||||
}
|
||||
|
||||
searchOpts := user_model.SearchUserOptions{
|
||||
@@ -566,7 +547,7 @@ func RenameUser(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
if ctx.ContextUser.IsOrganization() {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("%s is an organization not a user", ctx.ContextUser.Name))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "target is an organization but not user")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -575,7 +556,7 @@ func RenameUser(ctx *context.APIContext) {
|
||||
// Check if username has been changed
|
||||
if err := user_service.RenameUser(ctx, ctx.ContextUser, newName, ctx.Doer); err != nil {
|
||||
if user_model.IsErrUserAlreadyExist(err) || db.IsErrNameReserved(err) || db.IsErrNamePatternNotAllowed(err) || db.IsErrNameCharsNotAllowed(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ package admin
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// ListUserBadges lists all badges belonging to a user
|
||||
|
||||
@@ -68,36 +68,38 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/activitypub"
|
||||
"code.gitea.io/gitea/routers/api/v1/admin"
|
||||
"code.gitea.io/gitea/routers/api/v1/misc"
|
||||
"code.gitea.io/gitea/routers/api/v1/notify"
|
||||
"code.gitea.io/gitea/routers/api/v1/org"
|
||||
"code.gitea.io/gitea/routers/api/v1/packages"
|
||||
"code.gitea.io/gitea/routers/api/v1/repo"
|
||||
"code.gitea.io/gitea/routers/api/v1/settings"
|
||||
"code.gitea.io/gitea/routers/api/v1/user"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/services/actions"
|
||||
"code.gitea.io/gitea/services/auth"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
auth_model "gitea.dev/models/auth"
|
||||
"gitea.dev/models/organization"
|
||||
"gitea.dev/models/perm"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unit"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/activitypub"
|
||||
"gitea.dev/routers/api/v1/admin"
|
||||
"gitea.dev/routers/api/v1/misc"
|
||||
"gitea.dev/routers/api/v1/notify"
|
||||
"gitea.dev/routers/api/v1/org"
|
||||
"gitea.dev/routers/api/v1/packages"
|
||||
"gitea.dev/routers/api/v1/repo"
|
||||
"gitea.dev/routers/api/v1/settings"
|
||||
"gitea.dev/routers/api/v1/token"
|
||||
"gitea.dev/routers/api/v1/user"
|
||||
"gitea.dev/routers/common"
|
||||
"gitea.dev/services/actions"
|
||||
"gitea.dev/services/auth"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/forms"
|
||||
|
||||
_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
|
||||
_ "gitea.dev/routers/api/v1/swagger" // for swagger generation
|
||||
|
||||
"gitea.com/go-chi/binding"
|
||||
chi_middleware "github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
)
|
||||
|
||||
@@ -151,7 +153,7 @@ func repoAssignment() func(ctx *context.APIContext) {
|
||||
if redirectUserID, err := user_model.LookupUserRedirect(ctx, userName); err == nil {
|
||||
context.RedirectToUser(ctx.Base, ctx.Doer, userName, redirectUserID)
|
||||
} else if user_model.IsErrUserRedirectNotExist(err) {
|
||||
ctx.APIErrorNotFound("GetUserByName", err)
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -289,7 +291,9 @@ func checkTokenPublicOnly() func(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
case auth_model.AccessTokenScopeCategoryPackage:
|
||||
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
|
||||
// a public-only token must not reach limited-visibility owners either,
|
||||
// matching the org/user public-only enforcement above
|
||||
if ctx.Package != nil && !ctx.Package.Owner.Visibility.IsPublic() {
|
||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public packages")
|
||||
return
|
||||
}
|
||||
@@ -414,7 +418,7 @@ func reqSiteAdmin() func(ctx *context.APIContext) {
|
||||
// reqOwner user should be the owner of the repo or site admin.
|
||||
func reqOwner() func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if !ctx.Repo.IsOwner() && !ctx.IsUserSiteAdmin() {
|
||||
if !ctx.Repo.Permission.IsOwner() && !ctx.IsUserSiteAdmin() {
|
||||
ctx.APIError(http.StatusForbidden, "user should be the owner of the repo")
|
||||
return
|
||||
}
|
||||
@@ -454,7 +458,7 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) {
|
||||
// reqRepoReader user should have specific read permission or be a repo admin or a site admin
|
||||
func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if !ctx.Repo.CanRead(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
|
||||
if !ctx.Repo.Permission.CanRead(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
|
||||
ctx.APIError(http.StatusForbidden, "user should have specific read permission or be a repo admin or a site admin")
|
||||
return
|
||||
}
|
||||
@@ -504,41 +508,94 @@ func reqOrgOwnership() func(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
// reqTeamMembership user should be an team member, or a site admin
|
||||
func reqTeamMembership() func(ctx *context.APIContext) {
|
||||
// reqOrgVisible requires the organization to be visible to the doer, or a site admin
|
||||
func reqOrgVisible() func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if ctx.IsUserSiteAdmin() {
|
||||
if ctx.Org.Organization == nil {
|
||||
setting.PanicInDevOrTesting("reqOrgVisible: unprepared context")
|
||||
ctx.APIErrorInternal(errors.New("reqOrgVisible: unprepared context"))
|
||||
return
|
||||
}
|
||||
if ctx.Org.Team == nil {
|
||||
setting.PanicInDevOrTesting("reqTeamMembership: unprepared context")
|
||||
ctx.APIErrorInternal(errors.New("reqTeamMembership: unprepared context"))
|
||||
if !organization.HasOrgOrUserVisible(ctx, ctx.Org.Organization.AsUser(), ctx.Doer) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func teamAccessPrivileged(ctx *context.APIContext) (orgID int64, privileged, ok bool) {
|
||||
if ctx.IsUserSiteAdmin() {
|
||||
return 0, true, true
|
||||
}
|
||||
if ctx.Org.Team == nil {
|
||||
setting.PanicInDevOrTesting("teamAccess: unprepared context")
|
||||
ctx.APIErrorInternal(errors.New("teamAccess: unprepared context"))
|
||||
return 0, false, false
|
||||
}
|
||||
|
||||
orgID = ctx.Org.Team.OrgID
|
||||
isOwner, err := organization.IsOrganizationOwner(ctx, orgID, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return 0, false, false
|
||||
} else if isOwner {
|
||||
return orgID, true, true
|
||||
}
|
||||
|
||||
isTeamMember, err := organization.IsTeamMember(ctx, orgID, ctx.Org.Team.ID, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return 0, false, false
|
||||
}
|
||||
return orgID, isTeamMember, true
|
||||
}
|
||||
|
||||
func denyNonTeamMember(ctx *context.APIContext, orgID int64) {
|
||||
isOrgMember, err := organization.IsOrganizationMember(ctx, orgID, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
} else if isOrgMember {
|
||||
ctx.APIError(http.StatusForbidden, "Must be a team member")
|
||||
} else {
|
||||
ctx.APIErrorNotFound()
|
||||
}
|
||||
}
|
||||
|
||||
// reqTeamReadAccess allows callers who can list the team to read its metadata.
|
||||
// Non-members are admitted by the team's visibility tier and parent org visibility.
|
||||
// Not sufficient for mutations — use reqOrgOwnership() or reqTeamMembership() for those.
|
||||
func reqTeamReadAccess() func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
orgID, privileged, ok := teamAccessPrivileged(ctx)
|
||||
if !ok || privileged {
|
||||
return
|
||||
}
|
||||
if ctx.Org.Organization == nil {
|
||||
setting.PanicInDevOrTesting("reqTeamReadAccess: organization not loaded")
|
||||
ctx.APIErrorInternal(errors.New("reqTeamReadAccess: organization not loaded"))
|
||||
return
|
||||
}
|
||||
|
||||
orgID := ctx.Org.Team.OrgID
|
||||
isOwner, err := organization.IsOrganizationOwner(ctx, orgID, ctx.Doer.ID)
|
||||
visible, err := ctx.Org.Team.CanNonMemberReadMeta(ctx, ctx.Org.Organization.AsUser(), ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
} else if isOwner {
|
||||
return
|
||||
}
|
||||
if !visible {
|
||||
// Not admitted by visibility: 403 for org members, 404 otherwise.
|
||||
denyNonTeamMember(ctx, orgID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isTeamMember, err := organization.IsTeamMember(ctx, orgID, ctx.Org.Team.ID, ctx.Doer.ID); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
} else if !isTeamMember {
|
||||
isOrgMember, err := organization.IsOrganizationMember(ctx, orgID, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
} else if isOrgMember {
|
||||
ctx.APIError(http.StatusForbidden, "Must be a team member")
|
||||
} else {
|
||||
ctx.APIErrorNotFound()
|
||||
}
|
||||
// reqTeamMembership user should be a team member, or a site admin
|
||||
func reqTeamMembership() func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
orgID, privileged, ok := teamAccessPrivileged(ctx)
|
||||
if !ok || privileged {
|
||||
return
|
||||
}
|
||||
denyNonTeamMember(ctx, orgID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,7 +683,7 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) {
|
||||
if err == nil {
|
||||
context.RedirectToUser(ctx.Base, ctx.Doer, ctx.PathParam("org"), redirectUserID)
|
||||
} else if user_model.IsErrUserRedirectNotExist(err) {
|
||||
ctx.APIErrorNotFound("GetOrgByName", err)
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -648,12 +705,23 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if ctx.Org.Organization == nil {
|
||||
ctx.Org.Organization, err = organization.GetOrgByID(ctx, ctx.Org.Team.OrgID)
|
||||
if err != nil {
|
||||
if organization.IsErrOrgNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mustEnableIssues(ctx *context.APIContext) {
|
||||
if !ctx.Repo.CanRead(unit.TypeIssues) {
|
||||
if !ctx.Repo.Permission.CanRead(unit.TypeIssues) {
|
||||
if log.IsTrace() {
|
||||
if ctx.IsSigned {
|
||||
log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+
|
||||
@@ -676,7 +744,7 @@ func mustEnableIssues(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
func mustAllowPulls(ctx *context.APIContext) {
|
||||
if !(ctx.Repo.Repository.CanEnablePulls() && ctx.Repo.CanRead(unit.TypePullRequests)) {
|
||||
if !(ctx.Repo.Repository.CanEnablePulls() && ctx.Repo.Permission.CanRead(unit.TypePullRequests)) {
|
||||
if ctx.Repo.Repository.CanEnablePulls() && log.IsTrace() {
|
||||
if ctx.IsSigned {
|
||||
log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+
|
||||
@@ -699,8 +767,8 @@ func mustAllowPulls(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
func mustEnableIssuesOrPulls(ctx *context.APIContext) {
|
||||
if !ctx.Repo.CanRead(unit.TypeIssues) &&
|
||||
!(ctx.Repo.Repository.CanEnablePulls() && ctx.Repo.CanRead(unit.TypePullRequests)) {
|
||||
if !ctx.Repo.Permission.CanRead(unit.TypeIssues) &&
|
||||
!(ctx.Repo.Repository.CanEnablePulls() && ctx.Repo.Permission.CanRead(unit.TypePullRequests)) {
|
||||
if ctx.Repo.Repository.CanEnablePulls() && log.IsTrace() {
|
||||
if ctx.IsSigned {
|
||||
log.Trace("Permission Denied: User %-v cannot read %-v and %-v in Repo %-v\n"+
|
||||
@@ -725,7 +793,7 @@ func mustEnableIssuesOrPulls(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
func mustEnableWiki(ctx *context.APIContext) {
|
||||
if !(ctx.Repo.CanRead(unit.TypeWiki)) {
|
||||
if !(ctx.Repo.Permission.CanRead(unit.TypeWiki)) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
@@ -734,14 +802,14 @@ func mustEnableWiki(ctx *context.APIContext) {
|
||||
// FIXME: for consistency, maybe most mustNotBeArchived checks should be replaced with mustEnableEditor
|
||||
func mustNotBeArchived(ctx *context.APIContext) {
|
||||
if ctx.Repo.Repository.IsArchived {
|
||||
ctx.APIError(http.StatusLocked, fmt.Errorf("%s is archived", ctx.Repo.Repository.FullName()))
|
||||
ctx.APIError(http.StatusLocked, "repo is archived")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func mustEnableEditor(ctx *context.APIContext) {
|
||||
if !ctx.Repo.Repository.CanEnableEditor() {
|
||||
ctx.APIError(http.StatusLocked, fmt.Errorf("%s is not allowed to edit", ctx.Repo.Repository.FullName()))
|
||||
ctx.APIError(http.StatusLocked, "repo is not allowed to edit")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -858,19 +926,8 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIC
|
||||
|
||||
func individualPermsChecker(ctx *context.APIContext) {
|
||||
// org permissions have been checked in context.OrgAssignment(), but individual permissions haven't been checked.
|
||||
if ctx.ContextUser.IsIndividual() {
|
||||
switch ctx.ContextUser.Visibility {
|
||||
case api.VisibleTypePrivate:
|
||||
if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) {
|
||||
ctx.APIErrorNotFound("Visit Project", nil)
|
||||
return
|
||||
}
|
||||
case api.VisibleTypeLimited:
|
||||
if ctx.Doer == nil {
|
||||
ctx.APIErrorNotFound("Visit Project", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
if ctx.ContextUser.IsIndividual() && !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) {
|
||||
ctx.APIErrorNotFound()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -885,7 +942,9 @@ func checkDeprecatedAuthMethods(ctx *context.APIContext) {
|
||||
func Routes() *web.Router {
|
||||
m := web.NewRouter()
|
||||
|
||||
m.BeforeRouting(securityHeaders())
|
||||
// redirect HEAD requests to GET if no HEAD handler is defined (RFC 9110 §9.3.2)
|
||||
m.BeforeRouting(chi_middleware.GetHead)
|
||||
|
||||
if setting.CORSConfig.Enabled {
|
||||
m.BeforeRouting(cors.Handler(cors.Options{
|
||||
AllowedOrigins: setting.CORSConfig.AllowDomain,
|
||||
@@ -977,6 +1036,11 @@ func Routes() *web.Router {
|
||||
})
|
||||
})
|
||||
|
||||
// Token introspection and deletion endpoint
|
||||
m.Combo("/token").
|
||||
Get(reqToken(), token.GetCurrentToken).
|
||||
Delete(reqToken(), token.DeleteCurrentToken)
|
||||
|
||||
// Notifications (requires 'notifications' scope)
|
||||
// The notifications API is not available for public-only tokens because a user's notifications mix
|
||||
// public and private repository events in the same mailbox.
|
||||
@@ -1027,7 +1091,7 @@ func Routes() *web.Router {
|
||||
m.Get("/starred", reqStarsEnabled(), user.GetStarredRepos)
|
||||
|
||||
m.Get("/subscriptions", user.GetWatchedRepos)
|
||||
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
|
||||
}, context.UserAssignmentAPI(), checkTokenPublicOnly(), individualPermsChecker)
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
|
||||
|
||||
// Users (requires user scope)
|
||||
@@ -1168,7 +1232,7 @@ func Routes() *web.Router {
|
||||
m.Get("/search", repo.Search)
|
||||
|
||||
// (repo scope)
|
||||
m.Post("/migrate", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate)
|
||||
m.Post("/migrate", reqToken(), rejectPublicOnly(), bind(api.MigrateRepoOptions{}), repo.Migrate)
|
||||
|
||||
m.Group("/{username}/{reponame}", func() {
|
||||
m.Get("/compare/*", reqRepoReader(unit.TypeCode), repo.CompareDiff)
|
||||
@@ -1189,6 +1253,7 @@ func Routes() *web.Router {
|
||||
m.Group("/actions/workflows", func() {
|
||||
m.Get("", repo.ActionsListRepositoryWorkflows)
|
||||
m.Get("/{workflow_id}", repo.ActionsGetWorkflow)
|
||||
m.Get("/{workflow_id}/runs", repo.ActionsListWorkflowRuns)
|
||||
m.Put("/{workflow_id}/disable", reqRepoWriter(unit.TypeActions), repo.ActionsDisableWorkflow)
|
||||
m.Put("/{workflow_id}/enable", reqRepoWriter(unit.TypeActions), repo.ActionsEnableWorkflow)
|
||||
m.Post("/{workflow_id}/dispatches", reqRepoWriter(unit.TypeActions), bind(api.CreateActionWorkflowDispatch{}), repo.ActionsDispatchWorkflow)
|
||||
@@ -1227,6 +1292,7 @@ func Routes() *web.Router {
|
||||
})
|
||||
}, reqToken())
|
||||
m.Get("/assignees", reqToken(), reqAnyRepoReader(), repo.GetAssignees)
|
||||
m.Get("/assignees/{assignee}", reqToken(), reqAnyRepoReader(), repo.CheckRepoIssueAssignee)
|
||||
m.Get("/reviewers", reqToken(), reqAnyRepoReader(), repo.GetReviewers)
|
||||
m.Group("/teams", func() {
|
||||
m.Get("", reqAnyRepoReader(), repo.ListTeams)
|
||||
@@ -1278,6 +1344,10 @@ func Routes() *web.Router {
|
||||
m.Group("/runs", func() {
|
||||
m.Group("/{run}", func() {
|
||||
m.Get("", repo.GetWorkflowRun)
|
||||
m.Group("/attempts/{attempt}", func() {
|
||||
m.Get("", repo.GetWorkflowRunAttempt)
|
||||
m.Get("/jobs", repo.ListWorkflowRunAttemptJobs)
|
||||
})
|
||||
m.Delete("", reqToken(), reqRepoWriter(unit.TypeActions), repo.DeleteActionRun)
|
||||
m.Post("/rerun", reqToken(), reqRepoWriter(unit.TypeActions), repo.RerunWorkflowRun)
|
||||
m.Post("/rerun-failed-jobs", reqToken(), reqRepoWriter(unit.TypeActions), repo.RerunFailedWorkflowRun)
|
||||
@@ -1292,7 +1362,7 @@ func Routes() *web.Router {
|
||||
m.Delete("", reqRepoWriter(unit.TypeActions), repo.DeleteArtifact)
|
||||
})
|
||||
m.Get("/artifacts/{artifact_id}/zip", repo.DownloadArtifact)
|
||||
}, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true))
|
||||
}, reqRepoReader(unit.TypeActions))
|
||||
m.Group("/keys", func() {
|
||||
m.Combo("").Get(repo.ListDeployKeys).
|
||||
Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
|
||||
@@ -1389,6 +1459,7 @@ func Routes() *web.Router {
|
||||
m.Combo("/requested_reviewers", reqToken()).
|
||||
Delete(bind(api.PullReviewRequestOptions{}), repo.DeleteReviewRequests).
|
||||
Post(bind(api.PullReviewRequestOptions{}), repo.CreateReviewRequests)
|
||||
m.Post("/comments/{id}/replies", reqToken(), mustNotBeArchived, bind(api.CreatePullReviewCommentReplyOptions{}), repo.CreatePullReviewCommentReply)
|
||||
})
|
||||
m.Get("/{base}/*", repo.GetPullRequestByBaseHead)
|
||||
}, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
|
||||
@@ -1513,6 +1584,10 @@ func Routes() *web.Router {
|
||||
m.Combo("").Get(repo.GetIssue).
|
||||
Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue).
|
||||
Delete(reqToken(), reqAdmin(), context.ReferencesGitRepo(), repo.DeleteIssue)
|
||||
m.Combo("/assignees").
|
||||
Post(reqToken(), mustNotBeArchived, bind(api.IssueAssigneesOption{}), repo.AddIssueAssignees).
|
||||
Delete(reqToken(), mustNotBeArchived, bind(api.IssueAssigneesOption{}), repo.DeleteIssueAssignees)
|
||||
m.Get("/assignees/{assignee}", repo.CheckIssueAssignee)
|
||||
m.Group("/comments", func() {
|
||||
m.Combo("").Get(repo.ListIssueComments).
|
||||
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
|
||||
@@ -1624,21 +1699,22 @@ func Routes() *web.Router {
|
||||
m.Group("/users/{username}/orgs", func() {
|
||||
m.Get("", reqToken(), org.ListUserOrgs)
|
||||
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI(), checkTokenPublicOnly(), individualPermsChecker)
|
||||
m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create)
|
||||
m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization))
|
||||
m.Get("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), org.GetAll)
|
||||
m.Group("/orgs/{org}", func() {
|
||||
m.Combo("").Get(org.Get).
|
||||
Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
|
||||
Delete(reqToken(), reqOrgOwnership(), org.Delete)
|
||||
m.Post("/rename", reqToken(), reqOrgOwnership(), bind(api.RenameOrgOption{}), org.Rename)
|
||||
m.Combo("/repos").Get(user.ListOrgRepos).
|
||||
Post(reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
|
||||
Post(reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), bind(api.CreateRepoOption{}), repo.CreateOrgRepo).
|
||||
Delete(reqToken(), reqOrgOwnership(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), org.DeleteOrgRepos)
|
||||
m.Group("/members", func() {
|
||||
m.Get("", reqToken(), org.ListMembers)
|
||||
m.Combo("/{username}").Get(reqToken(), org.IsMember).
|
||||
Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
|
||||
})
|
||||
}, reqOrgVisible())
|
||||
addActionsRoutes(
|
||||
m,
|
||||
reqOrgMembership(),
|
||||
@@ -1650,7 +1726,7 @@ func Routes() *web.Router {
|
||||
m.Combo("/{username}").Get(org.IsPublicMember).
|
||||
Put(reqToken(), reqOrgMembership(), org.PublicizeMember).
|
||||
Delete(reqToken(), reqOrgMembership(), org.ConcealMember)
|
||||
})
|
||||
}, reqOrgVisible())
|
||||
m.Group("/teams", func() {
|
||||
m.Get("", org.ListTeams)
|
||||
m.Post("", reqOrgOwnership(), bind(api.CreateTeamOption{}), org.CreateTeam)
|
||||
@@ -1662,7 +1738,7 @@ func Routes() *web.Router {
|
||||
m.Combo("/{id}").Get(reqToken(), org.GetLabel).
|
||||
Patch(reqToken(), reqOrgOwnership(), bind(api.EditLabelOption{}), org.EditLabel).
|
||||
Delete(reqToken(), reqOrgOwnership(), org.DeleteLabel)
|
||||
})
|
||||
}, reqOrgVisible())
|
||||
m.Group("/hooks", func() {
|
||||
m.Combo("").Get(org.ListHooks).
|
||||
Post(bind(api.CreateHookOption{}), org.CreateHook)
|
||||
@@ -1686,25 +1762,31 @@ func Routes() *web.Router {
|
||||
}, reqToken(), reqOrgOwnership())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true), checkTokenPublicOnly())
|
||||
m.Group("/teams/{teamid}", func() {
|
||||
m.Combo("").Get(reqToken(), org.GetTeam).
|
||||
Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
|
||||
m.Combo("").Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
|
||||
Delete(reqToken(), reqOrgOwnership(), org.DeleteTeam)
|
||||
m.Group("", func() {
|
||||
m.Get("", org.GetTeam)
|
||||
m.Group("/members", func() {
|
||||
m.Get("", reqOrgMembership(), org.GetTeamMembers)
|
||||
m.Combo("/{username}").Get(reqOrgMembership(), org.GetTeamMember)
|
||||
})
|
||||
m.Group("/repos", func() {
|
||||
m.Get("", org.GetTeamRepos)
|
||||
m.Combo("/{org}/{reponame}").Get(org.GetTeamRepo)
|
||||
})
|
||||
m.Get("/activities/feeds", org.ListTeamActivityFeeds)
|
||||
}, reqTeamReadAccess())
|
||||
m.Group("/members", func() {
|
||||
m.Get("", reqToken(), org.GetTeamMembers)
|
||||
m.Combo("/{username}").
|
||||
Get(reqToken(), org.GetTeamMember).
|
||||
Put(reqToken(), reqOrgOwnership(), org.AddTeamMember).
|
||||
Delete(reqToken(), reqOrgOwnership(), org.RemoveTeamMember)
|
||||
})
|
||||
m.Group("/repos", func() {
|
||||
m.Get("", reqToken(), org.GetTeamRepos)
|
||||
m.Combo("/{org}/{reponame}").
|
||||
Put(reqToken(), org.AddTeamRepository).
|
||||
Delete(reqToken(), org.RemoveTeamRepository).
|
||||
Get(reqToken(), org.GetTeamRepo)
|
||||
Put(reqToken(), reqTeamMembership(), org.AddTeamRepository).
|
||||
Delete(reqToken(), reqTeamMembership(), org.RemoveTeamRepository)
|
||||
})
|
||||
m.Get("/activities/feeds", org.ListTeamActivityFeeds)
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), checkTokenPublicOnly())
|
||||
|
||||
m.Group("/admin", func() {
|
||||
m.Group("/cron", func() {
|
||||
@@ -1767,14 +1849,3 @@ func Routes() *web.Router {
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func securityHeaders() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
// CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers
|
||||
// http://stackoverflow.com/a/3146618/244009
|
||||
resp.Header().Set("x-content-type-options", "nosniff")
|
||||
next.ServeHTTP(resp, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ package misc
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/options"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"gitea.dev/modules/options"
|
||||
repo_module "gitea.dev/modules/repository"
|
||||
"gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// Shows a list of all Gitignore templates
|
||||
|
||||
@@ -6,10 +6,10 @@ package misc
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
repo_module "gitea.dev/modules/repository"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// Shows a list of all Label templates
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"code.gitea.io/gitea/modules/options"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"gitea.dev/modules/options"
|
||||
repo_module "gitea.dev/modules/repository"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// Returns a list of all License templates
|
||||
|
||||
@@ -4,15 +4,14 @@
|
||||
package misc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"io"
|
||||
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/common"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// Markup render markup document to HTML
|
||||
@@ -36,12 +35,6 @@ func Markup(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
form := web.GetForm(ctx).(*api.MarkupOption)
|
||||
|
||||
if ctx.HasAPIError() {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, ctx.GetErrMsg())
|
||||
return
|
||||
}
|
||||
|
||||
mode := util.Iif(form.Wiki, "wiki", form.Mode) //nolint:staticcheck // form.Wiki is deprecated
|
||||
common.RenderMarkup(ctx.Base, ctx.Repo, mode, form.Text, form.Context, form.FilePath)
|
||||
}
|
||||
@@ -67,12 +60,6 @@ func Markdown(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
form := web.GetForm(ctx).(*api.MarkdownOption)
|
||||
|
||||
if ctx.HasAPIError() {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, ctx.GetErrMsg())
|
||||
return
|
||||
}
|
||||
|
||||
mode := util.Iif(form.Wiki, "wiki", form.Mode) //nolint:staticcheck // form.Wiki is deprecated
|
||||
common.RenderMarkup(ctx.Base, ctx.Repo, mode, form.Text, form.Context, "")
|
||||
}
|
||||
@@ -98,9 +85,6 @@ func MarkdownRaw(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/MarkdownRender"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
defer ctx.Req.Body.Close()
|
||||
if err := markdown.RenderRaw(markup.NewRenderContext(ctx), ctx.Req.Body, ctx.Resp); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
textBytes, _ := io.ReadAll(io.LimitReader(ctx.Req.Body, setting.UI.MaxDisplayFileSize))
|
||||
common.RenderMarkup(ctx.Base, ctx.Repo, "markdown", util.UnsafeBytesToString(textBytes), "", "")
|
||||
}
|
||||
|
||||
@@ -7,33 +7,22 @@ import (
|
||||
go_context "context"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
context_service "code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/contexttest"
|
||||
"gitea.dev/modules/markup"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/test"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/contexttest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const AppURL = "http://localhost:3000/"
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m, &unittest.TestOptions{
|
||||
FixtureFiles: []string{"repository.yml", "user.yml"},
|
||||
})
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expectedBody string, expectedCode int) {
|
||||
setting.AppURL = AppURL
|
||||
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
||||
@@ -49,13 +38,11 @@ func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expe
|
||||
FilePath: filePath,
|
||||
}
|
||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markup")
|
||||
ctx.Repo = &context_service.Repository{}
|
||||
ctx.Repo.Repository = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
web.SetForm(ctx, &options)
|
||||
Markup(ctx)
|
||||
assert.Equal(t, expectedBody, resp.Body.String())
|
||||
assert.Equal(t, expectedCode, resp.Code)
|
||||
resp.Body.Reset()
|
||||
assert.Contains(t, resp.Header().Get("Content-Security-Policy"), "script-src * 'nonce-")
|
||||
}
|
||||
|
||||
func testRenderMarkdown(t *testing.T, mode string, wiki bool, text, responseBody string, responseCode int) {
|
||||
@@ -76,11 +63,10 @@ func testRenderMarkdown(t *testing.T, mode string, wiki bool, text, responseBody
|
||||
Markdown(ctx)
|
||||
assert.Equal(t, responseBody, resp.Body.String())
|
||||
assert.Equal(t, responseCode, resp.Code)
|
||||
resp.Body.Reset()
|
||||
assert.Contains(t, resp.Header().Get("Content-Security-Policy"), "script-src * 'nonce-")
|
||||
}
|
||||
|
||||
func TestAPI_RenderGFM(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
markup.Init(&markup.RenderHelperFuncs{
|
||||
IsUsernameMentionable: func(ctx go_context.Context, username string) bool {
|
||||
return username == "r-lyeh"
|
||||
@@ -177,49 +163,34 @@ Here are some links to the most important topics. You can find the full list of
|
||||
testRenderMarkup(t, "unknown", false, "", "## Test", "unsupported render mode: unknown\n", http.StatusUnprocessableEntity)
|
||||
}
|
||||
|
||||
var simpleCases = []string{
|
||||
// Guard wiki sidebar: special syntax
|
||||
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
|
||||
// rendered
|
||||
`<p>[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]</p>
|
||||
`,
|
||||
// special syntax
|
||||
`[[Name|Link]]`,
|
||||
// rendered
|
||||
`<p>[[Name|Link]]</p>
|
||||
`,
|
||||
// empty
|
||||
``,
|
||||
// rendered
|
||||
``,
|
||||
}
|
||||
|
||||
func TestAPI_RenderSimple(t *testing.T) {
|
||||
setting.AppURL = AppURL
|
||||
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
|
||||
options := api.MarkdownOption{
|
||||
Mode: "markdown",
|
||||
Text: "",
|
||||
Context: "/user2/repo1",
|
||||
}
|
||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
||||
for i := 0; i < len(simpleCases); i += 2 {
|
||||
options.Text = simpleCases[i]
|
||||
web.SetForm(ctx, &options)
|
||||
Markdown(ctx)
|
||||
assert.Equal(t, simpleCases[i+1], resp.Body.String())
|
||||
resp.Body.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPI_RenderRaw(t *testing.T) {
|
||||
setting.AppURL = AppURL
|
||||
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
|
||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
||||
for i := 0; i < len(simpleCases); i += 2 {
|
||||
ctx.Req.Body = io.NopCloser(strings.NewReader(simpleCases[i]))
|
||||
MarkdownRaw(ctx)
|
||||
assert.Equal(t, simpleCases[i+1], resp.Body.String())
|
||||
resp.Body.Reset()
|
||||
testCases := []struct {
|
||||
in, out string
|
||||
mode string
|
||||
}{
|
||||
{in: "", out: ""},
|
||||
{in: "[[special-syntax]]", out: "<p>[[special-syntax]]</p>\n", mode: "markdown"},
|
||||
{in: "[[special|syntax]]", out: "<p>[[special|syntax]]</p>\n", mode: "markdown"},
|
||||
{in: "01234567890123456789", out: "<p>01234567890123456789</p>\n", mode: "gfm"}, // commit-like content should not crash the render
|
||||
}
|
||||
t.Run("markdown", func(t *testing.T) {
|
||||
for _, c := range testCases {
|
||||
options := api.MarkdownOption{Mode: c.mode, Text: c.in, Context: "/user2/repo1"}
|
||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
||||
web.SetForm(ctx, &options)
|
||||
Markdown(ctx)
|
||||
assert.Equal(t, c.out, resp.Body.String())
|
||||
}
|
||||
})
|
||||
t.Run("markdown-raw", func(t *testing.T) {
|
||||
for _, c := range testCases {
|
||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
||||
ctx.Req.Body = io.NopCloser(strings.NewReader(c.in))
|
||||
MarkdownRaw(ctx)
|
||||
assert.Equal(t, c.out, resp.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
package misc
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"gitea.dev/modules/git"
|
||||
asymkey_service "gitea.dev/services/asymkey"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
func getSigningKey(ctx *context.APIContext, expectedFormat string) {
|
||||
|
||||
@@ -6,9 +6,9 @@ package misc
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/structs"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// Version shows the version of the Gitea server
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
activities_model "gitea.dev/models/activities"
|
||||
"gitea.dev/models/db"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// NewAvailable check if unread notifications exist
|
||||
@@ -28,7 +28,7 @@ func NewAvailable(ctx *context.APIContext) {
|
||||
Status: []activities_model.NotificationStatus{activities_model.NotificationStatusUnread},
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func NewAvailable(ctx *context.APIContext) {
|
||||
func getFindNotificationOptions(ctx *context.APIContext) *activities_model.FindNotificationOptions {
|
||||
before, since, err := context.GetQueryBeforeSince(ctx.Base)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return nil
|
||||
}
|
||||
opts := &activities_model.FindNotificationOptions{
|
||||
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
activities_model "gitea.dev/models/activities"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/structs"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
func statusStringToNotificationStatus(status string) activities_model.NotificationStatus {
|
||||
@@ -183,7 +183,7 @@ func ReadRepoNotifications(ctx *context.APIContext) {
|
||||
if len(qLastRead) > 0 {
|
||||
tmpLastRead, err := time.Parse(time.RFC3339, qLastRead)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if !tmpLastRead.IsZero() {
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
activities_model "gitea.dev/models/activities"
|
||||
"gitea.dev/models/db"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// GetThread get notification by ID
|
||||
@@ -104,14 +104,14 @@ func getThread(ctx *context.APIContext) *activities_model.Notification {
|
||||
n, err := activities_model.GetNotificationByID(ctx, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if db.IsErrNotExist(err) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if n.UserID != ctx.Doer.ID && !ctx.Doer.IsAdmin {
|
||||
ctx.APIError(http.StatusForbidden, fmt.Errorf("only user itself and admin are allowed to read/change this thread %d", n.ID))
|
||||
ctx.APIError(http.StatusForbidden, fmt.Sprintf("only user itself and admin are allowed to read/change this thread %d", n.ID))
|
||||
return nil
|
||||
}
|
||||
return n
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
activities_model "gitea.dev/models/activities"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/structs"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// ListNotifications list users's notification threads
|
||||
@@ -134,7 +134,7 @@ func ReadNotifications(ctx *context.APIContext) {
|
||||
if len(qLastRead) > 0 {
|
||||
tmpLastRead, err := time.Parse(time.RFC3339, qLastRead)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if !tmpLastRead.IsZero() {
|
||||
|
||||
@@ -7,17 +7,17 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
secret_model "code.gitea.io/gitea/models/secret"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/shared"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
secret_service "code.gitea.io/gitea/services/secrets"
|
||||
actions_model "gitea.dev/models/actions"
|
||||
"gitea.dev/models/db"
|
||||
secret_model "gitea.dev/models/secret"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/shared"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
actions_service "gitea.dev/services/actions"
|
||||
"gitea.dev/services/context"
|
||||
secret_service "gitea.dev/services/secrets"
|
||||
)
|
||||
|
||||
// ListActionsSecrets list an organization's actions secrets
|
||||
@@ -111,9 +111,9 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
|
||||
_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("secretname"), opt.Data, opt.Description)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -158,9 +158,9 @@ func (Action) DeleteSecret(ctx *context.APIContext) {
|
||||
err := secret_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("secretname"))
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -277,7 +277,7 @@ func (Action) GetVariable(ctx *context.APIContext) {
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -327,9 +327,9 @@ func (Action) DeleteVariable(ctx *context.APIContext) {
|
||||
|
||||
if err := actions_service.DeleteVariableByName(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("variablename")); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -387,13 +387,13 @@ func (Action) CreateVariable(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
if v != nil && v.ID > 0 {
|
||||
ctx.APIError(http.StatusConflict, util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
|
||||
ctx.APIError(http.StatusConflict, "variable name already exists")
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value, opt.Description); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -445,7 +445,7 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -462,7 +462,7 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
|
||||
|
||||
if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -624,7 +624,7 @@ func (Action) ListWorkflowJobs(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.ListJobs(ctx, ctx.Org.Organization.ID, 0, 0)
|
||||
shared.ListJobs(ctx, ctx.Org.Organization.ID, 0, 0, nil)
|
||||
}
|
||||
|
||||
func (Action) ListWorkflowRuns(ctx *context.APIContext) {
|
||||
@@ -679,7 +679,7 @@ func (Action) ListWorkflowRuns(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.ListRuns(ctx, ctx.Org.Organization.ID, 0)
|
||||
shared.ListRuns(ctx, ctx.Org.Organization.ID, 0, "")
|
||||
}
|
||||
|
||||
var _ actions_service.API = new(Action)
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
user_service "code.gitea.io/gitea/services/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/context"
|
||||
user_service "gitea.dev/services/user"
|
||||
)
|
||||
|
||||
// UpdateAvatarupdates the Avatar of an Organisation
|
||||
@@ -39,7 +39,7 @@ func UpdateAvatar(ctx *context.APIContext) {
|
||||
|
||||
content, err := base64.StdEncoding.DecodeString(form.Image)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
package org
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/routers/api/v1/shared"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"gitea.dev/routers/api/v1/shared"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
func ListBlocks(ctx *context.APIContext) {
|
||||
|
||||
@@ -6,11 +6,11 @@ package org
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
webhook_service "code.gitea.io/gitea/services/webhook"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
webhook_service "gitea.dev/services/webhook"
|
||||
)
|
||||
|
||||
// ListHooks list an organization's webhooks
|
||||
|
||||
@@ -8,13 +8,13 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/modules/label"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
"gitea.dev/modules/label"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// ListLabels list all the labels of an organization
|
||||
@@ -90,7 +90,7 @@ func CreateLabel(ctx *context.APIContext) {
|
||||
form.Color = strings.Trim(form.Color, " ")
|
||||
color, err := label.NormalizeColor(form.Color)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
form.Color = color
|
||||
@@ -209,7 +209,7 @@ func EditLabel(ctx *context.APIContext) {
|
||||
if form.Color != nil {
|
||||
color, err := label.NormalizeColor(*form.Color)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
l.Color = color
|
||||
|
||||
@@ -7,15 +7,15 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/user"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
org_service "code.gitea.io/gitea/services/org"
|
||||
"gitea.dev/models/organization"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/routers/api/v1/user"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
org_service "gitea.dev/services/org"
|
||||
)
|
||||
|
||||
// listMembers list an organization's members
|
||||
@@ -221,7 +221,7 @@ func checkCanChangeOrgUserStatus(ctx *context.APIContext, targetUser *user_model
|
||||
// allow org owners to change status of members
|
||||
isOwner, err := ctx.Org.Organization.IsOwnedBy(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusInternalServerError, err)
|
||||
ctx.APIErrorInternal(err)
|
||||
} else if !isOwner {
|
||||
ctx.APIError(http.StatusForbidden, "Cannot change member visibility")
|
||||
}
|
||||
|
||||
@@ -5,25 +5,32 @@
|
||||
package org
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/user"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
feed_service "code.gitea.io/gitea/services/feed"
|
||||
"code.gitea.io/gitea/services/org"
|
||||
user_service "code.gitea.io/gitea/services/user"
|
||||
activities_model "gitea.dev/models/activities"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/organization"
|
||||
"gitea.dev/models/perm"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
system_model "gitea.dev/models/system"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/graceful"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/optional"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/user"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
feed_service "gitea.dev/services/feed"
|
||||
"gitea.dev/services/org"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
user_service "gitea.dev/services/user"
|
||||
)
|
||||
|
||||
func listUserOrgs(ctx *context.APIContext, u *user_model.User) {
|
||||
@@ -138,8 +145,15 @@ func GetUserOrgsPermissions(ctx *context.APIContext) {
|
||||
|
||||
op := api.OrganizationPermissions{}
|
||||
|
||||
// A public-only token must not disclose membership/permission details of a
|
||||
// non-public org, even for the token owner's own private orgs.
|
||||
if ctx.PublicOnly && !o.Visibility.IsPublic() {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if !organization.HasOrgOrUserVisible(ctx, o, ctx.Doer) {
|
||||
ctx.APIErrorNotFound("HasOrgOrUserVisible", nil)
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -249,13 +263,13 @@ func Create(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/validationError"
|
||||
form := web.GetForm(ctx).(*api.CreateOrgOption)
|
||||
if !ctx.Doer.CanCreateOrganization() {
|
||||
ctx.APIError(http.StatusForbidden, nil)
|
||||
ctx.APIError(http.StatusForbidden, "not allowed to create org")
|
||||
return
|
||||
}
|
||||
|
||||
visibility := api.VisibleTypePublic
|
||||
if form.Visibility != "" {
|
||||
visibility = api.VisibilityModes[form.Visibility]
|
||||
visibility = api.VisibilityModes[string(form.Visibility)]
|
||||
}
|
||||
|
||||
org := &organization.Organization{
|
||||
@@ -275,7 +289,7 @@ func Create(ctx *context.APIContext) {
|
||||
db.IsErrNameReserved(err) ||
|
||||
db.IsErrNameCharsNotAllowed(err) ||
|
||||
db.IsErrNamePatternNotAllowed(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -305,7 +319,7 @@ func Get(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if !organization.HasOrgOrUserVisible(ctx, ctx.Org.Organization.AsUser(), ctx.Doer) {
|
||||
ctx.APIErrorNotFound("HasOrgOrUserVisible", nil)
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -348,7 +362,7 @@ func Rename(ctx *context.APIContext) {
|
||||
orgUser := ctx.Org.Organization.AsUser()
|
||||
if err := user_service.RenameUser(ctx, orgUser, form.NewName, ctx.Doer); err != nil {
|
||||
if user_model.IsErrUserAlreadyExist(err) || db.IsErrNameReserved(err) || db.IsErrNamePatternNotAllowed(err) || db.IsErrNameCharsNotAllowed(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -387,7 +401,7 @@ func Edit(ctx *context.APIContext) {
|
||||
|
||||
if err := org.UpdateOrgEmailAddress(ctx, ctx.Org.Organization, form.Email); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -399,7 +413,7 @@ func Edit(ctx *context.APIContext) {
|
||||
Description: optional.FromPtr(form.Description),
|
||||
Website: optional.FromPtr(form.Website),
|
||||
Location: optional.FromPtr(form.Location),
|
||||
Visibility: optional.FromMapLookup(api.VisibilityModes, optional.FromPtr(form.Visibility).Value()),
|
||||
Visibility: optional.FromMapLookup(api.VisibilityModes, string(optional.FromPtr(form.Visibility).Value())),
|
||||
RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, ctx.Org.Organization.AsUser(), opts); err != nil {
|
||||
@@ -502,3 +516,70 @@ func ListOrgActivityFeeds(ctx *context.APIContext) {
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToActivities(ctx, feeds, ctx.Doer))
|
||||
}
|
||||
|
||||
func deleteOrgReposBackground(ctx gocontext.Context, org *organization.Organization, repoIDs []int64, doer *user_model.User) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Error("panic during org repo deletion: %v, stack: %v", r, log.Stack(2))
|
||||
}
|
||||
}()
|
||||
|
||||
for _, repoID := range repoIDs {
|
||||
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
|
||||
if err != nil {
|
||||
desc := fmt.Sprintf("Failed to get repository ID %d in org %s: %v", repoID, org.Name, err)
|
||||
_ = system_model.CreateNotice(ctx, system_model.NoticeRepository, desc)
|
||||
log.Error("GetRepositoryByID failed: %v", desc)
|
||||
continue
|
||||
}
|
||||
if err := repo_service.DeleteRepository(ctx, doer, repo, true); err != nil {
|
||||
desc := fmt.Sprintf("Failed to delete repository %s (ID: %d) in org %s: %v", repo.Name, repo.ID, org.Name, err)
|
||||
_ = system_model.CreateNotice(ctx, system_model.NoticeRepository, desc)
|
||||
log.Error("DeleteRepository failed: %v", desc)
|
||||
continue
|
||||
}
|
||||
log.Info("Successfully deleted repository %s (ID: %d) in org %s", repo.Name, repo.ID, org.Name)
|
||||
}
|
||||
log.Info("Completed deletion of repositories in org %s", org.Name)
|
||||
}
|
||||
|
||||
func DeleteOrgRepos(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /orgs/{org}/repos organization orgDeleteRepos
|
||||
// ---
|
||||
// summary: Delete all repositories in an organization
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "202":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
// Intentionally it only loads repository IDs to avoid loading too much data into memory
|
||||
// There is no need to do pagination here as the number of repositories is expected to be manageable
|
||||
repoIDs, err := repo_model.GetOrgRepositoryIDs(ctx, ctx.Org.Organization.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(repoIDs) == 0 {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
// Start deletion (slow) in background with detached context, so it can continue even if the request is canceled
|
||||
go deleteOrgReposBackground(graceful.GetManager().ShutdownContext(), ctx.Org.Organization, repoIDs, ctx.Doer)
|
||||
|
||||
ctx.Status(http.StatusAccepted)
|
||||
}
|
||||
|
||||
@@ -8,23 +8,23 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/user"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
feed_service "code.gitea.io/gitea/services/feed"
|
||||
org_service "code.gitea.io/gitea/services/org"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
activities_model "gitea.dev/models/activities"
|
||||
"gitea.dev/models/organization"
|
||||
"gitea.dev/models/perm"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
unit_model "gitea.dev/models/unit"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/log"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/user"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
feed_service "gitea.dev/services/feed"
|
||||
org_service "gitea.dev/services/org"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
)
|
||||
|
||||
// ListTeams list all the teams of an organization
|
||||
@@ -55,10 +55,15 @@ func ListTeams(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{
|
||||
opts := &organization.SearchTeamOptions{
|
||||
ListOptions: listOptions,
|
||||
OrgID: ctx.Org.Organization.ID,
|
||||
})
|
||||
}
|
||||
if err := organization.ApplyTeamListFilter(ctx, ctx.Org.Organization.ID, ctx.Doer, ctx.IsSigned, opts); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
teams, count, err := organization.SearchTeam(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -210,7 +215,7 @@ func CreateTeam(ctx *context.APIContext) {
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
form := web.GetForm(ctx).(*api.CreateTeamOption)
|
||||
teamPermission := perm.ParseAccessMode(form.Permission, perm.AccessModeNone, perm.AccessModeAdmin)
|
||||
teamPermission := perm.ParseAccessMode(string(form.Permission), perm.AccessModeNone, perm.AccessModeAdmin)
|
||||
team := &organization.Team{
|
||||
OrgID: ctx.Org.Organization.ID,
|
||||
Name: form.Name,
|
||||
@@ -218,13 +223,14 @@ func CreateTeam(ctx *context.APIContext) {
|
||||
IncludesAllRepositories: form.IncludesAllRepositories,
|
||||
CanCreateOrgRepo: form.CanCreateOrgRepo,
|
||||
AccessMode: teamPermission,
|
||||
Visibility: organization.NormalizeTeamVisibility(string(form.Visibility)),
|
||||
}
|
||||
|
||||
if team.AccessMode < perm.AccessModeAdmin {
|
||||
if len(form.UnitsMap) > 0 {
|
||||
attachTeamUnitsMap(team, form.UnitsMap)
|
||||
} else if len(form.Units) > 0 {
|
||||
unitPerm := perm.ParseAccessMode(form.Permission, perm.AccessModeRead, perm.AccessModeWrite)
|
||||
unitPerm := perm.ParseAccessMode(string(form.Permission), perm.AccessModeRead, perm.AccessModeWrite)
|
||||
attachTeamUnits(team, unitPerm, form.Units)
|
||||
} else {
|
||||
ctx.APIErrorInternal(errors.New("units permission should not be empty"))
|
||||
@@ -236,7 +242,7 @@ func CreateTeam(ctx *context.APIContext) {
|
||||
|
||||
if err := org_service.NewTeam(ctx, team); err != nil {
|
||||
if organization.IsErrTeamAlreadyExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -295,10 +301,14 @@ func EditTeam(ctx *context.APIContext) {
|
||||
team.Description = *form.Description
|
||||
}
|
||||
|
||||
if form.Visibility != nil && !team.IsOwnerTeam() {
|
||||
team.Visibility = organization.NormalizeTeamVisibility(string(*form.Visibility))
|
||||
}
|
||||
|
||||
isAuthChanged := false
|
||||
isIncludeAllChanged := false
|
||||
if !team.IsOwnerTeam() && len(form.Permission) != 0 {
|
||||
teamPermission := perm.ParseAccessMode(form.Permission, perm.AccessModeNone, perm.AccessModeAdmin)
|
||||
teamPermission := perm.ParseAccessMode(string(form.Permission), perm.AccessModeNone, perm.AccessModeAdmin)
|
||||
if team.AccessMode != teamPermission {
|
||||
isAuthChanged = true
|
||||
team.AccessMode = teamPermission
|
||||
@@ -314,7 +324,7 @@ func EditTeam(ctx *context.APIContext) {
|
||||
if len(form.UnitsMap) > 0 {
|
||||
attachTeamUnitsMap(team, form.UnitsMap)
|
||||
} else if len(form.Units) > 0 {
|
||||
unitPerm := perm.ParseAccessMode(form.Permission, perm.AccessModeRead, perm.AccessModeWrite)
|
||||
unitPerm := perm.ParseAccessMode(string(form.Permission), perm.AccessModeRead, perm.AccessModeWrite)
|
||||
attachTeamUnits(team, unitPerm, form.Units)
|
||||
}
|
||||
} else {
|
||||
@@ -387,15 +397,6 @@ func GetTeamMembers(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
isMember, err := organization.IsOrganizationMember(ctx, ctx.Org.Team.OrgID, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
} else if !isMember && !ctx.Doer.IsAdmin {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
teamMembers, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{
|
||||
ListOptions: listOptions,
|
||||
@@ -490,7 +491,7 @@ func AddTeamMember(ctx *context.APIContext) {
|
||||
}
|
||||
if err := org_service.AddTeamMember(ctx, ctx.Org.Team, u); err != nil {
|
||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -566,25 +567,43 @@ func GetTeamRepos(ctx *context.APIContext) {
|
||||
|
||||
team := ctx.Org.Team
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
teamRepos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{
|
||||
// A public-only token must not expose (or count) private repos, even when the
|
||||
// doer owning the token otherwise has access to them, so filter them out at the
|
||||
// query level to keep the returned page and the total-count header consistent.
|
||||
searchOpts := &repo_model.SearchTeamRepoOptions{
|
||||
ListOptions: listOptions,
|
||||
TeamID: team.ID,
|
||||
})
|
||||
PublicOnly: ctx.PublicOnly,
|
||||
}
|
||||
teamRepos, err := repo_model.GetTeamRepositories(ctx, searchOpts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
repos := make([]*api.Repository, len(teamRepos))
|
||||
for i, repo := range teamRepos {
|
||||
count, err := repo_model.CountTeamRepositories(ctx, searchOpts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
repos := make([]*api.Repository, 0, len(teamRepos))
|
||||
for _, repo := range teamRepos {
|
||||
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
repos[i] = convert.ToRepo(ctx, repo, permission)
|
||||
// A team's repo list is reachable by non-team-members through the team's
|
||||
// visibility tier, so never expose repos (incl. their names) the doer
|
||||
// cannot access. This per-repo visibility trim can't be expressed in the
|
||||
// SQL count above without regressing per-unit public access, so for such
|
||||
// non-members the total-count header may be a small upper bound.
|
||||
if !permission.HasAnyUnitAccessOrPublicAccess() {
|
||||
continue
|
||||
}
|
||||
repos = append(repos, convert.ToRepo(ctx, repo, permission))
|
||||
}
|
||||
ctx.SetLinkHeader(int64(team.NumRepos), listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(int64(team.NumRepos))
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, repos)
|
||||
}
|
||||
|
||||
@@ -623,6 +642,12 @@ func GetTeamRepo(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
// A public-only token must not confirm the existence of a private repo.
|
||||
if !ctx.TokenCanAccessRepo(repo) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if !organization.HasTeamRepo(ctx, ctx.Org.Team.OrgID, ctx.Org.Team.ID, repo.ID) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
@@ -633,6 +658,12 @@ func GetTeamRepo(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
// The team may be reachable by a non-team-member via its visibility tier;
|
||||
// don't confirm the existence of a repo the doer cannot access.
|
||||
if !permission.HasAnyUnitAccessOrPublicAccess() {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, repo, permission))
|
||||
}
|
||||
@@ -651,6 +682,19 @@ func getRepositoryByParams(ctx *context.APIContext) *repo_model.Repository {
|
||||
return repo
|
||||
}
|
||||
|
||||
func canChangeTeamRepository(ctx *context.APIContext) bool {
|
||||
canChange, err := ctx.Org.Organization.CanChangeRepoTeamAccess(ctx, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return false
|
||||
}
|
||||
if !canChange {
|
||||
ctx.APIError(http.StatusForbidden, "Must be an organization owner")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// AddTeamRepository api for adding a repository to a team
|
||||
func AddTeamRepository(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /teams/{id}/repos/{org}/{repo} organization orgAddTeamRepository
|
||||
@@ -687,6 +731,9 @@ func AddTeamRepository(ctx *context.APIContext) {
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
if !canChangeTeamRepository(ctx) {
|
||||
return
|
||||
}
|
||||
if access, err := access_model.AccessLevel(ctx, ctx.Doer, repo); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -739,6 +786,9 @@ func RemoveTeamRepository(ctx *context.APIContext) {
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
if !canChangeTeamRepository(ctx) {
|
||||
return
|
||||
}
|
||||
if access, err := access_model.AccessLevel(ctx, ctx.Doer, repo); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -806,9 +856,9 @@ func SearchTeam(ctx *context.APIContext) {
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
|
||||
// Only admin is allowed to search for all teams
|
||||
if !ctx.Doer.IsAdmin {
|
||||
opts.UserID = ctx.Doer.ID
|
||||
if err := organization.ApplyTeamListFilter(ctx, ctx.Org.Organization.ID, ctx.Doer, ctx.IsSigned, opts); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
teams, maxResults, err := organization.SearchTeam(ctx, opts)
|
||||
@@ -876,6 +926,8 @@ func ListTeamActivityFeeds(ctx *context.APIContext) {
|
||||
Date: ctx.FormString("date"),
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
// A public-only token must not receive private activity entries.
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
feeds, count, err := feed_service.GetFeeds(ctx, opts)
|
||||
if err != nil {
|
||||
|
||||
@@ -7,15 +7,15 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/packages"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
"gitea.dev/models/packages"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/modules/optional"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
packages_service "gitea.dev/services/packages"
|
||||
)
|
||||
|
||||
// ListPackages gets all packages of an owner
|
||||
@@ -329,7 +329,7 @@ func GetLatestPackageVersion(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
if len(pvs) == 0 {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -383,7 +383,7 @@ func LinkPackage(ctx *context.APIContext) {
|
||||
pkg, err := packages.GetPackageByName(ctx, ctx.ContextUser.ID, packages.Type(ctx.PathParam("type")), ctx.PathParam("name"))
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -393,7 +393,7 @@ func LinkPackage(ctx *context.APIContext) {
|
||||
repo, err := repo_model.GetRepositoryByName(ctx, ctx.ContextUser.ID, ctx.PathParam("repo_name"))
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -404,9 +404,9 @@ func LinkPackage(ctx *context.APIContext) {
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, util.ErrInvalidArgument):
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
case errors.Is(err, util.ErrPermissionDenied):
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
default:
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -445,7 +445,7 @@ func UnlinkPackage(ctx *context.APIContext) {
|
||||
pkg, err := packages.GetPackageByName(ctx, ctx.ContextUser.ID, packages.Type(ctx.PathParam("type")), ctx.PathParam("name"))
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -456,9 +456,9 @@ func UnlinkPackage(ctx *context.APIContext) {
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, util.ErrPermissionDenied):
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
case errors.Is(err, util.ErrInvalidArgument):
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
default:
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
@@ -16,23 +16,24 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
secret_model "code.gitea.io/gitea/models/secret"
|
||||
"code.gitea.io/gitea/modules/actions"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/shared"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
secret_service "code.gitea.io/gitea/services/secrets"
|
||||
actions_model "gitea.dev/models/actions"
|
||||
"gitea.dev/models/db"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
secret_model "gitea.dev/models/secret"
|
||||
"gitea.dev/modules/actions"
|
||||
"gitea.dev/modules/httplib"
|
||||
"gitea.dev/modules/optional"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/shared"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
actions_service "gitea.dev/services/actions"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
secret_service "gitea.dev/services/secrets"
|
||||
|
||||
"github.com/nektos/act/pkg/model"
|
||||
"gitea.com/gitea/runner/act/model"
|
||||
)
|
||||
|
||||
// ListActionsSecrets list an repo's actions secrets
|
||||
@@ -140,9 +141,9 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
|
||||
_, created, err := secret_service.CreateOrUpdateSecret(ctx, 0, repo.ID, ctx.PathParam("secretname"), opt.Data, opt.Description)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -194,9 +195,9 @@ func (Action) DeleteSecret(ctx *context.APIContext) {
|
||||
err := secret_service.DeleteSecretByName(ctx, 0, repo.ID, ctx.PathParam("secretname"))
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -242,7 +243,7 @@ func (Action) GetVariable(ctx *context.APIContext) {
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -297,9 +298,9 @@ func (Action) DeleteVariable(ctx *context.APIContext) {
|
||||
|
||||
if err := actions_service.DeleteVariableByName(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParam("variablename")); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -360,13 +361,13 @@ func (Action) CreateVariable(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
if v != nil && v.ID > 0 {
|
||||
ctx.APIError(http.StatusConflict, util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
|
||||
ctx.APIError(http.StatusConflict, "variable name already exists")
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := actions_service.CreateVariable(ctx, 0, repoID, variableName, opt.Value, opt.Description); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -421,7 +422,7 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -438,7 +439,7 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
|
||||
|
||||
if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -674,7 +675,7 @@ func (Action) UpdateRunner(ctx *context.APIContext) {
|
||||
shared.UpdateRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// GetWorkflowRunJobs Lists all jobs for a workflow run.
|
||||
// ListWorkflowJobs Lists all jobs for a repository.
|
||||
func (Action) ListWorkflowJobs(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/jobs repository listWorkflowJobs
|
||||
// ---
|
||||
@@ -705,6 +706,14 @@ func (Action) ListWorkflowJobs(ctx *context.APIContext) {
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// - name: sort
|
||||
// in: query
|
||||
// description: sort jobs by attribute. Supported values are "id". Default is "id"
|
||||
// type: string
|
||||
// - name: order
|
||||
// in: query
|
||||
// description: sort order, either "asc" (ascending) or "desc" (descending). Default is "asc"
|
||||
// type: string
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/WorkflowJobsList"
|
||||
@@ -712,10 +721,12 @@ func (Action) ListWorkflowJobs(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
repoID := ctx.Repo.Repository.ID
|
||||
|
||||
shared.ListJobs(ctx, 0, repoID, 0)
|
||||
shared.ListJobs(ctx, 0, repoID, 0, nil)
|
||||
}
|
||||
|
||||
// ListWorkflowRuns Lists all runs for a repository run.
|
||||
@@ -761,6 +772,11 @@ func (Action) ListWorkflowRuns(ctx *context.APIContext) {
|
||||
// description: triggering sha of the workflow run
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: exclude_pull_requests
|
||||
// in: query
|
||||
// description: if true, the `pull_requests` field on each returned run is emptied
|
||||
// type: boolean
|
||||
// required: false
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
@@ -779,7 +795,7 @@ func (Action) ListWorkflowRuns(ctx *context.APIContext) {
|
||||
|
||||
repoID := ctx.Repo.Repository.ID
|
||||
|
||||
shared.ListRuns(ctx, 0, repoID)
|
||||
shared.ListRuns(ctx, 0, repoID, "")
|
||||
}
|
||||
|
||||
var _ actions_service.API = new(Action)
|
||||
@@ -845,6 +861,12 @@ func ListActionTasks(ctx *context.APIContext) {
|
||||
res := new(api.ActionTaskResponse)
|
||||
res.TotalCount = total
|
||||
|
||||
taskList := actions_model.TaskList(tasks)
|
||||
if err := taskList.LoadAttributes(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
res.Entries = make([]*api.ActionTask, len(tasks))
|
||||
for i := range tasks {
|
||||
convertedTask, err := convert.ToActionTask(ctx, tasks[i])
|
||||
@@ -856,7 +878,7 @@ func ListActionTasks(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(total, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(total) // Duplicates api response field but it's better to set it for consistency
|
||||
ctx.SetTotalCountHeader(total) // Duplicates api response field, but it's better to set it for consistency
|
||||
ctx.JSON(http.StatusOK, &res)
|
||||
}
|
||||
|
||||
@@ -940,7 +962,7 @@ func ActionsGetWorkflow(ctx *context.APIContext) {
|
||||
workflow, err := convert.GetActionWorkflow(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository, workflowID)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -950,6 +972,107 @@ func ActionsGetWorkflow(ctx *context.APIContext) {
|
||||
ctx.JSON(http.StatusOK, workflow)
|
||||
}
|
||||
|
||||
func ActionsListWorkflowRuns(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs repository ActionsListWorkflowRuns
|
||||
// ---
|
||||
// summary: List runs for a workflow
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: workflow_id
|
||||
// in: path
|
||||
// description: id of the workflow, must be the workflow file name (e.g. `build.yml`)
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: event
|
||||
// in: query
|
||||
// description: workflow event name
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: branch
|
||||
// in: query
|
||||
// description: workflow branch
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: status
|
||||
// in: query
|
||||
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: actor
|
||||
// in: query
|
||||
// description: triggered by user
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: head_sha
|
||||
// in: query
|
||||
// description: triggering sha of the workflow run
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: exclude_pull_requests
|
||||
// in: query
|
||||
// description: if true, the `pull_requests` field on each returned run is emptied
|
||||
// type: boolean
|
||||
// required: false
|
||||
// - name: scoped_workflow_source_repo_id
|
||||
// description: For a scoped workflow, the ID of the source repository providing it; omit or 0 for a repo-level workflow.
|
||||
// in: query
|
||||
// type: integer
|
||||
// format: int64
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/WorkflowRunsList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
workflowID := ctx.PathParam("workflow_id")
|
||||
scopedWorkflowSourceRepoID := ctx.FormInt64("scoped_workflow_source_repo_id")
|
||||
// Existing runs prove the workflow is/was valid and cover historical workflows whose file was later removed.
|
||||
// Repo-level never-run workflows fall back to a git lookup; scoped workflows are selected by source repo ID and may return an empty run list.
|
||||
runExists, err := db.Exist[actions_model.ActionRun](ctx, actions_model.FindRunOptions{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
WorkflowID: workflowID,
|
||||
WorkflowRepoID: scopedWorkflowSourceRepoID,
|
||||
IsScopedRun: optional.Some(scopedWorkflowSourceRepoID > 0),
|
||||
}.ToConds())
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if !runExists {
|
||||
if scopedWorkflowSourceRepoID == 0 {
|
||||
if _, err := convert.GetActionWorkflow(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository, workflowID); err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shared.ListRuns(ctx, 0, ctx.Repo.Repository.ID, workflowID)
|
||||
}
|
||||
|
||||
func ActionsDisableWorkflow(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/disable repository ActionsDisableWorkflow
|
||||
// ---
|
||||
@@ -988,7 +1111,7 @@ func ActionsDisableWorkflow(ctx *context.APIContext) {
|
||||
err := actions_service.EnableOrDisableWorkflow(ctx, workflowID, false)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -1028,6 +1151,11 @@ func ActionsDispatchWorkflow(ctx *context.APIContext) {
|
||||
// description: Whether the response should include the workflow run ID and URLs.
|
||||
// in: query
|
||||
// type: boolean
|
||||
// - name: scoped_workflow_source_repo_id
|
||||
// description: For a scoped workflow, the ID of the source repository providing it; omit or 0 for a repo-level workflow.
|
||||
// in: query
|
||||
// type: integer
|
||||
// format: int64
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RunDetails"
|
||||
@@ -1045,11 +1173,13 @@ func ActionsDispatchWorkflow(ctx *context.APIContext) {
|
||||
workflowID := ctx.PathParam("workflow_id")
|
||||
opt := web.GetForm(ctx).(*api.CreateActionWorkflowDispatch)
|
||||
if opt.Ref == "" {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, util.NewInvalidArgumentErrorf("ref is required parameter"))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "ref is required parameter")
|
||||
return
|
||||
}
|
||||
|
||||
runID, err := actions_service.DispatchActionWorkflow(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, workflowID, opt.Ref, func(workflowDispatch *model.WorkflowDispatch, inputs map[string]any) error {
|
||||
// a non-zero scoped_workflow_source_repo_id dispatches a scoped workflow from that source repo; 0/absent is repo-level.
|
||||
scopedWorkflowSourceRepoID := ctx.FormInt64("scoped_workflow_source_repo_id")
|
||||
runID, err := actions_service.DispatchActionWorkflow(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, workflowID, opt.Ref, scopedWorkflowSourceRepoID, func(workflowDispatch *model.WorkflowDispatch, inputs map[string]any) error {
|
||||
if strings.Contains(ctx.Req.Header.Get("Content-Type"), "form-urlencoded") {
|
||||
// The chi framework's "Binding" doesn't support to bind the form map values into a map[string]string
|
||||
// So we have to manually read the `inputs[key]` from the form
|
||||
@@ -1071,9 +1201,11 @@ func ActionsDispatchWorkflow(ctx *context.APIContext) {
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else if errors.Is(err, util.ErrPermissionDenied) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
} else if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -1132,7 +1264,7 @@ func ActionsEnableWorkflow(ctx *context.APIContext) {
|
||||
err := actions_service.EnableOrDisableWorkflow(ctx, workflowID, true)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -1145,13 +1277,11 @@ func ActionsEnableWorkflow(ctx *context.APIContext) {
|
||||
func getCurrentRepoActionRunByID(ctx *context.APIContext) *actions_model.ActionRun {
|
||||
runID := ctx.PathParamInt64("run")
|
||||
run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID)
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
return nil
|
||||
} else if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil
|
||||
}
|
||||
run.Repo = ctx.Repo.Repository
|
||||
return run
|
||||
}
|
||||
|
||||
@@ -1161,14 +1291,30 @@ func getCurrentRepoActionRunJobsByID(ctx *context.APIContext) (*actions_model.Ac
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
|
||||
jobs, err := actions_model.GetLatestAttemptJobsByRepoAndRunID(ctx, run.RepoID, run.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return nil, nil
|
||||
}
|
||||
jobs.SortMatrixGroupsByName()
|
||||
return run, jobs
|
||||
}
|
||||
|
||||
func getCurrentRepoActionRunAttemptByNumber(ctx *context.APIContext) (*actions_model.ActionRun, *actions_model.ActionRunAttempt) {
|
||||
run := getCurrentRepoActionRunByID(ctx)
|
||||
if ctx.Written() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
attemptNum := ctx.PathParamInt64("attempt")
|
||||
attempt, err := actions_model.GetRunAttemptByRunIDAndAttemptNum(ctx, run.ID, attemptNum)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil, nil
|
||||
}
|
||||
return run, attempt
|
||||
}
|
||||
|
||||
// GetWorkflowRun Gets a specific workflow run.
|
||||
func GetWorkflowRun(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run} repository GetWorkflowRun
|
||||
@@ -1205,7 +1351,56 @@ func GetWorkflowRun(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run)
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil, false)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, convertedRun)
|
||||
}
|
||||
|
||||
// GetWorkflowRunAttempt Gets a specific workflow run attempt.
|
||||
func GetWorkflowRunAttempt(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/attempts/{attempt} repository getWorkflowRunAttempt
|
||||
// ---
|
||||
// summary: Gets a specific workflow run attempt
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repository
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: run
|
||||
// in: path
|
||||
// description: id of the run
|
||||
// type: integer
|
||||
// required: true
|
||||
// - name: attempt
|
||||
// in: path
|
||||
// description: logical attempt number of the run
|
||||
// type: integer
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/WorkflowRun"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
run, attempt := getCurrentRepoActionRunAttemptByNumber(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, attempt, false)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -1245,6 +1440,8 @@ func RerunWorkflowRun(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "409":
|
||||
// "$ref": "#/responses/error"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
@@ -1253,12 +1450,12 @@ func RerunWorkflowRun(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, jobs); err != nil {
|
||||
if _, err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, ctx.Doer, jobs); err != nil {
|
||||
handleWorkflowRerunError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run)
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil, false)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -1296,6 +1493,8 @@ func RerunFailedWorkflowRun(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "409":
|
||||
// "$ref": "#/responses/error"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
@@ -1304,7 +1503,14 @@ func RerunFailedWorkflowRun(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, actions_service.GetFailedRerunJobs(jobs)); err != nil {
|
||||
failedJobs := actions_service.GetFailedJobsForRerun(jobs)
|
||||
// Empty failedJobs means no failed jobs to re-run
|
||||
if len(failedJobs) == 0 {
|
||||
ctx.APIError(http.StatusBadRequest, "this workflow run has no failed jobs to re-run")
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, ctx.Doer, failedJobs); err != nil {
|
||||
handleWorkflowRerunError(ctx, err)
|
||||
return
|
||||
}
|
||||
@@ -1349,6 +1555,8 @@ func RerunWorkflowJob(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "409":
|
||||
// "$ref": "#/responses/error"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
@@ -1360,17 +1568,33 @@ func RerunWorkflowJob(ctx *context.APIContext) {
|
||||
jobID := ctx.PathParamInt64("job_id")
|
||||
jobIdx := slices.IndexFunc(jobs, func(job *actions_model.ActionRunJob) bool { return job.ID == jobID })
|
||||
if jobIdx == -1 {
|
||||
ctx.APIErrorNotFound(util.NewNotExistErrorf("workflow job with id %d", jobID))
|
||||
ctx.APIErrorNotFound("workflow job not found")
|
||||
return
|
||||
}
|
||||
|
||||
targetJob := jobs[jobIdx]
|
||||
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, actions_service.GetAllRerunJobs(targetJob, jobs)); err != nil {
|
||||
newAttempt, err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, ctx.Doer, []*actions_model.ActionRunJob{targetJob})
|
||||
if err != nil {
|
||||
handleWorkflowRerunError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
convertedJob, err := convert.ToActionWorkflowJob(ctx, ctx.Repo.Repository, nil, targetJob)
|
||||
// Legacy jobs had AttemptJobID=0 before the rerun; createOriginalAttemptForLegacyRun inside
|
||||
// RerunWorkflowRunJobs has since backfilled it in the DB, so reload only in that case.
|
||||
if targetJob.AttemptJobID == 0 {
|
||||
targetJob, err = actions_model.GetRunJobByRepoAndID(ctx, run.RepoID, targetJob.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
rerunJob, err := actions_model.GetRunJobByAttemptJobID(ctx, run.ID, newAttempt.ID, targetJob.AttemptJobID)
|
||||
if err != nil {
|
||||
handleWorkflowRerunError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
convertedJob, err := convert.ToActionWorkflowJob(ctx, ctx.Repo.Repository, nil, rerunJob)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -1380,7 +1604,13 @@ func RerunWorkflowJob(ctx *context.APIContext) {
|
||||
|
||||
func handleWorkflowRerunError(ctx *context.APIContext, err error) {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
} else if errors.Is(err, util.ErrAlreadyExist) {
|
||||
ctx.APIError(http.StatusConflict, err.Error())
|
||||
return
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1422,6 +1652,83 @@ func ListWorkflowRunJobs(ctx *context.APIContext) {
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// - name: sort
|
||||
// in: query
|
||||
// description: sort jobs by attribute. Supported values are "id". Default is "id"
|
||||
// type: string
|
||||
// - name: order
|
||||
// in: query
|
||||
// description: sort order, either "asc" (ascending) or "desc" (descending). Default is "asc"
|
||||
// type: string
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/WorkflowJobsList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
repoID, runID := ctx.Repo.Repository.ID, ctx.PathParamInt64("run")
|
||||
|
||||
// Avoid the list all jobs functionality for this api route to be used with a runID == 0.
|
||||
if runID <= 0 {
|
||||
ctx.APIError(http.StatusBadRequest, "runID must be a positive integer")
|
||||
return
|
||||
}
|
||||
|
||||
run, err := actions_model.GetRunByRepoAndID(ctx, repoID, runID)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
// runID is used as an additional filter next to repoID to ensure that we only list jobs for the specified repoID and runID.
|
||||
// no additional checks for runID are needed here
|
||||
shared.ListJobs(ctx, 0, repoID, runID, optional.Some(run.LatestAttemptID))
|
||||
}
|
||||
|
||||
// ListWorkflowRunAttemptJobs Lists all jobs for a workflow run attempt.
|
||||
func ListWorkflowRunAttemptJobs(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/attempts/{attempt}/jobs repository listWorkflowRunAttemptJobs
|
||||
// ---
|
||||
// summary: Lists all jobs for a workflow run attempt
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repository
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: run
|
||||
// in: path
|
||||
// description: id of the workflow run
|
||||
// type: integer
|
||||
// required: true
|
||||
// - name: attempt
|
||||
// in: path
|
||||
// description: logical attempt number of the run
|
||||
// type: integer
|
||||
// required: true
|
||||
// - name: status
|
||||
// in: query
|
||||
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/WorkflowJobsList"
|
||||
@@ -1430,17 +1737,12 @@ func ListWorkflowRunJobs(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
repoID, runID := ctx.Repo.Repository.ID, ctx.PathParamInt64("run")
|
||||
|
||||
// Avoid the list all jobs functionality for this api route to be used with a runID == 0.
|
||||
if runID <= 0 {
|
||||
ctx.APIError(http.StatusBadRequest, util.NewInvalidArgumentErrorf("runID must be a positive integer"))
|
||||
run, attempt := getCurrentRepoActionRunAttemptByNumber(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
// runID is used as an additional filter next to repoID to ensure that we only list jobs for the specified repoID and runID.
|
||||
// no additional checks for runID are needed here
|
||||
shared.ListJobs(ctx, 0, repoID, runID)
|
||||
shared.ListJobs(ctx, 0, run.RepoID, run.ID, optional.Some(attempt.ID))
|
||||
}
|
||||
|
||||
// GetWorkflowJob Gets a specific workflow job for a workflow run.
|
||||
@@ -1482,7 +1784,7 @@ func GetWorkflowJob(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
if !has || job.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.APIErrorNotFound(util.ErrNotExist)
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1706,7 +2008,7 @@ func GetArtifact(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if actions.IsArtifactV4(art) {
|
||||
if actions_service.IsArtifactV4(art) {
|
||||
convertedArtifact, err := convert.ToActionArtifact(ctx.Repo.Repository, art)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1755,8 +2057,8 @@ func DeleteArtifact(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if actions.IsArtifactV4(art) {
|
||||
if err := actions_model.SetArtifactNeedDelete(ctx, art.RunID, art.ArtifactName); err != nil {
|
||||
if actions_service.IsArtifactV4(art) {
|
||||
if err := actions_model.SetArtifactNeedDeleteByID(ctx, art.ID); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
@@ -1771,8 +2073,8 @@ func buildSignature(endp string, expires, artifactID int64) []byte {
|
||||
return actions.BuildSignature("api", endp, strconv.FormatInt(expires, 10), strconv.FormatInt(artifactID, 10))
|
||||
}
|
||||
|
||||
func buildDownloadRawEndpoint(repo *repo_model.Repository, artifactID int64) string {
|
||||
return fmt.Sprintf("api/v1/repos/%s/%s/actions/artifacts/%d/zip/raw", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name), artifactID)
|
||||
func buildDownloadRawEndpoint(ownerName, repoName string, artifactID int64) string {
|
||||
return fmt.Sprintf("api/v1/repos/%s/%s/actions/artifacts/%d/zip/raw", url.PathEscape(ownerName), url.PathEscape(repoName), artifactID)
|
||||
}
|
||||
|
||||
func buildSigURL(ctx go_context.Context, endPoint string, artifactID int64) string {
|
||||
@@ -1824,16 +2126,16 @@ func DownloadArtifact(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if actions.IsArtifactV4(art) {
|
||||
if actions_service.IsArtifactV4(art) {
|
||||
// @actions/toolkit asserts that downloaded artifacts of a different runid return 302
|
||||
// https://github.com/actions/toolkit/blob/44d43b5490b02998bd09b0c4ff369a4cc67876c2/packages/artifact/src/internal/download/download-artifact.ts#L203-L210
|
||||
if actions.DownloadArtifactV4ServeDirect(ctx.Base, art) {
|
||||
if actions_service.DownloadArtifactV4ServeDirect(ctx.Base, art) {
|
||||
return
|
||||
}
|
||||
|
||||
// @actions/toolkit asserts a 302 for the artifact download, so we have to build a signed URL and redirect to it
|
||||
// TODO: a perma link to the code for reference
|
||||
redirectURL := buildSigURL(ctx, buildDownloadRawEndpoint(ctx.Repo.Repository, art.ID), art.ID)
|
||||
redirectURL := buildSigURL(ctx, buildDownloadRawEndpoint(ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name, art.ID), art.ID)
|
||||
ctx.Redirect(redirectURL, http.StatusFound)
|
||||
return
|
||||
}
|
||||
@@ -1844,7 +2146,22 @@ func DownloadArtifact(ctx *context.APIContext) {
|
||||
// DownloadArtifactRaw Downloads a specific artifact for a workflow run directly.
|
||||
func DownloadArtifactRaw(ctx *context.APIContext) {
|
||||
// it doesn't use repoAssignment middleware, so it needs to prepare the repo and check permission (sig) by itself
|
||||
repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, ctx.PathParam("username"), ctx.PathParam("reponame"))
|
||||
ownerName, repoName := ctx.PathParam("username"), ctx.PathParam("reponame")
|
||||
query := ctx.Req.URL.Query()
|
||||
sigBytes, _ := base64.RawURLEncoding.DecodeString(query.Get("sig"))
|
||||
expires, _ := strconv.ParseInt(query.Get("expires"), 10, 64)
|
||||
artifactID := ctx.PathParamInt64("artifact_id")
|
||||
|
||||
if !hmac.Equal(sigBytes, buildSignature(buildDownloadRawEndpoint(ownerName, repoName, artifactID), expires, artifactID)) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
if time.Unix(expires, 0).Before(time.Now()) {
|
||||
ctx.APIError(http.StatusUnauthorized, "Error link expired")
|
||||
return
|
||||
}
|
||||
|
||||
repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, ownerName, repoName)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIErrorNotFound()
|
||||
@@ -1858,29 +2175,13 @@ func DownloadArtifactRaw(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
sigStr := ctx.Req.URL.Query().Get("sig")
|
||||
expiresStr := ctx.Req.URL.Query().Get("expires")
|
||||
sigBytes, _ := base64.RawURLEncoding.DecodeString(sigStr)
|
||||
expires, _ := strconv.ParseInt(expiresStr, 10, 64)
|
||||
|
||||
expectedSig := buildSignature(buildDownloadRawEndpoint(repo, art.ID), expires, art.ID)
|
||||
if !hmac.Equal(sigBytes, expectedSig) {
|
||||
ctx.APIError(http.StatusUnauthorized, "Error unauthorized")
|
||||
return
|
||||
}
|
||||
t := time.Unix(expires, 0)
|
||||
if t.Before(time.Now()) {
|
||||
ctx.APIError(http.StatusUnauthorized, "Error link expired")
|
||||
return
|
||||
}
|
||||
|
||||
// if artifacts status is not uploaded-confirmed, treat it as not found
|
||||
if art.Status == actions_model.ArtifactStatusExpired {
|
||||
ctx.APIError(http.StatusNotFound, "Artifact has expired")
|
||||
return
|
||||
}
|
||||
if actions.IsArtifactV4(art) {
|
||||
err := actions.DownloadArtifactV4(ctx.Base, art)
|
||||
if actions_service.IsArtifactV4(art) {
|
||||
err := actions_service.DownloadArtifactV4(ctx.Base, art)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
||||
@@ -4,12 +4,9 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
actions_model "gitea.dev/models/actions"
|
||||
"gitea.dev/routers/common"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
func DownloadActionsRunJobLogs(ctx *context.APIContext) {
|
||||
@@ -45,11 +42,7 @@ func DownloadActionsRunJobLogs(ctx *context.APIContext) {
|
||||
jobID := ctx.PathParamInt64("job_id")
|
||||
curJob, err := actions_model.GetRunJobByRepoAndID(ctx, ctx.Repo.Repository.ID, jobID)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
if err = curJob.LoadRepo(ctx); err != nil {
|
||||
@@ -59,10 +52,6 @@ func DownloadActionsRunJobLogs(ctx *context.APIContext) {
|
||||
|
||||
err = common.DownloadActionsRunJobLogs(ctx.Base, ctx.Repo.Repository, curJob)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/context"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
)
|
||||
|
||||
// UpdateVatar updates the Avatar of an Repo
|
||||
@@ -44,7 +44,7 @@ func UpdateAvatar(ctx *context.APIContext) {
|
||||
|
||||
content, err := base64.StdEncoding.DecodeString(form.Image)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ package repo
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/services/context"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
"gitea.dev/services/context"
|
||||
files_service "gitea.dev/services/repository/files"
|
||||
)
|
||||
|
||||
// GetBlob get the blob of a repository file.
|
||||
@@ -48,7 +48,7 @@ func GetBlob(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
if blob, err := files_service.GetBlobBySHA(ctx.Repo.Repository, ctx.Repo.GitRepo, sha); err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
ctx.JSON(http.StatusOK, blob)
|
||||
}
|
||||
|
||||
@@ -8,24 +8,24 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
release_service "code.gitea.io/gitea/services/release"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
"gitea.dev/models/db"
|
||||
git_model "gitea.dev/models/git"
|
||||
"gitea.dev/models/organization"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/gitrepo"
|
||||
"gitea.dev/modules/optional"
|
||||
repo_module "gitea.dev/modules/repository"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
pull_service "gitea.dev/services/pull"
|
||||
release_service "gitea.dev/services/release"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
)
|
||||
|
||||
// GetBranch get a branch of a repository
|
||||
@@ -64,7 +64,7 @@ func GetBranch(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
} else if !exist {
|
||||
ctx.APIErrorNotFound(err)
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func GetBranch(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branchName, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
|
||||
br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branchName, c, branchProtection, ctx.Doer, ctx.Repo.Permission.IsAdmin())
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -153,11 +153,11 @@ func DeleteBranch(ctx *context.APIContext) {
|
||||
if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
|
||||
switch {
|
||||
case git.IsErrBranchNotExist(err):
|
||||
ctx.APIErrorNotFound(err)
|
||||
ctx.APIErrorNotFound()
|
||||
case errors.Is(err, repo_service.ErrBranchIsDefault):
|
||||
ctx.APIError(http.StatusForbidden, errors.New("can not delete default or pull request target branch"))
|
||||
ctx.APIError(http.StatusForbidden, "can not delete default or pull request target branch")
|
||||
case errors.Is(err, git_model.ErrBranchIsProtected):
|
||||
ctx.APIError(http.StatusForbidden, errors.New("branch protected"))
|
||||
ctx.APIError(http.StatusForbidden, "branch protected")
|
||||
default:
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -271,7 +271,7 @@ func CreateBranch(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
br, err := convert.ToBranch(ctx, ctx.Repo.Repository, opt.BranchName, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
|
||||
br, err := convert.ToBranch(ctx, ctx.Repo.Repository, opt.BranchName, commit, branchProtection, ctx.Doer, ctx.Repo.Permission.IsAdmin())
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -306,6 +306,10 @@ func ListBranches(ctx *context.APIContext) {
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// - name: q
|
||||
// in: query
|
||||
// description: branch name substring to filter by
|
||||
// type: string
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/BranchList"
|
||||
@@ -314,6 +318,7 @@ func ListBranches(ctx *context.APIContext) {
|
||||
var apiBranches []*api.Branch
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
keyword := ctx.FormString("q")
|
||||
|
||||
if !ctx.Repo.Repository.IsEmpty {
|
||||
if ctx.Repo.GitRepo == nil {
|
||||
@@ -325,6 +330,7 @@ func ListBranches(ctx *context.APIContext) {
|
||||
ListOptions: listOptions,
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
IsDeletedBranch: optional.Some(false),
|
||||
Keyword: keyword,
|
||||
}
|
||||
var err error
|
||||
totalNumOfBranches, err = db.Count[git_model.Branch](ctx, branchOpts)
|
||||
@@ -366,7 +372,7 @@ func ListBranches(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
branchProtection := rules.GetFirstMatched(branches[i].Name)
|
||||
apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i].Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
|
||||
apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i].Name, c, branchProtection, ctx.Doer, ctx.Repo.Permission.IsAdmin())
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -440,9 +446,9 @@ func UpdateBranch(ctx *context.APIContext) {
|
||||
if err := repo_service.UpdateBranch(ctx, repo, ctx.Repo.GitRepo, ctx.Doer, branchName, opt.NewCommitID, opt.OldCommitID, opt.Force); err != nil {
|
||||
switch {
|
||||
case git_model.IsErrBranchNotExist(err):
|
||||
ctx.APIErrorNotFound(err)
|
||||
ctx.APIErrorNotFound()
|
||||
case errors.Is(err, util.ErrInvalidArgument):
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
case git.IsErrPushRejected(err):
|
||||
rej := err.(*git.ErrPushRejected)
|
||||
ctx.APIError(http.StatusForbidden, rej.Message)
|
||||
@@ -678,7 +684,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||
whitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -687,7 +693,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||
forcePushAllowlistUsers, err := user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -696,7 +702,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||
mergeWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -705,18 +711,30 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||
approvalsWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
var whitelistTeams, forcePushAllowlistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
|
||||
var bypassAllowlistUsers []int64
|
||||
if form.EnableBypassAllowlist {
|
||||
bypassAllowlistUsers, err = user_model.GetUserIDsByNames(ctx, form.BypassAllowlistUsernames, false)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
var whitelistTeams, forcePushAllowlistTeams, mergeWhitelistTeams, approvalsWhitelistTeams, bypassAllowlistTeams []int64
|
||||
if repo.Owner.IsOrganization() {
|
||||
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -725,7 +743,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||
forcePushAllowlistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ForcePushAllowlistTeams, false)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -734,7 +752,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||
mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -743,12 +761,23 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||
approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ApprovalsWhitelistTeams, false)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if form.EnableBypassAllowlist {
|
||||
bypassAllowlistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.BypassAllowlistTeams, false)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protectBranch = &git_model.ProtectedBranch{
|
||||
@@ -762,6 +791,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||
EnableForcePushAllowlist: form.EnablePush && form.EnableForcePush && form.EnableForcePushAllowlist,
|
||||
ForcePushAllowlistDeployKeys: form.EnablePush && form.EnableForcePush && form.EnableForcePushAllowlist && form.ForcePushAllowlistDeployKeys,
|
||||
EnableMergeWhitelist: form.EnableMergeWhitelist,
|
||||
EnableBypassAllowlist: form.EnableBypassAllowlist,
|
||||
EnableStatusCheck: form.EnableStatusCheck,
|
||||
StatusCheckContexts: form.StatusCheckContexts,
|
||||
EnableApprovalsWhitelist: form.EnableApprovalsWhitelist,
|
||||
@@ -786,6 +816,8 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||
MergeTeamIDs: mergeWhitelistTeams,
|
||||
ApprovalsUserIDs: approvalsWhitelistUsers,
|
||||
ApprovalsTeamIDs: approvalsWhitelistTeams,
|
||||
BypassUserIDs: bypassAllowlistUsers,
|
||||
BypassTeamIDs: bypassAllowlistTeams,
|
||||
}); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -906,6 +938,10 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
protectBranch.EnableMergeWhitelist = *form.EnableMergeWhitelist
|
||||
}
|
||||
|
||||
if form.EnableBypassAllowlist != nil {
|
||||
protectBranch.EnableBypassAllowlist = *form.EnableBypassAllowlist
|
||||
}
|
||||
|
||||
if form.EnableStatusCheck != nil {
|
||||
protectBranch.EnableStatusCheck = *form.EnableStatusCheck
|
||||
}
|
||||
@@ -958,12 +994,12 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
protectBranch.BlockAdminMergeOverride = *form.BlockAdminMergeOverride
|
||||
}
|
||||
|
||||
var whitelistUsers, forcePushAllowlistUsers, mergeWhitelistUsers, approvalsWhitelistUsers []int64
|
||||
var whitelistUsers, forcePushAllowlistUsers, mergeWhitelistUsers, approvalsWhitelistUsers, bypassAllowlistUsers []int64
|
||||
if form.PushWhitelistUsernames != nil {
|
||||
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -976,7 +1012,7 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
forcePushAllowlistUsers, err = user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -989,7 +1025,7 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
mergeWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1002,7 +1038,7 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
approvalsWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1011,14 +1047,27 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
} else {
|
||||
approvalsWhitelistUsers = protectBranch.ApprovalsWhitelistUserIDs
|
||||
}
|
||||
if form.BypassAllowlistUsernames != nil {
|
||||
bypassAllowlistUsers, err = user_model.GetUserIDsByNames(ctx, form.BypassAllowlistUsernames, false)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
bypassAllowlistUsers = protectBranch.BypassAllowlistUserIDs
|
||||
}
|
||||
|
||||
var whitelistTeams, forcePushAllowlistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
|
||||
var whitelistTeams, forcePushAllowlistTeams, mergeWhitelistTeams, approvalsWhitelistTeams, bypassAllowlistTeams []int64
|
||||
if repo.Owner.IsOrganization() {
|
||||
if form.PushWhitelistTeams != nil {
|
||||
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1031,7 +1080,7 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
forcePushAllowlistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ForcePushAllowlistTeams, false)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1044,7 +1093,7 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1057,7 +1106,7 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ApprovalsWhitelistTeams, false)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1066,6 +1115,23 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
} else {
|
||||
approvalsWhitelistTeams = protectBranch.ApprovalsWhitelistTeamIDs
|
||||
}
|
||||
if form.BypassAllowlistTeams != nil {
|
||||
bypassAllowlistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.BypassAllowlistTeams, false)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
bypassAllowlistTeams = protectBranch.BypassAllowlistTeamIDs
|
||||
}
|
||||
}
|
||||
if !protectBranch.EnableBypassAllowlist {
|
||||
bypassAllowlistUsers = nil
|
||||
bypassAllowlistTeams = nil
|
||||
}
|
||||
|
||||
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
|
||||
@@ -1077,6 +1143,8 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
MergeTeamIDs: mergeWhitelistTeams,
|
||||
ApprovalsUserIDs: approvalsWhitelistUsers,
|
||||
ApprovalsTeamIDs: approvalsWhitelistTeams,
|
||||
BypassUserIDs: bypassAllowlistUsers,
|
||||
BypassTeamIDs: bypassAllowlistTeams,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1263,10 +1331,13 @@ func MergeUpstream(ctx *context.APIContext) {
|
||||
mergeStyle, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, form.Branch, form.FfOnly)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
return
|
||||
} else if errors.Is(err, util.ErrPermissionDenied) {
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
|
||||
@@ -9,18 +9,18 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
"gitea.dev/models/perm"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
issue_service "gitea.dev/services/issue"
|
||||
pull_service "gitea.dev/services/pull"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
)
|
||||
|
||||
// ListCollaborators list a repository's collaborators
|
||||
@@ -107,7 +107,7 @@ func IsCollaborator(ctx *context.APIContext) {
|
||||
user, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator"))
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -167,7 +167,7 @@ func AddOrUpdateCollaborator(ctx *context.APIContext) {
|
||||
collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator"))
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -181,12 +181,12 @@ func AddOrUpdateCollaborator(ctx *context.APIContext) {
|
||||
|
||||
p := perm.AccessModeWrite
|
||||
if form.Permission != nil {
|
||||
p = perm.ParseAccessMode(*form.Permission, perm.AccessModeRead, perm.AccessModeWrite, perm.AccessModeAdmin)
|
||||
p = perm.ParseAccessMode(string(*form.Permission), perm.AccessModeRead, perm.AccessModeWrite, perm.AccessModeAdmin)
|
||||
}
|
||||
|
||||
if err := repo_service.AddOrUpdateCollaborator(ctx, ctx.Repo.Repository, collaborator, p); err != nil {
|
||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -230,7 +230,7 @@ func DeleteCollaborator(ctx *context.APIContext) {
|
||||
collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator"))
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -284,7 +284,7 @@ func GetRepoPermissions(ctx *context.APIContext) {
|
||||
collaborator, err := user_model.GetUserByName(ctx, collaboratorUsername)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -326,7 +326,7 @@ func GetReviewers(ctx *context.APIContext) {
|
||||
|
||||
canChooseReviewer := issue_service.CanDoerChangeReviewRequests(ctx, ctx.Doer, ctx.Repo.Repository, 0)
|
||||
if !canChooseReviewer {
|
||||
ctx.APIError(http.StatusForbidden, errors.New("doer has no permission to get reviewers"))
|
||||
ctx.APIError(http.StatusForbidden, "doer has no permission to get reviewers")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -367,5 +367,42 @@ func GetAssignees(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToUsers(ctx, ctx.Doer, assignees))
|
||||
}
|
||||
|
||||
// CheckRepoIssueAssignee check if a user can be assigned to issues in a repository
|
||||
func CheckRepoIssueAssignee(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/assignees/{assignee} repository repoCheckAssignee
|
||||
// ---
|
||||
// summary: Check if a user can be assigned to issues in a repository
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: assignee
|
||||
// in: path
|
||||
// description: username of the user to check for being an assignee
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if checkAssignableUser(ctx, ctx.PathParam("assignee"), ctx.Repo.Repository) {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,15 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/gitrepo"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// GetSingleCommit get a commit via sha
|
||||
@@ -217,7 +217,7 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||
// get commit specified by sha
|
||||
baseCommit, err = ctx.Repo.GitRepo.GetCommit(sha)
|
||||
if err != nil {
|
||||
ctx.NotFoundOrServerError(err)
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -258,11 +258,30 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
} else if commitsCountTotal == 0 {
|
||||
ctx.APIErrorNotFound("FileCommitsCount", nil)
|
||||
return
|
||||
// when date filters are active, a zero count may just mean no
|
||||
// commits in the requested range — not that the path is invalid
|
||||
if since == "" && until == "" {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
// verify the path actually exists in the revision history
|
||||
totalWithoutDate, err := gitrepo.CommitsCount(ctx, ctx.Repo.Repository,
|
||||
gitrepo.CommitsCountOptions{
|
||||
Not: not,
|
||||
Revision: []string{sha},
|
||||
RelPath: []string{path},
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if totalWithoutDate == 0 {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
commits, err = ctx.Repo.GitRepo.CommitsByFileAndRange(
|
||||
commits, _, err = ctx.Repo.GitRepo.CommitsByFileAndRange(
|
||||
git.CommitsByFileAndRangeOptions{
|
||||
Revision: sha,
|
||||
File: path,
|
||||
@@ -383,7 +402,7 @@ func GetCommitPullRequest(ctx *context.APIContext) {
|
||||
pr, err := issues_model.GetPullRequestByMergedCommit(ctx, ctx.Repo.Repository.ID, ctx.PathParam("sha"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrPullRequestNotExist(err) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@ package repo
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/gitrepo"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
git_service "gitea.dev/services/git"
|
||||
)
|
||||
|
||||
// CompareDiff compare two branches or commits
|
||||
@@ -18,8 +19,12 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/compare/{basehead} repository repoCompareDiff
|
||||
// ---
|
||||
// summary: Get commit comparison information
|
||||
// description: |
|
||||
// By default returns JSON commit comparison information. The raw diff or patch can be
|
||||
// requested with the `output` query parameter set to `diff` or `patch` respectively.
|
||||
// produces:
|
||||
// - application/json
|
||||
// - text/plain
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
@@ -33,12 +38,21 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
// required: true
|
||||
// - name: basehead
|
||||
// in: path
|
||||
// description: compare two branches or commits
|
||||
// description: compare two refs as `base...head` (or `base..head`); refs may be branches, tags, full or short SHAs (including branch names that contain slashes), optionally with a `^` or `~N` revision suffix.
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: output
|
||||
// in: query
|
||||
// description: return the raw comparison as `diff` or `patch` instead of JSON
|
||||
// type: string
|
||||
// enum:
|
||||
// - diff
|
||||
// - patch
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Compare"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
@@ -57,6 +71,16 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
}
|
||||
defer closer()
|
||||
|
||||
// ?output=diff|patch returns the raw output, otherwise the JSON comparison is returned.
|
||||
switch ctx.FormString("output") {
|
||||
case "diff":
|
||||
downloadCompareDiffOrPatch(ctx, compareInfo, false)
|
||||
return
|
||||
case "patch":
|
||||
downloadCompareDiffOrPatch(ctx, compareInfo, true)
|
||||
return
|
||||
}
|
||||
|
||||
verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
|
||||
files := ctx.FormString("files") == "" || ctx.FormBool("files")
|
||||
|
||||
@@ -88,3 +112,20 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
Commits: apiCommits,
|
||||
})
|
||||
}
|
||||
|
||||
// downloadCompareDiffOrPatch writes a comparison's raw diff or patch to the response.
|
||||
func downloadCompareDiffOrPatch(ctx *context.APIContext, compareInfo *git_service.CompareInfo, patch bool) {
|
||||
ctx.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
compareArg := compareInfo.BaseCommitID + compareInfo.CompareSeparator + compareInfo.HeadCommitID
|
||||
|
||||
var err error
|
||||
if patch {
|
||||
err = compareInfo.HeadGitRepo.GetPatch(compareArg, ctx.Resp)
|
||||
} else {
|
||||
err = compareInfo.HeadGitRepo.GetDiff(compareArg, ctx.Resp)
|
||||
}
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,19 +7,19 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
archiver_service "code.gitea.io/gitea/services/repository/archiver"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/services/context"
|
||||
archiver_service "gitea.dev/services/repository/archiver"
|
||||
)
|
||||
|
||||
func serveRepoArchive(ctx *context.APIContext, reqFileName string, paths []string) {
|
||||
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, reqFileName, paths)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -28,7 +28,7 @@ func serveRepoArchive(ctx *context.APIContext, reqFileName string, paths []strin
|
||||
err = archiver_service.ServeRepoArchive(ctx.Base, aReq)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
@@ -14,22 +14,22 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/httpcache"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
git_model "gitea.dev/models/git"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/httpcache"
|
||||
"gitea.dev/modules/httplib"
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/lfs"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/storage"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/routers/common"
|
||||
"gitea.dev/services/context"
|
||||
pull_service "gitea.dev/services/pull"
|
||||
files_service "gitea.dev/services/repository/files"
|
||||
)
|
||||
|
||||
const giteaObjectTypeHeader = "X-Gitea-Object-Type"
|
||||
@@ -213,7 +213,7 @@ func getBlobForEntry(ctx *context.APIContext) (blob *git.Blob, entry *git.TreeEn
|
||||
}
|
||||
|
||||
if entry.IsDir() || entry.IsSubModule() {
|
||||
ctx.APIErrorNotFound("getBlobForEntry", nil)
|
||||
ctx.APIErrorNotFound()
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
@@ -301,18 +301,14 @@ func GetEditorconfig(ctx *context.APIContext) {
|
||||
|
||||
ec, _, err := ctx.Repo.GetEditorconfig(ctx.Repo.Commit)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
fileName := ctx.PathParam("filename")
|
||||
def, err := ec.GetDefinitionForFilename(fileName)
|
||||
if def == nil {
|
||||
ctx.APIErrorNotFound(err)
|
||||
if err != nil {
|
||||
ctx.APIErrorNotFound(err.Error())
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, def)
|
||||
@@ -409,7 +405,7 @@ func ChangeFiles(ctx *context.APIContext) {
|
||||
for _, file := range apiOpts.Files {
|
||||
contentReader, err := base64Reader(file.ContentBase64)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
// FIXME: ChangeFileOperation.SHA is NOT required for update or delete if last commit is provided in the options
|
||||
@@ -483,7 +479,7 @@ func CreateFile(ctx *context.APIContext) {
|
||||
}
|
||||
contentReader, err := base64Reader(apiOpts.ContentBase64)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -554,7 +550,7 @@ func UpdateFile(ctx *context.APIContext) {
|
||||
}
|
||||
contentReader, err := base64Reader(apiOpts.ContentBase64)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
willCreate := apiOpts.SHA == ""
|
||||
@@ -584,17 +580,17 @@ func handleChangeRepoFilesError(ctx *context.APIContext, err error) {
|
||||
return
|
||||
}
|
||||
if files_service.IsErrUserCannotCommit(err) || pull_service.IsErrFilePathProtected(err) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
return
|
||||
}
|
||||
if git_model.IsErrBranchAlreadyExists(err) || files_service.IsErrFilenameInvalid(err) || pull_service.IsErrSHADoesNotMatch(err) ||
|
||||
files_service.IsErrFilePathInvalid(err) || files_service.IsErrRepoFileAlreadyExists(err) ||
|
||||
files_service.IsErrCommitIDDoesNotMatch(err) || files_service.IsErrSHAOrCommitIDNotProvided(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -699,10 +695,8 @@ func DeleteFile(ctx *context.APIContext) {
|
||||
func resolveRefCommit(ctx *context.APIContext, ref string, minCommitIDLen ...int) *utils.RefCommit {
|
||||
ref = util.IfZero(ref, ctx.Repo.Repository.DefaultBranch)
|
||||
refCommit, err := utils.ResolveRefCommit(ctx, ctx.Repo.Repository, ref, minCommitIDLen...)
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
}
|
||||
return refCommit
|
||||
}
|
||||
@@ -828,11 +822,8 @@ func getRepoContents(ctx *context.APIContext, opts files_service.GetContentsOrLi
|
||||
}
|
||||
ret, err := files_service.GetContentsOrList(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, refCommit, opts)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
ctx.APIErrorNotFound("GetContentsOrList", err)
|
||||
return nil
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
@@ -8,19 +8,19 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
"gitea.dev/models/organization"
|
||||
"gitea.dev/models/perm"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/optional"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
)
|
||||
|
||||
// ListForks list a repository's forks
|
||||
@@ -55,7 +55,8 @@ func ListForks(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
forks, total, err := repo_service.FindForks(ctx, ctx.Repo.Repository, ctx.Doer, utils.GetListOptions(ctx))
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
forks, total, err := repo_service.FindForks(ctx, ctx.Repo.Repository, ctx.Doer, listOptions)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -79,6 +80,7 @@ func ListForks(ctx *context.APIContext) {
|
||||
apiForks[i] = convert.ToRepo(ctx, fork, permission)
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(total, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, apiForks)
|
||||
}
|
||||
@@ -165,9 +167,9 @@ func CreateFork(ctx *context.APIContext) {
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrAlreadyExist) || repo_model.IsErrReachLimitOfRepo(err) {
|
||||
ctx.APIError(http.StatusConflict, err)
|
||||
ctx.APIError(http.StatusConflict, err.Error())
|
||||
} else if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"gitea.dev/modules/git"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// ListGitHooks list all Git hooks of a repository
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// GetGitAllRefs get ref or an list all the refs of a repository
|
||||
|
||||
@@ -7,19 +7,19 @@ package repo
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
"code.gitea.io/gitea/models/webhook"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
webhook_service "code.gitea.io/gitea/services/webhook"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/perm"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
"gitea.dev/models/webhook"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
webhook_module "gitea.dev/modules/webhook"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
webhook_service "gitea.dev/services/webhook"
|
||||
)
|
||||
|
||||
// ListHooks list all hooks of a repository
|
||||
@@ -177,7 +177,7 @@ func TestHook(ctx *context.APIContext) {
|
||||
commit := convert.ToPayloadCommit(ctx, ctx.Repo.Repository, ctx.Repo.Commit)
|
||||
|
||||
commitID := ctx.Repo.Commit.ID.String()
|
||||
if err := webhook_service.PrepareWebhook(ctx, hook, webhook_module.HookEventPush, &api.PushPayload{
|
||||
if err := webhook_service.PrepareTestWebhook(ctx, hook, webhook_module.HookEventPush, &api.PushPayload{
|
||||
Ref: ref,
|
||||
Before: commitID,
|
||||
After: commitID,
|
||||
|
||||
@@ -5,11 +5,13 @@ package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/models/webhook"
|
||||
"code.gitea.io/gitea/services/contexttest"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/unittest"
|
||||
"gitea.dev/models/webhook"
|
||||
"gitea.dev/services/contexttest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -17,8 +19,17 @@ import (
|
||||
func TestTestHook(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
|
||||
hook := &webhook.Webhook{
|
||||
RepoID: 1,
|
||||
URL: "https://www.example.com/test_hook",
|
||||
ContentType: webhook.ContentTypeJSON,
|
||||
Events: `{"push_only":true}`,
|
||||
IsActive: true,
|
||||
}
|
||||
assert.NoError(t, db.Insert(t.Context(), hook))
|
||||
|
||||
ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/wiki/_pages")
|
||||
ctx.SetPathParam("id", "1")
|
||||
ctx.SetPathParam("id", strconv.FormatInt(hook.ID, 10))
|
||||
contexttest.LoadRepo(t, ctx, 1)
|
||||
contexttest.LoadRepoCommit(t, ctx)
|
||||
contexttest.LoadUser(t, ctx, 2)
|
||||
@@ -26,6 +37,6 @@ func TestTestHook(t *testing.T) {
|
||||
assert.Equal(t, http.StatusNoContent, ctx.Resp.WrittenStatus())
|
||||
|
||||
unittest.AssertExistsAndLoadBean(t, &webhook.HookTask{
|
||||
HookID: 1,
|
||||
HookID: hook.ID,
|
||||
}, unittest.Cond("is_delivered=?", false))
|
||||
}
|
||||
|
||||
@@ -12,82 +12,26 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
"gitea.dev/models/db"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unit"
|
||||
user_model "gitea.dev/models/user"
|
||||
issue_indexer "gitea.dev/modules/indexer/issues"
|
||||
"gitea.dev/modules/optional"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/routers/common"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
issue_service "gitea.dev/services/issue"
|
||||
)
|
||||
|
||||
// buildSearchIssuesRepoIDs builds the list of repository IDs for issue search based on query parameters.
|
||||
// It returns repoIDs, allPublic flag, and any error that occurred.
|
||||
func buildSearchIssuesRepoIDs(ctx *context.APIContext) (repoIDs []int64, allPublic bool, err error) {
|
||||
opts := repo_model.SearchRepoOptions{
|
||||
Private: false,
|
||||
AllPublic: true,
|
||||
TopicOnly: false,
|
||||
Collaborate: optional.None[bool](),
|
||||
// This needs to be a column that is not nil in fixtures or
|
||||
// MySQL will return different results when sorting by null in some cases
|
||||
OrderBy: db.SearchOrderByAlphabetically,
|
||||
Actor: ctx.Doer,
|
||||
}
|
||||
if ctx.IsSigned {
|
||||
opts.Private = true
|
||||
opts.AllLimited = true
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
if ctx.FormString("owner") != "" {
|
||||
owner, err := user_model.GetUserByName(ctx, ctx.FormString("owner"))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
opts.OwnerID = owner.ID
|
||||
opts.AllLimited = false
|
||||
opts.AllPublic = false
|
||||
opts.Collaborate = optional.Some(false)
|
||||
}
|
||||
if ctx.FormString("team") != "" {
|
||||
if ctx.FormString("owner") == "" {
|
||||
return nil, false, util.NewInvalidArgumentErrorf("owner organisation is required for filtering on team")
|
||||
}
|
||||
team, err := organization.GetTeam(ctx, opts.OwnerID, ctx.FormString("team"))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
opts.TeamID = team.ID
|
||||
}
|
||||
|
||||
if opts.AllPublic {
|
||||
allPublic = true
|
||||
opts.AllPublic = false // set it false to avoid returning too many repos, we could filter by indexer
|
||||
}
|
||||
repoIDs, _, err = repo_model.SearchRepositoryIDs(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if len(repoIDs) == 0 {
|
||||
// no repos found, don't let the indexer return all repos
|
||||
repoIDs = []int64{0}
|
||||
}
|
||||
|
||||
return repoIDs, allPublic, nil
|
||||
}
|
||||
|
||||
// SearchIssues searches for issues across the repositories that the user has access to
|
||||
func SearchIssues(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/issues/search issue issueSearchIssues
|
||||
@@ -187,16 +131,21 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
|
||||
before, since, err := context.GetQueryBeforeSince(ctx.Base)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
isClosed := common.ParseIssueFilterStateIsClosed(ctx.FormString("state"))
|
||||
|
||||
repoIDs, allPublic, err := buildSearchIssuesRepoIDs(ctx)
|
||||
repoIDs, allPublic, err := common.SearchIssuesRepoIDs(ctx, common.SearchIssuesRepoIDsOptions{
|
||||
Doer: ctx.Doer,
|
||||
PublicOnly: ctx.PublicOnly,
|
||||
OwnerName: ctx.FormString("owner"),
|
||||
TeamName: ctx.FormString("team"),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -204,10 +153,6 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
keyword := ctx.FormTrim("q")
|
||||
if strings.IndexByte(keyword, 0) >= 0 {
|
||||
keyword = ""
|
||||
}
|
||||
|
||||
isPull := common.ParseIssueFilterTypeIsPull(ctx.FormString("type"))
|
||||
|
||||
var includedAnyLabels []int64
|
||||
@@ -384,15 +329,12 @@ func ListIssues(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
before, since, err := context.GetQueryBeforeSince(ctx.Base)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
isClosed := common.ParseIssueFilterStateIsClosed(ctx.FormString("state"))
|
||||
keyword := ctx.FormTrim("q")
|
||||
if strings.IndexByte(keyword, 0) >= 0 {
|
||||
keyword = ""
|
||||
}
|
||||
|
||||
var labelIDs []int64
|
||||
if splitted := strings.Split(ctx.FormString("labels"), ","); len(splitted) > 0 {
|
||||
@@ -435,22 +377,16 @@ func ListIssues(ctx *context.APIContext) {
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
isPull := optional.None[bool]()
|
||||
switch ctx.FormString("type") {
|
||||
case "pulls":
|
||||
isPull = optional.Some(true)
|
||||
case "issues":
|
||||
isPull = optional.Some(false)
|
||||
}
|
||||
isPull := common.ParseIssueFilterTypeIsPull(ctx.FormString("type"))
|
||||
|
||||
if isPull.Has() && !ctx.Repo.CanReadIssuesOrPulls(isPull.Value()) {
|
||||
if isPull.Has() && !ctx.Repo.Permission.CanReadIssuesOrPulls(isPull.Value()) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if !isPull.Has() {
|
||||
canReadIssues := ctx.Repo.CanRead(unit.TypeIssues)
|
||||
canReadPulls := ctx.Repo.CanRead(unit.TypePullRequests)
|
||||
canReadIssues := ctx.Repo.Permission.CanRead(unit.TypeIssues)
|
||||
canReadPulls := ctx.Repo.Permission.CanRead(unit.TypePullRequests)
|
||||
if !canReadIssues && !canReadPulls {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
@@ -540,16 +476,10 @@ func getUserIDForFilter(ctx *context.APIContext, queryName string) int64 {
|
||||
}
|
||||
|
||||
user, err := user_model.GetUserByName(ctx, userName)
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
return 0
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
ctx.APIErrorAuto(err)
|
||||
return 0
|
||||
}
|
||||
|
||||
return user.ID
|
||||
}
|
||||
|
||||
@@ -592,7 +522,7 @@ func GetIssue(ctx *context.APIContext) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
if !ctx.Repo.Permission.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
@@ -639,7 +569,7 @@ func CreateIssue(ctx *context.APIContext) {
|
||||
|
||||
form := web.GetForm(ctx).(*api.CreateIssueOption)
|
||||
var deadlineUnix timeutil.TimeStamp
|
||||
if form.Deadline != nil && ctx.Repo.CanWrite(unit.TypeIssues) {
|
||||
if form.Deadline != nil && ctx.Repo.Permission.CanWrite(unit.TypeIssues) {
|
||||
deadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
|
||||
}
|
||||
|
||||
@@ -656,7 +586,7 @@ func CreateIssue(ctx *context.APIContext) {
|
||||
|
||||
assigneeIDs := make([]int64, 0)
|
||||
var err error
|
||||
if ctx.Repo.CanWrite(unit.TypeIssues) {
|
||||
if ctx.Repo.Permission.CanWrite(unit.TypeIssues) {
|
||||
issue.MilestoneID = form.Milestone
|
||||
assigneeIDs, err = issues_model.MakeIDsFromAPIAssigneesToAdd(ctx, form.Assignee, form.Assignees)
|
||||
if err != nil {
|
||||
@@ -676,13 +606,13 @@ func CreateIssue(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
valid, err := access_model.CanBeAssigned(ctx, assignee, ctx.Repo.Repository, false)
|
||||
valid, err := access_model.CanBeAssigned(ctx, assignee, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if !valid {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: ctx.Repo.Repository.Name})
|
||||
ctx.APIError(http.StatusUnprocessableEntity, repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: ctx.Repo.Repository.Name}.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -691,11 +621,11 @@ func CreateIssue(ctx *context.APIContext) {
|
||||
form.Labels = make([]int64, 0)
|
||||
}
|
||||
|
||||
if err := issue_service.NewIssue(ctx, ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs, 0); err != nil {
|
||||
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
if err := issue_service.NewIssue(ctx, ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs, form.Projects); err != nil {
|
||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
} else if errors.Is(err, util.ErrPermissionDenied) || errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -776,7 +706,7 @@ func EditIssue(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
issue.Repo = ctx.Repo.Repository
|
||||
canWrite := ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
|
||||
canWrite := ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull)
|
||||
|
||||
err = issue.LoadAttributes(ctx)
|
||||
if err != nil {
|
||||
@@ -794,7 +724,7 @@ func EditIssue(ctx *context.APIContext) {
|
||||
// handles concurrent requests.
|
||||
// TODO: wrap all mutations in a transaction to fully prevent partial writes.
|
||||
if form.ContentVersion != nil && *form.ContentVersion != issue.ContentVersion {
|
||||
ctx.APIError(http.StatusConflict, issues_model.ErrIssueAlreadyChanged)
|
||||
ctx.APIError(http.StatusConflict, issues_model.ErrIssueAlreadyChanged.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -813,7 +743,7 @@ func EditIssue(ctx *context.APIContext) {
|
||||
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, contentVersion)
|
||||
if err != nil {
|
||||
if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
|
||||
ctx.APIError(http.StatusConflict, err)
|
||||
ctx.APIError(http.StatusConflict, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -869,7 +799,7 @@ func EditIssue(ctx *context.APIContext) {
|
||||
err = issue_service.UpdateAssignees(ctx, issue, oneAssignee, form.Assignees, ctx.Doer)
|
||||
if err != nil {
|
||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -914,6 +844,18 @@ func EditIssue(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update projects if provided
|
||||
if canWrite && form.Projects != nil {
|
||||
if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, *form.Projects); err != nil {
|
||||
if errors.Is(err, util.ErrPermissionDenied) || errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Refetch from database to assign some automatic values
|
||||
issue, err = issues_model.GetIssueByID(ctx, issue.ID)
|
||||
if err != nil {
|
||||
@@ -957,11 +899,7 @@ func DeleteIssue(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrIssueNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1021,7 +959,7 @@ func UpdateIssueDeadline(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
if !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
ctx.APIError(http.StatusForbidden, "Not repo writer")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
issues_model "gitea.dev/models/issues"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
issue_service "gitea.dev/services/issue"
|
||||
)
|
||||
|
||||
// AddIssueAssignees add assignees to an issue
|
||||
func AddIssueAssignees(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/assignees issue issueAddAssignees
|
||||
// ---
|
||||
// summary: Add assignees to an issue
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: index
|
||||
// in: path
|
||||
// description: index of the issue
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// required: true
|
||||
// schema:
|
||||
// "$ref": "#/definitions/IssueAssigneesOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/Issue"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
opts := web.GetForm(ctx).(*api.IssueAssigneesOption)
|
||||
updateIssueAssignees(ctx, *opts, true)
|
||||
}
|
||||
|
||||
// DeleteIssueAssignees remove assignees from an issue
|
||||
func DeleteIssueAssignees(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/assignees issue issueRemoveAssignees
|
||||
// ---
|
||||
// summary: Remove assignees from an issue
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: index
|
||||
// in: path
|
||||
// description: index of the issue
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// required: true
|
||||
// schema:
|
||||
// "$ref": "#/definitions/IssueAssigneesOption"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Issue"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
opts := web.GetForm(ctx).(*api.IssueAssigneesOption)
|
||||
updateIssueAssignees(ctx, *opts, false)
|
||||
}
|
||||
|
||||
// CheckIssueAssignee check if a user can be assigned to an issue
|
||||
func CheckIssueAssignee(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/assignees/{assignee} issue issueCheckAssignee
|
||||
// ---
|
||||
// summary: Check if a user can be assigned to an issue
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: index
|
||||
// in: path
|
||||
// description: index of the issue
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: assignee
|
||||
// in: path
|
||||
// description: username of the user to check for being an assignee
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.Permission.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if checkAssignableUser(ctx, ctx.PathParam("assignee"), ctx.Repo.Repository) {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
// checkAssignableUser resolves assigneeName and verifies the user can be assigned to issues in repo.
|
||||
// Returns true only when the user resolves AND is assignable; the caller is responsible for writing the 204.
|
||||
// On any failure it writes the appropriate API response and returns false.
|
||||
func checkAssignableUser(ctx *context.APIContext, assigneeName string, repo *repo_model.Repository) bool {
|
||||
assignee, err := user_model.GetUserByName(ctx, assigneeName)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return false
|
||||
}
|
||||
|
||||
canAssign, err := access_model.CanBeAssigned(ctx, assignee, repo)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return false
|
||||
}
|
||||
|
||||
if !canAssign {
|
||||
ctx.APIErrorNotFound()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func updateIssueAssignees(ctx *context.APIContext, opts api.IssueAssigneesOption, isAdd bool) {
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if err := issue.LoadAttributes(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
assigneeIDs, err := user_model.GetUserIDsByNames(ctx, opts.Assignees, false)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
if isAdd {
|
||||
err = issue_service.AddAssignees(ctx, issue, ctx.Doer, assigneeIDs)
|
||||
} else {
|
||||
err = issue_service.RemoveAssignees(ctx, issue, ctx.Doer, assigneeIDs)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
issue, err = issues_model.GetIssueByID(ctx, issue.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
status := http.StatusOK
|
||||
if isAdd {
|
||||
status = http.StatusCreated
|
||||
}
|
||||
ctx.JSON(status, convert.ToAPIIssue(ctx, ctx.Doer, issue))
|
||||
}
|
||||
@@ -7,18 +7,18 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
attachment_service "code.gitea.io/gitea/services/attachment"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/context/upload"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
attachment_service "gitea.dev/services/attachment"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/context/upload"
|
||||
"gitea.dev/services/convert"
|
||||
issue_service "gitea.dev/services/issue"
|
||||
)
|
||||
|
||||
// GetIssueAttachment gets a single attachment of the issue
|
||||
@@ -194,9 +194,9 @@ func CreateIssueAttachment(ctx *context.APIContext) {
|
||||
})
|
||||
if err != nil {
|
||||
if upload.IsErrFileTypeForbidden(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else if errors.Is(err, util.ErrContentTooLarge) {
|
||||
ctx.APIError(http.StatusRequestEntityTooLarge, err)
|
||||
ctx.APIError(http.StatusRequestEntityTooLarge, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -272,7 +272,7 @@ func EditIssueAttachment(ctx *context.APIContext) {
|
||||
|
||||
if err := attachment_service.UpdateAttachment(ctx, setting.Attachment.AllowedTypes, attachment); err != nil {
|
||||
if upload.IsErrFileTypeForbidden(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -336,7 +336,7 @@ func DeleteIssueAttachment(ctx *context.APIContext) {
|
||||
func getIssueFromContext(ctx *context.APIContext) *issues_model.Issue {
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
ctx.NotFoundOrServerError(err)
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@ func getIssueAttachmentSafeWrite(ctx *context.APIContext) *repo_model.Attachment
|
||||
func getIssueAttachmentSafeRead(ctx *context.APIContext, issue *issues_model.Issue) *repo_model.Attachment {
|
||||
attachment, err := repo_model.GetAttachmentByID(ctx, ctx.PathParamInt64("attachment_id"))
|
||||
if err != nil {
|
||||
ctx.NotFoundOrServerError(err)
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil
|
||||
}
|
||||
if !attachmentBelongsToRepoOrIssue(ctx, attachment, issue) {
|
||||
@@ -371,7 +371,7 @@ func getIssueAttachmentSafeRead(ctx *context.APIContext, issue *issues_model.Iss
|
||||
}
|
||||
|
||||
func canUserWriteIssueAttachment(ctx *context.APIContext, issue *issues_model.Issue) bool {
|
||||
canEditIssue := ctx.IsSigned && (ctx.Doer.ID == issue.PosterID || ctx.IsUserRepoAdmin() || ctx.IsUserSiteAdmin() || ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull))
|
||||
canEditIssue := ctx.IsSigned && (ctx.Doer.ID == issue.PosterID || ctx.IsUserRepoAdmin() || ctx.IsUserSiteAdmin() || ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull))
|
||||
if !canEditIssue {
|
||||
ctx.APIError(http.StatusForbidden, "user should have permission to write issue")
|
||||
return false
|
||||
|
||||
@@ -9,18 +9,18 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unit"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/optional"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
issue_service "gitea.dev/services/issue"
|
||||
)
|
||||
|
||||
// ListIssueComments list all the comments of an issue
|
||||
@@ -65,7 +65,7 @@ func ListIssueComments(ctx *context.APIContext) {
|
||||
|
||||
before, since, err := context.GetQueryBeforeSince(ctx.Base)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
@@ -73,7 +73,7 @@ func ListIssueComments(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
if !ctx.Repo.Permission.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
@@ -169,7 +169,7 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) {
|
||||
|
||||
before, since, err := context.GetQueryBeforeSince(ctx.Base)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
@@ -274,13 +274,13 @@ func ListRepoIssueComments(ctx *context.APIContext) {
|
||||
|
||||
before, since, err := context.GetQueryBeforeSince(ctx.Base)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var isPull optional.Option[bool]
|
||||
canReadIssue := ctx.Repo.CanRead(unit.TypeIssues)
|
||||
canReadPull := ctx.Repo.CanRead(unit.TypePullRequests)
|
||||
canReadIssue := ctx.Repo.Permission.CanRead(unit.TypeIssues)
|
||||
canReadPull := ctx.Repo.Permission.CanRead(unit.TypePullRequests)
|
||||
if canReadIssue && canReadPull {
|
||||
isPull = optional.None[bool]()
|
||||
} else if canReadIssue {
|
||||
@@ -386,20 +386,20 @@ func CreateIssueComment(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
if !ctx.Repo.Permission.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin {
|
||||
ctx.APIError(http.StatusForbidden, errors.New(ctx.Locale.TrString("repo.issues.comment_on_locked")))
|
||||
if issue.IsLocked && !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin {
|
||||
ctx.APIError(http.StatusForbidden, ctx.Locale.TrString("repo.issues.comment_on_locked"))
|
||||
return
|
||||
}
|
||||
|
||||
comment, err := issue_service.CreateIssueComment(ctx, ctx.Doer, ctx.Repo.Repository, issue, form.Body, nil)
|
||||
if err != nil {
|
||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -447,15 +447,11 @@ func GetIssueComment(ctx *context.APIContext) {
|
||||
|
||||
comment, err := issues_model.GetCommentWithRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrCommentNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
if !ctx.Repo.Permission.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
@@ -572,15 +568,11 @@ func EditIssueCommentDeprecated(ctx *context.APIContext) {
|
||||
func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) {
|
||||
comment, err := issues_model.GetCommentWithRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrCommentNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.Permission.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
@@ -595,7 +587,7 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
|
||||
comment.Content = form.Body
|
||||
if err := issue_service.UpdateComment(ctx, comment, comment.ContentVersion, ctx.Doer, oldContent); err != nil {
|
||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -681,15 +673,11 @@ func DeleteIssueCommentDeprecated(ctx *context.APIContext) {
|
||||
func deleteIssueComment(ctx *context.APIContext) {
|
||||
comment, err := issues_model.GetCommentWithRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrCommentNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.Permission.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
} else if !comment.Type.HasContentSupport() {
|
||||
|
||||
@@ -7,19 +7,19 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
attachment_service "code.gitea.io/gitea/services/attachment"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/context/upload"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
attachment_service "gitea.dev/services/attachment"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/context/upload"
|
||||
"gitea.dev/services/convert"
|
||||
issue_service "gitea.dev/services/issue"
|
||||
)
|
||||
|
||||
// GetIssueCommentAttachment gets a single attachment of the comment
|
||||
@@ -202,9 +202,9 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
|
||||
})
|
||||
if err != nil {
|
||||
if upload.IsErrFileTypeForbidden(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else if errors.Is(err, util.ErrContentTooLarge) {
|
||||
ctx.APIError(http.StatusRequestEntityTooLarge, err)
|
||||
ctx.APIError(http.StatusRequestEntityTooLarge, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -218,7 +218,7 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
|
||||
|
||||
if err = issue_service.UpdateComment(ctx, comment, comment.ContentVersion, ctx.Doer, comment.Content); err != nil {
|
||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -285,7 +285,7 @@ func EditIssueCommentAttachment(ctx *context.APIContext) {
|
||||
|
||||
if err := attachment_service.UpdateAttachment(ctx, setting.Attachment.AllowedTypes, attach); err != nil {
|
||||
if upload.IsErrFileTypeForbidden(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -346,7 +346,7 @@ func DeleteIssueCommentAttachment(ctx *context.APIContext) {
|
||||
func getIssueCommentSafe(ctx *context.APIContext) *issues_model.Comment {
|
||||
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
ctx.NotFoundOrServerError(err)
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil
|
||||
}
|
||||
if err := comment.LoadIssue(ctx); err != nil {
|
||||
@@ -358,7 +358,7 @@ func getIssueCommentSafe(ctx *context.APIContext) *issues_model.Comment {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
if !ctx.Repo.Permission.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ func getIssueCommentAttachmentSafeWrite(ctx *context.APIContext) *repo_model.Att
|
||||
}
|
||||
|
||||
func canUserWriteIssueCommentAttachment(ctx *context.APIContext, comment *issues_model.Comment) bool {
|
||||
canEditComment := ctx.IsSigned && (ctx.Doer.ID == comment.PosterID || ctx.IsUserRepoAdmin() || ctx.IsUserSiteAdmin()) && ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)
|
||||
canEditComment := ctx.IsSigned && (ctx.Doer.ID == comment.PosterID || ctx.IsUserRepoAdmin() || ctx.IsUserSiteAdmin()) && ctx.Repo.Permission.CanWriteIssuesOrPulls(comment.Issue.IsPull)
|
||||
if !canEditComment {
|
||||
ctx.APIError(http.StatusForbidden, "user should have permission to edit comment")
|
||||
return false
|
||||
@@ -391,7 +391,7 @@ func canUserWriteIssueCommentAttachment(ctx *context.APIContext, comment *issues
|
||||
func getIssueCommentAttachmentSafeRead(ctx *context.APIContext, comment *issues_model.Comment) *repo_model.Attachment {
|
||||
attachment, err := repo_model.GetAttachmentByID(ctx, ctx.PathParamInt64("attachment_id"))
|
||||
if err != nil {
|
||||
ctx.NotFoundOrServerError(err)
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil
|
||||
}
|
||||
if !attachmentBelongsToRepoOrComment(ctx, attachment, comment) {
|
||||
|
||||
@@ -7,15 +7,15 @@ package repo
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// GetIssueDependencies list an issue's dependencies
|
||||
@@ -63,11 +63,7 @@ func GetIssueDependencies(ctx *context.APIContext) {
|
||||
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrIssueNotExist(err) {
|
||||
ctx.APIErrorNotFound("IsErrIssueNotExist", err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -487,11 +483,7 @@ func RemoveIssueBlocking(ctx *context.APIContext) {
|
||||
func getParamsIssue(ctx *context.APIContext) *issues_model.Issue {
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrIssueNotExist(err) {
|
||||
ctx.APIErrorNotFound("IsErrIssueNotExist", err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil
|
||||
}
|
||||
issue.Repo = ctx.Repo.Repository
|
||||
@@ -508,11 +500,7 @@ func getFormIssue(ctx *context.APIContext, form *api.IssueMeta) *issues_model.Is
|
||||
var err error
|
||||
repo, err = repo_model.GetRepositoryByOwnerAndName(ctx, form.Owner, form.Name)
|
||||
if err != nil {
|
||||
if repo_model.IsErrRepoNotExist(err) {
|
||||
ctx.APIErrorNotFound("IsErrRepoNotExist", err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
@@ -521,11 +509,7 @@ func getFormIssue(ctx *context.APIContext, form *api.IssueMeta) *issues_model.Is
|
||||
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, repo.ID, form.Index)
|
||||
if err != nil {
|
||||
if issues_model.IsErrIssueNotExist(err) {
|
||||
ctx.APIErrorNotFound("IsErrIssueNotExist", err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil
|
||||
}
|
||||
issue.Repo = repo
|
||||
|
||||
@@ -9,12 +9,12 @@ import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
issue_service "gitea.dev/services/issue"
|
||||
)
|
||||
|
||||
// ListIssueLabels list all the labels of an issue
|
||||
@@ -173,18 +173,17 @@ func DeleteIssueLabel(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
if !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
label, err := issues_model.GetLabelByID(ctx, ctx.PathParamInt64("id"))
|
||||
// the label must belong to this repo (or its owning org); otherwise a foreign label ID
|
||||
// is rejected the same way as a nonexistent one, closing a cross-repo enumeration oracle
|
||||
labelID := ctx.PathParamInt64("id")
|
||||
label, err := issues_model.GetLabelInRepoOrOrgByID(ctx, ctx.Repo.Repository.ID, ctx.Repo.Owner.ID, ctx.Repo.Owner.IsOrganization(), labelID)
|
||||
if err != nil {
|
||||
if issues_model.IsErrLabelNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -295,7 +294,7 @@ func ClearIssueLabels(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
if !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
@@ -319,7 +318,7 @@ func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
if !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
ctx.APIError(http.StatusForbidden, "write permission is required")
|
||||
return nil, nil, errors.New("permission denied")
|
||||
}
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// LockIssue lock an issue
|
||||
@@ -54,16 +53,12 @@ func LockIssue(ctx *context.APIContext) {
|
||||
reason := web.GetForm(ctx).(*api.LockIssueOption).Reason
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrIssueNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
ctx.APIError(http.StatusForbidden, errors.New("no permission to lock this issue"))
|
||||
if !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
ctx.APIError(http.StatusForbidden, "no permission to lock this issue")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -121,16 +116,12 @@ func UnlockIssue(ctx *context.APIContext) {
|
||||
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrIssueNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
ctx.APIError(http.StatusForbidden, errors.New("no permission to unlock this issue"))
|
||||
if !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
ctx.APIError(http.StatusForbidden, "no permission to unlock this issue")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ package repo
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// PinIssue pins a issue
|
||||
@@ -46,7 +46,7 @@ func PinIssue(ctx *context.APIContext) {
|
||||
if issues_model.IsErrIssueNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else if issues_model.IsErrIssueMaxPinReached(err) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
issue_service "gitea.dev/services/issue"
|
||||
)
|
||||
|
||||
// GetIssueCommentReactions list reactions of a comment from an issue
|
||||
@@ -53,11 +53,7 @@ func GetIssueCommentReactions(ctx *context.APIContext) {
|
||||
|
||||
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrCommentNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -71,8 +67,8 @@ func GetIssueCommentReactions(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
ctx.APIError(http.StatusForbidden, errors.New("no permission to get reactions"))
|
||||
if !ctx.Repo.Permission.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
ctx.APIError(http.StatusForbidden, "no permission to get reactions")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -190,11 +186,7 @@ func DeleteIssueCommentReaction(ctx *context.APIContext) {
|
||||
func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
|
||||
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrCommentNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -208,13 +200,13 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
if !ctx.Repo.Permission.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if comment.Issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull) {
|
||||
ctx.APIError(http.StatusForbidden, errors.New("no permission to change reaction"))
|
||||
if comment.Issue.IsLocked && !ctx.Repo.Permission.CanWriteIssuesOrPulls(comment.Issue.IsPull) {
|
||||
ctx.APIError(http.StatusForbidden, "no permission to change reaction")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -223,7 +215,7 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
|
||||
reaction, err := issue_service.CreateCommentReaction(ctx, ctx.Doer, comment, form.Reaction)
|
||||
if err != nil {
|
||||
if issues_model.IsErrForbiddenIssueReaction(err) || errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
} else if issues_model.IsErrReactionAlreadyExist(err) {
|
||||
ctx.JSON(http.StatusOK, api.Reaction{
|
||||
User: convert.ToUser(ctx, ctx.Doer, ctx.Doer),
|
||||
@@ -304,8 +296,8 @@ func GetIssueReactions(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
ctx.APIError(http.StatusForbidden, errors.New("no permission to get reactions"))
|
||||
if !ctx.Repo.Permission.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
ctx.APIError(http.StatusForbidden, "no permission to get reactions")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -428,8 +420,8 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i
|
||||
return
|
||||
}
|
||||
|
||||
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
ctx.APIError(http.StatusForbidden, errors.New("no permission to change reaction"))
|
||||
if issue.IsLocked && !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
ctx.APIError(http.StatusForbidden, "no permission to change reaction")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -438,7 +430,7 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i
|
||||
reaction, err := issue_service.CreateIssueReaction(ctx, ctx.Doer, issue, form.Reaction)
|
||||
if err != nil {
|
||||
if issues_model.IsErrForbiddenIssueReaction(err) || errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
} else if issues_model.IsErrReactionAlreadyExist(err) {
|
||||
ctx.JSON(http.StatusOK, api.Reaction{
|
||||
User: convert.ToUser(ctx, ctx.Doer, ctx.Doer),
|
||||
|
||||
@@ -6,10 +6,10 @@ package repo
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// StartIssueStopwatch creates a stopwatch for the given issue.
|
||||
@@ -178,7 +178,7 @@ func prepareIssueForStopwatch(ctx *context.APIContext) *issues_model.Issue {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
if !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// AddIssueSubscription Subscribe user to issue
|
||||
@@ -128,7 +128,7 @@ func setIssueSubscription(ctx *context.APIContext, watch bool) {
|
||||
|
||||
// only admin and user for itself can change subscription
|
||||
if user.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin {
|
||||
ctx.APIError(http.StatusForbidden, fmt.Errorf("%s is not permitted to change subscriptions for %s", ctx.Doer.Name, user.Name))
|
||||
ctx.APIError(http.StatusForbidden, fmt.Sprintf("%s is not permitted to change subscriptions for %s", ctx.Doer.Name, user.Name))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -4,19 +4,19 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"gitea.dev/models/db"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
"gitea.dev/models/unit"
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// ListTrackedTimes list all the tracked times of an issue
|
||||
@@ -72,16 +72,12 @@ func ListTrackedTimes(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if !ctx.Repo.Repository.IsTimetrackerEnabled(ctx) {
|
||||
ctx.APIErrorNotFound("Timetracker is disabled")
|
||||
ctx.APIErrorNotFound("timetracker is disabled")
|
||||
return
|
||||
}
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrIssueNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -95,7 +91,8 @@ func ListTrackedTimes(ctx *context.APIContext) {
|
||||
if qUser != "" {
|
||||
user, err := user_model.GetUserByName(ctx, qUser)
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
return
|
||||
} else if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -104,7 +101,7 @@ func ListTrackedTimes(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Base); err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -116,7 +113,7 @@ func ListTrackedTimes(ctx *context.APIContext) {
|
||||
if opts.UserID == 0 {
|
||||
opts.UserID = ctx.Doer.ID
|
||||
} else {
|
||||
ctx.APIError(http.StatusForbidden, errors.New("query by user not allowed; not enough rights"))
|
||||
ctx.APIError(http.StatusForbidden, "query by user not allowed; not enough rights")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -136,11 +133,33 @@ func ListTrackedTimes(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
trackedTimes, err = filterTrackedTimesByAccess(ctx, trackedTimes)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, convert.ToTrackedTimeList(ctx, ctx.Doer, trackedTimes))
|
||||
}
|
||||
|
||||
func filterTrackedTimesByAccess(ctx *context.APIContext, trackedTimes issues_model.TrackedTimeList) (issues_model.TrackedTimeList, error) {
|
||||
filtered := make(issues_model.TrackedTimeList, 0, len(trackedTimes))
|
||||
for _, trackedTime := range trackedTimes {
|
||||
if trackedTime.Issue == nil || trackedTime.Issue.Repo == nil {
|
||||
continue
|
||||
}
|
||||
permission, err := access_model.GetIndividualUserRepoPermission(ctx, trackedTime.Issue.Repo, ctx.Doer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if permission.HasAnyUnitAccessOrPublicAccess() {
|
||||
filtered = append(filtered, trackedTime)
|
||||
}
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
// AddTime add time manual to the given issue
|
||||
func AddTime(ctx *context.APIContext) {
|
||||
// swagger:operation Post /repos/{owner}/{repo}/issues/{index}/times issue issueAddTime
|
||||
@@ -183,11 +202,7 @@ func AddTime(ctx *context.APIContext) {
|
||||
form := web.GetForm(ctx).(*api.AddTimeOption)
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrIssueNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -206,7 +221,8 @@ func AddTime(ctx *context.APIContext) {
|
||||
// allow only RepoAdmin, Admin and User to add time
|
||||
user, err = user_model.GetUserByName(ctx, form.User)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,11 +282,7 @@ func ResetIssueTime(ctx *context.APIContext) {
|
||||
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrIssueNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -286,7 +298,7 @@ func ResetIssueTime(ctx *context.APIContext) {
|
||||
err = issues_model.DeleteIssueUserTimes(ctx, issue, ctx.Doer)
|
||||
if err != nil {
|
||||
if db.IsErrNotExist(err) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -339,11 +351,7 @@ func DeleteTime(ctx *context.APIContext) {
|
||||
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrIssueNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -358,11 +366,7 @@ func DeleteTime(ctx *context.APIContext) {
|
||||
|
||||
time, err := issues_model.GetTrackedTimeByID(ctx, issue.ID, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if db.IsErrNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
if time.Deleted {
|
||||
@@ -424,11 +428,7 @@ func ListTrackedTimesByUser(ctx *context.APIContext) {
|
||||
}
|
||||
user, err := user_model.GetUserByName(ctx, ctx.PathParam("timetrackingusername"))
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
if user == nil {
|
||||
@@ -437,7 +437,7 @@ func ListTrackedTimesByUser(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
if !ctx.IsUserRepoAdmin() && !ctx.Doer.IsAdmin && ctx.Doer.ID != user.ID {
|
||||
ctx.APIError(http.StatusForbidden, errors.New("query by user not allowed; not enough rights"))
|
||||
ctx.APIError(http.StatusForbidden, "query by user not allowed; not enough rights")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -523,7 +523,8 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
|
||||
if qUser != "" {
|
||||
user, err := user_model.GetUserByName(ctx, qUser)
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
return
|
||||
} else if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -533,7 +534,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
|
||||
|
||||
var err error
|
||||
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Base); err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -545,7 +546,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
|
||||
if opts.UserID == 0 {
|
||||
opts.UserID = ctx.Doer.ID
|
||||
} else {
|
||||
ctx.APIError(http.StatusForbidden, errors.New("query by user not allowed; not enough rights"))
|
||||
ctx.APIError(http.StatusForbidden, "query by user not allowed; not enough rights")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -565,6 +566,11 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
trackedTimes, err = filterTrackedTimesByAccess(ctx, trackedTimes)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, convert.ToTrackedTimeList(ctx, ctx.Doer, trackedTimes))
|
||||
@@ -607,7 +613,7 @@ func ListMyTrackedTimes(ctx *context.APIContext) {
|
||||
|
||||
var err error
|
||||
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Base); err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -627,6 +633,11 @@ func ListMyTrackedTimes(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
trackedTimes, err = filterTrackedTimesByAccess(ctx, trackedTimes)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, convert.ToTrackedTimeList(ctx, ctx.Doer, trackedTimes))
|
||||
|
||||
@@ -10,18 +10,18 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
asymkey_model "gitea.dev/models/asymkey"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/perm"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
asymkey_service "gitea.dev/services/asymkey"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// appendPrivateInformation appends the owner and key type information to api.PublicKey
|
||||
@@ -179,7 +179,7 @@ func HandleCheckKeyStringError(ctx *context.APIContext, err error) {
|
||||
} else if asymkey_model.IsErrKeyUnableVerify(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "Unable to verify key content")
|
||||
} else {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid key content: %w", err))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid key content: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/modules/label"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
"gitea.dev/modules/label"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// ListLabels list all the labels of a repository
|
||||
@@ -106,11 +106,7 @@ func GetLabel(ctx *context.APIContext) {
|
||||
l, err = issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, intID)
|
||||
}
|
||||
if err != nil {
|
||||
if issues_model.IsErrRepoLabelNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -153,7 +149,7 @@ func CreateLabel(ctx *context.APIContext) {
|
||||
|
||||
color, err := label.NormalizeColor(form.Color)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
form.Color = color
|
||||
@@ -214,11 +210,7 @@ func EditLabel(ctx *context.APIContext) {
|
||||
form := web.GetForm(ctx).(*api.EditLabelOption)
|
||||
l, err := issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrRepoLabelNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -231,7 +223,7 @@ func EditLabel(ctx *context.APIContext) {
|
||||
if form.Color != nil {
|
||||
color, err := label.NormalizeColor(*form.Color)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
l.Color = color
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
type languageResponse []*repo_model.LanguageStat
|
||||
|
||||
@@ -6,9 +6,9 @@ package repo
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// GetLicenses returns licenses
|
||||
|
||||
@@ -6,9 +6,9 @@ package repo
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
webhook_service "code.gitea.io/gitea/services/webhook"
|
||||
"gitea.dev/models/unittest"
|
||||
"gitea.dev/modules/setting"
|
||||
webhook_service "gitea.dev/services/webhook"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
||||
@@ -10,26 +10,26 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
base "code.gitea.io/gitea/modules/migration"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"code.gitea.io/gitea/services/migrations"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/organization"
|
||||
"gitea.dev/models/perm"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/graceful"
|
||||
"gitea.dev/modules/lfs"
|
||||
"gitea.dev/modules/log"
|
||||
base "gitea.dev/modules/migration"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
"gitea.dev/services/migrations"
|
||||
notify_service "gitea.dev/services/notify"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
)
|
||||
|
||||
// Migrate migrate remote git repository to gitea
|
||||
@@ -72,18 +72,13 @@ func Migrate(ctx *context.APIContext) {
|
||||
}
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.HasAPIError() {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, ctx.GetErrMsg())
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Doer.IsAdmin {
|
||||
if !repoOwner.IsOrganization() && ctx.Doer.ID != repoOwner.ID {
|
||||
ctx.APIError(http.StatusForbidden, "Given user is not an organization.")
|
||||
@@ -115,12 +110,12 @@ func Migrate(ctx *context.APIContext) {
|
||||
gitServiceType := convert.ToGitServiceType(form.Service)
|
||||
|
||||
if form.Mirror && setting.Mirror.DisableNewPull {
|
||||
ctx.APIError(http.StatusForbidden, errors.New("the site administrator has disabled the creation of new pull mirrors"))
|
||||
ctx.APIError(http.StatusForbidden, "the site administrator has disabled the creation of new pull mirrors")
|
||||
return
|
||||
}
|
||||
|
||||
if setting.Repository.DisableMigrations {
|
||||
ctx.APIError(http.StatusForbidden, errors.New("the site administrator has disabled migrations"))
|
||||
ctx.APIError(http.StatusForbidden, "the site administrator has disabled migrations")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -240,9 +235,9 @@ func handleMigrateError(ctx *context.APIContext, repoOwner *user_model.User, err
|
||||
case db.IsErrNamePatternNotAllowed(err):
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(db.ErrNamePatternNotAllowed).Pattern))
|
||||
case git.IsErrInvalidCloneAddr(err):
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
case base.IsErrNotSupported(err):
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
default:
|
||||
err = util.SanitizeErrorCredentialURLs(err)
|
||||
if strings.Contains(err.Error(), "Authentication failed") ||
|
||||
@@ -262,7 +257,7 @@ func handleRemoteAddrError(ctx *context.APIContext, err error) {
|
||||
addrErr := err.(*git.ErrInvalidCloneAddr)
|
||||
switch {
|
||||
case addrErr.IsURLError:
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "The provided URL is invalid.")
|
||||
case addrErr.IsPermissionDenied:
|
||||
if addrErr.LocalPath {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "You are not allowed to import local repositories.")
|
||||
|
||||
@@ -8,15 +8,15 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"gitea.dev/models/db"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/routers/common"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// ListMilestones list milestones for a repository
|
||||
|
||||
@@ -5,22 +5,24 @@ package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"code.gitea.io/gitea/services/migrations"
|
||||
mirror_service "code.gitea.io/gitea/services/mirror"
|
||||
"gitea.dev/models/db"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unit"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
"gitea.dev/services/migrations"
|
||||
mirror_service "gitea.dev/services/mirror"
|
||||
)
|
||||
|
||||
// MirrorSync adds a mirrored repository to the sync queue
|
||||
@@ -51,7 +53,7 @@ func MirrorSync(ctx *context.APIContext) {
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
|
||||
if !ctx.Repo.CanWrite(unit.TypeCode) {
|
||||
if !ctx.Repo.Permission.CanWrite(unit.TypeCode) {
|
||||
ctx.APIError(http.StatusForbidden, "Must have write access")
|
||||
}
|
||||
|
||||
@@ -101,6 +103,8 @@ func PushMirrorSync(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
if !setting.Mirror.Enabled {
|
||||
ctx.APIError(http.StatusBadRequest, "Mirror feature is disabled")
|
||||
@@ -109,17 +113,21 @@ func PushMirrorSync(ctx *context.APIContext) {
|
||||
// Get All push mirrors of a specific repo
|
||||
pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, ctx.Repo.Repository.ID, db.ListOptions{})
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
failedPushMirrors := make([]string, 0)
|
||||
for _, mirror := range pushMirrors {
|
||||
ok := mirror_service.SyncPushMirror(ctx, mirror.ID)
|
||||
if !ok {
|
||||
ctx.APIErrorInternal(errors.New("error occurred when syncing push mirror " + mirror.RemoteName))
|
||||
return
|
||||
failedPushMirrors = append(failedPushMirrors, mirror.RemoteName)
|
||||
}
|
||||
}
|
||||
|
||||
if len(failedPushMirrors) != 0 {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "error occurred when syncing push mirrors: "+strings.Join(failedPushMirrors, ", "))
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
@@ -168,7 +176,7 @@ func ListPushMirrors(ctx *context.APIContext) {
|
||||
// Get all push mirrors for the specified repository.
|
||||
pushMirrors, count, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, utils.GetListOptions(ctx))
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -232,7 +240,7 @@ func GetPushMirrorByName(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
} else if !exist {
|
||||
ctx.APIError(http.StatusNotFound, nil)
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -327,7 +335,7 @@ func DeletePushMirrorByRemoteName(ctx *context.APIContext) {
|
||||
// Delete push mirror on repo by name.
|
||||
err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{RepoID: ctx.Repo.Repository.ID, RemoteName: remoteName})
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
@@ -337,8 +345,12 @@ func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirro
|
||||
repo := ctx.Repo.Repository
|
||||
|
||||
interval, err := time.ParseDuration(mirrorOption.Interval)
|
||||
if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, fmt.Sprintf("invalid interval: %v", err))
|
||||
return
|
||||
}
|
||||
if interval != 0 && interval < setting.Mirror.MinInterval {
|
||||
ctx.APIError(http.StatusBadRequest, fmt.Sprintf("interval is shorter than minimum %v", setting.Mirror.MinInterval.String()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -351,11 +363,7 @@ func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirro
|
||||
return
|
||||
}
|
||||
|
||||
remoteSuffix, err := util.CryptoRandomString(10)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
remoteSuffix := util.CryptoRandomString(10)
|
||||
|
||||
remoteAddress, err := util.SanitizeURL(mirrorOption.RemoteAddress)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unittest"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/test"
|
||||
"gitea.dev/services/contexttest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestPushMirrorSync verifies the endpoint attempts every push mirror instead
|
||||
// of aborting on the first failure, reporting all failed remotes with a 422.
|
||||
// Each remote name is not a configured git remote, so SyncPushMirror fails fast
|
||||
// without any network access.
|
||||
func TestPushMirrorSync(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
defer test.MockVariableValue(&setting.Mirror.Enabled, true)()
|
||||
|
||||
for _, remoteName := range []string{"broken_remote_1", "broken_remote_2"} {
|
||||
assert.NoError(t, db.Insert(t.Context(), &repo_model.PushMirror{RepoID: 1, RemoteName: remoteName}))
|
||||
}
|
||||
|
||||
ctx, resp := contexttest.MockAPIContext(t, "user2/repo1")
|
||||
contexttest.LoadRepo(t, ctx, 1)
|
||||
|
||||
PushMirrorSync(ctx)
|
||||
|
||||
assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
|
||||
assert.Contains(t, resp.Body.String(), "broken_remote_1")
|
||||
assert.Contains(t, resp.Body.String(), "broken_remote_2")
|
||||
}
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"gitea.dev/modules/git"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// GetNote Get a note corresponding to a single commit from a repository
|
||||
@@ -68,11 +68,7 @@ func getNote(ctx *context.APIContext, identifier string) {
|
||||
|
||||
commitID, err := ctx.Repo.GitRepo.ConvertToGitID(identifier)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ package repo
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/repository/files"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/repository/files"
|
||||
)
|
||||
|
||||
// ApplyDiffPatch handles API call for applying a patch
|
||||
|
||||
@@ -12,39 +12,38 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
pull_model "code.gitea.io/gitea/models/pull"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
"code.gitea.io/gitea/services/automerge"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
git_service "code.gitea.io/gitea/services/git"
|
||||
"code.gitea.io/gitea/services/gitdiff"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
activities_model "gitea.dev/models/activities"
|
||||
git_model "gitea.dev/models/git"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
pull_model "gitea.dev/models/pull"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unit"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/base"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
"gitea.dev/modules/gitrepo"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/optional"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/routers/common"
|
||||
asymkey_service "gitea.dev/services/asymkey"
|
||||
"gitea.dev/services/automerge"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
"gitea.dev/services/forms"
|
||||
git_service "gitea.dev/services/git"
|
||||
"gitea.dev/services/gitdiff"
|
||||
issue_service "gitea.dev/services/issue"
|
||||
notify_service "gitea.dev/services/notify"
|
||||
pull_service "gitea.dev/services/pull"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
)
|
||||
|
||||
// ListPullRequests returns a list of all PRs
|
||||
@@ -126,7 +125,7 @@ func ListPullRequests(ctx *context.APIContext) {
|
||||
poster, err := user_model.GetUserByName(ctx, posterStr)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -448,7 +447,7 @@ func CreatePullRequest(ctx *context.APIContext) {
|
||||
HeadBranch: existingPr.HeadBranch,
|
||||
BaseBranch: existingPr.BaseBranch,
|
||||
}
|
||||
ctx.APIError(http.StatusConflict, err)
|
||||
ctx.APIError(http.StatusConflict, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -521,7 +520,7 @@ func CreatePullRequest(ctx *context.APIContext) {
|
||||
BaseBranch: compareResult.BaseRef.ShortName(),
|
||||
HeadRepo: compareResult.HeadRepo,
|
||||
BaseRepo: repo,
|
||||
MergeBase: compareResult.MergeBase,
|
||||
MergeBase: compareResult.CompareBase,
|
||||
Type: issues_model.PullRequestGitea,
|
||||
}
|
||||
|
||||
@@ -545,13 +544,13 @@ func CreatePullRequest(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
valid, err := access_model.CanBeAssigned(ctx, assignee, repo, true)
|
||||
valid, err := access_model.CanBeAssigned(ctx, assignee, repo)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if !valid {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: repo.Name})
|
||||
ctx.APIError(http.StatusUnprocessableEntity, repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: repo.Name}.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -570,11 +569,11 @@ func CreatePullRequest(ctx *context.APIContext) {
|
||||
|
||||
if err := pull_service.NewPullRequest(ctx, prOpts); err != nil {
|
||||
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
} else if errors.Is(err, issues_model.ErrMustCollaborator) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -653,7 +652,7 @@ func EditPullRequest(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !issue.IsPoster(ctx.Doer.ID) && !ctx.Repo.CanWrite(unit.TypePullRequests) {
|
||||
if !issue.IsPoster(ctx.Doer.ID) && !ctx.Repo.Permission.CanWrite(unit.TypePullRequests) {
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
@@ -663,7 +662,7 @@ func EditPullRequest(ctx *context.APIContext) {
|
||||
// handles concurrent requests.
|
||||
// TODO: wrap all mutations in a transaction to fully prevent partial writes.
|
||||
if form.ContentVersion != nil && *form.ContentVersion != issue.ContentVersion {
|
||||
ctx.APIError(http.StatusConflict, issues_model.ErrIssueAlreadyChanged)
|
||||
ctx.APIError(http.StatusConflict, issues_model.ErrIssueAlreadyChanged.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -682,7 +681,7 @@ func EditPullRequest(ctx *context.APIContext) {
|
||||
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, contentVersion)
|
||||
if err != nil {
|
||||
if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
|
||||
ctx.APIError(http.StatusConflict, err)
|
||||
ctx.APIError(http.StatusConflict, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -715,13 +714,13 @@ func EditPullRequest(ctx *context.APIContext) {
|
||||
// Pass one or more user logins to replace the set of assignees on this Issue.
|
||||
// Send an empty array ([]) to clear all assignees from the Issue.
|
||||
|
||||
if ctx.Repo.CanWrite(unit.TypePullRequests) && (form.Assignees != nil || len(form.Assignee) > 0) {
|
||||
if ctx.Repo.Permission.CanWrite(unit.TypePullRequests) && (form.Assignees != nil || len(form.Assignee) > 0) {
|
||||
err = issue_service.UpdateAssignees(ctx, issue, form.Assignee, form.Assignees, ctx.Doer)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("Assignee does not exist: [name: %s]", err))
|
||||
} else if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -729,7 +728,7 @@ func EditPullRequest(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Repo.CanWrite(unit.TypePullRequests) && form.Milestone != 0 &&
|
||||
if ctx.Repo.Permission.CanWrite(unit.TypePullRequests) && form.Milestone != 0 &&
|
||||
issue.MilestoneID != form.Milestone {
|
||||
oldMilestoneID := issue.MilestoneID
|
||||
issue.MilestoneID = form.Milestone
|
||||
@@ -744,7 +743,7 @@ func EditPullRequest(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Repo.CanWrite(unit.TypePullRequests) && form.Labels != nil {
|
||||
if ctx.Repo.Permission.CanWrite(unit.TypePullRequests) && form.Labels != nil {
|
||||
labels, err := issues_model.GetLabelsInRepoByIDs(ctx, ctx.Repo.Repository.ID, form.Labels)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -788,18 +787,18 @@ func EditPullRequest(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
if !branchExist {
|
||||
ctx.APIError(http.StatusNotFound, fmt.Errorf("new base '%s' not exist", form.Base))
|
||||
ctx.APIError(http.StatusNotFound, fmt.Sprintf("new base '%s' not exist", form.Base))
|
||||
return
|
||||
}
|
||||
if err := pull_service.ChangeTargetBranch(ctx, pr, ctx.Doer, form.Base); err != nil {
|
||||
if issues_model.IsErrPullRequestAlreadyExists(err) {
|
||||
ctx.APIError(http.StatusConflict, err)
|
||||
ctx.APIError(http.StatusConflict, err.Error())
|
||||
return
|
||||
} else if issues_model.IsErrIssueIsClosed(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
} else if pull_service.IsErrPullRequestHasMerged(err) {
|
||||
ctx.APIError(http.StatusConflict, err)
|
||||
ctx.APIError(http.StatusConflict, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -927,11 +926,7 @@ func MergePullRequest(ctx *context.APIContext) {
|
||||
|
||||
pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrPullRequestNotExist(err) {
|
||||
ctx.APIErrorNotFound("GetPullRequestByIndex", err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -965,7 +960,7 @@ func MergePullRequest(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
// start with merging by checking
|
||||
if err := pull_service.CheckPullMergeable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, mergeCheckType, form.ForceMerge); err != nil {
|
||||
if err := pull_service.CheckPullMergeable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, mergeCheckType, repo_model.MergeStyle(form.Do), form.ForceMerge); err != nil {
|
||||
if errors.Is(err, pull_service.ErrIsClosed) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else if errors.Is(err, pull_service.ErrNoPermissionToMerge) {
|
||||
@@ -977,9 +972,11 @@ func MergePullRequest(ctx *context.APIContext) {
|
||||
} else if errors.Is(err, pull_service.ErrNotMergeableState) {
|
||||
ctx.APIError(http.StatusMethodNotAllowed, "Please try again later")
|
||||
} else if errors.Is(err, pull_service.ErrNotReadyToMerge) {
|
||||
ctx.APIError(http.StatusMethodNotAllowed, err)
|
||||
ctx.APIError(http.StatusMethodNotAllowed, err.Error())
|
||||
} else if asymkey_service.IsErrWontSign(err) {
|
||||
ctx.APIError(http.StatusMethodNotAllowed, err)
|
||||
ctx.APIError(http.StatusMethodNotAllowed, err.Error())
|
||||
} else if errors.Is(err, pull_service.ErrHeadCommitsNotAllVerified) {
|
||||
ctx.APIError(http.StatusMethodNotAllowed, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -990,11 +987,11 @@ func MergePullRequest(ctx *context.APIContext) {
|
||||
if manuallyMerged {
|
||||
if err := pull_service.MergedManually(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil {
|
||||
if pull_service.IsErrInvalidMergeStyle(err) {
|
||||
ctx.APIError(http.StatusMethodNotAllowed, fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do)))
|
||||
ctx.APIError(http.StatusMethodNotAllowed, fmt.Sprintf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do)))
|
||||
return
|
||||
}
|
||||
if strings.Contains(err.Error(), "Wrong commit ID") {
|
||||
ctx.APIError(http.StatusConflict, err)
|
||||
ctx.APIError(http.StatusConflict, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1032,7 +1029,7 @@ func MergePullRequest(ctx *context.APIContext) {
|
||||
scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message, deleteBranchAfterMerge)
|
||||
if err != nil {
|
||||
if pull_model.IsErrAlreadyScheduledToAutoMerge(err) {
|
||||
ctx.APIError(http.StatusConflict, err)
|
||||
ctx.APIError(http.StatusConflict, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1044,9 +1041,9 @@ func MergePullRequest(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := pull_service.Merge(ctx, pr, ctx.Doer, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil {
|
||||
if err := pull_service.Merge(pr, ctx.Doer, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil {
|
||||
if pull_service.IsErrInvalidMergeStyle(err) {
|
||||
ctx.APIError(http.StatusMethodNotAllowed, fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do)))
|
||||
ctx.APIError(http.StatusMethodNotAllowed, fmt.Sprintf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do)))
|
||||
} else if pull_service.IsErrMergeConflicts(err) {
|
||||
conflictError := err.(pull_service.ErrMergeConflicts)
|
||||
ctx.JSON(http.StatusConflict, conflictError)
|
||||
@@ -1089,12 +1086,6 @@ func parseCompareInfo(ctx *context.APIContext, compareParam string) (result *git
|
||||
baseRepo := ctx.Repo.Repository
|
||||
compareReq := common.ParseCompareRouterParam(compareParam)
|
||||
|
||||
// remove the check when we support compare with carets
|
||||
if compareReq.BaseOriRefSuffix != "" {
|
||||
ctx.APIError(http.StatusBadRequest, "Unsupported comparison syntax: ref with suffix")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
_, headRepo, err := common.GetHeadOwnerAndRepo(ctx, baseRepo, compareReq)
|
||||
switch {
|
||||
case errors.Is(err, util.ErrInvalidArgument):
|
||||
@@ -1107,6 +1098,10 @@ func parseCompareInfo(ctx *context.APIContext, compareParam string) (result *git
|
||||
ctx.APIErrorInternal(err)
|
||||
return nil, nil
|
||||
}
|
||||
if !ctx.TokenCanAccessRepo(headRepo) {
|
||||
ctx.APIErrorNotFound()
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
isSameRepo := baseRepo.ID == headRepo.ID
|
||||
|
||||
@@ -1154,10 +1149,18 @@ func parseCompareInfo(ctx *context.APIContext, compareParam string) (result *git
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
baseRef := ctx.Repo.GitRepo.UnstableGuessRefByShortName(util.IfZero(compareReq.BaseOriRef, baseRepo.GetPullRequestTargetBranch(ctx)))
|
||||
headRef := headGitRepo.UnstableGuessRefByShortName(util.IfZero(compareReq.HeadOriRef, headRepo.DefaultBranch))
|
||||
baseRef, err := common.ResolveRefWithSuffix(ctx.Repo.GitRepo, util.IfZero(compareReq.BaseOriRef, baseRepo.GetPullRequestTargetBranch(ctx)), compareReq.BaseOriRefSuffix)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil, nil
|
||||
}
|
||||
headRef, err := common.ResolveRefWithSuffix(headGitRepo, util.IfZero(compareReq.HeadOriRef, headRepo.DefaultBranch), compareReq.HeadOriRefSuffix)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
log.Trace("Repo path: %q, base ref: %q->%q, head ref: %q->%q", ctx.Repo.Repository.RelativePath(), compareReq.BaseOriRef, baseRef, compareReq.HeadOriRef, headRef)
|
||||
log.Trace("Repo path: %q, base ref: %q->%q, head ref: %q->%q", ctx.Repo.Repository.RelativePath(), compareReq.BaseOriRef+compareReq.BaseOriRefSuffix, baseRef, compareReq.HeadOriRef+compareReq.HeadOriRefSuffix, headRef)
|
||||
|
||||
baseRefValid := baseRef.IsBranch() || baseRef.IsTag() || git.IsStringLikelyCommitID(git.ObjectFormatFromName(ctx.Repo.Repository.ObjectFormatName), baseRef.ShortName())
|
||||
headRefValid := headRef.IsBranch() || headRef.IsTag() || git.IsStringLikelyCommitID(git.ObjectFormatFromName(headRepo.ObjectFormatName), headRef.ShortName())
|
||||
@@ -1173,7 +1176,7 @@ func parseCompareInfo(ctx *context.APIContext, compareParam string) (result *git
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return compareInfo, closer
|
||||
return &compareInfo, closer
|
||||
}
|
||||
|
||||
// UpdatePullRequest merge PR's baseBranch into headBranch
|
||||
@@ -1228,7 +1231,7 @@ func UpdatePullRequest(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
if pr.HasMerged {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "pull request is already merged")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1238,7 +1241,7 @@ func UpdatePullRequest(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
if pr.Issue.IsClosed {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "pull request is already closed")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1251,15 +1254,24 @@ func UpdatePullRequest(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
rebase := ctx.FormString("style") == "rebase"
|
||||
// a public-only token must not update (push into) a private head repo,
|
||||
// even when the base repo named in the route is public
|
||||
if !ctx.TokenCanAccessRepo(pr.HeadRepo) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
allowedUpdateByMerge, allowedUpdateByRebase, err := pull_service.IsUserAllowedToUpdate(ctx, pr, ctx.Doer)
|
||||
// keep API back-compat: when no style is given, default to "merge" rather than the repo's DefaultUpdateStyle,
|
||||
// so existing API clients keep getting a merge update.
|
||||
rebase := repo_model.UpdateStyle(ctx.FormString("style", string(repo_model.UpdateStyleMerge))) == repo_model.UpdateStyleRebase
|
||||
|
||||
userUpdateStyles, err := pull_service.CheckUserAllowedToUpdate(ctx, pr, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if (!allowedUpdateByMerge && !rebase) || (rebase && !allowedUpdateByRebase) {
|
||||
if (rebase && !userUpdateStyles.RebaseAllowed) || (!rebase && !userUpdateStyles.MergeAllowed) {
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
@@ -1267,7 +1279,7 @@ func UpdatePullRequest(ctx *context.APIContext) {
|
||||
// default merge commit message
|
||||
message := fmt.Sprintf("Merge branch '%s' into %s", pr.BaseBranch, pr.HeadBranch)
|
||||
|
||||
if err = pull_service.Update(graceful.GetManager().ShutdownContext(), pr, ctx.Doer, message, rebase); err != nil {
|
||||
if err = pull_service.Update(pr, ctx.Doer, message, rebase); err != nil {
|
||||
if pull_service.IsErrMergeConflicts(err) {
|
||||
ctx.APIError(http.StatusConflict, "merge failed because of conflict")
|
||||
return
|
||||
@@ -1417,7 +1429,6 @@ func GetPullRequestCommits(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
var compareInfo *git_service.CompareInfo
|
||||
baseGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.BaseRepo)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1425,13 +1436,14 @@ func GetPullRequestCommits(ctx *context.APIContext) {
|
||||
}
|
||||
defer closer.Close()
|
||||
|
||||
var compareInfo git_service.CompareInfo
|
||||
if pr.HasMerged {
|
||||
compareInfo, err = git_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, git.RefName(pr.MergeBase), git.RefName(pr.GetGitHeadRefName()), false, false)
|
||||
} else {
|
||||
compareInfo, err = git_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, git.RefNameFromBranch(pr.BaseBranch), git.RefName(pr.GetGitHeadRefName()), false, false)
|
||||
}
|
||||
|
||||
if gitcmd.StderrHasPrefix(err, "fatal: bad revision") {
|
||||
if gitcmd.IsStderr(err, gitcmd.StderrBadRevision) {
|
||||
ctx.APIError(http.StatusNotFound, "invalid base branch or revision")
|
||||
return
|
||||
} else if err != nil {
|
||||
@@ -1550,7 +1562,7 @@ func GetPullRequestFiles(ctx *context.APIContext) {
|
||||
|
||||
baseGitRepo := ctx.Repo.GitRepo
|
||||
|
||||
var compareInfo *git_service.CompareInfo
|
||||
var compareInfo git_service.CompareInfo
|
||||
if pr.HasMerged {
|
||||
compareInfo, err = git_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, git.RefName(pr.MergeBase), git.RefName(pr.GetGitHeadRefName()), false, false)
|
||||
} else {
|
||||
@@ -1567,7 +1579,7 @@ func GetPullRequestFiles(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
startCommitID := compareInfo.MergeBase
|
||||
startCommitID := compareInfo.CompareBase
|
||||
endCommitID := headCommitID
|
||||
|
||||
maxLines := setting.Git.MaxGitDiffLines
|
||||
|
||||
@@ -9,18 +9,18 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
"gitea.dev/models/organization"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/gitrepo"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
issue_service "gitea.dev/services/issue"
|
||||
pull_service "gitea.dev/services/pull"
|
||||
)
|
||||
|
||||
// ListPullReviews lists all reviews of a pull request
|
||||
@@ -63,11 +63,7 @@ func ListPullReviews(ctx *context.APIContext) {
|
||||
|
||||
pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrPullRequestNotExist(err) {
|
||||
ctx.APIErrorNotFound("GetPullRequestByIndex", err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -208,6 +204,88 @@ func GetPullReviewComments(ctx *context.APIContext) {
|
||||
ctx.JSON(http.StatusOK, apiComments)
|
||||
}
|
||||
|
||||
// CreatePullReviewCommentReply replies to a pull request review comment.
|
||||
// The URL mirrors GitHub's endpoint, {index} is verified against the parent comment's pull request.
|
||||
func CreatePullReviewCommentReply(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/comments/{id}/replies repository repoCreatePullReviewCommentReply
|
||||
// ---
|
||||
// summary: Reply to a pull request review comment
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: index
|
||||
// in: path
|
||||
// description: index of the pull request
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: id of the review comment to reply to
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// required: true
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreatePullReviewCommentReplyOptions"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/PullReviewComment"
|
||||
// "400":
|
||||
// "$ref": "#/responses/validationError"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
opts := web.GetForm(ctx).(*api.CreatePullReviewCommentReplyOptions)
|
||||
|
||||
parent := getPullReviewCommentToResolve(ctx)
|
||||
if parent == nil {
|
||||
return
|
||||
}
|
||||
if parent.Issue.Index != ctx.PathParamInt64("index") {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
if parent.ReviewID == 0 {
|
||||
ctx.APIError(http.StatusBadRequest, "comment is not a review comment")
|
||||
return
|
||||
}
|
||||
|
||||
comment, err := pull_service.CreateCodeComment(ctx,
|
||||
ctx.Doer, ctx.Repo.GitRepo, parent.Issue,
|
||||
parent.Line, opts.Body, parent.TreePath,
|
||||
false, parent.ReviewID,
|
||||
"", nil,
|
||||
)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if err := comment.LoadPoster(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
comment.Issue = parent.Issue
|
||||
|
||||
ctx.JSON(http.StatusCreated, convert.ToPullReviewComment(ctx, comment, ctx.Doer))
|
||||
}
|
||||
|
||||
// ResolvePullReviewComment resolves a review comment in a pull request
|
||||
func ResolvePullReviewComment(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/pulls/comments/{id}/resolve repository repoResolvePullReviewComment
|
||||
@@ -307,11 +385,7 @@ func updatePullReviewCommentResolve(ctx *context.APIContext, isResolve bool) {
|
||||
func getPullReviewCommentToResolve(ctx *context.APIContext) *issues_model.Comment {
|
||||
comment, err := issues_model.GetCommentWithRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrCommentNotExist(err) {
|
||||
ctx.APIErrorNotFound("GetCommentByID", err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -376,7 +450,7 @@ func DeletePullReview(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
if !ctx.Doer.IsAdmin && ctx.Doer.ID != review.ReviewerID {
|
||||
ctx.APIError(http.StatusForbidden, nil)
|
||||
ctx.APIError(http.StatusForbidden, "no permission to delete comment")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -392,7 +466,7 @@ func DeletePullReview(ctx *context.APIContext) {
|
||||
func CreatePullReview(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/reviews repository repoCreatePullReview
|
||||
// ---
|
||||
// summary: Create a review to an pull request
|
||||
// summary: Create a review to a pull request
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
@@ -428,11 +502,7 @@ func CreatePullReview(ctx *context.APIContext) {
|
||||
opts := web.GetForm(ctx).(*api.CreatePullReviewOptions)
|
||||
pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrPullRequestNotExist(err) {
|
||||
ctx.APIErrorNotFound("GetPullRequestByIndex", err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -493,7 +563,7 @@ func CreatePullReview(ctx *context.APIContext) {
|
||||
review, _, err := pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, opts.CommitID, nil)
|
||||
if err != nil {
|
||||
if errors.Is(err, pull_service.ErrSubmitReviewOnClosedPR) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -509,11 +579,11 @@ func CreatePullReview(ctx *context.APIContext) {
|
||||
ctx.JSON(http.StatusOK, apiReview)
|
||||
}
|
||||
|
||||
// SubmitPullReview submit a pending review to an pull request
|
||||
// SubmitPullReview submit a pending review to a pull request
|
||||
func SubmitPullReview(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/reviews/{id} repository repoSubmitPullReview
|
||||
// ---
|
||||
// summary: Submit a pending review to an pull request
|
||||
// summary: Submit a pending review to a pull request
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
@@ -559,7 +629,7 @@ func SubmitPullReview(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
if review.Type != issues_model.ReviewTypePending {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, errors.New("only a pending review can be submitted"))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "only a pending review can be submitted")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -571,7 +641,7 @@ func SubmitPullReview(ctx *context.APIContext) {
|
||||
|
||||
// if review stay pending return
|
||||
if reviewType == issues_model.ReviewTypePending {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, errors.New("review stay pending"))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "review stay pending")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -585,7 +655,7 @@ func SubmitPullReview(ctx *context.APIContext) {
|
||||
review, _, err = pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, headCommitID, nil)
|
||||
if err != nil {
|
||||
if errors.Is(err, pull_service.ErrSubmitReviewOnClosedPR) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -616,7 +686,7 @@ func preparePullReviewType(ctx *context.APIContext, pr *issues_model.PullRequest
|
||||
case api.ReviewStateApproved:
|
||||
// can not approve your own PR
|
||||
if pr.Issue.IsPoster(ctx.Doer.ID) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, errors.New("approve your own pull is not allowed"))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "approve your own pull is not allowed")
|
||||
return -1, true
|
||||
}
|
||||
reviewType = issues_model.ReviewTypeApprove
|
||||
@@ -625,7 +695,7 @@ func preparePullReviewType(ctx *context.APIContext, pr *issues_model.PullRequest
|
||||
case api.ReviewStateRequestChanges:
|
||||
// can not reject your own PR
|
||||
if pr.Issue.IsPoster(ctx.Doer.ID) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, errors.New("reject your own pull is not allowed"))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "reject your own pull is not allowed")
|
||||
return -1, true
|
||||
}
|
||||
reviewType = issues_model.ReviewTypeReject
|
||||
@@ -635,7 +705,7 @@ func preparePullReviewType(ctx *context.APIContext, pr *issues_model.PullRequest
|
||||
needsBody = false
|
||||
// if there is no body we need to ensure that there are comments
|
||||
if !hasBody && !hasComments {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("review event %s requires a body or a comment", event))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("review event %s requires a body or a comment", event))
|
||||
return -1, true
|
||||
}
|
||||
default:
|
||||
@@ -644,7 +714,7 @@ func preparePullReviewType(ctx *context.APIContext, pr *issues_model.PullRequest
|
||||
|
||||
// reject reviews with empty body if a body is required for this call
|
||||
if needsBody && !hasBody {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("review event %s requires a body", event))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("review event %s requires a body", event))
|
||||
return -1, true
|
||||
}
|
||||
|
||||
@@ -655,33 +725,25 @@ func preparePullReviewType(ctx *context.APIContext, pr *issues_model.PullRequest
|
||||
func prepareSingleReview(ctx *context.APIContext) (*issues_model.Review, *issues_model.PullRequest, bool) {
|
||||
pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrPullRequestNotExist(err) {
|
||||
ctx.APIErrorNotFound("GetPullRequestByIndex", err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil, nil, true
|
||||
}
|
||||
|
||||
review, err := issues_model.GetReviewByID(ctx, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrReviewNotExist(err) {
|
||||
ctx.APIErrorNotFound("GetReviewByID", err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil, nil, true
|
||||
}
|
||||
|
||||
// validate the review is for the given PR
|
||||
if review.IssueID != pr.IssueID {
|
||||
ctx.APIErrorNotFound("ReviewNotInPR")
|
||||
ctx.APIErrorNotFound()
|
||||
return nil, nil, true
|
||||
}
|
||||
|
||||
// make sure that the user has access to this review if it is pending
|
||||
if review.Type == issues_model.ReviewTypePending && review.ReviewerID != ctx.Doer.ID && !ctx.Doer.IsAdmin {
|
||||
ctx.APIErrorNotFound("GetReviewByID")
|
||||
ctx.APIErrorNotFound()
|
||||
return nil, nil, true
|
||||
}
|
||||
|
||||
@@ -693,7 +755,7 @@ func prepareSingleReview(ctx *context.APIContext) (*issues_model.Review, *issues
|
||||
return review, pr, false
|
||||
}
|
||||
|
||||
// CreateReviewRequests create review requests to an pull request
|
||||
// CreateReviewRequests create review requests to a pull request
|
||||
func CreateReviewRequests(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/requested_reviewers repository repoCreatePullReviewRequests
|
||||
// ---
|
||||
@@ -734,7 +796,7 @@ func CreateReviewRequests(ctx *context.APIContext) {
|
||||
apiReviewRequest(ctx, *opts, true)
|
||||
}
|
||||
|
||||
// DeleteReviewRequests delete review requests to an pull request
|
||||
// DeleteReviewRequests delete review requests to a pull request
|
||||
func DeleteReviewRequests(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /repos/{owner}/{repo}/pulls/{index}/requested_reviewers repository repoDeletePullReviewRequests
|
||||
// ---
|
||||
@@ -788,7 +850,7 @@ func parseReviewersByNames(ctx *context.APIContext, reviewerNames, teamReviewerN
|
||||
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIErrorNotFound("UserNotExist", fmt.Sprintf("User '%s' not exist", r))
|
||||
ctx.APIErrorNotFound("user doesn't exist: " + r)
|
||||
return nil, nil
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -804,7 +866,7 @@ func parseReviewersByNames(ctx *context.APIContext, reviewerNames, teamReviewerN
|
||||
teamReviewer, err = organization.GetTeam(ctx, ctx.Repo.Owner.ID, t)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIErrorNotFound("TeamNotExist", fmt.Sprintf("Team '%s' not exist", t))
|
||||
ctx.APIErrorNotFound("team doesn't exist: " + t)
|
||||
return nil, nil
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -820,11 +882,7 @@ func parseReviewersByNames(ctx *context.APIContext, reviewerNames, teamReviewerN
|
||||
func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions, isAdd bool) {
|
||||
pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrPullRequestNotExist(err) {
|
||||
ctx.APIErrorNotFound("GetPullRequestByIndex", err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -853,11 +911,11 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions
|
||||
comment, err := issue_service.ReviewRequest(ctx, pr.Issue, ctx.Doer, &permDoer, reviewer, isAdd)
|
||||
if err != nil {
|
||||
if issues_model.IsErrReviewRequestOnClosedPR(err) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
return
|
||||
}
|
||||
if issues_model.IsErrNotValidReviewRequest(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -878,11 +936,11 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions
|
||||
comment, err := issue_service.TeamReviewRequest(ctx, pr.Issue, ctx.Doer, teamReviewer, isAdd)
|
||||
if err != nil {
|
||||
if issues_model.IsErrReviewRequestOnClosedPR(err) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
return
|
||||
}
|
||||
if issues_model.IsErrNotValidReviewRequest(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1003,7 +1061,7 @@ func UnDismissPullReview(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
func dismissReview(ctx *context.APIContext, msg string, isDismiss, dismissPriors bool) {
|
||||
if !ctx.Repo.IsAdmin() {
|
||||
if !ctx.Repo.Permission.IsAdmin() {
|
||||
ctx.APIError(http.StatusForbidden, "Must be repo admin")
|
||||
return
|
||||
}
|
||||
@@ -1020,7 +1078,7 @@ func dismissReview(ctx *context.APIContext, msg string, isDismiss, dismissPriors
|
||||
_, err := pull_service.DismissReview(ctx, review.ID, ctx.Repo.Repository.ID, msg, ctx.Doer, isDismiss, dismissPriors)
|
||||
if err != nil {
|
||||
if pull_service.IsErrDismissRequestOnClosedPR(err) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
|
||||
@@ -4,25 +4,23 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
release_service "code.gitea.io/gitea/services/release"
|
||||
auth_model "gitea.dev/models/auth"
|
||||
"gitea.dev/models/db"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unit"
|
||||
"gitea.dev/modules/git"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
release_service "gitea.dev/services/release"
|
||||
)
|
||||
|
||||
func canAccessReleaseDraft(ctx *context.APIContext) bool {
|
||||
if !ctx.IsSigned || !ctx.Repo.CanWrite(unit.TypeReleases) {
|
||||
if !ctx.IsSigned || !ctx.Repo.Permission.CanWrite(unit.TypeReleases) {
|
||||
return false
|
||||
}
|
||||
if ctx.Data["IsApiToken"] != true {
|
||||
@@ -243,7 +241,7 @@ func CreateRelease(ctx *context.APIContext) {
|
||||
|
||||
form := web.GetForm(ctx).(*api.CreateReleaseOption)
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, errors.New("repo is empty"))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "repo is empty")
|
||||
return
|
||||
}
|
||||
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, form.TagName)
|
||||
@@ -273,11 +271,11 @@ func CreateRelease(ctx *context.APIContext) {
|
||||
// It doesn't need to be the same as the "release note"
|
||||
if err := release_service.CreateRelease(ctx.Repo.GitRepo, rel, nil, form.TagMessage); err != nil {
|
||||
if repo_model.IsErrReleaseAlreadyExist(err) {
|
||||
ctx.APIError(http.StatusConflict, err)
|
||||
ctx.APIError(http.StatusConflict, err.Error())
|
||||
} else if release_service.IsErrProtectedTagName(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else if git.IsErrNotExist(err) {
|
||||
ctx.APIError(http.StatusNotFound, fmt.Errorf("target \"%v\" not found: %w", rel.Target, err))
|
||||
ctx.APIError(http.StatusNotFound, "target not found")
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
@@ -8,16 +8,16 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
attachment_service "code.gitea.io/gitea/services/attachment"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/context/upload"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
attachment_service "gitea.dev/services/attachment"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/context/upload"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
func checkReleaseMatchRepo(ctx *context.APIContext, releaseID int64) bool {
|
||||
@@ -205,7 +205,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
|
||||
|
||||
// Check if attachments are enabled
|
||||
if !setting.Attachment.Enabled {
|
||||
ctx.APIErrorNotFound("Attachment is not enabled")
|
||||
ctx.APIErrorNotFound("attachment is not enabled")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -250,12 +250,12 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
|
||||
})
|
||||
if err != nil {
|
||||
if upload.IsErrFileTypeForbidden(err) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if errors.Is(err, util.ErrContentTooLarge) {
|
||||
ctx.APIError(http.StatusRequestEntityTooLarge, err)
|
||||
ctx.APIError(http.StatusRequestEntityTooLarge, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -340,7 +340,7 @@ func EditReleaseAttachment(ctx *context.APIContext) {
|
||||
|
||||
if err := attachment_service.UpdateAttachment(ctx, setting.Repository.Release.AllowedTypes, attach); err != nil {
|
||||
if upload.IsErrFileTypeForbidden(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
|
||||
@@ -6,11 +6,11 @@ package repo
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
release_service "code.gitea.io/gitea/services/release"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
unit_model "gitea.dev/models/unit"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
release_service "gitea.dev/services/release"
|
||||
)
|
||||
|
||||
// GetReleaseByTag get a single release of a repository by tag name
|
||||
@@ -60,7 +60,7 @@ func GetReleaseByTag(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
if release.IsDraft { // only the users with write access can see draft releases
|
||||
if !ctx.IsSigned || !ctx.Repo.CanWrite(unit_model.TypeReleases) {
|
||||
if !ctx.IsSigned || !ctx.Repo.Permission.CanWrite(unit_model.TypeReleases) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -13,31 +13,34 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/label"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
feed_service "code.gitea.io/gitea/services/feed"
|
||||
"code.gitea.io/gitea/services/issue"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
activities_model "gitea.dev/models/activities"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/organization"
|
||||
"gitea.dev/models/perm"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
unit_model "gitea.dev/models/unit"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/gitrepo"
|
||||
"gitea.dev/modules/label"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/optional"
|
||||
repo_module "gitea.dev/modules/repository"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/validation"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
actions_service "gitea.dev/services/actions"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
feed_service "gitea.dev/services/feed"
|
||||
"gitea.dev/services/issue"
|
||||
"gitea.dev/services/migrations"
|
||||
mirror_service "gitea.dev/services/mirror"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
)
|
||||
|
||||
// Search repositories via options
|
||||
@@ -170,7 +173,7 @@ func Search(ctx *context.APIContext) {
|
||||
opts.Collaborate = optional.Some(true)
|
||||
case "":
|
||||
default:
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid search mode: \"%s\"", mode))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "invalid search mode")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -182,24 +185,11 @@ func Search(ctx *context.APIContext) {
|
||||
opts.IsPrivate = optional.Some(ctx.FormBool("is_private"))
|
||||
}
|
||||
|
||||
sortMode := ctx.FormString("sort")
|
||||
if len(sortMode) > 0 {
|
||||
sortOrder := ctx.FormString("order")
|
||||
if len(sortOrder) == 0 {
|
||||
sortOrder = "asc"
|
||||
}
|
||||
if searchModeMap, ok := repo_model.OrderByMap[sortOrder]; ok {
|
||||
if orderBy, ok := searchModeMap[sortMode]; ok {
|
||||
opts.OrderBy = orderBy
|
||||
} else {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort mode: \"%s\"", sortMode))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort order: \"%s\"", sortOrder))
|
||||
return
|
||||
}
|
||||
orderBy, ok := utils.ResolveSortOrder(ctx, repo_model.OrderByMap, "")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
opts.OrderBy = orderBy
|
||||
|
||||
repos, count, err := repo_model.SearchRepository(ctx, opts)
|
||||
if err != nil {
|
||||
@@ -244,7 +234,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
|
||||
|
||||
// If the readme template does not exist, a 400 will be returned.
|
||||
if opt.AutoInit && len(opt.Readme) > 0 && !slices.Contains(repo_module.Readmes, opt.Readme) {
|
||||
ctx.APIError(http.StatusBadRequest, fmt.Errorf("readme template does not exist, available templates: %v", repo_module.Readmes))
|
||||
ctx.APIError(http.StatusBadRequest, fmt.Sprintf("readme template does not exist, available templates: %v", repo_module.Readmes))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -260,7 +250,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
|
||||
DefaultBranch: opt.DefaultBranch,
|
||||
TrustModel: repo_model.ToTrustModel(opt.TrustModel),
|
||||
IsTemplate: opt.Template,
|
||||
ObjectFormatName: opt.ObjectFormatName,
|
||||
ObjectFormatName: string(opt.ObjectFormatName),
|
||||
})
|
||||
if err != nil {
|
||||
if repo_model.IsErrRepoAlreadyExist(err) {
|
||||
@@ -268,9 +258,9 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
|
||||
} else if db.IsErrNameReserved(err) ||
|
||||
db.IsErrNamePatternNotAllowed(err) ||
|
||||
label.IsErrTemplateLoad(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else if errors.Is(err, util.ErrPermissionDenied) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -423,7 +413,7 @@ func Generate(ctx *context.APIContext) {
|
||||
ctx.APIError(http.StatusConflict, "The repository with the same name already exists.")
|
||||
} else if db.IsErrNameReserved(err) ||
|
||||
db.IsErrNamePatternNotAllowed(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -630,7 +620,11 @@ func Edit(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
if opts.MirrorInterval != nil || opts.EnablePrune != nil {
|
||||
if opts.MirrorInterval != nil ||
|
||||
opts.EnablePrune != nil ||
|
||||
opts.MirrorUsername != nil ||
|
||||
opts.MirrorPassword != nil ||
|
||||
opts.MirrorToken != nil {
|
||||
if err := updateMirror(ctx, opts); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -658,13 +652,13 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
|
||||
if err := repo_service.ChangeRepositoryName(ctx, ctx.Doer, repo, newRepoName); err != nil {
|
||||
switch {
|
||||
case repo_model.IsErrRepoAlreadyExist(err):
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
case db.IsErrNameReserved(err):
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
case db.IsErrNamePatternNotAllowed(err):
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
default:
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("ChangeRepositoryName: %w", err))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("ChangeRepositoryName: %v", err))
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -698,7 +692,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
|
||||
// when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public
|
||||
if visibilityChanged && setting.Repository.ForcePrivate && !*opts.Private && !ctx.Doer.IsAdmin {
|
||||
err := errors.New("cannot change private repository to public")
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -760,14 +754,14 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
|
||||
if opts.HasIssues != nil {
|
||||
if *opts.HasIssues && opts.ExternalTracker != nil && !unit_model.TypeExternalTracker.UnitGlobalDisabled() {
|
||||
// Check that values are valid
|
||||
if !validation.IsValidExternalURL(opts.ExternalTracker.ExternalTrackerURL) {
|
||||
if !validation.IsValidURL(opts.ExternalTracker.ExternalTrackerURL) {
|
||||
err := errors.New("External tracker URL not valid")
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return err
|
||||
}
|
||||
if len(opts.ExternalTracker.ExternalTrackerFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(opts.ExternalTracker.ExternalTrackerFormat) {
|
||||
err := errors.New("External tracker URL format not valid")
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -822,7 +816,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
|
||||
if opts.HasWiki != nil {
|
||||
if *opts.HasWiki && opts.ExternalWiki != nil && !unit_model.TypeExternalWiki.UnitGlobalDisabled() {
|
||||
// Check that values are valid
|
||||
if !validation.IsValidExternalURL(opts.ExternalWiki.ExternalWikiURL) {
|
||||
if !validation.IsValidURL(opts.ExternalWiki.ExternalWikiURL) {
|
||||
err := errors.New("External wiki URL not valid")
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "Invalid external wiki URL")
|
||||
return err
|
||||
@@ -893,10 +887,20 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
|
||||
optional.AssignPtrValue(changed, &config.AllowFastForwardOnly, opts.AllowFastForwardOnly)
|
||||
optional.AssignPtrValue(changed, &config.AllowManualMerge, opts.AllowManualMerge)
|
||||
optional.AssignPtrValue(changed, &config.AutodetectManualMerge, opts.AutodetectManualMerge)
|
||||
optional.AssignPtrValue(changed, &config.AllowMergeUpdate, opts.AllowMergeUpdate)
|
||||
optional.AssignPtrValue(changed, &config.AllowRebaseUpdate, opts.AllowRebaseUpdate)
|
||||
optional.AssignPtrValue(changed, &config.DefaultDeleteBranchAfterMerge, opts.DefaultDeleteBranchAfterMerge)
|
||||
optional.AssignPtrValue(changed, &config.DefaultAllowMaintainerEdit, opts.DefaultAllowMaintainerEdit)
|
||||
optional.AssignPtrString(changed, &config.DefaultMergeStyle, opts.DefaultMergeStyle)
|
||||
optional.AssignPtrString(changed, &config.DefaultUpdateStyle, opts.DefaultUpdateStyle)
|
||||
// only validate update-style fields when the caller is actually changing one of them,
|
||||
// so unrelated PATCH calls don't reject historical configs.
|
||||
if opts.AllowMergeUpdate != nil || opts.AllowRebaseUpdate != nil || opts.DefaultUpdateStyle != nil {
|
||||
if err := config.ValidateUpdateSettings(); err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
if *changed || mustInsertPullRequestUnit {
|
||||
units = append(units, repo_model.RepoUnit{
|
||||
RepoID: repo.ID,
|
||||
@@ -984,7 +988,7 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e
|
||||
if opts.Archived != nil {
|
||||
if repo.IsMirror {
|
||||
err := errors.New("repo is a mirror, cannot archive/un-archive")
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return err
|
||||
}
|
||||
if *opts.Archived {
|
||||
@@ -1038,14 +1042,14 @@ func updateMirror(ctx *context.APIContext, opts api.EditRepoOption) error {
|
||||
interval, err := time.ParseDuration(*opts.MirrorInterval)
|
||||
if err != nil {
|
||||
log.Error("Wrong format for MirrorInternal Sent: %s", err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure the provided duration is not too short
|
||||
if interval != 0 && interval < setting.Mirror.MinInterval {
|
||||
err := fmt.Errorf("invalid mirror interval: %s is below minimum interval: %s", interval, setting.Mirror.MinInterval)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1061,10 +1065,61 @@ func updateMirror(ctx *context.APIContext, opts api.EditRepoOption) error {
|
||||
log.Trace("Repository %s Mirror[%d] Set EnablePrune: %t", repo.FullName(), mirror.ID, mirror.EnablePrune)
|
||||
}
|
||||
|
||||
authUpdateRequested := opts.MirrorPassword != nil || opts.MirrorToken != nil || opts.MirrorUsername != nil
|
||||
if authUpdateRequested {
|
||||
remoteURL, err := gitrepo.GitRemoteGetURL(ctx, repo, mirror.GetRemoteName())
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
authUsername := ""
|
||||
if opts.MirrorUsername != nil {
|
||||
authUsername = *opts.MirrorUsername
|
||||
} else if remoteURL.User != nil {
|
||||
authUsername = remoteURL.User.Username()
|
||||
}
|
||||
|
||||
authPassword := ""
|
||||
authToken := ""
|
||||
if opts.MirrorPassword != nil {
|
||||
authPassword = *opts.MirrorPassword
|
||||
}
|
||||
if opts.MirrorToken != nil {
|
||||
authToken = *opts.MirrorToken
|
||||
}
|
||||
|
||||
if opts.MirrorPassword == nil && opts.MirrorToken == nil && remoteURL.User != nil && (authUsername == "" || authUsername == remoteURL.User.Username()) {
|
||||
authPassword, _ = remoteURL.User.Password()
|
||||
}
|
||||
|
||||
if authToken != "" {
|
||||
authPassword = authToken
|
||||
}
|
||||
|
||||
composedAddress, err := git.ParseRemoteAddr(repo.OriginalURL, authUsername, authPassword)
|
||||
if err == nil {
|
||||
err = migrations.IsMigrateURLAllowed(composedAddress, ctx.Doer)
|
||||
}
|
||||
if err != nil {
|
||||
handleRemoteAddrError(ctx, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mirror_service.UpdateAddress(ctx, mirror, composedAddress); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if sanitized, err := util.SanitizeURL(repo.OriginalURL); err == nil {
|
||||
mirror.RemoteAddress = sanitized
|
||||
}
|
||||
}
|
||||
|
||||
// finally update the mirror in the DB
|
||||
if err := repo_model.UpdateMirror(ctx, mirror); err != nil {
|
||||
log.Error("Failed to Set Mirror Interval: %s", err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/contexttest"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unittest"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/contexttest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -6,11 +6,11 @@ package repo
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// ListStargazers list a repository's stargazers
|
||||
|
||||
@@ -7,14 +7,14 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus"
|
||||
"gitea.dev/models/db"
|
||||
git_model "gitea.dev/models/git"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
commitstatus_service "gitea.dev/services/repository/commitstatus"
|
||||
)
|
||||
|
||||
// NewCommitStatus creates a new CommitStatus
|
||||
@@ -55,7 +55,7 @@ func NewCommitStatus(ctx *context.APIContext) {
|
||||
form := web.GetForm(ctx).(*api.CreateStatusOption)
|
||||
sha := ctx.PathParam("sha")
|
||||
if len(sha) == 0 {
|
||||
ctx.APIError(http.StatusBadRequest, nil)
|
||||
ctx.APIError(http.StatusBadRequest, "sha not provided")
|
||||
return
|
||||
}
|
||||
status := &git_model.CommitStatus{
|
||||
|
||||
@@ -6,11 +6,11 @@ package repo
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// ListSubscribers list a repo's subscribers (i.e. watchers)
|
||||
|
||||
@@ -9,16 +9,16 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
release_service "code.gitea.io/gitea/services/release"
|
||||
git_model "gitea.dev/models/git"
|
||||
"gitea.dev/models/organization"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
release_service "gitea.dev/services/release"
|
||||
)
|
||||
|
||||
// ListTags list all the tags of a repository
|
||||
@@ -109,13 +109,13 @@ func GetAnnotatedTag(ctx *context.APIContext) {
|
||||
|
||||
tag, err := ctx.Repo.GitRepo.GetAnnotatedTag(sha)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
commit, err := ctx.Repo.GitRepo.GetTagCommit(tag.Name)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, convert.ToAnnotatedTag(ctx, ctx.Repo.Repository, tag, commit))
|
||||
@@ -203,13 +203,13 @@ func CreateTag(ctx *context.APIContext) {
|
||||
|
||||
commit, err := ctx.Repo.GitRepo.GetCommit(form.Target)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusNotFound, fmt.Errorf("target not found: %w", err))
|
||||
ctx.APIError(http.StatusNotFound, fmt.Sprintf("target not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, commit.ID.String(), form.TagName, form.Message); err != nil {
|
||||
if release_service.IsErrTagAlreadyExists(err) {
|
||||
ctx.APIError(http.StatusConflict, err)
|
||||
ctx.APIError(http.StatusConflict, err.Error())
|
||||
return
|
||||
}
|
||||
if release_service.IsErrProtectedTagName(err) {
|
||||
@@ -278,7 +278,7 @@ func DeleteTag(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
if !tag.IsTag {
|
||||
ctx.APIError(http.StatusConflict, errors.New("a tag attached to a release cannot be deleted directly"))
|
||||
ctx.APIError(http.StatusConflict, "a tag attached to a release cannot be deleted directly")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -438,7 +438,7 @@ func CreateTagProtection(ctx *context.APIContext) {
|
||||
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.WhitelistUsernames, false)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -449,7 +449,7 @@ func CreateTagProtection(ctx *context.APIContext) {
|
||||
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.WhitelistTeams, false)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -546,7 +546,7 @@ func EditTagProtection(ctx *context.APIContext) {
|
||||
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.WhitelistTeams, false)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -560,7 +560,7 @@ func EditTagProtection(ctx *context.APIContext) {
|
||||
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.WhitelistUsernames, false)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
"gitea.dev/models/organization"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
)
|
||||
|
||||
// ListTeams list a repository's teams
|
||||
@@ -137,6 +137,8 @@ func AddTeam(ctx *context.APIContext) {
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
// "405":
|
||||
@@ -173,6 +175,8 @@ func DeleteTeam(ctx *context.APIContext) {
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
// "405":
|
||||
@@ -186,9 +190,9 @@ func DeleteTeam(ctx *context.APIContext) {
|
||||
func changeRepoTeam(ctx *context.APIContext, add bool) {
|
||||
if !ctx.Repo.Owner.IsOrganization() {
|
||||
ctx.APIError(http.StatusMethodNotAllowed, "repo is not owned by an organization")
|
||||
return
|
||||
}
|
||||
if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() {
|
||||
ctx.APIError(http.StatusForbidden, "user is nor repo admin nor owner")
|
||||
if !canChangeRepoTeam(ctx) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -201,13 +205,13 @@ func changeRepoTeam(ctx *context.APIContext, add bool) {
|
||||
var err error
|
||||
if add {
|
||||
if repoHasTeam {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("team '%s' is already added to repo", team.Name))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("team '%s' is already added to repo", team.Name))
|
||||
return
|
||||
}
|
||||
err = repo_service.TeamAddRepository(ctx, team, ctx.Repo.Repository)
|
||||
} else {
|
||||
if !repoHasTeam {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("team '%s' was not added to repo", team.Name))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("team '%s' was not added to repo", team.Name))
|
||||
return
|
||||
}
|
||||
err = repo_service.RemoveRepositoryFromTeam(ctx, team, ctx.Repo.Repository.ID)
|
||||
@@ -220,11 +224,24 @@ func changeRepoTeam(ctx *context.APIContext, add bool) {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func canChangeRepoTeam(ctx *context.APIContext) bool {
|
||||
canChange, err := organization.OrgFromUser(ctx.Repo.Owner).CanChangeRepoTeamAccess(ctx, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return false
|
||||
}
|
||||
if !canChange {
|
||||
ctx.APIError(http.StatusForbidden, "Must be an organization owner")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getTeamByParam(ctx *context.APIContext) *organization.Team {
|
||||
team, err := organization.GetTeam(ctx, ctx.Repo.Owner.ID, ctx.PathParam("team"))
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
return nil
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
|
||||
@@ -7,14 +7,14 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"gitea.dev/models/db"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/modules/log"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// ListTopics returns list of current topics for repo
|
||||
@@ -300,7 +300,7 @@ func TopicSearch(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"topics": topicResponses,
|
||||
ctx.JSON(http.StatusOK, api.TopicListResponse{
|
||||
Topics: topicResponses,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,18 +8,18 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
"gitea.dev/models/organization"
|
||||
"gitea.dev/models/perm"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/log"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
)
|
||||
|
||||
// Transfer transfers the ownership of a repository
|
||||
@@ -87,12 +87,12 @@ func Transfer(ctx *context.APIContext) {
|
||||
for _, tID := range *opts.TeamIDs {
|
||||
team, err := organization.GetTeamByID(ctx, tID)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("team %d not found", tID))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("team %d not found", tID))
|
||||
return
|
||||
}
|
||||
|
||||
if team.OrgID != org.ID {
|
||||
ctx.APIError(http.StatusForbidden, fmt.Errorf("team %d belongs not to org %d", tID, org.ID))
|
||||
ctx.APIError(http.StatusForbidden, fmt.Sprintf("team %d belongs not to org %d", tID, org.ID))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -110,13 +110,13 @@ func Transfer(ctx *context.APIContext) {
|
||||
if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, ctx.Repo.Repository, teams); err != nil {
|
||||
switch {
|
||||
case repo_model.IsErrRepoTransferInProgress(err):
|
||||
ctx.APIError(http.StatusConflict, err)
|
||||
ctx.APIError(http.StatusConflict, err.Error())
|
||||
case repo_model.IsErrRepoAlreadyExist(err):
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
case repo_service.IsRepositoryLimitReached(err):
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
case errors.Is(err, user_model.ErrBlockedUser):
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
default:
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -163,11 +163,11 @@ func AcceptTransfer(ctx *context.APIContext) {
|
||||
if err != nil {
|
||||
switch {
|
||||
case repo_model.IsErrNoPendingTransfer(err):
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
case errors.Is(err, util.ErrPermissionDenied):
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
case repo_service.IsRepositoryLimitReached(err):
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
default:
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -207,9 +207,9 @@ func RejectTransfer(ctx *context.APIContext) {
|
||||
if err != nil {
|
||||
switch {
|
||||
case repo_model.IsErrNoPendingTransfer(err):
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
case errors.Is(err, util.ErrPermissionDenied):
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
default:
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ package repo
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/services/context"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
"gitea.dev/services/context"
|
||||
files_service "gitea.dev/services/repository/files"
|
||||
)
|
||||
|
||||
// GetTree get the tree of a repository.
|
||||
|
||||
@@ -9,17 +9,17 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
wiki_service "code.gitea.io/gitea/services/wiki"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/gitrepo"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
notify_service "gitea.dev/services/notify"
|
||||
wiki_service "gitea.dev/services/wiki"
|
||||
)
|
||||
|
||||
// NewWikiPage response for wiki create request
|
||||
@@ -59,7 +59,7 @@ func NewWikiPage(ctx *context.APIContext) {
|
||||
form := web.GetForm(ctx).(*api.CreateWikiPageOptions)
|
||||
|
||||
if util.IsEmptyString(form.Title) {
|
||||
ctx.APIError(http.StatusBadRequest, nil)
|
||||
ctx.APIError(http.StatusBadRequest, "title is required")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -71,16 +71,16 @@ func NewWikiPage(ctx *context.APIContext) {
|
||||
|
||||
content, err := base64.StdEncoding.DecodeString(form.ContentBase64)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
form.ContentBase64 = string(content)
|
||||
|
||||
if err := wiki_service.AddWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName, form.ContentBase64, form.Message); err != nil {
|
||||
if repo_model.IsErrWikiReservedName(err) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else if repo_model.IsErrWikiAlreadyExist(err) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -149,7 +149,7 @@ func EditWikiPage(ctx *context.APIContext) {
|
||||
|
||||
content, err := base64.StdEncoding.DecodeString(form.ContentBase64)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
form.ContentBase64 = string(content)
|
||||
@@ -245,11 +245,7 @@ func DeleteWikiPage(ctx *context.APIContext) {
|
||||
wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("pageName"))
|
||||
|
||||
if err := wiki_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName); err != nil {
|
||||
if err.Error() == "file does not exist" {
|
||||
ctx.APIErrorNotFound(err)
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -435,7 +431,7 @@ func ListPageRevisions(ctx *context.APIContext) {
|
||||
page := max(ctx.FormInt("page"), 1)
|
||||
|
||||
// get Commit Count
|
||||
commitsHistory, err := wikiRepo.CommitsByFileAndRange(
|
||||
commitsHistory, _, err := wikiRepo.CommitsByFileAndRange(
|
||||
git.CommitsByFileAndRangeOptions{
|
||||
Revision: ctx.Repo.Repository.DefaultWikiBranch,
|
||||
File: pageFilename,
|
||||
@@ -474,21 +470,13 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error)
|
||||
func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit) {
|
||||
wikiRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo())
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) || err.Error() == "no such file or directory" {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
commit, err := wikiRepo.GetBranchCommit(ctx.Repo.Repository.DefaultWikiBranch)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return wikiRepo, nil
|
||||
}
|
||||
return wikiRepo, commit
|
||||
|
||||
@@ -6,9 +6,9 @@ package settings
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// GetGeneralUISettings returns instance's global settings for ui
|
||||
|
||||
@@ -7,19 +7,35 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/webhook"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
actions_model "gitea.dev/models/actions"
|
||||
"gitea.dev/models/db"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/container"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/optional"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/webhook"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// actionsOwnerAccessibleRepoIDsSubQuery returns the sub-query restricting an owner-scoped actions
|
||||
// listing to the repos whose actions the caller can read, or nil when no restriction applies. A bare
|
||||
// org member must not be able to enumerate runs/jobs of repos they have no access to. A site admin may
|
||||
// skip the access filter, but a public-only token must stay confined to public repos even for an admin.
|
||||
func actionsOwnerAccessibleRepoIDsSubQuery(ctx *context.APIContext, ownerID int64) *builder.Builder {
|
||||
if ownerID > 0 && (ctx.Doer == nil || !ctx.Doer.IsAdmin || ctx.PublicOnly) {
|
||||
return repo_model.FindUserActionsAccessibleOwnerRepoIDsSubQuery(ownerID, ctx.Doer, ctx.PublicOnly)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListJobs lists jobs for api route validated ownerID and repoID
|
||||
// ownerID == 0 and repoID == 0 means all jobs
|
||||
// ownerID == 0 and repoID != 0 means all jobs for the given repo
|
||||
@@ -27,27 +43,38 @@ import (
|
||||
// ownerID != 0 and repoID != 0 undefined behavior
|
||||
// runID == 0 means all jobs
|
||||
// runID is used as an additional filter together with ownerID and repoID to only return jobs for the given run
|
||||
// runAttemptID, when set, additionally limits the result to jobs of the specified run attempt. Only takes effect when runID > 0.
|
||||
// Access rights are checked at the API route level
|
||||
func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64) {
|
||||
func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64, runAttemptID optional.Option[int64]) {
|
||||
if ownerID != 0 && repoID != 0 {
|
||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||
}
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
orderBy, ok := utils.ResolveSortOrder(ctx, actions_model.JobOrderByMap, actions_model.JobOrderByMap["asc"]["id"])
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
opts := actions_model.FindRunJobOptions{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
RunID: runID,
|
||||
ListOptions: listOptions,
|
||||
OrderBy: orderBy,
|
||||
}
|
||||
if runID > 0 {
|
||||
opts.RunAttemptID = runAttemptID
|
||||
}
|
||||
for _, status := range ctx.FormStrings("status") {
|
||||
values, err := convertToInternal(status)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, fmt.Errorf("Invalid status %s", status))
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
opts.Statuses = append(opts.Statuses, values...)
|
||||
}
|
||||
|
||||
opts.AccessibleRepoIDsSubQuery = actionsOwnerAccessibleRepoIDsSubQuery(ctx, opts.OwnerID)
|
||||
|
||||
jobs, total, err := db.FindAndCount[actions_model.ActionRunJob](ctx, opts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -57,6 +84,12 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64) {
|
||||
res := new(api.ActionWorkflowJobsResponse)
|
||||
res.TotalCount = total
|
||||
|
||||
jobList := actions_model.ActionJobList(jobs)
|
||||
if err := jobList.LoadAttributes(ctx, true); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
res.Entries = make([]*api.ActionWorkflowJob, len(jobs))
|
||||
|
||||
isRepoLevel := repoID != 0 && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == repoID
|
||||
@@ -65,11 +98,11 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64) {
|
||||
if isRepoLevel {
|
||||
repository = ctx.Repo.Repository
|
||||
} else {
|
||||
repository, err = repo_model.GetRepositoryByID(ctx, jobs[i].RepoID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
if jobs[i].Run == nil || jobs[i].Run.Repo == nil {
|
||||
ctx.APIErrorInternal(fmt.Errorf("job %d is missing its run or repository", jobs[i].ID))
|
||||
return
|
||||
}
|
||||
repository = jobs[i].Run.Repo
|
||||
}
|
||||
|
||||
convertedWorkflowJob, err := convert.ToActionWorkflowJob(ctx, repository, nil, jobs[i])
|
||||
@@ -108,7 +141,7 @@ func convertToInternal(s string) ([]actions_model.Status, error) {
|
||||
case "cancelled", "timed_out":
|
||||
return []actions_model.Status{actions_model.StatusCancelled}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid status %s", s)
|
||||
return nil, util.NewInvalidArgumentErrorf("invalid status %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,8 +150,9 @@ func convertToInternal(s string) ([]actions_model.Status, error) {
|
||||
// ownerID == 0 and repoID != 0 means all runs for the given repo
|
||||
// ownerID != 0 and repoID == 0 means all runs for the given user/org
|
||||
// ownerID != 0 and repoID != 0 undefined behavior
|
||||
// workflowID filters runs by workflow file name (e.g. "build.yml"), empty means no filter
|
||||
// Access rights are checked at the API route level
|
||||
func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
func ListRuns(ctx *context.APIContext, ownerID, repoID int64, workflowID string) {
|
||||
if ownerID != 0 && repoID != 0 {
|
||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||
}
|
||||
@@ -126,8 +160,14 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
opts := actions_model.FindRunOptions{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
WorkflowID: workflowID,
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
if workflowID != "" {
|
||||
workflowSourceRepoID := ctx.FormInt64("scoped_workflow_source_repo_id")
|
||||
opts.IsScopedRun = optional.Some(workflowSourceRepoID > 0)
|
||||
opts.WorkflowRepoID = workflowSourceRepoID
|
||||
}
|
||||
|
||||
if event := ctx.FormString("event"); event != "" {
|
||||
opts.TriggerEvent = webhook.HookEventType(event)
|
||||
@@ -138,7 +178,7 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
for _, status := range ctx.FormStrings("status") {
|
||||
values, err := convertToInternal(status)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, fmt.Errorf("Invalid status %s", status))
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
opts.Status = append(opts.Status, values...)
|
||||
@@ -154,6 +194,9 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
if headSHA := ctx.FormString("head_sha"); headSHA != "" {
|
||||
opts.CommitSHA = headSHA
|
||||
}
|
||||
excludePullRequests := ctx.FormBool("exclude_pull_requests")
|
||||
|
||||
opts.AccessibleRepoIDsSubQuery = actionsOwnerAccessibleRepoIDsSubQuery(ctx, opts.OwnerID)
|
||||
|
||||
runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts)
|
||||
if err != nil {
|
||||
@@ -164,21 +207,28 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
res := new(api.ActionWorkflowRunsResponse)
|
||||
res.TotalCount = total
|
||||
|
||||
res.Entries = make([]*api.ActionWorkflowRun, len(runs))
|
||||
isRepoLevel := repoID != 0 && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == repoID
|
||||
for i := range runs {
|
||||
var repository *repo_model.Repository
|
||||
if isRepoLevel {
|
||||
repository = ctx.Repo.Repository
|
||||
} else {
|
||||
repository, err = repo_model.GetRepositoryByID(ctx, runs[i].RepoID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
runList := actions_model.RunList(runs)
|
||||
if err := runList.LoadTriggerUser(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, repository, runs[i])
|
||||
if err := runList.LoadRepos(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
repos := repo_model.RepositoryList(container.FilterSlice(runs, func(r *actions_model.ActionRun) (*repo_model.Repository, bool) {
|
||||
return r.Repo, r.Repo != nil
|
||||
}))
|
||||
if err := repos.LoadOwners(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
res.Entries = make([]*api.ActionWorkflowRun, len(runs))
|
||||
for i := range runs {
|
||||
// TODO: load run attempts in batch
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, runs[i], nil, excludePullRequests)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
||||
@@ -7,12 +7,13 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
user_service "code.gitea.io/gitea/services/user"
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
user_service "gitea.dev/services/user"
|
||||
)
|
||||
|
||||
func ListBlocks(ctx *context.APIContext, blocker *user_model.User) {
|
||||
@@ -44,33 +45,30 @@ func ListBlocks(ctx *context.APIContext, blocker *user_model.User) {
|
||||
func CheckUserBlock(ctx *context.APIContext, blocker *user_model.User) {
|
||||
blockee, err := user_model.GetUserByName(ctx, ctx.PathParam("username"))
|
||||
if err != nil {
|
||||
ctx.APIErrorNotFound("GetUserByName", err)
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
status := http.StatusNotFound
|
||||
blocking, err := user_model.GetBlocking(ctx, blocker.ID, blockee.ID)
|
||||
if err != nil {
|
||||
_, err = user_model.GetBlocking(ctx, blocker.ID, blockee.ID)
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Status(http.StatusNotFound)
|
||||
} else if err == nil {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if blocking != nil {
|
||||
status = http.StatusNoContent
|
||||
}
|
||||
|
||||
ctx.Status(status)
|
||||
}
|
||||
|
||||
func BlockUser(ctx *context.APIContext, blocker *user_model.User) {
|
||||
blockee, err := user_model.GetUserByName(ctx, ctx.PathParam("username"))
|
||||
if err != nil {
|
||||
ctx.APIErrorNotFound("GetUserByName", err)
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := user_service.BlockUser(ctx, ctx.Doer, blocker, blockee, ctx.FormString("note")); err != nil {
|
||||
if errors.Is(err, user_model.ErrCanNotBlock) || errors.Is(err, user_model.ErrBlockOrganization) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -83,13 +81,13 @@ func BlockUser(ctx *context.APIContext, blocker *user_model.User) {
|
||||
func UnblockUser(ctx *context.APIContext, doer, blocker *user_model.User) {
|
||||
blockee, err := user_model.GetUserByName(ctx, ctx.PathParam("username"))
|
||||
if err != nil {
|
||||
ctx.APIErrorNotFound("GetUserByName", err)
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := user_service.UnblockUser(ctx, doer, blocker, blockee); err != nil {
|
||||
if errors.Is(err, user_model.ErrCanNotUnblock) || errors.Is(err, user_model.ErrBlockOrganization) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
@@ -7,15 +7,15 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
actions_model "gitea.dev/models/actions"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// RegistrationToken is response related to registration token
|
||||
@@ -77,11 +77,7 @@ func getRunnerByID(ctx *context.APIContext, ownerID, repoID, runnerID int64) (*a
|
||||
|
||||
runner, err := actions_model.GetRunnerByID(ctx, runnerID)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIErrorNotFound("Runner not found")
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
package swagger
|
||||
|
||||
import api "code.gitea.io/gitea/modules/structs"
|
||||
import api "gitea.dev/modules/structs"
|
||||
|
||||
// SecretList
|
||||
// swagger:response SecretList
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
api "gitea.dev/modules/structs"
|
||||
)
|
||||
|
||||
// ActivityFeedsList
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
api "gitea.dev/modules/structs"
|
||||
)
|
||||
|
||||
// OAuth2Application
|
||||
@@ -20,3 +20,10 @@ type swaggerResponseAccessToken struct {
|
||||
// in:body
|
||||
Body api.AccessToken `json:"body"`
|
||||
}
|
||||
|
||||
// CurrentAccessToken represents the currently authenticated access token.
|
||||
// swagger:response CurrentAccessToken
|
||||
type swaggerResponseCurrentAccessToken struct {
|
||||
// in:body
|
||||
Body api.CurrentAccessToken `json:"body"`
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
api "gitea.dev/modules/structs"
|
||||
)
|
||||
|
||||
// CronList
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
api "gitea.dev/modules/structs"
|
||||
)
|
||||
|
||||
// Issue
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
api "gitea.dev/modules/structs"
|
||||
)
|
||||
|
||||
// PublicKey
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
api "gitea.dev/modules/structs"
|
||||
)
|
||||
|
||||
// ServerVersion
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
api "gitea.dev/modules/structs"
|
||||
)
|
||||
|
||||
// NodeInfo
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
api "gitea.dev/modules/structs"
|
||||
)
|
||||
|
||||
// NotificationThread
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user