aboutsummaryrefslogtreecommitdiffstats
path: root/routers/api
diff options
context:
space:
mode:
Diffstat (limited to 'routers/api')
-rw-r--r--routers/api/actions/artifacts.go53
-rw-r--r--routers/api/actions/artifacts_chunks.go6
-rw-r--r--routers/api/actions/artifacts_utils.go10
-rw-r--r--routers/api/actions/artifactsv4.go78
-rw-r--r--routers/api/actions/ping/ping_test.go3
-rw-r--r--routers/api/actions/runner/runner.go23
-rw-r--r--routers/api/actions/runner/utils.go95
-rw-r--r--routers/api/packages/alpine/alpine.go4
-rw-r--r--routers/api/packages/api.go260
-rw-r--r--routers/api/packages/arch/arch.go2
-rw-r--r--routers/api/packages/cargo/cargo.go9
-rw-r--r--routers/api/packages/chef/auth.go7
-rw-r--r--routers/api/packages/chef/chef.go2
-rw-r--r--routers/api/packages/composer/api.go22
-rw-r--r--routers/api/packages/composer/composer.go7
-rw-r--r--routers/api/packages/conan/conan.go2
-rw-r--r--routers/api/packages/conda/conda.go28
-rw-r--r--routers/api/packages/container/auth.go2
-rw-r--r--routers/api/packages/container/blob.go39
-rw-r--r--routers/api/packages/container/container.go126
-rw-r--r--routers/api/packages/container/manifest.go331
-rw-r--r--routers/api/packages/cran/cran.go2
-rw-r--r--routers/api/packages/debian/debian.go6
-rw-r--r--routers/api/packages/generic/generic.go2
-rw-r--r--routers/api/packages/goproxy/goproxy.go2
-rw-r--r--routers/api/packages/helm/helm.go2
-rw-r--r--routers/api/packages/maven/api.go9
-rw-r--r--routers/api/packages/maven/maven.go18
-rw-r--r--routers/api/packages/npm/npm.go4
-rw-r--r--routers/api/packages/nuget/api_v2.go46
-rw-r--r--routers/api/packages/nuget/nuget.go8
-rw-r--r--routers/api/packages/pub/pub.go2
-rw-r--r--routers/api/packages/pypi/pypi.go2
-rw-r--r--routers/api/packages/rpm/rpm.go4
-rw-r--r--routers/api/packages/rubygems/rubygems.go50
-rw-r--r--routers/api/packages/rubygems/rubygems_test.go41
-rw-r--r--routers/api/packages/swift/swift.go38
-rw-r--r--routers/api/packages/vagrant/vagrant.go2
-rw-r--r--routers/api/v1/activitypub/person.go8
-rw-r--r--routers/api/v1/activitypub/reqsignature.go7
-rw-r--r--routers/api/v1/admin/action.go93
-rw-r--r--routers/api/v1/admin/adopt.go26
-rw-r--r--routers/api/v1/admin/cron.go2
-rw-r--r--routers/api/v1/admin/email.go2
-rw-r--r--routers/api/v1/admin/hooks.go19
-rw-r--r--routers/api/v1/admin/org.go10
-rw-r--r--routers/api/v1/admin/repo.go2
-rw-r--r--routers/api/v1/admin/runners.go78
-rw-r--r--routers/api/v1/admin/user.go86
-rw-r--r--routers/api/v1/admin/user_badge.go12
-rw-r--r--routers/api/v1/api.go344
-rw-r--r--routers/api/v1/misc/gitignore.go2
-rw-r--r--routers/api/v1/misc/label_templates.go2
-rw-r--r--routers/api/v1/misc/licenses.go3
-rw-r--r--routers/api/v1/misc/markup.go10
-rw-r--r--routers/api/v1/misc/markup_test.go10
-rw-r--r--routers/api/v1/misc/nodeinfo.go2
-rw-r--r--routers/api/v1/misc/signing.go79
-rw-r--r--routers/api/v1/notify/notifications.go4
-rw-r--r--routers/api/v1/notify/repo.go12
-rw-r--r--routers/api/v1/notify/threads.go12
-rw-r--r--routers/api/v1/notify/user.go12
-rw-r--r--routers/api/v1/org/action.go278
-rw-r--r--routers/api/v1/org/avatar.go6
-rw-r--r--routers/api/v1/org/block.go6
-rw-r--r--routers/api/v1/org/hook.go2
-rw-r--r--routers/api/v1/org/label.go22
-rw-r--r--routers/api/v1/org/member.go57
-rw-r--r--routers/api/v1/org/org.go80
-rw-r--r--routers/api/v1/org/team.go112
-rw-r--r--routers/api/v1/packages/package.go291
-rw-r--r--routers/api/v1/repo/action.go1182
-rw-r--r--routers/api/v1/repo/actions_run.go64
-rw-r--r--routers/api/v1/repo/avatar.go6
-rw-r--r--routers/api/v1/repo/blob.go6
-rw-r--r--routers/api/v1/repo/branch.go270
-rw-r--r--routers/api/v1/repo/collaborators.go54
-rw-r--r--routers/api/v1/repo/commits.go81
-rw-r--r--routers/api/v1/repo/compare.go4
-rw-r--r--routers/api/v1/repo/download.go9
-rw-r--r--routers/api/v1/repo/file.go600
-rw-r--r--routers/api/v1/repo/fork.go34
-rw-r--r--routers/api/v1/repo/git_hook.go18
-rw-r--r--routers/api/v1/repo/git_ref.go5
-rw-r--r--routers/api/v1/repo/hook.go12
-rw-r--r--routers/api/v1/repo/hook_test.go2
-rw-r--r--routers/api/v1/repo/issue.go143
-rw-r--r--routers/api/v1/repo/issue_attachment.go28
-rw-r--r--routers/api/v1/repo/issue_comment.go90
-rw-r--r--routers/api/v1/repo/issue_comment_attachment.go38
-rw-r--r--routers/api/v1/repo/issue_dependency.go58
-rw-r--r--routers/api/v1/repo/issue_label.go54
-rw-r--r--routers/api/v1/repo/issue_lock.go152
-rw-r--r--routers/api/v1/repo/issue_pin.go46
-rw-r--r--routers/api/v1/repo/issue_reaction.go54
-rw-r--r--routers/api/v1/repo/issue_stopwatch.go72
-rw-r--r--routers/api/v1/repo/issue_subscription.go34
-rw-r--r--routers/api/v1/repo/issue_tracked_time.go94
-rw-r--r--routers/api/v1/repo/key.go32
-rw-r--r--routers/api/v1/repo/label.go22
-rw-r--r--routers/api/v1/repo/language.go2
-rw-r--r--routers/api/v1/repo/license.go2
-rw-r--r--routers/api/v1/repo/migrate.go60
-rw-r--r--routers/api/v1/repo/milestone.go14
-rw-r--r--routers/api/v1/repo/mirror.go57
-rw-r--r--routers/api/v1/repo/notes.go16
-rw-r--r--routers/api/v1/repo/patch.go68
-rw-r--r--routers/api/v1/repo/pull.go314
-rw-r--r--routers/api/v1/repo/pull_review.go124
-rw-r--r--routers/api/v1/repo/release.go57
-rw-r--r--routers/api/v1/repo/release_attachment.go48
-rw-r--r--routers/api/v1/repo/release_tags.go18
-rw-r--r--routers/api/v1/repo/repo.go128
-rw-r--r--routers/api/v1/repo/repo_test.go4
-rw-r--r--routers/api/v1/repo/star.go4
-rw-r--r--routers/api/v1/repo/status.go35
-rw-r--r--routers/api/v1/repo/subscriber.go2
-rw-r--r--routers/api/v1/repo/tag.go84
-rw-r--r--routers/api/v1/repo/teams.go26
-rw-r--r--routers/api/v1/repo/topic.go14
-rw-r--r--routers/api/v1/repo/transfer.go94
-rw-r--r--routers/api/v1/repo/tree.go4
-rw-r--r--routers/api/v1/repo/wiki.go64
-rw-r--r--routers/api/v1/settings/settings.go1
-rw-r--r--routers/api/v1/shared/action.go187
-rw-r--r--routers/api/v1/shared/block.go20
-rw-r--r--routers/api/v1/shared/runners.go97
-rw-r--r--routers/api/v1/swagger/action.go14
-rw-r--r--routers/api/v1/swagger/options.go12
-rw-r--r--routers/api/v1/swagger/repo.go62
-rw-r--r--routers/api/v1/user/action.go163
-rw-r--r--routers/api/v1/user/app.go54
-rw-r--r--routers/api/v1/user/avatar.go6
-rw-r--r--routers/api/v1/user/block.go6
-rw-r--r--routers/api/v1/user/email.go16
-rw-r--r--routers/api/v1/user/follower.go26
-rw-r--r--routers/api/v1/user/gpg_key.go44
-rw-r--r--routers/api/v1/user/helper.go6
-rw-r--r--routers/api/v1/user/hook.go4
-rw-r--r--routers/api/v1/user/key.go31
-rw-r--r--routers/api/v1/user/repo.go18
-rw-r--r--routers/api/v1/user/runners.go78
-rw-r--r--routers/api/v1/user/settings.go2
-rw-r--r--routers/api/v1/user/star.go22
-rw-r--r--routers/api/v1/user/user.go14
-rw-r--r--routers/api/v1/user/watch.go14
-rw-r--r--routers/api/v1/utils/git.go104
-rw-r--r--routers/api/v1/utils/hook.go159
-rw-r--r--routers/api/v1/utils/hook_test.go82
-rw-r--r--routers/api/v1/utils/main_test.go21
150 files changed, 5703 insertions, 3101 deletions
diff --git a/routers/api/actions/artifacts.go b/routers/api/actions/artifacts.go
index 910edd6d58..6473659e5c 100644
--- a/routers/api/actions/artifacts.go
+++ b/routers/api/actions/artifacts.go
@@ -135,7 +135,7 @@ func ArtifactContexter() func(next http.Handler) http.Handler {
// we should verify the ACTIONS_RUNTIME_TOKEN
authHeader := req.Header.Get("Authorization")
if len(authHeader) == 0 || !strings.HasPrefix(authHeader, "Bearer ") {
- ctx.Error(http.StatusUnauthorized, "Bad authorization header")
+ ctx.HTTPError(http.StatusUnauthorized, "Bad authorization header")
return
}
@@ -147,12 +147,12 @@ func ArtifactContexter() func(next http.Handler) http.Handler {
task, err = actions.GetTaskByID(req.Context(), tID)
if err != nil {
log.Error("Error runner api getting task by ID: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error runner api getting task by ID")
+ ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting task by ID")
return
}
if task.Status != actions.StatusRunning {
log.Error("Error runner api getting task: task is not running")
- ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running")
+ ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting task: task is not running")
return
}
} else {
@@ -162,14 +162,14 @@ func ArtifactContexter() func(next http.Handler) http.Handler {
task, err = actions.GetRunningTaskByToken(req.Context(), authToken)
if err != nil {
log.Error("Error runner api getting task: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error runner api getting task")
+ ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting task")
return
}
}
if err := task.LoadJob(req.Context()); err != nil {
log.Error("Error runner api getting job: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error runner api getting job")
+ ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting job")
return
}
@@ -211,7 +211,7 @@ func (ar artifactRoutes) getUploadArtifactURL(ctx *ArtifactContext) {
var req getUploadArtifactRequest
if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil {
log.Error("Error decode request body: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error decode request body")
+ ctx.HTTPError(http.StatusInternalServerError, "Error decode request body")
return
}
@@ -250,7 +250,7 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) {
expiredDays, err = strconv.ParseInt(queryRetentionDays, 10, 64)
if err != nil {
log.Error("Error parse retention days: %v", err)
- ctx.Error(http.StatusBadRequest, "Error parse retention days")
+ ctx.HTTPError(http.StatusBadRequest, "Error parse retention days")
return
}
}
@@ -261,7 +261,7 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) {
artifact, err := actions.CreateArtifact(ctx, task, artifactName, artifactPath, expiredDays)
if err != nil {
log.Error("Error create or get artifact: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error create or get artifact")
+ ctx.HTTPError(http.StatusInternalServerError, "Error create or get artifact")
return
}
@@ -271,7 +271,7 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) {
chunksTotalSize, err := saveUploadChunk(ar.fs, ctx, artifact, contentLength, runID)
if err != nil {
log.Error("Error save upload chunk: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error save upload chunk")
+ ctx.HTTPError(http.StatusInternalServerError, "Error save upload chunk")
return
}
@@ -285,7 +285,7 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) {
artifact.ContentEncoding = ctx.Req.Header.Get("Content-Encoding")
if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
log.Error("Error update artifact: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error update artifact")
+ ctx.HTTPError(http.StatusInternalServerError, "Error update artifact")
return
}
log.Debug("[artifact] update artifact size, artifact_id: %d, size: %d, compressed size: %d",
@@ -307,12 +307,12 @@ func (ar artifactRoutes) comfirmUploadArtifact(ctx *ArtifactContext) {
artifactName := ctx.Req.URL.Query().Get("artifactName")
if artifactName == "" {
log.Error("Error artifact name is empty")
- ctx.Error(http.StatusBadRequest, "Error artifact name is empty")
+ ctx.HTTPError(http.StatusBadRequest, "Error artifact name is empty")
return
}
if err := mergeChunksForRun(ctx, ar.fs, runID, artifactName); err != nil {
log.Error("Error merge chunks: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error merge chunks")
+ ctx.HTTPError(http.StatusInternalServerError, "Error merge chunks")
return
}
ctx.JSON(http.StatusOK, map[string]string{
@@ -337,15 +337,18 @@ func (ar artifactRoutes) listArtifacts(ctx *ArtifactContext) {
return
}
- artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{RunID: runID})
+ artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{
+ RunID: runID,
+ Status: int(actions.ArtifactStatusUploadConfirmed),
+ })
if err != nil {
log.Error("Error getting artifacts: %v", err)
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.HTTPError(http.StatusInternalServerError, err.Error())
return
}
if len(artifacts) == 0 {
log.Debug("[artifact] handleListArtifacts, no artifacts")
- ctx.Error(http.StatusNotFound)
+ ctx.HTTPError(http.StatusNotFound)
return
}
@@ -402,21 +405,22 @@ func (ar artifactRoutes) getDownloadArtifactURL(ctx *ArtifactContext) {
artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{
RunID: runID,
ArtifactName: itemPath,
+ Status: int(actions.ArtifactStatusUploadConfirmed),
})
if err != nil {
log.Error("Error getting artifacts: %v", err)
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.HTTPError(http.StatusInternalServerError, err.Error())
return
}
if len(artifacts) == 0 {
log.Debug("[artifact] getDownloadArtifactURL, no artifacts")
- ctx.Error(http.StatusNotFound)
+ ctx.HTTPError(http.StatusNotFound)
return
}
if itemPath != artifacts[0].ArtifactName {
log.Error("Error dismatch artifact name, itemPath: %v, artifact: %v", itemPath, artifacts[0].ArtifactName)
- ctx.Error(http.StatusBadRequest, "Error dismatch artifact name")
+ ctx.HTTPError(http.StatusBadRequest, "Error dismatch artifact name")
return
}
@@ -460,24 +464,29 @@ func (ar artifactRoutes) downloadArtifact(ctx *ArtifactContext) {
artifact, exist, err := db.GetByID[actions.ActionArtifact](ctx, artifactID)
if err != nil {
log.Error("Error getting artifact: %v", err)
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.HTTPError(http.StatusInternalServerError, err.Error())
return
}
if !exist {
log.Error("artifact with ID %d does not exist", artifactID)
- ctx.Error(http.StatusNotFound, fmt.Sprintf("artifact with ID %d does not exist", artifactID))
+ ctx.HTTPError(http.StatusNotFound, fmt.Sprintf("artifact with ID %d does not exist", artifactID))
return
}
if artifact.RunID != runID {
log.Error("Error mismatch runID and artifactID, task: %v, artifact: %v", runID, artifactID)
- ctx.Error(http.StatusBadRequest)
+ ctx.HTTPError(http.StatusBadRequest)
+ return
+ }
+ if artifact.Status != actions.ArtifactStatusUploadConfirmed {
+ log.Error("Error artifact not found: %s", artifact.Status.ToString())
+ ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
return
}
fd, err := ar.fs.Open(artifact.StoragePath)
if err != nil {
log.Error("Error opening file: %v", err)
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.HTTPError(http.StatusInternalServerError, err.Error())
return
}
defer fd.Close()
diff --git a/routers/api/actions/artifacts_chunks.go b/routers/api/actions/artifacts_chunks.go
index cf48da12aa..708931d1ac 100644
--- a/routers/api/actions/artifacts_chunks.go
+++ b/routers/api/actions/artifacts_chunks.go
@@ -51,7 +51,7 @@ func saveUploadChunkBase(st storage.ObjectStorage, ctx *ArtifactContext,
log.Info("[artifact] check chunk md5, sum: %s, header: %s", chunkMd5String, reqMd5String)
// if md5 not match, delete the chunk
if reqMd5String != chunkMd5String {
- checkErr = fmt.Errorf("md5 not match")
+ checkErr = errors.New("md5 not match")
}
}
if writtenSize != contentSize {
@@ -261,7 +261,7 @@ func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st st
return fmt.Errorf("save merged file error: %v", err)
}
if written != artifact.FileCompressedSize {
- return fmt.Errorf("merged file size is not equal to chunk length")
+ return errors.New("merged file size is not equal to chunk length")
}
defer func() {
@@ -292,7 +292,7 @@ func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st st
}
artifact.StoragePath = storagePath
- artifact.Status = int64(actions.ArtifactStatusUploadConfirmed)
+ artifact.Status = actions.ArtifactStatusUploadConfirmed
if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
return fmt.Errorf("update artifact error: %v", err)
}
diff --git a/routers/api/actions/artifacts_utils.go b/routers/api/actions/artifacts_utils.go
index a4ca5797dc..35868c290e 100644
--- a/routers/api/actions/artifacts_utils.go
+++ b/routers/api/actions/artifacts_utils.go
@@ -26,7 +26,7 @@ var invalidArtifactNameChars = strings.Join([]string{"\\", "/", "\"", ":", "<",
func validateArtifactName(ctx *ArtifactContext, artifactName string) bool {
if strings.ContainsAny(artifactName, invalidArtifactNameChars) {
log.Error("Error checking artifact name contains invalid character")
- ctx.Error(http.StatusBadRequest, "Error checking artifact name contains invalid character")
+ ctx.HTTPError(http.StatusBadRequest, "Error checking artifact name contains invalid character")
return false
}
return true
@@ -37,18 +37,18 @@ func validateRunID(ctx *ArtifactContext) (*actions.ActionTask, int64, bool) {
runID := ctx.PathParamInt64("run_id")
if task.Job.RunID != runID {
log.Error("Error runID not match")
- ctx.Error(http.StatusBadRequest, "run-id does not match")
+ ctx.HTTPError(http.StatusBadRequest, "run-id does not match")
return nil, 0, false
}
return task, runID, true
}
-func validateRunIDV4(ctx *ArtifactContext, rawRunID string) (*actions.ActionTask, int64, bool) { //nolint:unparam
+func validateRunIDV4(ctx *ArtifactContext, rawRunID string) (*actions.ActionTask, int64, bool) { //nolint:unparam // ActionTask is never used
task := ctx.ActionTask
runID, err := strconv.ParseInt(rawRunID, 10, 64)
if err != nil || task.Job.RunID != runID {
log.Error("Error runID not match")
- ctx.Error(http.StatusBadRequest, "run-id does not match")
+ ctx.HTTPError(http.StatusBadRequest, "run-id does not match")
return nil, 0, false
}
return task, runID, true
@@ -62,7 +62,7 @@ func validateArtifactHash(ctx *ArtifactContext, artifactName string) bool {
return true
}
log.Error("Invalid artifact hash: %s", paramHash)
- ctx.Error(http.StatusBadRequest, "Invalid artifact hash")
+ ctx.HTTPError(http.StatusBadRequest, "Invalid artifact hash")
return false
}
diff --git a/routers/api/actions/artifactsv4.go b/routers/api/actions/artifactsv4.go
index 8917a7a8a2..e9e9fc6393 100644
--- a/routers/api/actions/artifactsv4.go
+++ b/routers/api/actions/artifactsv4.go
@@ -25,7 +25,7 @@ package actions
// 1.3. Continue Upload Zip Content to Blobstorage (unauthenticated request), repeat until everything is uploaded
// PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=appendBlock
// 1.4. BlockList xml payload to Blobstorage (unauthenticated request)
-// Files of about 800MB are parallel in parallel and / or out of order, this file is needed to enshure the correct order
+// Files of about 800MB are parallel in parallel and / or out of order, this file is needed to ensure the correct order
// PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=blockList
// Request
// <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
@@ -162,15 +162,15 @@ func (r artifactV4Routes) buildSignature(endp, expires, artifactName string, tas
mac.Write([]byte(endp))
mac.Write([]byte(expires))
mac.Write([]byte(artifactName))
- mac.Write([]byte(fmt.Sprint(taskID)))
- mac.Write([]byte(fmt.Sprint(artifactID)))
+ fmt.Fprint(mac, taskID)
+ fmt.Fprint(mac, artifactID)
return mac.Sum(nil)
}
func (r artifactV4Routes) buildArtifactURL(ctx *ArtifactContext, endp, artifactName string, taskID, artifactID int64) string {
expires := time.Now().Add(60 * time.Minute).Format("2006-01-02 15:04:05.999999999 -0700 MST")
uploadURL := strings.TrimSuffix(httplib.GuessCurrentAppURL(ctx), "/") + strings.TrimSuffix(r.prefix, "/") +
- "/" + endp + "?sig=" + base64.URLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID, artifactID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + fmt.Sprint(taskID) + "&artifactID=" + fmt.Sprint(artifactID)
+ "/" + endp + "?sig=" + base64.URLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID, artifactID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + strconv.FormatInt(taskID, 10) + "&artifactID=" + strconv.FormatInt(artifactID, 10)
return uploadURL
}
@@ -187,29 +187,29 @@ func (r artifactV4Routes) verifySignature(ctx *ArtifactContext, endp string) (*a
expecedsig := r.buildSignature(endp, expires, artifactName, taskID, artifactID)
if !hmac.Equal(dsig, expecedsig) {
log.Error("Error unauthorized")
- ctx.Error(http.StatusUnauthorized, "Error unauthorized")
+ ctx.HTTPError(http.StatusUnauthorized, "Error unauthorized")
return nil, "", false
}
t, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", expires)
if err != nil || t.Before(time.Now()) {
log.Error("Error link expired")
- ctx.Error(http.StatusUnauthorized, "Error link expired")
+ ctx.HTTPError(http.StatusUnauthorized, "Error link expired")
return nil, "", false
}
task, err := actions.GetTaskByID(ctx, taskID)
if err != nil {
log.Error("Error runner api getting task by ID: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error runner api getting task by ID")
+ ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting task by ID")
return nil, "", false
}
if task.Status != actions.StatusRunning {
log.Error("Error runner api getting task: task is not running")
- ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running")
+ ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting task: task is not running")
return nil, "", false
}
if err := task.LoadJob(ctx); err != nil {
log.Error("Error runner api getting job: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error runner api getting job")
+ ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting job")
return nil, "", false
}
return task, artifactName, true
@@ -230,13 +230,13 @@ func (r *artifactV4Routes) parseProtbufBody(ctx *ArtifactContext, req protorefle
body, err := io.ReadAll(ctx.Req.Body)
if err != nil {
log.Error("Error decode request body: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error decode request body")
+ ctx.HTTPError(http.StatusInternalServerError, "Error decode request body")
return false
}
err = protojson.Unmarshal(body, req)
if err != nil {
log.Error("Error decode request body: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error decode request body")
+ ctx.HTTPError(http.StatusInternalServerError, "Error decode request body")
return false
}
return true
@@ -246,7 +246,7 @@ func (r *artifactV4Routes) sendProtbufBody(ctx *ArtifactContext, req protoreflec
resp, err := protojson.Marshal(req)
if err != nil {
log.Error("Error encode response body: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error encode response body")
+ ctx.HTTPError(http.StatusInternalServerError, "Error encode response body")
return
}
ctx.Resp.Header().Set("Content-Type", "application/json;charset=utf-8")
@@ -275,7 +275,7 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) {
artifact, err := actions.CreateArtifact(ctx, ctx.ActionTask, artifactName, artifactName+".zip", rententionDays)
if err != nil {
log.Error("Error create or get artifact: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error create or get artifact")
+ ctx.HTTPError(http.StatusInternalServerError, "Error create or get artifact")
return
}
artifact.ContentEncoding = ArtifactV4ContentEncoding
@@ -283,7 +283,7 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) {
artifact.FileCompressedSize = 0
if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
log.Error("Error UpdateArtifactByID: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID")
+ ctx.HTTPError(http.StatusInternalServerError, "Error UpdateArtifactByID")
return
}
@@ -309,28 +309,28 @@ func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) {
artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName)
if err != nil {
log.Error("Error artifact not found: %v", err)
- ctx.Error(http.StatusNotFound, "Error artifact not found")
+ ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
return
}
_, err = appendUploadChunk(r.fs, ctx, artifact, artifact.FileSize, ctx.Req.ContentLength, artifact.RunID)
if err != nil {
log.Error("Error runner api getting task: task is not running")
- ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running")
+ ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting task: task is not running")
return
}
artifact.FileCompressedSize += ctx.Req.ContentLength
artifact.FileSize += ctx.Req.ContentLength
if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
log.Error("Error UpdateArtifactByID: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID")
+ ctx.HTTPError(http.StatusInternalServerError, "Error UpdateArtifactByID")
return
}
} else {
_, err := r.fs.Save(fmt.Sprintf("tmpv4%d/block-%d-%d-%s", task.Job.RunID, task.Job.RunID, ctx.Req.ContentLength, base64.URLEncoding.EncodeToString([]byte(blockid))), ctx.Req.Body, -1)
if err != nil {
log.Error("Error runner api getting task: task is not running")
- ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running")
+ ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting task: task is not running")
return
}
}
@@ -341,7 +341,7 @@ func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) {
_, err := r.fs.Save(fmt.Sprintf("tmpv4%d/%d-%d-blocklist", task.Job.RunID, task.Job.RunID, artifactID), ctx.Req.Body, -1)
if err != nil {
log.Error("Error runner api getting task: task is not running")
- ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running")
+ ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting task: task is not running")
return
}
ctx.JSON(http.StatusCreated, "created")
@@ -389,7 +389,7 @@ func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) {
artifact, err := r.getArtifactByName(ctx, runID, req.Name)
if err != nil {
log.Error("Error artifact not found: %v", err)
- ctx.Error(http.StatusNotFound, "Error artifact not found")
+ ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
return
}
@@ -400,20 +400,20 @@ func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) {
chunkMap, err := listChunksByRunID(r.fs, runID)
if err != nil {
log.Error("Error merge chunks: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error merge chunks")
+ ctx.HTTPError(http.StatusInternalServerError, "Error merge chunks")
return
}
chunks, ok = chunkMap[artifact.ID]
if !ok {
log.Error("Error merge chunks")
- ctx.Error(http.StatusInternalServerError, "Error merge chunks")
+ ctx.HTTPError(http.StatusInternalServerError, "Error merge chunks")
return
}
} else {
chunks, err = listChunksByRunIDV4(r.fs, runID, artifact.ID, blockList)
if err != nil {
log.Error("Error merge chunks: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error merge chunks")
+ ctx.HTTPError(http.StatusInternalServerError, "Error merge chunks")
return
}
artifact.FileSize = chunks[len(chunks)-1].End + 1
@@ -426,7 +426,7 @@ func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) {
}
if err := mergeChunksForArtifact(ctx, chunks, r.fs, artifact, checksum); err != nil {
log.Error("Error merge chunks: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error merge chunks")
+ ctx.HTTPError(http.StatusInternalServerError, "Error merge chunks")
return
}
@@ -448,15 +448,13 @@ func (r *artifactV4Routes) listArtifacts(ctx *ArtifactContext) {
return
}
- artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{RunID: runID})
+ artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{
+ RunID: runID,
+ Status: int(actions.ArtifactStatusUploadConfirmed),
+ })
if err != nil {
log.Error("Error getting artifacts: %v", err)
- ctx.Error(http.StatusInternalServerError, err.Error())
- return
- }
- if len(artifacts) == 0 {
- log.Debug("[artifact] handleListArtifacts, no artifacts")
- ctx.Error(http.StatusNotFound)
+ ctx.HTTPError(http.StatusInternalServerError, err.Error())
return
}
@@ -507,7 +505,12 @@ func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) {
artifact, err := r.getArtifactByName(ctx, runID, artifactName)
if err != nil {
log.Error("Error artifact not found: %v", err)
- ctx.Error(http.StatusNotFound, "Error artifact not found")
+ ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
+ return
+ }
+ if artifact.Status != actions.ArtifactStatusUploadConfirmed {
+ log.Error("Error artifact not found: %s", artifact.Status.ToString())
+ ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
return
}
@@ -535,7 +538,12 @@ func (r *artifactV4Routes) downloadArtifact(ctx *ArtifactContext) {
artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName)
if err != nil {
log.Error("Error artifact not found: %v", err)
- ctx.Error(http.StatusNotFound, "Error artifact not found")
+ ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
+ return
+ }
+ if artifact.Status != actions.ArtifactStatusUploadConfirmed {
+ log.Error("Error artifact not found: %s", artifact.Status.ToString())
+ ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
return
}
@@ -559,14 +567,14 @@ func (r *artifactV4Routes) deleteArtifact(ctx *ArtifactContext) {
artifact, err := r.getArtifactByName(ctx, runID, req.Name)
if err != nil {
log.Error("Error artifact not found: %v", err)
- ctx.Error(http.StatusNotFound, "Error artifact not found")
+ ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
return
}
err = actions.SetArtifactNeedDelete(ctx, runID, req.Name)
if err != nil {
log.Error("Error deleting artifacts: %v", err)
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.HTTPError(http.StatusInternalServerError, err.Error())
return
}
diff --git a/routers/api/actions/ping/ping_test.go b/routers/api/actions/ping/ping_test.go
index 098b003ea2..98d2dcb820 100644
--- a/routers/api/actions/ping/ping_test.go
+++ b/routers/api/actions/ping/ping_test.go
@@ -4,7 +4,6 @@
package ping
import (
- "context"
"net/http"
"net/http/httptest"
"testing"
@@ -51,7 +50,7 @@ func MainServiceTest(t *testing.T, h http.Handler) {
clients := []pingv1connect.PingServiceClient{connectClient, grpcClient, grpcWebClient}
t.Run("ping request", func(t *testing.T) {
for _, client := range clients {
- result, err := client.Ping(context.Background(), connect.NewRequest(&pingv1.PingRequest{
+ result, err := client.Ping(t.Context(), connect.NewRequest(&pingv1.PingRequest{
Data: "foobar",
}))
require.NoError(t, err)
diff --git a/routers/api/actions/runner/runner.go b/routers/api/actions/runner/runner.go
index c55b30f7eb..ce8137592d 100644
--- a/routers/api/actions/runner/runner.go
+++ b/routers/api/actions/runner/runner.go
@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
actions_service "code.gitea.io/gitea/services/actions"
+ notify_service "code.gitea.io/gitea/services/notify"
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
"code.gitea.io/actions-proto-go/runner/v1/runnerv1connect"
@@ -77,6 +78,7 @@ func (s *Service) Register(
RepoID: runnerToken.RepoID,
Version: req.Msg.Version,
AgentLabels: labels,
+ Ephemeral: req.Msg.Ephemeral,
}
if err := runner.GenerateToken(); err != nil {
return nil, errors.New("can't generate token")
@@ -95,12 +97,13 @@ func (s *Service) Register(
res := connect.NewResponse(&runnerv1.RegisterResponse{
Runner: &runnerv1.Runner{
- Id: runner.ID,
- Uuid: runner.UUID,
- Token: runner.Token,
- Name: runner.Name,
- Version: runner.Version,
- Labels: runner.AgentLabels,
+ Id: runner.ID,
+ Uuid: runner.UUID,
+ Token: runner.Token,
+ Name: runner.Name,
+ Version: runner.Version,
+ Labels: runner.AgentLabels,
+ Ephemeral: runner.Ephemeral,
},
})
@@ -156,7 +159,7 @@ func (s *Service) FetchTask(
// if the task version in request is not equal to the version in db,
// it means there may still be some tasks not be assgined.
// try to pick a task for the runner that send the request.
- if t, ok, err := pickTask(ctx, runner); err != nil {
+ if t, ok, err := actions_service.PickTask(ctx, runner); err != nil {
log.Error("pick task failed: %v", err)
return nil, status.Errorf(codes.Internal, "pick task: %v", err)
} else if ok {
@@ -210,7 +213,7 @@ func (s *Service) UpdateTask(
if err := task.LoadJob(ctx); err != nil {
return nil, status.Errorf(codes.Internal, "load job: %v", err)
}
- if err := task.Job.LoadRun(ctx); err != nil {
+ if err := task.Job.LoadAttributes(ctx); err != nil {
return nil, status.Errorf(codes.Internal, "load run: %v", err)
}
@@ -219,6 +222,10 @@ func (s *Service) UpdateTask(
actions_service.CreateCommitStatus(ctx, task.Job)
}
+ if task.Status.IsDone() {
+ notify_service.WorkflowJobStatusUpdate(ctx, task.Job.Run.Repo, task.Job.Run.TriggerUser, task.Job, task)
+ }
+
if req.Msg.State.Result != runnerv1.Result_RESULT_UNSPECIFIED {
if err := actions_service.EmitJobsIfReady(task.Job.RunID); err != nil {
log.Error("Emit ready jobs of run %d: %v", task.Job.RunID, err)
diff --git a/routers/api/actions/runner/utils.go b/routers/api/actions/runner/utils.go
deleted file mode 100644
index 0fd7ca5c44..0000000000
--- a/routers/api/actions/runner/utils.go
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2022 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package runner
-
-import (
- "context"
- "fmt"
-
- actions_model "code.gitea.io/gitea/models/actions"
- secret_model "code.gitea.io/gitea/models/secret"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/services/actions"
-
- runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
- "google.golang.org/protobuf/types/known/structpb"
-)
-
-func pickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv1.Task, bool, error) {
- t, ok, err := actions_model.CreateTaskForRunner(ctx, runner)
- if err != nil {
- return nil, false, fmt.Errorf("CreateTaskForRunner: %w", err)
- }
- if !ok {
- return nil, false, nil
- }
-
- secrets, err := secret_model.GetSecretsOfTask(ctx, t)
- if err != nil {
- return nil, false, fmt.Errorf("GetSecretsOfTask: %w", err)
- }
-
- vars, err := actions_model.GetVariablesOfRun(ctx, t.Job.Run)
- if err != nil {
- return nil, false, fmt.Errorf("GetVariablesOfRun: %w", err)
- }
-
- actions.CreateCommitStatus(ctx, t.Job)
-
- task := &runnerv1.Task{
- Id: t.ID,
- WorkflowPayload: t.Job.WorkflowPayload,
- Context: generateTaskContext(t),
- Secrets: secrets,
- Vars: vars,
- }
-
- if needs, err := findTaskNeeds(ctx, t); err != nil {
- log.Error("Cannot find needs for task %v: %v", t.ID, err)
- // Go on with empty needs.
- // If return error, the task will be wild, which means the runner will never get it when it has been assigned to the runner.
- // In contrast, missing needs is less serious.
- // And the task will fail and the runner will report the error in the logs.
- } else {
- task.Needs = needs
- }
-
- return task, true, nil
-}
-
-func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct {
- giteaRuntimeToken, err := actions.CreateAuthorizationToken(t.ID, t.Job.RunID, t.JobID)
- if err != nil {
- log.Error("actions.CreateAuthorizationToken failed: %v", err)
- }
-
- gitCtx := actions.GenerateGiteaContext(t.Job.Run, t.Job)
- gitCtx["token"] = t.Token
- gitCtx["gitea_runtime_token"] = giteaRuntimeToken
-
- taskContext, err := structpb.NewStruct(gitCtx)
- if err != nil {
- log.Error("structpb.NewStruct failed: %v", err)
- }
-
- return taskContext
-}
-
-func findTaskNeeds(ctx context.Context, task *actions_model.ActionTask) (map[string]*runnerv1.TaskNeed, error) {
- if err := task.LoadAttributes(ctx); err != nil {
- return nil, fmt.Errorf("task LoadAttributes: %w", err)
- }
- taskNeeds, err := actions.FindTaskNeeds(ctx, task.Job)
- if err != nil {
- return nil, err
- }
- ret := make(map[string]*runnerv1.TaskNeed, len(taskNeeds))
- for jobID, taskNeed := range taskNeeds {
- ret[jobID] = &runnerv1.TaskNeed{
- Outputs: taskNeed.Outputs,
- Result: runnerv1.Result(taskNeed.Result),
- }
- }
- return ret, nil
-}
diff --git a/routers/api/packages/alpine/alpine.go b/routers/api/packages/alpine/alpine.go
index f35cff3df2..ba4a4f23ce 100644
--- a/routers/api/packages/alpine/alpine.go
+++ b/routers/api/packages/alpine/alpine.go
@@ -68,7 +68,7 @@ func GetRepositoryFile(ctx *context.Context) {
return
}
- s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion(
ctx,
pv,
&packages_service.PackageFileInfo{
@@ -216,7 +216,7 @@ func DownloadPackageFile(ctx *context.Context) {
}
}
- s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
+ s, u, pf, err := packages_service.OpenFileForDownload(ctx, pfs[0])
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go
index 41c3eb95e9..878e0f9945 100644
--- a/routers/api/packages/api.go
+++ b/routers/api/packages/api.go
@@ -5,8 +5,6 @@ package packages
import (
"net/http"
- "regexp"
- "strings"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/perm"
@@ -46,35 +44,36 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) {
if ok { // it's a personal access token but not oauth2 token
scopeMatched := false
var err error
- if accessMode == perm.AccessModeRead {
+ switch accessMode {
+ case perm.AccessModeRead:
scopeMatched, err = scope.HasScope(auth_model.AccessTokenScopeReadPackage)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "HasScope", err.Error())
+ ctx.HTTPError(http.StatusInternalServerError, "HasScope", err.Error())
return
}
- } else if accessMode == perm.AccessModeWrite {
+ case perm.AccessModeWrite:
scopeMatched, err = scope.HasScope(auth_model.AccessTokenScopeWritePackage)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "HasScope", err.Error())
+ ctx.HTTPError(http.StatusInternalServerError, "HasScope", err.Error())
return
}
}
if !scopeMatched {
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea Package API"`)
- ctx.Error(http.StatusUnauthorized, "reqPackageAccess", "user should have specific permission or be a site admin")
+ ctx.HTTPError(http.StatusUnauthorized, "reqPackageAccess", "user should have specific permission or be a site admin")
return
}
// check if scope only applies to public resources
publicOnly, err := scope.PublicOnly()
if err != nil {
- ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
+ ctx.HTTPError(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
return
}
if publicOnly {
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public packages")
+ ctx.HTTPError(http.StatusForbidden, "reqToken", "token scope is limited to public packages")
return
}
}
@@ -83,7 +82,7 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) {
if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() {
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea Package API"`)
- ctx.Error(http.StatusUnauthorized, "reqPackageAccess", "user should have specific permission or be a site admin")
+ ctx.HTTPError(http.StatusUnauthorized, "reqPackageAccess", "user should have specific permission or be a site admin")
return
}
}
@@ -100,7 +99,7 @@ func verifyAuth(r *web.Router, authMethods []auth.Method) {
ctx.Doer, err = authGroup.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
if err != nil {
log.Error("Failed to verify user: %v", err)
- ctx.Error(http.StatusUnauthorized, "authGroup.Verify")
+ ctx.HTTPError(http.StatusUnauthorized, "authGroup.Verify")
return
}
ctx.IsSigned = ctx.Doer != nil
@@ -281,42 +280,10 @@ func CommonRoutes() *web.Router {
})
})
}, reqPackageAccess(perm.AccessModeRead))
- r.Group("/conda", func() {
- var (
- downloadPattern = regexp.MustCompile(`\A(.+/)?(.+)/((?:[^/]+(?:\.tar\.bz2|\.conda))|(?:current_)?repodata\.json(?:\.bz2)?)\z`)
- uploadPattern = regexp.MustCompile(`\A(.+/)?([^/]+(?:\.tar\.bz2|\.conda))\z`)
- )
-
- r.Get("/*", func(ctx *context.Context) {
- m := downloadPattern.FindStringSubmatch(ctx.PathParam("*"))
- if len(m) == 0 {
- ctx.Status(http.StatusNotFound)
- return
- }
-
- ctx.SetPathParam("channel", strings.TrimSuffix(m[1], "/"))
- ctx.SetPathParam("architecture", m[2])
- ctx.SetPathParam("filename", m[3])
-
- switch m[3] {
- case "repodata.json", "repodata.json.bz2", "current_repodata.json", "current_repodata.json.bz2":
- conda.EnumeratePackages(ctx)
- default:
- conda.DownloadPackageFile(ctx)
- }
- })
- r.Put("/*", reqPackageAccess(perm.AccessModeWrite), func(ctx *context.Context) {
- m := uploadPattern.FindStringSubmatch(ctx.PathParam("*"))
- if len(m) == 0 {
- ctx.Status(http.StatusNotFound)
- return
- }
-
- ctx.SetPathParam("channel", strings.TrimSuffix(m[1], "/"))
- ctx.SetPathParam("filename", m[2])
-
- conda.UploadPackageFile(ctx)
- })
+ r.PathGroup("/conda/*", func(g *web.RouterPathGroup) {
+ g.MatchPath("GET", "/<architecture>/<filename>", conda.ListOrGetPackages)
+ g.MatchPath("GET", "/<channel:*>/<architecture>/<filename>", conda.ListOrGetPackages)
+ g.MatchPath("PUT", "/<channel:*>/<filename>", reqPackageAccess(perm.AccessModeWrite), conda.UploadPackageFile)
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/cran", func() {
r.Group("/src", func() {
@@ -357,60 +324,15 @@ func CommonRoutes() *web.Router {
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/go", func() {
r.Put("/upload", reqPackageAccess(perm.AccessModeWrite), goproxy.UploadPackage)
- r.Get("/sumdb/sum.golang.org/supported", func(ctx *context.Context) {
- ctx.Status(http.StatusNotFound)
- })
+ r.Get("/sumdb/sum.golang.org/supported", http.NotFound)
- // Manual mapping of routes because the package name contains slashes which chi does not support
// https://go.dev/ref/mod#goproxy-protocol
- r.Get("/*", func(ctx *context.Context) {
- path := ctx.PathParam("*")
-
- if strings.HasSuffix(path, "/@latest") {
- ctx.SetPathParam("name", path[:len(path)-len("/@latest")])
- ctx.SetPathParam("version", "latest")
-
- goproxy.PackageVersionMetadata(ctx)
- return
- }
-
- parts := strings.SplitN(path, "/@v/", 2)
- if len(parts) != 2 {
- ctx.Status(http.StatusNotFound)
- return
- }
-
- ctx.SetPathParam("name", parts[0])
-
- // <package/name>/@v/list
- if parts[1] == "list" {
- goproxy.EnumeratePackageVersions(ctx)
- return
- }
-
- // <package/name>/@v/<version>.zip
- if strings.HasSuffix(parts[1], ".zip") {
- ctx.SetPathParam("version", parts[1][:len(parts[1])-len(".zip")])
-
- goproxy.DownloadPackageFile(ctx)
- return
- }
- // <package/name>/@v/<version>.info
- if strings.HasSuffix(parts[1], ".info") {
- ctx.SetPathParam("version", parts[1][:len(parts[1])-len(".info")])
-
- goproxy.PackageVersionMetadata(ctx)
- return
- }
- // <package/name>/@v/<version>.mod
- if strings.HasSuffix(parts[1], ".mod") {
- ctx.SetPathParam("version", parts[1][:len(parts[1])-len(".mod")])
-
- goproxy.PackageVersionGoModContent(ctx)
- return
- }
-
- ctx.Status(http.StatusNotFound)
+ r.PathGroup("/*", func(g *web.RouterPathGroup) {
+ g.MatchPath("GET", "/<name:*>/@<version:latest>", goproxy.PackageVersionMetadata)
+ g.MatchPath("GET", "/<name:*>/@v/list", goproxy.EnumeratePackageVersions)
+ g.MatchPath("GET", "/<name:*>/@v/<version>.zip", goproxy.DownloadPackageFile)
+ g.MatchPath("GET", "/<name:*>/@v/<version>.info", goproxy.PackageVersionMetadata)
+ g.MatchPath("GET", "/<name:*>/@v/<version>.mod", goproxy.PackageVersionGoModContent)
})
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/generic", func() {
@@ -531,82 +453,26 @@ func CommonRoutes() *web.Router {
})
})
}, reqPackageAccess(perm.AccessModeRead))
+
r.Group("/pypi", func() {
r.Post("/", reqPackageAccess(perm.AccessModeWrite), pypi.UploadPackageFile)
r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
r.Get("/simple/{id}", pypi.PackageMetadata)
}, reqPackageAccess(perm.AccessModeRead))
- r.Group("/rpm", func() {
- r.Group("/repository.key", func() {
- r.Head("", rpm.GetRepositoryKey)
- r.Get("", rpm.GetRepositoryKey)
- })
-
- var (
- repoPattern = regexp.MustCompile(`\A(.*?)\.repo\z`)
- uploadPattern = regexp.MustCompile(`\A(.*?)/upload\z`)
- filePattern = regexp.MustCompile(`\A(.*?)/package/([^/]+)/([^/]+)/([^/]+)(?:/([^/]+\.rpm)|)\z`)
- repoFilePattern = regexp.MustCompile(`\A(.*?)/repodata/([^/]+)\z`)
- )
-
- r.Methods("HEAD,GET,PUT,DELETE", "*", func(ctx *context.Context) {
- path := ctx.PathParam("*")
- isHead := ctx.Req.Method == "HEAD"
- isGetHead := ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET"
- isPut := ctx.Req.Method == "PUT"
- isDelete := ctx.Req.Method == "DELETE"
-
- m := repoPattern.FindStringSubmatch(path)
- if len(m) == 2 && isGetHead {
- ctx.SetPathParam("group", strings.Trim(m[1], "/"))
- rpm.GetRepositoryConfig(ctx)
- return
- }
- m = repoFilePattern.FindStringSubmatch(path)
- if len(m) == 3 && isGetHead {
- ctx.SetPathParam("group", strings.Trim(m[1], "/"))
- ctx.SetPathParam("filename", m[2])
- if isHead {
- rpm.CheckRepositoryFileExistence(ctx)
- } else {
- rpm.GetRepositoryFile(ctx)
- }
- return
- }
-
- m = uploadPattern.FindStringSubmatch(path)
- if len(m) == 2 && isPut {
- reqPackageAccess(perm.AccessModeWrite)(ctx)
- if ctx.Written() {
- return
- }
- ctx.SetPathParam("group", strings.Trim(m[1], "/"))
- rpm.UploadPackageFile(ctx)
- return
- }
-
- m = filePattern.FindStringSubmatch(path)
- if len(m) == 6 && (isGetHead || isDelete) {
- ctx.SetPathParam("group", strings.Trim(m[1], "/"))
- ctx.SetPathParam("name", m[2])
- ctx.SetPathParam("version", m[3])
- ctx.SetPathParam("architecture", m[4])
- if isGetHead {
- rpm.DownloadPackageFile(ctx)
- } else {
- reqPackageAccess(perm.AccessModeWrite)(ctx)
- if ctx.Written() {
- return
- }
- rpm.DeletePackageFile(ctx)
- }
- return
- }
-
- ctx.Status(http.StatusNotFound)
- })
+ r.Methods("HEAD,GET", "/rpm.repo", reqPackageAccess(perm.AccessModeRead), rpm.GetRepositoryConfig)
+ r.PathGroup("/rpm/*", func(g *web.RouterPathGroup) {
+ g.MatchPath("HEAD,GET", "/repository.key", rpm.GetRepositoryKey)
+ g.MatchPath("HEAD,GET", "/<group:*>.repo", rpm.GetRepositoryConfig)
+ g.MatchPath("HEAD", "/<group:*>/repodata/<filename>", rpm.CheckRepositoryFileExistence)
+ g.MatchPath("GET", "/<group:*>/repodata/<filename>", rpm.GetRepositoryFile)
+ g.MatchPath("PUT", "/<group:*>/upload", reqPackageAccess(perm.AccessModeWrite), rpm.UploadPackageFile)
+ // this URL pattern is only used internally in the RPM index, it is generated by us, the filename part is not really used (can be anything)
+ g.MatchPath("HEAD,GET", "/<group:*>/package/<name>/<version>/<architecture>", rpm.DownloadPackageFile)
+ g.MatchPath("HEAD,GET", "/<group:*>/package/<name>/<version>/<architecture>/<filename>", rpm.DownloadPackageFile)
+ g.MatchPath("DELETE", "/<group:*>/package/<name>/<version>/<architecture>", reqPackageAccess(perm.AccessModeWrite), rpm.DeletePackageFile)
}, reqPackageAccess(perm.AccessModeRead))
+
r.Group("/rubygems", func() {
r.Get("/specs.4.8.gz", rubygems.EnumeratePackages)
r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest)
@@ -620,6 +486,7 @@ func CommonRoutes() *web.Router {
r.Delete("/yank", rubygems.DeletePackage)
}, reqPackageAccess(perm.AccessModeWrite))
}, reqPackageAccess(perm.AccessModeRead))
+
r.Group("/swift", func() {
r.Group("", func() { // Needs to be unauthenticated.
r.Post("", swift.CheckAuthenticate)
@@ -631,31 +498,12 @@ func CommonRoutes() *web.Router {
r.Get("", swift.EnumeratePackageVersions)
r.Get(".json", swift.EnumeratePackageVersions)
}, swift.CheckAcceptMediaType(swift.AcceptJSON))
- r.Group("/{version}", func() {
- r.Get("/Package.swift", swift.CheckAcceptMediaType(swift.AcceptSwift), swift.DownloadManifest)
- r.Put("", reqPackageAccess(perm.AccessModeWrite), swift.CheckAcceptMediaType(swift.AcceptJSON), swift.UploadPackageFile)
- r.Get("", func(ctx *context.Context) {
- // Can't use normal routes here: https://github.com/go-chi/chi/issues/781
-
- version := ctx.PathParam("version")
- if strings.HasSuffix(version, ".zip") {
- swift.CheckAcceptMediaType(swift.AcceptZip)(ctx)
- if ctx.Written() {
- return
- }
- ctx.SetPathParam("version", version[:len(version)-4])
- swift.DownloadPackageFile(ctx)
- } else {
- swift.CheckAcceptMediaType(swift.AcceptJSON)(ctx)
- if ctx.Written() {
- return
- }
- if strings.HasSuffix(version, ".json") {
- ctx.SetPathParam("version", version[:len(version)-5])
- }
- swift.PackageVersionMetadata(ctx)
- }
- })
+ r.PathGroup("/*", func(g *web.RouterPathGroup) {
+ g.MatchPath("GET", "/<version>.json", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.PackageVersionMetadata)
+ g.MatchPath("GET", "/<version>.zip", swift.CheckAcceptMediaType(swift.AcceptZip), swift.DownloadPackageFile)
+ g.MatchPath("GET", "/<version>/Package.swift", swift.CheckAcceptMediaType(swift.AcceptSwift), swift.DownloadManifest)
+ g.MatchPath("GET", "/<version>", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.PackageVersionMetadata)
+ g.MatchPath("PUT", "/<version>", reqPackageAccess(perm.AccessModeWrite), swift.CheckAcceptMediaType(swift.AcceptJSON), swift.UploadPackageFile)
})
})
r.Get("/identifiers", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.LookupPackageIdentifiers)
@@ -692,6 +540,8 @@ func ContainerRoutes() *web.Router {
&container.Auth{},
})
+ // TODO: Content Discovery / References (not implemented yet)
+
r.Get("", container.ReqContainerAccess, container.DetermineSupport)
r.Group("/token", func() {
r.Get("", container.Authenticate)
@@ -700,26 +550,22 @@ func ContainerRoutes() *web.Router {
r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList)
r.Group("/{username}", func() {
r.PathGroup("/*", func(g *web.RouterPathGroup) {
- g.MatchPath("POST", "/<image:*>/blobs/uploads", reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName, container.InitiateUploadBlob)
- g.MatchPath("GET", "/<image:*>/tags/list", container.VerifyImageName, container.GetTagList)
- g.MatchPath("GET,PATCH,PUT,DELETE", `/<image:*>/blobs/uploads/<uuid:[-.=\w]+>`, reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName, func(ctx *context.Context) {
- if ctx.Req.Method == http.MethodGet {
- container.GetUploadBlob(ctx)
- } else if ctx.Req.Method == http.MethodPatch {
- container.UploadBlob(ctx)
- } else if ctx.Req.Method == http.MethodPut {
- container.EndUploadBlob(ctx)
- } else /* DELETE */ {
- container.CancelUploadBlob(ctx)
- }
- })
+ g.MatchPath("POST", "/<image:*>/blobs/uploads", reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName, container.PostBlobsUploads)
+ g.MatchPath("GET", "/<image:*>/tags/list", container.VerifyImageName, container.GetTagsList)
+
+ patternBlobsUploadsUUID := g.PatternRegexp(`/<image:*>/blobs/uploads/<uuid:[-.=\w]+>`, reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName)
+ g.MatchPattern("GET", patternBlobsUploadsUUID, container.GetBlobsUpload)
+ g.MatchPattern("PATCH", patternBlobsUploadsUUID, container.PatchBlobsUpload)
+ g.MatchPattern("PUT", patternBlobsUploadsUUID, container.PutBlobsUpload)
+ g.MatchPattern("DELETE", patternBlobsUploadsUUID, container.DeleteBlobsUpload)
+
g.MatchPath("HEAD", `/<image:*>/blobs/<digest>`, container.VerifyImageName, container.HeadBlob)
g.MatchPath("GET", `/<image:*>/blobs/<digest>`, container.VerifyImageName, container.GetBlob)
g.MatchPath("DELETE", `/<image:*>/blobs/<digest>`, container.VerifyImageName, reqPackageAccess(perm.AccessModeWrite), container.DeleteBlob)
g.MatchPath("HEAD", `/<image:*>/manifests/<reference>`, container.VerifyImageName, container.HeadManifest)
g.MatchPath("GET", `/<image:*>/manifests/<reference>`, container.VerifyImageName, container.GetManifest)
- g.MatchPath("PUT", `/<image:*>/manifests/<reference>`, container.VerifyImageName, reqPackageAccess(perm.AccessModeWrite), container.UploadManifest)
+ g.MatchPath("PUT", `/<image:*>/manifests/<reference>`, container.VerifyImageName, reqPackageAccess(perm.AccessModeWrite), container.PutManifest)
g.MatchPath("DELETE", `/<image:*>/manifests/<reference>`, container.VerifyImageName, reqPackageAccess(perm.AccessModeWrite), container.DeleteManifest)
})
}, container.ReqContainerAccess, context.UserAssignmentWeb(), context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead))
diff --git a/routers/api/packages/arch/arch.go b/routers/api/packages/arch/arch.go
index f5dc6c1d01..bf9cc3f1b8 100644
--- a/routers/api/packages/arch/arch.go
+++ b/routers/api/packages/arch/arch.go
@@ -239,7 +239,7 @@ func GetPackageOrRepositoryFile(ctx *context.Context) {
return
}
- s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
+ s, u, pf, err := packages_service.OpenFileForDownload(ctx, pfs[0])
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
diff --git a/routers/api/packages/cargo/cargo.go b/routers/api/packages/cargo/cargo.go
index 42ef13476c..cfcf79244f 100644
--- a/routers/api/packages/cargo/cargo.go
+++ b/routers/api/packages/cargo/cargo.go
@@ -51,7 +51,7 @@ func apiError(ctx *context.Context, status int, obj any) {
// https://rust-lang.github.io/rfcs/2789-sparse-index.html
func RepositoryConfig(ctx *context.Context) {
- ctx.JSON(http.StatusOK, cargo_service.BuildConfig(ctx.Package.Owner, setting.Service.RequireSignInView || ctx.Package.Owner.Visibility != structs.VisibleTypePublic))
+ ctx.JSON(http.StatusOK, cargo_service.BuildConfig(ctx.Package.Owner, setting.Service.RequireSignInViewStrict || ctx.Package.Owner.Visibility != structs.VisibleTypePublic))
}
func EnumeratePackageVersions(ctx *context.Context) {
@@ -95,10 +95,7 @@ type SearchResultMeta struct {
// https://doc.rust-lang.org/cargo/reference/registries.html#search
func SearchPackages(ctx *context.Context) {
- page := ctx.FormInt("page")
- if page < 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
perPage := ctx.FormInt("per_page")
paginator := db.ListOptions{
Page: page,
@@ -168,7 +165,7 @@ func ListOwners(ctx *context.Context) {
// DownloadPackageFile serves the content of a package
func DownloadPackageFile(ctx *context.Context) {
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
diff --git a/routers/api/packages/chef/auth.go b/routers/api/packages/chef/auth.go
index a790e9a363..c6808300a2 100644
--- a/routers/api/packages/chef/auth.go
+++ b/routers/api/packages/chef/auth.go
@@ -12,6 +12,7 @@ import (
"crypto/x509"
"encoding/base64"
"encoding/pem"
+ "errors"
"fmt"
"hash"
"math/big"
@@ -121,7 +122,7 @@ func verifyTimestamp(req *http.Request) error {
}
if diff > maxTimeDifference {
- return fmt.Errorf("time difference")
+ return errors.New("time difference")
}
return nil
@@ -190,7 +191,7 @@ func getAuthorizationData(req *http.Request) ([]byte, error) {
tmp := make([]string, len(valueList))
for k, v := range valueList {
if k > len(tmp) {
- return nil, fmt.Errorf("invalid X-Ops-Authorization headers")
+ return nil, errors.New("invalid X-Ops-Authorization headers")
}
tmp[k-1] = v
}
@@ -267,7 +268,7 @@ func verifyDataOld(signature, data []byte, pub *rsa.PublicKey) error {
}
if !slices.Equal(out[skip:], data) {
- return fmt.Errorf("could not verify signature")
+ return errors.New("could not verify signature")
}
return nil
diff --git a/routers/api/packages/chef/chef.go b/routers/api/packages/chef/chef.go
index a0c8c5696c..1f11afe548 100644
--- a/routers/api/packages/chef/chef.go
+++ b/routers/api/packages/chef/chef.go
@@ -343,7 +343,7 @@ func DownloadPackage(ctx *context.Context) {
pf := pd.Files[0].File
- s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
+ s, u, _, err := packages_service.OpenFileForDownload(ctx, pf)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
diff --git a/routers/api/packages/composer/api.go b/routers/api/packages/composer/api.go
index a3bcf80417..a3ea2c2f9a 100644
--- a/routers/api/packages/composer/api.go
+++ b/routers/api/packages/composer/api.go
@@ -66,6 +66,7 @@ type PackageMetadataResponse struct {
}
// PackageVersionMetadata contains package metadata
+// https://getcomposer.org/doc/05-repositories.md#package
type PackageVersionMetadata struct {
*composer_module.Metadata
Name string `json:"name"`
@@ -73,6 +74,7 @@ type PackageVersionMetadata struct {
Type string `json:"type"`
Created time.Time `json:"time"`
Dist Dist `json:"dist"`
+ Source Source `json:"source"`
}
// Dist contains package download information
@@ -82,6 +84,13 @@ type Dist struct {
Checksum string `json:"shasum"`
}
+// Source contains package source information
+type Source struct {
+ URL string `json:"url"`
+ Type string `json:"type"`
+ Reference string `json:"reference"`
+}
+
func createPackageMetadataResponse(registryURL string, pds []*packages_model.PackageDescriptor) *PackageMetadataResponse {
versions := make([]*PackageVersionMetadata, 0, len(pds))
@@ -94,7 +103,7 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac
}
}
- versions = append(versions, &PackageVersionMetadata{
+ pkg := PackageVersionMetadata{
Name: pd.Package.Name,
Version: pd.Version.Version,
Type: packageType,
@@ -105,7 +114,16 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac
URL: fmt.Sprintf("%s/files/%s/%s/%s", registryURL, url.PathEscape(pd.Package.LowerName), url.PathEscape(pd.Version.LowerVersion), url.PathEscape(pd.Files[0].File.LowerName)),
Checksum: pd.Files[0].Blob.HashSHA1,
},
- })
+ }
+ if pd.Repository != nil {
+ pkg.Source = Source{
+ URL: pd.Repository.HTMLURL(),
+ Type: "git",
+ Reference: pd.Version.Version,
+ }
+ }
+
+ versions = append(versions, &pkg)
}
return &PackageMetadataResponse{
diff --git a/routers/api/packages/composer/composer.go b/routers/api/packages/composer/composer.go
index c6c14e5cf4..9daf0ffeff 100644
--- a/routers/api/packages/composer/composer.go
+++ b/routers/api/packages/composer/composer.go
@@ -53,10 +53,7 @@ func ServiceIndex(ctx *context.Context) {
// SearchPackages searches packages, only "q" is supported
// https://packagist.org/apidoc#search-packages
func SearchPackages(ctx *context.Context) {
- page := ctx.FormInt("page")
- if page < 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
perPage := ctx.FormInt("per_page")
paginator := db.ListOptions{
Page: page,
@@ -163,7 +160,7 @@ func PackageMetadata(ctx *context.Context) {
// DownloadPackageFile serves the content of a package
func DownloadPackageFile(ctx *context.Context) {
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
diff --git a/routers/api/packages/conan/conan.go b/routers/api/packages/conan/conan.go
index 8019eee9f7..fe70e02cd6 100644
--- a/routers/api/packages/conan/conan.go
+++ b/routers/api/packages/conan/conan.go
@@ -480,7 +480,7 @@ func downloadFile(ctx *context.Context, fileFilter container.Set[string], fileKe
return
}
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
diff --git a/routers/api/packages/conda/conda.go b/routers/api/packages/conda/conda.go
index 7a46681235..cfe069d6db 100644
--- a/routers/api/packages/conda/conda.go
+++ b/routers/api/packages/conda/conda.go
@@ -36,6 +36,24 @@ func apiError(ctx *context.Context, status int, obj any) {
})
}
+func isCondaPackageFileName(filename string) bool {
+ return strings.HasSuffix(filename, ".tar.bz2") || strings.HasSuffix(filename, ".conda")
+}
+
+func ListOrGetPackages(ctx *context.Context) {
+ filename := ctx.PathParam("filename")
+ switch filename {
+ case "repodata.json", "repodata.json.bz2", "current_repodata.json", "current_repodata.json.bz2":
+ EnumeratePackages(ctx)
+ return
+ }
+ if isCondaPackageFileName(filename) {
+ DownloadPackageFile(ctx)
+ return
+ }
+ ctx.NotFound(nil)
+}
+
func EnumeratePackages(ctx *context.Context) {
type Info struct {
Subdir string `json:"subdir"`
@@ -174,6 +192,12 @@ func EnumeratePackages(ctx *context.Context) {
}
func UploadPackageFile(ctx *context.Context) {
+ filename := ctx.PathParam("filename")
+ if !isCondaPackageFileName(filename) {
+ apiError(ctx, http.StatusBadRequest, nil)
+ return
+ }
+
upload, needToClose, err := ctx.UploadStream()
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -191,7 +215,7 @@ func UploadPackageFile(ctx *context.Context) {
defer buf.Close()
var pck *conda_module.Package
- if strings.HasSuffix(strings.ToLower(ctx.PathParam("filename")), ".tar.bz2") {
+ if strings.HasSuffix(filename, ".tar.bz2") {
pck, err = conda_module.ParsePackageBZ2(buf)
} else {
pck, err = conda_module.ParsePackageConda(buf, buf.Size())
@@ -293,7 +317,7 @@ func DownloadPackageFile(ctx *context.Context) {
pf := pfs[0]
- s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
+ s, u, _, err := packages_service.OpenFileForDownload(ctx, pf)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
diff --git a/routers/api/packages/container/auth.go b/routers/api/packages/container/auth.go
index 1d8ae6af7d..1e1b87eb79 100644
--- a/routers/api/packages/container/auth.go
+++ b/routers/api/packages/container/auth.go
@@ -21,7 +21,7 @@ func (a *Auth) Name() string {
}
// Verify extracts the user from the Bearer token
-// If it's an anonymous session a ghost user is returned
+// If it's an anonymous session, a ghost user is returned
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataStore, sess auth.SessionStore) (*user_model.User, error) {
packageMeta, err := packages.ParseAuthorizationRequest(req)
if err != nil {
diff --git a/routers/api/packages/container/blob.go b/routers/api/packages/container/blob.go
index 671803788a..4b7bcee9d0 100644
--- a/routers/api/packages/container/blob.go
+++ b/routers/api/packages/container/blob.go
@@ -20,11 +20,13 @@ import (
container_module "code.gitea.io/gitea/modules/packages/container"
"code.gitea.io/gitea/modules/util"
packages_service "code.gitea.io/gitea/services/packages"
+
+ "github.com/opencontainers/go-digest"
)
// saveAsPackageBlob creates a package blob from an upload
// The uploaded blob gets stored in a special upload version to link them to the package/image
-func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) { //nolint:unparam
+func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) { //nolint:unparam // PackageBlob is never used
pb := packages_service.NewPackageBlob(hsr)
exists := false
@@ -88,20 +90,18 @@ func mountBlob(ctx context.Context, pi *packages_service.PackageInfo, pb *packag
})
}
-func containerPkgName(piOwnerID int64, piName string) string {
- return fmt.Sprintf("pkg_%d_container_%s", piOwnerID, strings.ToLower(piName))
+func containerGlobalLockKey(piOwnerID int64, piName, usage string) string {
+ return fmt.Sprintf("pkg_%d_container_%s_%s", piOwnerID, strings.ToLower(piName), usage)
}
func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageInfo) (*packages_model.PackageVersion, error) {
- var uploadVersion *packages_model.PackageVersion
-
- releaser, err := globallock.Lock(ctx, containerPkgName(pi.Owner.ID, pi.Name))
+ releaser, err := globallock.Lock(ctx, containerGlobalLockKey(pi.Owner.ID, pi.Name, "package"))
if err != nil {
return nil, err
}
defer releaser()
- err = db.WithTx(ctx, func(ctx context.Context) error {
+ return db.WithTx2(ctx, func(ctx context.Context) (*packages_model.PackageVersion, error) {
created := true
p := &packages_model.Package{
OwnerID: pi.Owner.ID,
@@ -113,7 +113,7 @@ func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageI
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
if !errors.Is(err, packages_model.ErrDuplicatePackage) {
log.Error("Error inserting package: %v", err)
- return err
+ return nil, err
}
created = false
}
@@ -121,35 +121,30 @@ func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageI
if created {
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, strings.ToLower(pi.Owner.LowerName+"/"+pi.Name)); err != nil {
log.Error("Error setting package property: %v", err)
- return err
+ return nil, err
}
}
pv := &packages_model.PackageVersion{
PackageID: p.ID,
CreatorID: pi.Owner.ID,
- Version: container_model.UploadVersion,
- LowerVersion: container_model.UploadVersion,
+ Version: container_module.UploadVersion,
+ LowerVersion: container_module.UploadVersion,
IsInternal: true,
MetadataJSON: "null",
}
if pv, err = packages_model.GetOrInsertVersion(ctx, pv); err != nil {
if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
log.Error("Error inserting package: %v", err)
- return err
+ return nil, err
}
}
-
- uploadVersion = pv
-
- return nil
+ return pv, nil
})
-
- return uploadVersion, err
}
func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, pb *packages_model.PackageBlob) error {
- filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256))
+ filename := strings.ToLower("sha256_" + pb.HashSHA256)
pf := &packages_model.PackageFile{
VersionID: pv.ID,
@@ -175,8 +170,8 @@ func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, p
return nil
}
-func deleteBlob(ctx context.Context, ownerID int64, image, digest string) error {
- releaser, err := globallock.Lock(ctx, containerPkgName(ownerID, image))
+func deleteBlob(ctx context.Context, ownerID int64, image string, digest digest.Digest) error {
+ releaser, err := globallock.Lock(ctx, containerGlobalLockKey(ownerID, image, "blob"))
if err != nil {
return err
}
@@ -186,7 +181,7 @@ func deleteBlob(ctx context.Context, ownerID int64, image, digest string) error
pfds, err := container_model.GetContainerBlobs(ctx, &container_model.BlobSearchOptions{
OwnerID: ownerID,
Image: image,
- Digest: digest,
+ Digest: string(digest),
})
if err != nil {
return err
diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go
index bb14db9db7..d532f698ad 100644
--- a/routers/api/packages/container/container.go
+++ b/routers/api/packages/container/container.go
@@ -13,6 +13,7 @@ import (
"regexp"
"strconv"
"strings"
+ "sync"
auth_model "code.gitea.io/gitea/models/auth"
packages_model "code.gitea.io/gitea/models/packages"
@@ -21,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
container_module "code.gitea.io/gitea/modules/packages/container"
"code.gitea.io/gitea/modules/setting"
@@ -31,17 +33,21 @@ import (
packages_service "code.gitea.io/gitea/services/packages"
container_service "code.gitea.io/gitea/services/packages/container"
- digest "github.com/opencontainers/go-digest"
+ "github.com/opencontainers/go-digest"
)
// maximum size of a container manifest
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-manifests
const maxManifestSize = 10 * 1024 * 1024
-var (
- imageNamePattern = regexp.MustCompile(`\A[a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)*\z`)
- referencePattern = regexp.MustCompile(`\A[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}\z`)
-)
+var globalVars = sync.OnceValue(func() (ret struct {
+ imageNamePattern, referencePattern *regexp.Regexp
+},
+) {
+ ret.imageNamePattern = regexp.MustCompile(`\A[a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)*\z`)
+ ret.referencePattern = regexp.MustCompile(`\A[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}\z`)
+ return ret
+})
type containerHeaders struct {
Status int
@@ -50,7 +56,7 @@ type containerHeaders struct {
Range string
Location string
ContentType string
- ContentLength int64
+ ContentLength optional.Option[int64]
}
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#legacy-docker-support-http-headers
@@ -64,8 +70,8 @@ func setResponseHeaders(resp http.ResponseWriter, h *containerHeaders) {
if h.ContentType != "" {
resp.Header().Set("Content-Type", h.ContentType)
}
- if h.ContentLength != 0 {
- resp.Header().Set("Content-Length", strconv.FormatInt(h.ContentLength, 10))
+ if h.ContentLength.Has() {
+ resp.Header().Set("Content-Length", strconv.FormatInt(h.ContentLength.Value(), 10))
}
if h.UploadUUID != "" {
resp.Header().Set("Docker-Upload-Uuid", h.UploadUUID)
@@ -83,9 +89,7 @@ func jsonResponse(ctx *context.Context, status int, obj any) {
Status: status,
ContentType: "application/json",
})
- if err := json.NewEncoder(ctx.Resp).Encode(obj); err != nil {
- log.Error("JSON encode: %v", err)
- }
+ _ = json.NewEncoder(ctx.Resp).Encode(obj) // ignore network errors
}
func apiError(ctx *context.Context, status int, err error) {
@@ -126,14 +130,14 @@ func apiUnauthorizedError(ctx *context.Context) {
// ReqContainerAccess is a middleware which checks the current user valid (real user or ghost if anonymous access is enabled)
func ReqContainerAccess(ctx *context.Context) {
- if ctx.Doer == nil || (setting.Service.RequireSignInView && ctx.Doer.IsGhost()) {
+ if ctx.Doer == nil || (setting.Service.RequireSignInViewStrict && ctx.Doer.IsGhost()) {
apiUnauthorizedError(ctx)
}
}
// VerifyImageName is a middleware which checks if the image name is allowed
func VerifyImageName(ctx *context.Context) {
- if !imageNamePattern.MatchString(ctx.PathParam("image")) {
+ if !globalVars().imageNamePattern.MatchString(ctx.PathParam("image")) {
apiErrorDefined(ctx, errNameInvalid)
}
}
@@ -152,7 +156,7 @@ func Authenticate(ctx *context.Context) {
u := ctx.Doer
packageScope := auth_service.GetAccessScope(ctx.Data)
if u == nil {
- if setting.Service.RequireSignInView {
+ if setting.Service.RequireSignInViewStrict {
apiUnauthorizedError(ctx)
return
}
@@ -215,7 +219,7 @@ func GetRepositoryList(ctx *context.Context) {
if len(repositories) == n {
v := url.Values{}
if n > 0 {
- v.Add("n", strconv.Itoa(n))
+ v.Add("n", strconv.Itoa(n)) // FIXME: "n" can't be zero here, the logic is inconsistent with GetTagsList
}
v.Add("last", repositories[len(repositories)-1])
@@ -230,7 +234,7 @@ func GetRepositoryList(ctx *context.Context) {
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#mounting-a-blob-from-another-repository
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#single-post
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks
-func InitiateUploadBlob(ctx *context.Context) {
+func PostBlobsUploads(ctx *context.Context) {
image := ctx.PathParam("image")
mount := ctx.FormTrim("mount")
@@ -312,14 +316,14 @@ func InitiateUploadBlob(ctx *context.Context) {
setResponseHeaders(ctx.Resp, &containerHeaders{
Location: fmt.Sprintf("/v2/%s/%s/blobs/uploads/%s", ctx.Package.Owner.LowerName, image, upload.ID),
- Range: "0-0",
UploadUUID: upload.ID,
Status: http.StatusAccepted,
})
}
-// https://docs.docker.com/registry/spec/api/#get-blob-upload
-func GetUploadBlob(ctx *context.Context) {
+// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks
+func GetBlobsUpload(ctx *context.Context) {
+ image := ctx.PathParam("image")
uuid := ctx.PathParam("uuid")
upload, err := packages_model.GetBlobUploadByID(ctx, uuid)
@@ -332,15 +336,21 @@ func GetUploadBlob(ctx *context.Context) {
return
}
- setResponseHeaders(ctx.Resp, &containerHeaders{
- Range: fmt.Sprintf("0-%d", upload.BytesReceived),
+ // FIXME: undefined behavior when the uploaded content is empty: https://github.com/opencontainers/distribution-spec/issues/578
+ respHeaders := &containerHeaders{
+ Location: fmt.Sprintf("/v2/%s/%s/blobs/uploads/%s", ctx.Package.Owner.LowerName, image, upload.ID),
UploadUUID: upload.ID,
Status: http.StatusNoContent,
- })
+ }
+ if upload.BytesReceived > 0 {
+ respHeaders.Range = fmt.Sprintf("0-%d", upload.BytesReceived-1)
+ }
+ setResponseHeaders(ctx.Resp, respHeaders)
}
+// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#single-post
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks
-func UploadBlob(ctx *context.Context) {
+func PatchBlobsUpload(ctx *context.Context) {
image := ctx.PathParam("image")
uploader, err := container_service.NewBlobUploader(ctx, ctx.PathParam("uuid"))
@@ -376,16 +386,19 @@ func UploadBlob(ctx *context.Context) {
return
}
- setResponseHeaders(ctx.Resp, &containerHeaders{
+ respHeaders := &containerHeaders{
Location: fmt.Sprintf("/v2/%s/%s/blobs/uploads/%s", ctx.Package.Owner.LowerName, image, uploader.ID),
- Range: fmt.Sprintf("0-%d", uploader.Size()-1),
UploadUUID: uploader.ID,
Status: http.StatusAccepted,
- })
+ }
+ if uploader.Size() > 0 {
+ respHeaders.Range = fmt.Sprintf("0-%d", uploader.Size()-1)
+ }
+ setResponseHeaders(ctx.Resp, respHeaders)
}
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks
-func EndUploadBlob(ctx *context.Context) {
+func PutBlobsUpload(ctx *context.Context) {
image := ctx.PathParam("image")
digest := ctx.FormTrim("digest")
@@ -403,12 +416,7 @@ func EndUploadBlob(ctx *context.Context) {
}
return
}
- doClose := true
- defer func() {
- if doClose {
- uploader.Close()
- }
- }()
+ defer uploader.Close()
if ctx.Req.Body != nil {
if err := uploader.Append(ctx, ctx.Req.Body); err != nil {
@@ -441,11 +449,10 @@ func EndUploadBlob(ctx *context.Context) {
return
}
- if err := uploader.Close(); err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
- doClose = false
+ // There was a strange bug: the "Close" fails with error "close .../tmp/package-upload/....: file already closed"
+ // AFAIK there should be no other "Close" call to the uploader between NewBlobUploader and this line.
+ // At least it's safe to call Close twice, so ignore the error.
+ _ = uploader.Close()
if err := container_service.RemoveBlobUploadByID(ctx, uploader.ID); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -460,7 +467,7 @@ func EndUploadBlob(ctx *context.Context) {
}
// https://docs.docker.com/registry/spec/api/#delete-blob-upload
-func CancelUploadBlob(ctx *context.Context) {
+func DeleteBlobsUpload(ctx *context.Context) {
uuid := ctx.PathParam("uuid")
_, err := packages_model.GetBlobUploadByID(ctx, uuid)
@@ -484,16 +491,15 @@ func CancelUploadBlob(ctx *context.Context) {
}
func getBlobFromContext(ctx *context.Context) (*packages_model.PackageFileDescriptor, error) {
- d := ctx.PathParam("digest")
-
- if digest.Digest(d).Validate() != nil {
+ d := digest.Digest(ctx.PathParam("digest"))
+ if d.Validate() != nil {
return nil, container_model.ErrContainerBlobNotExist
}
return workaroundGetContainerBlob(ctx, &container_model.BlobSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Image: ctx.PathParam("image"),
- Digest: d,
+ Digest: string(d),
})
}
@@ -511,7 +517,7 @@ func HeadBlob(ctx *context.Context) {
setResponseHeaders(ctx.Resp, &containerHeaders{
ContentDigest: blob.Properties.GetByName(container_module.PropertyDigest),
- ContentLength: blob.Blob.Size,
+ ContentLength: optional.Some(blob.Blob.Size),
Status: http.StatusOK,
})
}
@@ -533,9 +539,8 @@ func GetBlob(ctx *context.Context) {
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#deleting-blobs
func DeleteBlob(ctx *context.Context) {
- d := ctx.PathParam("digest")
-
- if digest.Digest(d).Validate() != nil {
+ d := digest.Digest(ctx.PathParam("digest"))
+ if d.Validate() != nil {
apiErrorDefined(ctx, errBlobUnknown)
return
}
@@ -551,7 +556,7 @@ func DeleteBlob(ctx *context.Context) {
}
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-manifests
-func UploadManifest(ctx *context.Context) {
+func PutManifest(ctx *context.Context) {
reference := ctx.PathParam("reference")
mci := &manifestCreationInfo{
@@ -563,7 +568,7 @@ func UploadManifest(ctx *context.Context) {
IsTagged: digest.Digest(reference).Validate() != nil,
}
- if mci.IsTagged && !referencePattern.MatchString(reference) {
+ if mci.IsTagged && !globalVars().referencePattern.MatchString(reference) {
apiErrorDefined(ctx, errManifestInvalid.WithMessage("Tag is invalid"))
return
}
@@ -607,18 +612,18 @@ func UploadManifest(ctx *context.Context) {
}
func getBlobSearchOptionsFromContext(ctx *context.Context) (*container_model.BlobSearchOptions, error) {
- reference := ctx.PathParam("reference")
-
opts := &container_model.BlobSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Image: ctx.PathParam("image"),
IsManifest: true,
}
- if digest.Digest(reference).Validate() == nil {
- opts.Digest = reference
- } else if referencePattern.MatchString(reference) {
+ reference := ctx.PathParam("reference")
+ if d := digest.Digest(reference); d.Validate() == nil {
+ opts.Digest = string(d)
+ } else if globalVars().referencePattern.MatchString(reference) {
opts.Tag = reference
+ opts.OnlyLead = true
} else {
return nil, container_model.ErrContainerBlobNotExist
}
@@ -650,7 +655,7 @@ func HeadManifest(ctx *context.Context) {
setResponseHeaders(ctx.Resp, &containerHeaders{
ContentDigest: manifest.Properties.GetByName(container_module.PropertyDigest),
ContentType: manifest.Properties.GetByName(container_module.PropertyMediaType),
- ContentLength: manifest.Blob.Size,
+ ContentLength: optional.Some(manifest.Blob.Size),
Status: http.StatusOK,
})
}
@@ -705,7 +710,7 @@ func DeleteManifest(ctx *context.Context) {
func serveBlob(ctx *context.Context, pfd *packages_model.PackageFileDescriptor) {
serveDirectReqParams := make(url.Values)
serveDirectReqParams.Set("response-content-type", pfd.Properties.GetByName(container_module.PropertyMediaType))
- s, u, _, err := packages_service.GetPackageBlobStream(ctx, pfd.File, pfd.Blob, serveDirectReqParams)
+ s, u, _, err := packages_service.OpenBlobForDownload(ctx, pfd.File, pfd.Blob, serveDirectReqParams)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
@@ -714,14 +719,14 @@ func serveBlob(ctx *context.Context, pfd *packages_model.PackageFileDescriptor)
headers := &containerHeaders{
ContentDigest: pfd.Properties.GetByName(container_module.PropertyDigest),
ContentType: pfd.Properties.GetByName(container_module.PropertyMediaType),
- ContentLength: pfd.Blob.Size,
+ ContentLength: optional.Some(pfd.Blob.Size),
Status: http.StatusOK,
}
if u != nil {
headers.Status = http.StatusTemporaryRedirect
headers.Location = u.String()
-
+ headers.ContentLength = optional.None[int64]() // do not set Content-Length for redirect responses
setResponseHeaders(ctx.Resp, headers)
return
}
@@ -735,7 +740,7 @@ func serveBlob(ctx *context.Context, pfd *packages_model.PackageFileDescriptor)
}
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#content-discovery
-func GetTagList(ctx *context.Context) {
+func GetTagsList(ctx *context.Context) {
image := ctx.PathParam("image")
if _, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.TypeContainer, image); err != nil {
@@ -780,7 +785,8 @@ func GetTagList(ctx *context.Context) {
})
}
-// FIXME: Workaround to be removed in v1.20
+// FIXME: Workaround to be removed in v1.20.
+// Update maybe we should never really remote it, as long as there is legacy data?
// https://github.com/go-gitea/gitea/issues/19586
func workaroundGetContainerBlob(ctx *context.Context, opts *container_model.BlobSearchOptions) (*packages_model.PackageFileDescriptor, error) {
blob, err := container_model.GetContainerBlob(ctx, opts)
diff --git a/routers/api/packages/container/manifest.go b/routers/api/packages/container/manifest.go
index ad035cf473..de40215aa7 100644
--- a/routers/api/packages/container/manifest.go
+++ b/routers/api/packages/container/manifest.go
@@ -10,11 +10,13 @@ import (
"io"
"os"
"strings"
+ "time"
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
container_model "code.gitea.io/gitea/models/packages/container"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/globallock"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
@@ -22,23 +24,12 @@ import (
"code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify"
packages_service "code.gitea.io/gitea/services/packages"
+ container_service "code.gitea.io/gitea/services/packages/container"
- digest "github.com/opencontainers/go-digest"
+ "github.com/opencontainers/go-digest"
oci "github.com/opencontainers/image-spec/specs-go/v1"
)
-func isValidMediaType(mt string) bool {
- return strings.HasPrefix(mt, "application/vnd.docker.") || strings.HasPrefix(mt, "application/vnd.oci.")
-}
-
-func isImageManifestMediaType(mt string) bool {
- return strings.EqualFold(mt, oci.MediaTypeImageManifest) || strings.EqualFold(mt, "application/vnd.docker.distribution.manifest.v2+json")
-}
-
-func isImageIndexMediaType(mt string) bool {
- return strings.EqualFold(mt, oci.MediaTypeImageIndex) || strings.EqualFold(mt, "application/vnd.docker.distribution.manifest.list.v2+json")
-}
-
// manifestCreationInfo describes a manifest to create
type manifestCreationInfo struct {
MediaType string
@@ -55,71 +46,71 @@ func processManifest(ctx context.Context, mci *manifestCreationInfo, buf *packag
if err := json.NewDecoder(buf).Decode(&index); err != nil {
return "", err
}
-
if index.SchemaVersion != 2 {
return "", errUnsupported.WithMessage("Schema version is not supported")
}
-
if _, err := buf.Seek(0, io.SeekStart); err != nil {
return "", err
}
- if !isValidMediaType(mci.MediaType) {
+ if !container_module.IsMediaTypeValid(mci.MediaType) {
mci.MediaType = index.MediaType
- if !isValidMediaType(mci.MediaType) {
+ if !container_module.IsMediaTypeValid(mci.MediaType) {
return "", errManifestInvalid.WithMessage("MediaType not recognized")
}
}
- if isImageManifestMediaType(mci.MediaType) {
- return processImageManifest(ctx, mci, buf)
- } else if isImageIndexMediaType(mci.MediaType) {
- return processImageManifestIndex(ctx, mci, buf)
+ // .../container/manifest.go:453:createManifestBlob() [E] Error inserting package blob: Error 1062 (23000): Duplicate entry '..........' for key 'package_blob.UQE_package_blob_md5'
+ releaser, err := globallock.Lock(ctx, containerGlobalLockKey(mci.Owner.ID, mci.Image, "manifest"))
+ if err != nil {
+ return "", err
+ }
+ defer releaser()
+
+ if container_module.IsMediaTypeImageManifest(mci.MediaType) {
+ return processOciImageManifest(ctx, mci, buf)
+ } else if container_module.IsMediaTypeImageIndex(mci.MediaType) {
+ return processOciImageIndex(ctx, mci, buf)
}
return "", errManifestInvalid
}
-func processImageManifest(ctx context.Context, mci *manifestCreationInfo, buf *packages_module.HashedBuffer) (string, error) {
- manifestDigest := ""
-
- err := func() error {
- var manifest oci.Manifest
- if err := json.NewDecoder(buf).Decode(&manifest); err != nil {
- return err
- }
-
- if _, err := buf.Seek(0, io.SeekStart); err != nil {
- return err
- }
-
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- configDescriptor, err := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
- OwnerID: mci.Owner.ID,
- Image: mci.Image,
- Digest: string(manifest.Config.Digest),
- })
- if err != nil {
- return err
- }
+type processManifestTxRet struct {
+ pv *packages_model.PackageVersion
+ pb *packages_model.PackageBlob
+ created bool
+ digest string
+}
- configReader, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(configDescriptor.Blob.HashSHA256))
- if err != nil {
- return err
+func handleCreateManifestResult(ctx context.Context, err error, mci *manifestCreationInfo, contentStore *packages_module.ContentStore, txRet *processManifestTxRet) (string, error) {
+ if err != nil && txRet.created && txRet.pb != nil {
+ if err := contentStore.Delete(packages_module.BlobHash256Key(txRet.pb.HashSHA256)); err != nil {
+ log.Error("Error deleting package blob from content store: %v", err)
}
- defer configReader.Close()
+ return "", err
+ }
+ pd, err := packages_model.GetPackageDescriptor(ctx, txRet.pv)
+ if err != nil {
+ log.Error("Error getting package descriptor: %v", err) // ignore this error
+ } else {
+ notify_service.PackageCreate(ctx, mci.Creator, pd)
+ }
+ return txRet.digest, nil
+}
- metadata, err := container_module.ParseImageConfig(manifest.Config.MediaType, configReader)
- if err != nil {
- return err
- }
+func processOciImageManifest(ctx context.Context, mci *manifestCreationInfo, buf *packages_module.HashedBuffer) (manifestDigest string, errRet error) {
+ manifest, configDescriptor, metadata, err := container_service.ParseManifestMetadata(ctx, buf, mci.Owner.ID, mci.Image)
+ if err != nil {
+ return "", err
+ }
+ if _, err = buf.Seek(0, io.SeekStart); err != nil {
+ return "", err
+ }
+ contentStore := packages_module.NewContentStore()
+ var txRet processManifestTxRet
+ err = db.WithTx(ctx, func(ctx context.Context) (err error) {
blobReferences := make([]*blobReference, 0, 1+len(manifest.Layers))
-
blobReferences = append(blobReferences, &blobReference{
Digest: manifest.Config.Digest,
MediaType: manifest.Config.MediaType,
@@ -150,78 +141,43 @@ func processImageManifest(ctx context.Context, mci *manifestCreationInfo, buf *p
return err
}
- uploadVersion, err := packages_model.GetInternalVersionByNameAndVersion(ctx, mci.Owner.ID, packages_model.TypeContainer, mci.Image, container_model.UploadVersion)
- if err != nil && err != packages_model.ErrPackageNotExist {
+ uploadVersion, err := packages_model.GetInternalVersionByNameAndVersion(ctx, mci.Owner.ID, packages_model.TypeContainer, mci.Image, container_module.UploadVersion)
+ if err != nil && !errors.Is(err, packages_model.ErrPackageNotExist) {
return err
}
for _, ref := range blobReferences {
- if err := createFileFromBlobReference(ctx, pv, uploadVersion, ref); err != nil {
+ if _, err = createFileFromBlobReference(ctx, pv, uploadVersion, ref); err != nil {
return err
}
}
+ txRet.pv = pv
+ txRet.pb, txRet.created, txRet.digest, err = createManifestBlob(ctx, contentStore, mci, pv, buf)
+ return err
+ })
- pb, created, digest, err := createManifestBlob(ctx, mci, pv, buf)
- removeBlob := false
- defer func() {
- if removeBlob {
- contentStore := packages_module.NewContentStore()
- if err := contentStore.Delete(packages_module.BlobHash256Key(pb.HashSHA256)); err != nil {
- log.Error("Error deleting package blob from content store: %v", err)
- }
- }
- }()
- if err != nil {
- removeBlob = created
- return err
- }
-
- if err := committer.Commit(); err != nil {
- removeBlob = created
- return err
- }
-
- if err := notifyPackageCreate(ctx, mci.Creator, pv); err != nil {
- return err
- }
-
- manifestDigest = digest
+ return handleCreateManifestResult(ctx, err, mci, contentStore, &txRet)
+}
- return nil
- }()
- if err != nil {
+func processOciImageIndex(ctx context.Context, mci *manifestCreationInfo, buf *packages_module.HashedBuffer) (manifestDigest string, errRet error) {
+ var index oci.Index
+ if err := json.NewDecoder(buf).Decode(&index); err != nil {
+ return "", err
+ }
+ if _, err := buf.Seek(0, io.SeekStart); err != nil {
return "", err
}
- return manifestDigest, nil
-}
-
-func processImageManifestIndex(ctx context.Context, mci *manifestCreationInfo, buf *packages_module.HashedBuffer) (string, error) {
- manifestDigest := ""
-
- err := func() error {
- var index oci.Index
- if err := json.NewDecoder(buf).Decode(&index); err != nil {
- return err
- }
-
- if _, err := buf.Seek(0, io.SeekStart); err != nil {
- return err
- }
-
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
+ contentStore := packages_module.NewContentStore()
+ var txRet processManifestTxRet
+ err := db.WithTx(ctx, func(ctx context.Context) (err error) {
metadata := &container_module.Metadata{
Type: container_module.TypeOCI,
Manifests: make([]*container_module.Manifest, 0, len(index.Manifests)),
}
for _, manifest := range index.Manifests {
- if !isImageManifestMediaType(manifest.MediaType) {
+ if !container_module.IsMediaTypeImageManifest(manifest.MediaType) {
return errManifestInvalid
}
@@ -265,50 +221,12 @@ func processImageManifestIndex(ctx context.Context, mci *manifestCreationInfo, b
return err
}
- pb, created, digest, err := createManifestBlob(ctx, mci, pv, buf)
- removeBlob := false
- defer func() {
- if removeBlob {
- contentStore := packages_module.NewContentStore()
- if err := contentStore.Delete(packages_module.BlobHash256Key(pb.HashSHA256)); err != nil {
- log.Error("Error deleting package blob from content store: %v", err)
- }
- }
- }()
- if err != nil {
- removeBlob = created
- return err
- }
-
- if err := committer.Commit(); err != nil {
- removeBlob = created
- return err
- }
-
- if err := notifyPackageCreate(ctx, mci.Creator, pv); err != nil {
- return err
- }
-
- manifestDigest = digest
-
- return nil
- }()
- if err != nil {
- return "", err
- }
-
- return manifestDigest, nil
-}
-
-func notifyPackageCreate(ctx context.Context, doer *user_model.User, pv *packages_model.PackageVersion) error {
- pd, err := packages_model.GetPackageDescriptor(ctx, pv)
- if err != nil {
+ txRet.pv = pv
+ txRet.pb, txRet.created, txRet.digest, err = createManifestBlob(ctx, contentStore, mci, pv, buf)
return err
- }
-
- notify_service.PackageCreate(ctx, doer, pd)
+ })
- return nil
+ return handleCreateManifestResult(ctx, err, mci, contentStore, &txRet)
}
func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, metadata *container_module.Metadata) (*packages_model.PackageVersion, error) {
@@ -349,24 +267,31 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
LowerVersion: strings.ToLower(mci.Reference),
MetadataJSON: string(metadataJSON),
}
- var pv *packages_model.PackageVersion
- if pv, err = packages_model.GetOrInsertVersion(ctx, _pv); err != nil {
+ pv, err := packages_model.GetOrInsertVersion(ctx, _pv)
+ if err != nil {
if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
log.Error("Error inserting package: %v", err)
return nil, err
}
- if err = packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
- return nil, err
- }
-
- // keep download count on overwrite
- _pv.DownloadCount = pv.DownloadCount
-
- if pv, err = packages_model.GetOrInsertVersion(ctx, _pv); err != nil {
- if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
- log.Error("Error inserting package: %v", err)
- return nil, err
+ if container_module.IsMediaTypeImageIndex(mci.MediaType) {
+ if pv.CreatedUnix.AsTime().Before(time.Now().Add(-24 * time.Hour)) {
+ if err = packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
+ return nil, err
+ }
+ // keep download count on overwriting
+ _pv.DownloadCount = pv.DownloadCount
+ if pv, err = packages_model.GetOrInsertVersion(ctx, _pv); err != nil {
+ if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
+ log.Error("Error inserting package: %v", err)
+ return nil, err
+ }
+ }
+ } else {
+ err = packages_model.UpdateVersion(ctx, &packages_model.PackageVersion{ID: pv.ID, MetadataJSON: _pv.MetadataJSON})
+ if err != nil {
+ return nil, err
+ }
}
}
}
@@ -376,14 +301,20 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
}
if mci.IsTagged {
- if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged, ""); err != nil {
- log.Error("Error setting package version property: %v", err)
+ if err = packages_model.InsertOrUpdateProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged, ""); err != nil {
+ return nil, err
+ }
+ } else {
+ if err = packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged); err != nil {
return nil, err
}
}
+
+ if err = packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference); err != nil {
+ return nil, err
+ }
for _, manifest := range metadata.Manifests {
- if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference, manifest.Digest); err != nil {
- log.Error("Error setting package version property: %v", err)
+ if _, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference, manifest.Digest); err != nil {
return nil, err
}
}
@@ -400,30 +331,31 @@ type blobReference struct {
IsLead bool
}
-func createFileFromBlobReference(ctx context.Context, pv, uploadVersion *packages_model.PackageVersion, ref *blobReference) error {
+func createFileFromBlobReference(ctx context.Context, pv, uploadVersion *packages_model.PackageVersion, ref *blobReference) (*packages_model.PackageFile, error) {
if ref.File.Blob.Size != ref.ExpectedSize {
- return errSizeInvalid
+ return nil, errSizeInvalid
}
if ref.Name == "" {
- ref.Name = strings.ToLower(fmt.Sprintf("sha256_%s", ref.File.Blob.HashSHA256))
+ ref.Name = strings.ToLower("sha256_" + ref.File.Blob.HashSHA256)
}
pf := &packages_model.PackageFile{
- VersionID: pv.ID,
- BlobID: ref.File.Blob.ID,
- Name: ref.Name,
- LowerName: ref.Name,
- IsLead: ref.IsLead,
+ VersionID: pv.ID,
+ BlobID: ref.File.Blob.ID,
+ Name: ref.Name,
+ LowerName: ref.Name,
+ CompositeKey: string(ref.Digest),
+ IsLead: ref.IsLead,
}
var err error
if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil {
if errors.Is(err, packages_model.ErrDuplicatePackageFile) {
// Skip this blob because the manifest contains the same filesystem layer multiple times.
- return nil
+ return pf, nil
}
log.Error("Error inserting package file: %v", err)
- return err
+ return nil, err
}
props := map[string]string{
@@ -433,21 +365,21 @@ func createFileFromBlobReference(ctx context.Context, pv, uploadVersion *package
for name, value := range props {
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeFile, pf.ID, name, value); err != nil {
log.Error("Error setting package file property: %v", err)
- return err
+ return nil, err
}
}
- // Remove the file from the blob upload version
+ // Remove the ref file (old file) from the blob upload version
if uploadVersion != nil && ref.File.File != nil && uploadVersion.ID == ref.File.File.VersionID {
if err := packages_service.DeletePackageFile(ctx, ref.File.File); err != nil {
- return err
+ return nil, err
}
}
- return nil
+ return pf, nil
}
-func createManifestBlob(ctx context.Context, mci *manifestCreationInfo, pv *packages_model.PackageVersion, buf *packages_module.HashedBuffer) (*packages_model.PackageBlob, bool, string, error) {
+func createManifestBlob(ctx context.Context, contentStore *packages_module.ContentStore, mci *manifestCreationInfo, pv *packages_model.PackageVersion, buf *packages_module.HashedBuffer) (_ *packages_model.PackageBlob, created bool, manifestDigest string, _ error) {
pb, exists, err := packages_model.GetOrInsertBlob(ctx, packages_service.NewPackageBlob(buf))
if err != nil {
log.Error("Error inserting package blob: %v", err)
@@ -456,29 +388,48 @@ func createManifestBlob(ctx context.Context, mci *manifestCreationInfo, pv *pack
// FIXME: Workaround to be removed in v1.20
// https://github.com/go-gitea/gitea/issues/19586
if exists {
- err = packages_module.NewContentStore().Has(packages_module.BlobHash256Key(pb.HashSHA256))
+ err = contentStore.Has(packages_module.BlobHash256Key(pb.HashSHA256))
if err != nil && (errors.Is(err, util.ErrNotExist) || errors.Is(err, os.ErrNotExist)) {
log.Debug("Package registry inconsistent: blob %s does not exist on file system", pb.HashSHA256)
exists = false
}
}
if !exists {
- contentStore := packages_module.NewContentStore()
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), buf, buf.Size()); err != nil {
log.Error("Error saving package blob in content store: %v", err)
return nil, false, "", err
}
}
- manifestDigest := digestFromHashSummer(buf)
- err = createFileFromBlobReference(ctx, pv, nil, &blobReference{
+ manifestDigest = digestFromHashSummer(buf)
+ pf, err := createFileFromBlobReference(ctx, pv, nil, &blobReference{
Digest: digest.Digest(manifestDigest),
MediaType: mci.MediaType,
- Name: container_model.ManifestFilename,
+ Name: container_module.ManifestFilename,
File: &packages_model.PackageFileDescriptor{Blob: pb},
ExpectedSize: pb.Size,
IsLead: true,
})
+ if err != nil {
+ return nil, false, "", err
+ }
+ oldManifestFiles, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
+ OwnerID: mci.Owner.ID,
+ PackageType: packages_model.TypeContainer,
+ VersionID: pv.ID,
+ Query: container_module.ManifestFilename,
+ })
+ if err != nil {
+ return nil, false, "", err
+ }
+ for _, oldManifestFile := range oldManifestFiles {
+ if oldManifestFile.ID != pf.ID && oldManifestFile.IsLead {
+ err = packages_model.UpdateFile(ctx, &packages_model.PackageFile{ID: oldManifestFile.ID, IsLead: false}, []string{"is_lead"})
+ if err != nil {
+ return nil, false, "", err
+ }
+ }
+ }
return pb, !exists, manifestDigest, err
}
diff --git a/routers/api/packages/cran/cran.go b/routers/api/packages/cran/cran.go
index 8a20072cb6..732acd215f 100644
--- a/routers/api/packages/cran/cran.go
+++ b/routers/api/packages/cran/cran.go
@@ -250,7 +250,7 @@ func downloadPackageFile(ctx *context.Context, opts *cran_model.SearchOptions) {
return
}
- s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
+ s, u, _, err := packages_service.OpenFileForDownload(ctx, pf)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go
index fec34c91a6..346f71fa5d 100644
--- a/routers/api/packages/debian/debian.go
+++ b/routers/api/packages/debian/debian.go
@@ -59,7 +59,7 @@ func GetRepositoryFile(ctx *context.Context) {
key += "|" + component + "|" + architecture
}
- s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion(
ctx,
pv,
&packages_service.PackageFileInfo{
@@ -106,7 +106,7 @@ func GetRepositoryFileByHash(ctx *context.Context) {
return
}
- s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
+ s, u, pf, err := packages_service.OpenFileForDownload(ctx, pfs[0])
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
@@ -210,7 +210,7 @@ func DownloadPackageFile(ctx *context.Context) {
name := ctx.PathParam("name")
version := ctx.PathParam("version")
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
diff --git a/routers/api/packages/generic/generic.go b/routers/api/packages/generic/generic.go
index 0b5daa7334..db7aeace50 100644
--- a/routers/api/packages/generic/generic.go
+++ b/routers/api/packages/generic/generic.go
@@ -31,7 +31,7 @@ func apiError(ctx *context.Context, status int, obj any) {
// DownloadPackageFile serves the specific generic package.
func DownloadPackageFile(ctx *context.Context) {
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
diff --git a/routers/api/packages/goproxy/goproxy.go b/routers/api/packages/goproxy/goproxy.go
index bde29df739..89ec86bce9 100644
--- a/routers/api/packages/goproxy/goproxy.go
+++ b/routers/api/packages/goproxy/goproxy.go
@@ -106,7 +106,7 @@ func DownloadPackageFile(ctx *context.Context) {
return
}
- s, u, _, err := packages_service.GetPackageFileStream(ctx, pfs[0])
+ s, u, _, err := packages_service.OpenFileForDownload(ctx, pfs[0])
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
diff --git a/routers/api/packages/helm/helm.go b/routers/api/packages/helm/helm.go
index fb12daaa46..39c34f4da4 100644
--- a/routers/api/packages/helm/helm.go
+++ b/routers/api/packages/helm/helm.go
@@ -122,7 +122,7 @@ func DownloadPackageFile(ctx *context.Context) {
return
}
- s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion(
ctx,
pvs[0],
&packages_service.PackageFileInfo{
diff --git a/routers/api/packages/maven/api.go b/routers/api/packages/maven/api.go
index 167fe42b56..ec6b9cfb0e 100644
--- a/routers/api/packages/maven/api.go
+++ b/routers/api/packages/maven/api.go
@@ -8,7 +8,6 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
- maven_module "code.gitea.io/gitea/modules/packages/maven"
)
// MetadataResponse https://maven.apache.org/ref/3.2.5/maven-repository-metadata/repository-metadata.html
@@ -22,7 +21,7 @@ type MetadataResponse struct {
}
// pds is expected to be sorted ascending by CreatedUnix
-func createMetadataResponse(pds []*packages_model.PackageDescriptor) *MetadataResponse {
+func createMetadataResponse(pds []*packages_model.PackageDescriptor, groupID, artifactID string) *MetadataResponse {
var release *packages_model.PackageDescriptor
versions := make([]string, 0, len(pds))
@@ -35,11 +34,9 @@ func createMetadataResponse(pds []*packages_model.PackageDescriptor) *MetadataRe
latest := pds[len(pds)-1]
- metadata := latest.Metadata.(*maven_module.Metadata)
-
resp := &MetadataResponse{
- GroupID: metadata.GroupID,
- ArtifactID: metadata.ArtifactID,
+ GroupID: groupID,
+ ArtifactID: artifactID,
Latest: latest.Version.Version,
Version: versions,
}
diff --git a/routers/api/packages/maven/maven.go b/routers/api/packages/maven/maven.go
index 4d04d4d1e9..40a8ff8242 100644
--- a/routers/api/packages/maven/maven.go
+++ b/routers/api/packages/maven/maven.go
@@ -84,16 +84,20 @@ func handlePackageFile(ctx *context.Context, serveContent bool) {
}
func serveMavenMetadata(ctx *context.Context, params parameters) {
- // /com/foo/project/maven-metadata.xml[.md5/.sha1/.sha256/.sha512]
-
- pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageName())
- if errors.Is(err, util.ErrNotExist) {
- pvs, err = packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageNameLegacy())
+ // path pattern: /com/foo/project/maven-metadata.xml[.md5/.sha1/.sha256/.sha512]
+ // in case there are legacy package names ("GroupID-ArtifactID") we need to check both, new packages always use ":" as separator("GroupID:ArtifactID")
+ pvsLegacy, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageNameLegacy())
+ if err != nil {
+ apiError(ctx, http.StatusInternalServerError, err)
+ return
}
+ pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageName())
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
+ pvs = append(pvsLegacy, pvs...)
+
if len(pvs) == 0 {
apiError(ctx, http.StatusNotFound, packages_model.ErrPackageNotExist)
return
@@ -110,7 +114,7 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
return pds[i].Version.CreatedUnix < pds[j].Version.CreatedUnix
})
- xmlMetadata, err := xml.Marshal(createMetadataResponse(pds))
+ xmlMetadata, err := xml.Marshal(createMetadataResponse(pds, params.GroupID, params.ArtifactID))
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
@@ -219,7 +223,7 @@ func servePackageFile(ctx *context.Context, params parameters, serveContent bool
return
}
- s, u, _, err := packages_service.GetPackageBlobStream(ctx, pf, pb, nil)
+ s, u, _, err := packages_service.OpenBlobForDownload(ctx, pf, pb, nil)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go
index 6ec46bcb36..1f09816d32 100644
--- a/routers/api/packages/npm/npm.go
+++ b/routers/api/packages/npm/npm.go
@@ -85,7 +85,7 @@ func DownloadPackageFile(ctx *context.Context) {
packageVersion := ctx.PathParam("version")
filename := ctx.PathParam("filename")
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
@@ -132,7 +132,7 @@ func DownloadPackageFileByName(ctx *context.Context) {
return
}
- s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion(
ctx,
pvs[0],
&packages_service.PackageFileInfo{
diff --git a/routers/api/packages/nuget/api_v2.go b/routers/api/packages/nuget/api_v2.go
index a726065ad0..801c60af13 100644
--- a/routers/api/packages/nuget/api_v2.go
+++ b/routers/api/packages/nuget/api_v2.go
@@ -246,21 +246,30 @@ type TypedValue[T any] struct {
}
type FeedEntryProperties struct {
- Version string `xml:"d:Version"`
- NormalizedVersion string `xml:"d:NormalizedVersion"`
Authors string `xml:"d:Authors"`
+ Copyright string `xml:"d:Copyright,omitempty"`
+ Created TypedValue[time.Time] `xml:"d:Created"`
Dependencies string `xml:"d:Dependencies"`
Description string `xml:"d:Description"`
- VersionDownloadCount TypedValue[int64] `xml:"d:VersionDownloadCount"`
+ DevelopmentDependency TypedValue[bool] `xml:"d:DevelopmentDependency"`
DownloadCount TypedValue[int64] `xml:"d:DownloadCount"`
- PackageSize TypedValue[int64] `xml:"d:PackageSize"`
- Created TypedValue[time.Time] `xml:"d:Created"`
+ ID string `xml:"d:Id"`
+ IconURL string `xml:"d:IconUrl,omitempty"`
+ Language string `xml:"d:Language,omitempty"`
LastUpdated TypedValue[time.Time] `xml:"d:LastUpdated"`
- Published TypedValue[time.Time] `xml:"d:Published"`
+ LicenseURL string `xml:"d:LicenseUrl,omitempty"`
+ MinClientVersion string `xml:"d:MinClientVersion,omitempty"`
+ NormalizedVersion string `xml:"d:NormalizedVersion"`
+ Owners string `xml:"d:Owners,omitempty"`
+ PackageSize TypedValue[int64] `xml:"d:PackageSize"`
ProjectURL string `xml:"d:ProjectUrl,omitempty"`
+ Published TypedValue[time.Time] `xml:"d:Published"`
ReleaseNotes string `xml:"d:ReleaseNotes,omitempty"`
RequireLicenseAcceptance TypedValue[bool] `xml:"d:RequireLicenseAcceptance"`
- Title string `xml:"d:Title"`
+ Tags string `xml:"d:Tags,omitempty"`
+ Title string `xml:"d:Title,omitempty"`
+ Version string `xml:"d:Version"`
+ VersionDownloadCount TypedValue[int64] `xml:"d:VersionDownloadCount"`
}
type FeedEntry struct {
@@ -353,21 +362,30 @@ func createEntry(l *linkBuilder, pd *packages_model.PackageDescriptor, withNames
Author: metadata.Authors,
Content: content,
Properties: &FeedEntryProperties{
- Version: pd.Version.Version,
- NormalizedVersion: pd.Version.Version,
Authors: metadata.Authors,
+ Copyright: metadata.Copyright,
+ Created: createdValue,
Dependencies: buildDependencyString(metadata),
Description: metadata.Description,
- VersionDownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount},
+ DevelopmentDependency: TypedValue[bool]{Type: "Edm.Boolean", Value: metadata.DevelopmentDependency},
DownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount},
- PackageSize: TypedValue[int64]{Type: "Edm.Int64", Value: pd.CalculateBlobSize()},
- Created: createdValue,
+ ID: pd.Package.Name,
+ IconURL: metadata.IconURL,
+ Language: metadata.Language,
LastUpdated: createdValue,
- Published: createdValue,
+ LicenseURL: metadata.LicenseURL,
+ MinClientVersion: metadata.MinClientVersion,
+ NormalizedVersion: pd.Version.Version,
+ Owners: metadata.Owners,
+ PackageSize: TypedValue[int64]{Type: "Edm.Int64", Value: pd.CalculateBlobSize()},
ProjectURL: metadata.ProjectURL,
+ Published: createdValue,
ReleaseNotes: metadata.ReleaseNotes,
RequireLicenseAcceptance: TypedValue[bool]{Type: "Edm.Boolean", Value: metadata.RequireLicenseAcceptance},
- Title: pd.Package.Name,
+ Tags: metadata.Tags,
+ Title: metadata.Title,
+ Version: pd.Version.Version,
+ VersionDownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount},
},
}
diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go
index 07a8de0a68..92d62d90b1 100644
--- a/routers/api/packages/nuget/nuget.go
+++ b/routers/api/packages/nuget/nuget.go
@@ -36,7 +36,7 @@ func apiError(ctx *context.Context, status int, obj any) {
})
}
-func xmlResponse(ctx *context.Context, status int, obj any) { //nolint:unparam
+func xmlResponse(ctx *context.Context, status int, obj any) { //nolint:unparam // status is always StatusOK
ctx.Resp.Header().Set("Content-Type", "application/atom+xml; charset=utf-8")
ctx.Resp.WriteHeader(status)
if _, err := ctx.Resp.Write([]byte(xml.Header)); err != nil {
@@ -405,7 +405,7 @@ func DownloadPackageFile(ctx *context.Context) {
packageVersion := ctx.PathParam("version")
filename := ctx.PathParam("filename")
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
@@ -488,7 +488,7 @@ func UploadPackage(ctx *context.Context) {
pv,
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
- Filename: strings.ToLower(fmt.Sprintf("%s.nuspec", np.ID)),
+ Filename: strings.ToLower(np.ID + ".nuspec"),
},
Data: nuspecBuf,
},
@@ -669,7 +669,7 @@ func DownloadSymbolFile(ctx *context.Context) {
return
}
- s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
+ s, u, pf, err := packages_service.OpenFileForDownload(ctx, pfs[0])
if err != nil {
if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err)
diff --git a/routers/api/packages/pub/pub.go b/routers/api/packages/pub/pub.go
index e7b07aefd0..4bd36e94b6 100644
--- a/routers/api/packages/pub/pub.go
+++ b/routers/api/packages/pub/pub.go
@@ -274,7 +274,7 @@ func DownloadPackageFile(ctx *context.Context) {
pf := pd.Files[0].File
- s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
+ s, u, _, err := packages_service.OpenFileForDownload(ctx, pf)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
diff --git a/routers/api/packages/pypi/pypi.go b/routers/api/packages/pypi/pypi.go
index 199f4e7478..9b5ae6c89d 100644
--- a/routers/api/packages/pypi/pypi.go
+++ b/routers/api/packages/pypi/pypi.go
@@ -82,7 +82,7 @@ func DownloadPackageFile(ctx *context.Context) {
packageVersion := ctx.PathParam("version")
filename := ctx.PathParam("filename")
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go
index a00a61c079..938c35341d 100644
--- a/routers/api/packages/rpm/rpm.go
+++ b/routers/api/packages/rpm/rpm.go
@@ -96,7 +96,7 @@ func GetRepositoryFile(ctx *context.Context) {
return
}
- s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion(
ctx,
pv,
&packages_service.PackageFileInfo{
@@ -220,7 +220,7 @@ func DownloadPackageFile(ctx *context.Context) {
name := ctx.PathParam("name")
version := ctx.PathParam("version")
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
diff --git a/routers/api/packages/rubygems/rubygems.go b/routers/api/packages/rubygems/rubygems.go
index de8c7ef3ed..774d5520fd 100644
--- a/routers/api/packages/rubygems/rubygems.go
+++ b/routers/api/packages/rubygems/rubygems.go
@@ -14,6 +14,7 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
+ "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
rubygems_module "code.gitea.io/gitea/modules/packages/rubygems"
@@ -177,7 +178,7 @@ func DownloadPackageFile(ctx *context.Context) {
return
}
- s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion(
ctx,
pvs[0],
&packages_service.PackageFileInfo{
@@ -309,7 +310,7 @@ func GetPackageInfo(ctx *context.Context) {
apiError(ctx, http.StatusNotFound, nil)
return
}
- infoContent, err := makePackageInfo(ctx, versions)
+ infoContent, err := makePackageInfo(ctx, versions, cache.NewEphemeralCache())
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
@@ -317,7 +318,7 @@ func GetPackageInfo(ctx *context.Context) {
ctx.PlainText(http.StatusOK, infoContent)
}
-// GetAllPackagesVersions returns a custom text based format containing information about all versions of all rubygems.
+// GetAllPackagesVersions returns a custom text-based format containing information about all versions of all rubygems.
// ref: https://guides.rubygems.org/rubygems-org-compact-index-api/
func GetAllPackagesVersions(ctx *context.Context) {
packages, err := packages_model.GetPackagesByType(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems)
@@ -326,6 +327,7 @@ func GetAllPackagesVersions(ctx *context.Context) {
return
}
+ ephemeralCache := cache.NewEphemeralCache()
out := &strings.Builder{}
out.WriteString("---\n")
for _, pkg := range packages {
@@ -338,7 +340,7 @@ func GetAllPackagesVersions(ctx *context.Context) {
continue
}
- info, err := makePackageInfo(ctx, versions)
+ info, err := makePackageInfo(ctx, versions, ephemeralCache)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
@@ -348,7 +350,14 @@ func GetAllPackagesVersions(ctx *context.Context) {
_, _ = fmt.Fprintf(out, "%s ", pkg.Name)
for i, v := range versions {
sep := util.Iif(i == len(versions)-1, "", ",")
- _, _ = fmt.Fprintf(out, "%s%s", v.Version, sep)
+ pd, err := packages_model.GetPackageDescriptorWithCache(ctx, v, ephemeralCache)
+ if errors.Is(err, util.ErrNotExist) {
+ continue
+ } else if err != nil {
+ apiError(ctx, http.StatusInternalServerError, err)
+ return
+ }
+ writePackageVersionForList(pd.Metadata, v.Version, sep, out)
}
_, _ = fmt.Fprintf(out, " %x\n", md5.Sum([]byte(info)))
}
@@ -356,6 +365,16 @@ func GetAllPackagesVersions(ctx *context.Context) {
ctx.PlainText(http.StatusOK, out.String())
}
+func writePackageVersionForList(metadata any, version, sep string, out *strings.Builder) {
+ if metadata, _ := metadata.(*rubygems_module.Metadata); metadata != nil && metadata.Platform != "" && metadata.Platform != "ruby" {
+ // VERSION_PLATFORM (see comment above in GetAllPackagesVersions)
+ _, _ = fmt.Fprintf(out, "%s_%s%s", version, metadata.Platform, sep)
+ } else {
+ // VERSION only
+ _, _ = fmt.Fprintf(out, "%s%s", version, sep)
+ }
+}
+
func writePackageVersionRequirements(prefix string, reqs []rubygems_module.VersionRequirement, out *strings.Builder) {
out.WriteString(prefix)
if len(reqs) == 0 {
@@ -367,11 +386,21 @@ func writePackageVersionRequirements(prefix string, reqs []rubygems_module.Versi
}
}
-func makePackageVersionDependency(ctx *context.Context, version *packages_model.PackageVersion) (string, error) {
+func writePackageVersionForDependency(version, platform string, out *strings.Builder) {
+ if platform != "" && platform != "ruby" {
+ // VERSION-PLATFORM (see comment below in makePackageVersionDependency)
+ _, _ = fmt.Fprintf(out, "%s-%s ", version, platform)
+ } else {
+ // VERSION only
+ _, _ = fmt.Fprintf(out, "%s ", version)
+ }
+}
+
+func makePackageVersionDependency(ctx *context.Context, version *packages_model.PackageVersion, c *cache.EphemeralCache) (string, error) {
// format: VERSION[-PLATFORM] [DEPENDENCY[,DEPENDENCY,...]]|REQUIREMENT[,REQUIREMENT,...]
// DEPENDENCY: GEM:CONSTRAINT[&CONSTRAINT]
// REQUIREMENT: KEY:VALUE (always contains "checksum")
- pd, err := packages_model.GetPackageDescriptor(ctx, version)
+ pd, err := packages_model.GetPackageDescriptorWithCache(ctx, version, c)
if err != nil {
return "", err
}
@@ -388,8 +417,7 @@ func makePackageVersionDependency(ctx *context.Context, version *packages_model.
}
buf := &strings.Builder{}
- buf.WriteString(version.Version)
- buf.WriteByte(' ')
+ writePackageVersionForDependency(version.Version, metadata.Platform, buf)
for i, dep := range metadata.RuntimeDependencies {
sep := util.Iif(i == 0, "", ",")
writePackageVersionRequirements(fmt.Sprintf("%s%s:", sep, dep.Name), dep.Version, buf)
@@ -404,10 +432,10 @@ func makePackageVersionDependency(ctx *context.Context, version *packages_model.
return buf.String(), nil
}
-func makePackageInfo(ctx *context.Context, versions []*packages_model.PackageVersion) (string, error) {
+func makePackageInfo(ctx *context.Context, versions []*packages_model.PackageVersion, c *cache.EphemeralCache) (string, error) {
ret := "---\n"
for _, v := range versions {
- dep, err := makePackageVersionDependency(ctx, v)
+ dep, err := makePackageVersionDependency(ctx, v, c)
if err != nil {
return "", err
}
diff --git a/routers/api/packages/rubygems/rubygems_test.go b/routers/api/packages/rubygems/rubygems_test.go
new file mode 100644
index 0000000000..a07e12a7d3
--- /dev/null
+++ b/routers/api/packages/rubygems/rubygems_test.go
@@ -0,0 +1,41 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package rubygems
+
+import (
+ "strings"
+ "testing"
+
+ rubygems_module "code.gitea.io/gitea/modules/packages/rubygems"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestWritePackageVersion(t *testing.T) {
+ buf := &strings.Builder{}
+
+ writePackageVersionForList(nil, "1.0", " ", buf)
+ assert.Equal(t, "1.0 ", buf.String())
+ buf.Reset()
+
+ writePackageVersionForList(&rubygems_module.Metadata{Platform: "ruby"}, "1.0", " ", buf)
+ assert.Equal(t, "1.0 ", buf.String())
+ buf.Reset()
+
+ writePackageVersionForList(&rubygems_module.Metadata{Platform: "linux"}, "1.0", " ", buf)
+ assert.Equal(t, "1.0_linux ", buf.String())
+ buf.Reset()
+
+ writePackageVersionForDependency("1.0", "", buf)
+ assert.Equal(t, "1.0 ", buf.String())
+ buf.Reset()
+
+ writePackageVersionForDependency("1.0", "ruby", buf)
+ assert.Equal(t, "1.0 ", buf.String())
+ buf.Reset()
+
+ writePackageVersionForDependency("1.0", "os", buf)
+ assert.Equal(t, "1.0-os ", buf.String())
+ buf.Reset()
+}
diff --git a/routers/api/packages/swift/swift.go b/routers/api/packages/swift/swift.go
index 4d7fb8b1a6..bf542f33a7 100644
--- a/routers/api/packages/swift/swift.go
+++ b/routers/api/packages/swift/swift.go
@@ -290,7 +290,24 @@ func DownloadManifest(ctx *context.Context) {
})
}
-// https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-6
+// formFileOptionalReadCloser returns (nil, nil) if the formKey is not present.
+func formFileOptionalReadCloser(ctx *context.Context, formKey string) (io.ReadCloser, error) {
+ multipartFile, _, err := ctx.Req.FormFile(formKey)
+ if err != nil && !errors.Is(err, http.ErrMissingFile) {
+ return nil, err
+ }
+ if multipartFile != nil {
+ return multipartFile, nil
+ }
+
+ content := ctx.Req.FormValue(formKey)
+ if content == "" {
+ return nil, nil
+ }
+ return io.NopCloser(strings.NewReader(content)), nil
+}
+
+// UploadPackageFile refers to https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-6
func UploadPackageFile(ctx *context.Context) {
packageScope := ctx.PathParam("scope")
packageName := ctx.PathParam("name")
@@ -304,9 +321,9 @@ func UploadPackageFile(ctx *context.Context) {
packageVersion := v.Core().String()
- file, _, err := ctx.Req.FormFile("source-archive")
- if err != nil {
- apiError(ctx, http.StatusBadRequest, err)
+ file, err := formFileOptionalReadCloser(ctx, "source-archive")
+ if file == nil || err != nil {
+ apiError(ctx, http.StatusBadRequest, "unable to read source-archive file")
return
}
defer file.Close()
@@ -318,10 +335,13 @@ func UploadPackageFile(ctx *context.Context) {
}
defer buf.Close()
- var mr io.Reader
- metadata := ctx.Req.FormValue("metadata")
- if metadata != "" {
- mr = strings.NewReader(metadata)
+ mr, err := formFileOptionalReadCloser(ctx, "metadata")
+ if err != nil {
+ apiError(ctx, http.StatusBadRequest, "unable to read metadata file")
+ return
+ }
+ if mr != nil {
+ defer mr.Close()
}
pck, err := swift_module.ParsePackage(buf, buf.Size(), mr)
@@ -409,7 +429,7 @@ func DownloadPackageFile(ctx *context.Context) {
pf := pd.Files[0].File
- s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
+ s, u, _, err := packages_service.OpenFileForDownload(ctx, pf)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
diff --git a/routers/api/packages/vagrant/vagrant.go b/routers/api/packages/vagrant/vagrant.go
index 3afaa5de1f..9eb67e5397 100644
--- a/routers/api/packages/vagrant/vagrant.go
+++ b/routers/api/packages/vagrant/vagrant.go
@@ -218,7 +218,7 @@ func UploadPackageFile(ctx *context.Context) {
}
func DownloadPackageFile(ctx *context.Context) {
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
diff --git a/routers/api/v1/activitypub/person.go b/routers/api/v1/activitypub/person.go
index 995a148f0b..0b0db011c6 100644
--- a/routers/api/v1/activitypub/person.go
+++ b/routers/api/v1/activitypub/person.go
@@ -41,14 +41,14 @@ func Person(ctx *context.APIContext) {
person.Name = ap.NaturalLanguageValuesNew()
err := person.Name.Set("en", ap.Content(ctx.ContextUser.FullName))
if err != nil {
- ctx.ServerError("Set Name", err)
+ ctx.APIErrorInternal(err)
return
}
person.PreferredUsername = ap.NaturalLanguageValuesNew()
err = person.PreferredUsername.Set("en", ap.Content(ctx.ContextUser.Name))
if err != nil {
- ctx.ServerError("Set PreferredUsername", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -68,14 +68,14 @@ func Person(ctx *context.APIContext) {
publicKeyPem, err := activitypub.GetPublicKey(ctx, ctx.ContextUser)
if err != nil {
- ctx.ServerError("GetPublicKey", err)
+ ctx.APIErrorInternal(err)
return
}
person.PublicKey.PublicKeyPem = publicKeyPem
binary, err := jsonld.WithContext(jsonld.IRI(ap.ActivityBaseURI), jsonld.IRI(ap.SecurityContextURI)).Marshal(person)
if err != nil {
- ctx.ServerError("MarshalJSON", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Resp.Header().Add("Content-Type", activitypub.ActivityStreamsContentType)
diff --git a/routers/api/v1/activitypub/reqsignature.go b/routers/api/v1/activitypub/reqsignature.go
index 853c3c0b59..4eff51782f 100644
--- a/routers/api/v1/activitypub/reqsignature.go
+++ b/routers/api/v1/activitypub/reqsignature.go
@@ -7,6 +7,7 @@ import (
"crypto"
"crypto/x509"
"encoding/pem"
+ "errors"
"fmt"
"io"
"net/http"
@@ -34,7 +35,7 @@ func getPublicKeyFromResponse(b []byte, keyID *url.URL) (p crypto.PublicKey, err
pubKeyPem := pubKey.PublicKeyPem
block, _ := pem.Decode([]byte(pubKeyPem))
if block == nil || block.Type != "PUBLIC KEY" {
- return nil, fmt.Errorf("could not decode publicKeyPem to PUBLIC KEY pem block type")
+ return nil, errors.New("could not decode publicKeyPem to PUBLIC KEY pem block type")
}
p, err = x509.ParsePKIXPublicKey(block.Bytes)
return p, err
@@ -89,9 +90,9 @@ func verifyHTTPSignatures(ctx *gitea_context.APIContext) (authenticated bool, er
func ReqHTTPSignature() func(ctx *gitea_context.APIContext) {
return func(ctx *gitea_context.APIContext) {
if authenticated, err := verifyHTTPSignatures(ctx); err != nil {
- ctx.ServerError("verifyHttpSignatures", err)
+ ctx.APIErrorInternal(err)
} else if !authenticated {
- ctx.Error(http.StatusForbidden, "reqSignature", "request signature verification failed")
+ ctx.APIError(http.StatusForbidden, "request signature verification failed")
}
}
}
diff --git a/routers/api/v1/admin/action.go b/routers/api/v1/admin/action.go
new file mode 100644
index 0000000000..2fbb8e1a95
--- /dev/null
+++ b/routers/api/v1/admin/action.go
@@ -0,0 +1,93 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package admin
+
+import (
+ "code.gitea.io/gitea/routers/api/v1/shared"
+ "code.gitea.io/gitea/services/context"
+)
+
+// ListWorkflowJobs Lists all jobs
+func ListWorkflowJobs(ctx *context.APIContext) {
+ // swagger:operation GET /admin/actions/jobs admin listAdminWorkflowJobs
+ // ---
+ // summary: Lists all jobs
+ // produces:
+ // - application/json
+ // parameters:
+ // - 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"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ shared.ListJobs(ctx, 0, 0, 0)
+}
+
+// ListWorkflowRuns Lists all runs
+func ListWorkflowRuns(ctx *context.APIContext) {
+ // swagger:operation GET /admin/actions/runs admin listAdminWorkflowRuns
+ // ---
+ // summary: Lists all runs
+ // produces:
+ // - application/json
+ // parameters:
+ // - 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: 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"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ shared.ListRuns(ctx, 0, 0)
+}
diff --git a/routers/api/v1/admin/adopt.go b/routers/api/v1/admin/adopt.go
index 55ea8c6758..c2efed7490 100644
--- a/routers/api/v1/admin/adopt.go
+++ b/routers/api/v1/admin/adopt.go
@@ -46,7 +46,7 @@ func ListUnadoptedRepositories(ctx *context.APIContext) {
}
repoNames, count, err := repo_service.ListUnadoptedRepositories(ctx, ctx.FormString("query"), &listOptions)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -86,33 +86,33 @@ func AdoptRepository(ctx *context.APIContext) {
ctxUser, err := user_model.GetUserByName(ctx, ownerName)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
// check not a repo
has, err := repo_model.IsRepositoryModelExist(ctx, ctxUser, repoName)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
isDir, err := util.IsDir(repo_model.RepoPath(ctxUser.Name, repoName))
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
if has || !isDir {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if _, err := repo_service.AdoptRepository(ctx, ctx.Doer, ctxUser, repo_service.CreateRepoOptions{
Name: repoName,
IsPrivate: true,
}); err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -148,31 +148,31 @@ func DeleteUnadoptedRepository(ctx *context.APIContext) {
ctxUser, err := user_model.GetUserByName(ctx, ownerName)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
// check not a repo
has, err := repo_model.IsRepositoryModelExist(ctx, ctxUser, repoName)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
isDir, err := util.IsDir(repo_model.RepoPath(ctxUser.Name, repoName))
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
if has || !isDir {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if err := repo_service.DeleteUnadoptedRepository(ctx, ctx.Doer, ctxUser, repoName); err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/admin/cron.go b/routers/api/v1/admin/cron.go
index 962e007776..b4dae11095 100644
--- a/routers/api/v1/admin/cron.go
+++ b/routers/api/v1/admin/cron.go
@@ -76,7 +76,7 @@ func PostCronTask(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
task := cron.GetTask(ctx.PathParam("task"))
if task == nil {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
task.Run()
diff --git a/routers/api/v1/admin/email.go b/routers/api/v1/admin/email.go
index 3de94d6868..ad078347a4 100644
--- a/routers/api/v1/admin/email.go
+++ b/routers/api/v1/admin/email.go
@@ -42,7 +42,7 @@ func GetAllEmails(ctx *context.APIContext) {
ListOptions: listOptions,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetAllEmails", err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/admin/hooks.go b/routers/api/v1/admin/hooks.go
index c812ca182d..a687541be5 100644
--- a/routers/api/v1/admin/hooks.go
+++ b/routers/api/v1/admin/hooks.go
@@ -51,22 +51,23 @@ func ListHooks(ctx *context.APIContext) {
// for compatibility the default value is true
isSystemWebhook := optional.Some(true)
typeValue := ctx.FormString("type")
- if typeValue == "default" {
+ switch typeValue {
+ case "default":
isSystemWebhook = optional.Some(false)
- } else if typeValue == "all" {
+ case "all":
isSystemWebhook = optional.None[bool]()
}
sysHooks, err := webhook.GetSystemOrDefaultWebhooks(ctx, isSystemWebhook)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetSystemWebhooks", err)
+ ctx.APIErrorInternal(err)
return
}
hooks := make([]*api.Hook, len(sysHooks))
for i, hook := range sysHooks {
h, err := webhook_service.ToHook(setting.AppURL+"/-/admin", hook)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "convert.ToHook", err)
+ ctx.APIErrorInternal(err)
return
}
hooks[i] = h
@@ -96,15 +97,15 @@ func GetHook(ctx *context.APIContext) {
hook, err := webhook.GetSystemOrDefaultWebhook(ctx, hookID)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetSystemOrDefaultWebhook", err)
+ ctx.APIErrorInternal(err)
}
return
}
h, err := webhook_service.ToHook("/-/admin/", hook)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "convert.ToHook", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, h)
@@ -186,9 +187,9 @@ func DeleteHook(ctx *context.APIContext) {
hookID := ctx.PathParamInt64("id")
if err := webhook.DeleteDefaultSystemWebhook(ctx, hookID); err != nil {
if errors.Is(err, util.ErrNotExist) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteDefaultSystemWebhook", err)
+ ctx.APIErrorInternal(err)
}
return
}
diff --git a/routers/api/v1/admin/org.go b/routers/api/v1/admin/org.go
index a5c299bbf0..c3473372f2 100644
--- a/routers/api/v1/admin/org.go
+++ b/routers/api/v1/admin/org.go
@@ -29,7 +29,7 @@ func CreateOrg(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of the user that will own the created organization
+ // description: username of the user who will own the created organization
// type: string
// required: true
// - name: organization
@@ -67,9 +67,9 @@ func CreateOrg(ctx *context.APIContext) {
db.IsErrNameReserved(err) ||
db.IsErrNameCharsNotAllowed(err) ||
db.IsErrNamePatternNotAllowed(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "CreateOrganization", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -101,7 +101,7 @@ func GetAllOrgs(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
- users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
+ users, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeOrganization,
OrderBy: db.SearchOrderByAlphabetically,
@@ -109,7 +109,7 @@ func GetAllOrgs(ctx *context.APIContext) {
Visible: []api.VisibleType{api.VisibleTypePublic, api.VisibleTypeLimited, api.VisibleTypePrivate},
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "SearchOrganizations", err)
+ ctx.APIErrorInternal(err)
return
}
orgs := make([]*api.Organization, len(users))
diff --git a/routers/api/v1/admin/repo.go b/routers/api/v1/admin/repo.go
index c119d5390a..12a78c9c4b 100644
--- a/routers/api/v1/admin/repo.go
+++ b/routers/api/v1/admin/repo.go
@@ -22,7 +22,7 @@ func CreateRepo(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of the user. This user will own the created repository
+ // description: username of the user who will own the created repository
// type: string
// required: true
// - name: repository
diff --git a/routers/api/v1/admin/runners.go b/routers/api/v1/admin/runners.go
index 329242d9f6..736c421229 100644
--- a/routers/api/v1/admin/runners.go
+++ b/routers/api/v1/admin/runners.go
@@ -24,3 +24,81 @@ func GetRegistrationToken(ctx *context.APIContext) {
shared.GetRegistrationToken(ctx, 0, 0)
}
+
+// CreateRegistrationToken returns the token to register global runners
+func CreateRegistrationToken(ctx *context.APIContext) {
+ // swagger:operation POST /admin/actions/runners/registration-token admin adminCreateRunnerRegistrationToken
+ // ---
+ // summary: Get an global actions runner registration token
+ // produces:
+ // - application/json
+ // parameters:
+ // responses:
+ // "200":
+ // "$ref": "#/responses/RegistrationToken"
+
+ shared.GetRegistrationToken(ctx, 0, 0)
+}
+
+// ListRunners get all runners
+func ListRunners(ctx *context.APIContext) {
+ // swagger:operation GET /admin/actions/runners admin getAdminRunners
+ // ---
+ // summary: Get all runners
+ // produces:
+ // - application/json
+ // responses:
+ // "200":
+ // "$ref": "#/definitions/ActionRunnersResponse"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.ListRunners(ctx, 0, 0)
+}
+
+// GetRunner get an global runner
+func GetRunner(ctx *context.APIContext) {
+ // swagger:operation GET /admin/actions/runners/{runner_id} admin getAdminRunner
+ // ---
+ // summary: Get an global runner
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: runner_id
+ // in: path
+ // description: id of the runner
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/definitions/ActionRunner"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.GetRunner(ctx, 0, 0, ctx.PathParamInt64("runner_id"))
+}
+
+// DeleteRunner delete an global runner
+func DeleteRunner(ctx *context.APIContext) {
+ // swagger:operation DELETE /admin/actions/runners/{runner_id} admin deleteAdminRunner
+ // ---
+ // summary: Delete an global runner
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: runner_id
+ // in: path
+ // description: id of the runner
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // description: runner has been deleted
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.DeleteRunner(ctx, 0, 0, ctx.PathParamInt64("runner_id"))
+}
diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go
index 21cb2f9ccd..8a267cc418 100644
--- a/routers/api/v1/admin/user.go
+++ b/routers/api/v1/admin/user.go
@@ -40,9 +40,9 @@ 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.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "auth.GetSourceByID", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -98,13 +98,13 @@ func CreateUser(ctx *context.APIContext) {
if u.LoginType == auth.Plain {
if len(form.Password) < setting.MinPasswordLength {
err := errors.New("PasswordIsRequired")
- ctx.Error(http.StatusBadRequest, "PasswordIsRequired", err)
+ ctx.APIError(http.StatusBadRequest, err)
return
}
if !password.IsComplexEnough(form.Password) {
err := errors.New("PasswordComplexity")
- ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
+ ctx.APIError(http.StatusBadRequest, err)
return
}
@@ -112,7 +112,7 @@ func CreateUser(ctx *context.APIContext) {
if password.IsErrIsPwnedRequest(err) {
log.Error(err.Error())
}
- ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
+ ctx.APIError(http.StatusBadRequest, errors.New("PasswordPwned"))
return
}
}
@@ -143,9 +143,9 @@ func CreateUser(ctx *context.APIContext) {
user_model.IsErrEmailCharIsNotSupported(err) ||
user_model.IsErrEmailInvalid(err) ||
db.IsErrNamePatternNotAllowed(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "CreateUser", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -175,7 +175,7 @@ func EditUser(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user to edit
+ // description: username of the user whose data is to be edited
// type: string
// required: true
// - name: body
@@ -204,13 +204,13 @@ func EditUser(ctx *context.APIContext) {
if err := user_service.UpdateAuth(ctx, ctx.ContextUser, authOpts); err != nil {
switch {
case errors.Is(err, password.ErrMinLength):
- ctx.Error(http.StatusBadRequest, "PasswordTooShort", fmt.Errorf("password must be at least %d characters", setting.MinPasswordLength))
+ ctx.APIError(http.StatusBadRequest, fmt.Errorf("password must be at least %d characters", setting.MinPasswordLength))
case errors.Is(err, password.ErrComplexity):
- ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
+ ctx.APIError(http.StatusBadRequest, err)
case errors.Is(err, password.ErrIsPwned), password.IsErrIsPwnedRequest(err):
- ctx.Error(http.StatusBadRequest, "PasswordIsPwned", err)
+ ctx.APIError(http.StatusBadRequest, err)
default:
- ctx.Error(http.StatusInternalServerError, "UpdateAuth", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -219,11 +219,11 @@ func EditUser(ctx *context.APIContext) {
if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, ctx.ContextUser, *form.Email); err != nil {
switch {
case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err):
- ctx.Error(http.StatusBadRequest, "EmailInvalid", err)
+ ctx.APIError(http.StatusBadRequest, err)
case user_model.IsErrEmailAlreadyUsed(err):
- ctx.Error(http.StatusBadRequest, "EmailUsed", err)
+ ctx.APIError(http.StatusBadRequest, err)
default:
- ctx.Error(http.StatusInternalServerError, "AddOrSetPrimaryEmailAddress", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -239,7 +239,7 @@ func EditUser(ctx *context.APIContext) {
Location: optional.FromPtr(form.Location),
Description: optional.FromPtr(form.Description),
IsActive: optional.FromPtr(form.Active),
- IsAdmin: optional.FromPtr(form.Admin),
+ IsAdmin: user_service.UpdateOptionFieldFromPtr(form.Admin),
Visibility: optional.FromNonDefault(api.VisibilityModes[form.Visibility]),
AllowGitHook: optional.FromPtr(form.AllowGitHook),
AllowImportLocal: optional.FromPtr(form.AllowImportLocal),
@@ -250,9 +250,9 @@ func EditUser(ctx *context.APIContext) {
if err := user_service.UpdateUser(ctx, ctx.ContextUser, opts); err != nil {
if user_model.IsErrDeleteLastAdminUser(err) {
- ctx.Error(http.StatusBadRequest, "LastAdmin", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else {
- ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -272,7 +272,7 @@ func DeleteUser(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user to delete
+ // description: username of the user to delete
// type: string
// required: true
// - name: purge
@@ -290,13 +290,13 @@ func DeleteUser(ctx *context.APIContext) {
// "$ref": "#/responses/validationError"
if ctx.ContextUser.IsOrganization() {
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("%s is an organization not a user", ctx.ContextUser.Name))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("%s is an organization not a user", ctx.ContextUser.Name))
return
}
// admin should not delete themself
if ctx.ContextUser.ID == ctx.Doer.ID {
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("you cannot delete yourself"))
+ ctx.APIError(http.StatusUnprocessableEntity, errors.New("you cannot delete yourself"))
return
}
@@ -305,9 +305,9 @@ func DeleteUser(ctx *context.APIContext) {
org_model.IsErrUserHasOrgs(err) ||
packages_model.IsErrUserOwnPackages(err) ||
user_model.IsErrDeleteLastAdminUser(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteUser", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -328,7 +328,7 @@ func CreatePublicKey(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of the user
+ // description: username of the user who is to receive a public key
// type: string
// required: true
// - name: key
@@ -358,7 +358,7 @@ func DeleteUserPublicKey(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose public key is to be deleted
// type: string
// required: true
// - name: id
@@ -377,11 +377,11 @@ func DeleteUserPublicKey(ctx *context.APIContext) {
if err := asymkey_service.DeletePublicKey(ctx, ctx.ContextUser, ctx.PathParamInt64("id")); err != nil {
if asymkey_model.IsErrKeyNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else if asymkey_model.IsErrKeyAccessDenied(err) {
- ctx.Error(http.StatusForbidden, "", "You do not have access to this key")
+ ctx.APIError(http.StatusForbidden, "You do not have access to this key")
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteUserPublicKey", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -405,7 +405,7 @@ func SearchUsers(ctx *context.APIContext) {
// format: int64
// - name: login_name
// in: query
- // description: user's login name to search for
+ // description: identifier of the user, provided by the external authenticator
// type: string
// - name: page
// in: query
@@ -423,7 +423,7 @@ func SearchUsers(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
- users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
+ users, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeIndividual,
LoginName: ctx.FormTrim("login_name"),
@@ -432,7 +432,7 @@ func SearchUsers(ctx *context.APIContext) {
ListOptions: listOptions,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "SearchUsers", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -456,7 +456,7 @@ func RenameUser(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: existing username of user
+ // description: current username of the user
// type: string
// required: true
// - name: body
@@ -473,30 +473,20 @@ func RenameUser(ctx *context.APIContext) {
// "$ref": "#/responses/validationError"
if ctx.ContextUser.IsOrganization() {
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("%s is an organization not a user", ctx.ContextUser.Name))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("%s is an organization not a user", ctx.ContextUser.Name))
return
}
- oldName := ctx.ContextUser.Name
newName := web.GetForm(ctx).(*api.RenameUserOption).NewName
- // Check if user name has been changed
+ // Check if username has been changed
if err := user_service.RenameUser(ctx, ctx.ContextUser, newName); err != nil {
- switch {
- case user_model.IsErrUserAlreadyExist(err):
- ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("form.username_been_taken"))
- case db.IsErrNameReserved(err):
- ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("user.form.name_reserved", newName))
- case db.IsErrNamePatternNotAllowed(err):
- ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("user.form.name_pattern_not_allowed", newName))
- case db.IsErrNameCharsNotAllowed(err):
- ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("user.form.name_chars_not_allowed", newName))
- default:
- ctx.ServerError("ChangeUserName", err)
+ if user_model.IsErrUserAlreadyExist(err) || db.IsErrNameReserved(err) || db.IsErrNamePatternNotAllowed(err) || db.IsErrNameCharsNotAllowed(err) {
+ ctx.APIError(http.StatusUnprocessableEntity, err)
+ } else {
+ ctx.APIErrorInternal(err)
}
return
}
-
- log.Trace("User name changed: %s -> %s", oldName, newName)
ctx.Status(http.StatusNoContent)
}
diff --git a/routers/api/v1/admin/user_badge.go b/routers/api/v1/admin/user_badge.go
index 99e20877fd..ce32f455b0 100644
--- a/routers/api/v1/admin/user_badge.go
+++ b/routers/api/v1/admin/user_badge.go
@@ -22,7 +22,7 @@ func ListUserBadges(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose badges are to be listed
// type: string
// required: true
// responses:
@@ -33,7 +33,7 @@ func ListUserBadges(ctx *context.APIContext) {
badges, maxResults, err := user_model.GetUserBadges(ctx, ctx.ContextUser)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserBadges", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -53,7 +53,7 @@ func AddUserBadges(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user to whom a badge is to be added
// type: string
// required: true
// - name: body
@@ -70,7 +70,7 @@ func AddUserBadges(ctx *context.APIContext) {
badges := prepareBadgesForReplaceOrAdd(*form)
if err := user_model.AddUserBadges(ctx, ctx.ContextUser, badges); err != nil {
- ctx.Error(http.StatusInternalServerError, "ReplaceUserBadges", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -87,7 +87,7 @@ func DeleteUserBadges(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose badge is to be deleted
// type: string
// required: true
// - name: body
@@ -106,7 +106,7 @@ func DeleteUserBadges(ctx *context.APIContext) {
badges := prepareBadgesForReplaceOrAdd(*form)
if err := user_model.RemoveUserBadges(ctx, ctx.ContextUser, badges); err != nil {
- ctx.Error(http.StatusInternalServerError, "ReplaceUserBadges", err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index b1a42a85e6..4a4bf12657 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -7,8 +7,6 @@
// This documentation describes the Gitea API.
//
// Schemes: https, http
-// BasePath: /api/v1
-// Version: {{AppVer | JSEscape}}
// License: MIT http://opensource.org/licenses/MIT
//
// Consumes:
@@ -66,6 +64,8 @@
package v1
import (
+ gocontext "context"
+ "errors"
"fmt"
"net/http"
"strings"
@@ -116,9 +116,9 @@ func sudo() func(ctx *context.APIContext) {
user, err := user_model.GetUserByName(ctx, sudo)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -154,12 +154,12 @@ func repoAssignment() func(ctx *context.APIContext) {
if redirectUserID, err := user_model.LookupUserRedirect(ctx, userName); err == nil {
context.RedirectToUser(ctx.Base, userName, redirectUserID)
} else if user_model.IsErrUserRedirectNotExist(err) {
- ctx.NotFound("GetUserByName", err)
+ ctx.APIErrorNotFound("GetUserByName", err)
} else {
- ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
+ ctx.APIErrorInternal(err)
}
} else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -175,12 +175,12 @@ func repoAssignment() func(ctx *context.APIContext) {
if err == nil {
context.RedirectToRepo(ctx.Base, redirectRepoID)
} else if repo_model.IsErrRedirectNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "LookupRepoRedirect", err)
+ ctx.APIErrorInternal(err)
}
} else {
- ctx.Error(http.StatusInternalServerError, "GetRepositoryByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -192,11 +192,11 @@ func repoAssignment() func(ctx *context.APIContext) {
taskID := ctx.Data["ActionsTaskID"].(int64)
task, err := actions_model.GetTaskByID(ctx, taskID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "actions_model.GetTaskByID", err)
+ ctx.APIErrorInternal(err)
return
}
if task.RepoID != repo.ID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -207,29 +207,52 @@ func repoAssignment() func(ctx *context.APIContext) {
}
if err := ctx.Repo.Repository.LoadUnits(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadUnits", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Repo.Permission.SetUnitsWithDefaultAccessMode(ctx.Repo.Repository.Units, ctx.Repo.Permission.AccessMode)
} else {
- ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
+ needTwoFactor, err := doerNeedTwoFactorAuth(ctx, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
+ ctx.APIErrorInternal(err)
return
}
+ if needTwoFactor {
+ ctx.Repo.Permission = access_model.PermissionNoAccess()
+ } else {
+ ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ }
}
- if !ctx.Repo.Permission.HasAnyUnitAccess() {
- ctx.NotFound()
+ if !ctx.Repo.Permission.HasAnyUnitAccessOrPublicAccess() {
+ ctx.APIErrorNotFound()
return
}
}
}
+func doerNeedTwoFactorAuth(ctx gocontext.Context, doer *user_model.User) (bool, error) {
+ if !setting.TwoFactorAuthEnforced {
+ return false, nil
+ }
+ if doer == nil {
+ return false, nil
+ }
+ has, err := auth_model.HasTwoFactorOrWebAuthn(ctx, doer.ID)
+ if err != nil {
+ return false, err
+ }
+ return !has, nil
+}
+
func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() {
- ctx.Error(http.StatusForbidden, "reqPackageAccess", "user should have specific permission or be a site admin")
+ ctx.APIError(http.StatusForbidden, "user should have specific permission or be a site admin")
return
}
}
@@ -250,41 +273,41 @@ func checkTokenPublicOnly() func(ctx *context.APIContext) {
switch {
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository):
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos")
+ ctx.APIError(http.StatusForbidden, "token scope is limited to public repos")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryIssue):
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public issues")
+ ctx.APIError(http.StatusForbidden, "token scope is limited to public issues")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization):
if ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
+ ctx.APIError(http.StatusForbidden, "token scope is limited to public orgs")
return
}
if ctx.ContextUser != nil && ctx.ContextUser.IsOrganization() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
+ ctx.APIError(http.StatusForbidden, "token scope is limited to public orgs")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryUser):
- if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public users")
+ if ctx.ContextUser != nil && ctx.ContextUser.IsTokenAccessAllowed() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
+ ctx.APIError(http.StatusForbidden, "token scope is limited to public users")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryActivityPub):
- if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public activitypub")
+ if ctx.ContextUser != nil && ctx.ContextUser.IsTokenAccessAllowed() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
+ ctx.APIError(http.StatusForbidden, "token scope is limited to public activitypub")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryNotification):
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public notifications")
+ ctx.APIError(http.StatusForbidden, "token scope is limited to public notifications")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryPackage):
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public packages")
+ ctx.APIError(http.StatusForbidden, "token scope is limited to public packages")
return
}
}
@@ -308,7 +331,7 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
// use the http method to determine the access level
requiredScopeLevel := auth_model.Read
- if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" || ctx.Req.Method == "DELETE" {
+ if ctx.Req.Method == http.MethodPost || ctx.Req.Method == http.MethodPut || ctx.Req.Method == http.MethodPatch || ctx.Req.Method == http.MethodDelete {
requiredScopeLevel = auth_model.Write
}
@@ -316,12 +339,12 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
requiredScopes := auth_model.GetRequiredScopes(requiredScopeLevel, requiredScopeCategories...)
allow, err := scope.HasScope(requiredScopes...)
if err != nil {
- ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error())
+ ctx.APIError(http.StatusForbidden, "checking scope failed: "+err.Error())
return
}
if !allow {
- ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s), required=%v, token scope=%v", requiredScopes, scope))
+ ctx.APIError(http.StatusForbidden, fmt.Sprintf("token does not have at least one of required scope(s), required=%v, token scope=%v", requiredScopes, scope))
return
}
@@ -330,7 +353,7 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
// check if scope only applies to public resources
publicOnly, err := scope.PublicOnly()
if err != nil {
- ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
+ ctx.APIError(http.StatusForbidden, "parsing public resource scope failed: "+err.Error())
return
}
@@ -350,14 +373,14 @@ func reqToken() func(ctx *context.APIContext) {
if ctx.IsSigned {
return
}
- ctx.Error(http.StatusUnauthorized, "reqToken", "token is required")
+ ctx.APIError(http.StatusUnauthorized, "token is required")
}
}
func reqExploreSignIn() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
- if (setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned {
- ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users")
+ if (setting.Service.RequireSignInViewStrict || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned {
+ ctx.APIError(http.StatusUnauthorized, "you must be signed in to search for users")
}
}
}
@@ -365,7 +388,7 @@ func reqExploreSignIn() func(ctx *context.APIContext) {
func reqUsersExploreEnabled() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if setting.Service.Explore.DisableUsersPage {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
}
}
}
@@ -376,7 +399,7 @@ func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
return
}
if !ctx.IsBasicAuth {
- ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth required")
+ ctx.APIError(http.StatusUnauthorized, "auth required")
return
}
}
@@ -386,7 +409,7 @@ func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
func reqSiteAdmin() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserSiteAdmin() {
- ctx.Error(http.StatusForbidden, "reqSiteAdmin", "user should be the site admin")
+ ctx.APIError(http.StatusForbidden, "user should be the site admin")
return
}
}
@@ -396,7 +419,7 @@ func reqSiteAdmin() func(ctx *context.APIContext) {
func reqOwner() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.Repo.IsOwner() && !ctx.IsUserSiteAdmin() {
- ctx.Error(http.StatusForbidden, "reqOwner", "user should be the owner of the repo")
+ ctx.APIError(http.StatusForbidden, "user should be the owner of the repo")
return
}
}
@@ -406,7 +429,7 @@ func reqOwner() func(ctx *context.APIContext) {
func reqSelfOrAdmin() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserSiteAdmin() && ctx.ContextUser != ctx.Doer {
- ctx.Error(http.StatusForbidden, "reqSelfOrAdmin", "doer should be the site admin or be same as the contextUser")
+ ctx.APIError(http.StatusForbidden, "doer should be the site admin or be same as the contextUser")
return
}
}
@@ -416,7 +439,7 @@ func reqSelfOrAdmin() func(ctx *context.APIContext) {
func reqAdmin() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
- ctx.Error(http.StatusForbidden, "reqAdmin", "user should be an owner or a collaborator with admin write of a repository")
+ ctx.APIError(http.StatusForbidden, "user should be an owner or a collaborator with admin write of a repository")
return
}
}
@@ -426,26 +449,17 @@ func reqAdmin() func(ctx *context.APIContext) {
func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
- ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo")
+ ctx.APIError(http.StatusForbidden, "user should have a permission to write to a repo")
return
}
}
}
-// reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin
-func reqRepoBranchWriter(ctx *context.APIContext) {
- options, ok := web.GetForm(ctx).(api.FileOptionInterface)
- if !ok || (!ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) {
- ctx.Error(http.StatusForbidden, "reqRepoBranchWriter", "user should have a permission to write to this branch")
- return
- }
-}
-
// 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() {
- ctx.Error(http.StatusForbidden, "reqRepoReader", "user should have specific read permission or be a repo admin or a site admin")
+ ctx.APIError(http.StatusForbidden, "user should have specific read permission or be a repo admin or a site admin")
return
}
}
@@ -455,7 +469,7 @@ func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) {
func reqAnyRepoReader() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.Repo.Permission.HasAnyUnitAccess() && !ctx.IsUserSiteAdmin() {
- ctx.Error(http.StatusForbidden, "reqAnyRepoReader", "user should have any permission to read repository or permissions of site admin")
+ ctx.APIError(http.StatusForbidden, "user should have any permission to read repository or permissions of site admin")
return
}
}
@@ -474,19 +488,20 @@ func reqOrgOwnership() func(ctx *context.APIContext) {
} else if ctx.Org.Team != nil {
orgID = ctx.Org.Team.OrgID
} else {
- ctx.Error(http.StatusInternalServerError, "", "reqOrgOwnership: unprepared context")
+ setting.PanicInDevOrTesting("reqOrgOwnership: unprepared context")
+ ctx.APIErrorInternal(errors.New("reqOrgOwnership: unprepared context"))
return
}
isOwner, err := organization.IsOrganizationOwner(ctx, orgID, ctx.Doer.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsOrganizationOwner", err)
+ ctx.APIErrorInternal(err)
return
} else if !isOwner {
if ctx.Org.Organization != nil {
- ctx.Error(http.StatusForbidden, "", "Must be an organization owner")
+ ctx.APIError(http.StatusForbidden, "Must be an organization owner")
} else {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
}
return
}
@@ -500,30 +515,31 @@ func reqTeamMembership() func(ctx *context.APIContext) {
return
}
if ctx.Org.Team == nil {
- ctx.Error(http.StatusInternalServerError, "", "reqTeamMembership: unprepared context")
+ setting.PanicInDevOrTesting("reqTeamMembership: unprepared context")
+ ctx.APIErrorInternal(errors.New("reqTeamMembership: unprepared context"))
return
}
orgID := ctx.Org.Team.OrgID
isOwner, err := organization.IsOrganizationOwner(ctx, orgID, ctx.Doer.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsOrganizationOwner", err)
+ ctx.APIErrorInternal(err)
return
} else if isOwner {
return
}
if isTeamMember, err := organization.IsTeamMember(ctx, orgID, ctx.Org.Team.ID, ctx.Doer.ID); err != nil {
- ctx.Error(http.StatusInternalServerError, "IsTeamMember", err)
+ ctx.APIErrorInternal(err)
return
} else if !isTeamMember {
isOrgMember, err := organization.IsOrganizationMember(ctx, orgID, ctx.Doer.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
+ ctx.APIErrorInternal(err)
} else if isOrgMember {
- ctx.Error(http.StatusForbidden, "", "Must be a team member")
+ ctx.APIError(http.StatusForbidden, "Must be a team member")
} else {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
}
return
}
@@ -543,18 +559,19 @@ func reqOrgMembership() func(ctx *context.APIContext) {
} else if ctx.Org.Team != nil {
orgID = ctx.Org.Team.OrgID
} else {
- ctx.Error(http.StatusInternalServerError, "", "reqOrgMembership: unprepared context")
+ setting.PanicInDevOrTesting("reqOrgMembership: unprepared context")
+ ctx.APIErrorInternal(errors.New("reqOrgMembership: unprepared context"))
return
}
if isMember, err := organization.IsOrganizationMember(ctx, orgID, ctx.Doer.ID); err != nil {
- ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
+ ctx.APIErrorInternal(err)
return
} else if !isMember {
if ctx.Org.Organization != nil {
- ctx.Error(http.StatusForbidden, "", "Must be an organization member")
+ ctx.APIError(http.StatusForbidden, "Must be an organization member")
} else {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
}
return
}
@@ -564,7 +581,7 @@ func reqOrgMembership() func(ctx *context.APIContext) {
func reqGitHook() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.Doer.CanEditGitHook() {
- ctx.Error(http.StatusForbidden, "", "must be allowed to edit Git hooks")
+ ctx.APIError(http.StatusForbidden, "must be allowed to edit Git hooks")
return
}
}
@@ -574,7 +591,17 @@ func reqGitHook() func(ctx *context.APIContext) {
func reqWebhooksEnabled() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if setting.DisableWebhooks {
- ctx.Error(http.StatusForbidden, "", "webhooks disabled by administrator")
+ ctx.APIError(http.StatusForbidden, "webhooks disabled by administrator")
+ return
+ }
+ }
+}
+
+// reqStarsEnabled requires Starring to be enabled in the config.
+func reqStarsEnabled() func(ctx *context.APIContext) {
+ return func(ctx *context.APIContext) {
+ if setting.Repository.DisableStars {
+ ctx.APIError(http.StatusForbidden, "stars disabled by administrator")
return
}
}
@@ -603,12 +630,12 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) {
if err == nil {
context.RedirectToUser(ctx.Base, ctx.PathParam("org"), redirectUserID)
} else if user_model.IsErrUserRedirectNotExist(err) {
- ctx.NotFound("GetOrgByName", err)
+ ctx.APIErrorNotFound("GetOrgByName", err)
} else {
- ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
+ ctx.APIErrorInternal(err)
}
} else {
- ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -619,9 +646,9 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) {
ctx.Org.Team, err = organization.GetTeamByID(ctx, ctx.PathParamInt64("teamid"))
if err != nil {
if organization.IsErrTeamNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetTeamById", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -647,7 +674,7 @@ func mustEnableIssues(ctx *context.APIContext) {
ctx.Repo.Permission)
}
}
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
}
@@ -670,7 +697,7 @@ func mustAllowPulls(ctx *context.APIContext) {
ctx.Repo.Permission)
}
}
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
}
@@ -696,28 +723,36 @@ func mustEnableIssuesOrPulls(ctx *context.APIContext) {
ctx.Repo.Permission)
}
}
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
}
func mustEnableWiki(ctx *context.APIContext) {
if !(ctx.Repo.CanRead(unit.TypeWiki)) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
}
+// FIXME: for consistency, maybe most mustNotBeArchived checks should be replaced with mustEnableEditor
func mustNotBeArchived(ctx *context.APIContext) {
if ctx.Repo.Repository.IsArchived {
- ctx.Error(http.StatusLocked, "RepoArchived", fmt.Errorf("%s is archived", ctx.Repo.Repository.LogString()))
+ ctx.APIError(http.StatusLocked, fmt.Errorf("%s is archived", ctx.Repo.Repository.FullName()))
+ 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()))
return
}
}
func mustEnableAttachments(ctx *context.APIContext) {
if !setting.Attachment.Enabled {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
}
@@ -728,7 +763,7 @@ func bind[T any](_ T) any {
theObj := new(T) // create a new form obj for every request but not use obj directly
errs := binding.Bind(ctx.Req, theObj)
if len(errs) > 0 {
- ctx.Error(http.StatusUnprocessableEntity, "validationError", fmt.Sprintf("%s: %s", errs[0].FieldNames, errs[0].Error()))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("%s: %s", errs[0].FieldNames, errs[0].Error()))
return
}
web.SetForm(ctx, theObj)
@@ -756,7 +791,7 @@ func apiAuth(authMethod auth.Method) func(*context.APIContext) {
return func(ctx *context.APIContext) {
ar, err := common.AuthShared(ctx.Base, nil, authMethod)
if err != nil {
- ctx.Error(http.StatusUnauthorized, "APIAuth", err)
+ ctx.APIError(http.StatusUnauthorized, err)
return
}
ctx.Doer = ar.Doer
@@ -830,15 +865,15 @@ 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 {
- case ctx.ContextUser.Visibility == api.VisibleTypePrivate:
+ switch ctx.ContextUser.Visibility {
+ case api.VisibleTypePrivate:
if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) {
- ctx.NotFound("Visit Project", nil)
+ ctx.APIErrorNotFound("Visit Project", nil)
return
}
- case ctx.ContextUser.Visibility == api.VisibleTypeLimited:
+ case api.VisibleTypeLimited:
if ctx.Doer == nil {
- ctx.NotFound("Visit Project", nil)
+ ctx.APIErrorNotFound("Visit Project", nil)
return
}
}
@@ -874,7 +909,7 @@ func Routes() *web.Router {
m.Use(apiAuth(buildAuthGroup()))
m.Use(verifyAuthWithOptions(&common.VerifyOptions{
- SignInRequired: setting.Service.RequireSignInView,
+ SignInRequired: setting.Service.RequireSignInViewStrict,
}))
addActionsRoutes := func(
@@ -900,8 +935,14 @@ func Routes() *web.Router {
})
m.Group("/runners", func() {
+ m.Get("", reqToken(), reqChecker, act.ListRunners)
m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken)
+ m.Post("/registration-token", reqToken(), reqChecker, act.CreateRegistrationToken)
+ m.Get("/{runner_id}", reqToken(), reqChecker, act.GetRunner)
+ m.Delete("/{runner_id}", reqToken(), reqChecker, act.DeleteRunner)
})
+ m.Get("/runs", reqToken(), reqChecker, act.ListWorkflowRuns)
+ m.Get("/jobs", reqToken(), reqChecker, act.ListWorkflowJobs)
})
}
@@ -931,7 +972,8 @@ func Routes() *web.Router {
// Misc (public accessible)
m.Group("", func() {
m.Get("/version", misc.Version)
- m.Get("/signing-key.gpg", misc.SigningKey)
+ m.Get("/signing-key.gpg", misc.SigningKeyGPG)
+ m.Get("/signing-key.pub", misc.SigningKeySSH)
m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown)
m.Post("/markdown/raw", reqToken(), misc.MarkdownRaw)
@@ -995,7 +1037,7 @@ func Routes() *web.Router {
m.Get("/{target}", user.CheckFollowing)
})
- m.Get("/starred", user.GetStarredRepos)
+ m.Get("/starred", reqStarsEnabled(), user.GetStarredRepos)
m.Get("/subscriptions", user.GetWatchedRepos)
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
@@ -1031,8 +1073,15 @@ func Routes() *web.Router {
})
m.Group("/runners", func() {
+ m.Get("", reqToken(), user.ListRunners)
m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
+ m.Post("/registration-token", reqToken(), user.CreateRegistrationToken)
+ m.Get("/{runner_id}", reqToken(), user.GetRunner)
+ m.Delete("/{runner_id}", reqToken(), user.DeleteRunner)
})
+
+ m.Get("/runs", reqToken(), user.ListWorkflowRuns)
+ m.Get("/jobs", reqToken(), user.ListWorkflowJobs)
})
m.Get("/followers", user.ListMyFollowers)
@@ -1086,7 +1135,7 @@ func Routes() *web.Router {
m.Put("", user.Star)
m.Delete("", user.Unstar)
}, repoAssignment(), checkTokenPublicOnly())
- }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
+ }, reqStarsEnabled(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
m.Get("/times", repo.ListMyTrackedTimes)
m.Get("/stopwatches", repo.GetStopwatches)
m.Get("/subscriptions", user.GetMyWatchedRepos)
@@ -1145,11 +1194,22 @@ func Routes() *web.Router {
m.Post("/accept", repo.AcceptTransfer)
m.Post("/reject", repo.RejectTransfer)
}, reqToken())
- addActionsRoutes(
- m,
- reqOwner(),
- repo.NewAction(),
- )
+
+ addActionsRoutes(m, reqOwner(), repo.NewAction()) // it adds the routes for secrets/variables and runner management
+
+ m.Group("/actions/workflows", func() {
+ m.Get("", repo.ActionsListRepositoryWorkflows)
+ m.Get("/{workflow_id}", repo.ActionsGetWorkflow)
+ 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)
+ }, context.ReferencesGitRepo(), reqToken(), reqRepoReader(unit.TypeActions))
+
+ m.Group("/actions/jobs", func() {
+ m.Get("/{job_id}", repo.GetWorkflowJob)
+ m.Get("/{job_id}/logs", repo.DownloadActionsRunJobLogs)
+ }, reqToken(), reqRepoReader(unit.TypeActions))
+
m.Group("/hooks/git", func() {
m.Combo("").Get(repo.ListGitHooks)
m.Group("/{id}", func() {
@@ -1187,7 +1247,7 @@ func Routes() *web.Router {
}, reqToken())
m.Get("/raw/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFile)
m.Get("/media/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFileOrLFS)
- m.Get("/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
+ m.Methods("HEAD,GET", "/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
m.Combo("/forks").Get(repo.ListForks).
Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
m.Post("/merge-upstream", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeCode), bind(api.MergeUpstreamRequest{}), repo.MergeUpstream)
@@ -1225,6 +1285,20 @@ func Routes() *web.Router {
}, reqToken(), reqAdmin())
m.Group("/actions", func() {
m.Get("/tasks", repo.ListActionTasks)
+ m.Group("/runs", func() {
+ m.Group("/{run}", func() {
+ m.Get("", repo.GetWorkflowRun)
+ m.Delete("", reqToken(), reqRepoWriter(unit.TypeActions), repo.DeleteActionRun)
+ m.Get("/jobs", repo.ListWorkflowRunJobs)
+ m.Get("/artifacts", repo.GetArtifactsOfRun)
+ })
+ })
+ m.Get("/artifacts", repo.GetArtifacts)
+ m.Group("/artifacts/{artifact_id}", func() {
+ m.Get("", repo.GetArtifact)
+ m.Delete("", reqRepoWriter(unit.TypeActions), repo.DeleteArtifact)
+ })
+ m.Get("/artifacts/{artifact_id}/zip", repo.DownloadArtifact)
}, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true))
m.Group("/keys", func() {
m.Combo("").Get(repo.ListDeployKeys).
@@ -1248,7 +1322,7 @@ func Routes() *web.Router {
m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown)
m.Post("/markdown/raw", reqToken(), misc.MarkdownRaw)
- m.Get("/stargazers", repo.ListStargazers)
+ m.Get("/stargazers", reqStarsEnabled(), repo.ListStargazers)
m.Get("/subscribers", repo.ListSubscribers)
m.Group("/subscription", func() {
m.Get("", user.IsWatching)
@@ -1349,18 +1423,29 @@ func Routes() *web.Router {
m.Get("/tags/{sha}", repo.GetAnnotatedTag)
m.Get("/notes/{sha}", repo.GetNote)
}, context.ReferencesGitRepo(true), reqRepoReader(unit.TypeCode))
- m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), mustNotBeArchived, repo.ApplyDiffPatch)
m.Group("/contents", func() {
m.Get("", repo.GetContentsList)
- m.Post("", reqToken(), bind(api.ChangeFilesOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.ChangeFiles)
m.Get("/*", repo.GetContents)
- m.Group("/*", func() {
- m.Post("", bind(api.CreateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.CreateFile)
- m.Put("", bind(api.UpdateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.UpdateFile)
- m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.DeleteFile)
- }, reqToken())
- }, reqRepoReader(unit.TypeCode))
- m.Get("/signing-key.gpg", misc.SigningKey)
+ m.Group("", func() {
+ // "change file" operations, need permission to write to the target branch provided by the form
+ m.Post("", bind(api.ChangeFilesOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.ChangeFiles)
+ m.Group("/*", func() {
+ m.Post("", bind(api.CreateFileOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.CreateFile)
+ m.Put("", bind(api.UpdateFileOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.UpdateFile)
+ m.Delete("", bind(api.DeleteFileOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.DeleteFile)
+ })
+ m.Post("/diffpatch", bind(api.ApplyDiffPatchFileOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.ApplyDiffPatch)
+ }, mustEnableEditor, reqToken())
+ }, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
+ m.Group("/contents-ext", func() {
+ m.Get("", repo.GetContentsExt)
+ m.Get("/*", repo.GetContentsExt)
+ }, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
+ m.Combo("/file-contents", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()).
+ Get(repo.GetFileContentsGet).
+ Post(bind(api.GetFilesOptions{}), repo.GetFileContentsPost) // the POST method requires "write" permission, so we also support "GET" method above
+ m.Get("/signing-key.gpg", misc.SigningKeyGPG)
+ m.Get("/signing-key.pub", misc.SigningKeySSH)
m.Group("/topics", func() {
m.Combo("").Get(repo.ListTopics).
Put(reqToken(), reqAdmin(), bind(api.RepoTopicOptions{}), repo.UpdateTopics)
@@ -1381,10 +1466,14 @@ func Routes() *web.Router {
m.Delete("", repo.DeleteAvatar)
}, reqAdmin(), reqToken())
- m.Get("/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), repo.DownloadArchive)
+ m.Methods("HEAD,GET", "/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), repo.DownloadArchive)
}, repoAssignment(), checkTokenPublicOnly())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
+ // Artifacts direct download endpoint authenticates via signed url
+ // it is protected by the "sig" parameter (to help to access private repo), so no need to use other middlewares
+ m.Get("/repos/{username}/{reponame}/actions/artifacts/{artifact_id}/zip/raw", repo.DownloadArtifactRaw)
+
// Notifications (requires notifications scope)
m.Group("/repos", func() {
m.Group("/{username}/{reponame}", func() {
@@ -1489,6 +1578,11 @@ func Routes() *web.Router {
Delete(reqToken(), reqAdmin(), repo.UnpinIssue)
m.Patch("/{position}", reqToken(), reqAdmin(), repo.MoveIssuePin)
})
+ m.Group("/lock", func() {
+ m.Combo("").
+ Put(bind(api.LockIssueOption{}), repo.LockIssue).
+ Delete(repo.UnlockIssue)
+ }, reqToken(), reqAdmin())
})
}, mustEnableIssuesOrPulls)
m.Group("/labels", func() {
@@ -1510,13 +1604,24 @@ func Routes() *web.Router {
// NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs
m.Group("/packages/{username}", func() {
- m.Group("/{type}/{name}/{version}", func() {
- m.Get("", reqToken(), packages.GetPackage)
- m.Delete("", reqToken(), reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage)
- m.Get("/files", reqToken(), packages.ListPackageFiles)
+ m.Group("/{type}/{name}", func() {
+ m.Get("/", packages.ListPackageVersions)
+
+ m.Group("/{version}", func() {
+ m.Get("", packages.GetPackage)
+ m.Delete("", reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage)
+ m.Get("/files", packages.ListPackageFiles)
+ })
+
+ m.Group("/-", func() {
+ m.Get("/latest", packages.GetLatestPackageVersion)
+ m.Post("/link/{repo_name}", reqPackageAccess(perm.AccessModeWrite), packages.LinkPackage)
+ m.Post("/unlink", reqPackageAccess(perm.AccessModeWrite), packages.UnlinkPackage)
+ })
})
- m.Get("/", reqToken(), packages.ListPackages)
- }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead), checkTokenPublicOnly())
+
+ m.Get("/", packages.ListPackages)
+ }, reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead), checkTokenPublicOnly())
// Organizations
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
@@ -1530,6 +1635,7 @@ func Routes() *web.Router {
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)
m.Group("/members", func() {
@@ -1644,6 +1750,16 @@ func Routes() *web.Router {
Patch(bind(api.EditHookOption{}), admin.EditHook).
Delete(admin.DeleteHook)
})
+ m.Group("/actions", func() {
+ m.Group("/runners", func() {
+ m.Get("", admin.ListRunners)
+ m.Post("/registration-token", admin.CreateRegistrationToken)
+ m.Get("/{runner_id}", admin.GetRunner)
+ m.Delete("/{runner_id}", admin.DeleteRunner)
+ })
+ m.Get("/runs", admin.ListWorkflowRuns)
+ m.Get("/jobs", admin.ListWorkflowJobs)
+ })
m.Group("/runners", func() {
m.Get("/registration-token", admin.GetRegistrationToken)
})
diff --git a/routers/api/v1/misc/gitignore.go b/routers/api/v1/misc/gitignore.go
index b0bf00a921..1ff2628ce8 100644
--- a/routers/api/v1/misc/gitignore.go
+++ b/routers/api/v1/misc/gitignore.go
@@ -48,7 +48,7 @@ func GetGitignoreTemplateInfo(ctx *context.APIContext) {
text, err := options.Gitignore(name)
if err != nil {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
diff --git a/routers/api/v1/misc/label_templates.go b/routers/api/v1/misc/label_templates.go
index f105b4c684..95c156c4ab 100644
--- a/routers/api/v1/misc/label_templates.go
+++ b/routers/api/v1/misc/label_templates.go
@@ -52,7 +52,7 @@ func GetLabelTemplate(ctx *context.APIContext) {
labels, err := repo_module.LoadTemplateLabelsByDisplayName(name)
if err != nil {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
diff --git a/routers/api/v1/misc/licenses.go b/routers/api/v1/misc/licenses.go
index d99b276232..12670afef9 100644
--- a/routers/api/v1/misc/licenses.go
+++ b/routers/api/v1/misc/licenses.go
@@ -37,7 +37,6 @@ func ListLicenseTemplates(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, response)
}
-// Returns information about a gitignore template
func GetLicenseTemplateInfo(ctx *context.APIContext) {
// swagger:operation GET /licenses/{name} miscellaneous getLicenseTemplateInfo
// ---
@@ -59,7 +58,7 @@ func GetLicenseTemplateInfo(ctx *context.APIContext) {
text, err := options.License(name)
if err != nil {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
diff --git a/routers/api/v1/misc/markup.go b/routers/api/v1/misc/markup.go
index 7b3633552f..909310b4c8 100644
--- a/routers/api/v1/misc/markup.go
+++ b/routers/api/v1/misc/markup.go
@@ -38,11 +38,11 @@ func Markup(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.MarkupOption)
if ctx.HasAPIError() {
- ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
+ ctx.APIError(http.StatusUnprocessableEntity, ctx.GetErrMsg())
return
}
- mode := util.Iif(form.Wiki, "wiki", form.Mode) //nolint:staticcheck
+ 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)
}
@@ -69,11 +69,11 @@ func Markdown(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.MarkdownOption)
if ctx.HasAPIError() {
- ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
+ ctx.APIError(http.StatusUnprocessableEntity, ctx.GetErrMsg())
return
}
- mode := util.Iif(form.Wiki, "wiki", form.Mode) //nolint:staticcheck
+ 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, "")
}
@@ -100,7 +100,7 @@ func MarkdownRaw(ctx *context.APIContext) {
// "$ref": "#/responses/validationError"
defer ctx.Req.Body.Close()
if err := markdown.RenderRaw(markup.NewRenderContext(ctx), ctx.Req.Body, ctx.Resp); err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
}
diff --git a/routers/api/v1/misc/markup_test.go b/routers/api/v1/misc/markup_test.go
index 6063e54cdc..38a1a3be9e 100644
--- a/routers/api/v1/misc/markup_test.go
+++ b/routers/api/v1/misc/markup_test.go
@@ -134,7 +134,7 @@ Here are some links to the most important topics. You can find the full list of
<h2 id="user-content-quick-links">Quick Links</h2>
<p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
<p><a href="http://localhost:3000/user2/repo1/wiki/Configuration" rel="nofollow">Configuration</a>
-<a href="http://localhost:3000/user2/repo1/wiki/raw/images/icon-bug.png" rel="nofollow"><img src="http://localhost:3000/user2/repo1/wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p>
+<a href="http://localhost:3000/user2/repo1/wiki/images/icon-bug.png" rel="nofollow"><img src="http://localhost:3000/user2/repo1/wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p>
`,
}
@@ -158,19 +158,19 @@ Here are some links to the most important topics. You can find the full list of
input := "[Link](test.md)\n![Image](image.png)"
testRenderMarkdown(t, "gfm", false, input, `<p><a href="http://localhost:3000/user2/repo1/src/branch/main/test.md" rel="nofollow">Link</a>
-<a href="http://localhost:3000/user2/repo1/media/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/user2/repo1/media/branch/main/image.png" alt="Image"/></a></p>
+<a href="http://localhost:3000/user2/repo1/src/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/user2/repo1/media/branch/main/image.png" alt="Image"/></a></p>
`, http.StatusOK)
testRenderMarkdown(t, "gfm", false, input, `<p><a href="http://localhost:3000/user2/repo1/src/branch/main/test.md" rel="nofollow">Link</a>
-<a href="http://localhost:3000/user2/repo1/media/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/user2/repo1/media/branch/main/image.png" alt="Image"/></a></p>
+<a href="http://localhost:3000/user2/repo1/src/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/user2/repo1/media/branch/main/image.png" alt="Image"/></a></p>
`, http.StatusOK)
testRenderMarkup(t, "gfm", false, "", input, `<p><a href="http://localhost:3000/user2/repo1/src/branch/main/test.md" rel="nofollow">Link</a>
-<a href="http://localhost:3000/user2/repo1/media/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/user2/repo1/media/branch/main/image.png" alt="Image"/></a></p>
+<a href="http://localhost:3000/user2/repo1/src/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/user2/repo1/media/branch/main/image.png" alt="Image"/></a></p>
`, http.StatusOK)
testRenderMarkup(t, "file", false, "path/new-file.md", input, `<p><a href="http://localhost:3000/user2/repo1/src/branch/main/path/test.md" rel="nofollow">Link</a>
-<a href="http://localhost:3000/user2/repo1/media/branch/main/path/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/user2/repo1/media/branch/main/path/image.png" alt="Image"/></a></p>
+<a href="http://localhost:3000/user2/repo1/src/branch/main/path/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/user2/repo1/media/branch/main/path/image.png" alt="Image"/></a></p>
`, http.StatusOK)
testRenderMarkup(t, "file", false, "path/test.unknown", "## Test", "unsupported file to render: \"path/test.unknown\"\n", http.StatusUnprocessableEntity)
diff --git a/routers/api/v1/misc/nodeinfo.go b/routers/api/v1/misc/nodeinfo.go
index 5973724782..ffe50e9fda 100644
--- a/routers/api/v1/misc/nodeinfo.go
+++ b/routers/api/v1/misc/nodeinfo.go
@@ -52,7 +52,7 @@ func NodeInfo(ctx *context.APIContext) {
}
if err := ctx.Cache.PutJSON(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
}
diff --git a/routers/api/v1/misc/signing.go b/routers/api/v1/misc/signing.go
index 24a46c1e70..db70e04b8f 100644
--- a/routers/api/v1/misc/signing.go
+++ b/routers/api/v1/misc/signing.go
@@ -4,15 +4,35 @@
package misc
import (
- "fmt"
- "net/http"
-
+ "code.gitea.io/gitea/modules/git"
asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/context"
)
-// SigningKey returns the public key of the default signing key if it exists
-func SigningKey(ctx *context.APIContext) {
+func getSigningKey(ctx *context.APIContext, expectedFormat string) {
+ // if the handler is in the repo's route group, get the repo's signing key
+ // otherwise, get the global signing key
+ path := ""
+ if ctx.Repo != nil && ctx.Repo.Repository != nil {
+ path = ctx.Repo.Repository.RepoPath()
+ }
+ content, format, err := asymkey_service.PublicSigningKey(ctx, path)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ if format == "" {
+ ctx.APIErrorNotFound("no signing key")
+ return
+ } else if format != expectedFormat {
+ ctx.APIErrorNotFound("signing key format is " + format)
+ return
+ }
+ _, _ = ctx.Write([]byte(content))
+}
+
+// SigningKeyGPG returns the public key of the default signing key if it exists
+func SigningKeyGPG(ctx *context.APIContext) {
// swagger:operation GET /signing-key.gpg miscellaneous getSigningKey
// ---
// summary: Get default signing-key.gpg
@@ -45,19 +65,42 @@ func SigningKey(ctx *context.APIContext) {
// description: "GPG armored public key"
// schema:
// type: string
+ getSigningKey(ctx, git.SigningKeyFormatOpenPGP)
+}
- path := ""
- if ctx.Repo != nil && ctx.Repo.Repository != nil {
- path = ctx.Repo.Repository.RepoPath()
- }
+// SigningKeySSH returns the public key of the default signing key if it exists
+func SigningKeySSH(ctx *context.APIContext) {
+ // swagger:operation GET /signing-key.pub miscellaneous getSigningKeySSH
+ // ---
+ // summary: Get default signing-key.pub
+ // produces:
+ // - text/plain
+ // responses:
+ // "200":
+ // description: "ssh public key"
+ // schema:
+ // type: string
- content, err := asymkey_service.PublicSigningKey(ctx, path)
- if err != nil {
- ctx.Error(http.StatusInternalServerError, "gpg export", err)
- return
- }
- _, err = ctx.Write([]byte(content))
- if err != nil {
- ctx.Error(http.StatusInternalServerError, "gpg export", fmt.Errorf("Error writing key content %w", err))
- }
+ // swagger:operation GET /repos/{owner}/{repo}/signing-key.pub repository repoSigningKeySSH
+ // ---
+ // summary: Get signing-key.pub for given repository
+ // produces:
+ // - text/plain
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // description: "ssh public key"
+ // schema:
+ // type: string
+ getSigningKey(ctx, git.SigningKeyFormatSSH)
}
diff --git a/routers/api/v1/notify/notifications.go b/routers/api/v1/notify/notifications.go
index 46b3c7f5e7..4e4c7dc6dd 100644
--- a/routers/api/v1/notify/notifications.go
+++ b/routers/api/v1/notify/notifications.go
@@ -28,7 +28,7 @@ func NewAvailable(ctx *context.APIContext) {
Status: []activities_model.NotificationStatus{activities_model.NotificationStatusUnread},
})
if err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "db.Count[activities_model.Notification]", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
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.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return nil
}
opts := &activities_model.FindNotificationOptions{
diff --git a/routers/api/v1/notify/repo.go b/routers/api/v1/notify/repo.go
index 1744426ee8..e87054e26c 100644
--- a/routers/api/v1/notify/repo.go
+++ b/routers/api/v1/notify/repo.go
@@ -110,18 +110,18 @@ func ListRepoNotifications(ctx *context.APIContext) {
totalCount, err := db.Count[activities_model.Notification](ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
nl, err := db.Find[activities_model.Notification](ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
err = activities_model.NotificationList(nl).LoadAttributes(ctx)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -183,7 +183,7 @@ func ReadRepoNotifications(ctx *context.APIContext) {
if len(qLastRead) > 0 {
tmpLastRead, err := time.Parse(time.RFC3339, qLastRead)
if err != nil {
- ctx.Error(http.StatusBadRequest, "Parse", err)
+ ctx.APIError(http.StatusBadRequest, err)
return
}
if !tmpLastRead.IsZero() {
@@ -203,7 +203,7 @@ func ReadRepoNotifications(ctx *context.APIContext) {
}
nl, err := db.Find[activities_model.Notification](ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -217,7 +217,7 @@ func ReadRepoNotifications(ctx *context.APIContext) {
for _, n := range nl {
notif, err := activities_model.SetNotificationStatus(ctx, n.ID, ctx.Doer, targetStatus)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
_ = notif.LoadAttributes(ctx)
diff --git a/routers/api/v1/notify/threads.go b/routers/api/v1/notify/threads.go
index 58a38cfd18..dd77e4aae4 100644
--- a/routers/api/v1/notify/threads.go
+++ b/routers/api/v1/notify/threads.go
@@ -42,7 +42,7 @@ func GetThread(ctx *context.APIContext) {
return
}
if err := n.LoadAttributes(ctx); err != nil && !issues_model.IsErrCommentNotExist(err) {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -90,11 +90,11 @@ func ReadThread(ctx *context.APIContext) {
notif, err := activities_model.SetNotificationStatus(ctx, n.ID, ctx.Doer, targetStatus)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
if err = notif.LoadAttributes(ctx); err != nil && !issues_model.IsErrCommentNotExist(err) {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusResetContent, convert.ToNotificationThread(ctx, notif))
@@ -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.Error(http.StatusNotFound, "GetNotificationByID", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
}
return nil
}
if n.UserID != ctx.Doer.ID && !ctx.Doer.IsAdmin {
- ctx.Error(http.StatusForbidden, "GetNotificationByID", fmt.Errorf("only user itself and admin are allowed to read/change this thread %d", n.ID))
+ ctx.APIError(http.StatusForbidden, fmt.Errorf("only user itself and admin are allowed to read/change this thread %d", n.ID))
return nil
}
return n
diff --git a/routers/api/v1/notify/user.go b/routers/api/v1/notify/user.go
index 879f484cce..3ebb678835 100644
--- a/routers/api/v1/notify/user.go
+++ b/routers/api/v1/notify/user.go
@@ -71,18 +71,18 @@ func ListNotifications(ctx *context.APIContext) {
totalCount, err := db.Count[activities_model.Notification](ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
nl, err := db.Find[activities_model.Notification](ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
err = activities_model.NotificationList(nl).LoadAttributes(ctx)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -133,7 +133,7 @@ func ReadNotifications(ctx *context.APIContext) {
if len(qLastRead) > 0 {
tmpLastRead, err := time.Parse(time.RFC3339, qLastRead)
if err != nil {
- ctx.Error(http.StatusBadRequest, "Parse", err)
+ ctx.APIError(http.StatusBadRequest, err)
return
}
if !tmpLastRead.IsZero() {
@@ -150,7 +150,7 @@ func ReadNotifications(ctx *context.APIContext) {
}
nl, err := db.Find[activities_model.Notification](ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -164,7 +164,7 @@ func ReadNotifications(ctx *context.APIContext) {
for _, n := range nl {
notif, err := activities_model.SetNotificationStatus(ctx, n.ID, ctx.Doer, targetStatus)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
_ = notif.LoadAttributes(ctx)
diff --git a/routers/api/v1/org/action.go b/routers/api/v1/org/action.go
index 199ee7d777..3ae5e60585 100644
--- a/routers/api/v1/org/action.go
+++ b/routers/api/v1/org/action.go
@@ -54,15 +54,16 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
apiSecrets := make([]*api.Secret, len(secrets))
for k, v := range secrets {
apiSecrets[k] = &api.Secret{
- Name: v.Name,
- Created: v.CreatedUnix.AsTime(),
+ Name: v.Name,
+ Description: v.Description,
+ Created: v.CreatedUnix.AsTime(),
}
}
@@ -106,14 +107,14 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
- _, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("secretname"), opt.Data)
+ _, 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.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else if errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusNotFound, "CreateOrUpdateSecret", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -156,11 +157,11 @@ 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.Error(http.StatusBadRequest, "DeleteSecret", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else if errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusNotFound, "DeleteSecret", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteSecret", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -189,6 +190,27 @@ func (Action) GetRegistrationToken(ctx *context.APIContext) {
shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
}
+// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
+// CreateRegistrationToken returns the token to register org runners
+func (Action) CreateRegistrationToken(ctx *context.APIContext) {
+ // swagger:operation POST /orgs/{org}/actions/runners/registration-token organization orgCreateRunnerRegistrationToken
+ // ---
+ // summary: Get an organization's actions runner registration token
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/RegistrationToken"
+
+ shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
+}
+
// ListVariables list org-level variables
func (Action) ListVariables(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
@@ -223,17 +245,18 @@ func (Action) ListVariables(ctx *context.APIContext) {
ListOptions: utils.GetListOptions(ctx),
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FindVariables", err)
+ ctx.APIErrorInternal(err)
return
}
variables := make([]*api.ActionVariable, len(vars))
for i, v := range vars {
variables[i] = &api.ActionVariable{
- OwnerID: v.OwnerID,
- RepoID: v.RepoID,
- Name: v.Name,
- Data: v.Data,
+ OwnerID: v.OwnerID,
+ RepoID: v.RepoID,
+ Name: v.Name,
+ Data: v.Data,
+ Description: v.Description,
}
}
@@ -273,18 +296,19 @@ func (Action) GetVariable(ctx *context.APIContext) {
})
if err != nil {
if errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusNotFound, "GetVariable", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetVariable", err)
+ ctx.APIErrorInternal(err)
}
return
}
variable := &api.ActionVariable{
- OwnerID: v.OwnerID,
- RepoID: v.RepoID,
- Name: v.Name,
- Data: v.Data,
+ OwnerID: v.OwnerID,
+ RepoID: v.RepoID,
+ Name: v.Name,
+ Data: v.Data,
+ Description: v.Description,
}
ctx.JSON(http.StatusOK, variable)
@@ -322,11 +346,11 @@ 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.Error(http.StatusBadRequest, "DeleteVariableByName", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else if errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusNotFound, "DeleteVariableByName", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -360,13 +384,13 @@ func (Action) CreateVariable(ctx *context.APIContext) {
// "$ref": "#/definitions/CreateVariableOption"
// responses:
// "201":
- // description: response when creating an org-level variable
- // "204":
- // description: response when creating an org-level variable
+ // description: successfully created the org-level variable
// "400":
// "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
+ // "409":
+ // description: variable name already exists.
+ // "500":
+ // "$ref": "#/responses/error"
opt := web.GetForm(ctx).(*api.CreateVariableOption)
@@ -378,24 +402,24 @@ func (Action) CreateVariable(ctx *context.APIContext) {
Name: variableName,
})
if err != nil && !errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusInternalServerError, "GetVariable", err)
+ ctx.APIErrorInternal(err)
return
}
if v != nil && v.ID > 0 {
- ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
+ ctx.APIError(http.StatusConflict, util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
return
}
- if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value); err != nil {
+ if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value, opt.Description); err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
- ctx.Error(http.StatusBadRequest, "CreateVariable", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else {
- ctx.Error(http.StatusInternalServerError, "CreateVariable", err)
+ ctx.APIErrorInternal(err)
}
return
}
- ctx.Status(http.StatusNoContent)
+ ctx.Status(http.StatusCreated)
}
// UpdateVariable update an org-level variable
@@ -440,9 +464,9 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
})
if err != nil {
if errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusNotFound, "GetVariable", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetVariable", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -450,11 +474,16 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
if opt.Name == "" {
opt.Name = ctx.PathParam("variablename")
}
- if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil {
+
+ v.Name = opt.Name
+ v.Data = opt.Value
+ v.Description = opt.Description
+
+ if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
- ctx.Error(http.StatusBadRequest, "UpdateVariable", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else {
- ctx.Error(http.StatusInternalServerError, "UpdateVariable", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -462,6 +491,175 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
ctx.Status(http.StatusNoContent)
}
+// ListRunners get org-level runners
+func (Action) ListRunners(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/actions/runners organization getOrgRunners
+ // ---
+ // summary: Get org-level runners
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/definitions/ActionRunnersResponse"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.ListRunners(ctx, ctx.Org.Organization.ID, 0)
+}
+
+// GetRunner get an org-level runner
+func (Action) GetRunner(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/actions/runners/{runner_id} organization getOrgRunner
+ // ---
+ // summary: Get an org-level runner
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: runner_id
+ // in: path
+ // description: id of the runner
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/definitions/ActionRunner"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.GetRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
+}
+
+// DeleteRunner delete an org-level runner
+func (Action) DeleteRunner(ctx *context.APIContext) {
+ // swagger:operation DELETE /orgs/{org}/actions/runners/{runner_id} organization deleteOrgRunner
+ // ---
+ // summary: Delete an org-level runner
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: runner_id
+ // in: path
+ // description: id of the runner
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // description: runner has been deleted
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.DeleteRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
+}
+
+func (Action) ListWorkflowJobs(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/actions/jobs organization getOrgWorkflowJobs
+ // ---
+ // summary: Get org-level workflow jobs
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // 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"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.ListJobs(ctx, ctx.Org.Organization.ID, 0, 0)
+}
+
+func (Action) ListWorkflowRuns(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/actions/runs organization getOrgWorkflowRuns
+ // ---
+ // summary: Get org-level workflow runs
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // 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: 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"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.ListRuns(ctx, ctx.Org.Organization.ID, 0)
+}
+
var _ actions_service.API = new(Action)
// Action implements actions_service.API
diff --git a/routers/api/v1/org/avatar.go b/routers/api/v1/org/avatar.go
index f11eb6c1cd..0eb771b2cd 100644
--- a/routers/api/v1/org/avatar.go
+++ b/routers/api/v1/org/avatar.go
@@ -39,13 +39,13 @@ func UpdateAvatar(ctx *context.APIContext) {
content, err := base64.StdEncoding.DecodeString(form.Image)
if err != nil {
- ctx.Error(http.StatusBadRequest, "DecodeImage", err)
+ ctx.APIError(http.StatusBadRequest, err)
return
}
err = user_service.UploadAvatar(ctx, ctx.Org.Organization.AsUser(), content)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "UploadAvatar", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -72,7 +72,7 @@ func DeleteAvatar(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
err := user_service.DeleteAvatar(ctx, ctx.Org.Organization.AsUser())
if err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteAvatar", err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/org/block.go b/routers/api/v1/org/block.go
index 69a5222a20..6b2f3dc615 100644
--- a/routers/api/v1/org/block.go
+++ b/routers/api/v1/org/block.go
@@ -47,7 +47,7 @@ func CheckUserBlock(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: user to check
+ // description: username of the user to check
// type: string
// required: true
// responses:
@@ -71,7 +71,7 @@ func BlockUser(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: user to block
+ // description: username of the user to block
// type: string
// required: true
// - name: note
@@ -101,7 +101,7 @@ func UnblockUser(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: user to unblock
+ // description: username of the user to unblock
// type: string
// required: true
// responses:
diff --git a/routers/api/v1/org/hook.go b/routers/api/v1/org/hook.go
index df82f4e5a2..f9e0684a97 100644
--- a/routers/api/v1/org/hook.go
+++ b/routers/api/v1/org/hook.go
@@ -78,7 +78,7 @@ func GetHook(ctx *context.APIContext) {
apiHook, err := webhook_service.ToHook(ctx.ContextUser.HomeLink(), hook)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, apiHook)
diff --git a/routers/api/v1/org/label.go b/routers/api/v1/org/label.go
index 2a9bd92e87..b5b70bdc7d 100644
--- a/routers/api/v1/org/label.go
+++ b/routers/api/v1/org/label.go
@@ -46,13 +46,13 @@ func ListLabels(ctx *context.APIContext) {
labels, err := issues_model.GetLabelsByOrgID(ctx, ctx.Org.Organization.ID, ctx.FormString("sort"), utils.GetListOptions(ctx))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetLabelsByOrgID", err)
+ ctx.APIErrorInternal(err)
return
}
count, err := issues_model.CountLabelsByOrgID(ctx, ctx.Org.Organization.ID)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -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.Error(http.StatusUnprocessableEntity, "Color", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
form.Color = color
@@ -103,7 +103,7 @@ func CreateLabel(ctx *context.APIContext) {
Description: form.Description,
}
if err := issues_model.NewLabel(ctx, label); err != nil {
- ctx.Error(http.StatusInternalServerError, "NewLabel", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -147,9 +147,9 @@ func GetLabel(ctx *context.APIContext) {
}
if err != nil {
if issues_model.IsErrOrgLabelNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetLabelByOrgID", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -193,9 +193,9 @@ func EditLabel(ctx *context.APIContext) {
l, err := issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrOrgLabelNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetLabelByRepoID", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -209,7 +209,7 @@ func EditLabel(ctx *context.APIContext) {
if form.Color != nil {
color, err := label.NormalizeColor(*form.Color)
if err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "Color", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
l.Color = color
@@ -219,7 +219,7 @@ func EditLabel(ctx *context.APIContext) {
}
l.SetArchived(form.IsArchived != nil && *form.IsArchived)
if err := issues_model.UpdateLabel(ctx, l); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -250,7 +250,7 @@ func DeleteLabel(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if err := issues_model.DeleteLabel(ctx, ctx.Org.Organization.ID, ctx.PathParamInt64("id")); err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteLabel", err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go
index 23c7da3d96..1c12b0cc94 100644
--- a/routers/api/v1/org/member.go
+++ b/routers/api/v1/org/member.go
@@ -8,6 +8,7 @@ import (
"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"
@@ -28,13 +29,13 @@ func listMembers(ctx *context.APIContext, isMember bool) {
count, err := organization.CountOrgMembers(ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
members, _, err := organization.FindOrgMembers(ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -82,7 +83,7 @@ func ListMembers(ctx *context.APIContext) {
if ctx.Doer != nil {
isMember, err = ctx.Org.Organization.IsOrgMember(ctx, ctx.Doer.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -132,7 +133,7 @@ func IsMember(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: username of the user
+ // description: username of the user to check for an organization membership
// type: string
// required: true
// responses:
@@ -150,20 +151,20 @@ func IsMember(ctx *context.APIContext) {
if ctx.Doer != nil {
userIsMember, err := ctx.Org.Organization.IsOrgMember(ctx, ctx.Doer.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
+ ctx.APIErrorInternal(err)
return
} else if userIsMember || ctx.Doer.IsAdmin {
userToCheckIsMember, err := ctx.Org.Organization.IsOrgMember(ctx, userToCheck.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
+ ctx.APIErrorInternal(err)
} else if userToCheckIsMember {
ctx.Status(http.StatusNoContent)
} else {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
}
return
} else if ctx.Doer.ID == userToCheck.ID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
}
@@ -185,7 +186,7 @@ func IsPublicMember(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: username of the user
+ // description: username of the user to check for a public organization membership
// type: string
// required: true
// responses:
@@ -200,13 +201,27 @@ func IsPublicMember(ctx *context.APIContext) {
}
is, err := organization.IsPublicMembership(ctx, ctx.Org.Organization.ID, userToCheck.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsPublicMembership", err)
+ ctx.APIErrorInternal(err)
return
}
if is {
ctx.Status(http.StatusNoContent)
} else {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
+ }
+}
+
+func checkCanChangeOrgUserStatus(ctx *context.APIContext, targetUser *user_model.User) {
+ // allow user themselves to change their status, and allow admins to change any user
+ if targetUser.ID == ctx.Doer.ID || ctx.Doer.IsAdmin {
+ return
+ }
+ // 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)
+ } else if !isOwner {
+ ctx.APIError(http.StatusForbidden, "Cannot change member visibility")
}
}
@@ -225,7 +240,7 @@ func PublicizeMember(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: username of the user
+ // description: username of the user whose membership is to be publicized
// type: string
// required: true
// responses:
@@ -240,13 +255,13 @@ func PublicizeMember(ctx *context.APIContext) {
if ctx.Written() {
return
}
- if userToPublicize.ID != ctx.Doer.ID {
- ctx.Error(http.StatusForbidden, "", "Cannot publicize another member")
+ checkCanChangeOrgUserStatus(ctx, userToPublicize)
+ if ctx.Written() {
return
}
err := organization.ChangeOrgUserStatus(ctx, ctx.Org.Organization.ID, userToPublicize.ID, true)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ChangeOrgUserStatus", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
@@ -267,7 +282,7 @@ func ConcealMember(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: username of the user
+ // description: username of the user whose membership is to be concealed
// type: string
// required: true
// responses:
@@ -282,13 +297,13 @@ func ConcealMember(ctx *context.APIContext) {
if ctx.Written() {
return
}
- if userToConceal.ID != ctx.Doer.ID {
- ctx.Error(http.StatusForbidden, "", "Cannot conceal another member")
+ checkCanChangeOrgUserStatus(ctx, userToConceal)
+ if ctx.Written() {
return
}
err := organization.ChangeOrgUserStatus(ctx, ctx.Org.Organization.ID, userToConceal.ID, false)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ChangeOrgUserStatus", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
@@ -309,7 +324,7 @@ func DeleteMember(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: username of the user
+ // description: username of the user to remove from the organization
// type: string
// required: true
// responses:
@@ -323,7 +338,7 @@ func DeleteMember(ctx *context.APIContext) {
return
}
if err := org_service.RemoveOrgUser(ctx, ctx.Org.Organization, member); err != nil {
- ctx.Error(http.StatusInternalServerError, "RemoveOrgUser", err)
+ ctx.APIErrorInternal(err)
}
ctx.Status(http.StatusNoContent)
}
diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go
index d65f922434..05744ba155 100644
--- a/routers/api/v1/org/org.go
+++ b/routers/api/v1/org/org.go
@@ -26,16 +26,14 @@ import (
func listUserOrgs(ctx *context.APIContext, u *user_model.User) {
listOptions := utils.GetListOptions(ctx)
- showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == u.ID)
-
opts := organization.FindOrgOptions{
- ListOptions: listOptions,
- UserID: u.ID,
- IncludePrivate: showPrivate,
+ ListOptions: listOptions,
+ UserID: u.ID,
+ IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, u),
}
orgs, maxResults, err := db.FindAndCount[organization.Organization](ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "db.FindAndCount[organization.Organization]", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -84,7 +82,7 @@ func ListUserOrgs(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose organizations are to be listed
// type: string
// required: true
// - name: page
@@ -114,7 +112,7 @@ func GetUserOrgsPermissions(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose permissions are to be obtained
// type: string
// required: true
// - name: org
@@ -138,14 +136,14 @@ func GetUserOrgsPermissions(ctx *context.APIContext) {
op := api.OrganizationPermissions{}
if !organization.HasOrgOrUserVisible(ctx, o, ctx.ContextUser) {
- ctx.NotFound("HasOrgOrUserVisible", nil)
+ ctx.APIErrorNotFound("HasOrgOrUserVisible", nil)
return
}
org := organization.OrgFromUser(o)
authorizeLevel, err := org.GetOrgUserMaxAuthorizeLevel(ctx, ctx.ContextUser.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetOrgUserAuthorizeLevel", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -164,7 +162,7 @@ func GetUserOrgsPermissions(ctx *context.APIContext) {
op.CanCreateRepository, err = org.CanCreateOrgRepo(ctx, ctx.ContextUser.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "CanCreateOrgRepo", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -201,7 +199,7 @@ func GetAll(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
- publicOrgs, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
+ publicOrgs, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
ListOptions: listOptions,
Type: user_model.UserTypeOrganization,
@@ -209,7 +207,7 @@ func GetAll(ctx *context.APIContext) {
Visible: vMode,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "SearchOrganizations", err)
+ ctx.APIErrorInternal(err)
return
}
orgs := make([]*api.Organization, len(publicOrgs))
@@ -245,7 +243,7 @@ func Create(ctx *context.APIContext) {
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateOrgOption)
if !ctx.Doer.CanCreateOrganization() {
- ctx.Error(http.StatusForbidden, "Create organization not allowed", nil)
+ ctx.APIError(http.StatusForbidden, nil)
return
}
@@ -271,9 +269,9 @@ func Create(ctx *context.APIContext) {
db.IsErrNameReserved(err) ||
db.IsErrNameCharsNotAllowed(err) ||
db.IsErrNamePatternNotAllowed(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "CreateOrganization", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -301,7 +299,7 @@ func Get(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if !organization.HasOrgOrUserVisible(ctx, ctx.Org.Organization.AsUser(), ctx.Doer) {
- ctx.NotFound("HasOrgOrUserVisible", nil)
+ ctx.APIErrorNotFound("HasOrgOrUserVisible", nil)
return
}
@@ -315,6 +313,44 @@ func Get(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, org)
}
+func Rename(ctx *context.APIContext) {
+ // swagger:operation POST /orgs/{org}/rename organization renameOrg
+ // ---
+ // summary: Rename an organization
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: existing org name
+ // type: string
+ // required: true
+ // - name: body
+ // in: body
+ // required: true
+ // schema:
+ // "$ref": "#/definitions/RenameOrgOption"
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "422":
+ // "$ref": "#/responses/validationError"
+
+ form := web.GetForm(ctx).(*api.RenameOrgOption)
+ orgUser := ctx.Org.Organization.AsUser()
+ if err := user_service.RenameUser(ctx, orgUser, form.NewName); err != nil {
+ if user_model.IsErrUserAlreadyExist(err) || db.IsErrNameReserved(err) || db.IsErrNamePatternNotAllowed(err) || db.IsErrNameCharsNotAllowed(err) {
+ ctx.APIError(http.StatusUnprocessableEntity, err)
+ } else {
+ ctx.APIErrorInternal(err)
+ }
+ return
+ }
+ ctx.Status(http.StatusNoContent)
+}
+
// Edit change an organization's information
func Edit(ctx *context.APIContext) {
// swagger:operation PATCH /orgs/{org} organization orgEdit
@@ -345,7 +381,7 @@ func Edit(ctx *context.APIContext) {
if form.Email != "" {
if err := user_service.ReplacePrimaryEmailAddress(ctx, ctx.Org.Organization.AsUser(), form.Email); err != nil {
- ctx.Error(http.StatusInternalServerError, "ReplacePrimaryEmailAddress", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -359,7 +395,7 @@ func Edit(ctx *context.APIContext) {
RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess),
}
if err := user_service.UpdateUser(ctx, ctx.Org.Organization.AsUser(), opts); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -386,7 +422,7 @@ func Delete(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if err := org.DeleteOrganization(ctx, ctx.Org.Organization, false); err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteOrganization", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
@@ -431,7 +467,7 @@ func ListOrgActivityFeeds(ctx *context.APIContext) {
org := organization.OrgFromUser(ctx.ContextUser)
isMember, err := org.IsOrgMember(ctx, ctx.Doer.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
+ ctx.APIErrorInternal(err)
return
}
includePrivate = isMember
@@ -450,7 +486,7 @@ func ListOrgActivityFeeds(ctx *context.APIContext) {
feeds, count, err := feed_service.GetFeeds(ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetFeeds", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.SetTotalCountHeader(count)
diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go
index 7f44f6ed95..1a1710750a 100644
--- a/routers/api/v1/org/team.go
+++ b/routers/api/v1/org/team.go
@@ -59,13 +59,13 @@ func ListTeams(ctx *context.APIContext) {
OrgID: ctx.Org.Organization.ID,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadTeams", err)
+ ctx.APIErrorInternal(err)
return
}
apiTeams, err := convert.ToTeams(ctx, teams, false)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ConvertToTeams", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -98,13 +98,13 @@ func ListUserTeams(ctx *context.APIContext) {
UserID: ctx.Doer.ID,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserTeams", err)
+ ctx.APIErrorInternal(err)
return
}
apiTeams, err := convert.ToTeams(ctx, teams, true)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ConvertToTeams", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -134,33 +134,25 @@ func GetTeam(ctx *context.APIContext) {
apiTeam, err := convert.ToTeam(ctx, ctx.Org.Team, true)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, apiTeam)
}
-func attachTeamUnits(team *organization.Team, units []string) {
+func attachTeamUnits(team *organization.Team, defaultAccessMode perm.AccessMode, units []string) {
unitTypes, _ := unit_model.FindUnitTypes(units...)
team.Units = make([]*organization.TeamUnit, 0, len(units))
for _, tp := range unitTypes {
team.Units = append(team.Units, &organization.TeamUnit{
OrgID: team.OrgID,
Type: tp,
- AccessMode: team.AccessMode,
+ AccessMode: defaultAccessMode,
})
}
}
-func convertUnitsMap(unitsMap map[string]string) map[unit_model.Type]perm.AccessMode {
- res := make(map[unit_model.Type]perm.AccessMode, len(unitsMap))
- for unitKey, p := range unitsMap {
- res[unit_model.TypeFromKey(unitKey)] = perm.ParseAccessMode(p)
- }
- return res
-}
-
func attachTeamUnitsMap(team *organization.Team, unitsMap map[string]string) {
team.Units = make([]*organization.TeamUnit, 0, len(unitsMap))
for unitKey, p := range unitsMap {
@@ -214,26 +206,24 @@ func CreateTeam(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateTeamOption)
- p := perm.ParseAccessMode(form.Permission)
- if p < perm.AccessModeAdmin && len(form.UnitsMap) > 0 {
- p = unit_model.MinUnitAccessMode(convertUnitsMap(form.UnitsMap))
- }
+ teamPermission := perm.ParseAccessMode(form.Permission, perm.AccessModeNone, perm.AccessModeAdmin)
team := &organization.Team{
OrgID: ctx.Org.Organization.ID,
Name: form.Name,
Description: form.Description,
IncludesAllRepositories: form.IncludesAllRepositories,
CanCreateOrgRepo: form.CanCreateOrgRepo,
- AccessMode: p,
+ AccessMode: teamPermission,
}
if team.AccessMode < perm.AccessModeAdmin {
if len(form.UnitsMap) > 0 {
attachTeamUnitsMap(team, form.UnitsMap)
} else if len(form.Units) > 0 {
- attachTeamUnits(team, form.Units)
+ unitPerm := perm.ParseAccessMode(form.Permission, perm.AccessModeRead, perm.AccessModeWrite)
+ attachTeamUnits(team, unitPerm, form.Units)
} else {
- ctx.Error(http.StatusInternalServerError, "getTeamUnits", errors.New("units permission should not be empty"))
+ ctx.APIErrorInternal(errors.New("units permission should not be empty"))
return
}
} else {
@@ -242,16 +232,16 @@ func CreateTeam(ctx *context.APIContext) {
if err := org_service.NewTeam(ctx, team); err != nil {
if organization.IsErrTeamAlreadyExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "NewTeam", err)
+ ctx.APIErrorInternal(err)
}
return
}
apiTeam, err := convert.ToTeam(ctx, team, true)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusCreated, apiTeam)
@@ -285,7 +275,7 @@ func EditTeam(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.EditTeamOption)
team := ctx.Org.Team
if err := team.LoadUnits(ctx); err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -304,15 +294,10 @@ func EditTeam(ctx *context.APIContext) {
isAuthChanged := false
isIncludeAllChanged := false
if !team.IsOwnerTeam() && len(form.Permission) != 0 {
- // Validate permission level.
- p := perm.ParseAccessMode(form.Permission)
- if p < perm.AccessModeAdmin && len(form.UnitsMap) > 0 {
- p = unit_model.MinUnitAccessMode(convertUnitsMap(form.UnitsMap))
- }
-
- if team.AccessMode != p {
+ teamPermission := perm.ParseAccessMode(form.Permission, perm.AccessModeNone, perm.AccessModeAdmin)
+ if team.AccessMode != teamPermission {
isAuthChanged = true
- team.AccessMode = p
+ team.AccessMode = teamPermission
}
if form.IncludesAllRepositories != nil {
@@ -325,20 +310,21 @@ func EditTeam(ctx *context.APIContext) {
if len(form.UnitsMap) > 0 {
attachTeamUnitsMap(team, form.UnitsMap)
} else if len(form.Units) > 0 {
- attachTeamUnits(team, form.Units)
+ unitPerm := perm.ParseAccessMode(form.Permission, perm.AccessModeRead, perm.AccessModeWrite)
+ attachTeamUnits(team, unitPerm, form.Units)
}
} else {
attachAdminTeamUnits(team)
}
if err := org_service.UpdateTeam(ctx, team, isAuthChanged, isIncludeAllChanged); err != nil {
- ctx.Error(http.StatusInternalServerError, "EditTeam", err)
+ ctx.APIErrorInternal(err)
return
}
apiTeam, err := convert.ToTeam(ctx, team)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, apiTeam)
@@ -363,7 +349,7 @@ func DeleteTeam(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if err := org_service.DeleteTeam(ctx, ctx.Org.Team); err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteTeam", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
@@ -399,10 +385,10 @@ func GetTeamMembers(ctx *context.APIContext) {
isMember, err := organization.IsOrganizationMember(ctx, ctx.Org.Team.OrgID, ctx.Doer.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
+ ctx.APIErrorInternal(err)
return
} else if !isMember && !ctx.Doer.IsAdmin {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -411,7 +397,7 @@ func GetTeamMembers(ctx *context.APIContext) {
TeamID: ctx.Org.Team.ID,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetTeamMembers", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -440,7 +426,7 @@ func GetTeamMember(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: username of the member to list
+ // description: username of the user whose data is to be listed
// type: string
// required: true
// responses:
@@ -456,10 +442,10 @@ func GetTeamMember(ctx *context.APIContext) {
teamID := ctx.PathParamInt64("teamid")
isTeamMember, err := organization.IsUserInTeams(ctx, u.ID, []int64{teamID})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsUserInTeams", err)
+ ctx.APIErrorInternal(err)
return
} else if !isTeamMember {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
ctx.JSON(http.StatusOK, convert.ToUser(ctx, u, ctx.Doer))
@@ -481,7 +467,7 @@ func AddTeamMember(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: username of the user to add
+ // description: username of the user to add to a team
// type: string
// required: true
// responses:
@@ -498,9 +484,9 @@ 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.Error(http.StatusForbidden, "AddTeamMember", err)
+ ctx.APIError(http.StatusForbidden, err)
} else {
- ctx.Error(http.StatusInternalServerError, "AddTeamMember", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -523,7 +509,7 @@ func RemoveTeamMember(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: username of the user to remove
+ // description: username of the user to remove from a team
// type: string
// required: true
// responses:
@@ -538,7 +524,7 @@ func RemoveTeamMember(ctx *context.APIContext) {
}
if err := org_service.RemoveTeamMember(ctx, ctx.Org.Team, u); err != nil {
- ctx.Error(http.StatusInternalServerError, "RemoveTeamMember", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
@@ -578,14 +564,14 @@ func GetTeamRepos(ctx *context.APIContext) {
TeamID: team.ID,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetTeamRepositories", err)
+ ctx.APIErrorInternal(err)
return
}
repos := make([]*api.Repository, len(teamRepos))
for i, repo := range teamRepos {
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
+ ctx.APIErrorInternal(err)
return
}
repos[i] = convert.ToRepo(ctx, repo, permission)
@@ -630,13 +616,13 @@ func GetTeamRepo(ctx *context.APIContext) {
}
if !organization.HasTeamRepo(ctx, ctx.Org.Team.OrgID, ctx.Org.Team.ID, repo.ID) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -648,9 +634,9 @@ func getRepositoryByParams(ctx *context.APIContext) *repo_model.Repository {
repo, err := repo_model.GetRepositoryByName(ctx, ctx.Org.Team.OrgID, ctx.PathParam("reponame"))
if err != nil {
if repo_model.IsErrRepoNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetRepositoryByName", err)
+ ctx.APIErrorInternal(err)
}
return nil
}
@@ -694,14 +680,14 @@ func AddTeamRepository(ctx *context.APIContext) {
return
}
if access, err := access_model.AccessLevel(ctx, ctx.Doer, repo); err != nil {
- ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
+ ctx.APIErrorInternal(err)
return
} else if access < perm.AccessModeAdmin {
- ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository")
+ ctx.APIError(http.StatusForbidden, "Must have admin-level access to the repository")
return
}
if err := repo_service.TeamAddRepository(ctx, ctx.Org.Team, repo); err != nil {
- ctx.Error(http.StatusInternalServerError, "TeamAddRepository", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
@@ -746,14 +732,14 @@ func RemoveTeamRepository(ctx *context.APIContext) {
return
}
if access, err := access_model.AccessLevel(ctx, ctx.Doer, repo); err != nil {
- ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
+ ctx.APIErrorInternal(err)
return
} else if access < perm.AccessModeAdmin {
- ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository")
+ ctx.APIError(http.StatusForbidden, "Must have admin-level access to the repository")
return
}
if err := repo_service.RemoveRepositoryFromTeam(ctx, ctx.Org.Team, repo.ID); err != nil {
- ctx.Error(http.StatusInternalServerError, "RemoveRepository", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
@@ -829,7 +815,7 @@ func SearchTeam(ctx *context.APIContext) {
apiTeams, err := convert.ToTeams(ctx, teams, false)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -885,7 +871,7 @@ func ListTeamActivityFeeds(ctx *context.APIContext) {
feeds, count, err := feed_service.GetFeeds(ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetFeeds", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.SetTotalCountHeader(count)
diff --git a/routers/api/v1/packages/package.go b/routers/api/v1/packages/package.go
index b38aa13167..41b7f2a43f 100644
--- a/routers/api/v1/packages/package.go
+++ b/routers/api/v1/packages/package.go
@@ -4,11 +4,14 @@
package packages
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"
@@ -53,37 +56,18 @@ func ListPackages(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
- packageType := ctx.FormTrim("type")
- query := ctx.FormTrim("q")
-
- pvs, count, err := packages.SearchVersions(ctx, &packages.PackageSearchOptions{
+ apiPackages, count, err := searchPackages(ctx, &packages.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
- Type: packages.Type(packageType),
- Name: packages.SearchValue{Value: query},
+ Type: packages.Type(ctx.FormTrim("type")),
+ Name: packages.SearchValue{Value: ctx.FormTrim("q")},
IsInternal: optional.Some(false),
Paginator: &listOptions,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "SearchVersions", err)
+ ctx.APIErrorInternal(err)
return
}
- pds, err := packages.GetPackageDescriptors(ctx, pvs)
- if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetPackageDescriptors", err)
- return
- }
-
- apiPackages := make([]*api.Package, 0, len(pds))
- for _, pd := range pds {
- apiPackage, err := convert.ToPackage(ctx, pd, ctx.Doer)
- if err != nil {
- ctx.Error(http.StatusInternalServerError, "Error converting package for api", err)
- return
- }
- apiPackages = append(apiPackages, apiPackage)
- }
-
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, apiPackages)
@@ -125,7 +109,7 @@ func GetPackage(ctx *context.APIContext) {
apiPackage, err := convert.ToPackage(ctx, ctx.Package.Descriptor, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "Error converting package for api", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -166,7 +150,7 @@ func DeletePackage(ctx *context.APIContext) {
err := packages_service.RemovePackageVersion(ctx, ctx.Doer, ctx.Package.Descriptor.Version)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "RemovePackageVersion", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
@@ -213,3 +197,260 @@ func ListPackageFiles(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, apiPackageFiles)
}
+
+// ListPackageVersions gets all versions of a package
+func ListPackageVersions(ctx *context.APIContext) {
+ // swagger:operation GET /packages/{owner}/{type}/{name} package listPackageVersions
+ // ---
+ // summary: Gets all versions of a package
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the package
+ // type: string
+ // required: true
+ // - name: type
+ // in: path
+ // description: type of the package
+ // type: string
+ // required: true
+ // - name: name
+ // in: path
+ // description: name of the package
+ // type: string
+ // required: true
+ // - 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/PackageList"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ listOptions := utils.GetListOptions(ctx)
+
+ apiPackages, count, err := searchPackages(ctx, &packages.PackageSearchOptions{
+ OwnerID: ctx.Package.Owner.ID,
+ Type: packages.Type(ctx.PathParam("type")),
+ Name: packages.SearchValue{Value: ctx.PathParam("name"), ExactMatch: true},
+ IsInternal: optional.Some(false),
+ Paginator: &listOptions,
+ })
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+
+ ctx.SetLinkHeader(int(count), listOptions.PageSize)
+ ctx.SetTotalCountHeader(count)
+ ctx.JSON(http.StatusOK, apiPackages)
+}
+
+// GetLatestPackageVersion gets the latest version of a package
+func GetLatestPackageVersion(ctx *context.APIContext) {
+ // swagger:operation GET /packages/{owner}/{type}/{name}/-/latest package getLatestPackageVersion
+ // ---
+ // summary: Gets the latest version of a package
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the package
+ // type: string
+ // required: true
+ // - name: type
+ // in: path
+ // description: type of the package
+ // type: string
+ // required: true
+ // - name: name
+ // in: path
+ // description: name of the package
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/Package"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ pvs, _, err := packages.SearchLatestVersions(ctx, &packages.PackageSearchOptions{
+ OwnerID: ctx.Package.Owner.ID,
+ Type: packages.Type(ctx.PathParam("type")),
+ Name: packages.SearchValue{Value: ctx.PathParam("name"), ExactMatch: true},
+ IsInternal: optional.Some(false),
+ })
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ if len(pvs) == 0 {
+ ctx.APIError(http.StatusNotFound, err)
+ return
+ }
+
+ pd, err := packages.GetPackageDescriptor(ctx, pvs[0])
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+
+ apiPackage, err := convert.ToPackage(ctx, pd, ctx.Doer)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+
+ ctx.JSON(http.StatusOK, apiPackage)
+}
+
+// LinkPackage sets a repository link for a package
+func LinkPackage(ctx *context.APIContext) {
+ // swagger:operation POST /packages/{owner}/{type}/{name}/-/link/{repo_name} package linkPackage
+ // ---
+ // summary: Link a package to a repository
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the package
+ // type: string
+ // required: true
+ // - name: type
+ // in: path
+ // description: type of the package
+ // type: string
+ // required: true
+ // - name: name
+ // in: path
+ // description: name of the package
+ // type: string
+ // required: true
+ // - name: repo_name
+ // in: path
+ // description: name of the repository to link.
+ // type: string
+ // required: true
+ // responses:
+ // "201":
+ // "$ref": "#/responses/empty"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ 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)
+ } else {
+ ctx.APIErrorInternal(err)
+ }
+ return
+ }
+
+ 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)
+ } else {
+ ctx.APIErrorInternal(err)
+ }
+ return
+ }
+
+ err = packages_service.LinkToRepository(ctx, pkg, repo, ctx.Doer)
+ if err != nil {
+ switch {
+ case errors.Is(err, util.ErrInvalidArgument):
+ ctx.APIError(http.StatusBadRequest, err)
+ case errors.Is(err, util.ErrPermissionDenied):
+ ctx.APIError(http.StatusForbidden, err)
+ default:
+ ctx.APIErrorInternal(err)
+ }
+ return
+ }
+ ctx.Status(http.StatusCreated)
+}
+
+// UnlinkPackage sets a repository link for a package
+func UnlinkPackage(ctx *context.APIContext) {
+ // swagger:operation POST /packages/{owner}/{type}/{name}/-/unlink package unlinkPackage
+ // ---
+ // summary: Unlink a package from a repository
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the package
+ // type: string
+ // required: true
+ // - name: type
+ // in: path
+ // description: type of the package
+ // type: string
+ // required: true
+ // - name: name
+ // in: path
+ // description: name of the package
+ // type: string
+ // required: true
+ // responses:
+ // "201":
+ // "$ref": "#/responses/empty"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ 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)
+ } else {
+ ctx.APIErrorInternal(err)
+ }
+ return
+ }
+
+ err = packages_service.UnlinkFromRepository(ctx, pkg, ctx.Doer)
+ if err != nil {
+ switch {
+ case errors.Is(err, util.ErrPermissionDenied):
+ ctx.APIError(http.StatusForbidden, err)
+ case errors.Is(err, util.ErrInvalidArgument):
+ ctx.APIError(http.StatusBadRequest, err)
+ default:
+ ctx.APIErrorInternal(err)
+ }
+ return
+ }
+ ctx.Status(http.StatusNoContent)
+}
+
+func searchPackages(ctx *context.APIContext, opts *packages.PackageSearchOptions) ([]*api.Package, int64, error) {
+ pvs, count, err := packages.SearchVersions(ctx, opts)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ pds, err := packages.GetPackageDescriptors(ctx, pvs)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ apiPackages := make([]*api.Package, 0, len(pds))
+ for _, pd := range pds {
+ apiPackage, err := convert.ToPackage(ctx, pd, ctx.Doer)
+ if err != nil {
+ return nil, 0, err
+ }
+ apiPackages = append(apiPackages, apiPackage)
+ }
+
+ return apiPackages, count, nil
+}
diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go
index d27e8d2427..a57db015f0 100644
--- a/routers/api/v1/repo/action.go
+++ b/routers/api/v1/repo/action.go
@@ -4,12 +4,25 @@
package repo
import (
+ go_context "context"
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/base64"
"errors"
+ "fmt"
"net/http"
+ "net/url"
+ "strconv"
+ "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"
+ "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"
@@ -19,6 +32,8 @@ import (
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
secret_service "code.gitea.io/gitea/services/secrets"
+
+ "github.com/nektos/act/pkg/model"
)
// ListActionsSecrets list an repo's actions secrets
@@ -62,15 +77,16 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
apiSecrets := make([]*api.Secret, len(secrets))
for k, v := range secrets {
apiSecrets[k] = &api.Secret{
- Name: v.Name,
- Created: v.CreatedUnix.AsTime(),
+ Name: v.Name,
+ Description: v.Description,
+ Created: v.CreatedUnix.AsTime(),
}
}
@@ -121,14 +137,14 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
- _, created, err := secret_service.CreateOrUpdateSecret(ctx, 0, repo.ID, ctx.PathParam("secretname"), opt.Data)
+ _, 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.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else if errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusNotFound, "CreateOrUpdateSecret", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -167,7 +183,7 @@ func (Action) DeleteSecret(ctx *context.APIContext) {
// required: true
// responses:
// "204":
- // description: delete one secret of the organization
+ // description: delete one secret of the repository
// "400":
// "$ref": "#/responses/error"
// "404":
@@ -178,11 +194,11 @@ 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.Error(http.StatusBadRequest, "DeleteSecret", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else if errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusNotFound, "DeleteSecret", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteSecret", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -226,18 +242,19 @@ func (Action) GetVariable(ctx *context.APIContext) {
})
if err != nil {
if errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusNotFound, "GetVariable", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetVariable", err)
+ ctx.APIErrorInternal(err)
}
return
}
variable := &api.ActionVariable{
- OwnerID: v.OwnerID,
- RepoID: v.RepoID,
- Name: v.Name,
- Data: v.Data,
+ OwnerID: v.OwnerID,
+ RepoID: v.RepoID,
+ Name: v.Name,
+ Data: v.Data,
+ Description: v.Description,
}
ctx.JSON(http.StatusOK, variable)
@@ -280,11 +297,11 @@ 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.Error(http.StatusBadRequest, "DeleteVariableByName", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else if errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusNotFound, "DeleteVariableByName", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -322,12 +339,12 @@ func (Action) CreateVariable(ctx *context.APIContext) {
// responses:
// "201":
// description: response when creating a repo-level variable
- // "204":
- // description: response when creating a repo-level variable
// "400":
// "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
+ // "409":
+ // description: variable name already exists.
+ // "500":
+ // "$ref": "#/responses/error"
opt := web.GetForm(ctx).(*api.CreateVariableOption)
@@ -339,24 +356,24 @@ func (Action) CreateVariable(ctx *context.APIContext) {
Name: variableName,
})
if err != nil && !errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusInternalServerError, "GetVariable", err)
+ ctx.APIErrorInternal(err)
return
}
if v != nil && v.ID > 0 {
- ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
+ ctx.APIError(http.StatusConflict, util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
return
}
- if _, err := actions_service.CreateVariable(ctx, 0, repoID, variableName, opt.Value); err != nil {
+ if _, err := actions_service.CreateVariable(ctx, 0, repoID, variableName, opt.Value, opt.Description); err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
- ctx.Error(http.StatusBadRequest, "CreateVariable", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else {
- ctx.Error(http.StatusInternalServerError, "CreateVariable", err)
+ ctx.APIErrorInternal(err)
}
return
}
- ctx.Status(http.StatusNoContent)
+ ctx.Status(http.StatusCreated)
}
// UpdateVariable update a repo-level variable
@@ -404,9 +421,9 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
})
if err != nil {
if errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusNotFound, "GetVariable", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetVariable", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -414,11 +431,16 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
if opt.Name == "" {
opt.Name = ctx.PathParam("variablename")
}
- if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil {
+
+ v.Name = opt.Name
+ v.Data = opt.Value
+ v.Description = opt.Description
+
+ if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
- ctx.Error(http.StatusBadRequest, "UpdateVariable", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else {
- ctx.Error(http.StatusInternalServerError, "UpdateVariable", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -465,16 +487,18 @@ func (Action) ListVariables(ctx *context.APIContext) {
ListOptions: utils.GetListOptions(ctx),
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FindVariables", err)
+ ctx.APIErrorInternal(err)
return
}
variables := make([]*api.ActionVariable, len(vars))
for i, v := range vars {
variables[i] = &api.ActionVariable{
- OwnerID: v.OwnerID,
- RepoID: v.RepoID,
- Name: v.Name,
+ OwnerID: v.OwnerID,
+ RepoID: v.RepoID,
+ Name: v.Name,
+ Data: v.Data,
+ Description: v.Description,
}
}
@@ -507,6 +531,233 @@ func (Action) GetRegistrationToken(ctx *context.APIContext) {
shared.GetRegistrationToken(ctx, 0, ctx.Repo.Repository.ID)
}
+// CreateRegistrationToken returns the token to register repo runners
+func (Action) CreateRegistrationToken(ctx *context.APIContext) {
+ // swagger:operation POST /repos/{owner}/{repo}/actions/runners/registration-token repository repoCreateRunnerRegistrationToken
+ // ---
+ // summary: Get a repository's actions runner registration token
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/RegistrationToken"
+
+ shared.GetRegistrationToken(ctx, 0, ctx.Repo.Repository.ID)
+}
+
+// ListRunners get repo-level runners
+func (Action) ListRunners(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/runners repository getRepoRunners
+ // ---
+ // summary: Get repo-level runners
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/definitions/ActionRunnersResponse"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.ListRunners(ctx, 0, ctx.Repo.Repository.ID)
+}
+
+// GetRunner get an repo-level runner
+func (Action) GetRunner(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/runners/{runner_id} repository getRepoRunner
+ // ---
+ // summary: Get an repo-level runner
+ // 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: runner_id
+ // in: path
+ // description: id of the runner
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/definitions/ActionRunner"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.GetRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
+}
+
+// DeleteRunner delete an repo-level runner
+func (Action) DeleteRunner(ctx *context.APIContext) {
+ // swagger:operation DELETE /repos/{owner}/{repo}/actions/runners/{runner_id} repository deleteRepoRunner
+ // ---
+ // summary: Delete an repo-level runner
+ // 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: runner_id
+ // in: path
+ // description: id of the runner
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // description: runner has been deleted
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.DeleteRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
+}
+
+// GetWorkflowRunJobs Lists all jobs for a workflow run.
+func (Action) ListWorkflowJobs(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/jobs repository listWorkflowJobs
+ // ---
+ // summary: Lists all jobs for a repository
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // type: string
+ // 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"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ repoID := ctx.Repo.Repository.ID
+
+ shared.ListJobs(ctx, 0, repoID, 0)
+}
+
+// ListWorkflowRuns Lists all runs for a repository run.
+func (Action) ListWorkflowRuns(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/runs repository getWorkflowRuns
+ // ---
+ // summary: Lists all runs for a repository run
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // 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: 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/ArtifactsList"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ repoID := ctx.Repo.Repository.ID
+
+ shared.ListRuns(ctx, 0, repoID)
+}
+
var _ actions_service.API = new(Action)
// Action implements actions_service.API
@@ -562,7 +813,7 @@ func ListActionTasks(ctx *context.APIContext) {
RepoID: ctx.Repo.Repository.ID,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ListActionTasks", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -573,7 +824,7 @@ func ListActionTasks(ctx *context.APIContext) {
for i := range tasks {
convertedTask, err := convert.ToActionTask(ctx, tasks[i])
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ToActionTask", err)
+ ctx.APIErrorInternal(err)
return
}
res.Entries[i] = convertedTask
@@ -581,3 +832,852 @@ func ListActionTasks(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, &res)
}
+
+func ActionsListRepositoryWorkflows(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/workflows repository ActionsListRepositoryWorkflows
+ // ---
+ // summary: List repository workflows
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/ActionWorkflowList"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ // "422":
+ // "$ref": "#/responses/validationError"
+ // "500":
+ // "$ref": "#/responses/error"
+
+ workflows, err := convert.ListActionWorkflows(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+
+ ctx.JSON(http.StatusOK, &api.ActionWorkflowResponse{Workflows: workflows, TotalCount: int64(len(workflows))})
+}
+
+func ActionsGetWorkflow(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/workflows/{workflow_id} repository ActionsGetWorkflow
+ // ---
+ // summary: Get 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
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/ActionWorkflow"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ // "422":
+ // "$ref": "#/responses/validationError"
+ // "500":
+ // "$ref": "#/responses/error"
+
+ workflowID := ctx.PathParam("workflow_id")
+ 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)
+ } else {
+ ctx.APIErrorInternal(err)
+ }
+ return
+ }
+
+ ctx.JSON(http.StatusOK, workflow)
+}
+
+func ActionsDisableWorkflow(ctx *context.APIContext) {
+ // swagger:operation PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/disable repository ActionsDisableWorkflow
+ // ---
+ // summary: Disable 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
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // description: No Content
+ // "400":
+ // "$ref": "#/responses/error"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ // "422":
+ // "$ref": "#/responses/validationError"
+
+ workflowID := ctx.PathParam("workflow_id")
+ err := actions_service.EnableOrDisableWorkflow(ctx, workflowID, false)
+ if err != nil {
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.APIError(http.StatusNotFound, err)
+ } else {
+ ctx.APIErrorInternal(err)
+ }
+ return
+ }
+
+ ctx.Status(http.StatusNoContent)
+}
+
+func ActionsDispatchWorkflow(ctx *context.APIContext) {
+ // swagger:operation POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches repository ActionsDispatchWorkflow
+ // ---
+ // summary: Create a workflow dispatch event
+ // 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
+ // type: string
+ // required: true
+ // - name: body
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/CreateActionWorkflowDispatch"
+ // responses:
+ // "204":
+ // description: No Content
+ // "400":
+ // "$ref": "#/responses/error"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ // "422":
+ // "$ref": "#/responses/validationError"
+
+ workflowID := ctx.PathParam("workflow_id")
+ opt := web.GetForm(ctx).(*api.CreateActionWorkflowDispatch)
+ if opt.Ref == "" {
+ ctx.APIError(http.StatusUnprocessableEntity, util.NewInvalidArgumentErrorf("ref is required parameter"))
+ return
+ }
+
+ 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 {
+ 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
+ for name, config := range workflowDispatch.Inputs {
+ value := ctx.FormString("inputs["+name+"]", config.Default)
+ inputs[name] = value
+ }
+ } else {
+ for name, config := range workflowDispatch.Inputs {
+ value, ok := opt.Inputs[name]
+ if ok {
+ inputs[name] = value
+ } else {
+ inputs[name] = config.Default
+ }
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.APIError(http.StatusNotFound, err)
+ } else if errors.Is(err, util.ErrPermissionDenied) {
+ ctx.APIError(http.StatusForbidden, err)
+ } else {
+ ctx.APIErrorInternal(err)
+ }
+ return
+ }
+
+ ctx.Status(http.StatusNoContent)
+}
+
+func ActionsEnableWorkflow(ctx *context.APIContext) {
+ // swagger:operation PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/enable repository ActionsEnableWorkflow
+ // ---
+ // summary: Enable 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
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // description: No Content
+ // "400":
+ // "$ref": "#/responses/error"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ // "409":
+ // "$ref": "#/responses/conflict"
+ // "422":
+ // "$ref": "#/responses/validationError"
+
+ workflowID := ctx.PathParam("workflow_id")
+ err := actions_service.EnableOrDisableWorkflow(ctx, workflowID, true)
+ if err != nil {
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.APIError(http.StatusNotFound, err)
+ } else {
+ ctx.APIErrorInternal(err)
+ }
+ return
+ }
+
+ ctx.Status(http.StatusNoContent)
+}
+
+// GetWorkflowRun Gets a specific workflow run.
+func GetWorkflowRun(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run} repository GetWorkflowRun
+ // ---
+ // summary: Gets a specific workflow run
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // 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: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/WorkflowRun"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ runID := ctx.PathParamInt64("run")
+ job, _, err := db.GetByID[actions_model.ActionRun](ctx, runID)
+
+ if err != nil || job.RepoID != ctx.Repo.Repository.ID {
+ ctx.APIError(http.StatusNotFound, util.ErrNotExist)
+ }
+
+ convertedArtifact, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, job)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ ctx.JSON(http.StatusOK, convertedArtifact)
+}
+
+// ListWorkflowRunJobs Lists all jobs for a workflow run.
+func ListWorkflowRunJobs(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/jobs repository listWorkflowRunJobs
+ // ---
+ // summary: Lists all jobs for a workflow run
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // type: string
+ // required: true
+ // - name: run
+ // in: path
+ // description: runid of the workflow 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"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ repoID := ctx.Repo.Repository.ID
+
+ runID := 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"))
+ 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)
+}
+
+// GetWorkflowJob Gets a specific workflow job for a workflow run.
+func GetWorkflowJob(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/jobs/{job_id} repository getWorkflowJob
+ // ---
+ // summary: Gets a specific workflow job for a workflow run
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // type: string
+ // required: true
+ // - name: job_id
+ // in: path
+ // description: id of the job
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/WorkflowJob"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ jobID := ctx.PathParamInt64("job_id")
+ job, _, err := db.GetByID[actions_model.ActionRunJob](ctx, jobID)
+
+ if err != nil || job.RepoID != ctx.Repo.Repository.ID {
+ ctx.APIError(http.StatusNotFound, util.ErrNotExist)
+ }
+
+ convertedWorkflowJob, err := convert.ToActionWorkflowJob(ctx, ctx.Repo.Repository, nil, job)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ ctx.JSON(http.StatusOK, convertedWorkflowJob)
+}
+
+// GetArtifacts Lists all artifacts for a repository.
+func GetArtifactsOfRun(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/artifacts repository getArtifactsOfRun
+ // ---
+ // summary: Lists all artifacts for a repository run
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // type: string
+ // required: true
+ // - name: run
+ // in: path
+ // description: runid of the workflow run
+ // type: integer
+ // required: true
+ // - name: name
+ // in: query
+ // description: name of the artifact
+ // type: string
+ // required: false
+ // responses:
+ // "200":
+ // "$ref": "#/responses/ArtifactsList"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ repoID := ctx.Repo.Repository.ID
+ artifactName := ctx.Req.URL.Query().Get("name")
+
+ runID := ctx.PathParamInt64("run")
+
+ artifacts, total, err := db.FindAndCount[actions_model.ActionArtifact](ctx, actions_model.FindArtifactsOptions{
+ RepoID: repoID,
+ RunID: runID,
+ ArtifactName: artifactName,
+ FinalizedArtifactsV4: true,
+ ListOptions: utils.GetListOptions(ctx),
+ })
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+
+ res := new(api.ActionArtifactsResponse)
+ res.TotalCount = total
+
+ res.Entries = make([]*api.ActionArtifact, len(artifacts))
+ for i := range artifacts {
+ convertedArtifact, err := convert.ToActionArtifact(ctx.Repo.Repository, artifacts[i])
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ res.Entries[i] = convertedArtifact
+ }
+
+ ctx.JSON(http.StatusOK, &res)
+}
+
+// DeleteActionRun Delete a workflow run
+func DeleteActionRun(ctx *context.APIContext) {
+ // swagger:operation DELETE /repos/{owner}/{repo}/actions/runs/{run} repository deleteActionRun
+ // ---
+ // summary: Delete a workflow run
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // type: string
+ // required: true
+ // - name: run
+ // in: path
+ // description: runid of the workflow run
+ // type: integer
+ // required: true
+ // responses:
+ // "204":
+ // description: "No Content"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ runID := ctx.PathParamInt64("run")
+ run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID)
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.APIError(http.StatusNotFound, err)
+ return
+ } else if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ if !run.Status.IsDone() {
+ ctx.APIError(http.StatusBadRequest, "this workflow run is not done")
+ return
+ }
+
+ if err := actions_service.DeleteRun(ctx, run); err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ ctx.Status(http.StatusNoContent)
+}
+
+// GetArtifacts Lists all artifacts for a repository.
+func GetArtifacts(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/artifacts repository getArtifacts
+ // ---
+ // summary: Lists all artifacts for a repository
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // type: string
+ // required: true
+ // - name: name
+ // in: query
+ // description: name of the artifact
+ // type: string
+ // required: false
+ // responses:
+ // "200":
+ // "$ref": "#/responses/ArtifactsList"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ repoID := ctx.Repo.Repository.ID
+ artifactName := ctx.Req.URL.Query().Get("name")
+
+ artifacts, total, err := db.FindAndCount[actions_model.ActionArtifact](ctx, actions_model.FindArtifactsOptions{
+ RepoID: repoID,
+ ArtifactName: artifactName,
+ FinalizedArtifactsV4: true,
+ ListOptions: utils.GetListOptions(ctx),
+ })
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+
+ res := new(api.ActionArtifactsResponse)
+ res.TotalCount = total
+
+ res.Entries = make([]*api.ActionArtifact, len(artifacts))
+ for i := range artifacts {
+ convertedArtifact, err := convert.ToActionArtifact(ctx.Repo.Repository, artifacts[i])
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ res.Entries[i] = convertedArtifact
+ }
+
+ ctx.JSON(http.StatusOK, &res)
+}
+
+// GetArtifact Gets a specific artifact for a workflow run.
+func GetArtifact(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id} repository getArtifact
+ // ---
+ // summary: Gets a specific artifact for a workflow run
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // type: string
+ // required: true
+ // - name: artifact_id
+ // in: path
+ // description: id of the artifact
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/Artifact"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ art := getArtifactByPathParam(ctx, ctx.Repo.Repository)
+ if ctx.Written() {
+ return
+ }
+
+ if actions.IsArtifactV4(art) {
+ convertedArtifact, err := convert.ToActionArtifact(ctx.Repo.Repository, art)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ ctx.JSON(http.StatusOK, convertedArtifact)
+ return
+ }
+ // v3 not supported due to not having one unique id
+ ctx.APIError(http.StatusNotFound, "Artifact not found")
+}
+
+// DeleteArtifact Deletes a specific artifact for a workflow run.
+func DeleteArtifact(ctx *context.APIContext) {
+ // swagger:operation DELETE /repos/{owner}/{repo}/actions/artifacts/{artifact_id} repository deleteArtifact
+ // ---
+ // summary: Deletes a specific artifact for a workflow run
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // type: string
+ // required: true
+ // - name: artifact_id
+ // in: path
+ // description: id of the artifact
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // description: "No Content"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ art := getArtifactByPathParam(ctx, ctx.Repo.Repository)
+ if ctx.Written() {
+ return
+ }
+
+ if actions.IsArtifactV4(art) {
+ if err := actions_model.SetArtifactNeedDelete(ctx, art.RunID, art.ArtifactName); err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ ctx.Status(http.StatusNoContent)
+ return
+ }
+ // v3 not supported due to not having one unique id
+ ctx.APIError(http.StatusNotFound, "Artifact not found")
+}
+
+func buildSignature(endp string, expires, artifactID int64) []byte {
+ mac := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret())
+ mac.Write([]byte(endp))
+ fmt.Fprint(mac, expires)
+ fmt.Fprint(mac, artifactID)
+ return mac.Sum(nil)
+}
+
+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 buildSigURL(ctx go_context.Context, endPoint string, artifactID int64) string {
+ // endPoint is a path like "api/v1/repos/owner/repo/actions/artifacts/1/zip/raw"
+ expires := time.Now().Add(60 * time.Minute).Unix()
+ uploadURL := httplib.GuessCurrentAppURL(ctx) + endPoint + "?sig=" + base64.URLEncoding.EncodeToString(buildSignature(endPoint, expires, artifactID)) + "&expires=" + strconv.FormatInt(expires, 10)
+ return uploadURL
+}
+
+// DownloadArtifact Downloads a specific artifact for a workflow run redirects to blob url.
+func DownloadArtifact(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}/zip repository downloadArtifact
+ // ---
+ // summary: Downloads a specific artifact for a workflow run redirects to blob url
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // type: string
+ // required: true
+ // - name: artifact_id
+ // in: path
+ // description: id of the artifact
+ // type: string
+ // required: true
+ // responses:
+ // "302":
+ // description: redirect to the blob download
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ art := getArtifactByPathParam(ctx, ctx.Repo.Repository)
+ if ctx.Written() {
+ 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
+ }
+ ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip; filename*=UTF-8''%s.zip", url.PathEscape(art.ArtifactName), art.ArtifactName))
+
+ if actions.IsArtifactV4(art) {
+ ok, err := actions.DownloadArtifactV4ServeDirectOnly(ctx.Base, art)
+ if ok {
+ return
+ }
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+
+ redirectURL := buildSigURL(ctx, buildDownloadRawEndpoint(ctx.Repo.Repository, art.ID), art.ID)
+ ctx.Redirect(redirectURL, http.StatusFound)
+ return
+ }
+ // v3 not supported due to not having one unique id
+ ctx.APIError(http.StatusNotFound, "Artifact not found")
+}
+
+// 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"))
+ if err != nil {
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.APIErrorNotFound()
+ } else {
+ ctx.APIErrorInternal(err)
+ }
+ return
+ }
+ art := getArtifactByPathParam(ctx, repo)
+ if ctx.Written() {
+ return
+ }
+
+ sigStr := ctx.Req.URL.Query().Get("sig")
+ expiresStr := ctx.Req.URL.Query().Get("expires")
+ sigBytes, _ := base64.URLEncoding.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
+ }
+ ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip; filename*=UTF-8''%s.zip", url.PathEscape(art.ArtifactName), art.ArtifactName))
+
+ if actions.IsArtifactV4(art) {
+ err := actions.DownloadArtifactV4(ctx.Base, art)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ return
+ }
+ // v3 not supported due to not having one unique id
+ ctx.APIError(http.StatusNotFound, "artifact not found")
+}
+
+// Try to get the artifact by ID and check access
+func getArtifactByPathParam(ctx *context.APIContext, repo *repo_model.Repository) *actions_model.ActionArtifact {
+ artifactID := ctx.PathParamInt64("artifact_id")
+
+ art, ok, err := db.GetByID[actions_model.ActionArtifact](ctx, artifactID)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return nil
+ }
+ // if artifacts status is not uploaded-confirmed, treat it as not found
+ // only check RepoID here, because the repository owner may change over the time
+ if !ok ||
+ art.RepoID != repo.ID ||
+ art.Status != actions_model.ArtifactStatusUploadConfirmed && art.Status != actions_model.ArtifactStatusExpired {
+ ctx.APIError(http.StatusNotFound, "artifact not found")
+ return nil
+ }
+ return art
+}
diff --git a/routers/api/v1/repo/actions_run.go b/routers/api/v1/repo/actions_run.go
new file mode 100644
index 0000000000..c6d18af6aa
--- /dev/null
+++ b/routers/api/v1/repo/actions_run.go
@@ -0,0 +1,64 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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"
+)
+
+func DownloadActionsRunJobLogs(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/jobs/{job_id}/logs repository downloadActionsRunJobLogs
+ // ---
+ // summary: Downloads the job logs for a workflow run
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // type: string
+ // required: true
+ // - name: job_id
+ // in: path
+ // description: id of the job
+ // type: integer
+ // required: true
+ // responses:
+ // "200":
+ // description: output blob content
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ jobID := ctx.PathParamInt64("job_id")
+ curJob, err := actions_model.GetRunJobByID(ctx, jobID)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ if err = curJob.LoadRepo(ctx); err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+
+ 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)
+ }
+ }
+}
diff --git a/routers/api/v1/repo/avatar.go b/routers/api/v1/repo/avatar.go
index 698337ffd2..593460586f 100644
--- a/routers/api/v1/repo/avatar.go
+++ b/routers/api/v1/repo/avatar.go
@@ -44,13 +44,13 @@ func UpdateAvatar(ctx *context.APIContext) {
content, err := base64.StdEncoding.DecodeString(form.Image)
if err != nil {
- ctx.Error(http.StatusBadRequest, "DecodeImage", err)
+ ctx.APIError(http.StatusBadRequest, err)
return
}
err = repo_service.UploadAvatar(ctx, ctx.Repo.Repository, content)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "UploadAvatar", err)
+ ctx.APIErrorInternal(err)
}
ctx.Status(http.StatusNoContent)
@@ -81,7 +81,7 @@ func DeleteAvatar(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
err := repo_service.DeleteAvatar(ctx, ctx.Repo.Repository)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteAvatar", err)
+ ctx.APIErrorInternal(err)
}
ctx.Status(http.StatusNoContent)
diff --git a/routers/api/v1/repo/blob.go b/routers/api/v1/repo/blob.go
index f38086954b..9a17fc1bbf 100644
--- a/routers/api/v1/repo/blob.go
+++ b/routers/api/v1/repo/blob.go
@@ -43,12 +43,12 @@ func GetBlob(ctx *context.APIContext) {
sha := ctx.PathParam("sha")
if len(sha) == 0 {
- ctx.Error(http.StatusBadRequest, "", "sha not provided")
+ ctx.APIError(http.StatusBadRequest, "sha not provided")
return
}
- if blob, err := files_service.GetBlobBySHA(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, sha); err != nil {
- ctx.Error(http.StatusBadRequest, "", err)
+ if blob, err := files_service.GetBlobBySHA(ctx.Repo.Repository, ctx.Repo.GitRepo, sha); err != nil {
+ ctx.APIError(http.StatusBadRequest, err)
} else {
ctx.JSON(http.StatusOK, blob)
}
diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go
index a5ea752cf1..9af958a5b7 100644
--- a/routers/api/v1/repo/branch.go
+++ b/routers/api/v1/repo/branch.go
@@ -6,7 +6,6 @@ package repo
import (
"errors"
- "fmt"
"net/http"
"code.gitea.io/gitea/models/db"
@@ -60,31 +59,30 @@ func GetBranch(ctx *context.APIContext) {
branchName := ctx.PathParam("*")
- branch, err := ctx.Repo.GitRepo.GetBranch(branchName)
+ exist, err := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, branchName)
if err != nil {
- if git.IsErrBranchNotExist(err) {
- ctx.NotFound(err)
- } else {
- ctx.Error(http.StatusInternalServerError, "GetBranch", err)
- }
+ ctx.APIErrorInternal(err)
+ return
+ } else if !exist {
+ ctx.APIErrorNotFound(err)
return
}
- c, err := branch.GetCommit()
+ c, err := ctx.Repo.GitRepo.GetBranchCommit(branchName)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetCommit", err)
+ ctx.APIErrorInternal(err)
return
}
branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branchName)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
+ ctx.APIErrorInternal(err)
return
}
- br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
+ br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branchName, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
if err != nil {
- ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -124,12 +122,12 @@ func DeleteBranch(ctx *context.APIContext) {
// "423":
// "$ref": "#/responses/repoArchivedError"
if ctx.Repo.Repository.IsEmpty {
- ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
+ ctx.APIError(http.StatusNotFound, "Git Repository is empty.")
return
}
if ctx.Repo.Repository.IsMirror {
- ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.")
+ ctx.APIError(http.StatusForbidden, "Git Repository is a mirror.")
return
}
@@ -141,13 +139,13 @@ func DeleteBranch(ctx *context.APIContext) {
IsDeletedBranch: optional.Some(false),
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "CountBranches", err)
+ ctx.APIErrorInternal(err)
return
}
if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
_, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
if err != nil {
- ctx.ServerError("SyncRepoBranches", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -155,13 +153,13 @@ func DeleteBranch(ctx *context.APIContext) {
if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName, nil); err != nil {
switch {
case git.IsErrBranchNotExist(err):
- ctx.NotFound(err)
+ ctx.APIErrorNotFound(err)
case errors.Is(err, repo_service.ErrBranchIsDefault):
- ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch"))
+ ctx.APIError(http.StatusForbidden, errors.New("can not delete default branch"))
case errors.Is(err, git_model.ErrBranchIsProtected):
- ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected"))
+ ctx.APIError(http.StatusForbidden, errors.New("branch protected"))
default:
- ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -206,12 +204,12 @@ func CreateBranch(ctx *context.APIContext) {
// "$ref": "#/responses/repoArchivedError"
if ctx.Repo.Repository.IsEmpty {
- ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
+ ctx.APIError(http.StatusNotFound, "Git Repository is empty.")
return
}
if ctx.Repo.Repository.IsMirror {
- ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.")
+ ctx.APIError(http.StatusForbidden, "Git Repository is a mirror.")
return
}
@@ -223,24 +221,24 @@ func CreateBranch(ctx *context.APIContext) {
if len(opt.OldRefName) > 0 {
oldCommit, err = ctx.Repo.GitRepo.GetCommit(opt.OldRefName)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetCommit", err)
+ ctx.APIErrorInternal(err)
return
}
- } else if len(opt.OldBranchName) > 0 { //nolint
- if ctx.Repo.GitRepo.IsBranchExist(opt.OldBranchName) { //nolint
- oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(opt.OldBranchName) //nolint
+ } else if len(opt.OldBranchName) > 0 { //nolint:staticcheck // deprecated field
+ if gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, opt.OldBranchName) { //nolint:staticcheck // deprecated field
+ oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(opt.OldBranchName) //nolint:staticcheck // deprecated field
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err)
+ ctx.APIErrorInternal(err)
return
}
} else {
- ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
+ ctx.APIError(http.StatusNotFound, "The old branch does not exist")
return
}
} else {
oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -248,40 +246,34 @@ func CreateBranch(ctx *context.APIContext) {
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, oldCommit.ID.String(), opt.BranchName)
if err != nil {
if git_model.IsErrBranchNotExist(err) {
- ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
+ ctx.APIError(http.StatusNotFound, "The old branch does not exist")
} else if release_service.IsErrTagAlreadyExists(err) {
- ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.")
+ ctx.APIError(http.StatusConflict, "The branch with the same tag already exists.")
} else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
- ctx.Error(http.StatusConflict, "", "The branch already exists.")
+ ctx.APIError(http.StatusConflict, "The branch already exists.")
} else if git_model.IsErrBranchNameConflict(err) {
- ctx.Error(http.StatusConflict, "", "The branch with the same name already exists.")
+ ctx.APIError(http.StatusConflict, "The branch with the same name already exists.")
} else {
- ctx.Error(http.StatusInternalServerError, "CreateNewBranchFromCommit", err)
+ ctx.APIErrorInternal(err)
}
return
}
- branch, err := ctx.Repo.GitRepo.GetBranch(opt.BranchName)
+ commit, err := ctx.Repo.GitRepo.GetBranchCommit(opt.BranchName)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetBranch", err)
+ ctx.APIErrorInternal(err)
return
}
- commit, err := branch.GetCommit()
+ branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, opt.BranchName)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetCommit", err)
+ ctx.APIErrorInternal(err)
return
}
- branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branch.Name)
+ br, err := convert.ToBranch(ctx, ctx.Repo.Repository, opt.BranchName, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
- return
- }
-
- br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
- if err != nil {
- ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -325,7 +317,7 @@ func ListBranches(ctx *context.APIContext) {
if !ctx.Repo.Repository.IsEmpty {
if ctx.Repo.GitRepo == nil {
- ctx.Error(http.StatusInternalServerError, "Load git repository failed", nil)
+ ctx.APIErrorInternal(nil)
return
}
@@ -337,26 +329,26 @@ func ListBranches(ctx *context.APIContext) {
var err error
totalNumOfBranches, err = db.Count[git_model.Branch](ctx, branchOpts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "CountBranches", err)
+ ctx.APIErrorInternal(err)
return
}
if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
totalNumOfBranches, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
if err != nil {
- ctx.ServerError("SyncRepoBranches", err)
+ ctx.APIErrorInternal(err)
return
}
}
rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err)
+ ctx.APIErrorInternal(err)
return
}
branches, err := db.Find[git_model.Branch](ctx, branchOpts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetBranches", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -369,14 +361,14 @@ func ListBranches(ctx *context.APIContext) {
totalNumOfBranches--
continue
}
- ctx.Error(http.StatusInternalServerError, "GetCommit", err)
+ ctx.APIErrorInternal(err)
return
}
branchProtection := rules.GetFirstMatched(branches[i].Name)
apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i].Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
if err != nil {
- ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
+ ctx.APIErrorInternal(err)
return
}
apiBranches = append(apiBranches, apiBranch)
@@ -433,12 +425,12 @@ func UpdateBranch(ctx *context.APIContext) {
repo := ctx.Repo.Repository
if repo.IsEmpty {
- ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
+ ctx.APIError(http.StatusNotFound, "Git Repository is empty.")
return
}
if repo.IsMirror {
- ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.")
+ ctx.APIError(http.StatusForbidden, "Git Repository is a mirror.")
return
}
@@ -446,20 +438,20 @@ func UpdateBranch(ctx *context.APIContext) {
if err != nil {
switch {
case repo_model.IsErrUserDoesNotHaveAccessToRepo(err):
- ctx.Error(http.StatusForbidden, "", "User must be a repo or site admin to rename default or protected branches.")
+ ctx.APIError(http.StatusForbidden, "User must be a repo or site admin to rename default or protected branches.")
case errors.Is(err, git_model.ErrBranchIsProtected):
- ctx.Error(http.StatusForbidden, "", "Branch is protected by glob-based protection rules.")
+ ctx.APIError(http.StatusForbidden, "Branch is protected by glob-based protection rules.")
default:
- ctx.Error(http.StatusInternalServerError, "RenameBranch", err)
+ ctx.APIErrorInternal(err)
}
return
}
if msg == "target_exist" {
- ctx.Error(http.StatusUnprocessableEntity, "", "Cannot rename a branch using the same name or rename to a branch that already exists.")
+ ctx.APIError(http.StatusUnprocessableEntity, "Cannot rename a branch using the same name or rename to a branch that already exists.")
return
}
if msg == "from_not_exist" {
- ctx.Error(http.StatusNotFound, "", "Branch doesn't exist.")
+ ctx.APIError(http.StatusNotFound, "Branch doesn't exist.")
return
}
@@ -499,11 +491,11 @@ func GetBranchProtection(ctx *context.APIContext) {
bpName := ctx.PathParam("name")
bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
+ ctx.APIErrorInternal(err)
return
}
if bp == nil || bp.RepoID != repo.ID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -535,7 +527,7 @@ func ListBranchProtections(ctx *context.APIContext) {
repo := ctx.Repo.Repository
bps, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetProtectedBranches", err)
+ ctx.APIErrorInternal(err)
return
}
apiBps := make([]*api.BranchProtection, len(bps))
@@ -587,25 +579,19 @@ func CreateBranchProtection(ctx *context.APIContext) {
ruleName := form.RuleName
if ruleName == "" {
- ruleName = form.BranchName //nolint
+ ruleName = form.BranchName //nolint:staticcheck // deprecated field
}
if len(ruleName) == 0 {
- ctx.Error(http.StatusBadRequest, "both rule_name and branch_name are empty", "both rule_name and branch_name are empty")
+ ctx.APIError(http.StatusBadRequest, "both rule_name and branch_name are empty")
return
}
- isPlainRule := !git_model.IsRuleNameSpecial(ruleName)
- var isBranchExist bool
- if isPlainRule {
- isBranchExist = git.IsBranchExist(ctx.Req.Context(), ctx.Repo.Repository.RepoPath(), ruleName)
- }
-
protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, ruleName)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetProtectBranchOfRepoByName", err)
+ ctx.APIErrorInternal(err)
return
} else if protectBranch != nil {
- ctx.Error(http.StatusForbidden, "Create branch protection", "Branch protection already exist")
+ ctx.APIError(http.StatusForbidden, "Branch protection already exist")
return
}
@@ -617,37 +603,37 @@ func CreateBranchProtection(ctx *context.APIContext) {
whitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
forcePushAllowlistUsers, err := user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
mergeWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
approvalsWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
var whitelistTeams, forcePushAllowlistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
@@ -655,37 +641,37 @@ func CreateBranchProtection(ctx *context.APIContext) {
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
if err != nil {
if organization.IsErrTeamNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
forcePushAllowlistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ForcePushAllowlistTeams, false)
if err != nil {
if organization.IsErrTeamNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
if err != nil {
if organization.IsErrTeamNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ApprovalsWhitelistTeams, false)
if err != nil {
if organization.IsErrTeamNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -716,7 +702,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
BlockAdminMergeOverride: form.BlockAdminMergeOverride,
}
- if err := git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
+ if err := pull_service.CreateOrUpdateProtectedBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
UserIDs: whitelistUsers,
TeamIDs: whitelistTeams,
ForcePushUserIDs: forcePushAllowlistUsers,
@@ -726,48 +712,18 @@ func CreateBranchProtection(ctx *context.APIContext) {
ApprovalsUserIDs: approvalsWhitelistUsers,
ApprovalsTeamIDs: approvalsWhitelistTeams,
}); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
+ ctx.APIErrorInternal(err)
return
}
- if isBranchExist {
- if err := pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, ruleName); err != nil {
- ctx.Error(http.StatusInternalServerError, "CheckPRsForBaseBranch", err)
- return
- }
- } else {
- if !isPlainRule {
- if ctx.Repo.GitRepo == nil {
- ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
- if err != nil {
- ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
- return
- }
- }
- // FIXME: since we only need to recheck files protected rules, we could improve this
- matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, ruleName)
- if err != nil {
- ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
- return
- }
-
- for _, branchName := range matchedBranches {
- if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, branchName); err != nil {
- ctx.Error(http.StatusInternalServerError, "CheckPRsForBaseBranch", err)
- return
- }
- }
- }
- }
-
// Reload from db to get all whitelists
bp, err := git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, ruleName)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
+ ctx.APIErrorInternal(err)
return
}
if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
- ctx.Error(http.StatusInternalServerError, "New branch protection not found", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -817,11 +773,11 @@ func EditBranchProtection(ctx *context.APIContext) {
bpName := ctx.PathParam("name")
protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
+ ctx.APIErrorInternal(err)
return
}
if protectBranch == nil || protectBranch.RepoID != repo.ID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -932,10 +888,10 @@ func EditBranchProtection(ctx *context.APIContext) {
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
} else {
@@ -945,10 +901,10 @@ func EditBranchProtection(ctx *context.APIContext) {
forcePushAllowlistUsers, err = user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
} else {
@@ -958,10 +914,10 @@ func EditBranchProtection(ctx *context.APIContext) {
mergeWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
} else {
@@ -971,10 +927,10 @@ func EditBranchProtection(ctx *context.APIContext) {
approvalsWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
} else {
@@ -987,10 +943,10 @@ func EditBranchProtection(ctx *context.APIContext) {
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
if err != nil {
if organization.IsErrTeamNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
} else {
@@ -1000,10 +956,10 @@ func EditBranchProtection(ctx *context.APIContext) {
forcePushAllowlistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ForcePushAllowlistTeams, false)
if err != nil {
if organization.IsErrTeamNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
} else {
@@ -1013,10 +969,10 @@ func EditBranchProtection(ctx *context.APIContext) {
mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
if err != nil {
if organization.IsErrTeamNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
} else {
@@ -1026,10 +982,10 @@ func EditBranchProtection(ctx *context.APIContext) {
approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ApprovalsWhitelistTeams, false)
if err != nil {
if organization.IsErrTeamNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
} else {
@@ -1048,19 +1004,19 @@ func EditBranchProtection(ctx *context.APIContext) {
ApprovalsTeamIDs: approvalsWhitelistTeams,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
+ ctx.APIErrorInternal(err)
return
}
isPlainRule := !git_model.IsRuleNameSpecial(bpName)
var isBranchExist bool
if isPlainRule {
- isBranchExist = git.IsBranchExist(ctx.Req.Context(), ctx.Repo.Repository.RepoPath(), bpName)
+ isBranchExist = gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, bpName)
}
if isBranchExist {
if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, bpName); err != nil {
- ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err)
+ ctx.APIErrorInternal(err)
return
}
} else {
@@ -1068,7 +1024,7 @@ func EditBranchProtection(ctx *context.APIContext) {
if ctx.Repo.GitRepo == nil {
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -1076,13 +1032,13 @@ func EditBranchProtection(ctx *context.APIContext) {
// FIXME: since we only need to recheck files protected rules, we could improve this
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
+ ctx.APIErrorInternal(err)
return
}
for _, branchName := range matchedBranches {
if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, branchName); err != nil {
- ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -1092,11 +1048,11 @@ func EditBranchProtection(ctx *context.APIContext) {
// Reload from db to ensure get all whitelists
bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
+ ctx.APIErrorInternal(err)
return
}
if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
- ctx.Error(http.StatusInternalServerError, "New branch protection not found", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -1136,16 +1092,16 @@ func DeleteBranchProtection(ctx *context.APIContext) {
bpName := ctx.PathParam("name")
bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
+ ctx.APIErrorInternal(err)
return
}
if bp == nil || bp.RepoID != repo.ID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository, bp.ID); err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteProtectedBranch", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -1189,7 +1145,7 @@ func UpdateBranchProtectionPriories(ctx *context.APIContext) {
repo := ctx.Repo.Repository
if err := git_model.UpdateProtectBranchPriorities(ctx, repo, form.IDs); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateProtectBranchPriorities", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -1225,16 +1181,16 @@ func MergeUpstream(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
form := web.GetForm(ctx).(*api.MergeUpstreamRequest)
- mergeStyle, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, form.Branch)
+ 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.Error(http.StatusBadRequest, "MergeUpstream", err)
+ ctx.APIError(http.StatusBadRequest, err)
return
} else if errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusNotFound, "MergeUpstream", err)
+ ctx.APIError(http.StatusNotFound, err)
return
}
- ctx.Error(http.StatusInternalServerError, "MergeUpstream", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, &api.MergeUpstreamResponse{MergeStyle: mergeStyle})
diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go
index da3ee54e69..c2c10cc695 100644
--- a/routers/api/v1/repo/collaborators.go
+++ b/routers/api/v1/repo/collaborators.go
@@ -7,6 +7,7 @@ package repo
import (
"errors"
"net/http"
+ "strings"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
@@ -59,7 +60,7 @@ func ListCollaborators(ctx *context.APIContext) {
RepoID: ctx.Repo.Repository.ID,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ListCollaborators", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -92,7 +93,7 @@ func IsCollaborator(ctx *context.APIContext) {
// required: true
// - name: collaborator
// in: path
- // description: username of the collaborator
+ // description: username of the user to check for being a collaborator
// type: string
// required: true
// responses:
@@ -106,21 +107,21 @@ func IsCollaborator(ctx *context.APIContext) {
user, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator"))
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
isColab, err := repo_model.IsCollaborator(ctx, ctx.Repo.Repository.ID, user.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsCollaborator", err)
+ ctx.APIErrorInternal(err)
return
}
if isColab {
ctx.Status(http.StatusNoContent)
} else {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
}
}
@@ -144,7 +145,7 @@ func AddOrUpdateCollaborator(ctx *context.APIContext) {
// required: true
// - name: collaborator
// in: path
- // description: username of the collaborator to add
+ // description: username of the user to add or update as a collaborator
// type: string
// required: true
// - name: body
@@ -166,28 +167,28 @@ func AddOrUpdateCollaborator(ctx *context.APIContext) {
collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator"))
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
if !collaborator.IsActive {
- ctx.Error(http.StatusInternalServerError, "InactiveCollaborator", errors.New("collaborator's account is inactive"))
+ ctx.APIErrorInternal(errors.New("collaborator's account is inactive"))
return
}
p := perm.AccessModeWrite
if form.Permission != nil {
- p = perm.ParseAccessMode(*form.Permission)
+ p = perm.ParseAccessMode(*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.Error(http.StatusForbidden, "AddOrUpdateCollaborator", err)
+ ctx.APIError(http.StatusForbidden, err)
} else {
- ctx.Error(http.StatusInternalServerError, "AddOrUpdateCollaborator", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -229,15 +230,15 @@ func DeleteCollaborator(ctx *context.APIContext) {
collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator"))
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err := repo_service.DeleteCollaboration(ctx, ctx.Repo.Repository, collaborator); err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteCollaboration", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
@@ -263,7 +264,7 @@ func GetRepoPermissions(ctx *context.APIContext) {
// required: true
// - name: collaborator
// in: path
- // description: username of the collaborator
+ // description: username of the collaborator whose permissions are to be obtained
// type: string
// required: true
// responses:
@@ -274,24 +275,25 @@ func GetRepoPermissions(ctx *context.APIContext) {
// "403":
// "$ref": "#/responses/forbidden"
- if !ctx.Doer.IsAdmin && ctx.Doer.LoginName != ctx.PathParam("collaborator") && !ctx.IsUserRepoAdmin() {
- ctx.Error(http.StatusForbidden, "User", "Only admins can query all permissions, repo admins can query all repo permissions, collaborators can query only their own")
+ collaboratorUsername := ctx.PathParam("collaborator")
+ if !ctx.Doer.IsAdmin && ctx.Doer.LowerName != strings.ToLower(collaboratorUsername) && !ctx.IsUserRepoAdmin() {
+ ctx.APIError(http.StatusForbidden, "Only admins can query all permissions, repo admins can query all repo permissions, collaborators can query only their own")
return
}
- collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator"))
+ collaborator, err := user_model.GetUserByName(ctx, collaboratorUsername)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusNotFound, "GetUserByName", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
permission, err := access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, collaborator)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -324,13 +326,13 @@ func GetReviewers(ctx *context.APIContext) {
canChooseReviewer := issue_service.CanDoerChangeReviewRequests(ctx, ctx.Doer, ctx.Repo.Repository, 0)
if !canChooseReviewer {
- ctx.Error(http.StatusForbidden, "GetReviewers", errors.New("doer has no permission to get reviewers"))
+ ctx.APIError(http.StatusForbidden, errors.New("doer has no permission to get reviewers"))
return
}
reviewers, err := pull_service.GetReviewers(ctx, ctx.Repo.Repository, ctx.Doer.ID, 0)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ListCollaborators", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, convert.ToUsers(ctx, ctx.Doer, reviewers))
@@ -362,7 +364,7 @@ func GetAssignees(ctx *context.APIContext) {
assignees, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ListCollaborators", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, convert.ToUsers(ctx, ctx.Doer, assignees))
diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go
index 3b144d0c43..6a93be624f 100644
--- a/routers/api/v1/repo/commits.go
+++ b/routers/api/v1/repo/commits.go
@@ -5,10 +5,10 @@
package repo
import (
- "fmt"
"math"
"net/http"
"strconv"
+ "time"
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
@@ -65,7 +65,7 @@ func GetSingleCommit(ctx *context.APIContext) {
sha := ctx.PathParam("sha")
if !git.IsValidRefPattern(sha) {
- ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha))
+ ctx.APIError(http.StatusUnprocessableEntity, "no valid ref or sha: "+sha)
return
}
@@ -76,16 +76,16 @@ func getCommit(ctx *context.APIContext, identifier string, toCommitOpts convert.
commit, err := ctx.Repo.GitRepo.GetCommit(identifier)
if err != nil {
if git.IsErrNotExist(err) {
- ctx.NotFound(identifier)
+ ctx.APIErrorNotFound("commit doesn't exist: " + identifier)
return
}
- ctx.Error(http.StatusInternalServerError, "gitRepo.GetCommit", err)
+ ctx.APIErrorInternal(err)
return
}
json, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, commit, nil, toCommitOpts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "toCommit", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, json)
@@ -117,6 +117,16 @@ func GetAllCommits(ctx *context.APIContext) {
// in: query
// description: filepath of a file/dir
// type: string
+ // - name: since
+ // in: query
+ // description: Only commits after this date will be returned (ISO 8601 format)
+ // type: string
+ // format: date-time
+ // - name: until
+ // in: query
+ // description: Only commits before this date will be returned (ISO 8601 format)
+ // type: string
+ // format: date-time
// - name: stat
// in: query
// description: include diff stats for every commit (disable for speedup, default 'true')
@@ -149,6 +159,23 @@ func GetAllCommits(ctx *context.APIContext) {
// "409":
// "$ref": "#/responses/EmptyRepository"
+ since := ctx.FormString("since")
+ until := ctx.FormString("until")
+
+ // Validate since/until as ISO 8601 (RFC3339)
+ if since != "" {
+ if _, err := time.Parse(time.RFC3339, since); err != nil {
+ ctx.APIError(http.StatusUnprocessableEntity, "invalid 'since' format, expected ISO 8601 (RFC3339)")
+ return
+ }
+ }
+ if until != "" {
+ if _, err := time.Parse(time.RFC3339, until); err != nil {
+ ctx.APIError(http.StatusUnprocessableEntity, "invalid 'until' format, expected ISO 8601 (RFC3339)")
+ return
+ }
+ }
+
if ctx.Repo.Repository.IsEmpty {
ctx.JSON(http.StatusConflict, api.APIError{
Message: "Git Repository is empty.",
@@ -180,22 +207,16 @@ func GetAllCommits(ctx *context.APIContext) {
var baseCommit *git.Commit
if len(sha) == 0 {
// no sha supplied - use default branch
- head, err := ctx.Repo.GitRepo.GetHEADBranch()
- if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetHEADBranch", err)
- return
- }
-
- baseCommit, err = ctx.Repo.GitRepo.GetBranchCommit(head.Name)
+ baseCommit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetCommit", err)
+ ctx.APIErrorInternal(err)
return
}
} else {
// get commit specified by sha
baseCommit, err = ctx.Repo.GitRepo.GetCommit(sha)
if err != nil {
- ctx.NotFoundOrServerError("GetCommit", git.IsErrNotExist, err)
+ ctx.NotFoundOrServerError(err)
return
}
}
@@ -205,16 +226,18 @@ func GetAllCommits(ctx *context.APIContext) {
RepoPath: ctx.Repo.GitRepo.Path,
Not: not,
Revision: []string{baseCommit.ID.String()},
+ Since: since,
+ Until: until,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetCommitsCount", err)
+ ctx.APIErrorInternal(err)
return
}
// Query commits
- commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize, not)
+ commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize, not, since, until)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "CommitsByRange", err)
+ ctx.APIErrorInternal(err)
return
}
} else {
@@ -228,13 +251,15 @@ func GetAllCommits(ctx *context.APIContext) {
Not: not,
Revision: []string{sha},
RelPath: []string{path},
+ Since: since,
+ Until: until,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FileCommitsCount", err)
+ ctx.APIErrorInternal(err)
return
} else if commitsCountTotal == 0 {
- ctx.NotFound("FileCommitsCount", nil)
+ ctx.APIErrorNotFound("FileCommitsCount", nil)
return
}
@@ -244,9 +269,11 @@ func GetAllCommits(ctx *context.APIContext) {
File: path,
Not: not,
Page: listOptions.Page,
+ Since: since,
+ Until: until,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "CommitsByFileAndRange", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -259,7 +286,7 @@ func GetAllCommits(ctx *context.APIContext) {
// Create json struct
apiCommits[i], err = convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, commit, userCache, convert.ParseCommitOptions(ctx))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "toCommit", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -317,10 +344,10 @@ func DownloadCommitDiffOrPatch(ctx *context.APIContext) {
if err := git.GetRawDiff(ctx.Repo.GitRepo, sha, diffType, ctx.Resp); err != nil {
if git.IsErrNotExist(err) {
- ctx.NotFound(sha)
+ ctx.APIErrorNotFound("commit doesn't exist: " + sha)
return
}
- ctx.Error(http.StatusInternalServerError, "DownloadCommitDiffOrPatch", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -357,19 +384,19 @@ 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.Error(http.StatusNotFound, "GetPullRequestByMergedCommit", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err = pr.LoadBaseRepo(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
+ ctx.APIErrorInternal(err)
return
}
if err = pr.LoadHeadRepo(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
diff --git a/routers/api/v1/repo/compare.go b/routers/api/v1/repo/compare.go
index a1813a8a76..6d427c8073 100644
--- a/routers/api/v1/repo/compare.go
+++ b/routers/api/v1/repo/compare.go
@@ -47,7 +47,7 @@ func CompareDiff(ctx *context.APIContext) {
var err error
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -82,7 +82,7 @@ func CompareDiff(ctx *context.APIContext) {
Files: files,
})
if err != nil {
- ctx.ServerError("toCommit", err)
+ ctx.APIErrorInternal(err)
return
}
apiCommits = append(apiCommits, apiCommit)
diff --git a/routers/api/v1/repo/download.go b/routers/api/v1/repo/download.go
index e6296c9fe7..acd93ecf2e 100644
--- a/routers/api/v1/repo/download.go
+++ b/routers/api/v1/repo/download.go
@@ -4,7 +4,6 @@
package repo
import (
- "fmt"
"net/http"
"code.gitea.io/gitea/modules/git"
@@ -23,7 +22,7 @@ func DownloadArchive(ctx *context.APIContext) {
case "bundle":
tp = git.ArchiveBundle
default:
- ctx.Error(http.StatusBadRequest, "", fmt.Sprintf("Unknown archive type: %s", ballType))
+ ctx.APIError(http.StatusBadRequest, "Unknown archive type: "+ballType)
return
}
@@ -31,20 +30,20 @@ func DownloadArchive(ctx *context.APIContext) {
var err error
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
+ ctx.APIErrorInternal(err)
return
}
}
r, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*")+"."+tp.String())
if err != nil {
- ctx.ServerError("NewRequest", err)
+ ctx.APIErrorInternal(err)
return
}
archive, err := r.Await(ctx)
if err != nil {
- ctx.ServerError("archive.Await", err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go
index 3eefd2ae29..69b5996222 100644
--- a/routers/api/v1/repo/file.go
+++ b/routers/api/v1/repo/file.go
@@ -16,16 +16,18 @@ import (
git_model "code.gitea.io/gitea/models/git"
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/gitrepo"
"code.gitea.io/gitea/modules/httpcache"
+ "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
"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"
@@ -60,7 +62,7 @@ func GetRawFile(ctx *context.APIContext) {
// required: true
// - name: ref
// in: query
- // description: "The name of the commit/branch/tag. Default the repository’s default branch"
+ // description: "The name of the commit/branch/tag. Default to the repository’s default branch"
// type: string
// required: false
// responses:
@@ -72,7 +74,7 @@ func GetRawFile(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if ctx.Repo.Repository.IsEmpty {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -83,8 +85,8 @@ func GetRawFile(ctx *context.APIContext) {
ctx.RespHeader().Set(giteaObjectTypeHeader, string(files_service.GetObjectTypeFromTreeEntry(entry)))
- if err := common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified); err != nil {
- ctx.Error(http.StatusInternalServerError, "ServeBlob", err)
+ if err := common.ServeBlob(ctx.Base, ctx.Repo.Repository, ctx.Repo.TreePath, blob, lastModified); err != nil {
+ ctx.APIErrorInternal(err)
}
}
@@ -113,7 +115,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
// required: true
// - name: ref
// in: query
- // description: "The name of the commit/branch/tag. Default the repository’s default branch"
+ // description: "The name of the commit/branch/tag. Default to the repository’s default branch"
// type: string
// required: false
// responses:
@@ -125,7 +127,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if ctx.Repo.Repository.IsEmpty {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -137,31 +139,31 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
ctx.RespHeader().Set(giteaObjectTypeHeader, string(files_service.GetObjectTypeFromTreeEntry(entry)))
// LFS Pointer files are at most 1024 bytes - so any blob greater than 1024 bytes cannot be an LFS file
- if blob.Size() > 1024 {
+ if blob.Size() > lfs.MetaFileMaxSize {
// First handle caching for the blob
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
return
}
- // OK not cached - serve!
- if err := common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified); err != nil {
- ctx.ServerError("ServeBlob", err)
+ // If not cached - serve!
+ if err := common.ServeBlob(ctx.Base, ctx.Repo.Repository, ctx.Repo.TreePath, blob, lastModified); err != nil {
+ ctx.APIErrorInternal(err)
}
return
}
- // OK, now the blob is known to have at most 1024 bytes we can simply read this in one go (This saves reading it twice)
+ // OK, now the blob is known to have at most 1024 (lfs pointer max size) bytes,
+ // we can simply read this in one go (This saves reading it twice)
dataRc, err := blob.DataAsync()
if err != nil {
- ctx.ServerError("DataAsync", err)
+ ctx.APIErrorInternal(err)
return
}
- // FIXME: code from #19689, what if the file is large ... OOM ...
buf, err := io.ReadAll(dataRc)
if err != nil {
_ = dataRc.Close()
- ctx.ServerError("DataAsync", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -179,7 +181,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
return
}
- // OK not cached - serve!
+ // If not cached - serve!
common.ServeContentByReader(ctx.Base, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf))
return
}
@@ -197,7 +199,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
common.ServeContentByReader(ctx.Base, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf))
return
} else if err != nil {
- ctx.ServerError("GetLFSMetaObjectByOid", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -217,7 +219,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
lfsDataRc, err := lfs.ReadMetaObject(meta.Pointer)
if err != nil {
- ctx.ServerError("ReadMetaObject", err)
+ ctx.APIErrorInternal(err)
return
}
defer lfsDataRc.Close()
@@ -229,21 +231,21 @@ func getBlobForEntry(ctx *context.APIContext) (blob *git.Blob, entry *git.TreeEn
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
if err != nil {
if git.IsErrNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetTreeEntryByPath", err)
+ ctx.APIErrorInternal(err)
}
return nil, nil, nil
}
if entry.IsDir() || entry.IsSubModule() {
- ctx.NotFound("getBlobForEntry", nil)
+ ctx.APIErrorNotFound("getBlobForEntry", nil)
return nil, nil, nil
}
latestCommit, err := ctx.Repo.GitRepo.GetTreePathLatestCommit(ctx.Repo.Commit.ID.String(), ctx.Repo.TreePath)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetTreePathLatestCommit", err)
+ ctx.APIErrorInternal(err)
return nil, nil, nil
}
when := &latestCommit.Committer.When
@@ -284,7 +286,7 @@ func GetArchive(ctx *context.APIContext) {
var err error
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -296,18 +298,18 @@ func archiveDownload(ctx *context.APIContext) {
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*"))
if err != nil {
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
- ctx.Error(http.StatusBadRequest, "unknown archive format", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else if errors.Is(err, archiver_service.RepoRefNotFoundError{}) {
- ctx.Error(http.StatusNotFound, "unrecognized reference", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.ServerError("archiver_service.NewRequest", err)
+ ctx.APIErrorInternal(err)
}
return
}
archiver, err := aReq.Await(ctx)
if err != nil {
- ctx.ServerError("archiver.Await", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -339,7 +341,7 @@ func download(ctx *context.APIContext, archiveName string, archiver *repo_model.
// If we have matched and access to release or issue
fr, err := storage.RepoArchives.Open(rPath)
if err != nil {
- ctx.ServerError("Open", err)
+ ctx.APIErrorInternal(err)
return
}
defer fr.Close()
@@ -375,7 +377,7 @@ func GetEditorconfig(ctx *context.APIContext) {
// required: true
// - name: ref
// in: query
- // description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)"
+ // description: "The name of the commit/branch/tag. Default to the repository’s default branch."
// type: string
// required: false
// responses:
@@ -387,9 +389,9 @@ func GetEditorconfig(ctx *context.APIContext) {
ec, _, err := ctx.Repo.GetEditorconfig(ctx.Repo.Commit)
if err != nil {
if git.IsErrNotExist(err) {
- ctx.NotFound(err)
+ ctx.APIErrorNotFound(err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetEditorconfig", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -397,24 +399,12 @@ func GetEditorconfig(ctx *context.APIContext) {
fileName := ctx.PathParam("filename")
def, err := ec.GetDefinitionForFilename(fileName)
if def == nil {
- ctx.NotFound(err)
+ ctx.APIErrorNotFound(err)
return
}
ctx.JSON(http.StatusOK, def)
}
-// canWriteFiles returns true if repository is editable and user has proper access level.
-func canWriteFiles(ctx *context.APIContext, branch string) bool {
- return ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, branch) &&
- !ctx.Repo.Repository.IsMirror &&
- !ctx.Repo.Repository.IsArchived
-}
-
-// canReadFiles returns true if repository is readable and user has proper access level.
-func canReadFiles(r *context.Repository) bool {
- return r.Permission.CanRead(unit.TypeCode)
-}
-
func base64Reader(s string) (io.ReadSeeker, error) {
b, err := base64.StdEncoding.DecodeString(s)
if err != nil {
@@ -423,6 +413,45 @@ func base64Reader(s string) (io.ReadSeeker, error) {
return bytes.NewReader(b), nil
}
+func ReqChangeRepoFileOptionsAndCheck(ctx *context.APIContext) {
+ commonOpts := web.GetForm(ctx).(api.FileOptionsInterface).GetFileOptions()
+ commonOpts.BranchName = util.IfZero(commonOpts.BranchName, ctx.Repo.Repository.DefaultBranch)
+ commonOpts.NewBranchName = util.IfZero(commonOpts.NewBranchName, commonOpts.BranchName)
+ if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, commonOpts.NewBranchName) && !ctx.IsUserSiteAdmin() {
+ ctx.APIError(http.StatusForbidden, "user should have a permission to write to the target branch")
+ return
+ }
+ changeFileOpts := &files_service.ChangeRepoFilesOptions{
+ Message: commonOpts.Message,
+ OldBranch: commonOpts.BranchName,
+ NewBranch: commonOpts.NewBranchName,
+ Committer: &files_service.IdentityOptions{
+ GitUserName: commonOpts.Committer.Name,
+ GitUserEmail: commonOpts.Committer.Email,
+ },
+ Author: &files_service.IdentityOptions{
+ GitUserName: commonOpts.Author.Name,
+ GitUserEmail: commonOpts.Author.Email,
+ },
+ Dates: &files_service.CommitDateOptions{
+ Author: commonOpts.Dates.Author,
+ Committer: commonOpts.Dates.Committer,
+ },
+ Signoff: commonOpts.Signoff,
+ }
+ if commonOpts.Dates.Author.IsZero() {
+ commonOpts.Dates.Author = time.Now()
+ }
+ if commonOpts.Dates.Committer.IsZero() {
+ commonOpts.Dates.Committer = time.Now()
+ }
+ ctx.Data["__APIChangeRepoFilesOptions"] = changeFileOpts
+}
+
+func getAPIChangeRepoFileOptions[T api.FileOptionsInterface](ctx *context.APIContext) (apiOpts T, opts *files_service.ChangeRepoFilesOptions) {
+ return web.GetForm(ctx).(T), ctx.Data["__APIChangeRepoFilesOptions"].(*files_service.ChangeRepoFilesOptions)
+}
+
// ChangeFiles handles API call for modifying multiple files
func ChangeFiles(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/contents repository repoChangeFiles
@@ -459,20 +488,18 @@ func ChangeFiles(ctx *context.APIContext) {
// "$ref": "#/responses/error"
// "423":
// "$ref": "#/responses/repoArchivedError"
-
- apiOpts := web.GetForm(ctx).(*api.ChangeFilesOptions)
-
- if apiOpts.BranchName == "" {
- apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
+ apiOpts, opts := getAPIChangeRepoFileOptions[*api.ChangeFilesOptions](ctx)
+ if ctx.Written() {
+ return
}
-
- var files []*files_service.ChangeRepoFile
for _, file := range apiOpts.Files {
contentReader, err := base64Reader(file.ContentBase64)
if err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "Invalid base64 content", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
+ // FIXME: ChangeFileOperation.SHA is NOT required for update or delete if last commit is provided in the options
+ // But the LastCommitID is not provided in the API options, need to fully fix them in API
changeRepoFile := &files_service.ChangeRepoFile{
Operation: file.Operation,
TreePath: file.Path,
@@ -480,41 +507,15 @@ func ChangeFiles(ctx *context.APIContext) {
ContentReader: contentReader,
SHA: file.SHA,
}
- files = append(files, changeRepoFile)
- }
-
- opts := &files_service.ChangeRepoFilesOptions{
- Files: files,
- Message: apiOpts.Message,
- OldBranch: apiOpts.BranchName,
- NewBranch: apiOpts.NewBranchName,
- Committer: &files_service.IdentityOptions{
- Name: apiOpts.Committer.Name,
- Email: apiOpts.Committer.Email,
- },
- Author: &files_service.IdentityOptions{
- Name: apiOpts.Author.Name,
- Email: apiOpts.Author.Email,
- },
- Dates: &files_service.CommitDateOptions{
- Author: apiOpts.Dates.Author,
- Committer: apiOpts.Dates.Committer,
- },
- Signoff: apiOpts.Signoff,
- }
- if opts.Dates.Author.IsZero() {
- opts.Dates.Author = time.Now()
- }
- if opts.Dates.Committer.IsZero() {
- opts.Dates.Committer = time.Now()
+ opts.Files = append(opts.Files, changeRepoFile)
}
if opts.Message == "" {
- opts.Message = changeFilesCommitMessage(ctx, files)
+ opts.Message = changeFilesCommitMessage(ctx, opts.Files)
}
- if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
- handleCreateOrUpdateFileError(ctx, err)
+ if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
+ handleChangeRepoFilesError(ctx, err)
} else {
ctx.JSON(http.StatusCreated, filesResponse)
}
@@ -562,56 +563,27 @@ func CreateFile(ctx *context.APIContext) {
// "423":
// "$ref": "#/responses/repoArchivedError"
- apiOpts := web.GetForm(ctx).(*api.CreateFileOptions)
-
- if apiOpts.BranchName == "" {
- apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
+ apiOpts, opts := getAPIChangeRepoFileOptions[*api.CreateFileOptions](ctx)
+ if ctx.Written() {
+ return
}
-
contentReader, err := base64Reader(apiOpts.ContentBase64)
if err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "Invalid base64 content", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- opts := &files_service.ChangeRepoFilesOptions{
- Files: []*files_service.ChangeRepoFile{
- {
- Operation: "create",
- TreePath: ctx.PathParam("*"),
- ContentReader: contentReader,
- },
- },
- Message: apiOpts.Message,
- OldBranch: apiOpts.BranchName,
- NewBranch: apiOpts.NewBranchName,
- Committer: &files_service.IdentityOptions{
- Name: apiOpts.Committer.Name,
- Email: apiOpts.Committer.Email,
- },
- Author: &files_service.IdentityOptions{
- Name: apiOpts.Author.Name,
- Email: apiOpts.Author.Email,
- },
- Dates: &files_service.CommitDateOptions{
- Author: apiOpts.Dates.Author,
- Committer: apiOpts.Dates.Committer,
- },
- Signoff: apiOpts.Signoff,
- }
- if opts.Dates.Author.IsZero() {
- opts.Dates.Author = time.Now()
- }
- if opts.Dates.Committer.IsZero() {
- opts.Dates.Committer = time.Now()
- }
-
+ opts.Files = append(opts.Files, &files_service.ChangeRepoFile{
+ Operation: "create",
+ TreePath: ctx.PathParam("*"),
+ ContentReader: contentReader,
+ })
if opts.Message == "" {
opts.Message = changeFilesCommitMessage(ctx, opts.Files)
}
- if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
- handleCreateOrUpdateFileError(ctx, err)
+ if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
+ handleChangeRepoFilesError(ctx, err)
} else {
fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
ctx.JSON(http.StatusCreated, fileResponse)
@@ -659,96 +631,55 @@ func UpdateFile(ctx *context.APIContext) {
// "$ref": "#/responses/error"
// "423":
// "$ref": "#/responses/repoArchivedError"
- apiOpts := web.GetForm(ctx).(*api.UpdateFileOptions)
- if ctx.Repo.Repository.IsEmpty {
- ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty"))
- return
- }
- if apiOpts.BranchName == "" {
- apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
+ apiOpts, opts := getAPIChangeRepoFileOptions[*api.UpdateFileOptions](ctx)
+ if ctx.Written() {
+ return
}
-
contentReader, err := base64Reader(apiOpts.ContentBase64)
if err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "Invalid base64 content", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
-
- opts := &files_service.ChangeRepoFilesOptions{
- Files: []*files_service.ChangeRepoFile{
- {
- Operation: "update",
- ContentReader: contentReader,
- SHA: apiOpts.SHA,
- FromTreePath: apiOpts.FromPath,
- TreePath: ctx.PathParam("*"),
- },
- },
- Message: apiOpts.Message,
- OldBranch: apiOpts.BranchName,
- NewBranch: apiOpts.NewBranchName,
- Committer: &files_service.IdentityOptions{
- Name: apiOpts.Committer.Name,
- Email: apiOpts.Committer.Email,
- },
- Author: &files_service.IdentityOptions{
- Name: apiOpts.Author.Name,
- Email: apiOpts.Author.Email,
- },
- Dates: &files_service.CommitDateOptions{
- Author: apiOpts.Dates.Author,
- Committer: apiOpts.Dates.Committer,
- },
- Signoff: apiOpts.Signoff,
- }
- if opts.Dates.Author.IsZero() {
- opts.Dates.Author = time.Now()
- }
- if opts.Dates.Committer.IsZero() {
- opts.Dates.Committer = time.Now()
- }
-
+ opts.Files = append(opts.Files, &files_service.ChangeRepoFile{
+ Operation: "update",
+ ContentReader: contentReader,
+ SHA: apiOpts.SHA,
+ FromTreePath: apiOpts.FromPath,
+ TreePath: ctx.PathParam("*"),
+ })
if opts.Message == "" {
opts.Message = changeFilesCommitMessage(ctx, opts.Files)
}
- if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
- handleCreateOrUpdateFileError(ctx, err)
+ if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
+ handleChangeRepoFilesError(ctx, err)
} else {
fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
ctx.JSON(http.StatusOK, fileResponse)
}
}
-func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) {
+func handleChangeRepoFilesError(ctx *context.APIContext, err error) {
if files_service.IsErrUserCannotCommit(err) || pull_service.IsErrFilePathProtected(err) {
- ctx.Error(http.StatusForbidden, "Access", err)
+ ctx.APIError(http.StatusForbidden, err)
return
}
if git_model.IsErrBranchAlreadyExists(err) || files_service.IsErrFilenameInvalid(err) || pull_service.IsErrSHADoesNotMatch(err) ||
- files_service.IsErrFilePathInvalid(err) || files_service.IsErrRepoFileAlreadyExists(err) {
- ctx.Error(http.StatusUnprocessableEntity, "Invalid", err)
+ files_service.IsErrFilePathInvalid(err) || files_service.IsErrRepoFileAlreadyExists(err) ||
+ files_service.IsErrCommitIDDoesNotMatch(err) || files_service.IsErrSHAOrCommitIDNotProvided(err) {
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) {
- ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err)
+ if git.IsErrBranchNotExist(err) || files_service.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) {
+ ctx.APIError(http.StatusNotFound, err)
return
}
-
- ctx.Error(http.StatusInternalServerError, "UpdateFile", err)
-}
-
-// Called from both CreateFile or UpdateFile to handle both
-func createOrUpdateFiles(ctx *context.APIContext, opts *files_service.ChangeRepoFilesOptions) (*api.FilesResponse, error) {
- if !canWriteFiles(ctx, opts.OldBranch) {
- return nil, repo_model.ErrUserDoesNotHaveAccessToRepo{
- UserID: ctx.Doer.ID,
- RepoName: ctx.Repo.Repository.LowerName,
- }
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.APIError(http.StatusNotFound, err)
+ return
}
-
- return files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts)
+ ctx.APIErrorInternal(err)
}
// format commit message if empty
@@ -762,7 +693,7 @@ func changeFilesCommitMessage(ctx *context.APIContext, files []*files_service.Ch
switch file.Operation {
case "create":
createFiles = append(createFiles, file.TreePath)
- case "update":
+ case "update", "upload", "rename": // upload and rename works like "update", there is no translation for them at the moment
updateFiles = append(updateFiles, file.TreePath)
case "delete":
deleteFiles = append(deleteFiles, file.TreePath)
@@ -820,85 +751,119 @@ func DeleteFile(ctx *context.APIContext) {
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/error"
+ // "422":
+ // "$ref": "#/responses/error"
// "423":
// "$ref": "#/responses/repoArchivedError"
- apiOpts := web.GetForm(ctx).(*api.DeleteFileOptions)
- if !canWriteFiles(ctx, apiOpts.BranchName) {
- ctx.Error(http.StatusForbidden, "DeleteFile", repo_model.ErrUserDoesNotHaveAccessToRepo{
- UserID: ctx.Doer.ID,
- RepoName: ctx.Repo.Repository.LowerName,
- })
+ apiOpts, opts := getAPIChangeRepoFileOptions[*api.DeleteFileOptions](ctx)
+ if ctx.Written() {
return
}
- if apiOpts.BranchName == "" {
- apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
- }
-
- opts := &files_service.ChangeRepoFilesOptions{
- Files: []*files_service.ChangeRepoFile{
- {
- Operation: "delete",
- SHA: apiOpts.SHA,
- TreePath: ctx.PathParam("*"),
- },
- },
- Message: apiOpts.Message,
- OldBranch: apiOpts.BranchName,
- NewBranch: apiOpts.NewBranchName,
- Committer: &files_service.IdentityOptions{
- Name: apiOpts.Committer.Name,
- Email: apiOpts.Committer.Email,
- },
- Author: &files_service.IdentityOptions{
- Name: apiOpts.Author.Name,
- Email: apiOpts.Author.Email,
- },
- Dates: &files_service.CommitDateOptions{
- Author: apiOpts.Dates.Author,
- Committer: apiOpts.Dates.Committer,
- },
- Signoff: apiOpts.Signoff,
- }
- if opts.Dates.Author.IsZero() {
- opts.Dates.Author = time.Now()
- }
- if opts.Dates.Committer.IsZero() {
- opts.Dates.Committer = time.Now()
- }
-
+ opts.Files = append(opts.Files, &files_service.ChangeRepoFile{
+ Operation: "delete",
+ SHA: apiOpts.SHA,
+ TreePath: ctx.PathParam("*"),
+ })
if opts.Message == "" {
opts.Message = changeFilesCommitMessage(ctx, opts.Files)
}
if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
- if git.IsErrBranchNotExist(err) || files_service.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) {
- ctx.Error(http.StatusNotFound, "DeleteFile", err)
- return
- } else if git_model.IsErrBranchAlreadyExists(err) ||
- files_service.IsErrFilenameInvalid(err) ||
- pull_service.IsErrSHADoesNotMatch(err) ||
- files_service.IsErrCommitIDDoesNotMatch(err) ||
- files_service.IsErrSHAOrCommitIDNotProvided(err) {
- ctx.Error(http.StatusBadRequest, "DeleteFile", err)
- return
- } else if files_service.IsErrUserCannotCommit(err) {
- ctx.Error(http.StatusForbidden, "DeleteFile", err)
- return
- }
- ctx.Error(http.StatusInternalServerError, "DeleteFile", err)
+ handleChangeRepoFilesError(ctx, err)
} else {
fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
ctx.JSON(http.StatusOK, fileResponse) // FIXME on APIv2: return http.StatusNoContent
}
}
-// GetContents Get the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir
+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)
+ }
+ return refCommit
+}
+
+func GetContentsExt(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/contents-ext/{filepath} repository repoGetContentsExt
+ // ---
+ // summary: The extended "contents" API, to get file metadata and/or content, or list a directory.
+ // description: It guarantees that only one of the response fields is set if the request succeeds.
+ // Users can pass "includes=file_content" or "includes=lfs_metadata" to retrieve more fields.
+ // "includes=file_content" only works for single file, if you need to retrieve file contents in batch,
+ // use "file-contents" API after listing the directory.
+ // 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: filepath
+ // in: path
+ // description: path of the dir, file, symlink or submodule in the repo. Swagger requires path parameter to be "required",
+ // you can leave it empty or pass a single dot (".") to get the root directory.
+ // type: string
+ // required: true
+ // - name: ref
+ // in: query
+ // description: the name of the commit/branch/tag, default to the repository’s default branch.
+ // type: string
+ // required: false
+ // - name: includes
+ // in: query
+ // description: By default this API's response only contains file's metadata. Use comma-separated "includes" options to retrieve more fields.
+ // Option "file_content" will try to retrieve the file content, "lfs_metadata" will try to retrieve LFS metadata,
+ // "commit_metadata" will try to retrieve commit metadata, and "commit_message" will try to retrieve commit message.
+ // type: string
+ // required: false
+ // responses:
+ // "200":
+ // "$ref": "#/responses/ContentsExtResponse"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ if treePath := ctx.PathParam("*"); treePath == "." || treePath == "/" {
+ ctx.SetPathParam("*", "") // workaround for swagger, it requires path parameter to be "required", but we need to list root directory
+ }
+ opts := files_service.GetContentsOrListOptions{TreePath: ctx.PathParam("*")}
+ for includeOpt := range strings.SplitSeq(ctx.FormString("includes"), ",") {
+ if includeOpt == "" {
+ continue
+ }
+ switch includeOpt {
+ case "file_content":
+ opts.IncludeSingleFileContent = true
+ case "lfs_metadata":
+ opts.IncludeLfsMetadata = true
+ case "commit_metadata":
+ opts.IncludeCommitMetadata = true
+ case "commit_message":
+ opts.IncludeCommitMessage = true
+ default:
+ ctx.APIError(http.StatusBadRequest, fmt.Sprintf("unknown include option %q", includeOpt))
+ return
+ }
+ }
+ ctx.JSON(http.StatusOK, getRepoContents(ctx, opts))
+}
+
func GetContents(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/contents/{filepath} repository repoGetContents
// ---
- // summary: Gets the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir
+ // summary: Gets the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir.
+ // description: This API follows GitHub's design, and it is not easy to use. Recommend users to use the "contents-ext" API instead.
// produces:
// - application/json
// parameters:
@@ -919,7 +884,7 @@ func GetContents(ctx *context.APIContext) {
// required: true
// - name: ref
// in: query
- // description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)"
+ // description: "The name of the commit/branch/tag. Default to the repository’s default branch."
// type: string
// required: false
// responses:
@@ -927,34 +892,38 @@ func GetContents(ctx *context.APIContext) {
// "$ref": "#/responses/ContentsResponse"
// "404":
// "$ref": "#/responses/notFound"
-
- if !canReadFiles(ctx.Repo) {
- ctx.Error(http.StatusInternalServerError, "GetContentsOrList", repo_model.ErrUserDoesNotHaveAccessToRepo{
- UserID: ctx.Doer.ID,
- RepoName: ctx.Repo.Repository.LowerName,
- })
+ ret := getRepoContents(ctx, files_service.GetContentsOrListOptions{
+ TreePath: ctx.PathParam("*"),
+ IncludeSingleFileContent: true,
+ IncludeCommitMetadata: true,
+ })
+ if ctx.Written() {
return
}
+ ctx.JSON(http.StatusOK, util.Iif[any](ret.FileContents != nil, ret.FileContents, ret.DirContents))
+}
- treePath := ctx.PathParam("*")
- ref := ctx.FormTrim("ref")
-
- if fileList, err := files_service.GetContentsOrList(ctx, ctx.Repo.Repository, treePath, ref); err != nil {
+func getRepoContents(ctx *context.APIContext, opts files_service.GetContentsOrListOptions) *api.ContentsExtResponse {
+ refCommit := resolveRefCommit(ctx, ctx.FormTrim("ref"))
+ if ctx.Written() {
+ return nil
+ }
+ ret, err := files_service.GetContentsOrList(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, refCommit, opts)
+ if err != nil {
if git.IsErrNotExist(err) {
- ctx.NotFound("GetContentsOrList", err)
- return
+ ctx.APIErrorNotFound("GetContentsOrList", err)
+ return nil
}
- ctx.Error(http.StatusInternalServerError, "GetContentsOrList", err)
- } else {
- ctx.JSON(http.StatusOK, fileList)
+ ctx.APIErrorInternal(err)
}
+ return &ret
}
-// GetContentsList Get the metadata of all the entries of the root dir
func GetContentsList(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/contents repository repoGetContentsList
// ---
- // summary: Gets the metadata of all the entries of the root dir
+ // summary: Gets the metadata of all the entries of the root dir.
+ // description: This API follows GitHub's design, and it is not easy to use. Recommend users to use our "contents-ext" API instead.
// produces:
// - application/json
// parameters:
@@ -970,7 +939,7 @@ func GetContentsList(ctx *context.APIContext) {
// required: true
// - name: ref
// in: query
- // description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)"
+ // description: "The name of the commit/branch/tag. Default to the repository’s default branch."
// type: string
// required: false
// responses:
@@ -982,3 +951,102 @@ func GetContentsList(ctx *context.APIContext) {
// same as GetContents(), this function is here because swagger fails if path is empty in GetContents() interface
GetContents(ctx)
}
+
+func GetFileContentsGet(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/file-contents repository repoGetFileContents
+ // ---
+ // summary: Get the metadata and contents of requested files
+ // description: See the POST method. This GET method supports using JSON encoded request body in query parameter.
+ // 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: ref
+ // in: query
+ // description: "The name of the commit/branch/tag. Default to the repository’s default branch."
+ // type: string
+ // required: false
+ // - name: body
+ // in: query
+ // description: "The JSON encoded body (see the POST request): {\"files\": [\"filename1\", \"filename2\"]}"
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/ContentsListResponse"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ // The POST method requires "write" permission, so we also support this "GET" method
+ handleGetFileContents(ctx)
+}
+
+func GetFileContentsPost(ctx *context.APIContext) {
+ // swagger:operation POST /repos/{owner}/{repo}/file-contents repository repoGetFileContentsPost
+ // ---
+ // summary: Get the metadata and contents of requested files
+ // description: Uses automatic pagination based on default page size and
+ // max response size and returns the maximum allowed number of files.
+ // Files which could not be retrieved are null. Files which are too large
+ // are being returned with `encoding == null`, `content == null` and `size > 0`,
+ // they can be requested separately by using the `download_url`.
+ // 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: ref
+ // in: query
+ // description: "The name of the commit/branch/tag. Default to the repository’s default branch."
+ // type: string
+ // required: false
+ // - name: body
+ // in: body
+ // required: true
+ // schema:
+ // "$ref": "#/definitions/GetFilesOptions"
+ // responses:
+ // "200":
+ // "$ref": "#/responses/ContentsListResponse"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ // This is actually a "read" request, but we need to accept a "files" list, then POST method seems easy to use.
+ // But the permission system requires that the caller must have "write" permission to use POST method.
+ // At the moment, there is no other way to get around the permission check, so there is a "GET" workaround method above.
+ handleGetFileContents(ctx)
+}
+
+func handleGetFileContents(ctx *context.APIContext) {
+ opts, ok := web.GetForm(ctx).(*api.GetFilesOptions)
+ if !ok {
+ err := json.Unmarshal(util.UnsafeStringToBytes(ctx.FormString("body")), &opts)
+ if err != nil {
+ ctx.APIError(http.StatusBadRequest, "invalid body parameter")
+ return
+ }
+ }
+ refCommit := resolveRefCommit(ctx, ctx.FormTrim("ref"))
+ if ctx.Written() {
+ return
+ }
+ filesResponse := files_service.GetContentsListFromTreePaths(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, refCommit, opts.Files)
+ ctx.JSON(http.StatusOK, util.SliceNilAsEmpty(filesResponse))
+}
diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go
index 14a1a8d1c4..58f66954e1 100644
--- a/routers/api/v1/repo/fork.go
+++ b/routers/api/v1/repo/fork.go
@@ -57,15 +57,15 @@ func ListForks(ctx *context.APIContext) {
forks, total, err := repo_service.FindForks(ctx, ctx.Repo.Repository, ctx.Doer, utils.GetListOptions(ctx))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FindForks", err)
+ ctx.APIErrorInternal(err)
return
}
if err := repo_model.RepositoryList(forks).LoadOwners(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadOwners", err)
+ ctx.APIErrorInternal(err)
return
}
if err := repo_model.RepositoryList(forks).LoadUnits(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadUnits", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -73,7 +73,7 @@ func ListForks(ctx *context.APIContext) {
for i, fork := range forks {
permission, err := access_model.GetUserRepoPermission(ctx, fork, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
+ ctx.APIErrorInternal(err)
return
}
apiForks[i] = convert.ToRepo(ctx, fork, permission)
@@ -126,19 +126,21 @@ func CreateFork(ctx *context.APIContext) {
org, err := organization.GetOrgByName(ctx, *form.Organization)
if err != nil {
if organization.IsErrOrgNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
- isMember, err := org.IsOrgMember(ctx, ctx.Doer.ID)
- if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
- return
- } else if !isMember {
- ctx.Error(http.StatusForbidden, "isMemberNot", fmt.Sprintf("User is no Member of Organisation '%s'", org.Name))
- return
+ if !ctx.Doer.IsAdmin {
+ isMember, err := org.IsOrgMember(ctx, ctx.Doer.ID)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ } else if !isMember {
+ ctx.APIError(http.StatusForbidden, fmt.Sprintf("User is no Member of Organisation '%s'", org.Name))
+ return
+ }
}
forker = org.AsUser()
}
@@ -157,11 +159,11 @@ func CreateFork(ctx *context.APIContext) {
})
if err != nil {
if errors.Is(err, util.ErrAlreadyExist) || repo_model.IsErrReachLimitOfRepo(err) {
- ctx.Error(http.StatusConflict, "ForkRepository", err)
+ ctx.APIError(http.StatusConflict, err)
} else if errors.Is(err, user_model.ErrBlockedUser) {
- ctx.Error(http.StatusForbidden, "ForkRepository", err)
+ ctx.APIError(http.StatusForbidden, err)
} else {
- ctx.Error(http.StatusInternalServerError, "ForkRepository", err)
+ ctx.APIErrorInternal(err)
}
return
}
diff --git a/routers/api/v1/repo/git_hook.go b/routers/api/v1/repo/git_hook.go
index 868acf3d85..487c74e183 100644
--- a/routers/api/v1/repo/git_hook.go
+++ b/routers/api/v1/repo/git_hook.go
@@ -40,7 +40,7 @@ func ListGitHooks(ctx *context.APIContext) {
hooks, err := ctx.Repo.GitRepo.Hooks()
if err != nil {
- ctx.Error(http.StatusInternalServerError, "Hooks", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -84,9 +84,9 @@ func GetGitHook(ctx *context.APIContext) {
hook, err := ctx.Repo.GitRepo.GetHook(hookID)
if err != nil {
if errors.Is(err, git.ErrNotValidHook) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetHook", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -131,16 +131,16 @@ func EditGitHook(ctx *context.APIContext) {
hook, err := ctx.Repo.GitRepo.GetHook(hookID)
if err != nil {
if errors.Is(err, git.ErrNotValidHook) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetHook", err)
+ ctx.APIErrorInternal(err)
}
return
}
hook.Content = form.Content
if err = hook.Update(); err != nil {
- ctx.Error(http.StatusInternalServerError, "hook.Update", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -180,16 +180,16 @@ func DeleteGitHook(ctx *context.APIContext) {
hook, err := ctx.Repo.GitRepo.GetHook(hookID)
if err != nil {
if errors.Is(err, git.ErrNotValidHook) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetHook", err)
+ ctx.APIErrorInternal(err)
}
return
}
hook.Content = ""
if err = hook.Update(); err != nil {
- ctx.Error(http.StatusInternalServerError, "hook.Update", err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/repo/git_ref.go b/routers/api/v1/repo/git_ref.go
index 1743c0fc20..f042e9e344 100644
--- a/routers/api/v1/repo/git_ref.go
+++ b/routers/api/v1/repo/git_ref.go
@@ -4,6 +4,7 @@
package repo
import (
+ "fmt"
"net/http"
"net/url"
@@ -77,12 +78,12 @@ func GetGitRefs(ctx *context.APIContext) {
func getGitRefsInternal(ctx *context.APIContext, filter string) {
refs, lastMethodName, err := utils.GetGitRefs(ctx, filter)
if err != nil {
- ctx.Error(http.StatusInternalServerError, lastMethodName, err)
+ ctx.APIErrorInternal(fmt.Errorf("%s: %w", lastMethodName, err))
return
}
if len(refs) == 0 {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
diff --git a/routers/api/v1/repo/hook.go b/routers/api/v1/repo/hook.go
index 03143c8f99..ac47e15d64 100644
--- a/routers/api/v1/repo/hook.go
+++ b/routers/api/v1/repo/hook.go
@@ -61,7 +61,7 @@ func ListHooks(ctx *context.APIContext) {
hooks, count, err := db.FindAndCount[webhook.Webhook](ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -69,7 +69,7 @@ func ListHooks(ctx *context.APIContext) {
for i := range hooks {
apiHooks[i], err = webhook_service.ToHook(ctx.Repo.RepoLink, hooks[i])
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -116,7 +116,7 @@ func GetHook(ctx *context.APIContext) {
}
apiHook, err := webhook_service.ToHook(repo.RepoLink, hook)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, apiHook)
@@ -189,7 +189,7 @@ func TestHook(ctx *context.APIContext) {
Pusher: convert.ToUserWithAccessMode(ctx, ctx.Doer, perm.AccessModeNone),
Sender: convert.ToUserWithAccessMode(ctx, ctx.Doer, perm.AccessModeNone),
}); err != nil {
- ctx.Error(http.StatusInternalServerError, "PrepareWebhook: ", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -298,9 +298,9 @@ func DeleteHook(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if err := webhook.DeleteWebhookByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id")); err != nil {
if webhook.IsErrWebhookNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteWebhookByRepoID", err)
+ ctx.APIErrorInternal(err)
}
return
}
diff --git a/routers/api/v1/repo/hook_test.go b/routers/api/v1/repo/hook_test.go
index c659a16f54..f8d61ccf00 100644
--- a/routers/api/v1/repo/hook_test.go
+++ b/routers/api/v1/repo/hook_test.go
@@ -23,7 +23,7 @@ func TestTestHook(t *testing.T) {
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
TestHook(ctx)
- assert.EqualValues(t, http.StatusNoContent, ctx.Resp.Status())
+ assert.Equal(t, http.StatusNoContent, ctx.Resp.WrittenStatus())
unittest.AssertExistsAndLoadBean(t, &webhook.HookTask{
HookID: 1,
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go
index 86dbcee5f7..d4a5872fd1 100644
--- a/routers/api/v1/repo/issue.go
+++ b/routers/api/v1/repo/issue.go
@@ -132,7 +132,7 @@ func SearchIssues(ctx *context.APIContext) {
before, since, err := context.GetQueryBeforeSince(ctx.Base)
if err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
@@ -152,7 +152,7 @@ func SearchIssues(ctx *context.APIContext) {
)
{
// find repos user can access (for issue search)
- opts := &repo_model.SearchRepoOptions{
+ opts := repo_model.SearchRepoOptions{
Private: false,
AllPublic: true,
TopicOnly: false,
@@ -170,9 +170,9 @@ func SearchIssues(ctx *context.APIContext) {
owner, err := user_model.GetUserByName(ctx, ctx.FormString("owner"))
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusBadRequest, "Owner not found", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -183,15 +183,15 @@ func SearchIssues(ctx *context.APIContext) {
}
if ctx.FormString("team") != "" {
if ctx.FormString("owner") == "" {
- ctx.Error(http.StatusBadRequest, "", "Owner organisation is required for filtering on team")
+ ctx.APIError(http.StatusBadRequest, "Owner organisation is required for filtering on team")
return
}
team, err := organization.GetTeam(ctx, opts.OwnerID, ctx.FormString("team"))
if err != nil {
if organization.IsErrTeamNotExist(err) {
- ctx.Error(http.StatusBadRequest, "Team not found", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -204,7 +204,7 @@ func SearchIssues(ctx *context.APIContext) {
}
repoIDs, _, err = repo_model.SearchRepositoryIDs(ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "SearchRepositoryIDs", err)
+ ctx.APIErrorInternal(err)
return
}
if len(repoIDs) == 0 {
@@ -237,7 +237,7 @@ func SearchIssues(ctx *context.APIContext) {
}
includedAnyLabels, err = issues_model.GetLabelIDsByNames(ctx, includedLabelNames)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetLabelIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -251,7 +251,7 @@ func SearchIssues(ctx *context.APIContext) {
}
includedMilestones, err = issues_model.GetMilestoneIDsByNames(ctx, includedMilestoneNames)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetMilestoneIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -290,10 +290,10 @@ func SearchIssues(ctx *context.APIContext) {
if ctx.IsSigned {
ctxUserID := ctx.Doer.ID
if ctx.FormBool("created") {
- searchOpt.PosterID = optional.Some(ctxUserID)
+ searchOpt.PosterID = strconv.FormatInt(ctxUserID, 10)
}
if ctx.FormBool("assigned") {
- searchOpt.AssigneeID = optional.Some(ctxUserID)
+ searchOpt.AssigneeID = strconv.FormatInt(ctxUserID, 10)
}
if ctx.FormBool("mentioned") {
searchOpt.MentionID = optional.Some(ctxUserID)
@@ -312,12 +312,12 @@ func SearchIssues(ctx *context.APIContext) {
ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "SearchIssues", err)
+ ctx.APIErrorInternal(err)
return
}
issues, err := issues_model.GetIssuesByIDs(ctx, ids, true)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FindIssuesByIDs", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -405,7 +405,7 @@ func ListIssues(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
before, since, err := context.GetQueryBeforeSince(ctx.Base)
if err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
@@ -428,7 +428,7 @@ func ListIssues(ctx *context.APIContext) {
if splitted := strings.Split(ctx.FormString("labels"), ","); len(splitted) > 0 {
labelIDs, err = issues_model.GetLabelIDsInRepoByNames(ctx, ctx.Repo.Repository.ID, splitted)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetLabelIDsInRepoByNames", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -444,7 +444,7 @@ func ListIssues(ctx *context.APIContext) {
continue
}
if !issues_model.IsErrMilestoneNotExist(err) {
- ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoIDANDName", err)
+ ctx.APIErrorInternal(err)
return
}
id, err := strconv.ParseInt(part[i], 10, 64)
@@ -459,7 +459,7 @@ func ListIssues(ctx *context.APIContext) {
if issues_model.IsErrMilestoneNotExist(err) {
continue
}
- ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
+ ctx.APIErrorInternal(err)
}
}
@@ -474,7 +474,7 @@ func ListIssues(ctx *context.APIContext) {
}
if isPull.Has() && !ctx.Repo.CanReadIssuesOrPulls(isPull.Value()) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -482,7 +482,7 @@ func ListIssues(ctx *context.APIContext) {
canReadIssues := ctx.Repo.CanRead(unit.TypeIssues)
canReadPulls := ctx.Repo.CanRead(unit.TypePullRequests)
if !canReadIssues && !canReadPulls {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
} else if !canReadIssues {
isPull = optional.Some(true)
@@ -538,10 +538,10 @@ func ListIssues(ctx *context.APIContext) {
}
if createdByID > 0 {
- searchOpt.PosterID = optional.Some(createdByID)
+ searchOpt.PosterID = strconv.FormatInt(createdByID, 10)
}
if assignedByID > 0 {
- searchOpt.AssigneeID = optional.Some(assignedByID)
+ searchOpt.AssigneeID = strconv.FormatInt(assignedByID, 10)
}
if mentionedByID > 0 {
searchOpt.MentionID = optional.Some(mentionedByID)
@@ -549,12 +549,12 @@ func ListIssues(ctx *context.APIContext) {
ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "SearchIssues", err)
+ ctx.APIErrorInternal(err)
return
}
issues, err := issues_model.GetIssuesByIDs(ctx, ids, true)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FindIssuesByIDs", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -571,12 +571,12 @@ func getUserIDForFilter(ctx *context.APIContext, queryName string) int64 {
user, err := user_model.GetUserByName(ctx, userName)
if user_model.IsErrUserNotExist(err) {
- ctx.NotFound(err)
+ ctx.APIErrorNotFound(err)
return 0
}
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return 0
}
@@ -616,14 +616,14 @@ func GetIssue(ctx *context.APIContext) {
issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, ctx.Doer, issue))
@@ -691,9 +691,9 @@ func CreateIssue(ctx *context.APIContext) {
assigneeIDs, err = issues_model.MakeIDsFromAPIAssigneesToAdd(ctx, form.Assignee, form.Assignees)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("Assignee does not exist: [name: %s]", err))
} else {
- ctx.Error(http.StatusInternalServerError, "AddAssigneeByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -702,17 +702,17 @@ func CreateIssue(ctx *context.APIContext) {
for _, aID := range assigneeIDs {
assignee, err := user_model.GetUserByID(ctx, aID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserByID", err)
+ ctx.APIErrorInternal(err)
return
}
valid, err := access_model.CanBeAssigned(ctx, assignee, ctx.Repo.Repository, false)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "canBeAssigned", err)
+ ctx.APIErrorInternal(err)
return
}
if !valid {
- ctx.Error(http.StatusUnprocessableEntity, "canBeAssigned", repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: ctx.Repo.Repository.Name})
+ ctx.APIError(http.StatusUnprocessableEntity, repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: ctx.Repo.Repository.Name})
return
}
}
@@ -723,11 +723,11 @@ func CreateIssue(ctx *context.APIContext) {
if err := issue_service.NewIssue(ctx, ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs, 0); err != nil {
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) {
- ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else if errors.Is(err, user_model.ErrBlockedUser) {
- ctx.Error(http.StatusForbidden, "NewIssue", err)
+ ctx.APIError(http.StatusForbidden, err)
} else {
- ctx.Error(http.StatusInternalServerError, "NewIssue", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -735,10 +735,10 @@ func CreateIssue(ctx *context.APIContext) {
if form.Closed {
if err := issue_service.CloseIssue(ctx, issue, ctx.Doer, ""); err != nil {
if issues_model.IsErrDependenciesLeft(err) {
- ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
+ ctx.APIError(http.StatusPreconditionFailed, "cannot close this issue because it still has open dependencies")
return
}
- ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -746,7 +746,7 @@ func CreateIssue(ctx *context.APIContext) {
// Refetch from database to assign some automatic values
issue, err = issues_model.GetIssueByID(ctx, issue.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetIssueByID", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, ctx.Doer, issue))
@@ -796,9 +796,9 @@ func EditIssue(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -807,7 +807,7 @@ func EditIssue(ctx *context.APIContext) {
err = issue.LoadAttributes(ctx)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -819,7 +819,7 @@ func EditIssue(ctx *context.APIContext) {
if len(form.Title) > 0 {
err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ChangeTitle", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -827,18 +827,18 @@ func EditIssue(ctx *context.APIContext) {
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, issue.ContentVersion)
if err != nil {
if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
- ctx.Error(http.StatusBadRequest, "ChangeContent", err)
+ ctx.APIError(http.StatusBadRequest, err)
return
}
- ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
+ ctx.APIErrorInternal(err)
return
}
}
if form.Ref != nil {
err = issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, *form.Ref)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateRef", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -849,7 +849,7 @@ func EditIssue(ctx *context.APIContext) {
if form.RemoveDeadline == nil || !*form.RemoveDeadline {
if form.Deadline == nil {
- ctx.Error(http.StatusBadRequest, "", "The due_date cannot be empty")
+ ctx.APIError(http.StatusBadRequest, "The due_date cannot be empty")
return
}
if !form.Deadline.IsZero() {
@@ -860,7 +860,7 @@ func EditIssue(ctx *context.APIContext) {
}
if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err)
+ ctx.APIErrorInternal(err)
return
}
issue.DeadlineUnix = deadlineUnix
@@ -883,9 +883,9 @@ 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.Error(http.StatusForbidden, "UpdateAssignees", err)
+ ctx.APIError(http.StatusForbidden, err)
} else {
- ctx.Error(http.StatusInternalServerError, "UpdateAssignees", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -895,19 +895,28 @@ func EditIssue(ctx *context.APIContext) {
issue.MilestoneID != *form.Milestone {
oldMilestoneID := issue.MilestoneID
issue.MilestoneID = *form.Milestone
+ if issue.MilestoneID > 0 {
+ issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, *form.Milestone)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ } else {
+ issue.Milestone = nil
+ }
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
- ctx.Error(http.StatusInternalServerError, "ChangeMilestoneAssign", err)
+ ctx.APIErrorInternal(err)
return
}
}
if form.State != nil {
if issue.IsPull {
if err := issue.LoadPullRequest(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "GetPullRequest", err)
+ ctx.APIErrorInternal(err)
return
}
if issue.PullRequest.HasMerged {
- ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged")
+ ctx.APIError(http.StatusPreconditionFailed, "cannot change state of this pull request, it was already merged")
return
}
}
@@ -922,11 +931,11 @@ func EditIssue(ctx *context.APIContext) {
// Refetch from database to assign some automatic values
issue, err = issues_model.GetIssueByID(ctx, issue.ID)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
if err = issue.LoadMilestone(ctx); err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, ctx.Doer, issue))
@@ -963,15 +972,15 @@ func DeleteIssue(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.NotFound(err)
+ ctx.APIErrorNotFound(err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByID", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err = issue_service.DeleteIssue(ctx, ctx.Doer, ctx.Repo.GitRepo, issue); err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteIssueByID", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -1019,21 +1028,21 @@ func UpdateIssueDeadline(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
- ctx.Error(http.StatusForbidden, "", "Not repo writer")
+ ctx.APIError(http.StatusForbidden, "Not repo writer")
return
}
deadlineUnix, _ := common.ParseAPIDeadlineToEndOfDay(form.Deadline)
if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -1042,22 +1051,22 @@ func UpdateIssueDeadline(ctx *context.APIContext) {
func closeOrReopenIssue(ctx *context.APIContext, issue *issues_model.Issue, state api.StateType) {
if state != api.StateOpen && state != api.StateClosed {
- ctx.Error(http.StatusPreconditionFailed, "UnknownIssueStateError", fmt.Sprintf("unknown state: %s", state))
+ ctx.APIError(http.StatusPreconditionFailed, fmt.Sprintf("unknown state: %s", state))
return
}
if state == api.StateClosed && !issue.IsClosed {
if err := issue_service.CloseIssue(ctx, issue, ctx.Doer, ""); err != nil {
if issues_model.IsErrDependenciesLeft(err) {
- ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue or pull request because it still has open dependencies")
+ ctx.APIError(http.StatusPreconditionFailed, "cannot close this issue or pull request because it still has open dependencies")
return
}
- ctx.Error(http.StatusInternalServerError, "CloseIssue", err)
+ ctx.APIErrorInternal(err)
return
}
} else if state == api.StateOpen && issue.IsClosed {
if err := issue_service.ReopenIssue(ctx, issue, ctx.Doer, ""); err != nil {
- ctx.Error(http.StatusInternalServerError, "ReopenIssue", err)
+ ctx.APIErrorInternal(err)
return
}
}
diff --git a/routers/api/v1/repo/issue_attachment.go b/routers/api/v1/repo/issue_attachment.go
index d0bcadde37..3f751a295c 100644
--- a/routers/api/v1/repo/issue_attachment.go
+++ b/routers/api/v1/repo/issue_attachment.go
@@ -104,7 +104,7 @@ func ListIssueAttachments(ctx *context.APIContext) {
}
if err := issue.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -171,7 +171,7 @@ func CreateIssueAttachment(ctx *context.APIContext) {
// Get uploaded file from request
file, header, err := ctx.Req.FormFile("attachment")
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FormFile", err)
+ ctx.APIErrorInternal(err)
return
}
defer file.Close()
@@ -189,9 +189,9 @@ func CreateIssueAttachment(ctx *context.APIContext) {
})
if err != nil {
if upload.IsErrFileTypeForbidden(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -199,7 +199,7 @@ func CreateIssueAttachment(ctx *context.APIContext) {
issue.Attachments = append(issue.Attachments, attachment)
if err := issue_service.ChangeContent(ctx, issue, ctx.Doer, issue.Content, issue.ContentVersion); err != nil {
- ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -265,10 +265,10 @@ func EditIssueAttachment(ctx *context.APIContext) {
if err := attachment_service.UpdateAttachment(ctx, setting.Attachment.AllowedTypes, attachment); err != nil {
if upload.IsErrFileTypeForbidden(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "UpdateAttachment", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -319,7 +319,7 @@ func DeleteIssueAttachment(ctx *context.APIContext) {
}
if err := repo_model.DeleteAttachment(ctx, attachment, true); err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteAttachment", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -329,7 +329,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("GetIssueByIndex", issues_model.IsErrIssueNotExist, err)
+ ctx.NotFoundOrServerError(err)
return nil
}
@@ -354,7 +354,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("GetAttachmentByID", repo_model.IsErrAttachmentNotExist, err)
+ ctx.NotFoundOrServerError(err)
return nil
}
if !attachmentBelongsToRepoOrIssue(ctx, attachment, issue) {
@@ -366,7 +366,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))
if !canEditIssue {
- ctx.Error(http.StatusForbidden, "", "user should have permission to write issue")
+ ctx.APIError(http.StatusForbidden, "user should have permission to write issue")
return false
}
@@ -376,16 +376,16 @@ func canUserWriteIssueAttachment(ctx *context.APIContext, issue *issues_model.Is
func attachmentBelongsToRepoOrIssue(ctx *context.APIContext, attachment *repo_model.Attachment, issue *issues_model.Issue) bool {
if attachment.RepoID != ctx.Repo.Repository.ID {
log.Debug("Requested attachment[%d] does not belong to repo[%-v].", attachment.ID, ctx.Repo.Repository)
- ctx.NotFound("no such attachment in repo")
+ ctx.APIErrorNotFound("no such attachment in repo")
return false
}
if attachment.IssueID == 0 {
log.Debug("Requested attachment[%d] is not in an issue.", attachment.ID)
- ctx.NotFound("no such attachment in issue")
+ ctx.APIErrorNotFound("no such attachment in issue")
return false
} else if issue != nil && attachment.IssueID != issue.ID {
log.Debug("Requested attachment[%d] does not belong to issue[%d, #%d].", attachment.ID, issue.ID, issue.Index)
- ctx.NotFound("no such attachment in issue")
+ ctx.APIErrorNotFound("no such attachment in issue")
return false
}
return true
diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go
index 96a61a527e..cc342a9313 100644
--- a/routers/api/v1/repo/issue_comment.go
+++ b/routers/api/v1/repo/issue_comment.go
@@ -65,16 +65,16 @@ func ListIssueComments(ctx *context.APIContext) {
before, since, err := context.GetQueryBeforeSince(ctx.Base)
if err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
+ ctx.APIErrorInternal(err)
return
}
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -89,23 +89,23 @@ func ListIssueComments(ctx *context.APIContext) {
comments, err := issues_model.FindComments(ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FindComments", err)
+ ctx.APIErrorInternal(err)
return
}
totalCount, err := issues_model.CountComments(ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
if err := comments.LoadPosters(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
+ ctx.APIErrorInternal(err)
return
}
if err := comments.LoadAttachments(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -169,12 +169,12 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) {
before, since, err := context.GetQueryBeforeSince(ctx.Base)
if err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
+ ctx.APIErrorInternal(err)
return
}
issue.Repo = ctx.Repo.Repository
@@ -189,12 +189,12 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) {
comments, err := issues_model.FindComments(ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FindComments", err)
+ ctx.APIErrorInternal(err)
return
}
if err := comments.LoadPosters(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -274,7 +274,7 @@ func ListRepoIssueComments(ctx *context.APIContext) {
before, since, err := context.GetQueryBeforeSince(ctx.Base)
if err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
@@ -288,7 +288,7 @@ func ListRepoIssueComments(ctx *context.APIContext) {
} else if canReadPull {
isPull = optional.Some(true)
} else {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -303,32 +303,32 @@ func ListRepoIssueComments(ctx *context.APIContext) {
comments, err := issues_model.FindComments(ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FindComments", err)
+ ctx.APIErrorInternal(err)
return
}
totalCount, err := issues_model.CountComments(ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
if err = comments.LoadPosters(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
+ ctx.APIErrorInternal(err)
return
}
apiComments := make([]*api.Comment, len(comments))
if err := comments.LoadIssues(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadIssues", err)
+ ctx.APIErrorInternal(err)
return
}
if err := comments.LoadAttachments(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
+ ctx.APIErrorInternal(err)
return
}
if _, err := comments.Issues().LoadRepositories(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadRepositories", err)
+ ctx.APIErrorInternal(err)
return
}
for i := range comments {
@@ -382,26 +382,26 @@ func CreateIssueComment(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.CreateIssueCommentOption)
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
return
}
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin {
- ctx.Error(http.StatusForbidden, "CreateIssueComment", errors.New(ctx.Locale.TrString("repo.issues.comment_on_locked")))
+ ctx.APIError(http.StatusForbidden, errors.New(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.Error(http.StatusForbidden, "CreateIssueComment", err)
+ ctx.APIError(http.StatusForbidden, err)
} else {
- ctx.Error(http.StatusInternalServerError, "CreateIssueComment", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -448,15 +448,15 @@ func GetIssueComment(ctx *context.APIContext) {
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrCommentNotExist(err) {
- ctx.NotFound(err)
+ ctx.APIErrorNotFound(err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err = comment.LoadIssue(ctx); err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
@@ -465,7 +465,7 @@ func GetIssueComment(ctx *context.APIContext) {
}
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -475,7 +475,7 @@ func GetIssueComment(ctx *context.APIContext) {
}
if err := comment.LoadPoster(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "comment.LoadPoster", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -582,15 +582,15 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrCommentNotExist(err) {
- ctx.NotFound(err)
+ ctx.APIErrorNotFound(err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err := comment.LoadIssue(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -609,15 +609,17 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
return
}
- oldContent := comment.Content
- 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.Error(http.StatusForbidden, "UpdateComment", err)
- } else {
- ctx.Error(http.StatusInternalServerError, "UpdateComment", err)
+ if form.Body != comment.Content {
+ oldContent := comment.Content
+ 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)
+ } else {
+ ctx.APIErrorInternal(err)
+ }
+ return
}
- return
}
ctx.JSON(http.StatusOK, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment))
@@ -699,15 +701,15 @@ func deleteIssueComment(ctx *context.APIContext) {
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrCommentNotExist(err) {
- ctx.NotFound(err)
+ ctx.APIErrorNotFound(err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err := comment.LoadIssue(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -725,7 +727,7 @@ func deleteIssueComment(ctx *context.APIContext) {
}
if err = issue_service.DeleteComment(ctx, ctx.Doer, comment); err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteCommentByID", err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/repo/issue_comment_attachment.go b/routers/api/v1/repo/issue_comment_attachment.go
index a556a803e5..5f660c5750 100644
--- a/routers/api/v1/repo/issue_comment_attachment.go
+++ b/routers/api/v1/repo/issue_comment_attachment.go
@@ -67,7 +67,7 @@ func GetIssueCommentAttachment(ctx *context.APIContext) {
}
if attachment.CommentID != comment.ID {
log.Debug("User requested attachment[%d] is not in comment[%d].", attachment.ID, comment.ID)
- ctx.NotFound("attachment not in comment")
+ ctx.APIErrorNotFound("attachment not in comment")
return
}
@@ -109,7 +109,7 @@ func ListIssueCommentAttachments(ctx *context.APIContext) {
}
if err := comment.LoadAttachments(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -179,7 +179,7 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
// Get uploaded file from request
file, header, err := ctx.Req.FormFile("attachment")
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FormFile", err)
+ ctx.APIErrorInternal(err)
return
}
defer file.Close()
@@ -198,23 +198,23 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
})
if err != nil {
if upload.IsErrFileTypeForbidden(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err := comment.LoadAttachments(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
+ ctx.APIErrorInternal(err)
return
}
if err = issue_service.UpdateComment(ctx, comment, comment.ContentVersion, ctx.Doer, comment.Content); err != nil {
if errors.Is(err, user_model.ErrBlockedUser) {
- ctx.Error(http.StatusForbidden, "UpdateComment", err)
+ ctx.APIError(http.StatusForbidden, err)
} else {
- ctx.ServerError("UpdateComment", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -279,10 +279,10 @@ func EditIssueCommentAttachment(ctx *context.APIContext) {
if err := attachment_service.UpdateAttachment(ctx, setting.Attachment.AllowedTypes, attach); err != nil {
if upload.IsErrFileTypeForbidden(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
@@ -331,7 +331,7 @@ func DeleteIssueCommentAttachment(ctx *context.APIContext) {
}
if err := repo_model.DeleteAttachment(ctx, attach, true); err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteAttachment", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
@@ -340,15 +340,15 @@ 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("GetCommentByID", issues_model.IsErrCommentNotExist, err)
+ ctx.NotFoundOrServerError(err)
return nil
}
if err := comment.LoadIssue(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "comment.LoadIssue", err)
+ ctx.APIErrorInternal(err)
return nil
}
if comment.Issue == nil || comment.Issue.RepoID != ctx.Repo.Repository.ID {
- ctx.Error(http.StatusNotFound, "", "no matching issue comment found")
+ ctx.APIError(http.StatusNotFound, "no matching issue comment found")
return nil
}
@@ -375,7 +375,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)
if !canEditComment {
- ctx.Error(http.StatusForbidden, "", "user should have permission to edit comment")
+ ctx.APIError(http.StatusForbidden, "user should have permission to edit comment")
return false
}
@@ -385,7 +385,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("GetAttachmentByID", repo_model.IsErrAttachmentNotExist, err)
+ ctx.NotFoundOrServerError(err)
return nil
}
if !attachmentBelongsToRepoOrComment(ctx, attachment, comment) {
@@ -397,17 +397,17 @@ func getIssueCommentAttachmentSafeRead(ctx *context.APIContext, comment *issues_
func attachmentBelongsToRepoOrComment(ctx *context.APIContext, attachment *repo_model.Attachment, comment *issues_model.Comment) bool {
if attachment.RepoID != ctx.Repo.Repository.ID {
log.Debug("Requested attachment[%d] does not belong to repo[%-v].", attachment.ID, ctx.Repo.Repository)
- ctx.NotFound("no such attachment in repo")
+ ctx.APIErrorNotFound("no such attachment in repo")
return false
}
if attachment.IssueID == 0 || attachment.CommentID == 0 {
log.Debug("Requested attachment[%d] is not in a comment.", attachment.ID)
- ctx.NotFound("no such attachment in comment")
+ ctx.APIErrorNotFound("no such attachment in comment")
return false
}
if comment != nil && attachment.CommentID != comment.ID {
log.Debug("Requested attachment[%d] does not belong to comment[%d].", attachment.ID, comment.ID)
- ctx.NotFound("no such attachment in comment")
+ ctx.APIErrorNotFound("no such attachment in comment")
return false
}
return true
diff --git a/routers/api/v1/repo/issue_dependency.go b/routers/api/v1/repo/issue_dependency.go
index 19dcf999b8..1b58beb7b6 100644
--- a/routers/api/v1/repo/issue_dependency.go
+++ b/routers/api/v1/repo/issue_dependency.go
@@ -57,30 +57,27 @@ func GetIssueDependencies(ctx *context.APIContext) {
// If this issue's repository does not enable dependencies then there can be no dependencies by default
if !ctx.Repo.Repository.IsDependenciesEnabled(ctx) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
- ctx.NotFound("IsErrIssueNotExist", err)
+ ctx.APIErrorNotFound("IsErrIssueNotExist", err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
// 1. We must be able to read this issue
if !ctx.Repo.Permission.CanReadIssuesOrPulls(issue.IsPull) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
limit := ctx.FormInt("limit")
if limit == 0 {
limit = setting.API.DefaultPagingNum
@@ -98,7 +95,7 @@ func GetIssueDependencies(ctx *context.APIContext) {
PageSize: limit,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "BlockedByDependencies", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -116,7 +113,7 @@ func GetIssueDependencies(ctx *context.APIContext) {
var err error
perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
if err != nil {
- ctx.ServerError("GetUserRepoPermission", err)
+ ctx.APIErrorInternal(err)
return
}
repoPerms[blocker.RepoID] = perm
@@ -324,14 +321,11 @@ func GetIssueBlocks(ctx *context.APIContext) {
}
if !ctx.Repo.Permission.CanReadIssuesOrPulls(issue.IsPull) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
limit := ctx.FormInt("limit")
if limit <= 1 {
limit = setting.API.DefaultPagingNum
@@ -342,7 +336,7 @@ func GetIssueBlocks(ctx *context.APIContext) {
deps, err := issue.BlockingDependencies(ctx)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "BlockingDependencies", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -367,7 +361,7 @@ func GetIssueBlocks(ctx *context.APIContext) {
var err error
perm, err = access_model.GetUserRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
if err != nil {
- ctx.ServerError("GetUserRepoPermission", err)
+ ctx.APIErrorInternal(err)
return
}
repoPerms[depMeta.RepoID] = perm
@@ -502,9 +496,9 @@ 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.NotFound("IsErrIssueNotExist", err)
+ ctx.APIErrorNotFound("IsErrIssueNotExist", err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return nil
}
@@ -523,9 +517,9 @@ func getFormIssue(ctx *context.APIContext, form *api.IssueMeta) *issues_model.Is
repo, err = repo_model.GetRepositoryByOwnerAndName(ctx, form.Owner, form.Name)
if err != nil {
if repo_model.IsErrRepoNotExist(err) {
- ctx.NotFound("IsErrRepoNotExist", err)
+ ctx.APIErrorNotFound("IsErrRepoNotExist", err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetRepositoryByOwnerAndName", err)
+ ctx.APIErrorInternal(err)
}
return nil
}
@@ -536,9 +530,9 @@ 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.NotFound("IsErrIssueNotExist", err)
+ ctx.APIErrorNotFound("IsErrIssueNotExist", err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return nil
}
@@ -553,7 +547,7 @@ func getPermissionForRepo(ctx *context.APIContext, repo *repo_model.Repository)
perm, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
+ ctx.APIErrorInternal(err)
return nil
}
@@ -563,25 +557,25 @@ func getPermissionForRepo(ctx *context.APIContext, repo *repo_model.Repository)
func createIssueDependency(ctx *context.APIContext, target, dependency *issues_model.Issue, targetPerm, dependencyPerm access_model.Permission) {
if target.Repo.IsArchived || !target.Repo.IsDependenciesEnabled(ctx) {
// The target's repository doesn't have dependencies enabled
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if !targetPerm.CanWriteIssuesOrPulls(target.IsPull) {
// We can't write to the target
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if !dependencyPerm.CanReadIssuesOrPulls(dependency.IsPull) {
// We can't read the dependency
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
err := issues_model.CreateIssueDependency(ctx, ctx.Doer, target, dependency)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "CreateIssueDependency", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -589,25 +583,25 @@ func createIssueDependency(ctx *context.APIContext, target, dependency *issues_m
func removeIssueDependency(ctx *context.APIContext, target, dependency *issues_model.Issue, targetPerm, dependencyPerm access_model.Permission) {
if target.Repo.IsArchived || !target.Repo.IsDependenciesEnabled(ctx) {
// The target's repository doesn't have dependencies enabled
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if !targetPerm.CanWriteIssuesOrPulls(target.IsPull) {
// We can't write to the target
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if !dependencyPerm.CanReadIssuesOrPulls(dependency.IsPull) {
// We can't read the dependency
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
err := issues_model.RemoveIssueDependency(ctx, ctx.Doer, target, dependency, issues_model.DependencyTypeBlockedBy)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "CreateIssueDependency", err)
+ ctx.APIErrorInternal(err)
return
}
}
diff --git a/routers/api/v1/repo/issue_label.go b/routers/api/v1/repo/issue_label.go
index ee1a842bc6..d5eee2d469 100644
--- a/routers/api/v1/repo/issue_label.go
+++ b/routers/api/v1/repo/issue_label.go
@@ -5,7 +5,7 @@
package repo
import (
- "fmt"
+ "errors"
"net/http"
"reflect"
@@ -50,15 +50,15 @@ func ListIssueLabels(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err := issue.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -110,13 +110,13 @@ func AddIssueLabels(ctx *context.APIContext) {
}
if err = issue_service.AddLabels(ctx, issue, ctx.Doer, labels); err != nil {
- ctx.Error(http.StatusInternalServerError, "AddLabels", err)
+ ctx.APIErrorInternal(err)
return
}
labels, err = issues_model.GetLabelsByIssueID(ctx, issue.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetLabelsByIssueID", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -166,9 +166,9 @@ func DeleteIssueLabel(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -181,15 +181,15 @@ func DeleteIssueLabel(ctx *context.APIContext) {
label, err := issues_model.GetLabelByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrLabelNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetLabelByID", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err := issue_service.RemoveLabel(ctx, issue, ctx.Doer, label); err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteIssueLabel", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -240,13 +240,13 @@ func ReplaceIssueLabels(ctx *context.APIContext) {
}
if err := issue_service.ReplaceLabels(ctx, issue, ctx.Doer, labels); err != nil {
- ctx.Error(http.StatusInternalServerError, "ReplaceLabels", err)
+ ctx.APIErrorInternal(err)
return
}
labels, err = issues_model.GetLabelsByIssueID(ctx, issue.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetLabelsByIssueID", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -288,9 +288,9 @@ func ClearIssueLabels(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -301,7 +301,7 @@ func ClearIssueLabels(ctx *context.APIContext) {
}
if err := issue_service.ClearLabels(ctx, issue, ctx.Doer); err != nil {
- ctx.Error(http.StatusInternalServerError, "ClearLabels", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -312,16 +312,16 @@ func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption)
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return nil, nil, err
}
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
- ctx.Error(http.StatusForbidden, "CanWriteIssuesOrPulls", "write permission is required")
- return nil, nil, fmt.Errorf("permission denied")
+ ctx.APIError(http.StatusForbidden, "write permission is required")
+ return nil, nil, errors.New("permission denied")
}
var (
@@ -336,25 +336,25 @@ func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption)
case reflect.String:
labelNames = append(labelNames, rv.String())
default:
- ctx.Error(http.StatusBadRequest, "InvalidLabel", "a label must be an integer or a string")
- return nil, nil, fmt.Errorf("invalid label")
+ ctx.APIError(http.StatusBadRequest, "a label must be an integer or a string")
+ return nil, nil, errors.New("invalid label")
}
}
if len(labelIDs) > 0 && len(labelNames) > 0 {
- ctx.Error(http.StatusBadRequest, "InvalidLabels", "labels should be an array of strings or integers")
- return nil, nil, fmt.Errorf("invalid labels")
+ ctx.APIError(http.StatusBadRequest, "labels should be an array of strings or integers")
+ return nil, nil, errors.New("invalid labels")
}
if len(labelNames) > 0 {
repoLabelIDs, err := issues_model.GetLabelIDsInRepoByNames(ctx, ctx.Repo.Repository.ID, labelNames)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetLabelIDsInRepoByNames", err)
+ ctx.APIErrorInternal(err)
return nil, nil, err
}
labelIDs = append(labelIDs, repoLabelIDs...)
if ctx.Repo.Owner.IsOrganization() {
orgLabelIDs, err := issues_model.GetLabelIDsInOrgByNames(ctx, ctx.Repo.Owner.ID, labelNames)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetLabelIDsInOrgByNames", err)
+ ctx.APIErrorInternal(err)
return nil, nil, err
}
labelIDs = append(labelIDs, orgLabelIDs...)
@@ -363,7 +363,7 @@ func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption)
labels, err := issues_model.GetLabelsByIDs(ctx, labelIDs, "id", "repo_id", "org_id", "name", "exclusive")
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetLabelsByIDs", err)
+ ctx.APIErrorInternal(err)
return nil, nil, err
}
diff --git a/routers/api/v1/repo/issue_lock.go b/routers/api/v1/repo/issue_lock.go
new file mode 100644
index 0000000000..b9e5bcf6eb
--- /dev/null
+++ b/routers/api/v1/repo/issue_lock.go
@@ -0,0 +1,152 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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"
+)
+
+// LockIssue lock an issue
+func LockIssue(ctx *context.APIContext) {
+ // swagger:operation PUT /repos/{owner}/{repo}/issues/{index}/lock issue issueLockIssue
+ // ---
+ // summary: Lock 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
+ // schema:
+ // "$ref": "#/definitions/LockIssueOption"
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ 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)
+ }
+ return
+ }
+
+ if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
+ ctx.APIError(http.StatusForbidden, errors.New("no permission to lock this issue"))
+ return
+ }
+
+ if !issue.IsLocked {
+ opt := &issues_model.IssueLockOptions{
+ Doer: ctx.ContextUser,
+ Issue: issue,
+ Reason: reason,
+ }
+
+ issue.Repo = ctx.Repo.Repository
+ err = issues_model.LockIssue(ctx, opt)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ }
+
+ ctx.Status(http.StatusNoContent)
+}
+
+// UnlockIssue unlock an issue
+func UnlockIssue(ctx *context.APIContext) {
+ // swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/lock issue issueUnlockIssue
+ // ---
+ // summary: Unlock 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
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$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)
+ }
+ return
+ }
+
+ if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
+ ctx.APIError(http.StatusForbidden, errors.New("no permission to unlock this issue"))
+ return
+ }
+
+ if issue.IsLocked {
+ opt := &issues_model.IssueLockOptions{
+ Doer: ctx.ContextUser,
+ Issue: issue,
+ }
+
+ issue.Repo = ctx.Repo.Repository
+ err = issues_model.UnlockIssue(ctx, opt)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ }
+
+ ctx.Status(http.StatusNoContent)
+}
diff --git a/routers/api/v1/repo/issue_pin.go b/routers/api/v1/repo/issue_pin.go
index 388d4a3e99..71985ac765 100644
--- a/routers/api/v1/repo/issue_pin.go
+++ b/routers/api/v1/repo/issue_pin.go
@@ -44,11 +44,11 @@ func PinIssue(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.NotFound()
+ ctx.APIErrorNotFound()
} else if issues_model.IsErrIssueMaxPinReached(err) {
- ctx.Error(http.StatusBadRequest, "MaxPinReached", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -56,13 +56,13 @@ func PinIssue(ctx *context.APIContext) {
// If we don't do this, it will crash when trying to add the pin event to the comment history
err = issue.LoadRepo(ctx)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadRepo", err)
+ ctx.APIErrorInternal(err)
return
}
- err = issue.Pin(ctx, ctx.Doer)
+ err = issues_model.PinIssue(ctx, issue, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "PinIssue", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -101,9 +101,9 @@ func UnpinIssue(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -111,13 +111,13 @@ func UnpinIssue(ctx *context.APIContext) {
// If we don't do this, it will crash when trying to add the unpin event to the comment history
err = issue.LoadRepo(ctx)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadRepo", err)
+ ctx.APIErrorInternal(err)
return
}
- err = issue.Unpin(ctx, ctx.Doer)
+ err = issues_model.UnpinIssue(ctx, issue, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "UnpinIssue", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -162,16 +162,16 @@ func MoveIssuePin(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
- err = issue.MovePin(ctx, int(ctx.PathParamInt64("position")))
+ err = issues_model.MovePin(ctx, issue, int(ctx.PathParamInt64("position")))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "MovePin", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -203,7 +203,7 @@ func ListPinnedIssues(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
issues, err := issues_model.GetPinnedIssues(ctx, ctx.Repo.Repository.ID, false)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadPinnedIssues", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -235,29 +235,29 @@ func ListPinnedPullRequests(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
issues, err := issues_model.GetPinnedIssues(ctx, ctx.Repo.Repository.ID, true)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadPinnedPullRequests", err)
+ ctx.APIErrorInternal(err)
return
}
apiPrs := make([]*api.PullRequest, len(issues))
if err := issues.LoadPullRequests(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadPullRequests", err)
+ ctx.APIErrorInternal(err)
return
}
for i, currentIssue := range issues {
pr := currentIssue.PullRequest
if err = pr.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
if err = pr.LoadBaseRepo(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
+ ctx.APIErrorInternal(err)
return
}
if err = pr.LoadHeadRepo(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -295,13 +295,13 @@ func AreNewIssuePinsAllowed(ctx *context.APIContext) {
pinsAllowed.Issues, err = issues_model.IsNewPinAllowed(ctx, ctx.Repo.Repository.ID, false)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsNewIssuePinAllowed", err)
+ ctx.APIErrorInternal(err)
return
}
pinsAllowed.PullRequests, err = issues_model.IsNewPinAllowed(ctx, ctx.Repo.Repository.ID, true)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsNewPullRequestPinAllowed", err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/repo/issue_reaction.go b/routers/api/v1/repo/issue_reaction.go
index ead86a717f..e535b5e009 100644
--- a/routers/api/v1/repo/issue_reaction.go
+++ b/routers/api/v1/repo/issue_reaction.go
@@ -54,36 +54,36 @@ func GetIssueCommentReactions(ctx *context.APIContext) {
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrCommentNotExist(err) {
- ctx.NotFound(err)
+ ctx.APIErrorNotFound(err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err := comment.LoadIssue(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "comment.LoadIssue", err)
+ ctx.APIErrorInternal(err)
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
- ctx.Error(http.StatusForbidden, "GetIssueCommentReactions", errors.New("no permission to get reactions"))
+ ctx.APIError(http.StatusForbidden, errors.New("no permission to get reactions"))
return
}
reactions, _, err := issues_model.FindCommentReactions(ctx, comment.IssueID, comment.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FindCommentReactions", err)
+ ctx.APIErrorInternal(err)
return
}
_, err = reactions.LoadUsers(ctx, ctx.Repo.Repository)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ReactionList.LoadUsers()", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -191,30 +191,30 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrCommentNotExist(err) {
- ctx.NotFound(err)
+ ctx.APIErrorNotFound(err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err = comment.LoadIssue(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "comment.LoadIssue() failed", err)
+ ctx.APIErrorInternal(err)
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if comment.Issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull) {
- ctx.Error(http.StatusForbidden, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
+ ctx.APIError(http.StatusForbidden, errors.New("no permission to change reaction"))
return
}
@@ -223,7 +223,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.Error(http.StatusForbidden, err.Error(), err)
+ ctx.APIError(http.StatusForbidden, err)
} else if issues_model.IsErrReactionAlreadyExist(err) {
ctx.JSON(http.StatusOK, api.Reaction{
User: convert.ToUser(ctx, ctx.Doer, ctx.Doer),
@@ -231,7 +231,7 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
Created: reaction.CreatedUnix.AsTime(),
})
} else {
- ctx.Error(http.StatusInternalServerError, "CreateCommentReaction", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -245,7 +245,7 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
// DeleteIssueCommentReaction part
err = issues_model.DeleteCommentReaction(ctx, ctx.Doer.ID, comment.Issue.ID, comment.ID, form.Reaction)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteCommentReaction", err)
+ ctx.APIErrorInternal(err)
return
}
// ToDo respond 204
@@ -298,26 +298,26 @@ func GetIssueReactions(ctx *context.APIContext) {
issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
- ctx.Error(http.StatusForbidden, "GetIssueReactions", errors.New("no permission to get reactions"))
+ ctx.APIError(http.StatusForbidden, errors.New("no permission to get reactions"))
return
}
reactions, count, err := issues_model.FindIssueReactions(ctx, issue.ID, utils.GetListOptions(ctx))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FindIssueReactions", err)
+ ctx.APIErrorInternal(err)
return
}
_, err = reactions.LoadUsers(ctx, ctx.Repo.Repository)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ReactionList.LoadUsers()", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -422,15 +422,15 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i
issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
- ctx.Error(http.StatusForbidden, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
+ ctx.APIError(http.StatusForbidden, errors.New("no permission to change reaction"))
return
}
@@ -439,7 +439,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.Error(http.StatusForbidden, err.Error(), err)
+ ctx.APIError(http.StatusForbidden, err)
} else if issues_model.IsErrReactionAlreadyExist(err) {
ctx.JSON(http.StatusOK, api.Reaction{
User: convert.ToUser(ctx, ctx.Doer, ctx.Doer),
@@ -447,7 +447,7 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i
Created: reaction.CreatedUnix.AsTime(),
})
} else {
- ctx.Error(http.StatusInternalServerError, "CreateIssueReaction", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -461,7 +461,7 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i
// DeleteIssueReaction part
err = issues_model.DeleteIssueReaction(ctx, ctx.Doer.ID, issue.ID, form.Reaction)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteIssueReaction", err)
+ ctx.APIErrorInternal(err)
return
}
// ToDo respond 204
diff --git a/routers/api/v1/repo/issue_stopwatch.go b/routers/api/v1/repo/issue_stopwatch.go
index e7fba6d0ed..0f28b9757d 100644
--- a/routers/api/v1/repo/issue_stopwatch.go
+++ b/routers/api/v1/repo/issue_stopwatch.go
@@ -4,7 +4,6 @@
package repo
import (
- "errors"
"net/http"
issues_model "code.gitea.io/gitea/models/issues"
@@ -49,13 +48,16 @@ func StartIssueStopwatch(ctx *context.APIContext) {
// "409":
// description: Cannot start a stopwatch again if it already exists
- issue, err := prepareIssueStopwatch(ctx, false)
- if err != nil {
+ issue := prepareIssueForStopwatch(ctx)
+ if ctx.Written() {
return
}
- if err := issues_model.CreateIssueStopwatch(ctx, ctx.Doer, issue); err != nil {
- ctx.Error(http.StatusInternalServerError, "CreateOrStopIssueStopwatch", err)
+ if ok, err := issues_model.CreateIssueStopwatch(ctx, ctx.Doer, issue); err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ } else if !ok {
+ ctx.APIError(http.StatusConflict, "cannot start a stopwatch again if it already exists")
return
}
@@ -96,18 +98,20 @@ func StopIssueStopwatch(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
// "409":
- // description: Cannot stop a non existent stopwatch
+ // description: Cannot stop a non-existent stopwatch
- issue, err := prepareIssueStopwatch(ctx, true)
- if err != nil {
+ issue := prepareIssueForStopwatch(ctx)
+ if ctx.Written() {
return
}
- if err := issues_model.FinishIssueStopwatch(ctx, ctx.Doer, issue); err != nil {
- ctx.Error(http.StatusInternalServerError, "CreateOrStopIssueStopwatch", err)
+ if ok, err := issues_model.FinishIssueStopwatch(ctx, ctx.Doer, issue); err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ } else if !ok {
+ ctx.APIError(http.StatusConflict, "cannot stop a non-existent stopwatch")
return
}
-
ctx.Status(http.StatusCreated)
}
@@ -145,55 +149,45 @@ func DeleteIssueStopwatch(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
// "409":
- // description: Cannot cancel a non existent stopwatch
+ // description: Cannot cancel a non-existent stopwatch
- issue, err := prepareIssueStopwatch(ctx, true)
- if err != nil {
+ issue := prepareIssueForStopwatch(ctx)
+ if ctx.Written() {
return
}
- if err := issues_model.CancelStopwatch(ctx, ctx.Doer, issue); err != nil {
- ctx.Error(http.StatusInternalServerError, "CancelStopwatch", err)
+ if ok, err := issues_model.CancelStopwatch(ctx, ctx.Doer, issue); err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ } else if !ok {
+ ctx.APIError(http.StatusConflict, "cannot cancel a non-existent stopwatch")
return
}
ctx.Status(http.StatusNoContent)
}
-func prepareIssueStopwatch(ctx *context.APIContext, shouldExist bool) (*issues_model.Issue, error) {
+func prepareIssueForStopwatch(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
-
- return nil, err
+ return nil
}
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
ctx.Status(http.StatusForbidden)
- return nil, errors.New("Unable to write to PRs")
+ return nil
}
if !ctx.Repo.CanUseTimetracker(ctx, issue, ctx.Doer) {
ctx.Status(http.StatusForbidden)
- return nil, errors.New("Cannot use time tracker")
- }
-
- if issues_model.StopwatchExists(ctx, ctx.Doer.ID, issue.ID) != shouldExist {
- if shouldExist {
- ctx.Error(http.StatusConflict, "StopwatchExists", "cannot stop/cancel a non existent stopwatch")
- err = errors.New("cannot stop/cancel a non existent stopwatch")
- } else {
- ctx.Error(http.StatusConflict, "StopwatchExists", "cannot start a stopwatch again if it already exists")
- err = errors.New("cannot start a stopwatch again if it already exists")
- }
- return nil, err
+ return nil
}
-
- return issue, nil
+ return issue
}
// GetStopwatches get all stopwatches
@@ -220,19 +214,19 @@ func GetStopwatches(ctx *context.APIContext) {
sws, err := issues_model.GetUserStopwatches(ctx, ctx.Doer.ID, utils.GetListOptions(ctx))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserStopwatches", err)
+ ctx.APIErrorInternal(err)
return
}
count, err := issues_model.CountUserStopwatches(ctx, ctx.Doer.ID)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
apiSWs, err := convert.ToStopWatches(ctx, sws)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "APIFormat", err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/repo/issue_subscription.go b/routers/api/v1/repo/issue_subscription.go
index 4fb80b1ec4..c89f228a06 100644
--- a/routers/api/v1/repo/issue_subscription.go
+++ b/routers/api/v1/repo/issue_subscription.go
@@ -43,7 +43,7 @@ func AddIssueSubscription(ctx *context.APIContext) {
// required: true
// - name: user
// in: path
- // description: user to subscribe
+ // description: username of the user to subscribe the issue to
// type: string
// required: true
// responses:
@@ -87,7 +87,7 @@ func DelIssueSubscription(ctx *context.APIContext) {
// required: true
// - name: user
// in: path
- // description: user witch unsubscribe
+ // description: username of the user to unsubscribe from an issue
// type: string
// required: true
// responses:
@@ -107,9 +107,9 @@ func setIssueSubscription(ctx *context.APIContext, watch bool) {
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
@@ -118,9 +118,9 @@ func setIssueSubscription(ctx *context.APIContext, watch bool) {
user, err := user_model.GetUserByName(ctx, ctx.PathParam("user"))
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
}
return
@@ -128,13 +128,13 @@ 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.Error(http.StatusForbidden, "User", fmt.Errorf("%s is not permitted to change subscriptions for %s", ctx.Doer.Name, user.Name))
+ ctx.APIError(http.StatusForbidden, fmt.Errorf("%s is not permitted to change subscriptions for %s", ctx.Doer.Name, user.Name))
return
}
current, err := issues_model.CheckIssueWatch(ctx, user, issue)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "CheckIssueWatch", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -146,7 +146,7 @@ func setIssueSubscription(ctx *context.APIContext, watch bool) {
// Update watch state
if err := issues_model.CreateOrUpdateIssueWatch(ctx, user.ID, issue.ID, watch); err != nil {
- ctx.Error(http.StatusInternalServerError, "CreateOrUpdateIssueWatch", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -188,9 +188,9 @@ func CheckIssueSubscription(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
@@ -198,7 +198,7 @@ func CheckIssueSubscription(ctx *context.APIContext) {
watching, err := issues_model.CheckIssueWatch(ctx, ctx.Doer, issue)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, api.WatchInfo{
@@ -254,9 +254,9 @@ func GetIssueSubscribers(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
@@ -264,7 +264,7 @@ func GetIssueSubscribers(ctx *context.APIContext) {
iwl, err := issues_model.GetIssueWatchers(ctx, issue.ID, utils.GetListOptions(ctx))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetIssueWatchers", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -275,7 +275,7 @@ func GetIssueSubscribers(ctx *context.APIContext) {
users, err := user_model.GetUsersByIDs(ctx, userIDs)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUsersByIDs", err)
+ ctx.APIErrorInternal(err)
return
}
apiUsers := make([]*api.User, 0, len(users))
@@ -285,7 +285,7 @@ func GetIssueSubscribers(ctx *context.APIContext) {
count, err := issues_model.CountIssueWatchers(ctx, issue.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "CountIssueWatchers", err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go
index 57961b0660..171da272cc 100644
--- a/routers/api/v1/repo/issue_tracked_time.go
+++ b/routers/api/v1/repo/issue_tracked_time.go
@@ -4,7 +4,7 @@
package repo
import (
- "fmt"
+ "errors"
"net/http"
"time"
@@ -72,15 +72,15 @@ func ListTrackedTimes(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if !ctx.Repo.Repository.IsTimetrackerEnabled(ctx) {
- ctx.NotFound("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.NotFound(err)
+ ctx.APIErrorNotFound(err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -95,16 +95,16 @@ func ListTrackedTimes(ctx *context.APIContext) {
if qUser != "" {
user, err := user_model.GetUserByName(ctx, qUser)
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusNotFound, "User does not exist", err)
+ ctx.APIError(http.StatusNotFound, err)
} else if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
return
}
opts.UserID = user.ID
}
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Base); err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
@@ -116,24 +116,24 @@ func ListTrackedTimes(ctx *context.APIContext) {
if opts.UserID == 0 {
opts.UserID = ctx.Doer.ID
} else {
- ctx.Error(http.StatusForbidden, "", fmt.Errorf("query by user not allowed; not enough rights"))
+ ctx.APIError(http.StatusForbidden, errors.New("query by user not allowed; not enough rights"))
return
}
}
count, err := issues_model.CountTrackedTimes(ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
trackedTimes, err := issues_model.GetTrackedTimes(ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetTrackedTimes", err)
+ ctx.APIErrorInternal(err)
return
}
if err = trackedTimes.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -184,16 +184,16 @@ func AddTime(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.NotFound(err)
+ ctx.APIErrorNotFound(err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
if !ctx.Repo.CanUseTimetracker(ctx, issue, ctx.Doer) {
if !ctx.Repo.Repository.IsTimetrackerEnabled(ctx) {
- ctx.Error(http.StatusBadRequest, "", "time tracking disabled")
+ ctx.APIError(http.StatusBadRequest, "time tracking disabled")
return
}
ctx.Status(http.StatusForbidden)
@@ -206,7 +206,7 @@ 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.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
}
}
}
@@ -218,11 +218,11 @@ func AddTime(ctx *context.APIContext) {
trackedTime, err := issues_model.AddTime(ctx, user, issue, form.Time, created)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "AddTime", err)
+ ctx.APIErrorInternal(err)
return
}
if err = trackedTime.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, convert.ToTrackedTime(ctx, user, trackedTime))
@@ -267,9 +267,9 @@ 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.NotFound(err)
+ ctx.APIErrorNotFound(err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -286,9 +286,9 @@ func ResetIssueTime(ctx *context.APIContext) {
err = issues_model.DeleteIssueUserTimes(ctx, issue, ctx.Doer)
if err != nil {
if db.IsErrNotExist(err) {
- ctx.Error(http.StatusNotFound, "DeleteIssueUserTimes", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteIssueUserTimes", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -340,9 +340,9 @@ 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.NotFound(err)
+ ctx.APIErrorNotFound(err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -359,14 +359,14 @@ func DeleteTime(ctx *context.APIContext) {
time, err := issues_model.GetTrackedTimeByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if db.IsErrNotExist(err) {
- ctx.NotFound(err)
+ ctx.APIErrorNotFound(err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetTrackedTimeByID", err)
+ ctx.APIErrorInternal(err)
return
}
if time.Deleted {
- ctx.NotFound(fmt.Errorf("tracked time [%d] already deleted", time.ID))
+ ctx.APIErrorNotFound("tracked time was already deleted")
return
}
@@ -378,7 +378,7 @@ func DeleteTime(ctx *context.APIContext) {
err = issues_model.DeleteTime(ctx, time)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteTime", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
@@ -405,7 +405,7 @@ func ListTrackedTimesByUser(ctx *context.APIContext) {
// required: true
// - name: user
// in: path
- // description: username of user
+ // description: username of the user whose tracked times are to be listed
// type: string
// required: true
// responses:
@@ -419,25 +419,25 @@ func ListTrackedTimesByUser(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if !ctx.Repo.Repository.IsTimetrackerEnabled(ctx) {
- ctx.Error(http.StatusBadRequest, "", "time tracking disabled")
+ ctx.APIError(http.StatusBadRequest, "time tracking disabled")
return
}
user, err := user_model.GetUserByName(ctx, ctx.PathParam("timetrackingusername"))
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.NotFound(err)
+ ctx.APIErrorNotFound(err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
if user == nil {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if !ctx.IsUserRepoAdmin() && !ctx.Doer.IsAdmin && ctx.Doer.ID != user.ID {
- ctx.Error(http.StatusForbidden, "", fmt.Errorf("query by user not allowed; not enough rights"))
+ ctx.APIError(http.StatusForbidden, errors.New("query by user not allowed; not enough rights"))
return
}
@@ -448,11 +448,11 @@ func ListTrackedTimesByUser(ctx *context.APIContext) {
trackedTimes, err := issues_model.GetTrackedTimes(ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetTrackedTimes", err)
+ ctx.APIErrorInternal(err)
return
}
if err = trackedTimes.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, convert.ToTrackedTimeList(ctx, ctx.Doer, trackedTimes))
@@ -509,7 +509,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if !ctx.Repo.Repository.IsTimetrackerEnabled(ctx) {
- ctx.Error(http.StatusBadRequest, "", "time tracking disabled")
+ ctx.APIError(http.StatusBadRequest, "time tracking disabled")
return
}
@@ -523,9 +523,9 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
if qUser != "" {
user, err := user_model.GetUserByName(ctx, qUser)
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusNotFound, "User does not exist", err)
+ ctx.APIError(http.StatusNotFound, err)
} else if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
return
}
opts.UserID = user.ID
@@ -533,7 +533,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
var err error
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Base); err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
@@ -545,24 +545,24 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
if opts.UserID == 0 {
opts.UserID = ctx.Doer.ID
} else {
- ctx.Error(http.StatusForbidden, "", fmt.Errorf("query by user not allowed; not enough rights"))
+ ctx.APIError(http.StatusForbidden, errors.New("query by user not allowed; not enough rights"))
return
}
}
count, err := issues_model.CountTrackedTimes(ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
trackedTimes, err := issues_model.GetTrackedTimes(ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetTrackedTimes", err)
+ ctx.APIErrorInternal(err)
return
}
if err = trackedTimes.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -607,24 +607,24 @@ func ListMyTrackedTimes(ctx *context.APIContext) {
var err error
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Base); err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
count, err := issues_model.CountTrackedTimes(ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
trackedTimes, err := issues_model.GetTrackedTimes(ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetTrackedTimesByUser", err)
+ ctx.APIErrorInternal(err)
return
}
if err = trackedTimes.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/repo/key.go b/routers/api/v1/repo/key.go
index 23cc922628..8cb61e9e0c 100644
--- a/routers/api/v1/repo/key.go
+++ b/routers/api/v1/repo/key.go
@@ -92,7 +92,7 @@ func ListDeployKeys(ctx *context.APIContext) {
keys, count, err := db.FindAndCount[asymkey_model.DeployKey](ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -100,7 +100,7 @@ func ListDeployKeys(ctx *context.APIContext) {
apiKeys := make([]*api.DeployKey, len(keys))
for i := range keys {
if err := keys[i].GetContent(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "GetContent", err)
+ ctx.APIErrorInternal(err)
return
}
apiKeys[i] = convert.ToDeployKey(apiLink, keys[i])
@@ -146,21 +146,21 @@ func GetDeployKey(ctx *context.APIContext) {
key, err := asymkey_model.GetDeployKeyByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if asymkey_model.IsErrDeployKeyNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetDeployKeyByID", err)
+ ctx.APIErrorInternal(err)
}
return
}
// this check make it more consistent
if key.RepoID != ctx.Repo.Repository.ID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if err = key.GetContent(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "GetContent", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -175,11 +175,11 @@ func GetDeployKey(ctx *context.APIContext) {
// HandleCheckKeyStringError handle check key error
func HandleCheckKeyStringError(ctx *context.APIContext, err error) {
if db.IsErrSSHDisabled(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", "SSH is disabled")
+ ctx.APIError(http.StatusUnprocessableEntity, "SSH is disabled")
} else if asymkey_model.IsErrKeyUnableVerify(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", "Unable to verify key content")
+ ctx.APIError(http.StatusUnprocessableEntity, "Unable to verify key content")
} else {
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid key content: %w", err))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid key content: %w", err))
}
}
@@ -187,15 +187,15 @@ func HandleCheckKeyStringError(ctx *context.APIContext, err error) {
func HandleAddKeyError(ctx *context.APIContext, err error) {
switch {
case asymkey_model.IsErrDeployKeyAlreadyExist(err):
- ctx.Error(http.StatusUnprocessableEntity, "", "This key has already been added to this repository")
+ ctx.APIError(http.StatusUnprocessableEntity, "This key has already been added to this repository")
case asymkey_model.IsErrKeyAlreadyExist(err):
- ctx.Error(http.StatusUnprocessableEntity, "", "Key content has been used as non-deploy key")
+ ctx.APIError(http.StatusUnprocessableEntity, "Key content has been used as non-deploy key")
case asymkey_model.IsErrKeyNameAlreadyUsed(err):
- ctx.Error(http.StatusUnprocessableEntity, "", "Key title has been used")
+ ctx.APIError(http.StatusUnprocessableEntity, "Key title has been used")
case asymkey_model.IsErrDeployKeyNameAlreadyUsed(err):
- ctx.Error(http.StatusUnprocessableEntity, "", "A key with the same name already exists")
+ ctx.APIError(http.StatusUnprocessableEntity, "A key with the same name already exists")
default:
- ctx.Error(http.StatusInternalServerError, "AddKey", err)
+ ctx.APIErrorInternal(err)
}
}
@@ -281,9 +281,9 @@ func DeleteDeploykey(ctx *context.APIContext) {
if err := asymkey_service.DeleteDeployKey(ctx, ctx.Repo.Repository, ctx.PathParamInt64("id")); err != nil {
if asymkey_model.IsErrKeyAccessDenied(err) {
- ctx.Error(http.StatusForbidden, "", "You do not have access to this key")
+ ctx.APIError(http.StatusForbidden, "You do not have access to this key")
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteDeployKey", err)
+ ctx.APIErrorInternal(err)
}
return
}
diff --git a/routers/api/v1/repo/label.go b/routers/api/v1/repo/label.go
index 1ece2521e0..4f79d42595 100644
--- a/routers/api/v1/repo/label.go
+++ b/routers/api/v1/repo/label.go
@@ -51,13 +51,13 @@ func ListLabels(ctx *context.APIContext) {
labels, err := issues_model.GetLabelsByRepoID(ctx, ctx.Repo.Repository.ID, ctx.FormString("sort"), utils.GetListOptions(ctx))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetLabelsByRepoID", err)
+ ctx.APIErrorInternal(err)
return
}
count, err := issues_model.CountLabelsByRepoID(ctx, ctx.Repo.Repository.ID)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -107,9 +107,9 @@ func GetLabel(ctx *context.APIContext) {
}
if err != nil {
if issues_model.IsErrRepoLabelNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetLabelByRepoID", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -153,7 +153,7 @@ func CreateLabel(ctx *context.APIContext) {
color, err := label.NormalizeColor(form.Color)
if err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "StringToColor", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
form.Color = color
@@ -166,7 +166,7 @@ func CreateLabel(ctx *context.APIContext) {
}
l.SetArchived(form.IsArchived)
if err := issues_model.NewLabel(ctx, l); err != nil {
- ctx.Error(http.StatusInternalServerError, "NewLabel", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -215,9 +215,9 @@ func EditLabel(ctx *context.APIContext) {
l, err := issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrRepoLabelNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetLabelByRepoID", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -231,7 +231,7 @@ func EditLabel(ctx *context.APIContext) {
if form.Color != nil {
color, err := label.NormalizeColor(*form.Color)
if err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "StringToColor", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
l.Color = color
@@ -241,7 +241,7 @@ func EditLabel(ctx *context.APIContext) {
}
l.SetArchived(form.IsArchived != nil && *form.IsArchived)
if err := issues_model.UpdateLabel(ctx, l); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -277,7 +277,7 @@ func DeleteLabel(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if err := issues_model.DeleteLabel(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id")); err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteLabel", err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/repo/language.go b/routers/api/v1/repo/language.go
index f1d5bbe45f..00789983ce 100644
--- a/routers/api/v1/repo/language.go
+++ b/routers/api/v1/repo/language.go
@@ -70,7 +70,7 @@ func GetLanguages(ctx *context.APIContext) {
langs, err := repo_model.GetLanguageStats(ctx, ctx.Repo.Repository)
if err != nil {
log.Error("GetLanguageStats failed: %v", err)
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/repo/license.go b/routers/api/v1/repo/license.go
index 8a6bdfd42f..3040815e8a 100644
--- a/routers/api/v1/repo/license.go
+++ b/routers/api/v1/repo/license.go
@@ -38,7 +38,7 @@ func GetLicenses(ctx *context.APIContext) {
licenses, err := repo_model.GetRepoLicenses(ctx, ctx.Repo.Repository)
if err != nil {
log.Error("GetRepoLicenses failed: %v", err)
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go
index 452825c0a3..c1e0b47d33 100644
--- a/routers/api/v1/repo/migrate.go
+++ b/routers/api/v1/repo/migrate.go
@@ -72,21 +72,21 @@ func Migrate(ctx *context.APIContext) {
}
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetUser", err)
+ ctx.APIErrorInternal(err)
}
return
}
if ctx.HasAPIError() {
- ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
+ ctx.APIError(http.StatusUnprocessableEntity, ctx.GetErrMsg())
return
}
if !ctx.Doer.IsAdmin {
if !repoOwner.IsOrganization() && ctx.Doer.ID != repoOwner.ID {
- ctx.Error(http.StatusForbidden, "", "Given user is not an organization.")
+ ctx.APIError(http.StatusForbidden, "Given user is not an organization.")
return
}
@@ -94,10 +94,10 @@ func Migrate(ctx *context.APIContext) {
// Check ownership of organization.
isOwner, err := organization.OrgFromUser(repoOwner).IsOwnedBy(ctx, ctx.Doer.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsOwnedBy", err)
+ ctx.APIErrorInternal(err)
return
} else if !isOwner {
- ctx.Error(http.StatusForbidden, "", "Given user is not owner of organization.")
+ ctx.APIError(http.StatusForbidden, "Given user is not owner of organization.")
return
}
}
@@ -115,12 +115,12 @@ func Migrate(ctx *context.APIContext) {
gitServiceType := convert.ToGitServiceType(form.Service)
if form.Mirror && setting.Mirror.DisableNewPull {
- ctx.Error(http.StatusForbidden, "MirrorsGlobalDisabled", fmt.Errorf("the site administrator has disabled the creation of new pull mirrors"))
+ ctx.APIError(http.StatusForbidden, errors.New("the site administrator has disabled the creation of new pull mirrors"))
return
}
if setting.Repository.DisableMigrations {
- ctx.Error(http.StatusForbidden, "MigrationsGlobalDisabled", fmt.Errorf("the site administrator has disabled migrations"))
+ ctx.APIError(http.StatusForbidden, errors.New("the site administrator has disabled migrations"))
return
}
@@ -129,7 +129,7 @@ func Migrate(ctx *context.APIContext) {
if form.LFS && len(form.LFSEndpoint) > 0 {
ep := lfs.DetermineEndpoint("", form.LFSEndpoint)
if ep == nil {
- ctx.Error(http.StatusInternalServerError, "", ctx.Tr("repo.migrate.invalid_lfs_endpoint"))
+ ctx.APIErrorInternal(errors.New("the LFS endpoint is not valid"))
return
}
err = migrations.IsMigrateURLAllowed(ep.String(), ctx.Doer)
@@ -181,7 +181,7 @@ func Migrate(ctx *context.APIContext) {
IsPrivate: opts.Private || setting.Repository.ForcePrivate,
IsMirror: opts.Mirror,
Status: repo_model.RepositoryBeingMigrated,
- })
+ }, false)
if err != nil {
handleMigrateError(ctx, repoOwner, err)
return
@@ -203,7 +203,7 @@ func Migrate(ctx *context.APIContext) {
}
if repo != nil {
- if errDelete := repo_service.DeleteRepositoryDirectly(ctx, ctx.Doer, repo.ID); errDelete != nil {
+ if errDelete := repo_service.DeleteRepositoryDirectly(ctx, repo.ID); errDelete != nil {
log.Error("DeleteRepository: %v", errDelete)
}
}
@@ -221,35 +221,35 @@ func Migrate(ctx *context.APIContext) {
func handleMigrateError(ctx *context.APIContext, repoOwner *user_model.User, err error) {
switch {
case repo_model.IsErrRepoAlreadyExist(err):
- ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
+ ctx.APIError(http.StatusConflict, "The repository with the same name already exists.")
case repo_model.IsErrRepoFilesAlreadyExist(err):
- ctx.Error(http.StatusConflict, "", "Files already exist for this repository. Adopt them or delete them.")
+ ctx.APIError(http.StatusConflict, "Files already exist for this repository. Adopt them or delete them.")
case migrations.IsRateLimitError(err):
- ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit addressed rate limitation.")
+ ctx.APIError(http.StatusUnprocessableEntity, "Remote visit addressed rate limitation.")
case migrations.IsTwoFactorAuthError(err):
- ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit required two factors authentication.")
+ ctx.APIError(http.StatusUnprocessableEntity, "Remote visit required two factors authentication.")
case repo_model.IsErrReachLimitOfRepo(err):
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))
case db.IsErrNameReserved(err):
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The username '%s' is reserved.", err.(db.ErrNameReserved).Name))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("The username '%s' is reserved.", err.(db.ErrNameReserved).Name))
case db.IsErrNameCharsNotAllowed(err):
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The username '%s' contains invalid characters.", err.(db.ErrNameCharsNotAllowed).Name))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("The username '%s' contains invalid characters.", err.(db.ErrNameCharsNotAllowed).Name))
case db.IsErrNamePatternNotAllowed(err):
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(db.ErrNamePatternNotAllowed).Pattern))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(db.ErrNamePatternNotAllowed).Pattern))
case git.IsErrInvalidCloneAddr(err):
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
case base.IsErrNotSupported(err):
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
default:
err = util.SanitizeErrorCredentialURLs(err)
if strings.Contains(err.Error(), "Authentication failed") ||
strings.Contains(err.Error(), "Bad credentials") ||
strings.Contains(err.Error(), "could not read Username") {
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Authentication failed: %v.", err))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("Authentication failed: %v.", err))
} else if strings.Contains(err.Error(), "fatal:") {
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Migration failed: %v.", err))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("Migration failed: %v.", err))
} else {
- ctx.Error(http.StatusInternalServerError, "MigrateRepository", err)
+ ctx.APIErrorInternal(err)
}
}
}
@@ -259,19 +259,19 @@ func handleRemoteAddrError(ctx *context.APIContext, err error) {
addrErr := err.(*git.ErrInvalidCloneAddr)
switch {
case addrErr.IsURLError:
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
case addrErr.IsPermissionDenied:
if addrErr.LocalPath {
- ctx.Error(http.StatusUnprocessableEntity, "", "You are not allowed to import local repositories.")
+ ctx.APIError(http.StatusUnprocessableEntity, "You are not allowed to import local repositories.")
} else {
- ctx.Error(http.StatusUnprocessableEntity, "", "You can not import from disallowed hosts.")
+ ctx.APIError(http.StatusUnprocessableEntity, "You can not import from disallowed hosts.")
}
case addrErr.IsInvalidPath:
- ctx.Error(http.StatusUnprocessableEntity, "", "Invalid local path, it does not exist or not a directory.")
+ ctx.APIError(http.StatusUnprocessableEntity, "Invalid local path, it does not exist or not a directory.")
default:
- ctx.Error(http.StatusInternalServerError, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error())
+ ctx.APIErrorInternal(fmt.Errorf("unknown error type (ErrInvalidCloneAddr): %w", err))
}
} else {
- ctx.Error(http.StatusInternalServerError, "ParseRemoteAddr", err)
+ ctx.APIErrorInternal(err)
}
}
diff --git a/routers/api/v1/repo/milestone.go b/routers/api/v1/repo/milestone.go
index 8d7516491e..33fa7c4b16 100644
--- a/routers/api/v1/repo/milestone.go
+++ b/routers/api/v1/repo/milestone.go
@@ -74,7 +74,7 @@ func ListMilestones(ctx *context.APIContext) {
Name: ctx.FormString("name"),
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "db.FindAndCount[issues_model.Milestone]", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -173,7 +173,7 @@ func CreateMilestone(ctx *context.APIContext) {
}
if err := issues_model.NewMilestone(ctx, milestone); err != nil {
- ctx.Error(http.StatusInternalServerError, "NewMilestone", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusCreated, convert.ToAPIMilestone(milestone))
@@ -233,7 +233,7 @@ func EditMilestone(ctx *context.APIContext) {
}
if err := issues_model.UpdateMilestone(ctx, milestone, oldIsClosed); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateMilestone", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, convert.ToAPIMilestone(milestone))
@@ -272,7 +272,7 @@ func DeleteMilestone(ctx *context.APIContext) {
}
if err := issues_model.DeleteMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, m.ID); err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteMilestoneByRepoID", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
@@ -288,7 +288,7 @@ func getMilestoneByIDOrName(ctx *context.APIContext) *issues_model.Milestone {
if err == nil {
return milestone
} else if !issues_model.IsErrMilestoneNotExist(err) {
- ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
+ ctx.APIErrorInternal(err)
return nil
}
}
@@ -296,10 +296,10 @@ func getMilestoneByIDOrName(ctx *context.APIContext) *issues_model.Milestone {
milestone, err := issues_model.GetMilestoneByRepoIDANDName(ctx, ctx.Repo.Repository.ID, mile)
if err != nil {
if issues_model.IsErrMilestoneNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return nil
}
- ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
+ ctx.APIErrorInternal(err)
return nil
}
diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go
index c911f6830c..f11a1603c4 100644
--- a/routers/api/v1/repo/mirror.go
+++ b/routers/api/v1/repo/mirror.go
@@ -5,7 +5,6 @@ package repo
import (
"errors"
- "fmt"
"net/http"
"time"
@@ -53,20 +52,20 @@ func MirrorSync(ctx *context.APIContext) {
repo := ctx.Repo.Repository
if !ctx.Repo.CanWrite(unit.TypeCode) {
- ctx.Error(http.StatusForbidden, "MirrorSync", "Must have write access")
+ ctx.APIError(http.StatusForbidden, "Must have write access")
}
if !setting.Mirror.Enabled {
- ctx.Error(http.StatusBadRequest, "MirrorSync", "Mirror feature is disabled")
+ ctx.APIError(http.StatusBadRequest, "Mirror feature is disabled")
return
}
if _, err := repo_model.GetMirrorByRepoID(ctx, repo.ID); err != nil {
if errors.Is(err, repo_model.ErrMirrorNotExist) {
- ctx.Error(http.StatusBadRequest, "MirrorSync", "Repository is not a mirror")
+ ctx.APIError(http.StatusBadRequest, "Repository is not a mirror")
return
}
- ctx.Error(http.StatusInternalServerError, "MirrorSync", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -104,19 +103,19 @@ func PushMirrorSync(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if !setting.Mirror.Enabled {
- ctx.Error(http.StatusBadRequest, "PushMirrorSync", "Mirror feature is disabled")
+ ctx.APIError(http.StatusBadRequest, "Mirror feature is disabled")
return
}
// Get All push mirrors of a specific repo
pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, ctx.Repo.Repository.ID, db.ListOptions{})
if err != nil {
- ctx.Error(http.StatusNotFound, "PushMirrorSync", err)
+ ctx.APIError(http.StatusNotFound, err)
return
}
for _, mirror := range pushMirrors {
ok := mirror_service.SyncPushMirror(ctx, mirror.ID)
if !ok {
- ctx.Error(http.StatusInternalServerError, "PushMirrorSync", "error occurred when syncing push mirror "+mirror.RemoteName)
+ ctx.APIErrorInternal(errors.New("error occurred when syncing push mirror " + mirror.RemoteName))
return
}
}
@@ -161,7 +160,7 @@ func ListPushMirrors(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if !setting.Mirror.Enabled {
- ctx.Error(http.StatusBadRequest, "GetPushMirrorsByRepoID", "Mirror feature is disabled")
+ ctx.APIError(http.StatusBadRequest, "Mirror feature is disabled")
return
}
@@ -169,7 +168,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.Error(http.StatusNotFound, "GetPushMirrorsByRepoID", err)
+ ctx.APIError(http.StatusNotFound, err)
return
}
@@ -219,7 +218,7 @@ func GetPushMirrorByName(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if !setting.Mirror.Enabled {
- ctx.Error(http.StatusBadRequest, "GetPushMirrorByRemoteName", "Mirror feature is disabled")
+ ctx.APIError(http.StatusBadRequest, "Mirror feature is disabled")
return
}
@@ -230,16 +229,16 @@ func GetPushMirrorByName(ctx *context.APIContext) {
RemoteName: mirrorName,
}.ToConds())
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetPushMirrors", err)
+ ctx.APIErrorInternal(err)
return
} else if !exist {
- ctx.Error(http.StatusNotFound, "GetPushMirrors", nil)
+ ctx.APIError(http.StatusNotFound, nil)
return
}
m, err := convert.ToPushMirror(ctx, pushMirror)
if err != nil {
- ctx.ServerError("GetPushMirrorByRemoteName", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, m)
@@ -280,7 +279,7 @@ func AddPushMirror(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if !setting.Mirror.Enabled {
- ctx.Error(http.StatusBadRequest, "AddPushMirror", "Mirror feature is disabled")
+ ctx.APIError(http.StatusBadRequest, "Mirror feature is disabled")
return
}
@@ -320,7 +319,7 @@ func DeletePushMirrorByRemoteName(ctx *context.APIContext) {
// "$ref": "#/responses/error"
if !setting.Mirror.Enabled {
- ctx.Error(http.StatusBadRequest, "DeletePushMirrorByName", "Mirror feature is disabled")
+ ctx.APIError(http.StatusBadRequest, "Mirror feature is disabled")
return
}
@@ -328,7 +327,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.Error(http.StatusNotFound, "DeletePushMirrors", err)
+ ctx.APIError(http.StatusNotFound, err)
return
}
ctx.Status(http.StatusNoContent)
@@ -339,7 +338,7 @@ func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirro
interval, err := time.ParseDuration(mirrorOption.Interval)
if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) {
- ctx.Error(http.StatusBadRequest, "CreatePushMirror", err)
+ ctx.APIError(http.StatusBadRequest, err)
return
}
@@ -354,42 +353,42 @@ func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirro
remoteSuffix, err := util.CryptoRandomString(10)
if err != nil {
- ctx.ServerError("CryptoRandomString", err)
+ ctx.APIErrorInternal(err)
return
}
remoteAddress, err := util.SanitizeURL(mirrorOption.RemoteAddress)
if err != nil {
- ctx.ServerError("SanitizeURL", err)
+ ctx.APIErrorInternal(err)
return
}
pushMirror := &repo_model.PushMirror{
RepoID: repo.ID,
Repo: repo,
- RemoteName: fmt.Sprintf("remote_mirror_%s", remoteSuffix),
+ RemoteName: "remote_mirror_" + remoteSuffix,
Interval: interval,
SyncOnCommit: mirrorOption.SyncOnCommit,
RemoteAddress: remoteAddress,
}
if err = db.Insert(ctx, pushMirror); err != nil {
- ctx.ServerError("InsertPushMirror", err)
+ ctx.APIErrorInternal(err)
return
}
// if the registration of the push mirrorOption fails remove it from the database
if err = mirror_service.AddPushMirrorRemote(ctx, pushMirror, address); err != nil {
if err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ID: pushMirror.ID, RepoID: pushMirror.RepoID}); err != nil {
- ctx.ServerError("DeletePushMirrors", err)
+ ctx.APIErrorInternal(err)
return
}
- ctx.ServerError("AddPushMirrorRemote", err)
+ ctx.APIErrorInternal(err)
return
}
m, err := convert.ToPushMirror(ctx, pushMirror)
if err != nil {
- ctx.ServerError("ToPushMirror", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, m)
@@ -400,13 +399,13 @@ func HandleRemoteAddressError(ctx *context.APIContext, err error) {
addrErr := err.(*git.ErrInvalidCloneAddr)
switch {
case addrErr.IsProtocolInvalid:
- ctx.Error(http.StatusBadRequest, "CreatePushMirror", "Invalid mirror protocol")
+ ctx.APIError(http.StatusBadRequest, "Invalid mirror protocol")
case addrErr.IsURLError:
- ctx.Error(http.StatusBadRequest, "CreatePushMirror", "Invalid Url ")
+ ctx.APIError(http.StatusBadRequest, "Invalid Url ")
case addrErr.IsPermissionDenied:
- ctx.Error(http.StatusUnauthorized, "CreatePushMirror", "Permission denied")
+ ctx.APIError(http.StatusUnauthorized, "Permission denied")
default:
- ctx.Error(http.StatusBadRequest, "CreatePushMirror", "Unknown error")
+ ctx.APIError(http.StatusBadRequest, "Unknown error")
}
return
}
diff --git a/routers/api/v1/repo/notes.go b/routers/api/v1/repo/notes.go
index 8fec844cc4..87efb1caf2 100644
--- a/routers/api/v1/repo/notes.go
+++ b/routers/api/v1/repo/notes.go
@@ -4,7 +4,7 @@
package repo
import (
- "fmt"
+ "errors"
"net/http"
"code.gitea.io/gitea/modules/git"
@@ -54,7 +54,7 @@ func GetNote(ctx *context.APIContext) {
sha := ctx.PathParam("sha")
if !git.IsValidRefPattern(sha) {
- ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha))
+ ctx.APIError(http.StatusUnprocessableEntity, "no valid ref or sha: "+sha)
return
}
getNote(ctx, sha)
@@ -62,16 +62,16 @@ func GetNote(ctx *context.APIContext) {
func getNote(ctx *context.APIContext, identifier string) {
if ctx.Repo.GitRepo == nil {
- ctx.InternalServerError(fmt.Errorf("no open git repo"))
+ ctx.APIErrorInternal(errors.New("no open git repo"))
return
}
commitID, err := ctx.Repo.GitRepo.ConvertToGitID(identifier)
if err != nil {
if git.IsErrNotExist(err) {
- ctx.NotFound(err)
+ ctx.APIErrorNotFound(err)
} else {
- ctx.Error(http.StatusInternalServerError, "ConvertToSHA1", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -79,10 +79,10 @@ func getNote(ctx *context.APIContext, identifier string) {
var note git.Note
if err := git.GetNote(ctx, ctx.Repo.GitRepo, commitID.String(), &note); err != nil {
if git.IsErrNotExist(err) {
- ctx.NotFound(identifier)
+ ctx.APIErrorNotFound("commit doesn't exist: " + identifier)
return
}
- ctx.Error(http.StatusInternalServerError, "GetNote", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -96,7 +96,7 @@ func getNote(ctx *context.APIContext, identifier string) {
Files: files,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ToCommit", err)
+ ctx.APIErrorInternal(err)
return
}
apiNote := api.Note{Message: string(note.Message), Commit: cmt}
diff --git a/routers/api/v1/repo/patch.go b/routers/api/v1/repo/patch.go
index 5e24dcf891..e9f5cf5d90 100644
--- a/routers/api/v1/repo/patch.go
+++ b/routers/api/v1/repo/patch.go
@@ -5,15 +5,10 @@ package repo
import (
"net/http"
- "time"
- git_model "code.gitea.io/gitea/models/git"
- repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
- pull_service "code.gitea.io/gitea/services/pull"
"code.gitea.io/gitea/services/repository/files"
)
@@ -49,63 +44,22 @@ func ApplyDiffPatch(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
// "423":
// "$ref": "#/responses/repoArchivedError"
- apiOpts := web.GetForm(ctx).(*api.ApplyDiffPatchFileOptions)
-
+ apiOpts, changeRepoFileOpts := getAPIChangeRepoFileOptions[*api.ApplyDiffPatchFileOptions](ctx)
opts := &files.ApplyDiffPatchOptions{
- Content: apiOpts.Content,
- SHA: apiOpts.SHA,
- Message: apiOpts.Message,
- OldBranch: apiOpts.BranchName,
- NewBranch: apiOpts.NewBranchName,
- Committer: &files.IdentityOptions{
- Name: apiOpts.Committer.Name,
- Email: apiOpts.Committer.Email,
- },
- Author: &files.IdentityOptions{
- Name: apiOpts.Author.Name,
- Email: apiOpts.Author.Email,
- },
- Dates: &files.CommitDateOptions{
- Author: apiOpts.Dates.Author,
- Committer: apiOpts.Dates.Committer,
- },
- Signoff: apiOpts.Signoff,
- }
- if opts.Dates.Author.IsZero() {
- opts.Dates.Author = time.Now()
- }
- if opts.Dates.Committer.IsZero() {
- opts.Dates.Committer = time.Now()
- }
-
- if opts.Message == "" {
- opts.Message = "apply-patch"
- }
+ Content: apiOpts.Content,
+ Message: util.IfZero(apiOpts.Message, "apply-patch"),
- if !canWriteFiles(ctx, apiOpts.BranchName) {
- ctx.Error(http.StatusInternalServerError, "ApplyPatch", repo_model.ErrUserDoesNotHaveAccessToRepo{
- UserID: ctx.Doer.ID,
- RepoName: ctx.Repo.Repository.LowerName,
- })
- return
+ OldBranch: changeRepoFileOpts.OldBranch,
+ NewBranch: changeRepoFileOpts.NewBranch,
+ Committer: changeRepoFileOpts.Committer,
+ Author: changeRepoFileOpts.Author,
+ Dates: changeRepoFileOpts.Dates,
+ Signoff: changeRepoFileOpts.Signoff,
}
fileResponse, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, opts)
if err != nil {
- if files.IsErrUserCannotCommit(err) || pull_service.IsErrFilePathProtected(err) {
- ctx.Error(http.StatusForbidden, "Access", err)
- return
- }
- if git_model.IsErrBranchAlreadyExists(err) || files.IsErrFilenameInvalid(err) || pull_service.IsErrSHADoesNotMatch(err) ||
- files.IsErrFilePathInvalid(err) || files.IsErrRepoFileAlreadyExists(err) {
- ctx.Error(http.StatusUnprocessableEntity, "Invalid", err)
- return
- }
- if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) {
- ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err)
- return
- }
- ctx.Error(http.StatusInternalServerError, "ApplyPatch", err)
+ handleChangeRepoFilesError(ctx, err)
} else {
ctx.JSON(http.StatusCreated, fileResponse)
}
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index f7fdc93f81..2c194f9253 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -23,6 +23,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
@@ -59,6 +60,10 @@ func ListPullRequests(ctx *context.APIContext) {
// description: Name of the repo
// type: string
// required: true
+ // - name: base_branch
+ // in: query
+ // description: Filter by target base branch of the pull request
+ // type: string
// - name: state
// in: query
// description: State of pull request
@@ -69,7 +74,7 @@ func ListPullRequests(ctx *context.APIContext) {
// in: query
// description: Type of sort
// type: string
- // enum: [oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority]
+ // enum: [oldest, recentupdate, recentclose, leastupdate, mostcomment, leastcomment, priority]
// - name: milestone
// in: query
// description: ID of the milestone
@@ -108,7 +113,7 @@ func ListPullRequests(ctx *context.APIContext) {
labelIDs, err := base.StringsToInt64s(ctx.FormStrings("labels"))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "PullRequests", err)
+ ctx.APIErrorInternal(err)
return
}
var posterID int64
@@ -116,9 +121,9 @@ func ListPullRequests(ctx *context.APIContext) {
poster, err := user_model.GetUserByName(ctx, posterStr)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusBadRequest, "Poster not found", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -132,15 +137,16 @@ func ListPullRequests(ctx *context.APIContext) {
Labels: labelIDs,
MilestoneID: ctx.FormInt64("milestone"),
PosterID: posterID,
+ BaseBranch: ctx.FormTrim("base_branch"),
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "PullRequests", err)
+ ctx.APIErrorInternal(err)
return
}
apiPrs, err := convert.ToAPIPullRequests(ctx, ctx.Repo.Repository, prs, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ToAPIPullRequests", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -182,21 +188,25 @@ func GetPullRequest(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err = pr.LoadBaseRepo(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
+ ctx.APIErrorInternal(err)
return
}
if err = pr.LoadHeadRepo(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
+ ctx.APIErrorInternal(err)
return
}
+
+ // Consider API access a view for delayed checking.
+ pull_service.StartPullRequestCheckOnView(ctx, pr)
+
ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
}
@@ -252,9 +262,9 @@ func GetPullRequestByBaseHead(ctx *context.APIContext) {
repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner, name)
if err != nil {
if repo_model.IsErrRepoNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetRepositoryByOwnerName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -267,21 +277,25 @@ func GetPullRequestByBaseHead(ctx *context.APIContext) {
pr, err := issues_model.GetPullRequestByBaseHeadInfo(ctx, ctx.Repo.Repository.ID, headRepoID, ctx.PathParam("base"), headBranch)
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetPullRequestByBaseHeadInfo", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err = pr.LoadBaseRepo(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
+ ctx.APIErrorInternal(err)
return
}
if err = pr.LoadHeadRepo(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
+ ctx.APIErrorInternal(err)
return
}
+
+ // Consider API access a view for delayed checking.
+ pull_service.StartPullRequestCheckOnView(ctx, pr)
+
ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
}
@@ -327,9 +341,9 @@ func DownloadPullDiffOrPatch(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -343,7 +357,7 @@ func DownloadPullDiffOrPatch(ctx *context.APIContext) {
binary := ctx.FormBool("binary")
if err := pull_service.DownloadDiffOrPatch(ctx, pr, ctx, patch, binary); err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -388,7 +402,7 @@ func CreatePullRequest(ctx *context.APIContext) {
form := *web.GetForm(ctx).(*api.CreatePullRequestOption)
if form.Head == form.Base {
- ctx.Error(http.StatusUnprocessableEntity, "BaseHeadSame", "Invalid PullRequest: There are no changes between the head and the base")
+ ctx.APIError(http.StatusUnprocessableEntity, "Invalid PullRequest: There are no changes between the head and the base")
return
}
@@ -406,7 +420,7 @@ func CreatePullRequest(ctx *context.APIContext) {
defer closer()
if !compareResult.baseRef.IsBranch() || !compareResult.headRef.IsBranch() {
- ctx.Error(http.StatusUnprocessableEntity, "BaseHeadInvalidRefType", "Invalid PullRequest: base and head must be branches")
+ ctx.APIError(http.StatusUnprocessableEntity, "Invalid PullRequest: base and head must be branches")
return
}
@@ -417,7 +431,7 @@ func CreatePullRequest(ctx *context.APIContext) {
)
if err != nil {
if !issues_model.IsErrPullRequestNotExist(err) {
- ctx.Error(http.StatusInternalServerError, "GetUnmergedPullRequest", err)
+ ctx.APIErrorInternal(err)
return
}
} else {
@@ -429,14 +443,14 @@ func CreatePullRequest(ctx *context.APIContext) {
HeadBranch: existingPr.HeadBranch,
BaseBranch: existingPr.BaseBranch,
}
- ctx.Error(http.StatusConflict, "GetUnmergedPullRequest", err)
+ ctx.APIError(http.StatusConflict, err)
return
}
if len(form.Labels) > 0 {
labels, err := issues_model.GetLabelsInRepoByIDs(ctx, ctx.Repo.Repository.ID, form.Labels)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetLabelsInRepoByIDs", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -448,7 +462,7 @@ func CreatePullRequest(ctx *context.APIContext) {
if ctx.Repo.Owner.IsOrganization() {
orgLabels, err := issues_model.GetLabelsInOrgByIDs(ctx, ctx.Repo.Owner.ID, form.Labels)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetLabelsInOrgByIDs", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -464,9 +478,9 @@ func CreatePullRequest(ctx *context.APIContext) {
milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, form.Milestone)
if err != nil {
if issues_model.IsErrMilestoneNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
+ ctx.APIErrorInternal(fmt.Errorf("GetMilestoneByRepoID: %w", err))
}
return
}
@@ -504,9 +518,9 @@ func CreatePullRequest(ctx *context.APIContext) {
assigneeIDs, err := issues_model.MakeIDsFromAPIAssigneesToAdd(ctx, form.Assignee, form.Assignees)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("Assignee does not exist: [name: %s]", err))
} else {
- ctx.Error(http.StatusInternalServerError, "AddAssigneeByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -514,17 +528,17 @@ func CreatePullRequest(ctx *context.APIContext) {
for _, aID := range assigneeIDs {
assignee, err := user_model.GetUserByID(ctx, aID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserByID", err)
+ ctx.APIErrorInternal(err)
return
}
valid, err := access_model.CanBeAssigned(ctx, assignee, repo, true)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "canBeAssigned", err)
+ ctx.APIErrorInternal(err)
return
}
if !valid {
- ctx.Error(http.StatusUnprocessableEntity, "canBeAssigned", repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: repo.Name})
+ ctx.APIError(http.StatusUnprocessableEntity, repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: repo.Name})
return
}
}
@@ -543,13 +557,13 @@ func CreatePullRequest(ctx *context.APIContext) {
if err := pull_service.NewPullRequest(ctx, prOpts); err != nil {
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) {
- ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else if errors.Is(err, user_model.ErrBlockedUser) {
- ctx.Error(http.StatusForbidden, "BlockedUser", err)
+ ctx.APIError(http.StatusForbidden, err)
} else if errors.Is(err, issues_model.ErrMustCollaborator) {
- ctx.Error(http.StatusForbidden, "MustCollaborator", err)
+ ctx.APIError(http.StatusForbidden, err)
} else {
- ctx.Error(http.StatusInternalServerError, "NewPullRequest", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -606,23 +620,23 @@ func EditPullRequest(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
err = pr.LoadIssue(ctx)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
+ ctx.APIErrorInternal(err)
return
}
issue := pr.Issue
issue.Repo = ctx.Repo.Repository
if err := issue.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -634,7 +648,7 @@ func EditPullRequest(ctx *context.APIContext) {
if len(form.Title) > 0 {
err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ChangeTitle", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -642,11 +656,11 @@ func EditPullRequest(ctx *context.APIContext) {
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, issue.ContentVersion)
if err != nil {
if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
- ctx.Error(http.StatusBadRequest, "ChangeContent", err)
+ ctx.APIError(http.StatusBadRequest, err)
return
}
- ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -661,7 +675,7 @@ func EditPullRequest(ctx *context.APIContext) {
}
if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err)
+ ctx.APIErrorInternal(err)
return
}
issue.DeadlineUnix = deadlineUnix
@@ -679,11 +693,11 @@ func EditPullRequest(ctx *context.APIContext) {
err = issue_service.UpdateAssignees(ctx, issue, form.Assignee, form.Assignees, ctx.Doer)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("Assignee does not exist: [name: %s]", err))
} else if errors.Is(err, user_model.ErrBlockedUser) {
- ctx.Error(http.StatusForbidden, "UpdateAssignees", err)
+ ctx.APIError(http.StatusForbidden, err)
} else {
- ctx.Error(http.StatusInternalServerError, "UpdateAssignees", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -693,8 +707,13 @@ func EditPullRequest(ctx *context.APIContext) {
issue.MilestoneID != form.Milestone {
oldMilestoneID := issue.MilestoneID
issue.MilestoneID = form.Milestone
+ issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, form.Milestone)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
- ctx.Error(http.StatusInternalServerError, "ChangeMilestoneAssign", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -702,14 +721,14 @@ func EditPullRequest(ctx *context.APIContext) {
if ctx.Repo.CanWrite(unit.TypePullRequests) && form.Labels != nil {
labels, err := issues_model.GetLabelsInRepoByIDs(ctx, ctx.Repo.Repository.ID, form.Labels)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetLabelsInRepoByIDsError", err)
+ ctx.APIErrorInternal(err)
return
}
if ctx.Repo.Owner.IsOrganization() {
orgLabels, err := issues_model.GetLabelsInOrgByIDs(ctx, ctx.Repo.Owner.ID, form.Labels)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetLabelsInOrgByIDs", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -717,14 +736,14 @@ func EditPullRequest(ctx *context.APIContext) {
}
if err = issues_model.ReplaceIssueLabels(ctx, issue, labels, ctx.Doer); err != nil {
- ctx.Error(http.StatusInternalServerError, "ReplaceLabelsError", err)
+ ctx.APIErrorInternal(err)
return
}
}
if form.State != nil {
if pr.HasMerged {
- ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged")
+ ctx.APIError(http.StatusPreconditionFailed, "cannot change state of this pull request, it was already merged")
return
}
@@ -737,22 +756,22 @@ func EditPullRequest(ctx *context.APIContext) {
// change pull target branch
if !pr.HasMerged && len(form.Base) != 0 && form.Base != pr.BaseBranch {
- if !ctx.Repo.GitRepo.IsBranchExist(form.Base) {
- ctx.Error(http.StatusNotFound, "NewBaseBranchNotExist", fmt.Errorf("new base '%s' not exist", form.Base))
+ if !gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, form.Base) {
+ ctx.APIError(http.StatusNotFound, fmt.Errorf("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.Error(http.StatusConflict, "IsErrPullRequestAlreadyExists", err)
+ ctx.APIError(http.StatusConflict, err)
return
} else if issues_model.IsErrIssueIsClosed(err) {
- ctx.Error(http.StatusUnprocessableEntity, "IsErrIssueIsClosed", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
} else if pull_service.IsErrPullRequestHasMerged(err) {
- ctx.Error(http.StatusConflict, "IsErrPullRequestHasMerged", err)
+ ctx.APIError(http.StatusConflict, err)
return
}
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
notify_service.PullRequestChangeTargetBranch(ctx, ctx.Doer, pr, form.Base)
@@ -762,10 +781,10 @@ func EditPullRequest(ctx *context.APIContext) {
if form.AllowMaintainerEdit != nil {
if err := pull_service.SetAllowEdits(ctx, ctx.Doer, pr, *form.AllowMaintainerEdit); err != nil {
if errors.Is(err, pull_service.ErrUserHasNoPermissionForAction) {
- ctx.Error(http.StatusForbidden, "SetAllowEdits", fmt.Sprintf("SetAllowEdits: %s", err))
+ ctx.APIError(http.StatusForbidden, fmt.Sprintf("SetAllowEdits: %s", err))
return
}
- ctx.ServerError("SetAllowEdits", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -774,9 +793,9 @@ func EditPullRequest(ctx *context.APIContext) {
pr, err = issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, pr.Index)
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -818,9 +837,9 @@ func IsPullRequestMerged(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -828,7 +847,7 @@ func IsPullRequestMerged(ctx *context.APIContext) {
if pr.HasMerged {
ctx.Status(http.StatusNoContent)
}
- ctx.NotFound()
+ ctx.APIErrorNotFound()
}
// MergePullRequest merges a PR given an index
@@ -876,20 +895,20 @@ 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.NotFound("GetPullRequestByIndex", err)
+ ctx.APIErrorNotFound("GetPullRequestByIndex", err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err := pr.LoadHeadRepo(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
+ ctx.APIErrorInternal(err)
return
}
if err := pr.LoadIssue(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
+ ctx.APIErrorInternal(err)
return
}
pr.Issue.Repo = ctx.Repo.Repository
@@ -897,7 +916,7 @@ func MergePullRequest(ctx *context.APIContext) {
if ctx.IsSigned {
// Update issue-user.
if err = activities_model.SetIssueReadBy(ctx, pr.Issue.ID, ctx.Doer.ID); err != nil {
- ctx.Error(http.StatusInternalServerError, "ReadBy", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -915,21 +934,21 @@ 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 errors.Is(err, pull_service.ErrIsClosed) {
- ctx.NotFound()
- } else if errors.Is(err, pull_service.ErrUserNotAllowedToMerge) {
- ctx.Error(http.StatusMethodNotAllowed, "Merge", "User not allowed to merge PR")
+ ctx.APIErrorNotFound()
+ } else if errors.Is(err, pull_service.ErrNoPermissionToMerge) {
+ ctx.APIError(http.StatusMethodNotAllowed, "User not allowed to merge PR")
} else if errors.Is(err, pull_service.ErrHasMerged) {
- ctx.Error(http.StatusMethodNotAllowed, "PR already merged", "")
+ ctx.APIError(http.StatusMethodNotAllowed, "")
} else if errors.Is(err, pull_service.ErrIsWorkInProgress) {
- ctx.Error(http.StatusMethodNotAllowed, "PR is a work in progress", "Work in progress PRs cannot be merged")
+ ctx.APIError(http.StatusMethodNotAllowed, "Work in progress PRs cannot be merged")
} else if errors.Is(err, pull_service.ErrNotMergeableState) {
- ctx.Error(http.StatusMethodNotAllowed, "PR not in mergeable state", "Please try again later")
- } else if pull_service.IsErrDisallowedToMerge(err) {
- ctx.Error(http.StatusMethodNotAllowed, "PR is not ready to be merged", err)
+ ctx.APIError(http.StatusMethodNotAllowed, "Please try again later")
+ } else if errors.Is(err, pull_service.ErrNotReadyToMerge) {
+ ctx.APIError(http.StatusMethodNotAllowed, err)
} else if asymkey_service.IsErrWontSign(err) {
- ctx.Error(http.StatusMethodNotAllowed, fmt.Sprintf("Protected branch %s requires signed commits but this merge would not be signed", pr.BaseBranch), err)
+ ctx.APIError(http.StatusMethodNotAllowed, err)
} else {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -938,14 +957,14 @@ 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.Error(http.StatusMethodNotAllowed, "Invalid merge style", fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do)))
+ ctx.APIError(http.StatusMethodNotAllowed, fmt.Errorf("%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.JSON(http.StatusConflict, err)
return
}
- ctx.Error(http.StatusInternalServerError, "Manually-Merged", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusOK)
@@ -960,7 +979,7 @@ func MergePullRequest(ctx *context.APIContext) {
if len(message) == 0 {
message, _, err = pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pr, repo_model.MergeStyle(form.Do))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetDefaultMergeMessage", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -974,10 +993,10 @@ func MergePullRequest(ctx *context.APIContext) {
scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message, form.DeleteBranchAfterMerge)
if err != nil {
if pull_model.IsErrAlreadyScheduledToAutoMerge(err) {
- ctx.Error(http.StatusConflict, "ScheduleAutoMerge", err)
+ ctx.APIError(http.StatusConflict, err)
return
}
- ctx.Error(http.StatusInternalServerError, "ScheduleAutoMerge", err)
+ ctx.APIErrorInternal(err)
return
} else if scheduled {
// nothing more to do ...
@@ -988,7 +1007,7 @@ func MergePullRequest(ctx *context.APIContext) {
if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil {
if pull_service.IsErrInvalidMergeStyle(err) {
- ctx.Error(http.StatusMethodNotAllowed, "Invalid merge style", fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do)))
+ ctx.APIError(http.StatusMethodNotAllowed, fmt.Errorf("%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)
@@ -999,18 +1018,18 @@ func MergePullRequest(ctx *context.APIContext) {
conflictError := err.(pull_service.ErrMergeUnrelatedHistories)
ctx.JSON(http.StatusConflict, conflictError)
} else if git.IsErrPushOutOfDate(err) {
- ctx.Error(http.StatusConflict, "Merge", "merge push out of date")
+ ctx.APIError(http.StatusConflict, "merge push out of date")
} else if pull_service.IsErrSHADoesNotMatch(err) {
- ctx.Error(http.StatusConflict, "Merge", "head out of date")
+ ctx.APIError(http.StatusConflict, "head out of date")
} else if git.IsErrPushRejected(err) {
errPushRej := err.(*git.ErrPushRejected)
if len(errPushRej.Message) == 0 {
- ctx.Error(http.StatusConflict, "Merge", "PushRejected without remote error message")
+ ctx.APIError(http.StatusConflict, "PushRejected without remote error message")
} else {
- ctx.Error(http.StatusConflict, "Merge", "PushRejected with remote message: "+errPushRej.Message)
+ ctx.APIError(http.StatusConflict, "PushRejected with remote message: "+errPushRej.Message)
}
} else {
- ctx.Error(http.StatusInternalServerError, "Merge", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -1024,7 +1043,7 @@ func MergePullRequest(ctx *context.APIContext) {
// Don't cleanup when there are other PR's that use this branch as head branch.
exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch)
if err != nil {
- ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err)
+ ctx.APIErrorInternal(err)
return
}
if exist {
@@ -1038,7 +1057,7 @@ func MergePullRequest(ctx *context.APIContext) {
} else {
headRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo)
if err != nil {
- ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.FullName()), err)
+ ctx.APIErrorInternal(err)
return
}
defer headRepo.Close()
@@ -1047,13 +1066,13 @@ func MergePullRequest(ctx *context.APIContext) {
if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, headRepo, pr.HeadBranch, pr); err != nil {
switch {
case git.IsErrBranchNotExist(err):
- ctx.NotFound(err)
+ ctx.APIErrorNotFound(err)
case errors.Is(err, repo_service.ErrBranchIsDefault):
- ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch"))
+ ctx.APIError(http.StatusForbidden, errors.New("can not delete default branch"))
case errors.Is(err, git_model.ErrBranchIsProtected):
- ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected"))
+ ctx.APIError(http.StatusForbidden, errors.New("branch protected"))
default:
- ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -1092,14 +1111,14 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
headUser, err = user_model.GetUserByName(ctx, headInfos[0])
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.NotFound("GetUserByName")
+ ctx.APIErrorNotFound("GetUserByName")
} else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
}
return nil, nil
}
} else {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return nil, nil
}
@@ -1110,14 +1129,14 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
if headRepo == nil && !isSameRepo {
err = baseRepo.GetBaseRepo(ctx)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetBaseRepo", err)
+ ctx.APIErrorInternal(err)
return nil, nil
}
// Check if baseRepo's base repository is the same as headUser's repository.
if baseRepo.BaseRepo == nil || baseRepo.BaseRepo.OwnerID != headUser.ID {
log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID)
- ctx.NotFound("GetBaseRepo")
+ ctx.APIErrorNotFound("GetBaseRepo")
return nil, nil
}
// Assign headRepo so it can be used below.
@@ -1132,7 +1151,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
} else {
headGitRepo, err = gitrepo.OpenRepository(ctx, headRepo)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
+ ctx.APIErrorInternal(err)
return nil, nil
}
closer = func() { _ = headGitRepo.Close() }
@@ -1146,13 +1165,13 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
permBase, err := access_model.GetUserRepoPermission(ctx, baseRepo, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
+ ctx.APIErrorInternal(err)
return nil, nil
}
if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(unit.TypeCode) {
log.Trace("Permission Denied: User %-v cannot create/read pull requests or cannot read code in Repo %-v\nUser in baseRepo has Permissions: %-+v", ctx.Doer, baseRepo, permBase)
- ctx.NotFound("Can't read pulls or can't read UnitTypeCode")
+ ctx.APIErrorNotFound("Can't read pulls or can't read UnitTypeCode")
return nil, nil
}
@@ -1160,12 +1179,12 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
// TODO: could the logic be simplified if the headRepo is the same as the baseRepo? Need to think more about it.
permHead, err := access_model.GetUserRepoPermission(ctx, headRepo, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
+ ctx.APIErrorInternal(err)
return nil, nil
}
if !permHead.CanRead(unit.TypeCode) {
log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v", ctx.Doer, headRepo, permHead)
- ctx.NotFound("Can't read headRepo UnitTypeCode")
+ ctx.APIErrorNotFound("Can't read headRepo UnitTypeCode")
return nil, nil
}
@@ -1178,13 +1197,13 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
headRefValid := headRef.IsBranch() || headRef.IsTag() || git.IsStringLikelyCommitID(git.ObjectFormatFromName(headRepo.ObjectFormatName), headRef.ShortName())
// Check if base&head ref are valid.
if !baseRefValid || !headRefValid {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return nil, nil
}
compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseRef.ShortName(), headRef.ShortName(), false, false)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err)
+ ctx.APIErrorInternal(err)
return nil, nil
}
@@ -1236,34 +1255,34 @@ func UpdatePullRequest(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
if pr.HasMerged {
- ctx.Error(http.StatusUnprocessableEntity, "UpdatePullRequest", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
if err = pr.LoadIssue(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
+ ctx.APIErrorInternal(err)
return
}
if pr.Issue.IsClosed {
- ctx.Error(http.StatusUnprocessableEntity, "UpdatePullRequest", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
if err = pr.LoadBaseRepo(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
+ ctx.APIErrorInternal(err)
return
}
if err = pr.LoadHeadRepo(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -1271,7 +1290,7 @@ func UpdatePullRequest(ctx *context.APIContext) {
allowedUpdateByMerge, allowedUpdateByRebase, err := pull_service.IsUserAllowedToUpdate(ctx, pr, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsUserAllowedToMerge", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -1283,15 +1302,15 @@ 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(ctx, pr, ctx.Doer, message, rebase); err != nil {
+ if err = pull_service.Update(graceful.GetManager().ShutdownContext(), pr, ctx.Doer, message, rebase); err != nil {
if pull_service.IsErrMergeConflicts(err) {
- ctx.Error(http.StatusConflict, "Update", "merge failed because of conflict")
+ ctx.APIError(http.StatusConflict, "merge failed because of conflict")
return
} else if pull_service.IsErrRebaseConflicts(err) {
- ctx.Error(http.StatusConflict, "Update", "rebase failed because of conflict")
+ ctx.APIError(http.StatusConflict, "rebase failed because of conflict")
return
}
- ctx.Error(http.StatusInternalServerError, "pull_service.Update", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -1336,37 +1355,37 @@ func CancelScheduledAutoMerge(ctx *context.APIContext) {
pull, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, pullIndex)
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
exist, autoMerge, err := pull_model.GetScheduledMergeByPullID(ctx, pull.ID)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
if !exist {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if ctx.Doer.ID != autoMerge.DoerID {
allowed, err := access_model.IsUserRepoAdmin(ctx, ctx.Repo.Repository, ctx.Doer)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
if !allowed {
- ctx.Error(http.StatusForbidden, "No permission to cancel", "user has no permission to cancel the scheduled auto merge")
+ ctx.APIError(http.StatusForbidden, "user has no permission to cancel the scheduled auto merge")
return
}
}
if err := automerge.RemoveScheduledAutoMerge(ctx, ctx.Doer, pull); err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
} else {
ctx.Status(http.StatusNoContent)
}
@@ -1421,22 +1440,22 @@ func GetPullRequestCommits(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err := pr.LoadBaseRepo(ctx); err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
var prInfo *git.CompareInfo
baseGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.BaseRepo)
if err != nil {
- ctx.ServerError("OpenRepository", err)
+ ctx.APIErrorInternal(err)
return
}
defer closer.Close()
@@ -1447,7 +1466,7 @@ func GetPullRequestCommits(ctx *context.APIContext) {
prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName(), false, false)
}
if err != nil {
- ctx.ServerError("GetCompareInfo", err)
+ ctx.APIErrorInternal(err)
return
}
commits := prInfo.Commits
@@ -1476,7 +1495,7 @@ func GetPullRequestCommits(ctx *context.APIContext) {
Files: files,
})
if err != nil {
- ctx.ServerError("toCommit", err)
+ ctx.APIErrorInternal(err)
return
}
apiCommits = append(apiCommits, apiCommit)
@@ -1544,20 +1563,20 @@ func GetPullRequestFiles(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.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err := pr.LoadBaseRepo(ctx); err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
if err := pr.LoadHeadRepo(ctx); err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -1570,13 +1589,13 @@ func GetPullRequestFiles(ctx *context.APIContext) {
prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName(), true, false)
}
if err != nil {
- ctx.ServerError("GetCompareInfo", err)
+ ctx.APIErrorInternal(err)
return
}
headCommitID, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
if err != nil {
- ctx.ServerError("GetRefCommitID", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -1586,7 +1605,7 @@ func GetPullRequestFiles(ctx *context.APIContext) {
maxLines := setting.Git.MaxGitDiffLines
// FIXME: If there are too many files in the repo, may cause some unpredictable issues.
- diff, err := gitdiff.GetDiff(ctx, baseGitRepo,
+ diff, err := gitdiff.GetDiffForAPI(ctx, baseGitRepo,
&gitdiff.DiffOptions{
BeforeCommitID: startCommitID,
AfterCommitID: endCommitID,
@@ -1597,13 +1616,18 @@ func GetPullRequestFiles(ctx *context.APIContext) {
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.FormString("whitespace")),
})
if err != nil {
- ctx.ServerError("GetDiff", err)
+ ctx.APIErrorInternal(err)
return
}
+ diffShortStat, err := gitdiff.GetDiffShortStat(baseGitRepo, startCommitID, endCommitID)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
listOptions := utils.GetListOptions(ctx)
- totalNumberOfFiles := diff.NumFiles
+ totalNumberOfFiles := diffShortStat.NumFiles
totalNumberOfPages := int(math.Ceil(float64(totalNumberOfFiles) / float64(listOptions.PageSize)))
start, limit := listOptions.GetSkipTake()
@@ -1614,7 +1638,9 @@ func GetPullRequestFiles(ctx *context.APIContext) {
apiFiles := make([]*api.ChangedFile, 0, limit)
for i := start; i < start+limit; i++ {
- apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.HeadRepo, endCommitID))
+ // refs/pull/1/head stores the HEAD commit ID, allowing all related commits to be found in the base repository.
+ // The head repository might have been deleted, so we should not rely on it here.
+ apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.BaseRepo, endCommitID))
}
ctx.SetLinkHeader(totalNumberOfFiles, listOptions.PageSize)
diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go
index 6d7a326370..9421a052db 100644
--- a/routers/api/v1/repo/pull_review.go
+++ b/routers/api/v1/repo/pull_review.go
@@ -64,20 +64,20 @@ 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.NotFound("GetPullRequestByIndex", err)
+ ctx.APIErrorNotFound("GetPullRequestByIndex", err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err = pr.LoadIssue(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
+ ctx.APIErrorInternal(err)
return
}
if err = pr.Issue.LoadRepo(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadRepo", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -88,19 +88,19 @@ func ListPullReviews(ctx *context.APIContext) {
allReviews, err := issues_model.FindReviews(ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
count, err := issues_model.CountReviews(ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
apiReviews, err := convert.ToPullReviewList(ctx, allReviews, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "convertToPullReviewList", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -151,7 +151,7 @@ func GetPullReview(ctx *context.APIContext) {
apiReview, err := convert.ToPullReview(ctx, review, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "convertToPullReview", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -201,7 +201,7 @@ func GetPullReviewComments(ctx *context.APIContext) {
apiComments, err := convert.ToPullReviewCommentList(ctx, review, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "convertToPullReviewCommentList", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -252,16 +252,16 @@ func DeletePullReview(ctx *context.APIContext) {
}
if ctx.Doer == nil {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if !ctx.Doer.IsAdmin && ctx.Doer.ID != review.ReviewerID {
- ctx.Error(http.StatusForbidden, "only admin and user itself can delete a review", nil)
+ ctx.APIError(http.StatusForbidden, nil)
return
}
if err := issues_model.DeleteReview(ctx, review); err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteReview", fmt.Errorf("can not delete ReviewID: %d", review.ID))
+ ctx.APIErrorInternal(fmt.Errorf("can not delete ReviewID: %d", review.ID))
return
}
@@ -309,9 +309,9 @@ func CreatePullReview(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.NotFound("GetPullRequestByIndex", err)
+ ctx.APIErrorNotFound("GetPullRequestByIndex", err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -323,7 +323,7 @@ func CreatePullReview(ctx *context.APIContext) {
}
if err := pr.Issue.LoadRepo(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "pr.Issue.LoadRepo", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -331,14 +331,14 @@ func CreatePullReview(ctx *context.APIContext) {
if opts.CommitID == "" {
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.Issue.Repo)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "git.OpenRepository", err)
+ ctx.APIErrorInternal(err)
return
}
defer closer.Close()
headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetRefCommitID", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -364,7 +364,7 @@ func CreatePullReview(ctx *context.APIContext) {
opts.CommitID,
nil,
); err != nil {
- ctx.Error(http.StatusInternalServerError, "CreateCodeComment", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -373,9 +373,9 @@ 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.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "SubmitReview", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -383,7 +383,7 @@ func CreatePullReview(ctx *context.APIContext) {
// convert response
apiReview, err := convert.ToPullReview(ctx, review, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "convertToPullReview", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, apiReview)
@@ -439,7 +439,7 @@ func SubmitPullReview(ctx *context.APIContext) {
}
if review.Type != issues_model.ReviewTypePending {
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("only a pending review can be submitted"))
+ ctx.APIError(http.StatusUnprocessableEntity, errors.New("only a pending review can be submitted"))
return
}
@@ -451,13 +451,13 @@ func SubmitPullReview(ctx *context.APIContext) {
// if review stay pending return
if reviewType == issues_model.ReviewTypePending {
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("review stay pending"))
+ ctx.APIError(http.StatusUnprocessableEntity, errors.New("review stay pending"))
return
}
headCommitID, err := ctx.Repo.GitRepo.GetRefCommitID(pr.GetGitRefName())
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GitRepo: GetRefCommitID", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -465,9 +465,9 @@ 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.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "SubmitReview", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -475,7 +475,7 @@ func SubmitPullReview(ctx *context.APIContext) {
// convert response
apiReview, err := convert.ToPullReview(ctx, review, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "convertToPullReview", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, apiReview)
@@ -484,7 +484,7 @@ func SubmitPullReview(ctx *context.APIContext) {
// preparePullReviewType return ReviewType and false or nil and true if an error happen
func preparePullReviewType(ctx *context.APIContext, pr *issues_model.PullRequest, event api.ReviewStateType, body string, hasComments bool) (issues_model.ReviewType, bool) {
if err := pr.LoadIssue(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
+ ctx.APIErrorInternal(err)
return -1, true
}
@@ -496,7 +496,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.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("approve your own pull is not allowed"))
+ ctx.APIError(http.StatusUnprocessableEntity, errors.New("approve your own pull is not allowed"))
return -1, true
}
reviewType = issues_model.ReviewTypeApprove
@@ -505,7 +505,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.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("reject your own pull is not allowed"))
+ ctx.APIError(http.StatusUnprocessableEntity, errors.New("reject your own pull is not allowed"))
return -1, true
}
reviewType = issues_model.ReviewTypeReject
@@ -515,7 +515,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.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("review event %s requires a body or a comment", event))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("review event %s requires a body or a comment", event))
return -1, true
}
default:
@@ -524,7 +524,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.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("review event %s requires a body", event))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("review event %s requires a body", event))
return -1, true
}
@@ -536,9 +536,9 @@ func prepareSingleReview(ctx *context.APIContext) (*issues_model.Review, *issues
pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
- ctx.NotFound("GetPullRequestByIndex", err)
+ ctx.APIErrorNotFound("GetPullRequestByIndex", err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
+ ctx.APIErrorInternal(err)
}
return nil, nil, true
}
@@ -546,27 +546,27 @@ func prepareSingleReview(ctx *context.APIContext) (*issues_model.Review, *issues
review, err := issues_model.GetReviewByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrReviewNotExist(err) {
- ctx.NotFound("GetReviewByID", err)
+ ctx.APIErrorNotFound("GetReviewByID", err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetReviewByID", err)
+ ctx.APIErrorInternal(err)
}
return nil, nil, true
}
// validate the review is for the given PR
if review.IssueID != pr.IssueID {
- ctx.NotFound("ReviewNotInPR")
+ ctx.APIErrorNotFound("ReviewNotInPR")
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.NotFound("GetReviewByID")
+ ctx.APIErrorNotFound("GetReviewByID")
return nil, nil, true
}
if err := review.LoadAttributes(ctx); err != nil && !user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusInternalServerError, "ReviewLoadAttributes", err)
+ ctx.APIErrorInternal(err)
return nil, nil, true
}
@@ -668,10 +668,10 @@ func parseReviewersByNames(ctx *context.APIContext, reviewerNames, teamReviewerN
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.NotFound("UserNotExist", fmt.Sprintf("User '%s' not exist", r))
+ ctx.APIErrorNotFound("UserNotExist", fmt.Sprintf("User '%s' not exist", r))
return nil, nil
}
- ctx.Error(http.StatusInternalServerError, "GetUser", err)
+ ctx.APIErrorInternal(err)
return nil, nil
}
@@ -684,10 +684,10 @@ 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.NotFound("TeamNotExist", fmt.Sprintf("Team '%s' not exist", t))
+ ctx.APIErrorNotFound("TeamNotExist", fmt.Sprintf("Team '%s' not exist", t))
return nil, nil
}
- ctx.Error(http.StatusInternalServerError, "ReviewRequest", err)
+ ctx.APIErrorInternal(err)
return nil, nil
}
@@ -701,21 +701,21 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions
pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
- ctx.NotFound("GetPullRequestByIndex", err)
+ ctx.APIErrorNotFound("GetPullRequestByIndex", err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err := pr.Issue.LoadRepo(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "pr.Issue.LoadRepo", err)
+ ctx.APIErrorInternal(err)
return
}
permDoer, err := access_model.GetUserRepoPermission(ctx, pr.Issue.Repo, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -733,20 +733,20 @@ 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.Error(http.StatusForbidden, "", err)
+ ctx.APIError(http.StatusForbidden, err)
return
}
if issues_model.IsErrNotValidReviewRequest(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "ReviewRequest", err)
+ ctx.APIErrorInternal(err)
return
}
if comment != nil && isAdd {
if err = comment.LoadReview(ctx); err != nil {
- ctx.ServerError("ReviewRequest", err)
+ ctx.APIErrorInternal(err)
return
}
reviews = append(reviews, comment.Review)
@@ -758,20 +758,20 @@ 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.Error(http.StatusForbidden, "", err)
+ ctx.APIError(http.StatusForbidden, err)
return
}
if issues_model.IsErrNotValidReviewRequest(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.ServerError("TeamReviewRequest", err)
+ ctx.APIErrorInternal(err)
return
}
if comment != nil && isAdd {
if err = comment.LoadReview(ctx); err != nil {
- ctx.ServerError("ReviewRequest", err)
+ ctx.APIErrorInternal(err)
return
}
reviews = append(reviews, comment.Review)
@@ -782,7 +782,7 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions
if isAdd {
apiReviews, err := convert.ToPullReviewList(ctx, reviews, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "convertToPullReviewList", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusCreated, apiReviews)
@@ -884,7 +884,7 @@ func UnDismissPullReview(ctx *context.APIContext) {
func dismissReview(ctx *context.APIContext, msg string, isDismiss, dismissPriors bool) {
if !ctx.Repo.IsAdmin() {
- ctx.Error(http.StatusForbidden, "", "Must be repo admin")
+ ctx.APIError(http.StatusForbidden, "Must be repo admin")
return
}
review, _, isWrong := prepareSingleReview(ctx)
@@ -893,29 +893,29 @@ func dismissReview(ctx *context.APIContext, msg string, isDismiss, dismissPriors
}
if review.Type != issues_model.ReviewTypeApprove && review.Type != issues_model.ReviewTypeReject {
- ctx.Error(http.StatusForbidden, "", "not need to dismiss this review because it's type is not Approve or change request")
+ ctx.APIError(http.StatusForbidden, "not need to dismiss this review because it's type is not Approve or change request")
return
}
_, 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.Error(http.StatusForbidden, "", err)
+ ctx.APIError(http.StatusForbidden, err)
return
}
- ctx.Error(http.StatusInternalServerError, "pull_service.DismissReview", err)
+ ctx.APIErrorInternal(err)
return
}
if review, err = issues_model.GetReviewByID(ctx, review.ID); err != nil {
- ctx.Error(http.StatusInternalServerError, "GetReviewByID", err)
+ ctx.APIErrorInternal(err)
return
}
// convert response
apiReview, err := convert.ToPullReview(ctx, review, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "convertToPullReview", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, apiReview)
diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go
index 076f00f1d1..272b395dfb 100644
--- a/routers/api/v1/repo/release.go
+++ b/routers/api/v1/repo/release.go
@@ -4,6 +4,7 @@
package repo
import (
+ "errors"
"fmt"
"net/http"
@@ -53,16 +54,16 @@ func GetRelease(ctx *context.APIContext) {
id := ctx.PathParamInt64("id")
release, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
- ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
+ ctx.APIErrorInternal(err)
return
}
if err != nil && repo_model.IsErrReleaseNotExist(err) || release.IsTag {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if err := release.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release))
@@ -93,17 +94,17 @@ func GetLatestRelease(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
release, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID)
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
- ctx.Error(http.StatusInternalServerError, "GetLatestRelease", err)
+ ctx.APIErrorInternal(err)
return
}
if err != nil && repo_model.IsErrReleaseNotExist(err) ||
release.IsTag || release.RepoID != ctx.Repo.Repository.ID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if err := release.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release))
@@ -161,13 +162,13 @@ func ListReleases(ctx *context.APIContext) {
releases, err := db.Find[repo_model.Release](ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetReleasesByRepoID", err)
+ ctx.APIErrorInternal(err)
return
}
rels := make([]*api.Release, len(releases))
for i, release := range releases {
if err := release.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
rels[i] = convert.ToAPIRelease(ctx, ctx.Repo.Repository, release)
@@ -175,7 +176,7 @@ func ListReleases(ctx *context.APIContext) {
filteredCount, err := db.Count[repo_model.Release](ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -220,13 +221,13 @@ func CreateRelease(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.CreateReleaseOption)
if ctx.Repo.Repository.IsEmpty {
- ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty"))
+ ctx.APIError(http.StatusUnprocessableEntity, errors.New("repo is empty"))
return
}
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, form.TagName)
if err != nil {
if !repo_model.IsErrReleaseNotExist(err) {
- ctx.Error(http.StatusInternalServerError, "GetRelease", err)
+ ctx.APIErrorInternal(err)
return
}
// If target is not provided use default branch
@@ -246,21 +247,23 @@ func CreateRelease(ctx *context.APIContext) {
IsTag: false,
Repo: ctx.Repo.Repository,
}
- if err := release_service.CreateRelease(ctx.Repo.GitRepo, rel, nil, ""); err != nil {
+ // GitHub doesn't have "tag_message", GitLab has: https://docs.gitlab.com/api/releases/#create-a-release
+ // 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.Error(http.StatusConflict, "ReleaseAlreadyExist", err)
+ ctx.APIError(http.StatusConflict, err)
} else if release_service.IsErrProtectedTagName(err) {
- ctx.Error(http.StatusUnprocessableEntity, "ProtectedTagName", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else if git.IsErrNotExist(err) {
- ctx.Error(http.StatusNotFound, "ErrNotExist", fmt.Errorf("target \"%v\" not found: %w", rel.Target, err))
+ ctx.APIError(http.StatusNotFound, fmt.Errorf("target \"%v\" not found: %w", rel.Target, err))
} else {
- ctx.Error(http.StatusInternalServerError, "CreateRelease", err)
+ ctx.APIErrorInternal(err)
}
return
}
} else {
if !rel.IsTag {
- ctx.Error(http.StatusConflict, "GetRelease", "Release is has no Tag")
+ ctx.APIError(http.StatusConflict, "Release is has no Tag")
return
}
@@ -275,7 +278,7 @@ func CreateRelease(ctx *context.APIContext) {
rel.Target = form.Target
if err = release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, nil, nil, nil); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateRelease", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -322,11 +325,11 @@ func EditRelease(ctx *context.APIContext) {
id := ctx.PathParamInt64("id")
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
- ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
+ ctx.APIErrorInternal(err)
return
}
if err != nil && repo_model.IsErrReleaseNotExist(err) || rel.IsTag {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -349,18 +352,18 @@ func EditRelease(ctx *context.APIContext) {
rel.IsPrerelease = *form.IsPrerelease
}
if err := release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, nil, nil, nil); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateRelease", err)
+ ctx.APIErrorInternal(err)
return
}
// reload data from database
rel, err = repo_model.GetReleaseByID(ctx, id)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
+ ctx.APIErrorInternal(err)
return
}
if err := rel.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, rel))
@@ -399,19 +402,19 @@ func DeleteRelease(ctx *context.APIContext) {
id := ctx.PathParamInt64("id")
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
- ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
+ ctx.APIErrorInternal(err)
return
}
if err != nil && repo_model.IsErrReleaseNotExist(err) || rel.IsTag {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if err := release_service.DeleteReleaseByID(ctx, ctx.Repo.Repository, rel, ctx.Doer, false); err != nil {
if release_service.IsErrProtectedTagName(err) {
- ctx.Error(http.StatusUnprocessableEntity, "delTag", "user not allowed to delete protected tag")
+ ctx.APIError(http.StatusUnprocessableEntity, "user not allowed to delete protected tag")
return
}
- ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
diff --git a/routers/api/v1/repo/release_attachment.go b/routers/api/v1/repo/release_attachment.go
index 54ca1fc843..defde81a1d 100644
--- a/routers/api/v1/repo/release_attachment.go
+++ b/routers/api/v1/repo/release_attachment.go
@@ -23,14 +23,14 @@ func checkReleaseMatchRepo(ctx *context.APIContext, releaseID int64) bool {
release, err := repo_model.GetReleaseByID(ctx, releaseID)
if err != nil {
if repo_model.IsErrReleaseNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return false
}
- ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
+ ctx.APIErrorInternal(err)
return false
}
if release.RepoID != ctx.Repo.Repository.ID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return false
}
return true
@@ -81,15 +81,15 @@ func GetReleaseAttachment(ctx *context.APIContext) {
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil {
if repo_model.IsErrAttachmentNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
- ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err)
+ ctx.APIErrorInternal(err)
return
}
if attach.ReleaseID != releaseID {
log.Info("User requested attachment is not in release, release_id %v, attachment_id: %v", releaseID, attachID)
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
// FIXME Should prove the existence of the given repo, but results in unnecessary database requests
@@ -130,18 +130,18 @@ func ListReleaseAttachments(ctx *context.APIContext) {
release, err := repo_model.GetReleaseByID(ctx, releaseID)
if err != nil {
if repo_model.IsErrReleaseNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
- ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
+ ctx.APIErrorInternal(err)
return
}
if release.RepoID != ctx.Repo.Repository.ID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if err := release.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release).Attachments)
@@ -194,7 +194,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
// Check if attachments are enabled
if !setting.Attachment.Enabled {
- ctx.NotFound("Attachment is not enabled")
+ ctx.APIErrorNotFound("Attachment is not enabled")
return
}
@@ -212,7 +212,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
if strings.HasPrefix(strings.ToLower(ctx.Req.Header.Get("Content-Type")), "multipart/form-data") {
file, header, err := ctx.Req.FormFile("attachment")
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetFile", err)
+ ctx.APIErrorInternal(err)
return
}
defer file.Close()
@@ -229,7 +229,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
}
if filename == "" {
- ctx.Error(http.StatusBadRequest, "CreateReleaseAttachment", "Could not determine name of attachment.")
+ ctx.APIError(http.StatusBadRequest, "Could not determine name of attachment.")
return
}
@@ -242,10 +242,10 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
})
if err != nil {
if upload.IsErrFileTypeForbidden(err) {
- ctx.Error(http.StatusBadRequest, "DetectContentType", err)
+ ctx.APIError(http.StatusBadRequest, err)
return
}
- ctx.Error(http.StatusInternalServerError, "NewAttachment", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -308,15 +308,15 @@ func EditReleaseAttachment(ctx *context.APIContext) {
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil {
if repo_model.IsErrAttachmentNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
- ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err)
+ ctx.APIErrorInternal(err)
return
}
if attach.ReleaseID != releaseID {
log.Info("User requested attachment is not in release, release_id %v, attachment_id: %v", releaseID, attachID)
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
// FIXME Should prove the existence of the given repo, but results in unnecessary database requests
@@ -326,10 +326,10 @@ func EditReleaseAttachment(ctx *context.APIContext) {
if err := attachment_service.UpdateAttachment(ctx, setting.Repository.Release.AllowedTypes, attach); err != nil {
if upload.IsErrFileTypeForbidden(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
@@ -381,21 +381,21 @@ func DeleteReleaseAttachment(ctx *context.APIContext) {
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil {
if repo_model.IsErrAttachmentNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
- ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err)
+ ctx.APIErrorInternal(err)
return
}
if attach.ReleaseID != releaseID {
log.Info("User requested attachment is not in release, release_id %v, attachment_id: %v", releaseID, attachID)
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
// FIXME Should prove the existence of the given repo, but results in unnecessary database requests
if err := repo_model.DeleteAttachment(ctx, attach, true); err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteAttachment", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
diff --git a/routers/api/v1/repo/release_tags.go b/routers/api/v1/repo/release_tags.go
index 7380c5231c..b5e7d83b2a 100644
--- a/routers/api/v1/repo/release_tags.go
+++ b/routers/api/v1/repo/release_tags.go
@@ -46,20 +46,20 @@ func GetReleaseByTag(ctx *context.APIContext) {
release, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tag)
if err != nil {
if repo_model.IsErrReleaseNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
- ctx.Error(http.StatusInternalServerError, "GetRelease", err)
+ ctx.APIErrorInternal(err)
return
}
if release.IsTag {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if err = release.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release))
@@ -99,24 +99,24 @@ func DeleteReleaseByTag(ctx *context.APIContext) {
release, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tag)
if err != nil {
if repo_model.IsErrReleaseNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
- ctx.Error(http.StatusInternalServerError, "GetRelease", err)
+ ctx.APIErrorInternal(err)
return
}
if release.IsTag {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
if err = release_service.DeleteReleaseByID(ctx, ctx.Repo.Repository, release, ctx.Doer, false); err != nil {
if release_service.IsErrProtectedTagName(err) {
- ctx.Error(http.StatusUnprocessableEntity, "delTag", "user not allowed to delete protected tag")
+ ctx.APIError(http.StatusUnprocessableEntity, "user not allowed to delete protected tag")
return
}
- ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index ce09e7fc0f..8acc912796 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -5,6 +5,7 @@
package repo
import (
+ "errors"
"fmt"
"net/http"
"slices"
@@ -12,7 +13,6 @@ import (
"strings"
"time"
- actions_model "code.gitea.io/gitea/models/actions"
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
@@ -134,7 +134,7 @@ func Search(ctx *context.APIContext) {
private = false
}
- opts := &repo_model.SearchRepoOptions{
+ opts := repo_model.SearchRepoOptions{
ListOptions: utils.GetListOptions(ctx),
Actor: ctx.Doer,
Keyword: ctx.FormTrim("q"),
@@ -171,7 +171,7 @@ func Search(ctx *context.APIContext) {
opts.Collaborate = optional.Some(true)
case "":
default:
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid search mode: \"%s\"", mode))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid search mode: \"%s\"", mode))
return
}
@@ -193,11 +193,11 @@ func Search(ctx *context.APIContext) {
if orderBy, ok := searchModeMap[sortMode]; ok {
opts.OrderBy = orderBy
} else {
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid sort mode: \"%s\"", sortMode))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort mode: \"%s\"", sortMode))
return
}
} else {
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid sort order: \"%s\"", sortOrder))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort order: \"%s\"", sortOrder))
return
}
}
@@ -245,7 +245,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.Error(http.StatusBadRequest, "", fmt.Errorf("readme template does not exist, available templates: %v", repo_module.Readmes))
+ ctx.APIError(http.StatusBadRequest, fmt.Errorf("readme template does not exist, available templates: %v", repo_module.Readmes))
return
}
@@ -265,13 +265,13 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
})
if err != nil {
if repo_model.IsErrRepoAlreadyExist(err) {
- ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
+ ctx.APIError(http.StatusConflict, "The repository with the same name already exists.")
} else if db.IsErrNameReserved(err) ||
db.IsErrNamePatternNotAllowed(err) ||
label.IsErrTemplateLoad(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "CreateRepository", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -279,7 +279,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
// reload repo from db to get a real state after creation
repo, err = repo_model.GetRepositoryByID(ctx, repo.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetRepositoryByID", err)
+ ctx.APIErrorInternal(err)
}
ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}))
@@ -311,7 +311,7 @@ func Create(ctx *context.APIContext) {
opt := web.GetForm(ctx).(*api.CreateRepoOption)
if ctx.Doer.IsOrganization() {
// Shouldn't reach this condition, but just in case.
- ctx.Error(http.StatusUnprocessableEntity, "", "not allowed creating repository for organization")
+ ctx.APIError(http.StatusUnprocessableEntity, "not allowed creating repository for organization")
return
}
CreateUserRepo(ctx, ctx.Doer, *opt)
@@ -355,12 +355,12 @@ func Generate(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.GenerateRepoOption)
if !ctx.Repo.Repository.IsTemplate {
- ctx.Error(http.StatusUnprocessableEntity, "", "this is not a template repo")
+ ctx.APIError(http.StatusUnprocessableEntity, "this is not a template repo")
return
}
if ctx.Doer.IsOrganization() {
- ctx.Error(http.StatusUnprocessableEntity, "", "not allowed creating repository for organization")
+ ctx.APIError(http.StatusUnprocessableEntity, "not allowed creating repository for organization")
return
}
@@ -379,7 +379,7 @@ func Generate(ctx *context.APIContext) {
}
if !opts.IsValid() {
- ctx.Error(http.StatusUnprocessableEntity, "", "must select at least one template item")
+ ctx.APIError(http.StatusUnprocessableEntity, "must select at least one template item")
return
}
@@ -395,22 +395,22 @@ func Generate(ctx *context.APIContext) {
return
}
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
return
}
if !ctx.Doer.IsAdmin && !ctxUser.IsOrganization() {
- ctx.Error(http.StatusForbidden, "", "Only admin can generate repository for other user.")
+ ctx.APIError(http.StatusForbidden, "Only admin can generate repository for other user.")
return
}
if !ctx.Doer.IsAdmin {
canCreate, err := organization.OrgFromUser(ctxUser).CanCreateOrgRepo(ctx, ctx.Doer.ID)
if err != nil {
- ctx.ServerError("CanCreateOrgRepo", err)
+ ctx.APIErrorInternal(err)
return
} else if !canCreate {
- ctx.Error(http.StatusForbidden, "", "Given user is not allowed to create repository in organization.")
+ ctx.APIError(http.StatusForbidden, "Given user is not allowed to create repository in organization.")
return
}
}
@@ -419,12 +419,12 @@ func Generate(ctx *context.APIContext) {
repo, err := repo_service.GenerateRepository(ctx, ctx.Doer, ctxUser, ctx.Repo.Repository, opts)
if err != nil {
if repo_model.IsErrRepoAlreadyExist(err) {
- ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
+ ctx.APIError(http.StatusConflict, "The repository with the same name already exists.")
} else if db.IsErrNameReserved(err) ||
db.IsErrNamePatternNotAllowed(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "CreateRepository", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -498,25 +498,25 @@ func CreateOrgRepo(ctx *context.APIContext) {
org, err := organization.GetOrgByName(ctx, ctx.PathParam("org"))
if err != nil {
if organization.IsErrOrgNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
if !organization.HasOrgOrUserVisible(ctx, org.AsUser(), ctx.Doer) {
- ctx.NotFound("HasOrgOrUserVisible", nil)
+ ctx.APIErrorNotFound("HasOrgOrUserVisible", nil)
return
}
if !ctx.Doer.IsAdmin {
canCreate, err := org.CanCreateOrgRepo(ctx, ctx.Doer.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "CanCreateOrgRepo", err)
+ ctx.APIErrorInternal(err)
return
} else if !canCreate {
- ctx.Error(http.StatusForbidden, "", "Given user is not allowed to create repository in organization.")
+ ctx.APIError(http.StatusForbidden, "Given user is not allowed to create repository in organization.")
return
}
}
@@ -548,7 +548,7 @@ func Get(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if err := ctx.Repo.Repository.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "Repository.LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -578,19 +578,19 @@ func GetByID(ctx *context.APIContext) {
repo, err := repo_model.GetRepositoryByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if repo_model.IsErrRepoNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetRepositoryByID", err)
+ ctx.APIErrorInternal(err)
}
return
}
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
+ ctx.APIErrorInternal(err)
return
} else if !permission.HasAnyUnitAccess() {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, repo, permission))
@@ -653,7 +653,7 @@ func Edit(ctx *context.APIContext) {
repo, err := repo_model.GetRepositoryByID(ctx, ctx.Repo.Repository.ID)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -673,13 +673,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.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is already taken [name: %s]", newRepoName), err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
case db.IsErrNameReserved(err):
- ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is reserved [name: %s]", newRepoName), err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
case db.IsErrNamePatternNotAllowed(err):
- ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name's pattern is not allowed [name: %s, pattern: %s]", newRepoName, err.(db.ErrNamePatternNotAllowed).Pattern), err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
default:
- ctx.Error(http.StatusUnprocessableEntity, "ChangeRepositoryName", err)
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("ChangeRepositoryName: %w", err))
}
return err
}
@@ -703,7 +703,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
// Visibility of forked repository is forced sync with base repository.
if repo.IsFork {
if err := repo.GetBaseRepo(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "Unable to load base repository", err)
+ ctx.APIErrorInternal(err)
return err
}
*opts.Private = repo.BaseRepo.IsPrivate
@@ -712,8 +712,8 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
visibilityChanged = repo.IsPrivate != *opts.Private
// 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 := fmt.Errorf("cannot change private repository to public")
- ctx.Error(http.StatusUnprocessableEntity, "Force Private enabled", err)
+ err := errors.New("cannot change private repository to public")
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return err
}
@@ -728,17 +728,17 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
var err error
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, repo)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "Unable to OpenRepository", err)
+ ctx.APIErrorInternal(err)
return err
}
}
// Default branch only updated if changed and exist or the repository is empty
updateRepoLicense := false
- if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && (repo.IsEmpty || ctx.Repo.GitRepo.IsBranchExist(*opts.DefaultBranch)) {
+ if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && (repo.IsEmpty || gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, *opts.DefaultBranch)) {
if !repo.IsEmpty {
if err := gitrepo.SetDefaultBranch(ctx, ctx.Repo.Repository, *opts.DefaultBranch); err != nil {
- ctx.Error(http.StatusInternalServerError, "SetDefaultBranch", err)
+ ctx.APIErrorInternal(err)
return err
}
updateRepoLicense = true
@@ -747,7 +747,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
}
if err := repo_service.UpdateRepository(ctx, repo, visibilityChanged); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateRepository", err)
+ ctx.APIErrorInternal(err)
return err
}
@@ -755,7 +755,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
if err := repo_service.AddRepoToLicenseUpdaterQueue(&repo_service.LicenseUpdaterOptions{
RepoID: ctx.Repo.Repository.ID,
}); err != nil {
- ctx.Error(http.StatusInternalServerError, "AddRepoToLicenseUpdaterQueue", err)
+ ctx.APIErrorInternal(err)
return err
}
}
@@ -781,13 +781,13 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
if newHasIssues && opts.ExternalTracker != nil && !unit_model.TypeExternalTracker.UnitGlobalDisabled() {
// Check that values are valid
if !validation.IsValidExternalURL(opts.ExternalTracker.ExternalTrackerURL) {
- err := fmt.Errorf("External tracker URL not valid")
- ctx.Error(http.StatusUnprocessableEntity, "Invalid external tracker URL", err)
+ err := errors.New("External tracker URL not valid")
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return err
}
if len(opts.ExternalTracker.ExternalTrackerFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(opts.ExternalTracker.ExternalTrackerFormat) {
- err := fmt.Errorf("External tracker URL format not valid")
- ctx.Error(http.StatusUnprocessableEntity, "Invalid external tracker URL format", err)
+ err := errors.New("External tracker URL format not valid")
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return err
}
@@ -848,8 +848,8 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
if newHasWiki && opts.ExternalWiki != nil && !unit_model.TypeExternalWiki.UnitGlobalDisabled() {
// Check that values are valid
if !validation.IsValidExternalURL(opts.ExternalWiki.ExternalWikiURL) {
- err := fmt.Errorf("External wiki URL not valid")
- ctx.Error(http.StatusUnprocessableEntity, "", "Invalid external wiki URL")
+ err := errors.New("External wiki URL not valid")
+ ctx.APIError(http.StatusUnprocessableEntity, "Invalid external wiki URL")
return err
}
@@ -1024,7 +1024,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
if len(units)+len(deleteUnitTypes) > 0 {
if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err)
+ ctx.APIErrorInternal(err)
return err
}
}
@@ -1039,24 +1039,24 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e
// archive / un-archive
if opts.Archived != nil {
if repo.IsMirror {
- err := fmt.Errorf("repo is a mirror, cannot archive/un-archive")
- ctx.Error(http.StatusUnprocessableEntity, err.Error(), err)
+ err := errors.New("repo is a mirror, cannot archive/un-archive")
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return err
}
if *opts.Archived {
if err := repo_model.SetArchiveRepoState(ctx, repo, *opts.Archived); err != nil {
log.Error("Tried to archive a repo: %s", err)
- ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
+ ctx.APIErrorInternal(err)
return err
}
- if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil {
+ if err := actions_service.CleanRepoScheduleTasks(ctx, repo); err != nil {
log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
}
log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
} else {
if err := repo_model.SetArchiveRepoState(ctx, repo, *opts.Archived); err != nil {
log.Error("Tried to un-archive a repo: %s", err)
- ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
+ ctx.APIErrorInternal(err)
return err
}
if ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypeActions) {
@@ -1084,7 +1084,7 @@ func updateMirror(ctx *context.APIContext, opts api.EditRepoOption) error {
mirror, err := repo_model.GetMirrorByRepoID(ctx, repo.ID)
if err != nil {
log.Error("Failed to get mirror: %s", err)
- ctx.Error(http.StatusInternalServerError, "MirrorInterval", err)
+ ctx.APIErrorInternal(err)
return err
}
@@ -1094,14 +1094,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.Error(http.StatusUnprocessableEntity, "MirrorInterval", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
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.Error(http.StatusUnprocessableEntity, "MirrorInterval", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return err
}
@@ -1120,7 +1120,7 @@ func updateMirror(ctx *context.APIContext, opts api.EditRepoOption) error {
// 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.Error(http.StatusUnprocessableEntity, "MirrorInterval", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return err
}
@@ -1158,10 +1158,10 @@ func Delete(ctx *context.APIContext) {
canDelete, err := repo_module.CanUserDelete(ctx, repo, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "CanUserDelete", err)
+ ctx.APIErrorInternal(err)
return
} else if !canDelete {
- ctx.Error(http.StatusForbidden, "", "Given user is not owner of organization.")
+ ctx.APIError(http.StatusForbidden, "Given user is not owner of organization.")
return
}
@@ -1170,7 +1170,7 @@ func Delete(ctx *context.APIContext) {
}
if err := repo_service.DeleteRepository(ctx, ctx.Doer, repo, true); err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteRepository", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -1315,7 +1315,7 @@ func ListRepoActivityFeeds(ctx *context.APIContext) {
feeds, count, err := feed_service.GetFeeds(ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetFeeds", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.SetTotalCountHeader(count)
diff --git a/routers/api/v1/repo/repo_test.go b/routers/api/v1/repo/repo_test.go
index 8d6ca9e3b5..97233f85dc 100644
--- a/routers/api/v1/repo/repo_test.go
+++ b/routers/api/v1/repo/repo_test.go
@@ -58,7 +58,7 @@ func TestRepoEdit(t *testing.T) {
web.SetForm(ctx, &opts)
Edit(ctx)
- assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
+ assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus())
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{
ID: 1,
}, unittest.Cond("name = ? AND is_archived = 1", *opts.Name))
@@ -78,7 +78,7 @@ func TestRepoEditNameChange(t *testing.T) {
web.SetForm(ctx, &opts)
Edit(ctx)
- assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
+ assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus())
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{
ID: 1,
diff --git a/routers/api/v1/repo/star.go b/routers/api/v1/repo/star.go
index 99676de119..46218e0e28 100644
--- a/routers/api/v1/repo/star.go
+++ b/routers/api/v1/repo/star.go
@@ -44,10 +44,12 @@ func ListStargazers(ctx *context.APIContext) {
// "$ref": "#/responses/UserList"
// "404":
// "$ref": "#/responses/notFound"
+ // "403":
+ // "$ref": "#/responses/forbidden"
stargazers, err := repo_model.GetStargazers(ctx, ctx.Repo.Repository, utils.GetListOptions(ctx))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetStargazers", err)
+ ctx.APIErrorInternal(err)
return
}
users := make([]*api.User, len(stargazers))
diff --git a/routers/api/v1/repo/status.go b/routers/api/v1/repo/status.go
index 8c910a68f9..40007ea1e5 100644
--- a/routers/api/v1/repo/status.go
+++ b/routers/api/v1/repo/status.go
@@ -55,7 +55,7 @@ func NewCommitStatus(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.CreateStatusOption)
sha := ctx.PathParam("sha")
if len(sha) == 0 {
- ctx.Error(http.StatusBadRequest, "sha not given", nil)
+ ctx.APIError(http.StatusBadRequest, nil)
return
}
status := &git_model.CommitStatus{
@@ -65,7 +65,7 @@ func NewCommitStatus(ctx *context.APIContext) {
Context: form.Context,
}
if err := commitstatus_service.CreateCommitStatus(ctx, ctx.Repo.Repository, ctx.Doer, sha, status); err != nil {
- ctx.Error(http.StatusInternalServerError, "CreateCommitStatus", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -177,20 +177,14 @@ func GetCommitStatusesByRef(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- filter := utils.ResolveRefOrSha(ctx, ctx.PathParam("ref"))
+ refCommit := resolveRefCommit(ctx, ctx.PathParam("ref"), 7)
if ctx.Written() {
return
}
-
- getCommitStatuses(ctx, filter) // By default filter is maybe the raw SHA
+ getCommitStatuses(ctx, refCommit.CommitID)
}
-func getCommitStatuses(ctx *context.APIContext, sha string) {
- if len(sha) == 0 {
- ctx.Error(http.StatusBadRequest, "ref/sha not given", nil)
- return
- }
- sha = utils.MustConvertToSHA1(ctx.Base, ctx.Repo, sha)
+func getCommitStatuses(ctx *context.APIContext, commitID string) {
repo := ctx.Repo.Repository
listOptions := utils.GetListOptions(ctx)
@@ -198,12 +192,12 @@ func getCommitStatuses(ctx *context.APIContext, sha string) {
statuses, maxResults, err := db.FindAndCount[git_model.CommitStatus](ctx, &git_model.CommitStatusOptions{
ListOptions: listOptions,
RepoID: repo.ID,
- SHA: sha,
+ SHA: commitID,
SortType: ctx.FormTrim("sort"),
State: ctx.FormTrim("state"),
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetCommitStatuses", fmt.Errorf("GetCommitStatuses[%s, %s, %d]: %w", repo.FullName(), sha, ctx.FormInt("page"), err))
+ ctx.APIErrorInternal(fmt.Errorf("GetCommitStatuses[%s, %s, %d]: %w", repo.FullName(), commitID, ctx.FormInt("page"), err))
return
}
@@ -257,26 +251,31 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- sha := utils.ResolveRefOrSha(ctx, ctx.PathParam("ref"))
+ refCommit := resolveRefCommit(ctx, ctx.PathParam("ref"), 7)
if ctx.Written() {
return
}
repo := ctx.Repo.Repository
- statuses, count, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, utils.GetListOptions(ctx))
+ statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, refCommit.Commit.ID.String(), utils.GetListOptions(ctx))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetLatestCommitStatus", fmt.Errorf("GetLatestCommitStatus[%s, %s]: %w", repo.FullName(), sha, err))
+ ctx.APIErrorInternal(fmt.Errorf("GetLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err))
return
}
+ count, err := git_model.CountLatestCommitStatus(ctx, repo.ID, refCommit.Commit.ID.String())
+ if err != nil {
+ ctx.APIErrorInternal(fmt.Errorf("CountLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err))
+ return
+ }
+ ctx.SetTotalCountHeader(count)
+
if len(statuses) == 0 {
ctx.JSON(http.StatusOK, &api.CombinedStatus{})
return
}
combiStatus := convert.ToCombinedStatus(ctx, statuses, convert.ToRepo(ctx, repo, ctx.Repo.Permission))
-
- ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, combiStatus)
}
diff --git a/routers/api/v1/repo/subscriber.go b/routers/api/v1/repo/subscriber.go
index 8584182857..14f296a83d 100644
--- a/routers/api/v1/repo/subscriber.go
+++ b/routers/api/v1/repo/subscriber.go
@@ -47,7 +47,7 @@ func ListSubscribers(ctx *context.APIContext) {
subscribers, err := repo_model.GetRepoWatchers(ctx, ctx.Repo.Repository.ID, utils.GetListOptions(ctx))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetRepoWatchers", err)
+ ctx.APIErrorInternal(err)
return
}
users := make([]*api.User, len(subscribers))
diff --git a/routers/api/v1/repo/tag.go b/routers/api/v1/repo/tag.go
index 8447a8f1f2..9e77637282 100644
--- a/routers/api/v1/repo/tag.go
+++ b/routers/api/v1/repo/tag.go
@@ -57,7 +57,7 @@ func ListTags(ctx *context.APIContext) {
tags, total, err := ctx.Repo.GitRepo.GetTagInfos(listOpts.Page, listOpts.PageSize)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetTags", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -103,16 +103,16 @@ func GetAnnotatedTag(ctx *context.APIContext) {
sha := ctx.PathParam("sha")
if len(sha) == 0 {
- ctx.Error(http.StatusBadRequest, "", "SHA not provided")
+ ctx.APIError(http.StatusBadRequest, "SHA not provided")
return
}
if tag, err := ctx.Repo.GitRepo.GetAnnotatedTag(sha); err != nil {
- ctx.Error(http.StatusBadRequest, "GetAnnotatedTag", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else {
- commit, err := tag.Commit(ctx.Repo.GitRepo)
+ commit, err := ctx.Repo.GitRepo.GetTagCommit(tag.Name)
if err != nil {
- ctx.Error(http.StatusBadRequest, "GetAnnotatedTag", err)
+ ctx.APIError(http.StatusBadRequest, err)
}
ctx.JSON(http.StatusOK, convert.ToAnnotatedTag(ctx, ctx.Repo.Repository, tag, commit))
}
@@ -150,7 +150,7 @@ func GetTag(ctx *context.APIContext) {
tag, err := ctx.Repo.GitRepo.GetTag(tagName)
if err != nil {
- ctx.NotFound(tagName)
+ ctx.APIErrorNotFound("tag doesn't exist: " + tagName)
return
}
ctx.JSON(http.StatusOK, convert.ToTag(ctx.Repo.Repository, tag))
@@ -200,27 +200,27 @@ func CreateTag(ctx *context.APIContext) {
commit, err := ctx.Repo.GitRepo.GetCommit(form.Target)
if err != nil {
- ctx.Error(http.StatusNotFound, "target not found", fmt.Errorf("target not found: %w", err))
+ ctx.APIError(http.StatusNotFound, fmt.Errorf("target not found: %w", 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.Error(http.StatusConflict, "tag exist", err)
+ ctx.APIError(http.StatusConflict, err)
return
}
if release_service.IsErrProtectedTagName(err) {
- ctx.Error(http.StatusUnprocessableEntity, "CreateNewTag", "user not allowed to create protected tag")
+ ctx.APIError(http.StatusUnprocessableEntity, "user not allowed to create protected tag")
return
}
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
tag, err := ctx.Repo.GitRepo.GetTag(form.TagName)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusCreated, convert.ToTag(ctx.Repo.Repository, tag))
@@ -267,24 +267,24 @@ func DeleteTag(ctx *context.APIContext) {
tag, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tagName)
if err != nil {
if repo_model.IsErrReleaseNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
- ctx.Error(http.StatusInternalServerError, "GetRelease", err)
+ ctx.APIErrorInternal(err)
return
}
if !tag.IsTag {
- ctx.Error(http.StatusConflict, "IsTag", errors.New("a tag attached to a release cannot be deleted directly"))
+ ctx.APIError(http.StatusConflict, errors.New("a tag attached to a release cannot be deleted directly"))
return
}
if err = release_service.DeleteReleaseByID(ctx, ctx.Repo.Repository, tag, ctx.Doer, true); err != nil {
if release_service.IsErrProtectedTagName(err) {
- ctx.Error(http.StatusUnprocessableEntity, "delTag", "user not allowed to delete protected tag")
+ ctx.APIError(http.StatusUnprocessableEntity, "user not allowed to delete protected tag")
return
}
- ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -316,7 +316,7 @@ func ListTagProtection(ctx *context.APIContext) {
repo := ctx.Repo.Repository
pts, err := git_model.GetProtectedTags(ctx, repo.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetProtectedTags", err)
+ ctx.APIErrorInternal(err)
return
}
apiPts := make([]*api.TagProtection, len(pts))
@@ -360,12 +360,12 @@ func GetTagProtection(ctx *context.APIContext) {
id := ctx.PathParamInt64("id")
pt, err := git_model.GetProtectedTagByID(ctx, id)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
+ ctx.APIErrorInternal(err)
return
}
if pt == nil || repo.ID != pt.RepoID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -413,21 +413,21 @@ func CreateTagProtection(ctx *context.APIContext) {
namePattern := strings.TrimSpace(form.NamePattern)
if namePattern == "" {
- ctx.Error(http.StatusBadRequest, "name_pattern are empty", "name_pattern are empty")
+ ctx.APIError(http.StatusBadRequest, "name_pattern are empty")
return
}
if len(form.WhitelistUsernames) == 0 && len(form.WhitelistTeams) == 0 {
- ctx.Error(http.StatusBadRequest, "both whitelist_usernames and whitelist_teams are empty", "both whitelist_usernames and whitelist_teams are empty")
+ ctx.APIError(http.StatusBadRequest, "both whitelist_usernames and whitelist_teams are empty")
return
}
pt, err := git_model.GetProtectedTagByNamePattern(ctx, repo.ID, namePattern)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetProtectTagOfRepo", err)
+ ctx.APIErrorInternal(err)
return
} else if pt != nil {
- ctx.Error(http.StatusForbidden, "Create tag protection", "Tag protection already exist")
+ ctx.APIError(http.StatusForbidden, "Tag protection already exist")
return
}
@@ -435,10 +435,10 @@ func CreateTagProtection(ctx *context.APIContext) {
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.WhitelistUsernames, false)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -446,10 +446,10 @@ func CreateTagProtection(ctx *context.APIContext) {
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.WhitelistTeams, false)
if err != nil {
if organization.IsErrTeamNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -461,18 +461,18 @@ func CreateTagProtection(ctx *context.APIContext) {
AllowlistTeamIDs: whitelistTeams,
}
if err := git_model.InsertProtectedTag(ctx, protectTag); err != nil {
- ctx.Error(http.StatusInternalServerError, "InsertProtectedTag", err)
+ ctx.APIErrorInternal(err)
return
}
pt, err = git_model.GetProtectedTagByID(ctx, protectTag.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
+ ctx.APIErrorInternal(err)
return
}
if pt == nil || pt.RepoID != repo.ID {
- ctx.Error(http.StatusInternalServerError, "New tag protection not found", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -524,12 +524,12 @@ func EditTagProtection(ctx *context.APIContext) {
id := ctx.PathParamInt64("id")
pt, err := git_model.GetProtectedTagByID(ctx, id)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
+ ctx.APIErrorInternal(err)
return
}
if pt == nil || pt.RepoID != repo.ID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -543,10 +543,10 @@ func EditTagProtection(ctx *context.APIContext) {
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.WhitelistTeams, false)
if err != nil {
if organization.IsErrTeamNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -557,10 +557,10 @@ func EditTagProtection(ctx *context.APIContext) {
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.WhitelistUsernames, false)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
+ ctx.APIErrorInternal(err)
return
}
pt.AllowlistUserIDs = whitelistUsers
@@ -568,18 +568,18 @@ func EditTagProtection(ctx *context.APIContext) {
err = git_model.UpdateProtectedTag(ctx, pt)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateProtectedTag", err)
+ ctx.APIErrorInternal(err)
return
}
pt, err = git_model.GetProtectedTagByID(ctx, id)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
+ ctx.APIErrorInternal(err)
return
}
if pt == nil || pt.RepoID != repo.ID {
- ctx.Error(http.StatusInternalServerError, "New tag protection not found", "New tag protection not found")
+ ctx.APIErrorInternal(errors.New("new tag protection not found"))
return
}
@@ -619,18 +619,18 @@ func DeleteTagProtection(ctx *context.APIContext) {
id := ctx.PathParamInt64("id")
pt, err := git_model.GetProtectedTagByID(ctx, id)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
+ ctx.APIErrorInternal(err)
return
}
if pt == nil || pt.RepoID != repo.ID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
err = git_model.DeleteProtectedTag(ctx, pt)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteProtectedTag", err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/repo/teams.go b/routers/api/v1/repo/teams.go
index e5a2d5c320..739a9e3892 100644
--- a/routers/api/v1/repo/teams.go
+++ b/routers/api/v1/repo/teams.go
@@ -38,19 +38,19 @@ func ListTeams(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if !ctx.Repo.Owner.IsOrganization() {
- ctx.Error(http.StatusMethodNotAllowed, "noOrg", "repo is not owned by an organization")
+ ctx.APIError(http.StatusMethodNotAllowed, "repo is not owned by an organization")
return
}
teams, err := organization.GetRepoTeams(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
apiTeams, err := convert.ToTeams(ctx, teams, false)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -89,7 +89,7 @@ func IsTeam(ctx *context.APIContext) {
// "$ref": "#/responses/error"
if !ctx.Repo.Owner.IsOrganization() {
- ctx.Error(http.StatusMethodNotAllowed, "noOrg", "repo is not owned by an organization")
+ ctx.APIError(http.StatusMethodNotAllowed, "repo is not owned by an organization")
return
}
@@ -101,14 +101,14 @@ func IsTeam(ctx *context.APIContext) {
if repo_service.HasRepository(ctx, team, ctx.Repo.Repository.ID) {
apiTeam, err := convert.ToTeam(ctx, team)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, apiTeam)
return
}
- ctx.NotFound()
+ ctx.APIErrorNotFound()
}
// AddTeam add a team to a repository
@@ -185,10 +185,10 @@ func DeleteTeam(ctx *context.APIContext) {
func changeRepoTeam(ctx *context.APIContext, add bool) {
if !ctx.Repo.Owner.IsOrganization() {
- ctx.Error(http.StatusMethodNotAllowed, "noOrg", "repo is not owned by an organization")
+ ctx.APIError(http.StatusMethodNotAllowed, "repo is not owned by an organization")
}
if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() {
- ctx.Error(http.StatusForbidden, "noAdmin", "user is nor repo admin nor owner")
+ ctx.APIError(http.StatusForbidden, "user is nor repo admin nor owner")
return
}
@@ -201,19 +201,19 @@ func changeRepoTeam(ctx *context.APIContext, add bool) {
var err error
if add {
if repoHasTeam {
- ctx.Error(http.StatusUnprocessableEntity, "alreadyAdded", fmt.Errorf("team '%s' is already added to repo", team.Name))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("team '%s' is already added to repo", team.Name))
return
}
err = repo_service.TeamAddRepository(ctx, team, ctx.Repo.Repository)
} else {
if !repoHasTeam {
- ctx.Error(http.StatusUnprocessableEntity, "notAdded", fmt.Errorf("team '%s' was not added to repo", team.Name))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("team '%s' was not added to repo", team.Name))
return
}
err = repo_service.RemoveRepositoryFromTeam(ctx, team, ctx.Repo.Repository.ID)
}
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -224,10 +224,10 @@ 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.Error(http.StatusNotFound, "TeamNotExit", err)
+ ctx.APIError(http.StatusNotFound, err)
return nil
}
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return nil
}
return team
diff --git a/routers/api/v1/repo/topic.go b/routers/api/v1/repo/topic.go
index a1a15e7f46..9c4c22e039 100644
--- a/routers/api/v1/repo/topic.go
+++ b/routers/api/v1/repo/topic.go
@@ -56,7 +56,7 @@ func ListTopics(ctx *context.APIContext) {
topics, total, err := db.FindAndCount[repo_model.Topic](ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -124,7 +124,7 @@ func UpdateTopics(ctx *context.APIContext) {
err := repo_model.SaveTopics(ctx, ctx.Repo.Repository.ID, validTopics...)
if err != nil {
log.Error("SaveTopics failed: %v", err)
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -178,7 +178,7 @@ func AddTopic(ctx *context.APIContext) {
})
if err != nil {
log.Error("CountTopics failed: %v", err)
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
if count >= 25 {
@@ -191,7 +191,7 @@ func AddTopic(ctx *context.APIContext) {
_, err = repo_model.AddTopic(ctx, ctx.Repo.Repository.ID, topicName)
if err != nil {
log.Error("AddTopic failed: %v", err)
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -242,12 +242,12 @@ func DeleteTopic(ctx *context.APIContext) {
topic, err := repo_model.DeleteTopic(ctx, ctx.Repo.Repository.ID, topicName)
if err != nil {
log.Error("DeleteTopic failed: %v", err)
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
if topic == nil {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -290,7 +290,7 @@ func TopicSearch(ctx *context.APIContext) {
topics, total, err := db.FindAndCount[repo_model.Topic](ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go
index b2090cac41..cbf3d10c39 100644
--- a/routers/api/v1/repo/transfer.go
+++ b/routers/api/v1/repo/transfer.go
@@ -15,6 +15,7 @@ import (
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"
@@ -60,17 +61,17 @@ func Transfer(ctx *context.APIContext) {
newOwner, err := user_model.GetUserByName(ctx, opts.NewOwner)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.Error(http.StatusNotFound, "", "The new owner does not exist or cannot be found")
+ ctx.APIError(http.StatusNotFound, "The new owner does not exist or cannot be found")
return
}
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
if newOwner.Type == user_model.UserTypeOrganization {
if !ctx.Doer.IsAdmin && newOwner.Visibility == api.VisibleTypePrivate && !organization.OrgFromUser(newOwner).HasMemberWithUserID(ctx, ctx.Doer.ID) {
// The user shouldn't know about this organization
- ctx.Error(http.StatusNotFound, "", "The new owner does not exist or cannot be found")
+ ctx.APIError(http.StatusNotFound, "The new owner does not exist or cannot be found")
return
}
}
@@ -78,7 +79,7 @@ func Transfer(ctx *context.APIContext) {
var teams []*organization.Team
if opts.TeamIDs != nil {
if !newOwner.IsOrganization() {
- ctx.Error(http.StatusUnprocessableEntity, "repoTransfer", "Teams can only be added to organization-owned repositories")
+ ctx.APIError(http.StatusUnprocessableEntity, "Teams can only be added to organization-owned repositories")
return
}
@@ -86,12 +87,12 @@ func Transfer(ctx *context.APIContext) {
for _, tID := range *opts.TeamIDs {
team, err := organization.GetTeamByID(ctx, tID)
if err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "team", fmt.Errorf("team %d not found", tID))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("team %d not found", tID))
return
}
if team.OrgID != org.ID {
- ctx.Error(http.StatusForbidden, "team", fmt.Errorf("team %d belongs not to org %d", tID, org.ID))
+ ctx.APIError(http.StatusForbidden, fmt.Errorf("team %d belongs not to org %d", tID, org.ID))
return
}
@@ -107,20 +108,17 @@ func Transfer(ctx *context.APIContext) {
oldFullname := ctx.Repo.Repository.FullName()
if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, ctx.Repo.Repository, teams); err != nil {
- if repo_model.IsErrRepoTransferInProgress(err) {
- ctx.Error(http.StatusConflict, "StartRepositoryTransfer", err)
- return
- }
-
- if repo_model.IsErrRepoAlreadyExist(err) {
- ctx.Error(http.StatusUnprocessableEntity, "StartRepositoryTransfer", err)
- return
- }
-
- if errors.Is(err, user_model.ErrBlockedUser) {
- ctx.Error(http.StatusForbidden, "BlockedUser", err)
- } else {
- ctx.InternalServerError(err)
+ switch {
+ case repo_model.IsErrRepoTransferInProgress(err):
+ ctx.APIError(http.StatusConflict, err)
+ case repo_model.IsErrRepoAlreadyExist(err):
+ ctx.APIError(http.StatusUnprocessableEntity, err)
+ case repo_service.IsRepositoryLimitReached(err):
+ ctx.APIError(http.StatusForbidden, err)
+ case errors.Is(err, user_model.ErrBlockedUser):
+ ctx.APIError(http.StatusForbidden, err)
+ default:
+ ctx.APIErrorInternal(err)
}
return
}
@@ -161,12 +159,18 @@ func AcceptTransfer(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- err := acceptOrRejectRepoTransfer(ctx, true)
- if ctx.Written() {
- return
- }
+ err := repo_service.AcceptTransferOwnership(ctx, ctx.Repo.Repository, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "acceptOrRejectRepoTransfer", err)
+ switch {
+ case repo_model.IsErrNoPendingTransfer(err):
+ ctx.APIError(http.StatusNotFound, err)
+ case errors.Is(err, util.ErrPermissionDenied):
+ ctx.APIError(http.StatusForbidden, err)
+ case repo_service.IsRepositoryLimitReached(err):
+ ctx.APIError(http.StatusForbidden, err)
+ default:
+ ctx.APIErrorInternal(err)
+ }
return
}
@@ -199,40 +203,18 @@ func RejectTransfer(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- err := acceptOrRejectRepoTransfer(ctx, false)
- if ctx.Written() {
- return
- }
+ err := repo_service.RejectRepositoryTransfer(ctx, ctx.Repo.Repository, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "acceptOrRejectRepoTransfer", err)
+ switch {
+ case repo_model.IsErrNoPendingTransfer(err):
+ ctx.APIError(http.StatusNotFound, err)
+ case errors.Is(err, util.ErrPermissionDenied):
+ ctx.APIError(http.StatusForbidden, err)
+ default:
+ ctx.APIErrorInternal(err)
+ }
return
}
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, ctx.Repo.Repository, ctx.Repo.Permission))
}
-
-func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error {
- repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
- if err != nil {
- if repo_model.IsErrNoPendingTransfer(err) {
- ctx.NotFound()
- return nil
- }
- return err
- }
-
- if err := repoTransfer.LoadAttributes(ctx); err != nil {
- return err
- }
-
- if !repoTransfer.CanUserAcceptTransfer(ctx, ctx.Doer) {
- ctx.Error(http.StatusForbidden, "CanUserAcceptTransfer", nil)
- return fmt.Errorf("user does not have permissions to do this")
- }
-
- if accept {
- return repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams)
- }
-
- return repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository)
-}
diff --git a/routers/api/v1/repo/tree.go b/routers/api/v1/repo/tree.go
index 768e5d41c1..dfd69600fb 100644
--- a/routers/api/v1/repo/tree.go
+++ b/routers/api/v1/repo/tree.go
@@ -58,11 +58,11 @@ func GetTree(ctx *context.APIContext) {
sha := ctx.PathParam("sha")
if len(sha) == 0 {
- ctx.Error(http.StatusBadRequest, "", "sha not provided")
+ ctx.APIError(http.StatusBadRequest, "sha not provided")
return
}
if tree, err := files_service.GetTreeBySHA(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, sha, ctx.FormInt("page"), ctx.FormInt("per_page"), ctx.FormBool("recursive")); err != nil {
- ctx.Error(http.StatusBadRequest, "", err.Error())
+ ctx.APIError(http.StatusBadRequest, err.Error())
} else {
ctx.SetTotalCountHeader(int64(tree.TotalCount))
ctx.JSON(http.StatusOK, tree)
diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go
index 352d8f48fc..8e24ffa465 100644
--- a/routers/api/v1/repo/wiki.go
+++ b/routers/api/v1/repo/wiki.go
@@ -59,7 +59,7 @@ func NewWikiPage(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.CreateWikiPageOptions)
if util.IsEmptyString(form.Title) {
- ctx.Error(http.StatusBadRequest, "emptyTitle", nil)
+ ctx.APIError(http.StatusBadRequest, nil)
return
}
@@ -71,18 +71,18 @@ func NewWikiPage(ctx *context.APIContext) {
content, err := base64.StdEncoding.DecodeString(form.ContentBase64)
if err != nil {
- ctx.Error(http.StatusBadRequest, "invalid base64 encoding of content", err)
+ ctx.APIError(http.StatusBadRequest, err)
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.Error(http.StatusBadRequest, "IsErrWikiReservedName", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else if repo_model.IsErrWikiAlreadyExist(err) {
- ctx.Error(http.StatusBadRequest, "IsErrWikiAlreadyExists", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else {
- ctx.Error(http.StatusInternalServerError, "AddWikiPage", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -149,13 +149,13 @@ func EditWikiPage(ctx *context.APIContext) {
content, err := base64.StdEncoding.DecodeString(form.ContentBase64)
if err != nil {
- ctx.Error(http.StatusBadRequest, "invalid base64 encoding of content", err)
+ ctx.APIError(http.StatusBadRequest, err)
return
}
form.ContentBase64 = string(content)
if err := wiki_service.EditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, oldWikiName, newWikiName, form.ContentBase64, form.Message); err != nil {
- ctx.Error(http.StatusInternalServerError, "EditWikiPage", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -193,12 +193,12 @@ func getWikiPage(ctx *context.APIContext, wikiName wiki_service.WebPath) *api.Wi
}
// get commit count - wiki revisions
- commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
+ commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
// Get last change information.
lastCommit, err := wikiRepo.GetCommitByPath(pageFilename)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetCommitByPath", err)
+ ctx.APIErrorInternal(err)
return nil
}
@@ -246,10 +246,10 @@ func DeleteWikiPage(ctx *context.APIContext) {
if err := wiki_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName); err != nil {
if err.Error() == "file does not exist" {
- ctx.NotFound(err)
+ ctx.APIErrorNotFound(err)
return
}
- ctx.Error(http.StatusInternalServerError, "DeleteWikiPage", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -298,10 +298,7 @@ func ListWikiPages(ctx *context.APIContext) {
return
}
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
limit := ctx.FormInt("limit")
if limit <= 1 {
limit = setting.API.DefaultPagingNum
@@ -312,7 +309,7 @@ func ListWikiPages(ctx *context.APIContext) {
entries, err := commit.ListEntries()
if err != nil {
- ctx.ServerError("ListEntries", err)
+ ctx.APIErrorInternal(err)
return
}
pages := make([]*api.WikiPageMetaData, 0, len(entries))
@@ -322,7 +319,7 @@ func ListWikiPages(ctx *context.APIContext) {
}
c, err := wikiRepo.GetCommitByPath(entry.Name())
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetCommit", err)
+ ctx.APIErrorInternal(err)
return
}
wikiName, err := wiki_service.GitPathToWebPath(entry.Name())
@@ -330,7 +327,7 @@ func ListWikiPages(ctx *context.APIContext) {
if repo_model.IsErrWikiInvalidFileName(err) {
continue
}
- ctx.Error(http.StatusInternalServerError, "WikiFilenameToName", err)
+ ctx.APIErrorInternal(err)
return
}
pages = append(pages, wiki_service.ToWikiPageMetaData(wikiName, c, ctx.Repo.Repository))
@@ -432,22 +429,19 @@ func ListPageRevisions(ctx *context.APIContext) {
}
// get commit count - wiki revisions
- commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
+ commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
// get Commit Count
commitsHistory, err := wikiRepo.CommitsByFileAndRange(
git.CommitsByFileAndRangeOptions{
- Revision: "master",
+ Revision: ctx.Repo.Repository.DefaultWikiBranch,
File: pageFilename,
Page: page,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "CommitsByFileAndRange", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -476,22 +470,22 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error)
// findWikiRepoCommit opens the wiki repo and returns the latest commit, writing to context on error.
// The caller is responsible for closing the returned repo again
func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit) {
- wikiRepo, err := gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository)
+ wikiRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo())
if err != nil {
if git.IsErrNotExist(err) || err.Error() == "no such file or directory" {
- ctx.NotFound(err)
+ ctx.APIErrorNotFound(err)
} else {
- ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
+ ctx.APIErrorInternal(err)
}
return nil, nil
}
- commit, err := wikiRepo.GetBranchCommit("master")
+ commit, err := wikiRepo.GetBranchCommit(ctx.Repo.Repository.DefaultWikiBranch)
if err != nil {
if git.IsErrNotExist(err) {
- ctx.NotFound(err)
+ ctx.APIErrorNotFound(err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err)
+ ctx.APIErrorInternal(err)
}
return wikiRepo, nil
}
@@ -505,9 +499,9 @@ func wikiContentsByEntry(ctx *context.APIContext, entry *git.TreeEntry) string {
if blob.Size() > setting.API.DefaultMaxBlobSize {
return ""
}
- content, err := blob.GetBlobContentBase64()
+ content, err := blob.GetBlobContentBase64(nil)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetBlobContentBase64", err)
+ ctx.APIErrorInternal(err)
return ""
}
return content
@@ -521,10 +515,10 @@ func wikiContentsByName(ctx *context.APIContext, commit *git.Commit, wikiName wi
if err != nil {
if git.IsErrNotExist(err) {
if !isSidebarOrFooter {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
}
} else {
- ctx.ServerError("findEntryForFile", err)
+ ctx.APIErrorInternal(err)
}
return "", ""
}
diff --git a/routers/api/v1/settings/settings.go b/routers/api/v1/settings/settings.go
index 0ee81b96d5..94fbadeab0 100644
--- a/routers/api/v1/settings/settings.go
+++ b/routers/api/v1/settings/settings.go
@@ -43,6 +43,7 @@ func GetGeneralAPISettings(ctx *context.APIContext) {
DefaultPagingNum: setting.API.DefaultPagingNum,
DefaultGitTreesPerPage: setting.API.DefaultGitTreesPerPage,
DefaultMaxBlobSize: setting.API.DefaultMaxBlobSize,
+ DefaultMaxResponseSize: setting.API.DefaultMaxResponseSize,
})
}
diff --git a/routers/api/v1/shared/action.go b/routers/api/v1/shared/action.go
new file mode 100644
index 0000000000..c97e9419fd
--- /dev/null
+++ b/routers/api/v1/shared/action.go
@@ -0,0 +1,187 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package shared
+
+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"
+)
+
+// 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
+// ownerID != 0 and repoID == 0 means all jobs for the given user/org
+// 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
+// Access rights are checked at the API route level
+func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64) {
+ if ownerID != 0 && repoID != 0 {
+ setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
+ }
+ opts := actions_model.FindRunJobOptions{
+ OwnerID: ownerID,
+ RepoID: repoID,
+ RunID: runID,
+ ListOptions: utils.GetListOptions(ctx),
+ }
+ for _, status := range ctx.FormStrings("status") {
+ values, err := convertToInternal(status)
+ if err != nil {
+ ctx.APIError(http.StatusBadRequest, fmt.Errorf("Invalid status %s", status))
+ return
+ }
+ opts.Statuses = append(opts.Statuses, values...)
+ }
+
+ jobs, total, err := db.FindAndCount[actions_model.ActionRunJob](ctx, opts)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+
+ res := new(api.ActionWorkflowJobsResponse)
+ res.TotalCount = total
+
+ res.Entries = make([]*api.ActionWorkflowJob, len(jobs))
+
+ isRepoLevel := repoID != 0 && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == repoID
+ for i := range jobs {
+ var repository *repo_model.Repository
+ if isRepoLevel {
+ repository = ctx.Repo.Repository
+ } else {
+ repository, err = repo_model.GetRepositoryByID(ctx, jobs[i].RepoID)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ }
+
+ convertedWorkflowJob, err := convert.ToActionWorkflowJob(ctx, repository, nil, jobs[i])
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ res.Entries[i] = convertedWorkflowJob
+ }
+
+ ctx.JSON(http.StatusOK, &res)
+}
+
+func convertToInternal(s string) ([]actions_model.Status, error) {
+ switch s {
+ case "pending", "waiting", "requested", "action_required":
+ return []actions_model.Status{actions_model.StatusBlocked}, nil
+ case "queued":
+ return []actions_model.Status{actions_model.StatusWaiting}, nil
+ case "in_progress":
+ return []actions_model.Status{actions_model.StatusRunning}, nil
+ case "completed":
+ return []actions_model.Status{
+ actions_model.StatusSuccess,
+ actions_model.StatusFailure,
+ actions_model.StatusSkipped,
+ actions_model.StatusCancelled,
+ }, nil
+ case "failure":
+ return []actions_model.Status{actions_model.StatusFailure}, nil
+ case "success":
+ return []actions_model.Status{actions_model.StatusSuccess}, nil
+ case "skipped", "neutral":
+ return []actions_model.Status{actions_model.StatusSkipped}, nil
+ case "cancelled", "timed_out":
+ return []actions_model.Status{actions_model.StatusCancelled}, nil
+ default:
+ return nil, fmt.Errorf("invalid status %s", s)
+ }
+}
+
+// ListRuns lists jobs for api route validated ownerID and repoID
+// ownerID == 0 and repoID == 0 means all runs
+// 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
+// Access rights are checked at the API route level
+func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
+ if ownerID != 0 && repoID != 0 {
+ setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
+ }
+ opts := actions_model.FindRunOptions{
+ OwnerID: ownerID,
+ RepoID: repoID,
+ ListOptions: utils.GetListOptions(ctx),
+ }
+
+ if event := ctx.FormString("event"); event != "" {
+ opts.TriggerEvent = webhook.HookEventType(event)
+ }
+ if branch := ctx.FormString("branch"); branch != "" {
+ opts.Ref = string(git.RefNameFromBranch(branch))
+ }
+ for _, status := range ctx.FormStrings("status") {
+ values, err := convertToInternal(status)
+ if err != nil {
+ ctx.APIError(http.StatusBadRequest, fmt.Errorf("Invalid status %s", status))
+ return
+ }
+ opts.Status = append(opts.Status, values...)
+ }
+ if actor := ctx.FormString("actor"); actor != "" {
+ user, err := user_model.GetUserByName(ctx, actor)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ opts.TriggerUserID = user.ID
+ }
+ if headSHA := ctx.FormString("head_sha"); headSHA != "" {
+ opts.CommitSHA = headSHA
+ }
+
+ runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+
+ 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
+ }
+ }
+
+ convertedRun, err := convert.ToActionWorkflowRun(ctx, repository, runs[i])
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ res.Entries[i] = convertedRun
+ }
+
+ ctx.JSON(http.StatusOK, &res)
+}
diff --git a/routers/api/v1/shared/block.go b/routers/api/v1/shared/block.go
index 490a48f81c..b22f8a74fd 100644
--- a/routers/api/v1/shared/block.go
+++ b/routers/api/v1/shared/block.go
@@ -21,12 +21,12 @@ func ListBlocks(ctx *context.APIContext, blocker *user_model.User) {
BlockerID: blocker.ID,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FindBlockings", err)
+ ctx.APIErrorInternal(err)
return
}
if err := user_model.BlockingList(blocks).LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -42,14 +42,14 @@ 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.NotFound("GetUserByName", err)
+ ctx.APIErrorNotFound("GetUserByName", err)
return
}
status := http.StatusNotFound
blocking, err := user_model.GetBlocking(ctx, blocker.ID, blockee.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetBlocking", err)
+ ctx.APIErrorInternal(err)
return
}
if blocking != nil {
@@ -62,15 +62,15 @@ func CheckUserBlock(ctx *context.APIContext, blocker *user_model.User) {
func BlockUser(ctx *context.APIContext, blocker *user_model.User) {
blockee, err := user_model.GetUserByName(ctx, ctx.PathParam("username"))
if err != nil {
- ctx.NotFound("GetUserByName", err)
+ ctx.APIErrorNotFound("GetUserByName", 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.Error(http.StatusBadRequest, "BlockUser", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else {
- ctx.Error(http.StatusInternalServerError, "BlockUser", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -81,15 +81,15 @@ 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.NotFound("GetUserByName", err)
+ ctx.APIErrorNotFound("GetUserByName", 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.Error(http.StatusBadRequest, "UnblockUser", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else {
- ctx.Error(http.StatusInternalServerError, "UnblockUser", err)
+ ctx.APIErrorInternal(err)
}
return
}
diff --git a/routers/api/v1/shared/runners.go b/routers/api/v1/shared/runners.go
index f088e9a2d4..e9834aff9f 100644
--- a/routers/api/v1/shared/runners.go
+++ b/routers/api/v1/shared/runners.go
@@ -8,8 +8,13 @@ import (
"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/routers/api/v1/utils"
"code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/convert"
)
// RegistrationToken is response related to registration token
@@ -24,9 +29,99 @@ func GetRegistrationToken(ctx *context.APIContext, ownerID, repoID int64) {
token, err = actions_model.NewRunnerToken(ctx, ownerID, repoID)
}
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, RegistrationToken{Token: token.Token})
}
+
+// ListRunners lists runners for api route validated ownerID and repoID
+// ownerID == 0 and repoID == 0 means all runners including global runners, does not appear in sql where clause
+// ownerID == 0 and repoID != 0 means all runners for the given repo
+// ownerID != 0 and repoID == 0 means all runners for the given user/org
+// ownerID != 0 and repoID != 0 undefined behavior
+// Access rights are checked at the API route level
+func ListRunners(ctx *context.APIContext, ownerID, repoID int64) {
+ if ownerID != 0 && repoID != 0 {
+ setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
+ }
+ runners, total, err := db.FindAndCount[actions_model.ActionRunner](ctx, &actions_model.FindRunnerOptions{
+ OwnerID: ownerID,
+ RepoID: repoID,
+ ListOptions: utils.GetListOptions(ctx),
+ })
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+
+ res := new(api.ActionRunnersResponse)
+ res.TotalCount = total
+
+ res.Entries = make([]*api.ActionRunner, len(runners))
+ for i, runner := range runners {
+ res.Entries[i] = convert.ToActionRunner(ctx, runner)
+ }
+
+ ctx.JSON(http.StatusOK, &res)
+}
+
+func getRunnerByID(ctx *context.APIContext, ownerID, repoID, runnerID int64) (*actions_model.ActionRunner, bool) {
+ if ownerID != 0 && repoID != 0 {
+ setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
+ }
+
+ 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)
+ }
+ return nil, false
+ }
+
+ if !runner.EditableInContext(ownerID, repoID) {
+ ctx.APIErrorNotFound("No permission to access this runner")
+ return nil, false
+ }
+ return runner, true
+}
+
+// GetRunner get the runner for api route validated ownerID and repoID
+// ownerID == 0 and repoID == 0 means any runner including global runners
+// ownerID == 0 and repoID != 0 means any runner for the given repo
+// ownerID != 0 and repoID == 0 means any runner for the given user/org
+// ownerID != 0 and repoID != 0 undefined behavior
+// Access rights are checked at the API route level
+func GetRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
+ if ownerID != 0 && repoID != 0 {
+ setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
+ }
+ runner, ok := getRunnerByID(ctx, ownerID, repoID, runnerID)
+ if !ok {
+ return
+ }
+ ctx.JSON(http.StatusOK, convert.ToActionRunner(ctx, runner))
+}
+
+// DeleteRunner deletes the runner for api route validated ownerID and repoID
+// ownerID == 0 and repoID == 0 means any runner including global runners
+// ownerID == 0 and repoID != 0 means any runner for the given repo
+// ownerID != 0 and repoID == 0 means any runner for the given user/org
+// ownerID != 0 and repoID != 0 undefined behavior
+// Access rights are checked at the API route level
+func DeleteRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
+ runner, ok := getRunnerByID(ctx, ownerID, repoID, runnerID)
+ if !ok {
+ return
+ }
+
+ err := actions_model.DeleteRunner(ctx, runner.ID)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ ctx.Status(http.StatusNoContent)
+}
diff --git a/routers/api/v1/swagger/action.go b/routers/api/v1/swagger/action.go
index 665f4d0b85..16a250184a 100644
--- a/routers/api/v1/swagger/action.go
+++ b/routers/api/v1/swagger/action.go
@@ -32,3 +32,17 @@ type swaggerResponseVariableList struct {
// in:body
Body []api.ActionVariable `json:"body"`
}
+
+// ActionWorkflow
+// swagger:response ActionWorkflow
+type swaggerResponseActionWorkflow struct {
+ // in:body
+ Body api.ActionWorkflow `json:"body"`
+}
+
+// ActionWorkflowList
+// swagger:response ActionWorkflowList
+type swaggerResponseActionWorkflowList struct {
+ // in:body
+ Body []api.ActionWorkflow `json:"body"`
+}
diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go
index 125605d98f..bafd5e04a2 100644
--- a/routers/api/v1/swagger/options.go
+++ b/routers/api/v1/swagger/options.go
@@ -119,6 +119,9 @@ type swaggerParameterBodies struct {
EditAttachmentOptions api.EditAttachmentOptions
// in:body
+ GetFilesOptions api.GetFilesOptions
+
+ // in:body
ChangeFilesOptions api.ChangeFilesOptions
// in:body
@@ -209,5 +212,14 @@ type swaggerParameterBodies struct {
CreateVariableOption api.CreateVariableOption
// in:body
+ RenameOrgOption api.RenameOrgOption
+
+ // in:body
+ CreateActionWorkflowDispatch api.CreateActionWorkflowDispatch
+
+ // in:body
UpdateVariableOption api.UpdateVariableOption
+
+ // in:body
+ LockIssueOption api.LockIssueOption
}
diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go
index f754c80a5b..9e20c0533b 100644
--- a/routers/api/v1/swagger/repo.go
+++ b/routers/api/v1/swagger/repo.go
@@ -331,6 +331,12 @@ type swaggerContentsListResponse struct {
Body []api.ContentsResponse `json:"body"`
}
+// swagger:response ContentsExtResponse
+type swaggerContentsExtResponse struct {
+ // in:body
+ Body api.ContentsExtResponse `json:"body"`
+}
+
// FileDeleteResponse
// swagger:response FileDeleteResponse
type swaggerFileDeleteResponse struct {
@@ -443,6 +449,62 @@ type swaggerRepoTasksList struct {
Body api.ActionTaskResponse `json:"body"`
}
+// WorkflowRunsList
+// swagger:response WorkflowRunsList
+type swaggerActionWorkflowRunsResponse struct {
+ // in:body
+ Body api.ActionWorkflowRunsResponse `json:"body"`
+}
+
+// WorkflowRun
+// swagger:response WorkflowRun
+type swaggerWorkflowRun struct {
+ // in:body
+ Body api.ActionWorkflowRun `json:"body"`
+}
+
+// WorkflowJobsList
+// swagger:response WorkflowJobsList
+type swaggerActionWorkflowJobsResponse struct {
+ // in:body
+ Body api.ActionWorkflowJobsResponse `json:"body"`
+}
+
+// WorkflowJob
+// swagger:response WorkflowJob
+type swaggerWorkflowJob struct {
+ // in:body
+ Body api.ActionWorkflowJob `json:"body"`
+}
+
+// ArtifactsList
+// swagger:response ArtifactsList
+type swaggerRepoArtifactsList struct {
+ // in:body
+ Body api.ActionArtifactsResponse `json:"body"`
+}
+
+// Artifact
+// swagger:response Artifact
+type swaggerRepoArtifact struct {
+ // in:body
+ Body api.ActionArtifact `json:"body"`
+}
+
+// RunnerList
+// swagger:response RunnerList
+type swaggerRunnerList struct {
+ // in:body
+ Body api.ActionRunnersResponse `json:"body"`
+}
+
+// Runner
+// swagger:response Runner
+type swaggerRunner struct {
+ // in:body
+ Body api.ActionRunner `json:"body"`
+}
+
// swagger:response Compare
type swaggerCompare struct {
// in:body
diff --git a/routers/api/v1/user/action.go b/routers/api/v1/user/action.go
index 22707196f4..e934d02aa7 100644
--- a/routers/api/v1/user/action.go
+++ b/routers/api/v1/user/action.go
@@ -12,6 +12,7 @@ import (
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"
@@ -49,14 +50,14 @@ func CreateOrUpdateSecret(ctx *context.APIContext) {
opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
- _, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Doer.ID, 0, ctx.PathParam("secretname"), opt.Data)
+ _, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Doer.ID, 0, ctx.PathParam("secretname"), opt.Data, opt.Description)
if err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
- ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else if errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusNotFound, "CreateOrUpdateSecret", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -94,11 +95,11 @@ func DeleteSecret(ctx *context.APIContext) {
err := secret_service.DeleteSecretByName(ctx, ctx.Doer.ID, 0, ctx.PathParam("secretname"))
if err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
- ctx.Error(http.StatusBadRequest, "DeleteSecret", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else if errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusNotFound, "DeleteSecret", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteSecret", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -127,13 +128,11 @@ func CreateVariable(ctx *context.APIContext) {
// "$ref": "#/definitions/CreateVariableOption"
// responses:
// "201":
- // description: response when creating a variable
- // "204":
- // description: response when creating a variable
+ // description: successfully created the user-level variable
// "400":
// "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
+ // "409":
+ // description: variable name already exists.
opt := web.GetForm(ctx).(*api.CreateVariableOption)
@@ -145,24 +144,24 @@ func CreateVariable(ctx *context.APIContext) {
Name: variableName,
})
if err != nil && !errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusInternalServerError, "GetVariable", err)
+ ctx.APIErrorInternal(err)
return
}
if v != nil && v.ID > 0 {
- ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
+ ctx.APIError(http.StatusConflict, util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
return
}
- if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value); err != nil {
+ if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value, opt.Description); err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
- ctx.Error(http.StatusBadRequest, "CreateVariable", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else {
- ctx.Error(http.StatusInternalServerError, "CreateVariable", err)
+ ctx.APIErrorInternal(err)
}
return
}
- ctx.Status(http.StatusNoContent)
+ ctx.Status(http.StatusCreated)
}
// UpdateVariable update a user-level variable which is created by current doer
@@ -202,9 +201,9 @@ func UpdateVariable(ctx *context.APIContext) {
})
if err != nil {
if errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusNotFound, "GetVariable", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetVariable", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -212,11 +211,16 @@ func UpdateVariable(ctx *context.APIContext) {
if opt.Name == "" {
opt.Name = ctx.PathParam("variablename")
}
- if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil {
+
+ v.Name = opt.Name
+ v.Data = opt.Value
+ v.Description = opt.Description
+
+ if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
- ctx.Error(http.StatusBadRequest, "UpdateVariable", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else {
- ctx.Error(http.StatusInternalServerError, "UpdateVariable", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -249,11 +253,11 @@ func DeleteVariable(ctx *context.APIContext) {
if err := actions_service.DeleteVariableByName(ctx, ctx.Doer.ID, 0, ctx.PathParam("variablename")); err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
- ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err)
+ ctx.APIError(http.StatusBadRequest, err)
} else if errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusNotFound, "DeleteVariableByName", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -288,18 +292,19 @@ func GetVariable(ctx *context.APIContext) {
})
if err != nil {
if errors.Is(err, util.ErrNotExist) {
- ctx.Error(http.StatusNotFound, "GetVariable", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "GetVariable", err)
+ ctx.APIErrorInternal(err)
}
return
}
variable := &api.ActionVariable{
- OwnerID: v.OwnerID,
- RepoID: v.RepoID,
- Name: v.Name,
- Data: v.Data,
+ OwnerID: v.OwnerID,
+ RepoID: v.RepoID,
+ Name: v.Name,
+ Data: v.Data,
+ Description: v.Description,
}
ctx.JSON(http.StatusOK, variable)
@@ -334,20 +339,104 @@ func ListVariables(ctx *context.APIContext) {
ListOptions: utils.GetListOptions(ctx),
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "FindVariables", err)
+ ctx.APIErrorInternal(err)
return
}
variables := make([]*api.ActionVariable, len(vars))
for i, v := range vars {
variables[i] = &api.ActionVariable{
- OwnerID: v.OwnerID,
- RepoID: v.RepoID,
- Name: v.Name,
- Data: v.Data,
+ OwnerID: v.OwnerID,
+ RepoID: v.RepoID,
+ Name: v.Name,
+ Data: v.Data,
+ Description: v.Description,
}
}
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, variables)
}
+
+// ListWorkflowRuns lists workflow runs
+func ListWorkflowRuns(ctx *context.APIContext) {
+ // swagger:operation GET /user/actions/runs user getUserWorkflowRuns
+ // ---
+ // summary: Get workflow runs
+ // parameters:
+ // - 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: 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
+ // produces:
+ // - application/json
+ // responses:
+ // "200":
+ // "$ref": "#/responses/WorkflowRunsList"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.ListRuns(ctx, ctx.Doer.ID, 0)
+}
+
+// ListWorkflowJobs lists workflow jobs
+func ListWorkflowJobs(ctx *context.APIContext) {
+ // swagger:operation GET /user/actions/jobs user getUserWorkflowJobs
+ // ---
+ // summary: Get workflow jobs
+ // parameters:
+ // - 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
+ // produces:
+ // - application/json
+ // responses:
+ // "200":
+ // "$ref": "#/responses/WorkflowJobsList"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ shared.ListJobs(ctx, ctx.Doer.ID, 0, 0)
+}
diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go
index bfbc2ba622..6f1053e7ac 100644
--- a/routers/api/v1/user/app.go
+++ b/routers/api/v1/user/app.go
@@ -30,7 +30,7 @@ func ListAccessTokens(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of to user whose access tokens are to be listed
// type: string
// required: true
// - name: page
@@ -51,7 +51,7 @@ func ListAccessTokens(ctx *context.APIContext) {
tokens, count, err := db.FindAndCount[auth_model.AccessToken](ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -62,6 +62,8 @@ func ListAccessTokens(ctx *context.APIContext) {
Name: tokens[i].Name,
TokenLastEight: tokens[i].TokenLastEight,
Scopes: tokens[i].Scope.StringSlice(),
+ Created: tokens[i].CreatedUnix.AsTime(),
+ Updated: tokens[i].UpdatedUnix.AsTime(),
}
}
@@ -81,7 +83,7 @@ func CreateAccessToken(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose token is to be created
// required: true
// type: string
// - name: body
@@ -105,27 +107,27 @@ func CreateAccessToken(ctx *context.APIContext) {
exist, err := auth_model.AccessTokenByNameExists(ctx, t)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
if exist {
- ctx.Error(http.StatusBadRequest, "AccessTokenByNameExists", errors.New("access token name has been used already"))
+ ctx.APIError(http.StatusBadRequest, errors.New("access token name has been used already"))
return
}
scope, err := auth_model.AccessTokenScope(strings.Join(form.Scopes, ",")).Normalize()
if err != nil {
- ctx.Error(http.StatusBadRequest, "AccessTokenScope.Normalize", fmt.Errorf("invalid access token scope provided: %w", err))
+ ctx.APIError(http.StatusBadRequest, fmt.Errorf("invalid access token scope provided: %w", err))
return
}
if scope == "" {
- ctx.Error(http.StatusBadRequest, "AccessTokenScope", "access token must have a scope")
+ ctx.APIError(http.StatusBadRequest, "access token must have a scope")
return
}
t.Scope = scope
if err := auth_model.NewAccessToken(ctx, t); err != nil {
- ctx.Error(http.StatusInternalServerError, "NewAccessToken", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusCreated, &api.AccessToken{
@@ -147,7 +149,7 @@ func DeleteAccessToken(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose token is to be deleted
// type: string
// required: true
// - name: token
@@ -174,31 +176,31 @@ func DeleteAccessToken(ctx *context.APIContext) {
UserID: ctx.ContextUser.ID,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ListAccessTokens", err)
+ ctx.APIErrorInternal(err)
return
}
switch len(tokens) {
case 0:
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
case 1:
tokenID = tokens[0].ID
default:
- ctx.Error(http.StatusUnprocessableEntity, "DeleteAccessTokenByID", fmt.Errorf("multiple matches for token name '%s'", token))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("multiple matches for token name '%s'", token))
return
}
}
if tokenID == 0 {
- ctx.Error(http.StatusInternalServerError, "Invalid TokenID", nil)
+ ctx.APIErrorInternal(nil)
return
}
if err := auth_model.DeleteAccessTokenByID(ctx, tokenID, ctx.ContextUser.ID); err != nil {
if auth_model.IsErrAccessTokenNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteAccessTokenByID", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -235,12 +237,12 @@ func CreateOauth2Application(ctx *context.APIContext) {
SkipSecondaryAuthorization: data.SkipSecondaryAuthorization,
})
if err != nil {
- ctx.Error(http.StatusBadRequest, "", "error creating oauth2 application")
+ ctx.APIError(http.StatusBadRequest, "error creating oauth2 application")
return
}
secret, err := app.GenerateClientSecret(ctx)
if err != nil {
- ctx.Error(http.StatusBadRequest, "", "error creating application secret")
+ ctx.APIError(http.StatusBadRequest, "error creating application secret")
return
}
app.ClientSecret = secret
@@ -273,7 +275,7 @@ func ListOauth2Applications(ctx *context.APIContext) {
OwnerID: ctx.Doer.ID,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ListOAuth2Applications", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -309,9 +311,9 @@ func DeleteOauth2Application(ctx *context.APIContext) {
appID := ctx.PathParamInt64("id")
if err := auth_model.DeleteOAuth2Application(ctx, appID, ctx.Doer.ID); err != nil {
if auth_model.IsErrOAuthApplicationNotFound(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteOauth2ApplicationByID", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -342,14 +344,14 @@ func GetOauth2Application(ctx *context.APIContext) {
app, err := auth_model.GetOAuth2ApplicationByID(ctx, appID)
if err != nil {
if auth_model.IsErrOauthClientIDInvalid(err) || auth_model.IsErrOAuthApplicationNotFound(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetOauth2ApplicationByID", err)
+ ctx.APIErrorInternal(err)
}
return
}
if app.UID != ctx.Doer.ID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -396,15 +398,15 @@ func UpdateOauth2Application(ctx *context.APIContext) {
})
if err != nil {
if auth_model.IsErrOauthClientIDInvalid(err) || auth_model.IsErrOAuthApplicationNotFound(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "UpdateOauth2ApplicationByID", err)
+ ctx.APIErrorInternal(err)
}
return
}
app.ClientSecret, err = app.GenerateClientSecret(ctx)
if err != nil {
- ctx.Error(http.StatusBadRequest, "", "error updating application secret")
+ ctx.APIError(http.StatusBadRequest, "error updating application secret")
return
}
diff --git a/routers/api/v1/user/avatar.go b/routers/api/v1/user/avatar.go
index 30ccb63587..9c7bd57bc0 100644
--- a/routers/api/v1/user/avatar.go
+++ b/routers/api/v1/user/avatar.go
@@ -32,13 +32,13 @@ func UpdateAvatar(ctx *context.APIContext) {
content, err := base64.StdEncoding.DecodeString(form.Image)
if err != nil {
- ctx.Error(http.StatusBadRequest, "DecodeImage", err)
+ ctx.APIError(http.StatusBadRequest, err)
return
}
err = user_service.UploadAvatar(ctx, ctx.Doer, content)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "UploadAvatar", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -57,7 +57,7 @@ func DeleteAvatar(ctx *context.APIContext) {
// "$ref": "#/responses/empty"
err := user_service.DeleteAvatar(ctx, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "DeleteAvatar", err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/user/block.go b/routers/api/v1/user/block.go
index 7231e9add7..8365188f60 100644
--- a/routers/api/v1/user/block.go
+++ b/routers/api/v1/user/block.go
@@ -37,7 +37,7 @@ func CheckUserBlock(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: user to check
+ // description: username of the user to check
// type: string
// required: true
// responses:
@@ -56,7 +56,7 @@ func BlockUser(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: user to block
+ // description: username of the user to block
// type: string
// required: true
// - name: note
@@ -81,7 +81,7 @@ func UnblockUser(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: user to unblock
+ // description: username of the user to unblock
// type: string
// required: true
// responses:
diff --git a/routers/api/v1/user/email.go b/routers/api/v1/user/email.go
index 33aa851a80..055e5ea419 100644
--- a/routers/api/v1/user/email.go
+++ b/routers/api/v1/user/email.go
@@ -29,7 +29,7 @@ func ListEmails(ctx *context.APIContext) {
emails, err := user_model.GetEmailAddresses(ctx, ctx.Doer.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetEmailAddresses", err)
+ ctx.APIErrorInternal(err)
return
}
apiEmails := make([]*api.Email, len(emails))
@@ -59,13 +59,13 @@ func AddEmail(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.CreateEmailOption)
if len(form.Emails) == 0 {
- ctx.Error(http.StatusUnprocessableEntity, "", "Email list empty")
+ ctx.APIError(http.StatusUnprocessableEntity, "Email list empty")
return
}
if err := user_service.AddEmailAddresses(ctx, ctx.Doer, form.Emails); err != nil {
if user_model.IsErrEmailAlreadyUsed(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", "Email address has been used: "+err.(user_model.ErrEmailAlreadyUsed).Email)
+ ctx.APIError(http.StatusUnprocessableEntity, "Email address has been used: "+err.(user_model.ErrEmailAlreadyUsed).Email)
} else if user_model.IsErrEmailCharIsNotSupported(err) || user_model.IsErrEmailInvalid(err) {
email := ""
if typedError, ok := err.(user_model.ErrEmailInvalid); ok {
@@ -76,16 +76,16 @@ func AddEmail(ctx *context.APIContext) {
}
errMsg := fmt.Sprintf("Email address %q invalid", email)
- ctx.Error(http.StatusUnprocessableEntity, "", errMsg)
+ ctx.APIError(http.StatusUnprocessableEntity, errMsg)
} else {
- ctx.Error(http.StatusInternalServerError, "AddEmailAddresses", err)
+ ctx.APIErrorInternal(err)
}
return
}
emails, err := user_model.GetEmailAddresses(ctx, ctx.Doer.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetEmailAddresses", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -122,9 +122,9 @@ func DeleteEmail(ctx *context.APIContext) {
if err := user_service.DeleteEmailAddresses(ctx, ctx.Doer, form.Emails); err != nil {
if user_model.IsErrEmailAddressNotExist(err) {
- ctx.Error(http.StatusNotFound, "DeleteEmailAddresses", err)
+ ctx.APIError(http.StatusNotFound, err)
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteEmailAddresses", err)
+ ctx.APIErrorInternal(err)
}
return
}
diff --git a/routers/api/v1/user/follower.go b/routers/api/v1/user/follower.go
index 8f46808f9e..339b994af4 100644
--- a/routers/api/v1/user/follower.go
+++ b/routers/api/v1/user/follower.go
@@ -26,7 +26,7 @@ func responseAPIUsers(ctx *context.APIContext, users []*user_model.User) {
func listUserFollowers(ctx *context.APIContext, u *user_model.User) {
users, count, err := user_model.GetUserFollowers(ctx, u, ctx.Doer, utils.GetListOptions(ctx))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserFollowers", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -67,7 +67,7 @@ func ListFollowers(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose followers are to be listed
// type: string
// required: true
// - name: page
@@ -90,7 +90,7 @@ func ListFollowers(ctx *context.APIContext) {
func listUserFollowing(ctx *context.APIContext, u *user_model.User) {
users, count, err := user_model.GetUserFollowing(ctx, u, ctx.Doer, utils.GetListOptions(ctx))
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserFollowing", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -131,7 +131,7 @@ func ListFollowing(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose followed users are to be listed
// type: string
// required: true
// - name: page
@@ -155,7 +155,7 @@ func checkUserFollowing(ctx *context.APIContext, u *user_model.User, followID in
if user_model.IsFollowing(ctx, u.ID, followID) {
ctx.Status(http.StatusNoContent)
} else {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
}
}
@@ -167,7 +167,7 @@ func CheckMyFollowing(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of followed user
+ // description: username of the user to check for authenticated followers
// type: string
// required: true
// responses:
@@ -187,12 +187,12 @@ func CheckFollowing(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of following user
+ // description: username of the following user
// type: string
// required: true
// - name: target
// in: path
- // description: username of followed user
+ // description: username of the followed user
// type: string
// required: true
// responses:
@@ -216,7 +216,7 @@ func Follow(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user to follow
+ // description: username of the user to follow
// type: string
// required: true
// responses:
@@ -229,9 +229,9 @@ func Follow(ctx *context.APIContext) {
if err := user_model.FollowUser(ctx, ctx.Doer, ctx.ContextUser); err != nil {
if errors.Is(err, user_model.ErrBlockedUser) {
- ctx.Error(http.StatusForbidden, "FollowUser", err)
+ ctx.APIError(http.StatusForbidden, err)
} else {
- ctx.Error(http.StatusInternalServerError, "FollowUser", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -246,7 +246,7 @@ func Unfollow(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user to unfollow
+ // description: username of the user to unfollow
// type: string
// required: true
// responses:
@@ -256,7 +256,7 @@ func Unfollow(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if err := user_model.UnfollowUser(ctx, ctx.Doer.ID, ctx.ContextUser.ID); err != nil {
- ctx.Error(http.StatusInternalServerError, "UnfollowUser", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
diff --git a/routers/api/v1/user/gpg_key.go b/routers/api/v1/user/gpg_key.go
index ef667a1883..9ec4d2c938 100644
--- a/routers/api/v1/user/gpg_key.go
+++ b/routers/api/v1/user/gpg_key.go
@@ -4,7 +4,7 @@
package user
import (
- "fmt"
+ "errors"
"net/http"
"strings"
@@ -25,12 +25,12 @@ func listGPGKeys(ctx *context.APIContext, uid int64, listOptions db.ListOptions)
OwnerID: uid,
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ListGPGKeys", err)
+ ctx.APIErrorInternal(err)
return
}
if err := asymkey_model.GPGKeyList(keys).LoadSubKeys(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "ListGPGKeys", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -53,7 +53,7 @@ func ListGPGKeys(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose GPG key list is to be obtained
// type: string
// required: true
// - name: page
@@ -119,14 +119,14 @@ func GetGPGKey(ctx *context.APIContext) {
key, err := asymkey_model.GetGPGKeyForUserByID(ctx, ctx.Doer.ID, ctx.PathParamInt64("id"))
if err != nil {
if asymkey_model.IsErrGPGKeyNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetGPGKeyByID", err)
+ ctx.APIErrorInternal(err)
}
return
}
if err := key.LoadSubKeys(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadSubKeys", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, convert.ToGPGKey(key))
@@ -135,7 +135,7 @@ func GetGPGKey(ctx *context.APIContext) {
// CreateUserGPGKey creates new GPG key to given user by ID.
func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) {
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
- ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
+ ctx.APIErrorNotFound("Not Found", errors.New("gpg keys setting is not allowed to be visited"))
return
}
@@ -194,7 +194,7 @@ func VerifyUserGPGKey(ctx *context.APIContext) {
form.KeyID = strings.TrimLeft(form.KeyID, "0")
if form.KeyID == "" {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -205,10 +205,10 @@ func VerifyUserGPGKey(ctx *context.APIContext) {
if err != nil {
if asymkey_model.IsErrGPGInvalidTokenSignature(err) {
- ctx.Error(http.StatusUnprocessableEntity, "GPGInvalidSignature", fmt.Sprintf("The provided GPG key, signature and token do not match or token is out of date. Provide a valid signature for the token: %s", token))
+ ctx.APIError(http.StatusUnprocessableEntity, "The provided GPG key, signature and token do not match or token is out of date. Provide a valid signature for the token: "+token)
return
}
- ctx.Error(http.StatusInternalServerError, "VerifyUserGPGKey", err)
+ ctx.APIErrorInternal(err)
}
keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
@@ -217,9 +217,9 @@ func VerifyUserGPGKey(ctx *context.APIContext) {
})
if err != nil {
if asymkey_model.IsErrGPGKeyNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetGPGKeysByKeyID", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -276,15 +276,15 @@ func DeleteGPGKey(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
- ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
+ ctx.APIErrorNotFound("Not Found", errors.New("gpg keys setting is not allowed to be visited"))
return
}
if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.PathParamInt64("id")); err != nil {
if asymkey_model.IsErrGPGKeyAccessDenied(err) {
- ctx.Error(http.StatusForbidden, "", "You do not have access to this key")
+ ctx.APIError(http.StatusForbidden, "You do not have access to this key")
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteGPGKey", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -296,16 +296,16 @@ func DeleteGPGKey(ctx *context.APIContext) {
func HandleAddGPGKeyError(ctx *context.APIContext, err error, token string) {
switch {
case asymkey_model.IsErrGPGKeyAccessDenied(err):
- ctx.Error(http.StatusUnprocessableEntity, "GPGKeyAccessDenied", "You do not have access to this GPG key")
+ ctx.APIError(http.StatusUnprocessableEntity, "You do not have access to this GPG key")
case asymkey_model.IsErrGPGKeyIDAlreadyUsed(err):
- ctx.Error(http.StatusUnprocessableEntity, "GPGKeyIDAlreadyUsed", "A key with the same id already exists")
+ ctx.APIError(http.StatusUnprocessableEntity, "A key with the same id already exists")
case asymkey_model.IsErrGPGKeyParsing(err):
- ctx.Error(http.StatusUnprocessableEntity, "GPGKeyParsing", err)
+ ctx.APIError(http.StatusUnprocessableEntity, err)
case asymkey_model.IsErrGPGNoEmailFound(err):
- ctx.Error(http.StatusNotFound, "GPGNoEmailFound", fmt.Sprintf("None of the emails attached to the GPG key could be found. It may still be added if you provide a valid signature for the token: %s", token))
+ ctx.APIError(http.StatusNotFound, "None of the emails attached to the GPG key could be found. It may still be added if you provide a valid signature for the token: "+token)
case asymkey_model.IsErrGPGInvalidTokenSignature(err):
- ctx.Error(http.StatusUnprocessableEntity, "GPGInvalidSignature", fmt.Sprintf("The provided GPG key, signature and token do not match or token is out of date. Provide a valid signature for the token: %s", token))
+ ctx.APIError(http.StatusUnprocessableEntity, "The provided GPG key, signature and token do not match or token is out of date. Provide a valid signature for the token: "+token)
default:
- ctx.Error(http.StatusInternalServerError, "AddGPGKey", err)
+ ctx.APIErrorInternal(err)
}
}
diff --git a/routers/api/v1/user/helper.go b/routers/api/v1/user/helper.go
index 9a6f305700..f49bbbd6db 100644
--- a/routers/api/v1/user/helper.go
+++ b/routers/api/v1/user/helper.go
@@ -4,8 +4,6 @@
package user
import (
- "net/http"
-
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/services/context"
)
@@ -20,10 +18,10 @@ func GetUserByPathParam(ctx *context.APIContext, name string) *user_model.User {
if redirectUserID, err2 := user_model.LookupUserRedirect(ctx, username); err2 == nil {
context.RedirectToUser(ctx.Base, username, redirectUserID)
} else {
- ctx.NotFound("GetUserByName", err)
+ ctx.APIErrorNotFound("GetUserByName", err)
}
} else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
}
return nil
}
diff --git a/routers/api/v1/user/hook.go b/routers/api/v1/user/hook.go
index b4605c8a29..73c98ce746 100644
--- a/routers/api/v1/user/hook.go
+++ b/routers/api/v1/user/hook.go
@@ -63,13 +63,13 @@ func GetHook(ctx *context.APIContext) {
}
if !ctx.Doer.IsAdmin && hook.OwnerID != ctx.Doer.ID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
apiHook, err := webhook_service.ToHook(ctx.Doer.HomeLink(), hook)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, apiHook)
diff --git a/routers/api/v1/user/key.go b/routers/api/v1/user/key.go
index 5a9125b4f3..aa69245e49 100644
--- a/routers/api/v1/user/key.go
+++ b/routers/api/v1/user/key.go
@@ -5,7 +5,7 @@ package user
import (
std_ctx "context"
- "fmt"
+ "errors"
"net/http"
asymkey_model "code.gitea.io/gitea/models/asymkey"
@@ -24,9 +24,10 @@ import (
// appendPrivateInformation appends the owner and key type information to api.PublicKey
func appendPrivateInformation(ctx std_ctx.Context, apiKey *api.PublicKey, key *asymkey_model.PublicKey, defaultUser *user_model.User) (*api.PublicKey, error) {
- if key.Type == asymkey_model.KeyTypeDeploy {
+ switch key.Type {
+ case asymkey_model.KeyTypeDeploy:
apiKey.KeyType = "deploy"
- } else if key.Type == asymkey_model.KeyTypeUser {
+ case asymkey_model.KeyTypeUser:
apiKey.KeyType = "user"
if defaultUser.ID == key.OwnerID {
@@ -38,7 +39,7 @@ func appendPrivateInformation(ctx std_ctx.Context, apiKey *api.PublicKey, key *a
}
apiKey.Owner = convert.ToUser(ctx, user, user)
}
- } else {
+ default:
apiKey.KeyType = "unknown"
}
apiKey.ReadOnly = key.Mode == perm.AccessModeRead
@@ -81,7 +82,7 @@ func listPublicKeys(ctx *context.APIContext, user *user_model.User) {
}
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ListPublicKeys", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -135,7 +136,7 @@ func ListPublicKeys(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose public keys are to be listed
// type: string
// required: true
// - name: fingerprint
@@ -182,9 +183,9 @@ func GetPublicKey(ctx *context.APIContext) {
key, err := asymkey_model.GetPublicKeyByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if asymkey_model.IsErrKeyNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetPublicKeyByID", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -200,7 +201,7 @@ func GetPublicKey(ctx *context.APIContext) {
// CreateUserPublicKey creates new public key to given user by ID.
func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid int64) {
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
- ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
+ ctx.APIErrorNotFound("Not Found", errors.New("ssh keys setting is not allowed to be visited"))
return
}
@@ -270,7 +271,7 @@ func DeletePublicKey(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
- ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
+ ctx.APIErrorNotFound("Not Found", errors.New("ssh keys setting is not allowed to be visited"))
return
}
@@ -278,23 +279,23 @@ func DeletePublicKey(ctx *context.APIContext) {
externallyManaged, err := asymkey_model.PublicKeyIsExternallyManaged(ctx, id)
if err != nil {
if asymkey_model.IsErrKeyNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "PublicKeyIsExternallyManaged", err)
+ ctx.APIErrorInternal(err)
}
return
}
if externallyManaged {
- ctx.Error(http.StatusForbidden, "", "SSH Key is externally managed for this user")
+ ctx.APIError(http.StatusForbidden, "SSH Key is externally managed for this user")
return
}
if err := asymkey_service.DeletePublicKey(ctx, ctx.Doer, id); err != nil {
if asymkey_model.IsErrKeyAccessDenied(err) {
- ctx.Error(http.StatusForbidden, "", "You do not have access to this key")
+ ctx.APIError(http.StatusForbidden, "You do not have access to this key")
} else {
- ctx.Error(http.StatusInternalServerError, "DeletePublicKey", err)
+ ctx.APIErrorInternal(err)
}
return
}
diff --git a/routers/api/v1/user/repo.go b/routers/api/v1/user/repo.go
index 6111341423..6d0129681e 100644
--- a/routers/api/v1/user/repo.go
+++ b/routers/api/v1/user/repo.go
@@ -19,19 +19,19 @@ import (
func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
opts := utils.GetListOptions(ctx)
- repos, count, err := repo_model.GetUserRepositories(ctx, &repo_model.SearchRepoOptions{
+ repos, count, err := repo_model.GetUserRepositories(ctx, repo_model.SearchRepoOptions{
Actor: u,
Private: private,
ListOptions: opts,
OrderBy: "id ASC",
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserRepositories", err)
+ ctx.APIErrorInternal(err)
return
}
if err := repos.LoadAttributes(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "RepositoryList.LoadAttributes", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -39,7 +39,7 @@ func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
for i := range repos {
permission, err := access_model.GetUserRepoPermission(ctx, repos[i], ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
+ ctx.APIErrorInternal(err)
return
}
if ctx.IsSigned && ctx.Doer.IsAdmin || permission.HasAnyUnitAccess() {
@@ -62,7 +62,7 @@ func ListUserRepos(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose owned repos are to be listed
// type: string
// required: true
// - name: page
@@ -103,7 +103,7 @@ func ListMyRepos(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/RepositoryList"
- opts := &repo_model.SearchRepoOptions{
+ opts := repo_model.SearchRepoOptions{
ListOptions: utils.GetListOptions(ctx),
Actor: ctx.Doer,
OwnerID: ctx.Doer.ID,
@@ -113,19 +113,19 @@ func ListMyRepos(ctx *context.APIContext) {
repos, count, err := repo_model.SearchRepository(ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "SearchRepository", err)
+ ctx.APIErrorInternal(err)
return
}
results := make([]*api.Repository, len(repos))
for i, repo := range repos {
if err = repo.LoadOwner(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadOwner", err)
+ ctx.APIErrorInternal(err)
return
}
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
+ ctx.APIErrorInternal(err)
}
results[i] = convert.ToRepo(ctx, repo, permission)
}
diff --git a/routers/api/v1/user/runners.go b/routers/api/v1/user/runners.go
index 899218473e..be3f63cc5e 100644
--- a/routers/api/v1/user/runners.go
+++ b/routers/api/v1/user/runners.go
@@ -24,3 +24,81 @@ func GetRegistrationToken(ctx *context.APIContext) {
shared.GetRegistrationToken(ctx, ctx.Doer.ID, 0)
}
+
+// CreateRegistrationToken returns the token to register user runners
+func CreateRegistrationToken(ctx *context.APIContext) {
+ // swagger:operation POST /user/actions/runners/registration-token user userCreateRunnerRegistrationToken
+ // ---
+ // summary: Get an user's actions runner registration token
+ // produces:
+ // - application/json
+ // parameters:
+ // responses:
+ // "200":
+ // "$ref": "#/responses/RegistrationToken"
+
+ shared.GetRegistrationToken(ctx, ctx.Doer.ID, 0)
+}
+
+// ListRunners get user-level runners
+func ListRunners(ctx *context.APIContext) {
+ // swagger:operation GET /user/actions/runners user getUserRunners
+ // ---
+ // summary: Get user-level runners
+ // produces:
+ // - application/json
+ // responses:
+ // "200":
+ // "$ref": "#/definitions/ActionRunnersResponse"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.ListRunners(ctx, ctx.Doer.ID, 0)
+}
+
+// GetRunner get an user-level runner
+func GetRunner(ctx *context.APIContext) {
+ // swagger:operation GET /user/actions/runners/{runner_id} user getUserRunner
+ // ---
+ // summary: Get an user-level runner
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: runner_id
+ // in: path
+ // description: id of the runner
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/definitions/ActionRunner"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.GetRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
+}
+
+// DeleteRunner delete an user-level runner
+func DeleteRunner(ctx *context.APIContext) {
+ // swagger:operation DELETE /user/actions/runners/{runner_id} user deleteUserRunner
+ // ---
+ // summary: Delete an user-level runner
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: runner_id
+ // in: path
+ // description: id of the runner
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // description: runner has been deleted
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.DeleteRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
+}
diff --git a/routers/api/v1/user/settings.go b/routers/api/v1/user/settings.go
index d0a8daaa85..d67c54b339 100644
--- a/routers/api/v1/user/settings.go
+++ b/routers/api/v1/user/settings.go
@@ -57,7 +57,7 @@ func UpdateUserSettings(ctx *context.APIContext) {
KeepActivityPrivate: optional.FromPtr(form.HideActivity),
}
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
diff --git a/routers/api/v1/user/star.go b/routers/api/v1/user/star.go
index ad9ed9548d..ee5d63063b 100644
--- a/routers/api/v1/user/star.go
+++ b/routers/api/v1/user/star.go
@@ -50,7 +50,7 @@ func GetStarredRepos(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose starred repos are to be listed
// type: string
// required: true
// - name: page
@@ -66,11 +66,13 @@ func GetStarredRepos(ctx *context.APIContext) {
// "$ref": "#/responses/RepositoryList"
// "404":
// "$ref": "#/responses/notFound"
+ // "403":
+ // "$ref": "#/responses/forbidden"
private := ctx.ContextUser.ID == ctx.Doer.ID
repos, err := getStarredRepos(ctx, ctx.ContextUser, private)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "getStarredRepos", err)
+ ctx.APIErrorInternal(err)
return
}
@@ -97,10 +99,12 @@ func GetMyStarredRepos(ctx *context.APIContext) {
// responses:
// "200":
// "$ref": "#/responses/RepositoryList"
+ // "403":
+ // "$ref": "#/responses/forbidden"
repos, err := getStarredRepos(ctx, ctx.Doer, true)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "getStarredRepos", err)
+ ctx.APIErrorInternal(err)
}
ctx.SetTotalCountHeader(int64(ctx.Doer.NumStars))
@@ -128,11 +132,13 @@ func IsStarring(ctx *context.APIContext) {
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
+ // "403":
+ // "$ref": "#/responses/forbidden"
if repo_model.IsStaring(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) {
ctx.Status(http.StatusNoContent)
} else {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
}
}
@@ -163,9 +169,9 @@ func Star(ctx *context.APIContext) {
err := repo_model.StarRepo(ctx, ctx.Doer, ctx.Repo.Repository, true)
if err != nil {
if errors.Is(err, user_model.ErrBlockedUser) {
- ctx.Error(http.StatusForbidden, "BlockedUser", err)
+ ctx.APIError(http.StatusForbidden, err)
} else {
- ctx.Error(http.StatusInternalServerError, "StarRepo", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -193,10 +199,12 @@ func Unstar(ctx *context.APIContext) {
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
+ // "403":
+ // "$ref": "#/responses/forbidden"
err := repo_model.StarRepo(ctx, ctx.Doer, ctx.Repo.Repository, false)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "StarRepo", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go
index 43dabe1b60..6de1125c40 100644
--- a/routers/api/v1/user/user.go
+++ b/routers/api/v1/user/user.go
@@ -73,7 +73,7 @@ func Search(ctx *context.APIContext) {
if ctx.PublicOnly {
visible = []structs.VisibleType{structs.VisibleTypePublic}
}
- users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
+ users, maxResults, err = user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Keyword: ctx.FormTrim("q"),
UID: uid,
@@ -110,7 +110,7 @@ func GetInfo(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user to get
+ // description: username of the user whose data is to be listed
// type: string
// required: true
// responses:
@@ -121,7 +121,7 @@ func GetInfo(ctx *context.APIContext) {
if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) {
// fake ErrUserNotExist error message to not leak information about existence
- ctx.NotFound("GetUserByName", user_model.ErrUserNotExist{Name: ctx.PathParam("username")})
+ ctx.APIErrorNotFound("GetUserByName", user_model.ErrUserNotExist{Name: ctx.PathParam("username")})
return
}
ctx.JSON(http.StatusOK, convert.ToUser(ctx, ctx.ContextUser, ctx.Doer))
@@ -151,7 +151,7 @@ func GetUserHeatmapData(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user to get
+ // description: username of the user whose heatmap is to be obtained
// type: string
// required: true
// responses:
@@ -162,7 +162,7 @@ func GetUserHeatmapData(ctx *context.APIContext) {
heatmap, err := activities_model.GetUserHeatmapDataByUser(ctx, ctx.ContextUser, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserHeatmapDataByUser", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, heatmap)
@@ -177,7 +177,7 @@ func ListUserActivityFeeds(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose activity feeds are to be listed
// type: string
// required: true
// - name: only-performed-by
@@ -217,7 +217,7 @@ func ListUserActivityFeeds(ctx *context.APIContext) {
feeds, count, err := feed_service.GetFeeds(ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetFeeds", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.SetTotalCountHeader(count)
diff --git a/routers/api/v1/user/watch.go b/routers/api/v1/user/watch.go
index 2cc23ae476..844eac2c67 100644
--- a/routers/api/v1/user/watch.go
+++ b/routers/api/v1/user/watch.go
@@ -49,7 +49,7 @@ func GetWatchedRepos(ctx *context.APIContext) {
// - name: username
// type: string
// in: path
- // description: username of the user
+ // description: username of the user whose watched repos are to be listed
// required: true
// - name: page
// in: query
@@ -68,7 +68,7 @@ func GetWatchedRepos(ctx *context.APIContext) {
private := ctx.ContextUser.ID == ctx.Doer.ID
repos, total, err := getWatchedRepos(ctx, ctx.ContextUser, private)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "getWatchedRepos", err)
+ ctx.APIErrorInternal(err)
}
ctx.SetTotalCountHeader(total)
@@ -97,7 +97,7 @@ func GetMyWatchedRepos(ctx *context.APIContext) {
repos, total, err := getWatchedRepos(ctx, ctx.Doer, true)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "getWatchedRepos", err)
+ ctx.APIErrorInternal(err)
}
ctx.SetTotalCountHeader(total)
@@ -137,7 +137,7 @@ func IsWatching(ctx *context.APIContext) {
RepositoryURL: ctx.Repo.Repository.APIURL(),
})
} else {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
}
}
@@ -168,9 +168,9 @@ func Watch(ctx *context.APIContext) {
err := repo_model.WatchRepo(ctx, ctx.Doer, ctx.Repo.Repository, true)
if err != nil {
if errors.Is(err, user_model.ErrBlockedUser) {
- ctx.Error(http.StatusForbidden, "BlockedUser", err)
+ ctx.APIError(http.StatusForbidden, err)
} else {
- ctx.Error(http.StatusInternalServerError, "WatchRepo", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -208,7 +208,7 @@ func Unwatch(ctx *context.APIContext) {
err := repo_model.WatchRepo(ctx, ctx.Doer, ctx.Repo.Repository, false)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "UnwatchRepo", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
diff --git a/routers/api/v1/utils/git.go b/routers/api/v1/utils/git.go
index 4e25137817..1cfe01a639 100644
--- a/routers/api/v1/utils/git.go
+++ b/routers/api/v1/utils/git.go
@@ -4,53 +4,54 @@
package utils
import (
- gocontext "context"
- "fmt"
- "net/http"
+ "errors"
+ 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/log"
+ "code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/services/context"
)
-// ResolveRefOrSha resolve ref to sha if exist
-func ResolveRefOrSha(ctx *context.APIContext, ref string) string {
- if len(ref) == 0 {
- ctx.Error(http.StatusBadRequest, "ref not given", nil)
- return ""
- }
+type RefCommit struct {
+ InputRef string
+ RefName git.RefName
+ Commit *git.Commit
+ CommitID string
+}
- sha := ref
- // Search branches and tags
- for _, refType := range []string{"heads", "tags"} {
- refSHA, lastMethodName, err := searchRefCommitByType(ctx, refType, ref)
- if err != nil {
- ctx.Error(http.StatusInternalServerError, lastMethodName, err)
- return ""
- }
- if refSHA != "" {
- sha = refSHA
- break
- }
+// ResolveRefCommit resolve ref to a commit if exist
+func ResolveRefCommit(ctx reqctx.RequestContext, repo *repo_model.Repository, inputRef string, minCommitIDLen ...int) (_ *RefCommit, err error) {
+ gitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, repo)
+ if err != nil {
+ return nil, err
}
-
- sha = MustConvertToSHA1(ctx, ctx.Repo, sha)
-
- if ctx.Repo.GitRepo != nil {
- err := ctx.Repo.GitRepo.AddLastCommitCache(ctx.Repo.Repository.GetCommitsCountCacheKey(ref, ref != sha), ctx.Repo.Repository.FullName(), sha)
- if err != nil {
- log.Error("Unable to get commits count for %s in %s. Error: %v", sha, ctx.Repo.Repository.FullName(), err)
- }
+ refCommit := RefCommit{InputRef: inputRef}
+ if gitrepo.IsBranchExist(ctx, repo, inputRef) {
+ refCommit.RefName = git.RefNameFromBranch(inputRef)
+ } else if gitrepo.IsTagExist(ctx, repo, inputRef) {
+ refCommit.RefName = git.RefNameFromTag(inputRef)
+ } else if git.IsStringLikelyCommitID(git.ObjectFormatFromName(repo.ObjectFormatName), inputRef, minCommitIDLen...) {
+ refCommit.RefName = git.RefNameFromCommit(inputRef)
+ }
+ if refCommit.RefName == "" {
+ return nil, git.ErrNotExist{ID: inputRef}
}
+ if refCommit.Commit, err = gitRepo.GetCommit(refCommit.RefName.String()); err != nil {
+ return nil, err
+ }
+ refCommit.CommitID = refCommit.Commit.ID.String()
+ return &refCommit, nil
+}
- return sha
+func NewRefCommit(refName git.RefName, commit *git.Commit) *RefCommit {
+ return &RefCommit{InputRef: refName.ShortName(), RefName: refName, Commit: commit, CommitID: commit.ID.String()}
}
// GetGitRefs return git references based on filter
func GetGitRefs(ctx *context.APIContext, filter string) ([]*git.Reference, string, error) {
if ctx.Repo.GitRepo == nil {
- return nil, "", fmt.Errorf("no open git repo found in context")
+ return nil, "", errors.New("no open git repo found in context")
}
if len(filter) > 0 {
filter = "refs/" + filter
@@ -58,42 +59,3 @@ func GetGitRefs(ctx *context.APIContext, filter string) ([]*git.Reference, strin
refs, err := ctx.Repo.GitRepo.GetRefsFiltered(filter)
return refs, "GetRefsFiltered", err
}
-
-func searchRefCommitByType(ctx *context.APIContext, refType, filter string) (string, string, error) {
- refs, lastMethodName, err := GetGitRefs(ctx, refType+"/"+filter) // Search by type
- if err != nil {
- return "", lastMethodName, err
- }
- if len(refs) > 0 {
- return refs[0].Object.String(), "", nil // Return found SHA
- }
- return "", "", nil
-}
-
-// ConvertToObjectID returns a full-length SHA1 from a potential ID string
-func ConvertToObjectID(ctx gocontext.Context, repo *context.Repository, commitID string) (git.ObjectID, error) {
- objectFormat := repo.GetObjectFormat()
- if len(commitID) == objectFormat.FullLength() && objectFormat.IsValid(commitID) {
- sha, err := git.NewIDFromString(commitID)
- if err == nil {
- return sha, nil
- }
- }
-
- gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo.Repository)
- if err != nil {
- return objectFormat.EmptyObjectID(), fmt.Errorf("RepositoryFromContextOrOpen: %w", err)
- }
- defer closer.Close()
-
- return gitRepo.ConvertToGitID(commitID)
-}
-
-// MustConvertToSHA1 returns a full-length SHA1 string from a potential ID string, or returns origin input if it can't convert to SHA1
-func MustConvertToSHA1(ctx gocontext.Context, repo *context.Repository, commitID string) string {
- sha, err := ConvertToObjectID(ctx, repo, commitID)
- if err != nil {
- return commitID
- }
- return sha.String()
-}
diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go
index 4328878e19..6f598f14c8 100644
--- a/routers/api/v1/utils/hook.go
+++ b/routers/api/v1/utils/hook.go
@@ -4,7 +4,6 @@
package utils
import (
- "fmt"
"net/http"
"strconv"
"strings"
@@ -16,6 +15,7 @@ import (
"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"
webhook_module "code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/services/context"
webhook_service "code.gitea.io/gitea/services/webhook"
@@ -30,7 +30,7 @@ func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) {
hooks, count, err := db.FindAndCount[webhook.Webhook](ctx, opts)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
@@ -38,7 +38,7 @@ func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) {
for i, hook := range hooks {
apiHooks[i], err = webhook_service.ToHook(owner.HomeLink(), hook)
if err != nil {
- ctx.InternalServerError(err)
+ ctx.APIErrorInternal(err)
return
}
}
@@ -52,9 +52,9 @@ func GetOwnerHook(ctx *context.APIContext, ownerID, hookID int64) (*webhook.Webh
w, err := webhook.GetWebhookByOwnerID(ctx, ownerID, hookID)
if err != nil {
if webhook.IsErrWebhookNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetWebhookByOwnerID", err)
+ ctx.APIErrorInternal(err)
}
return nil, err
}
@@ -67,9 +67,9 @@ func GetRepoHook(ctx *context.APIContext, repoID, hookID int64) (*webhook.Webhoo
w, err := webhook.GetWebhookByRepoID(ctx, repoID, hookID)
if err != nil {
if webhook.IsErrWebhookNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetWebhookByID", err)
+ ctx.APIErrorInternal(err)
}
return nil, err
}
@@ -80,17 +80,21 @@ func GetRepoHook(ctx *context.APIContext, repoID, hookID int64) (*webhook.Webhoo
// write the appropriate error to `ctx`. Return whether the form is valid
func checkCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption) bool {
if !webhook_service.IsValidHookTaskType(form.Type) {
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Invalid hook type: %s", form.Type))
+ ctx.APIError(http.StatusUnprocessableEntity, "Invalid hook type: "+form.Type)
return false
}
for _, name := range []string{"url", "content_type"} {
if _, ok := form.Config[name]; !ok {
- ctx.Error(http.StatusUnprocessableEntity, "", "Missing config option: "+name)
+ ctx.APIError(http.StatusUnprocessableEntity, "Missing config option: "+name)
return false
}
}
if !webhook.IsValidHookContentType(form.Config["content_type"]) {
- ctx.Error(http.StatusUnprocessableEntity, "", "Invalid content type")
+ ctx.APIError(http.StatusUnprocessableEntity, "Invalid content type")
+ return false
+ }
+ if !validation.IsValidURL(form.Config["url"]) {
+ ctx.APIError(http.StatusUnprocessableEntity, "Invalid url")
return false
}
return true
@@ -102,7 +106,7 @@ func AddSystemHook(ctx *context.APIContext, form *api.CreateHookOption) {
if ok {
h, err := webhook_service.ToHook(setting.AppSubURL+"/-/admin", hook)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "convert.ToHook", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusCreated, h)
@@ -141,7 +145,7 @@ func AddRepoHook(ctx *context.APIContext, form *api.CreateHookOption) {
func toAPIHook(ctx *context.APIContext, repoLink string, hook *webhook.Webhook) (*api.Hook, bool) {
apiHook, err := webhook_service.ToHook(repoLink, hook)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "ToHook", err)
+ ctx.APIErrorInternal(err)
return nil, false
}
return apiHook, true
@@ -155,6 +159,42 @@ func pullHook(events []string, event string) bool {
return util.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventPullRequest), true)
}
+func updateHookEvents(events []string) webhook_module.HookEvents {
+ if len(events) == 0 {
+ events = []string{"push"}
+ }
+ hookEvents := make(webhook_module.HookEvents)
+ hookEvents[webhook_module.HookEventCreate] = util.SliceContainsString(events, string(webhook_module.HookEventCreate), true)
+ hookEvents[webhook_module.HookEventPush] = util.SliceContainsString(events, string(webhook_module.HookEventPush), true)
+ hookEvents[webhook_module.HookEventDelete] = util.SliceContainsString(events, string(webhook_module.HookEventDelete), true)
+ hookEvents[webhook_module.HookEventFork] = util.SliceContainsString(events, string(webhook_module.HookEventFork), true)
+ hookEvents[webhook_module.HookEventRepository] = util.SliceContainsString(events, string(webhook_module.HookEventRepository), true)
+ hookEvents[webhook_module.HookEventWiki] = util.SliceContainsString(events, string(webhook_module.HookEventWiki), true)
+ hookEvents[webhook_module.HookEventRelease] = util.SliceContainsString(events, string(webhook_module.HookEventRelease), true)
+ hookEvents[webhook_module.HookEventPackage] = util.SliceContainsString(events, string(webhook_module.HookEventPackage), true)
+ hookEvents[webhook_module.HookEventStatus] = util.SliceContainsString(events, string(webhook_module.HookEventStatus), true)
+ hookEvents[webhook_module.HookEventWorkflowRun] = util.SliceContainsString(events, string(webhook_module.HookEventWorkflowRun), true)
+ hookEvents[webhook_module.HookEventWorkflowJob] = util.SliceContainsString(events, string(webhook_module.HookEventWorkflowJob), true)
+
+ // Issues
+ hookEvents[webhook_module.HookEventIssues] = issuesHook(events, "issues_only")
+ hookEvents[webhook_module.HookEventIssueAssign] = issuesHook(events, string(webhook_module.HookEventIssueAssign))
+ hookEvents[webhook_module.HookEventIssueLabel] = issuesHook(events, string(webhook_module.HookEventIssueLabel))
+ hookEvents[webhook_module.HookEventIssueMilestone] = issuesHook(events, string(webhook_module.HookEventIssueMilestone))
+ hookEvents[webhook_module.HookEventIssueComment] = issuesHook(events, string(webhook_module.HookEventIssueComment))
+
+ // Pull requests
+ hookEvents[webhook_module.HookEventPullRequest] = pullHook(events, "pull_request_only")
+ hookEvents[webhook_module.HookEventPullRequestAssign] = pullHook(events, string(webhook_module.HookEventPullRequestAssign))
+ hookEvents[webhook_module.HookEventPullRequestLabel] = pullHook(events, string(webhook_module.HookEventPullRequestLabel))
+ hookEvents[webhook_module.HookEventPullRequestMilestone] = pullHook(events, string(webhook_module.HookEventPullRequestMilestone))
+ hookEvents[webhook_module.HookEventPullRequestComment] = pullHook(events, string(webhook_module.HookEventPullRequestComment))
+ hookEvents[webhook_module.HookEventPullRequestReview] = pullHook(events, "pull_request_review")
+ hookEvents[webhook_module.HookEventPullRequestReviewRequest] = pullHook(events, string(webhook_module.HookEventPullRequestReviewRequest))
+ hookEvents[webhook_module.HookEventPullRequestSync] = pullHook(events, string(webhook_module.HookEventPullRequestSync))
+ return hookEvents
+}
+
// addHook add the hook specified by `form`, `ownerID` and `repoID`. If there is
// an error, write to `ctx` accordingly. Return (webhook, ok)
func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoID int64) (*webhook.Webhook, bool) {
@@ -163,13 +203,10 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
return nil, false
}
- if len(form.Events) == 0 {
- form.Events = []string{"push"}
- }
if form.Config["is_system_webhook"] != "" {
sw, err := strconv.ParseBool(form.Config["is_system_webhook"])
if err != nil {
- ctx.Error(http.StatusUnprocessableEntity, "", "Invalid is_system_webhook value")
+ ctx.APIError(http.StatusUnprocessableEntity, "Invalid is_system_webhook value")
return nil, false
}
isSystemWebhook = sw
@@ -184,28 +221,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
IsSystemWebhook: isSystemWebhook,
HookEvent: &webhook_module.HookEvent{
ChooseEvents: true,
- HookEvents: webhook_module.HookEvents{
- Create: util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true),
- Delete: util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true),
- Fork: util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true),
- Issues: issuesHook(form.Events, "issues_only"),
- IssueAssign: issuesHook(form.Events, string(webhook_module.HookEventIssueAssign)),
- IssueLabel: issuesHook(form.Events, string(webhook_module.HookEventIssueLabel)),
- IssueMilestone: issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone)),
- IssueComment: issuesHook(form.Events, string(webhook_module.HookEventIssueComment)),
- Push: util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true),
- PullRequest: pullHook(form.Events, "pull_request_only"),
- PullRequestAssign: pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign)),
- PullRequestLabel: pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel)),
- PullRequestMilestone: pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone)),
- PullRequestComment: pullHook(form.Events, string(webhook_module.HookEventPullRequestComment)),
- PullRequestReview: pullHook(form.Events, "pull_request_review"),
- PullRequestReviewRequest: pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest)),
- PullRequestSync: pullHook(form.Events, string(webhook_module.HookEventPullRequestSync)),
- Wiki: util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true),
- Repository: util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true),
- Release: util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true),
- },
+ HookEvents: updateHookEvents(form.Events),
BranchFilter: form.BranchFilter,
},
IsActive: form.Active,
@@ -213,19 +229,19 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
}
err := w.SetHeaderAuthorization(form.AuthorizationHeader)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "SetHeaderAuthorization", err)
+ ctx.APIErrorInternal(err)
return nil, false
}
if w.Type == webhook_module.SLACK {
channel, ok := form.Config["channel"]
if !ok {
- ctx.Error(http.StatusUnprocessableEntity, "", "Missing config option: channel")
+ ctx.APIError(http.StatusUnprocessableEntity, "Missing config option: channel")
return nil, false
}
channel = strings.TrimSpace(channel)
if !webhook_service.IsValidSlackChannel(channel) {
- ctx.Error(http.StatusBadRequest, "", "Invalid slack channel name")
+ ctx.APIError(http.StatusBadRequest, "Invalid slack channel name")
return nil, false
}
@@ -236,17 +252,17 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
Color: form.Config["color"],
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "slack: JSON marshal failed", err)
+ ctx.APIErrorInternal(err)
return nil, false
}
w.Meta = string(meta)
}
if err := w.UpdateEvent(); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateEvent", err)
+ ctx.APIErrorInternal(err)
return nil, false
} else if err := webhook.CreateWebhook(ctx, w); err != nil {
- ctx.Error(http.StatusInternalServerError, "CreateWebhook", err)
+ ctx.APIErrorInternal(err)
return nil, false
}
return w, true
@@ -256,21 +272,21 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
func EditSystemHook(ctx *context.APIContext, form *api.EditHookOption, hookID int64) {
hook, err := webhook.GetSystemOrDefaultWebhook(ctx, hookID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetSystemOrDefaultWebhook", err)
+ ctx.APIErrorInternal(err)
return
}
if !editHook(ctx, form, hook) {
- ctx.Error(http.StatusInternalServerError, "editHook", err)
+ ctx.APIErrorInternal(err)
return
}
updated, err := webhook.GetSystemOrDefaultWebhook(ctx, hookID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetSystemOrDefaultWebhook", err)
+ ctx.APIErrorInternal(err)
return
}
h, err := webhook_service.ToHook(setting.AppURL+"/-/admin", updated)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "convert.ToHook", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, h)
@@ -322,11 +338,15 @@ func EditRepoHook(ctx *context.APIContext, form *api.EditHookOption, hookID int6
func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webhook) bool {
if form.Config != nil {
if url, ok := form.Config["url"]; ok {
+ if !validation.IsValidURL(url) {
+ ctx.APIError(http.StatusUnprocessableEntity, "Invalid url")
+ return false
+ }
w.URL = url
}
if ct, ok := form.Config["content_type"]; ok {
if !webhook.IsValidHookContentType(ct) {
- ctx.Error(http.StatusUnprocessableEntity, "", "Invalid content type")
+ ctx.APIError(http.StatusUnprocessableEntity, "Invalid content type")
return false
}
w.ContentType = webhook.ToHookContentType(ct)
@@ -341,7 +361,7 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
Color: form.Config["color"],
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "slack: JSON marshal failed", err)
+ ctx.APIErrorInternal(err)
return false
}
w.Meta = string(meta)
@@ -350,47 +370,20 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
}
// Update events
- if len(form.Events) == 0 {
- form.Events = []string{"push"}
- }
+ w.HookEvents = updateHookEvents(form.Events)
w.PushOnly = false
w.SendEverything = false
w.ChooseEvents = true
- w.Create = util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true)
- w.Push = util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true)
- w.Create = util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true)
- w.Delete = util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true)
- w.Fork = util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true)
- w.Repository = util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true)
- w.Wiki = util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true)
- w.Release = util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true)
w.BranchFilter = form.BranchFilter
err := w.SetHeaderAuthorization(form.AuthorizationHeader)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "SetHeaderAuthorization", err)
+ ctx.APIErrorInternal(err)
return false
}
- // Issues
- w.Issues = issuesHook(form.Events, "issues_only")
- w.IssueAssign = issuesHook(form.Events, string(webhook_module.HookEventIssueAssign))
- w.IssueLabel = issuesHook(form.Events, string(webhook_module.HookEventIssueLabel))
- w.IssueMilestone = issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone))
- w.IssueComment = issuesHook(form.Events, string(webhook_module.HookEventIssueComment))
-
- // Pull requests
- w.PullRequest = pullHook(form.Events, "pull_request_only")
- w.PullRequestAssign = pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign))
- w.PullRequestLabel = pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel))
- w.PullRequestMilestone = pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone))
- w.PullRequestComment = pullHook(form.Events, string(webhook_module.HookEventPullRequestComment))
- w.PullRequestReview = pullHook(form.Events, "pull_request_review")
- w.PullRequestReviewRequest = pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest))
- w.PullRequestSync = pullHook(form.Events, string(webhook_module.HookEventPullRequestSync))
-
if err := w.UpdateEvent(); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateEvent", err)
+ ctx.APIErrorInternal(err)
return false
}
@@ -399,7 +392,7 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
}
if err := webhook.UpdateWebhook(ctx, w); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateWebhook", err)
+ ctx.APIErrorInternal(err)
return false
}
return true
@@ -409,9 +402,9 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
func DeleteOwnerHook(ctx *context.APIContext, owner *user_model.User, hookID int64) {
if err := webhook.DeleteWebhookByOwnerID(ctx, owner.ID, hookID); err != nil {
if webhook.IsErrWebhookNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "DeleteWebhookByOwnerID", err)
+ ctx.APIErrorInternal(err)
}
return
}
diff --git a/routers/api/v1/utils/hook_test.go b/routers/api/v1/utils/hook_test.go
new file mode 100644
index 0000000000..e5e8ce07ce
--- /dev/null
+++ b/routers/api/v1/utils/hook_test.go
@@ -0,0 +1,82 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package utils
+
+import (
+ "net/http"
+ "testing"
+
+ "code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/contexttest"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestTestHookValidation(t *testing.T) {
+ unittest.PrepareTestEnv(t)
+
+ t.Run("Test Validation", func(t *testing.T) {
+ ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
+ contexttest.LoadRepo(t, ctx, 1)
+ contexttest.LoadRepoCommit(t, ctx)
+ contexttest.LoadUser(t, ctx, 2)
+
+ checkCreateHookOption(ctx, &structs.CreateHookOption{
+ Type: "gitea",
+ Config: map[string]string{
+ "content_type": "json",
+ "url": "https://example.com/webhook",
+ },
+ })
+ assert.Equal(t, 0, ctx.Resp.WrittenStatus()) // not written yet
+ })
+
+ t.Run("Test Validation with invalid URL", func(t *testing.T) {
+ ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
+ contexttest.LoadRepo(t, ctx, 1)
+ contexttest.LoadRepoCommit(t, ctx)
+ contexttest.LoadUser(t, ctx, 2)
+
+ checkCreateHookOption(ctx, &structs.CreateHookOption{
+ Type: "gitea",
+ Config: map[string]string{
+ "content_type": "json",
+ "url": "example.com/webhook",
+ },
+ })
+ assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
+ })
+
+ t.Run("Test Validation with invalid webhook type", func(t *testing.T) {
+ ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
+ contexttest.LoadRepo(t, ctx, 1)
+ contexttest.LoadRepoCommit(t, ctx)
+ contexttest.LoadUser(t, ctx, 2)
+
+ checkCreateHookOption(ctx, &structs.CreateHookOption{
+ Type: "unknown",
+ Config: map[string]string{
+ "content_type": "json",
+ "url": "example.com/webhook",
+ },
+ })
+ assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
+ })
+
+ t.Run("Test Validation with empty content type", func(t *testing.T) {
+ ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
+ contexttest.LoadRepo(t, ctx, 1)
+ contexttest.LoadRepoCommit(t, ctx)
+ contexttest.LoadUser(t, ctx, 2)
+
+ checkCreateHookOption(ctx, &structs.CreateHookOption{
+ Type: "unknown",
+ Config: map[string]string{
+ "url": "https://example.com/webhook",
+ },
+ })
+ assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
+ })
+}
diff --git a/routers/api/v1/utils/main_test.go b/routers/api/v1/utils/main_test.go
new file mode 100644
index 0000000000..4eace1f369
--- /dev/null
+++ b/routers/api/v1/utils/main_test.go
@@ -0,0 +1,21 @@
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package utils
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/setting"
+ webhook_service "code.gitea.io/gitea/services/webhook"
+)
+
+func TestMain(m *testing.M) {
+ unittest.MainTest(m, &unittest.TestOptions{
+ SetUp: func() error {
+ setting.LoadQueueSettings()
+ return webhook_service.Init()
+ },
+ })
+}