diff --git a/.eslintrc.cjs b/.eslintrc.cjs index abb9bde232..83410dc07c 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,3 +1,4 @@ +const vitestPlugin = require('@vitest/eslint-plugin'); const restrictedSyntax = ['WithStatement', 'ForInStatement', 'LabeledStatement', 'SequenceExpression']; module.exports = { @@ -37,8 +38,6 @@ module.exports = { 'eslint-plugin-regexp', 'eslint-plugin-sonarjs', 'eslint-plugin-unicorn', - 'eslint-plugin-vitest', - 'eslint-plugin-vitest-globals', 'eslint-plugin-wc', ], env: { @@ -46,6 +45,13 @@ module.exports = { node: true, }, overrides: [ + { + files: ['**/*.cjs'], + rules: { + 'import-x/no-commonjs': [0], + '@typescript-eslint/no-require-imports': [0], + }, + }, { files: ['web_src/**/*'], globals: { @@ -82,59 +88,58 @@ module.exports = { }, { files: ['**/*.test.*', 'web_src/js/test/setup.ts'], - env: { - 'vitest-globals/env': true, - }, + plugins: ['@vitest/eslint-plugin'], + globals: vitestPlugin.environments.env.globals, rules: { - 'vitest/consistent-test-filename': [0], - 'vitest/consistent-test-it': [0], - 'vitest/expect-expect': [0], - 'vitest/max-expects': [0], - 'vitest/max-nested-describe': [0], - 'vitest/no-alias-methods': [0], - 'vitest/no-commented-out-tests': [0], - 'vitest/no-conditional-expect': [0], - 'vitest/no-conditional-in-test': [0], - 'vitest/no-conditional-tests': [0], - 'vitest/no-disabled-tests': [0], - 'vitest/no-done-callback': [0], - 'vitest/no-duplicate-hooks': [0], - 'vitest/no-focused-tests': [0], - 'vitest/no-hooks': [0], - 'vitest/no-identical-title': [2], - 'vitest/no-interpolation-in-snapshots': [0], - 'vitest/no-large-snapshots': [0], - 'vitest/no-mocks-import': [0], - 'vitest/no-restricted-matchers': [0], - 'vitest/no-restricted-vi-methods': [0], - 'vitest/no-standalone-expect': [0], - 'vitest/no-test-prefixes': [0], - 'vitest/no-test-return-statement': [0], - 'vitest/prefer-called-with': [0], - 'vitest/prefer-comparison-matcher': [0], - 'vitest/prefer-each': [0], - 'vitest/prefer-equality-matcher': [0], - 'vitest/prefer-expect-resolves': [0], - 'vitest/prefer-hooks-in-order': [0], - 'vitest/prefer-hooks-on-top': [2], - 'vitest/prefer-lowercase-title': [0], - 'vitest/prefer-mock-promise-shorthand': [0], - 'vitest/prefer-snapshot-hint': [0], - 'vitest/prefer-spy-on': [0], - 'vitest/prefer-strict-equal': [0], - 'vitest/prefer-to-be': [0], - 'vitest/prefer-to-be-falsy': [0], - 'vitest/prefer-to-be-object': [0], - 'vitest/prefer-to-be-truthy': [0], - 'vitest/prefer-to-contain': [0], - 'vitest/prefer-to-have-length': [0], - 'vitest/prefer-todo': [0], - 'vitest/require-hook': [0], - 'vitest/require-to-throw-message': [0], - 'vitest/require-top-level-describe': [0], - 'vitest/valid-describe-callback': [2], - 'vitest/valid-expect': [2], - 'vitest/valid-title': [2], + '@vitest/consistent-test-filename': [0], + '@vitest/consistent-test-it': [0], + '@vitest/expect-expect': [0], + '@vitest/max-expects': [0], + '@vitest/max-nested-describe': [0], + '@vitest/no-alias-methods': [0], + '@vitest/no-commented-out-tests': [0], + '@vitest/no-conditional-expect': [0], + '@vitest/no-conditional-in-test': [0], + '@vitest/no-conditional-tests': [0], + '@vitest/no-disabled-tests': [0], + '@vitest/no-done-callback': [0], + '@vitest/no-duplicate-hooks': [0], + '@vitest/no-focused-tests': [0], + '@vitest/no-hooks': [0], + '@vitest/no-identical-title': [2], + '@vitest/no-interpolation-in-snapshots': [0], + '@vitest/no-large-snapshots': [0], + '@vitest/no-mocks-import': [0], + '@vitest/no-restricted-matchers': [0], + '@vitest/no-restricted-vi-methods': [0], + '@vitest/no-standalone-expect': [0], + '@vitest/no-test-prefixes': [0], + '@vitest/no-test-return-statement': [0], + '@vitest/prefer-called-with': [0], + '@vitest/prefer-comparison-matcher': [0], + '@vitest/prefer-each': [0], + '@vitest/prefer-equality-matcher': [0], + '@vitest/prefer-expect-resolves': [0], + '@vitest/prefer-hooks-in-order': [0], + '@vitest/prefer-hooks-on-top': [2], + '@vitest/prefer-lowercase-title': [0], + '@vitest/prefer-mock-promise-shorthand': [0], + '@vitest/prefer-snapshot-hint': [0], + '@vitest/prefer-spy-on': [0], + '@vitest/prefer-strict-equal': [0], + '@vitest/prefer-to-be': [0], + '@vitest/prefer-to-be-falsy': [0], + '@vitest/prefer-to-be-object': [0], + '@vitest/prefer-to-be-truthy': [0], + '@vitest/prefer-to-contain': [0], + '@vitest/prefer-to-have-length': [0], + '@vitest/prefer-todo': [0], + '@vitest/require-hook': [0], + '@vitest/require-to-throw-message': [0], + '@vitest/require-top-level-describe': [0], + '@vitest/valid-describe-callback': [2], + '@vitest/valid-expect': [2], + '@vitest/valid-title': [2], }, }, { @@ -163,7 +168,7 @@ module.exports = { { files: ['tests/e2e/**'], plugins: [ - 'eslint-plugin-playwright' + 'eslint-plugin-playwright', ], extends: [ 'plugin:playwright/recommended', diff --git a/.github/workflows/cron-licenses.yml b/.github/workflows/cron-licenses.yml index 7e57f48aa9..33cbc507d9 100644 --- a/.github/workflows/cron-licenses.yml +++ b/.github/workflows/cron-licenses.yml @@ -1,8 +1,8 @@ name: cron-licenses on: - #schedule: - # - cron: "7 0 * * 1" # every Monday at 00:07 UTC + # schedule: + # - cron: "7 0 * * 1" # every Monday at 00:07 UTC workflow_dispatch: jobs: diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000000..88ff1591a4 --- /dev/null +++ b/.mailmap @@ -0,0 +1,2 @@ +Unknwon +Unknwon 无闻 diff --git a/Dockerfile b/Dockerfile index b95ba83289..fa2ae9913c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build stage -FROM docker.io/library/golang:1.23-alpine3.21 AS build-env +FROM docker.io/library/golang:1.24-alpine3.21 AS build-env ARG GOPROXY ENV GOPROXY=${GOPROXY:-direct} diff --git a/Dockerfile.rootless b/Dockerfile.rootless index be6f125104..b74dfa58e0 100644 --- a/Dockerfile.rootless +++ b/Dockerfile.rootless @@ -1,5 +1,5 @@ # Build stage -FROM docker.io/library/golang:1.23-alpine3.21 AS build-env +FROM docker.io/library/golang:1.24-alpine3.21 AS build-env ARG GOPROXY ENV GOPROXY=${GOPROXY:-direct} diff --git a/MAINTAINERS b/MAINTAINERS index f0caae4d22..7d21f449fe 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -63,3 +63,4 @@ Kemal Zebari (@kemzeb) Rowan Bohde (@bohde) hiifong (@hiifong) metiftikci (@metiftikci) +Christopher Homberger (@ChristopherHX) diff --git a/Makefile b/Makefile index 8a7855bd5a..0b0d2afad6 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ SHASUM ?= shasum -a 256 HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes) COMMA := , -XGO_VERSION := go-1.23.x +XGO_VERSION := go-1.24.x AIR_PACKAGE ?= github.com/air-verse/air@v1 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.1.2 @@ -144,9 +144,9 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMAN GO_DIRS := build cmd models modules routers services tests WEB_DIRS := web_src/js web_src/css -ESLINT_FILES := web_src/js tools *.js *.ts tests/e2e +ESLINT_FILES := web_src/js tools *.js *.ts *.cjs tests/e2e STYLELINT_FILES := web_src/css web_src/js/components/*.vue -SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.js *.md *.yml *.yaml *.toml)) +SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.js *.md *.yml *.yaml *.toml)) $(filter-out tools/misspellings.csv, $(wildcard tools/*)) EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini GO_SOURCES := $(wildcard *.go) @@ -393,7 +393,7 @@ lint-templates: .venv node_modules ## lint template files .PHONY: lint-yaml lint-yaml: .venv ## lint yaml files - @poetry run yamllint . + @poetry run yamllint -s . .PHONY: watch watch: ## watch everything and continuously rebuild diff --git a/README.md b/README.md index f747d993d7..5ae65cd2ac 100644 --- a/README.md +++ b/README.md @@ -150,10 +150,64 @@ for the full license text.
Looking for an overview of the interface? Check it out! -|![Dashboard](https://dl.gitea.com/screenshots/home_timeline.png)|![User Profile](https://dl.gitea.com/screenshots/user_profile.png)|![Global Issues](https://dl.gitea.com/screenshots/global_issues.png)| -|:---:|:---:|:---:| -|![Branches](https://dl.gitea.com/screenshots/branches.png)|![Web Editor](https://dl.gitea.com/screenshots/web_editor.png)|![Activity](https://dl.gitea.com/screenshots/activity.png)| -|![New Migration](https://dl.gitea.com/screenshots/migration.png)|![Migrating](https://dl.gitea.com/screenshots/migration.gif)|![Pull Request View](https://image.ibb.co/e02dSb/6.png)| -|![Pull Request Dark](https://dl.gitea.com/screenshots/pull_requests_dark.png)|![Diff Review Dark](https://dl.gitea.com/screenshots/review_dark.png)|![Diff Dark](https://dl.gitea.com/screenshots/diff_dark.png)| +### Login/Register Page + +![Login](https://dl.gitea.com/screenshots/login.png) +![Register](https://dl.gitea.com/screenshots/register.png) + +### User Dashboard + +![Home](https://dl.gitea.com/screenshots/home.png) +![Issues](https://dl.gitea.com/screenshots/issues.png) +![Pull Requests](https://dl.gitea.com/screenshots/pull_requests.png) +![Milestones](https://dl.gitea.com/screenshots/milestones.png) + +### User Profile + +![Profile](https://dl.gitea.com/screenshots/user_profile.png) + +### Explore + +![Repos](https://dl.gitea.com/screenshots/explore_repos.png) +![Users](https://dl.gitea.com/screenshots/explore_users.png) +![Orgs](https://dl.gitea.com/screenshots/explore_orgs.png) + +### Repository + +![Home](https://dl.gitea.com/screenshots/repo_home.png) +![Commits](https://dl.gitea.com/screenshots/repo_commits.png) +![Branches](https://dl.gitea.com/screenshots/repo_branches.png) +![Labels](https://dl.gitea.com/screenshots/repo_labels.png) +![Milestones](https://dl.gitea.com/screenshots/repo_milestones.png) +![Releases](https://dl.gitea.com/screenshots/repo_releases.png) +![Tags](https://dl.gitea.com/screenshots/repo_tags.png) + +#### Repository Issue + +![List](https://dl.gitea.com/screenshots/repo_issues.png) +![Issue](https://dl.gitea.com/screenshots/repo_issue.png) + +#### Repository Pull Requests + +![List](https://dl.gitea.com/screenshots/repo_pull_requests.png) +![Pull Request](https://dl.gitea.com/screenshots/repo_pull_request.png) +![File](https://dl.gitea.com/screenshots/repo_pull_request_file.png) +![Commits](https://dl.gitea.com/screenshots/repo_pull_request_commits.png) + +#### Repository Actions + +![List](https://dl.gitea.com/screenshots/repo_actions.png) +![Details](https://dl.gitea.com/screenshots/repo_actions_run.png) + +#### Repository Activity + +![Activity](https://dl.gitea.com/screenshots/repo_activity.png) +![Contributors](https://dl.gitea.com/screenshots/repo_contributors.png) +![Code Frequency](https://dl.gitea.com/screenshots/repo_code_frequency.png) +![Recent Commits](https://dl.gitea.com/screenshots/repo_recent_commits.png) + +### Organization + +![Home](https://dl.gitea.com/screenshots/org_home.png)
diff --git a/README_ZH.md b/README_ZH.md index 2dd60fd564..89c34f6b63 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -93,10 +93,64 @@ Gitea 提供官方的 [go-sdk](https://gitea.com/gitea/go-sdk),以及名为 [t
截图 -|![Dashboard](https://dl.gitea.com/screenshots/home_timeline.png)|![User Profile](https://dl.gitea.com/screenshots/user_profile.png)|![Global Issues](https://dl.gitea.com/screenshots/global_issues.png)| -|:---:|:---:|:---:| -|![Branches](https://dl.gitea.com/screenshots/branches.png)|![Web Editor](https://dl.gitea.com/screenshots/web_editor.png)|![Activity](https://dl.gitea.com/screenshots/activity.png)| -|![New Migration](https://dl.gitea.com/screenshots/migration.png)|![Migrating](https://dl.gitea.com/screenshots/migration.gif)|![Pull Request View](https://image.ibb.co/e02dSb/6.png)| -|![Pull Request Dark](https://dl.gitea.com/screenshots/pull_requests_dark.png)|![Diff Review Dark](https://dl.gitea.com/screenshots/review_dark.png)|![Diff Dark](https://dl.gitea.com/screenshots/diff_dark.png)| +### 登录界面 + +![登录](https://dl.gitea.com/screenshots/login.png) +![注册](https://dl.gitea.com/screenshots/register.png) + +### 用户首页 + +![首页](https://dl.gitea.com/screenshots/home.png) +![工单列表](https://dl.gitea.com/screenshots/issues.png) +![合并请求列表](https://dl.gitea.com/screenshots/pull_requests.png) +![里程碑列表](https://dl.gitea.com/screenshots/milestones.png) + +### 用户资料 + +![用户资料](https://dl.gitea.com/screenshots/user_profile.png) + +### 探索 + +![仓库列表](https://dl.gitea.com/screenshots/explore_repos.png) +![用户列表](https://dl.gitea.com/screenshots/explore_users.png) +![组织列表](https://dl.gitea.com/screenshots/explore_orgs.png) + +### 仓库 + +![首页](https://dl.gitea.com/screenshots/repo_home.png) +![提交列表](https://dl.gitea.com/screenshots/repo_commits.png) +![分支列表](https://dl.gitea.com/screenshots/repo_branches.png) +![标签列表](https://dl.gitea.com/screenshots/repo_labels.png) +![里程碑列表](https://dl.gitea.com/screenshots/repo_milestones.png) +![版本发布](https://dl.gitea.com/screenshots/repo_releases.png) +![标签列表](https://dl.gitea.com/screenshots/repo_tags.png) + +#### 仓库工单 + +![列表](https://dl.gitea.com/screenshots/repo_issues.png) +![工单](https://dl.gitea.com/screenshots/repo_issue.png) + +#### 仓库合并请求 + +![列表](https://dl.gitea.com/screenshots/repo_pull_requests.png) +![合并请求](https://dl.gitea.com/screenshots/repo_pull_request.png) +![文件](https://dl.gitea.com/screenshots/repo_pull_request_file.png) +![提交列表](https://dl.gitea.com/screenshots/repo_pull_request_commits.png) + +#### 仓库 Actions + +![列表](https://dl.gitea.com/screenshots/repo_actions.png) +![Run](https://dl.gitea.com/screenshots/repo_actions_run.png) + +#### 仓库动态 + +![动态](https://dl.gitea.com/screenshots/repo_activity.png) +![贡献者](https://dl.gitea.com/screenshots/repo_contributors.png) +![代码频率](https://dl.gitea.com/screenshots/repo_code_frequency.png) +![最近的提交](https://dl.gitea.com/screenshots/repo_recent_commits.png) + +### 组织 + +![首页](https://dl.gitea.com/screenshots/org_home.png)
diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index bf8cbc7c4c..5e03d6ca3f 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -31,6 +31,11 @@ var microcmdUserCreate = &cli.Command{ Name: "username", Usage: "Username", }, + &cli.StringFlag{ + Name: "user-type", + Usage: "Set user's type: individual or bot", + Value: "individual", + }, &cli.StringFlag{ Name: "password", Usage: "User password", @@ -77,6 +82,22 @@ func runCreateUser(c *cli.Context) error { return err } + userTypes := map[string]user_model.UserType{ + "individual": user_model.UserTypeIndividual, + "bot": user_model.UserTypeBot, + } + userType, ok := userTypes[c.String("user-type")] + if !ok { + return fmt.Errorf("invalid user type: %s", c.String("user-type")) + } + if userType != user_model.UserTypeIndividual { + // Some other commands like "change-password" also only support individual users. + // It needs to clarify the "password" behavior for bot users in the future. + // At the moment, we do not allow setting password for bot users. + if c.IsSet("password") || c.IsSet("random-password") { + return errors.New("password can only be set for individual users") + } + } if c.IsSet("name") && c.IsSet("username") { return errors.New("cannot set both --name and --username flags") } @@ -118,16 +139,19 @@ func runCreateUser(c *cli.Context) error { return err } fmt.Printf("generated random password is '%s'\n", password) - } else { + } else if userType == user_model.UserTypeIndividual { return errors.New("must set either password or random-password flag") } isAdmin := c.Bool("admin") mustChangePassword := true // always default to true if c.IsSet("must-change-password") { + if userType != user_model.UserTypeIndividual { + return errors.New("must-change-password flag can only be set for individual users") + } // if the flag is set, use the value provided by the user mustChangePassword = c.Bool("must-change-password") - } else { + } else if userType == user_model.UserTypeIndividual { // check whether there are users in the database hasUserRecord, err := db.IsTableNotEmpty(&user_model.User{}) if err != nil { @@ -151,8 +175,9 @@ func runCreateUser(c *cli.Context) error { u := &user_model.User{ Name: username, Email: c.String("email"), - Passwd: password, IsAdmin: isAdmin, + Type: userType, + Passwd: password, MustChangePassword: mustChangePassword, Visibility: visibility, } diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go index 83754e97b1..d8044e8de7 100644 --- a/cmd/admin_user_create_test.go +++ b/cmd/admin_user_create_test.go @@ -13,32 +13,54 @@ import ( user_model "code.gitea.io/gitea/models/user" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestAdminUserCreate(t *testing.T) { app := NewMainApp(AppVersion{}) reset := func() { - assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) - assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{})) + require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) + require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{})) } - type createCheck struct{ IsAdmin, MustChangePassword bool } - createUser := func(name, args string) createCheck { - assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args)))) - u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name}) - return createCheck{u.IsAdmin, u.MustChangePassword} - } - reset() - assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u", ""), "first non-admin user doesn't need to change password") + t.Run("MustChangePassword", func(t *testing.T) { + type check struct { + IsAdmin bool + MustChangePassword bool + } + createCheck := func(name, args string) check { + require.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args)))) + u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name}) + return check{IsAdmin: u.IsAdmin, MustChangePassword: u.MustChangePassword} + } + reset() + assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u", ""), "first non-admin user doesn't need to change password") - reset() - assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u", "--admin"), "first admin user doesn't need to change password") + reset() + assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u", "--admin"), "first admin user doesn't need to change password") - reset() - assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u", "--admin --must-change-password")) - assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u2", "--admin")) - assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u3", "--admin --must-change-password=false")) - assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: true}, createUser("u4", "")) - assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u5", "--must-change-password=false")) + reset() + assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u", "--admin --must-change-password")) + assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u2", "--admin")) + assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u3", "--admin --must-change-password=false")) + assert.Equal(t, check{IsAdmin: false, MustChangePassword: true}, createCheck("u4", "")) + assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false")) + }) + + t.Run("UserType", func(t *testing.T) { + createUser := func(name, args string) error { + return app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s", name, name, args))) + } + + reset() + assert.ErrorContains(t, createUser("u", "--user-type invalid"), "invalid user type") + assert.ErrorContains(t, createUser("u", "--user-type bot --password 123"), "can only be set for individual users") + assert.ErrorContains(t, createUser("u", "--user-type bot --must-change-password"), "can only be set for individual users") + + assert.NoError(t, createUser("u", "--user-type bot")) + u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "u"}) + assert.Equal(t, user_model.UserTypeBot, u.Type) + assert.Equal(t, "", u.Passwd) + }) } diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go index 6ece4bf661..2e3aba021d 100644 --- a/cmd/migrate_storage.go +++ b/cmd/migrate_storage.go @@ -196,7 +196,7 @@ func migrateActionsLog(ctx context.Context, dstStorage storage.ObjectStorage) er func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStorage) error { return db.Iterate(ctx, nil, func(ctx context.Context, artifact *actions_model.ActionArtifact) error { - if artifact.Status == int64(actions_model.ArtifactStatusExpired) { + if artifact.Status == actions_model.ArtifactStatusExpired { return nil } diff --git a/flake.lock b/flake.lock index 4319737c26..2f7b86359b 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1736798957, - "narHash": "sha256-qwpCtZhSsSNQtK4xYGzMiyEDhkNzOCz/Vfu4oL2ETsQ=", + "lastModified": 1739214665, + "narHash": "sha256-26L8VAu3/1YRxS8MHgBOyOM8xALdo6N0I04PgorE7UM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "9abb87b552b7f55ac8916b6fc9e5cb486656a2f3", + "rev": "64e75cd44acf21c7933d61d7721e812eac1b5a0a", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index f54eba1c3b..1b930649d0 100644 --- a/flake.nix +++ b/flake.nix @@ -29,13 +29,13 @@ poetry # backend - go_1_23 + go_1_24 gofumpt sqlite ]; shellHook = '' - export GO="${pkgs.go_1_23}/bin/go" - export GOROOT="${pkgs.go_1_23}/share/go" + export GO="${pkgs.go_1_24}/bin/go" + export GOROOT="${pkgs.go_1_24}/share/go" ''; }; } diff --git a/go.mod b/go.mod index e2ccdd5ae6..593e69acd3 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module code.gitea.io/gitea -go 1.23 +go 1.24 // rfc5280 said: "The serial number is an integer assigned by the CA to each certificate." // But some CAs use negative serial number, just relax the check. related: diff --git a/main_timezones.go b/main_timezones.go new file mode 100644 index 0000000000..e1233007c6 --- /dev/null +++ b/main_timezones.go @@ -0,0 +1,16 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +//go:build windows + +package main + +// Golang has the ability to load OS's timezone data from most UNIX systems (https://github.com/golang/go/blob/master/src/time/zoneinfo_unix.go) +// Even if the timezone data is missing, users could install the related packages to get it. +// But on Windows, although `zoneinfo_windows.go` tries to load the timezone data from Windows registry, +// some users still suffer from the issue that the timezone data is missing: https://github.com/go-gitea/gitea/issues/33235 +// So we import the tzdata package to make sure the timezone data is included in the binary. +// +// For non-Windows package builders, they could still use the "TAGS=timetzdata" to include the tzdata package in the binary. +// If we decided to add the tzdata for other platforms, modify the "go:build" directive above. +import _ "time/tzdata" diff --git a/models/actions/artifact.go b/models/actions/artifact.go index 0bc66ba24e..524224f070 100644 --- a/models/actions/artifact.go +++ b/models/actions/artifact.go @@ -48,7 +48,7 @@ type ActionArtifact struct { ContentEncoding string // The content encoding of the artifact ArtifactPath string `xorm:"index unique(runid_name_path)"` // The path to the artifact when runner uploads it ArtifactName string `xorm:"index unique(runid_name_path)"` // The name of the artifact when runner uploads it - Status int64 `xorm:"index"` // The status of the artifact, uploading, expired or need-delete + Status ArtifactStatus `xorm:"index"` // The status of the artifact, uploading, expired or need-delete CreatedUnix timeutil.TimeStamp `xorm:"created"` UpdatedUnix timeutil.TimeStamp `xorm:"updated index"` ExpiredUnix timeutil.TimeStamp `xorm:"index"` // The time when the artifact will be expired @@ -68,7 +68,7 @@ func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPa RepoID: t.RepoID, OwnerID: t.OwnerID, CommitSHA: t.CommitSHA, - Status: int64(ArtifactStatusUploadPending), + Status: ArtifactStatusUploadPending, ExpiredUnix: timeutil.TimeStamp(time.Now().Unix() + timeutil.Day*expiredDays), } if _, err := db.GetEngine(ctx).Insert(artifact); err != nil { @@ -108,12 +108,19 @@ func UpdateArtifactByID(ctx context.Context, id int64, art *ActionArtifact) erro type FindArtifactsOptions struct { db.ListOptions - RepoID int64 - RunID int64 - ArtifactName string - Status int + RepoID int64 + RunID int64 + ArtifactName string + Status int + FinalizedArtifactsV4 bool } +func (opts FindArtifactsOptions) ToOrders() string { + return "id" +} + +var _ db.FindOptionsOrder = (*FindArtifactsOptions)(nil) + func (opts FindArtifactsOptions) ToConds() builder.Cond { cond := builder.NewCond() if opts.RepoID > 0 { @@ -128,11 +135,15 @@ func (opts FindArtifactsOptions) ToConds() builder.Cond { if opts.Status > 0 { cond = cond.And(builder.Eq{"status": opts.Status}) } + if opts.FinalizedArtifactsV4 { + cond = cond.And(builder.Eq{"status": ArtifactStatusUploadConfirmed}.Or(builder.Eq{"status": ArtifactStatusExpired})) + cond = cond.And(builder.Eq{"content_encoding": "application/zip"}) + } return cond } -// ActionArtifactMeta is the meta data of an artifact +// ActionArtifactMeta is the meta-data of an artifact type ActionArtifactMeta struct { ArtifactName string FileSize int64 @@ -166,18 +177,18 @@ func ListPendingDeleteArtifacts(ctx context.Context, limit int) ([]*ActionArtifa // SetArtifactExpired sets an artifact to expired func SetArtifactExpired(ctx context.Context, artifactID int64) error { - _, err := db.GetEngine(ctx).Where("id=? AND status = ?", artifactID, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusExpired)}) + _, err := db.GetEngine(ctx).Where("id=? AND status = ?", artifactID, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: ArtifactStatusExpired}) return err } // SetArtifactNeedDelete sets an artifact to need-delete, cron job will delete it func SetArtifactNeedDelete(ctx context.Context, runID int64, name string) error { - _, err := db.GetEngine(ctx).Where("run_id=? AND artifact_name=? AND status = ?", runID, name, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusPendingDeletion)}) + _, err := db.GetEngine(ctx).Where("run_id=? AND artifact_name=? AND status = ?", runID, name, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: ArtifactStatusPendingDeletion}) return err } // SetArtifactDeleted sets an artifact to deleted func SetArtifactDeleted(ctx context.Context, artifactID int64) error { - _, err := db.GetEngine(ctx).ID(artifactID).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusDeleted)}) + _, err := db.GetEngine(ctx).ID(artifactID).Cols("status").Update(&ActionArtifact{Status: ArtifactStatusDeleted}) return err } diff --git a/models/actions/run_list.go b/models/actions/run_list.go index 4046c7d369..b9b9324e07 100644 --- a/models/actions/run_list.go +++ b/models/actions/run_list.go @@ -10,6 +10,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/translation" webhook_module "code.gitea.io/gitea/modules/webhook" "xorm.io/builder" @@ -112,14 +113,14 @@ type StatusInfo struct { } // GetStatusInfoList returns a slice of StatusInfo -func GetStatusInfoList(ctx context.Context) []StatusInfo { +func GetStatusInfoList(ctx context.Context, lang translation.Locale) []StatusInfo { // same as those in aggregateJobStatus allStatus := []Status{StatusSuccess, StatusFailure, StatusWaiting, StatusRunning} statusInfoList := make([]StatusInfo, 0, 4) for _, s := range allStatus { statusInfoList = append(statusInfoList, StatusInfo{ Status: int(s), - DisplayedStatus: s.String(), + DisplayedStatus: s.LocaleString(lang), }) } return statusInfoList diff --git a/models/actions/runner.go b/models/actions/runner.go index 0d5464a5be..798a647180 100644 --- a/models/actions/runner.go +++ b/models/actions/runner.go @@ -167,6 +167,7 @@ func init() { type FindRunnerOptions struct { db.ListOptions + IDs []int64 RepoID int64 OwnerID int64 // it will be ignored if RepoID is set Sort string @@ -178,6 +179,14 @@ type FindRunnerOptions struct { func (opts FindRunnerOptions) ToConds() builder.Cond { cond := builder.NewCond() + if len(opts.IDs) > 0 { + if len(opts.IDs) == 1 { + cond = cond.And(builder.Eq{"id": opts.IDs[0]}) + } else { + cond = cond.And(builder.In("id", opts.IDs)) + } + } + if opts.RepoID > 0 { c := builder.NewCond().And(builder.Eq{"repo_id": opts.RepoID}) if opts.WithAvailable { diff --git a/models/actions/variable.go b/models/actions/variable.go index d0f917d923..163bb12c93 100644 --- a/models/actions/variable.go +++ b/models/actions/variable.go @@ -58,6 +58,7 @@ func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data strin type FindVariablesOpts struct { db.ListOptions + IDs []int64 RepoID int64 OwnerID int64 // it will be ignored if RepoID is set Name string @@ -65,6 +66,15 @@ type FindVariablesOpts struct { func (opts FindVariablesOpts) ToConds() builder.Cond { cond := builder.NewCond() + + if len(opts.IDs) > 0 { + if len(opts.IDs) == 1 { + cond = cond.And(builder.Eq{"id": opts.IDs[0]}) + } else { + cond = cond.And(builder.In("id", opts.IDs)) + } + } + // Since we now support instance-level variables, // there is no need to check for null values for `owner_id` and `repo_id` cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) @@ -85,12 +95,12 @@ func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*ActionVariab return db.Find[ActionVariable](ctx, opts) } -func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) { - count, err := db.GetEngine(ctx).ID(variable.ID).Cols("name", "data"). - Update(&ActionVariable{ - Name: variable.Name, - Data: variable.Data, - }) +func UpdateVariableCols(ctx context.Context, variable *ActionVariable, cols ...string) (bool, error) { + variable.Name = strings.ToUpper(variable.Name) + count, err := db.GetEngine(ctx). + ID(variable.ID). + Cols(cols...). + Update(variable) return count != 0, err } diff --git a/models/asymkey/gpg_key.go b/models/asymkey/gpg_key.go index e921340730..7f35a96a59 100644 --- a/models/asymkey/gpg_key.go +++ b/models/asymkey/gpg_key.go @@ -106,7 +106,7 @@ func GPGKeyToEntity(ctx context.Context, k *GPGKey) (*openpgp.Entity, error) { if err != nil { return nil, err } - keys, err := checkArmoredGPGKeyString(impKey.Content) + keys, err := CheckArmoredGPGKeyString(impKey.Content) if err != nil { return nil, err } @@ -115,7 +115,7 @@ func GPGKeyToEntity(ctx context.Context, k *GPGKey) (*openpgp.Entity, error) { // parseSubGPGKey parse a sub Key func parseSubGPGKey(ownerID int64, primaryID string, pubkey *packet.PublicKey, expiry time.Time) (*GPGKey, error) { - content, err := base64EncPubKey(pubkey) + content, err := Base64EncPubKey(pubkey) if err != nil { return nil, err } @@ -183,7 +183,7 @@ func parseGPGKey(ctx context.Context, ownerID int64, e *openpgp.Entity, verified } } - content, err := base64EncPubKey(pubkey) + content, err := Base64EncPubKey(pubkey) if err != nil { return nil, err } @@ -239,33 +239,3 @@ func DeleteGPGKey(ctx context.Context, doer *user_model.User, id int64) (err err return committer.Commit() } - -func checkKeyEmails(ctx context.Context, email string, keys ...*GPGKey) (bool, string) { - uid := int64(0) - var userEmails []*user_model.EmailAddress - var user *user_model.User - for _, key := range keys { - for _, e := range key.Emails { - if e.IsActivated && (email == "" || strings.EqualFold(e.Email, email)) { - return true, e.Email - } - } - if key.Verified && key.OwnerID != 0 { - if uid != key.OwnerID { - userEmails, _ = user_model.GetEmailAddresses(ctx, key.OwnerID) - uid = key.OwnerID - user = &user_model.User{ID: uid} - _, _ = user_model.GetUser(ctx, user) - } - for _, e := range userEmails { - if e.IsActivated && (email == "" || strings.EqualFold(e.Email, email)) { - return true, e.Email - } - } - if user.KeepEmailPrivate && strings.EqualFold(email, user.GetEmail()) { - return true, user.GetEmail() - } - } - } - return false, email -} diff --git a/models/asymkey/gpg_key_add.go b/models/asymkey/gpg_key_add.go index 6c0f6e01a7..ec2031088a 100644 --- a/models/asymkey/gpg_key_add.go +++ b/models/asymkey/gpg_key_add.go @@ -67,7 +67,7 @@ func addGPGSubKey(ctx context.Context, key *GPGKey) (err error) { // AddGPGKey adds new public key to database. func AddGPGKey(ctx context.Context, ownerID int64, content, token, signature string) ([]*GPGKey, error) { - ekeys, err := checkArmoredGPGKeyString(content) + ekeys, err := CheckArmoredGPGKeyString(content) if err != nil { return nil, err } diff --git a/models/asymkey/gpg_key_commit_verification.go b/models/asymkey/gpg_key_commit_verification.go index 9219a509df..1591cd5068 100644 --- a/models/asymkey/gpg_key_commit_verification.go +++ b/models/asymkey/gpg_key_commit_verification.go @@ -4,17 +4,12 @@ package asymkey import ( - "context" "fmt" "hash" - "strings" - "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/log" - "code.gitea.io/gitea/modules/setting" "github.com/ProtonMail/go-crypto/openpgp/packet" ) @@ -70,263 +65,6 @@ const ( NoKeyFound = "gpg.error.no_gpg_keys_found" ) -// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys. -func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error)) []*SignCommit { - newCommits := make([]*SignCommit, 0, len(oldCommits)) - keyMap := map[string]bool{} - - for _, c := range oldCommits { - signCommit := &SignCommit{ - UserCommit: c, - Verification: ParseCommitWithSignature(ctx, c.Commit), - } - - _ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap) - - newCommits = append(newCommits, signCommit) - } - return newCommits -} - -// ParseCommitWithSignature check if signature is good against keystore. -func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerification { - var committer *user_model.User - if c.Committer != nil { - var err error - // Find Committer account - committer, err = user_model.GetUserByEmail(ctx, c.Committer.Email) // This finds the user by primary email or activated email so commit will not be valid if email is not - if err != nil { // Skipping not user for committer - committer = &user_model.User{ - Name: c.Committer.Name, - Email: c.Committer.Email, - } - // We can expect this to often be an ErrUserNotExist. in the case - // it is not, however, it is important to log it. - if !user_model.IsErrUserNotExist(err) { - log.Error("GetUserByEmail: %v", err) - return &CommitVerification{ - CommittingUser: committer, - Verified: false, - Reason: "gpg.error.no_committer_account", - } - } - } - } - - // If no signature just report the committer - if c.Signature == nil { - return &CommitVerification{ - CommittingUser: committer, - Verified: false, // Default value - Reason: "gpg.error.not_signed_commit", // Default value - } - } - - // If this a SSH signature handle it differently - if strings.HasPrefix(c.Signature.Signature, "-----BEGIN SSH SIGNATURE-----") { - return ParseCommitWithSSHSignature(ctx, c, committer) - } - - // Parsing signature - sig, err := extractSignature(c.Signature.Signature) - if err != nil { // Skipping failed to extract sign - log.Error("SignatureRead err: %v", err) - return &CommitVerification{ - CommittingUser: committer, - Verified: false, - Reason: "gpg.error.extract_sign", - } - } - - keyID := tryGetKeyIDFromSignature(sig) - defaultReason := NoKeyFound - - // First check if the sig has a keyID and if so just look at that - if commitVerification := hashAndVerifyForKeyID( - ctx, - sig, - c.Signature.Payload, - committer, - keyID, - setting.AppName, - ""); commitVerification != nil { - if commitVerification.Reason == BadSignature { - defaultReason = BadSignature - } else { - return commitVerification - } - } - - // Now try to associate the signature with the committer, if present - if committer.ID != 0 { - keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{ - OwnerID: committer.ID, - }) - if err != nil { // Skipping failed to get gpg keys of user - log.Error("ListGPGKeys: %v", err) - return &CommitVerification{ - CommittingUser: committer, - Verified: false, - Reason: "gpg.error.failed_retrieval_gpg_keys", - } - } - - if err := GPGKeyList(keys).LoadSubKeys(ctx); err != nil { - log.Error("LoadSubKeys: %v", err) - return &CommitVerification{ - CommittingUser: committer, - Verified: false, - Reason: "gpg.error.failed_retrieval_gpg_keys", - } - } - - committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID) - activated := false - for _, e := range committerEmailAddresses { - if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) { - activated = true - break - } - } - - for _, k := range keys { - // Pre-check (& optimization) that emails attached to key can be attached to the committer email and can validate - canValidate := false - email := "" - if k.Verified && activated { - canValidate = true - email = c.Committer.Email - } - if !canValidate { - for _, e := range k.Emails { - if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) { - canValidate = true - email = e.Email - break - } - } - } - if !canValidate { - continue // Skip this key - } - - commitVerification := hashAndVerifyWithSubKeysCommitVerification(sig, c.Signature.Payload, k, committer, committer, email) - if commitVerification != nil { - return commitVerification - } - } - } - - if setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" { - // OK we should try the default key - gpgSettings := git.GPGSettings{ - Sign: true, - KeyID: setting.Repository.Signing.SigningKey, - Name: setting.Repository.Signing.SigningName, - Email: setting.Repository.Signing.SigningEmail, - } - if err := gpgSettings.LoadPublicKeyContent(); err != nil { - log.Error("Error getting default signing key: %s %v", gpgSettings.KeyID, err) - } else if commitVerification := verifyWithGPGSettings(ctx, &gpgSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil { - if commitVerification.Reason == BadSignature { - defaultReason = BadSignature - } else { - return commitVerification - } - } - } - - defaultGPGSettings, err := c.GetRepositoryDefaultPublicGPGKey(false) - if err != nil { - log.Error("Error getting default public gpg key: %v", err) - } else if defaultGPGSettings == nil { - log.Warn("Unable to get defaultGPGSettings for unattached commit: %s", c.ID.String()) - } else if defaultGPGSettings.Sign { - if commitVerification := verifyWithGPGSettings(ctx, defaultGPGSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil { - if commitVerification.Reason == BadSignature { - defaultReason = BadSignature - } else { - return commitVerification - } - } - } - - return &CommitVerification{ // Default at this stage - CommittingUser: committer, - Verified: false, - Warning: defaultReason != NoKeyFound, - Reason: defaultReason, - SigningKey: &GPGKey{ - KeyID: keyID, - }, - } -} - -func verifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, sig *packet.Signature, payload string, committer *user_model.User, keyID string) *CommitVerification { - // First try to find the key in the db - if commitVerification := hashAndVerifyForKeyID(ctx, sig, payload, committer, gpgSettings.KeyID, gpgSettings.Name, gpgSettings.Email); commitVerification != nil { - return commitVerification - } - - // Otherwise we have to parse the key - ekeys, err := checkArmoredGPGKeyString(gpgSettings.PublicKeyContent) - if err != nil { - log.Error("Unable to get default signing key: %v", err) - return &CommitVerification{ - CommittingUser: committer, - Verified: false, - Reason: "gpg.error.generate_hash", - } - } - for _, ekey := range ekeys { - pubkey := ekey.PrimaryKey - content, err := base64EncPubKey(pubkey) - if err != nil { - return &CommitVerification{ - CommittingUser: committer, - Verified: false, - Reason: "gpg.error.generate_hash", - } - } - k := &GPGKey{ - Content: content, - CanSign: pubkey.CanSign(), - KeyID: pubkey.KeyIdString(), - } - for _, subKey := range ekey.Subkeys { - content, err := base64EncPubKey(subKey.PublicKey) - if err != nil { - return &CommitVerification{ - CommittingUser: committer, - Verified: false, - Reason: "gpg.error.generate_hash", - } - } - k.SubsKey = append(k.SubsKey, &GPGKey{ - Content: content, - CanSign: subKey.PublicKey.CanSign(), - KeyID: subKey.PublicKey.KeyIdString(), - }) - } - if commitVerification := hashAndVerifyWithSubKeysCommitVerification(sig, payload, k, committer, &user_model.User{ - Name: gpgSettings.Name, - Email: gpgSettings.Email, - }, gpgSettings.Email); commitVerification != nil { - return commitVerification - } - if keyID == k.KeyID { - // This is a bad situation ... We have a key id that matches our default key but the signature doesn't match. - return &CommitVerification{ - CommittingUser: committer, - Verified: false, - Warning: true, - Reason: BadSignature, - } - } - } - return nil -} - func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error { // Check if key can sign if !k.CanSign { @@ -369,7 +107,7 @@ func hashAndVerifyWithSubKeys(sig *packet.Signature, payload string, k *GPGKey) return nil, nil } -func hashAndVerifyWithSubKeysCommitVerification(sig *packet.Signature, payload string, k *GPGKey, committer, signer *user_model.User, email string) *CommitVerification { +func HashAndVerifyWithSubKeysCommitVerification(sig *packet.Signature, payload string, k *GPGKey, committer, signer *user_model.User, email string) *CommitVerification { key, err := hashAndVerifyWithSubKeys(sig, payload, k) if err != nil { // Skipping failed to generate hash return &CommitVerification{ @@ -392,78 +130,6 @@ func hashAndVerifyWithSubKeysCommitVerification(sig *packet.Signature, payload s return nil } -func hashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload string, committer *user_model.User, keyID, name, email string) *CommitVerification { - if keyID == "" { - return nil - } - keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{ - KeyID: keyID, - IncludeSubKeys: true, - }) - if err != nil { - log.Error("GetGPGKeysByKeyID: %v", err) - return &CommitVerification{ - CommittingUser: committer, - Verified: false, - Reason: "gpg.error.failed_retrieval_gpg_keys", - } - } - if len(keys) == 0 { - return nil - } - for _, key := range keys { - var primaryKeys []*GPGKey - if key.PrimaryKeyID != "" { - primaryKeys, err = db.Find[GPGKey](ctx, FindGPGKeyOptions{ - KeyID: key.PrimaryKeyID, - IncludeSubKeys: true, - }) - if err != nil { - log.Error("GetGPGKeysByKeyID: %v", err) - return &CommitVerification{ - CommittingUser: committer, - Verified: false, - Reason: "gpg.error.failed_retrieval_gpg_keys", - } - } - } - - activated, email := checkKeyEmails(ctx, email, append([]*GPGKey{key}, primaryKeys...)...) - if !activated { - continue - } - - signer := &user_model.User{ - Name: name, - Email: email, - } - if key.OwnerID != 0 { - owner, err := user_model.GetUserByID(ctx, key.OwnerID) - if err == nil { - signer = owner - } else if !user_model.IsErrUserNotExist(err) { - log.Error("Failed to user_model.GetUserByID: %d for key ID: %d (%s) %v", key.OwnerID, key.ID, key.KeyID, err) - return &CommitVerification{ - CommittingUser: committer, - Verified: false, - Reason: "gpg.error.no_committer_account", - } - } - } - commitVerification := hashAndVerifyWithSubKeysCommitVerification(sig, payload, key, committer, signer, email) - if commitVerification != nil { - return commitVerification - } - } - // This is a bad situation ... We have a key id that is in our database but the signature doesn't match. - return &CommitVerification{ - CommittingUser: committer, - Verified: false, - Warning: true, - Reason: BadSignature, - } -} - // CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository // There are several trust models in Gitea func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error), keyMap *map[string]bool) error { diff --git a/models/asymkey/gpg_key_common.go b/models/asymkey/gpg_key_common.go index 92c34a2569..1291cbc542 100644 --- a/models/asymkey/gpg_key_common.go +++ b/models/asymkey/gpg_key_common.go @@ -33,9 +33,9 @@ import ( // This file provides common functions relating to GPG Keys -// checkArmoredGPGKeyString checks if the given key string is a valid GPG armored key. +// CheckArmoredGPGKeyString checks if the given key string is a valid GPG armored key. // The function returns the actual public key on success -func checkArmoredGPGKeyString(content string) (openpgp.EntityList, error) { +func CheckArmoredGPGKeyString(content string) (openpgp.EntityList, error) { list, err := openpgp.ReadArmoredKeyRing(strings.NewReader(content)) if err != nil { return nil, ErrGPGKeyParsing{err} @@ -43,8 +43,8 @@ func checkArmoredGPGKeyString(content string) (openpgp.EntityList, error) { return list, nil } -// base64EncPubKey encode public key content to base 64 -func base64EncPubKey(pubkey *packet.PublicKey) (string, error) { +// Base64EncPubKey encode public key content to base 64 +func Base64EncPubKey(pubkey *packet.PublicKey) (string, error) { var w bytes.Buffer err := pubkey.Serialize(&w) if err != nil { @@ -119,7 +119,7 @@ func readArmoredSign(r io.Reader) (body io.Reader, err error) { return block.Body, nil } -func extractSignature(s string) (*packet.Signature, error) { +func ExtractSignature(s string) (*packet.Signature, error) { r, err := readArmoredSign(strings.NewReader(s)) if err != nil { return nil, fmt.Errorf("Failed to read signature armor") @@ -135,7 +135,7 @@ func extractSignature(s string) (*packet.Signature, error) { return sig, nil } -func tryGetKeyIDFromSignature(sig *packet.Signature) string { +func TryGetKeyIDFromSignature(sig *packet.Signature) string { if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 { return fmt.Sprintf("%016X", *sig.IssuerKeyId) } diff --git a/models/asymkey/gpg_key_test.go b/models/asymkey/gpg_key_test.go index d7f0ff5364..408cf15763 100644 --- a/models/asymkey/gpg_key_test.go +++ b/models/asymkey/gpg_key_test.go @@ -51,7 +51,7 @@ MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg== =i9b7 -----END PGP PUBLIC KEY BLOCK-----` - key, err := checkArmoredGPGKeyString(testGPGArmor) + key, err := CheckArmoredGPGKeyString(testGPGArmor) assert.NoError(t, err, "Could not parse a valid GPG public armored rsa key", key) // TODO verify value of key } @@ -72,7 +72,7 @@ OyjLLnFQiVmq7kEA/0z0CQe3ZQiQIq5zrs7Nh1XRkFAo8GlU/SGC9XFFi722 =ZiSe -----END PGP PUBLIC KEY BLOCK-----` - key, err := checkArmoredGPGKeyString(testGPGArmor) + key, err := CheckArmoredGPGKeyString(testGPGArmor) assert.NoError(t, err, "Could not parse a valid GPG public armored brainpoolP256r1 key", key) // TODO verify value of key } @@ -108,14 +108,14 @@ Av844q/BfRuVsJsK1NDNG09LC30B0l3LKBqlrRmRTUMHtgchdX2dY+p7GPOoSzlR MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg== =i9b7 -----END PGP PUBLIC KEY BLOCK-----` - keys, err := checkArmoredGPGKeyString(testGPGArmor) + keys, err := CheckArmoredGPGKeyString(testGPGArmor) require.NotEmpty(t, keys) ekey := keys[0] assert.NoError(t, err, "Could not parse a valid GPG armored key", ekey) pubkey := ekey.PrimaryKey - content, err := base64EncPubKey(pubkey) + content, err := Base64EncPubKey(pubkey) assert.NoError(t, err, "Could not base64 encode a valid PublicKey content", ekey) key := &GPGKey{ @@ -176,9 +176,9 @@ committer Antoine GIRARD 1489013107 +0100 Unknown GPG key with good email ` // Reading Sign - goodSig, err := extractSignature(testGoodSigArmor) + goodSig, err := ExtractSignature(testGoodSigArmor) assert.NoError(t, err, "Could not parse a valid GPG armored signature", testGoodSigArmor) - badSig, err := extractSignature(testBadSigArmor) + badSig, err := ExtractSignature(testBadSigArmor) assert.NoError(t, err, "Could not parse a valid GPG armored signature", testBadSigArmor) // Generating hash of commit @@ -386,7 +386,7 @@ epiDVQ== =VSKJ -----END PGP PUBLIC KEY BLOCK----- ` - keys, err := checkArmoredGPGKeyString(testIssue6599) + keys, err := CheckArmoredGPGKeyString(testIssue6599) assert.NoError(t, err) if assert.NotEmpty(t, keys) { ekey := keys[0] @@ -396,11 +396,11 @@ epiDVQ== } func TestTryGetKeyIDFromSignature(t *testing.T) { - assert.Empty(t, tryGetKeyIDFromSignature(&packet.Signature{})) - assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{ + assert.Empty(t, TryGetKeyIDFromSignature(&packet.Signature{})) + assert.Equal(t, "038D1A3EADDBEA9C", TryGetKeyIDFromSignature(&packet.Signature{ IssuerKeyId: util.ToPointer(uint64(0x38D1A3EADDBEA9C)), })) - assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{ + assert.Equal(t, "038D1A3EADDBEA9C", TryGetKeyIDFromSignature(&packet.Signature{ IssuerFingerprint: []uint8{0xb, 0x23, 0x24, 0xc7, 0xe6, 0xfe, 0x4f, 0x3a, 0x6, 0x26, 0xc1, 0x21, 0x3, 0x8d, 0x1a, 0x3e, 0xad, 0xdb, 0xea, 0x9c}, })) } diff --git a/models/asymkey/gpg_key_verify.go b/models/asymkey/gpg_key_verify.go index 01812a2d54..6eedb5b7ba 100644 --- a/models/asymkey/gpg_key_verify.go +++ b/models/asymkey/gpg_key_verify.go @@ -50,7 +50,7 @@ func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature st return "", err } - sig, err := extractSignature(signature) + sig, err := ExtractSignature(signature) if err != nil { return "", ErrGPGInvalidTokenSignature{ ID: key.KeyID, diff --git a/models/fixtures/action_artifact.yml b/models/fixtures/action_artifact.yml index 2c51c11ebd..485474108f 100644 --- a/models/fixtures/action_artifact.yml +++ b/models/fixtures/action_artifact.yml @@ -69,3 +69,21 @@ created_unix: 1730330775 updated_unix: 1730330775 expired_unix: 1738106775 + +- + id: 23 + run_id: 793 + runner_id: 1 + repo_id: 2 + owner_id: 2 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 + storage_path: "27/5/1730330775594233150.chunk" + file_size: 1024 + file_compressed_size: 1024 + content_encoding: "application/zip" + artifact_path: "artifact-v4-download.zip" + artifact_name: "artifact-v4-download" + status: 2 + created_unix: 1730330775 + updated_unix: 1730330775 + expired_unix: 1738106775 diff --git a/models/fixtures/issue_pin.yml b/models/fixtures/issue_pin.yml new file mode 100644 index 0000000000..14b7a72d84 --- /dev/null +++ b/models/fixtures/issue_pin.yml @@ -0,0 +1,6 @@ +- + id: 1 + repo_id: 2 + issue_id: 4 + is_pull: false + pin_order: 1 diff --git a/models/git/commit_status.go b/models/git/commit_status.go index 0579a41209..5432eea55e 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -496,47 +496,11 @@ type SignCommitWithStatuses struct { *asymkey_model.SignCommit } -// ParseCommitsWithStatus checks commits latest statuses and calculates its worst status state -func ParseCommitsWithStatus(ctx context.Context, oldCommits []*asymkey_model.SignCommit, repo *repo_model.Repository) []*SignCommitWithStatuses { - newCommits := make([]*SignCommitWithStatuses, 0, len(oldCommits)) - - for _, c := range oldCommits { - commit := &SignCommitWithStatuses{ - SignCommit: c, - } - statuses, _, err := GetLatestCommitStatus(ctx, repo.ID, commit.ID.String(), db.ListOptions{}) - if err != nil { - log.Error("GetLatestCommitStatus: %v", err) - } else { - commit.Statuses = statuses - commit.Status = CalcCommitStatus(statuses) - } - - newCommits = append(newCommits, commit) - } - return newCommits -} - // hashCommitStatusContext hash context func hashCommitStatusContext(context string) string { return fmt.Sprintf("%x", sha1.Sum([]byte(context))) } -// ConvertFromGitCommit converts git commits into SignCommitWithStatuses -func ConvertFromGitCommit(ctx context.Context, commits []*git.Commit, repo *repo_model.Repository) []*SignCommitWithStatuses { - return ParseCommitsWithStatus(ctx, - asymkey_model.ParseCommitsWithSignature( - ctx, - user_model.ValidateCommitsWithEmails(ctx, commits), - repo.GetTrustModel(), - func(user *user_model.User) (bool, error) { - return repo_model.IsOwnerMemberCollaborator(ctx, repo, user.ID) - }, - ), - repo, - ) -} - // CommitStatusesHideActionsURL hide Gitea Actions urls func CommitStatusesHideActionsURL(ctx context.Context, statuses []*CommitStatus) { idToRepos := make(map[int64]*repo_model.Repository) diff --git a/models/issues/comment.go b/models/issues/comment.go index a248708820..ab9b2042f3 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -19,8 +19,6 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/gitrepo" - "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/references" @@ -774,41 +772,6 @@ func (c *Comment) CodeCommentLink(ctx context.Context) string { return fmt.Sprintf("%s/files#%s", c.Issue.Link(), c.HashTag()) } -// LoadPushCommits Load push commits -func (c *Comment) LoadPushCommits(ctx context.Context) (err error) { - if c.Content == "" || c.Commits != nil || c.Type != CommentTypePullRequestPush { - return nil - } - - var data PushActionContent - - err = json.Unmarshal([]byte(c.Content), &data) - if err != nil { - return err - } - - c.IsForcePush = data.IsForcePush - - if c.IsForcePush { - if len(data.CommitIDs) != 2 { - return nil - } - c.OldCommit = data.CommitIDs[0] - c.NewCommit = data.CommitIDs[1] - } else { - gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, c.Issue.Repo) - if err != nil { - return err - } - defer closer.Close() - - c.Commits = git_model.ConvertFromGitCommit(ctx, gitRepo.GetCommitsFromIDs(data.CommitIDs), c.Issue.Repo) - c.CommitsNum = int64(len(c.Commits)) - } - - return err -} - // CreateComment creates comment with context func CreateComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment, err error) { ctx, committer, err := db.TxContext(ctx) diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go index 67a77ceb13..b562aab500 100644 --- a/models/issues/comment_code.go +++ b/models/issues/comment_code.go @@ -86,8 +86,10 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu ids = append(ids, comment.ReviewID) } } - if err := e.In("id", ids).Find(&reviews); err != nil { - return nil, err + if len(ids) > 0 { + if err := e.In("id", ids).Find(&reviews); err != nil { + return nil, err + } } n := 0 diff --git a/models/issues/issue.go b/models/issues/issue.go index 564a9fb835..7e72bb776c 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -17,6 +17,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" @@ -96,7 +97,7 @@ type Issue struct { // TODO: RemoveIssueRef: see "repo/issue/branch_selector_field.tmpl" Ref string - PinOrder int `xorm:"DEFAULT 0"` + PinOrder int `xorm:"-"` // 0 means not loaded, -1 means loaded but not pinned DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"` @@ -290,6 +291,23 @@ func (issue *Issue) LoadMilestone(ctx context.Context) (err error) { return nil } +func (issue *Issue) LoadPinOrder(ctx context.Context) error { + if issue.PinOrder != 0 { + return nil + } + issuePin, err := GetIssuePin(ctx, issue) + if err != nil && !db.IsErrNotExist(err) { + return err + } + + if issuePin != nil { + issue.PinOrder = issuePin.PinOrder + } else { + issue.PinOrder = -1 + } + return nil +} + // LoadAttributes loads the attribute of this issue. func (issue *Issue) LoadAttributes(ctx context.Context) (err error) { if err = issue.LoadRepo(ctx); err != nil { @@ -329,6 +347,10 @@ func (issue *Issue) LoadAttributes(ctx context.Context) (err error) { return err } + if err = issue.LoadPinOrder(ctx); err != nil { + return err + } + if err = issue.Comments.LoadAttributes(ctx); err != nil { return err } @@ -341,6 +363,14 @@ func (issue *Issue) LoadAttributes(ctx context.Context) (err error) { return issue.loadReactions(ctx) } +// IsPinned returns if a Issue is pinned +func (issue *Issue) IsPinned() bool { + if issue.PinOrder == 0 { + setting.PanicInDevOrTesting("issue's pinorder has not been loaded") + } + return issue.PinOrder > 0 +} + func (issue *Issue) ResetAttributesLoaded() { issue.isLabelsLoaded = false issue.isMilestoneLoaded = false @@ -501,6 +531,45 @@ func GetIssueByIndex(ctx context.Context, repoID, index int64) (*Issue, error) { return issue, nil } +func isPullToCond(isPull optional.Option[bool]) builder.Cond { + if isPull.Has() { + return builder.Eq{"is_pull": isPull.Value()} + } + return builder.NewCond() +} + +func FindLatestUpdatedIssues(ctx context.Context, repoID int64, isPull optional.Option[bool], pageSize int) (IssueList, error) { + issues := make([]*Issue, 0, pageSize) + err := db.GetEngine(ctx).Where("repo_id = ?", repoID). + And(isPullToCond(isPull)). + OrderBy("updated_unix DESC"). + Limit(pageSize). + Find(&issues) + return issues, err +} + +func FindIssuesSuggestionByKeyword(ctx context.Context, repoID int64, keyword string, isPull optional.Option[bool], excludedID int64, pageSize int) (IssueList, error) { + cond := builder.NewCond() + if excludedID > 0 { + cond = cond.And(builder.Neq{"`id`": excludedID}) + } + + // It seems that GitHub searches both title and content (maybe sorting by the search engine's ranking system?) + // The first PR (https://github.com/go-gitea/gitea/pull/32327) uses "search indexer" to search "name(title) + content" + // But it seems that searching "content" (especially LIKE by DB engine) generates worse (unusable) results. + // So now (https://github.com/go-gitea/gitea/pull/33538) it only searches "name(title)", leave the improvements to the future. + cond = cond.And(db.BuildCaseInsensitiveLike("`name`", keyword)) + + issues := make([]*Issue, 0, pageSize) + err := db.GetEngine(ctx).Where("repo_id = ?", repoID). + And(isPullToCond(isPull)). + And(cond). + OrderBy("updated_unix DESC, `index` DESC"). + Limit(pageSize). + Find(&issues) + return issues, err +} + // GetIssueWithAttrsByIndex returns issue by index in a repository. func GetIssueWithAttrsByIndex(ctx context.Context, repoID, index int64) (*Issue, error) { issue, err := GetIssueByIndex(ctx, repoID, index) @@ -680,190 +749,6 @@ func (issue *Issue) HasOriginalAuthor() bool { return issue.OriginalAuthor != "" && issue.OriginalAuthorID != 0 } -var ErrIssueMaxPinReached = util.NewInvalidArgumentErrorf("the max number of pinned issues has been readched") - -// IsPinned returns if a Issue is pinned -func (issue *Issue) IsPinned() bool { - return issue.PinOrder != 0 -} - -// Pin pins a Issue -func (issue *Issue) Pin(ctx context.Context, user *user_model.User) error { - // If the Issue is already pinned, we don't need to pin it twice - if issue.IsPinned() { - return nil - } - - var maxPin int - _, err := db.GetEngine(ctx).SQL("SELECT MAX(pin_order) FROM issue WHERE repo_id = ? AND is_pull = ?", issue.RepoID, issue.IsPull).Get(&maxPin) - if err != nil { - return err - } - - // Check if the maximum allowed Pins reached - if maxPin >= setting.Repository.Issue.MaxPinned { - return ErrIssueMaxPinReached - } - - _, err = db.GetEngine(ctx).Table("issue"). - Where("id = ?", issue.ID). - Update(map[string]any{ - "pin_order": maxPin + 1, - }) - if err != nil { - return err - } - - // Add the pin event to the history - opts := &CreateCommentOptions{ - Type: CommentTypePin, - Doer: user, - Repo: issue.Repo, - Issue: issue, - } - if _, err = CreateComment(ctx, opts); err != nil { - return err - } - - return nil -} - -// UnpinIssue unpins a Issue -func (issue *Issue) Unpin(ctx context.Context, user *user_model.User) error { - // If the Issue is not pinned, we don't need to unpin it - if !issue.IsPinned() { - return nil - } - - // This sets the Pin for all Issues that come after the unpined Issue to the correct value - _, err := db.GetEngine(ctx).Exec("UPDATE issue SET pin_order = pin_order - 1 WHERE repo_id = ? AND is_pull = ? AND pin_order > ?", issue.RepoID, issue.IsPull, issue.PinOrder) - if err != nil { - return err - } - - _, err = db.GetEngine(ctx).Table("issue"). - Where("id = ?", issue.ID). - Update(map[string]any{ - "pin_order": 0, - }) - if err != nil { - return err - } - - // Add the unpin event to the history - opts := &CreateCommentOptions{ - Type: CommentTypeUnpin, - Doer: user, - Repo: issue.Repo, - Issue: issue, - } - if _, err = CreateComment(ctx, opts); err != nil { - return err - } - - return nil -} - -// PinOrUnpin pins or unpins a Issue -func (issue *Issue) PinOrUnpin(ctx context.Context, user *user_model.User) error { - if !issue.IsPinned() { - return issue.Pin(ctx, user) - } - - return issue.Unpin(ctx, user) -} - -// MovePin moves a Pinned Issue to a new Position -func (issue *Issue) MovePin(ctx context.Context, newPosition int) error { - // If the Issue is not pinned, we can't move them - if !issue.IsPinned() { - return nil - } - - if newPosition < 1 { - return fmt.Errorf("The Position can't be lower than 1") - } - - dbctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - var maxPin int - _, err = db.GetEngine(dbctx).SQL("SELECT MAX(pin_order) FROM issue WHERE repo_id = ? AND is_pull = ?", issue.RepoID, issue.IsPull).Get(&maxPin) - if err != nil { - return err - } - - // If the new Position bigger than the current Maximum, set it to the Maximum - if newPosition > maxPin+1 { - newPosition = maxPin + 1 - } - - // Lower the Position of all Pinned Issue that came after the current Position - _, err = db.GetEngine(dbctx).Exec("UPDATE issue SET pin_order = pin_order - 1 WHERE repo_id = ? AND is_pull = ? AND pin_order > ?", issue.RepoID, issue.IsPull, issue.PinOrder) - if err != nil { - return err - } - - // Higher the Position of all Pinned Issues that comes after the new Position - _, err = db.GetEngine(dbctx).Exec("UPDATE issue SET pin_order = pin_order + 1 WHERE repo_id = ? AND is_pull = ? AND pin_order >= ?", issue.RepoID, issue.IsPull, newPosition) - if err != nil { - return err - } - - _, err = db.GetEngine(dbctx).Table("issue"). - Where("id = ?", issue.ID). - Update(map[string]any{ - "pin_order": newPosition, - }) - if err != nil { - return err - } - - return committer.Commit() -} - -// GetPinnedIssues returns the pinned Issues for the given Repo and type -func GetPinnedIssues(ctx context.Context, repoID int64, isPull bool) (IssueList, error) { - issues := make(IssueList, 0) - - err := db.GetEngine(ctx). - Table("issue"). - Where("repo_id = ?", repoID). - And("is_pull = ?", isPull). - And("pin_order > 0"). - OrderBy("pin_order"). - Find(&issues) - if err != nil { - return nil, err - } - - err = issues.LoadAttributes(ctx) - if err != nil { - return nil, err - } - - return issues, nil -} - -// IsNewPinAllowed returns if a new Issue or Pull request can be pinned -func IsNewPinAllowed(ctx context.Context, repoID int64, isPull bool) (bool, error) { - var maxPin int - _, err := db.GetEngine(ctx).SQL("SELECT COUNT(pin_order) FROM issue WHERE repo_id = ? AND is_pull = ? AND pin_order > 0", repoID, isPull).Get(&maxPin) - if err != nil { - return false, err - } - - return maxPin < setting.Repository.Issue.MaxPinned, nil -} - -// IsErrIssueMaxPinReached returns if the error is, that the User can't pin more Issues -func IsErrIssueMaxPinReached(err error) bool { - return err == ErrIssueMaxPinReached -} - // InsertIssues insert issues to database func InsertIssues(ctx context.Context, issues ...*Issue) error { ctx, committer, err := db.TxContext(ctx) diff --git a/models/issues/issue_list.go b/models/issues/issue_list.go index 02fd330f0a..6c74b533b3 100644 --- a/models/issues/issue_list.go +++ b/models/issues/issue_list.go @@ -506,6 +506,39 @@ func (issues IssueList) loadTotalTrackedTimes(ctx context.Context) (err error) { return nil } +func (issues IssueList) LoadPinOrder(ctx context.Context) error { + if len(issues) == 0 { + return nil + } + + issueIDs := container.FilterSlice(issues, func(issue *Issue) (int64, bool) { + return issue.ID, issue.PinOrder == 0 + }) + if len(issueIDs) == 0 { + return nil + } + issuePins, err := GetIssuePinsByIssueIDs(ctx, issueIDs) + if err != nil { + return err + } + + for _, issue := range issues { + if issue.PinOrder != 0 { + continue + } + for _, pin := range issuePins { + if pin.IssueID == issue.ID { + issue.PinOrder = pin.PinOrder + break + } + } + if issue.PinOrder == 0 { + issue.PinOrder = -1 + } + } + return nil +} + // loadAttributes loads all attributes, expect for attachments and comments func (issues IssueList) LoadAttributes(ctx context.Context) error { if _, err := issues.LoadRepositories(ctx); err != nil { diff --git a/models/issues/issue_pin.go b/models/issues/issue_pin.go new file mode 100644 index 0000000000..ae6195b05d --- /dev/null +++ b/models/issues/issue_pin.go @@ -0,0 +1,246 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package issues + +import ( + "context" + "errors" + "sort" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" +) + +type IssuePin struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"UNIQUE(s) NOT NULL"` + IssueID int64 `xorm:"UNIQUE(s) NOT NULL"` + IsPull bool `xorm:"NOT NULL"` + PinOrder int `xorm:"DEFAULT 0"` +} + +var ErrIssueMaxPinReached = util.NewInvalidArgumentErrorf("the max number of pinned issues has been readched") + +// IsErrIssueMaxPinReached returns if the error is, that the User can't pin more Issues +func IsErrIssueMaxPinReached(err error) bool { + return err == ErrIssueMaxPinReached +} + +func init() { + db.RegisterModel(new(IssuePin)) +} + +func GetIssuePin(ctx context.Context, issue *Issue) (*IssuePin, error) { + pin := new(IssuePin) + has, err := db.GetEngine(ctx). + Where("repo_id = ?", issue.RepoID). + And("issue_id = ?", issue.ID).Get(pin) + if err != nil { + return nil, err + } else if !has { + return nil, db.ErrNotExist{ + Resource: "IssuePin", + ID: issue.ID, + } + } + return pin, nil +} + +func GetIssuePinsByIssueIDs(ctx context.Context, issueIDs []int64) ([]IssuePin, error) { + var pins []IssuePin + if err := db.GetEngine(ctx).In("issue_id", issueIDs).Find(&pins); err != nil { + return nil, err + } + return pins, nil +} + +// Pin pins a Issue +func PinIssue(ctx context.Context, issue *Issue, user *user_model.User) error { + return db.WithTx(ctx, func(ctx context.Context) error { + pinnedIssuesNum, err := getPinnedIssuesNum(ctx, issue.RepoID, issue.IsPull) + if err != nil { + return err + } + + // Check if the maximum allowed Pins reached + if pinnedIssuesNum >= setting.Repository.Issue.MaxPinned { + return ErrIssueMaxPinReached + } + + pinnedIssuesMaxPinOrder, err := getPinnedIssuesMaxPinOrder(ctx, issue.RepoID, issue.IsPull) + if err != nil { + return err + } + + if _, err = db.GetEngine(ctx).Insert(&IssuePin{ + RepoID: issue.RepoID, + IssueID: issue.ID, + IsPull: issue.IsPull, + PinOrder: pinnedIssuesMaxPinOrder + 1, + }); err != nil { + return err + } + + // Add the pin event to the history + _, err = CreateComment(ctx, &CreateCommentOptions{ + Type: CommentTypePin, + Doer: user, + Repo: issue.Repo, + Issue: issue, + }) + return err + }) +} + +// UnpinIssue unpins a Issue +func UnpinIssue(ctx context.Context, issue *Issue, user *user_model.User) error { + return db.WithTx(ctx, func(ctx context.Context) error { + // This sets the Pin for all Issues that come after the unpined Issue to the correct value + cnt, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Delete(new(IssuePin)) + if err != nil { + return err + } + if cnt == 0 { + return nil + } + + // Add the unpin event to the history + _, err = CreateComment(ctx, &CreateCommentOptions{ + Type: CommentTypeUnpin, + Doer: user, + Repo: issue.Repo, + Issue: issue, + }) + return err + }) +} + +func getPinnedIssuesNum(ctx context.Context, repoID int64, isPull bool) (int, error) { + var pinnedIssuesNum int + _, err := db.GetEngine(ctx).SQL("SELECT count(pin_order) FROM issue_pin WHERE repo_id = ? AND is_pull = ?", repoID, isPull).Get(&pinnedIssuesNum) + return pinnedIssuesNum, err +} + +func getPinnedIssuesMaxPinOrder(ctx context.Context, repoID int64, isPull bool) (int, error) { + var maxPinnedIssuesMaxPinOrder int + _, err := db.GetEngine(ctx).SQL("SELECT max(pin_order) FROM issue_pin WHERE repo_id = ? AND is_pull = ?", repoID, isPull).Get(&maxPinnedIssuesMaxPinOrder) + return maxPinnedIssuesMaxPinOrder, err +} + +// MovePin moves a Pinned Issue to a new Position +func MovePin(ctx context.Context, issue *Issue, newPosition int) error { + if newPosition < 1 { + return errors.New("The Position can't be lower than 1") + } + + issuePin, err := GetIssuePin(ctx, issue) + if err != nil { + return err + } + if issuePin.PinOrder == newPosition { + return nil + } + + return db.WithTx(ctx, func(ctx context.Context) error { + if issuePin.PinOrder > newPosition { // move the issue to a lower position + _, err = db.GetEngine(ctx).Exec("UPDATE issue_pin SET pin_order = pin_order + 1 WHERE repo_id = ? AND is_pull = ? AND pin_order >= ? AND pin_order < ?", issue.RepoID, issue.IsPull, newPosition, issuePin.PinOrder) + } else { // move the issue to a higher position + // Lower the Position of all Pinned Issue that came after the current Position + _, err = db.GetEngine(ctx).Exec("UPDATE issue_pin SET pin_order = pin_order - 1 WHERE repo_id = ? AND is_pull = ? AND pin_order > ? AND pin_order <= ?", issue.RepoID, issue.IsPull, issuePin.PinOrder, newPosition) + } + if err != nil { + return err + } + + _, err = db.GetEngine(ctx). + Table("issue_pin"). + Where("id = ?", issuePin.ID). + Update(map[string]any{ + "pin_order": newPosition, + }) + return err + }) +} + +func GetPinnedIssueIDs(ctx context.Context, repoID int64, isPull bool) ([]int64, error) { + var issuePins []IssuePin + if err := db.GetEngine(ctx). + Table("issue_pin"). + Where("repo_id = ?", repoID). + And("is_pull = ?", isPull). + Find(&issuePins); err != nil { + return nil, err + } + + sort.Slice(issuePins, func(i, j int) bool { + return issuePins[i].PinOrder < issuePins[j].PinOrder + }) + + var ids []int64 + for _, pin := range issuePins { + ids = append(ids, pin.IssueID) + } + return ids, nil +} + +func GetIssuePinsByRepoID(ctx context.Context, repoID int64, isPull bool) ([]*IssuePin, error) { + var pins []*IssuePin + if err := db.GetEngine(ctx).Where("repo_id = ? AND is_pull = ?", repoID, isPull).Find(&pins); err != nil { + return nil, err + } + return pins, nil +} + +// GetPinnedIssues returns the pinned Issues for the given Repo and type +func GetPinnedIssues(ctx context.Context, repoID int64, isPull bool) (IssueList, error) { + issuePins, err := GetIssuePinsByRepoID(ctx, repoID, isPull) + if err != nil { + return nil, err + } + if len(issuePins) == 0 { + return IssueList{}, nil + } + ids := make([]int64, 0, len(issuePins)) + for _, pin := range issuePins { + ids = append(ids, pin.IssueID) + } + + issues := make(IssueList, 0, len(ids)) + if err := db.GetEngine(ctx).In("id", ids).Find(&issues); err != nil { + return nil, err + } + for _, issue := range issues { + for _, pin := range issuePins { + if pin.IssueID == issue.ID { + issue.PinOrder = pin.PinOrder + break + } + } + if (!setting.IsProd || setting.IsInTesting) && issue.PinOrder == 0 { + panic("It should not happen that a pinned Issue has no PinOrder") + } + } + sort.Slice(issues, func(i, j int) bool { + return issues[i].PinOrder < issues[j].PinOrder + }) + + if err = issues.LoadAttributes(ctx); err != nil { + return nil, err + } + + return issues, nil +} + +// IsNewPinAllowed returns if a new Issue or Pull request can be pinned +func IsNewPinAllowed(ctx context.Context, repoID int64, isPull bool) (bool, error) { + var maxPin int + _, err := db.GetEngine(ctx).SQL("SELECT COUNT(pin_order) FROM issue_pin WHERE repo_id = ? AND is_pull = ?", repoID, isPull).Get(&maxPin) + if err != nil { + return false, err + } + + return maxPin < setting.Repository.Issue.MaxPinned, nil +} diff --git a/models/issues/issue_project.go b/models/issues/issue_project.go index c4515fd898..0185244783 100644 --- a/models/issues/issue_project.go +++ b/models/issues/issue_project.go @@ -38,13 +38,30 @@ func (issue *Issue) projectID(ctx context.Context) int64 { } // ProjectColumnID return project column id if issue was assigned to one -func (issue *Issue) ProjectColumnID(ctx context.Context) int64 { +func (issue *Issue) ProjectColumnID(ctx context.Context) (int64, error) { var ip project_model.ProjectIssue has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip) - if err != nil || !has { - return 0 + if err != nil { + return 0, err + } else if !has { + return 0, nil } - return ip.ProjectColumnID + return ip.ProjectColumnID, nil +} + +func LoadProjectIssueColumnMap(ctx context.Context, projectID, defaultColumnID int64) (map[int64]int64, error) { + issues := make([]project_model.ProjectIssue, 0) + if err := db.GetEngine(ctx).Where("project_id=?", projectID).Find(&issues); err != nil { + return nil, err + } + result := make(map[int64]int64, len(issues)) + for _, issue := range issues { + if issue.ProjectColumnID == 0 { + issue.ProjectColumnID = defaultColumnID + } + result[issue.IssueID] = issue.ProjectColumnID + } + return result, nil } // LoadIssuesFromColumn load issues assigned to this column @@ -59,11 +76,11 @@ func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *Is } if b.Default { - issues, err := Issues(ctx, &IssuesOptions{ - ProjectColumnID: db.NoConditionID, - ProjectID: b.ProjectID, - SortType: "project-column-sorting", - }) + issues, err := Issues(ctx, opts.Copy(func(o *IssuesOptions) { + o.ProjectColumnID = db.NoConditionID + o.ProjectID = b.ProjectID + o.SortType = "project-column-sorting" + })) if err != nil { return nil, err } @@ -77,19 +94,6 @@ func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *Is return issueList, nil } -// LoadIssuesFromColumnList load issues assigned to the columns -func LoadIssuesFromColumnList(ctx context.Context, bs project_model.ColumnList, opts *IssuesOptions) (map[int64]IssueList, error) { - issuesMap := make(map[int64]IssueList, len(bs)) - for i := range bs { - il, err := LoadIssuesFromColumn(ctx, bs[i], opts) - if err != nil { - return nil, err - } - issuesMap[bs[i].ID] = il - } - return issuesMap, nil -} - // IssueAssignOrRemoveProject changes the project associated with an issue // If newProjectID is 0, the issue is removed from the project func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID, newColumnID int64) error { @@ -110,7 +114,7 @@ func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_mo return util.NewPermissionDeniedErrorf("issue %d can't be accessed by project %d", issue.ID, newProject.ID) } if newColumnID == 0 { - newDefaultColumn, err := newProject.GetDefaultColumn(ctx) + newDefaultColumn, err := newProject.MustDefaultColumn(ctx) if err != nil { return err } diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go index f1cd125d49..694b918755 100644 --- a/models/issues/issue_search.go +++ b/models/issues/issue_search.go @@ -49,9 +49,9 @@ type IssuesOptions struct { //nolint // prioritize issues from this repo PriorityRepoID int64 IsArchived optional.Option[bool] - Org *organization.Organization // issues permission scope - Team *organization.Team // issues permission scope - User *user_model.User // issues permission scope + Owner *user_model.User // issues permission scope, it could be an organization or a user + Team *organization.Team // issues permission scope + Doer *user_model.User // issues permission scope } // Copy returns a copy of the options. @@ -273,8 +273,12 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) { applyLabelsCondition(sess, opts) - if opts.User != nil { - sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.Value())) + if opts.Owner != nil { + sess.And(repo_model.UserOwnedRepoCond(opts.Owner.ID)) + } + + if opts.Doer != nil && !opts.Doer.IsAdmin { + sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.Doer.ID, opts.Owner, opts.Team, opts.IsPull.Value())) } } @@ -321,20 +325,20 @@ func teamUnitsRepoCond(id string, userID, orgID, teamID int64, units ...unit.Typ } // issuePullAccessibleRepoCond userID must not be zero, this condition require join repository table -func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *organization.Organization, team *organization.Team, isPull bool) builder.Cond { +func issuePullAccessibleRepoCond(repoIDstr string, userID int64, owner *user_model.User, team *organization.Team, isPull bool) builder.Cond { cond := builder.NewCond() unitType := unit.TypeIssues if isPull { unitType = unit.TypePullRequests } - if org != nil { + if owner != nil && owner.IsOrganization() { if team != nil { - cond = cond.And(teamUnitsRepoCond(repoIDstr, userID, org.ID, team.ID, unitType)) // special team member repos + cond = cond.And(teamUnitsRepoCond(repoIDstr, userID, owner.ID, team.ID, unitType)) // special team member repos } else { cond = cond.And( builder.Or( - repo_model.UserOrgUnitRepoCond(repoIDstr, userID, org.ID, unitType), // team member repos - repo_model.UserOrgPublicUnitRepoCond(userID, org.ID), // user org public non-member repos, TODO: check repo has issues + repo_model.UserOrgUnitRepoCond(repoIDstr, userID, owner.ID, unitType), // team member repos + repo_model.UserOrgPublicUnitRepoCond(userID, owner.ID), // user org public non-member repos, TODO: check repo has issues ), ) } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 95364ab705..87d674a440 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -373,6 +373,7 @@ func prepareMigrationTasks() []*migration { // Gitea 1.23.0-rc0 ends at migration ID number 311 (database version 312) newMigration(312, "Add DeleteBranchAfterMerge to AutoMerge", v1_24.AddDeleteBranchAfterMergeForAutoMerge), + newMigration(313, "Move PinOrder from issue table to a new table issue_pin", v1_24.MovePinOrderToTableIssuePin), } return preparedMigrations } diff --git a/models/migrations/v1_24/v313.go b/models/migrations/v1_24/v313.go new file mode 100644 index 0000000000..ee9d479340 --- /dev/null +++ b/models/migrations/v1_24/v313.go @@ -0,0 +1,31 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_24 //nolint + +import ( + "code.gitea.io/gitea/models/migrations/base" + + "xorm.io/xorm" +) + +func MovePinOrderToTableIssuePin(x *xorm.Engine) error { + type IssuePin struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"UNIQUE(s) NOT NULL"` + IssueID int64 `xorm:"UNIQUE(s) NOT NULL"` + IsPull bool `xorm:"NOT NULL"` + PinOrder int `xorm:"DEFAULT 0"` + } + + if err := x.Sync(new(IssuePin)); err != nil { + return err + } + + if _, err := x.Exec("INSERT INTO issue_pin (repo_id, issue_id, is_pull, pin_order) SELECT repo_id, id, is_pull, pin_order FROM issue WHERE pin_order > 0"); err != nil { + return err + } + sess := x.NewSession() + defer sess.Close() + return base.DropTableColumns(sess, "issue", "pin_order") +} diff --git a/models/packages/package.go b/models/packages/package.go index 31e1277a6e..8935dbaa1c 100644 --- a/models/packages/package.go +++ b/models/packages/package.go @@ -228,6 +228,11 @@ func SetRepositoryLink(ctx context.Context, packageID, repoID int64) error { return err } +func UnlinkRepository(ctx context.Context, packageID int64) error { + _, err := db.GetEngine(ctx).ID(packageID).Cols("repo_id").Update(&Package{RepoID: 0}) + return err +} + // UnlinkRepositoryFromAllPackages unlinks every package from the repository func UnlinkRepositoryFromAllPackages(ctx context.Context, repoID int64) error { _, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Cols("repo_id").Update(&Package{}) diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index e00b7c5320..5e7ecb31ea 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -152,7 +152,7 @@ func (p *Permission) ReadableUnitTypes() []unit.Type { } func (p *Permission) LogString() string { - format := " 1 { + // only remove the prefix and suffix quotes, no need to decode the content at the moment + return keyword[1 : len(keyword)-1], true + } + return "", false +} diff --git a/modules/indexer/code/internal/util_test.go b/modules/indexer/code/internal/util_test.go new file mode 100644 index 0000000000..457936296b --- /dev/null +++ b/modules/indexer/code/internal/util_test.go @@ -0,0 +1,30 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package internal + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseKeywordAsPhrase(t *testing.T) { + cases := []struct { + keyword string + phrase string + isPhrase bool + }{ + {``, "", false}, + {`a`, "", false}, + {`"`, "", false}, + {`"a`, "", false}, + {`"a"`, "a", true}, + {`""\"""`, `"\""`, true}, + } + for _, c := range cases { + phrase, isPhrase := ParseKeywordAsPhrase(c.keyword) + assert.Equal(t, c.phrase, phrase, "keyword=%q", c.keyword) + assert.Equal(t, c.isPhrase, isPhrase, "keyword=%q", c.keyword) + } +} diff --git a/modules/indexer/issues/db/options.go b/modules/indexer/issues/db/options.go index 42834f6e88..87ce398a20 100644 --- a/modules/indexer/issues/db/options.go +++ b/modules/indexer/issues/db/options.go @@ -73,9 +73,9 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m UpdatedBeforeUnix: options.UpdatedBeforeUnix.Value(), PriorityRepoID: 0, IsArchived: options.IsArchived, - Org: nil, + Owner: nil, Team: nil, - User: nil, + Doer: nil, } if len(options.MilestoneIDs) == 1 && options.MilestoneIDs[0] == 0 { diff --git a/modules/indexer/issues/util.go b/modules/indexer/issues/util.go index deb19adc49..19d835a1d8 100644 --- a/modules/indexer/issues/util.go +++ b/modules/indexer/issues/util.go @@ -92,6 +92,11 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD projectID = issue.Project.ID } + projectColumnID, err := issue.ProjectColumnID(ctx) + if err != nil { + return nil, false, err + } + return &internal.IndexerData{ ID: issue.ID, RepoID: issue.RepoID, @@ -106,7 +111,7 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD NoLabel: len(labels) == 0, MilestoneID: issue.MilestoneID, ProjectID: projectID, - ProjectColumnID: issue.ProjectColumnID(ctx), + ProjectColumnID: projectColumnID, PosterID: issue.PosterID, AssigneeID: issue.AssigneeID, MentionIDs: mentionIDs, diff --git a/modules/log/logger_impl.go b/modules/log/logger_impl.go index 76dd5f43fb..b7a1f4e1e1 100644 --- a/modules/log/logger_impl.go +++ b/modules/log/logger_impl.go @@ -5,6 +5,7 @@ package log import ( "context" + "reflect" "runtime" "strings" "sync" @@ -175,6 +176,20 @@ func (l *LoggerImpl) IsEnabled() bool { return l.level.Load() < int32(FATAL) && len(l.eventWriters) > 0 } +func asLogStringer(v any) LogStringer { + if s, ok := v.(LogStringer); ok { + return s + } else if a := reflect.ValueOf(v); a.Kind() == reflect.Struct { + // in case the receiver is a pointer, but the value is a struct + vp := reflect.New(a.Type()) + vp.Elem().Set(a) + if s, ok := vp.Interface().(LogStringer); ok { + return s + } + } + return nil +} + // Log prepares the log event, if the level matches, the event will be sent to the writers func (l *LoggerImpl) Log(skip int, level Level, format string, logArgs ...any) { if Level(l.level.Load()) > level { @@ -207,11 +222,11 @@ func (l *LoggerImpl) Log(skip int, level Level, format string, logArgs ...any) { // handle LogStringer values for i, v := range msgArgs { if cv, ok := v.(*ColoredValue); ok { - if s, ok := cv.v.(LogStringer); ok { - cv.v = logStringFormatter{v: s} + if ls := asLogStringer(cv.v); ls != nil { + cv.v = logStringFormatter{v: ls} } - } else if s, ok := v.(LogStringer); ok { - msgArgs[i] = logStringFormatter{v: s} + } else if ls := asLogStringer(v); ls != nil { + msgArgs[i] = logStringFormatter{v: ls} } } diff --git a/modules/log/logger_test.go b/modules/log/logger_test.go index 0de14eb411..e794732ce2 100644 --- a/modules/log/logger_test.go +++ b/modules/log/logger_test.go @@ -116,6 +116,14 @@ func (t testLogString) LogString() string { return "log-string" } +type testLogStringPtrReceiver struct { + Field string +} + +func (t *testLogStringPtrReceiver) LogString() string { + return "log-string-ptr-receiver" +} + func TestLoggerLogString(t *testing.T) { logger := NewLoggerWithWriters(context.Background(), "test") @@ -124,9 +132,13 @@ func TestLoggerLogString(t *testing.T) { logger.AddWriters(w1) logger.Info("%s %s %#v %v", testLogString{}, &testLogString{}, testLogString{Field: "detail"}, NewColoredValue(testLogString{}, FgRed)) + logger.Info("%s %s %#v %v", testLogStringPtrReceiver{}, &testLogStringPtrReceiver{}, testLogStringPtrReceiver{Field: "detail"}, NewColoredValue(testLogStringPtrReceiver{}, FgRed)) logger.Close() - assert.Equal(t, []string{"log-string log-string log.testLogString{Field:\"detail\"} \x1b[31mlog-string\x1b[0m\n"}, w1.GetLogs()) + assert.Equal(t, []string{ + "log-string log-string log.testLogString{Field:\"detail\"} \x1b[31mlog-string\x1b[0m\n", + "log-string-ptr-receiver log-string-ptr-receiver &log.testLogStringPtrReceiver{Field:\"detail\"} \x1b[31mlog-string-ptr-receiver\x1b[0m\n", + }, w1.GetLogs()) } func TestLoggerExpressionFilter(t *testing.T) { diff --git a/modules/migration/downloader.go b/modules/migration/downloader.go index 08dbbc29a9..669222dea2 100644 --- a/modules/migration/downloader.go +++ b/modules/migration/downloader.go @@ -12,18 +12,17 @@ import ( // Downloader downloads the site repo information type Downloader interface { - SetContext(context.Context) - GetRepoInfo() (*Repository, error) - GetTopics() ([]string, error) - GetMilestones() ([]*Milestone, error) - GetReleases() ([]*Release, error) - GetLabels() ([]*Label, error) - GetIssues(page, perPage int) ([]*Issue, bool, error) - GetComments(commentable Commentable) ([]*Comment, bool, error) - GetAllComments(page, perPage int) ([]*Comment, bool, error) + GetRepoInfo(ctx context.Context) (*Repository, error) + GetTopics(ctx context.Context) ([]string, error) + GetMilestones(ctx context.Context) ([]*Milestone, error) + GetReleases(ctx context.Context) ([]*Release, error) + GetLabels(ctx context.Context) ([]*Label, error) + GetIssues(ctx context.Context, page, perPage int) ([]*Issue, bool, error) + GetComments(ctx context.Context, commentable Commentable) ([]*Comment, bool, error) + GetAllComments(ctx context.Context, page, perPage int) ([]*Comment, bool, error) SupportGetRepoComments() bool - GetPullRequests(page, perPage int) ([]*PullRequest, bool, error) - GetReviews(reviewable Reviewable) ([]*Review, error) + GetPullRequests(ctx context.Context, page, perPage int) ([]*PullRequest, bool, error) + GetReviews(ctx context.Context, reviewable Reviewable) ([]*Review, error) FormatCloneURL(opts MigrateOptions, remoteAddr string) (string, error) } diff --git a/modules/migration/null_downloader.go b/modules/migration/null_downloader.go index e5b69331df..e488f6914f 100644 --- a/modules/migration/null_downloader.go +++ b/modules/migration/null_downloader.go @@ -13,56 +13,53 @@ type NullDownloader struct{} var _ Downloader = &NullDownloader{} -// SetContext set context -func (n NullDownloader) SetContext(_ context.Context) {} - // GetRepoInfo returns a repository information -func (n NullDownloader) GetRepoInfo() (*Repository, error) { +func (n NullDownloader) GetRepoInfo(_ context.Context) (*Repository, error) { return nil, ErrNotSupported{Entity: "RepoInfo"} } // GetTopics return repository topics -func (n NullDownloader) GetTopics() ([]string, error) { +func (n NullDownloader) GetTopics(_ context.Context) ([]string, error) { return nil, ErrNotSupported{Entity: "Topics"} } // GetMilestones returns milestones -func (n NullDownloader) GetMilestones() ([]*Milestone, error) { +func (n NullDownloader) GetMilestones(_ context.Context) ([]*Milestone, error) { return nil, ErrNotSupported{Entity: "Milestones"} } // GetReleases returns releases -func (n NullDownloader) GetReleases() ([]*Release, error) { +func (n NullDownloader) GetReleases(_ context.Context) ([]*Release, error) { return nil, ErrNotSupported{Entity: "Releases"} } // GetLabels returns labels -func (n NullDownloader) GetLabels() ([]*Label, error) { +func (n NullDownloader) GetLabels(_ context.Context) ([]*Label, error) { return nil, ErrNotSupported{Entity: "Labels"} } // GetIssues returns issues according start and limit -func (n NullDownloader) GetIssues(page, perPage int) ([]*Issue, bool, error) { +func (n NullDownloader) GetIssues(_ context.Context, page, perPage int) ([]*Issue, bool, error) { return nil, false, ErrNotSupported{Entity: "Issues"} } // GetComments returns comments of an issue or PR -func (n NullDownloader) GetComments(commentable Commentable) ([]*Comment, bool, error) { +func (n NullDownloader) GetComments(_ context.Context, commentable Commentable) ([]*Comment, bool, error) { return nil, false, ErrNotSupported{Entity: "Comments"} } // GetAllComments returns paginated comments -func (n NullDownloader) GetAllComments(page, perPage int) ([]*Comment, bool, error) { +func (n NullDownloader) GetAllComments(_ context.Context, page, perPage int) ([]*Comment, bool, error) { return nil, false, ErrNotSupported{Entity: "AllComments"} } // GetPullRequests returns pull requests according page and perPage -func (n NullDownloader) GetPullRequests(page, perPage int) ([]*PullRequest, bool, error) { +func (n NullDownloader) GetPullRequests(_ context.Context, page, perPage int) ([]*PullRequest, bool, error) { return nil, false, ErrNotSupported{Entity: "PullRequests"} } // GetReviews returns pull requests review -func (n NullDownloader) GetReviews(reviewable Reviewable) ([]*Review, error) { +func (n NullDownloader) GetReviews(_ context.Context, reviewable Reviewable) ([]*Review, error) { return nil, ErrNotSupported{Entity: "Reviews"} } diff --git a/modules/migration/retry_downloader.go b/modules/migration/retry_downloader.go index 1cacf5f375..2926c40df7 100644 --- a/modules/migration/retry_downloader.go +++ b/modules/migration/retry_downloader.go @@ -49,21 +49,15 @@ func (d *RetryDownloader) retry(work func() error) error { return err } -// SetContext set context -func (d *RetryDownloader) SetContext(ctx context.Context) { - d.ctx = ctx - d.Downloader.SetContext(ctx) -} - // GetRepoInfo returns a repository information with retry -func (d *RetryDownloader) GetRepoInfo() (*Repository, error) { +func (d *RetryDownloader) GetRepoInfo(ctx context.Context) (*Repository, error) { var ( repo *Repository err error ) err = d.retry(func() error { - repo, err = d.Downloader.GetRepoInfo() + repo, err = d.Downloader.GetRepoInfo(ctx) return err }) @@ -71,14 +65,14 @@ func (d *RetryDownloader) GetRepoInfo() (*Repository, error) { } // GetTopics returns a repository's topics with retry -func (d *RetryDownloader) GetTopics() ([]string, error) { +func (d *RetryDownloader) GetTopics(ctx context.Context) ([]string, error) { var ( topics []string err error ) err = d.retry(func() error { - topics, err = d.Downloader.GetTopics() + topics, err = d.Downloader.GetTopics(ctx) return err }) @@ -86,14 +80,14 @@ func (d *RetryDownloader) GetTopics() ([]string, error) { } // GetMilestones returns a repository's milestones with retry -func (d *RetryDownloader) GetMilestones() ([]*Milestone, error) { +func (d *RetryDownloader) GetMilestones(ctx context.Context) ([]*Milestone, error) { var ( milestones []*Milestone err error ) err = d.retry(func() error { - milestones, err = d.Downloader.GetMilestones() + milestones, err = d.Downloader.GetMilestones(ctx) return err }) @@ -101,14 +95,14 @@ func (d *RetryDownloader) GetMilestones() ([]*Milestone, error) { } // GetReleases returns a repository's releases with retry -func (d *RetryDownloader) GetReleases() ([]*Release, error) { +func (d *RetryDownloader) GetReleases(ctx context.Context) ([]*Release, error) { var ( releases []*Release err error ) err = d.retry(func() error { - releases, err = d.Downloader.GetReleases() + releases, err = d.Downloader.GetReleases(ctx) return err }) @@ -116,14 +110,14 @@ func (d *RetryDownloader) GetReleases() ([]*Release, error) { } // GetLabels returns a repository's labels with retry -func (d *RetryDownloader) GetLabels() ([]*Label, error) { +func (d *RetryDownloader) GetLabels(ctx context.Context) ([]*Label, error) { var ( labels []*Label err error ) err = d.retry(func() error { - labels, err = d.Downloader.GetLabels() + labels, err = d.Downloader.GetLabels(ctx) return err }) @@ -131,7 +125,7 @@ func (d *RetryDownloader) GetLabels() ([]*Label, error) { } // GetIssues returns a repository's issues with retry -func (d *RetryDownloader) GetIssues(page, perPage int) ([]*Issue, bool, error) { +func (d *RetryDownloader) GetIssues(ctx context.Context, page, perPage int) ([]*Issue, bool, error) { var ( issues []*Issue isEnd bool @@ -139,7 +133,7 @@ func (d *RetryDownloader) GetIssues(page, perPage int) ([]*Issue, bool, error) { ) err = d.retry(func() error { - issues, isEnd, err = d.Downloader.GetIssues(page, perPage) + issues, isEnd, err = d.Downloader.GetIssues(ctx, page, perPage) return err }) @@ -147,7 +141,7 @@ func (d *RetryDownloader) GetIssues(page, perPage int) ([]*Issue, bool, error) { } // GetComments returns a repository's comments with retry -func (d *RetryDownloader) GetComments(commentable Commentable) ([]*Comment, bool, error) { +func (d *RetryDownloader) GetComments(ctx context.Context, commentable Commentable) ([]*Comment, bool, error) { var ( comments []*Comment isEnd bool @@ -155,7 +149,7 @@ func (d *RetryDownloader) GetComments(commentable Commentable) ([]*Comment, bool ) err = d.retry(func() error { - comments, isEnd, err = d.Downloader.GetComments(commentable) + comments, isEnd, err = d.Downloader.GetComments(ctx, commentable) return err }) @@ -163,7 +157,7 @@ func (d *RetryDownloader) GetComments(commentable Commentable) ([]*Comment, bool } // GetPullRequests returns a repository's pull requests with retry -func (d *RetryDownloader) GetPullRequests(page, perPage int) ([]*PullRequest, bool, error) { +func (d *RetryDownloader) GetPullRequests(ctx context.Context, page, perPage int) ([]*PullRequest, bool, error) { var ( prs []*PullRequest err error @@ -171,7 +165,7 @@ func (d *RetryDownloader) GetPullRequests(page, perPage int) ([]*PullRequest, bo ) err = d.retry(func() error { - prs, isEnd, err = d.Downloader.GetPullRequests(page, perPage) + prs, isEnd, err = d.Downloader.GetPullRequests(ctx, page, perPage) return err }) @@ -179,14 +173,13 @@ func (d *RetryDownloader) GetPullRequests(page, perPage int) ([]*PullRequest, bo } // GetReviews returns pull requests reviews -func (d *RetryDownloader) GetReviews(reviewable Reviewable) ([]*Review, error) { +func (d *RetryDownloader) GetReviews(ctx context.Context, reviewable Reviewable) ([]*Review, error) { var ( reviews []*Review err error ) - err = d.retry(func() error { - reviews, err = d.Downloader.GetReviews(reviewable) + reviews, err = d.Downloader.GetReviews(ctx, reviewable) return err }) diff --git a/modules/migration/uploader.go b/modules/migration/uploader.go index ff642aa4fa..65752e248e 100644 --- a/modules/migration/uploader.go +++ b/modules/migration/uploader.go @@ -4,20 +4,22 @@ package migration +import "context" + // Uploader uploads all the information of one repository type Uploader interface { MaxBatchInsertSize(tp string) int - CreateRepo(repo *Repository, opts MigrateOptions) error - CreateTopics(topic ...string) error - CreateMilestones(milestones ...*Milestone) error - CreateReleases(releases ...*Release) error - SyncTags() error - CreateLabels(labels ...*Label) error - CreateIssues(issues ...*Issue) error - CreateComments(comments ...*Comment) error - CreatePullRequests(prs ...*PullRequest) error - CreateReviews(reviews ...*Review) error + CreateRepo(ctx context.Context, repo *Repository, opts MigrateOptions) error + CreateTopics(ctx context.Context, topic ...string) error + CreateMilestones(ctx context.Context, milestones ...*Milestone) error + CreateReleases(ctx context.Context, releases ...*Release) error + SyncTags(ctx context.Context) error + CreateLabels(ctx context.Context, labels ...*Label) error + CreateIssues(ctx context.Context, issues ...*Issue) error + CreateComments(ctx context.Context, comments ...*Comment) error + CreatePullRequests(ctx context.Context, prs ...*PullRequest) error + CreateReviews(ctx context.Context, reviews ...*Review) error Rollback() error - Finish() error + Finish(ctx context.Context) error Close() } diff --git a/modules/structs/repo_actions.go b/modules/structs/repo_actions.go index b13f344738..203491ac02 100644 --- a/modules/structs/repo_actions.go +++ b/modules/structs/repo_actions.go @@ -32,3 +32,67 @@ type ActionTaskResponse struct { Entries []*ActionTask `json:"workflow_runs"` TotalCount int64 `json:"total_count"` } + +// CreateActionWorkflowDispatch represents the payload for triggering a workflow dispatch event +// swagger:model +type CreateActionWorkflowDispatch struct { + // required: true + // example: refs/heads/main + Ref string `json:"ref" binding:"Required"` + // required: false + Inputs map[string]string `json:"inputs,omitempty"` +} + +// ActionWorkflow represents a ActionWorkflow +type ActionWorkflow struct { + ID string `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + State string `json:"state"` + // swagger:strfmt date-time + CreatedAt time.Time `json:"created_at"` + // swagger:strfmt date-time + UpdatedAt time.Time `json:"updated_at"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + BadgeURL string `json:"badge_url"` + // swagger:strfmt date-time + DeletedAt time.Time `json:"deleted_at,omitempty"` +} + +// ActionWorkflowResponse returns a ActionWorkflow +type ActionWorkflowResponse struct { + Workflows []*ActionWorkflow `json:"workflows"` + TotalCount int64 `json:"total_count"` +} + +// ActionArtifact represents a ActionArtifact +type ActionArtifact struct { + ID int64 `json:"id"` + Name string `json:"name"` + SizeInBytes int64 `json:"size_in_bytes"` + URL string `json:"url"` + ArchiveDownloadURL string `json:"archive_download_url"` + Expired bool `json:"expired"` + WorkflowRun *ActionWorkflowRun `json:"workflow_run"` + + // swagger:strfmt date-time + CreatedAt time.Time `json:"created_at"` + // swagger:strfmt date-time + UpdatedAt time.Time `json:"updated_at"` + // swagger:strfmt date-time + ExpiresAt time.Time `json:"expires_at"` +} + +// ActionWorkflowRun represents a WorkflowRun +type ActionWorkflowRun struct { + ID int64 `json:"id"` + RepositoryID int64 `json:"repository_id"` + HeadSha string `json:"head_sha"` +} + +// ActionArtifactsResponse returns ActionArtifacts +type ActionArtifactsResponse struct { + Entries []*ActionArtifact `json:"artifacts"` + TotalCount int64 `json:"total_count"` +} diff --git a/modules/util/error.go b/modules/util/error.go index 0f3597147c..13b592a9f8 100644 --- a/modules/util/error.go +++ b/modules/util/error.go @@ -10,13 +10,16 @@ import ( // Common Errors forming the base of our error system // -// Many Errors returned by Gitea can be tested against these errors -// using errors.Is. +// Many Errors returned by Gitea can be tested against these errors using "errors.Is". var ( - ErrInvalidArgument = errors.New("invalid argument") - ErrPermissionDenied = errors.New("permission denied") - ErrAlreadyExist = errors.New("resource already exists") - ErrNotExist = errors.New("resource does not exist") + ErrInvalidArgument = errors.New("invalid argument") // also implies HTTP 400 + ErrPermissionDenied = errors.New("permission denied") // also implies HTTP 403 + ErrNotExist = errors.New("resource does not exist") // also implies HTTP 404 + ErrAlreadyExist = errors.New("resource already exists") // also implies HTTP 409 + + // ErrUnprocessableContent implies HTTP 422, syntax of the request content was correct, + // but server was unable to process the contained instructions + ErrUnprocessableContent = errors.New("unprocessable content") ) // SilentWrap provides a simple wrapper for a wrapped error where the wrapped error message plays no part in the error message @@ -36,6 +39,22 @@ func (w SilentWrap) Unwrap() error { return w.Err } +type LocaleWrap struct { + err error + TrKey string + TrArgs []any +} + +// Error returns the message +func (w LocaleWrap) Error() string { + return w.err.Error() +} + +// Unwrap returns the underlying error +func (w LocaleWrap) Unwrap() error { + return w.err +} + // NewSilentWrapErrorf returns an error that formats as the given text but unwraps as the provided error func NewSilentWrapErrorf(unwrap error, message string, args ...any) error { if len(args) == 0 { @@ -63,3 +82,16 @@ func NewAlreadyExistErrorf(message string, args ...any) error { func NewNotExistErrorf(message string, args ...any) error { return NewSilentWrapErrorf(ErrNotExist, message, args...) } + +// ErrWrapLocale wraps an err with a translation key and arguments +func ErrWrapLocale(err error, trKey string, trArgs ...any) error { + return LocaleWrap{err: err, TrKey: trKey, TrArgs: trArgs} +} + +func ErrAsLocale(err error) *LocaleWrap { + var e LocaleWrap + if errors.As(err, &e) { + return &e + } + return nil +} diff --git a/modules/util/slice.go b/modules/util/slice.go index 9c878c24be..da6886491e 100644 --- a/modules/util/slice.go +++ b/modules/util/slice.go @@ -71,3 +71,10 @@ func KeysOfMap[K comparable, V any](m map[K]V) []K { } return keys } + +func SliceNilAsEmpty[T any](a []T) []T { + if a == nil { + return []T{} + } + return a +} diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 8d43077f50..3f2ac68802 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -385,6 +385,7 @@ show_only_public=Zobrazeny pouze veřejné issues.in_your_repos=Ve vašich repozitářích + [explore] repos=Repozitáře users=Uživatelé diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 32f3a4bbcc..f1eada3990 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -384,6 +384,7 @@ show_only_public=Nur öffentliche anzeigen issues.in_your_repos=Eigene Repositories + [explore] repos=Repositories users=Benutzer diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index cab6320016..7fb4151f17 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -335,6 +335,7 @@ show_only_public=Εμφανίζονται μόνο δημόσια issues.in_your_repos=Στα αποθετήρια σας + [explore] repos=Αποθετήρια users=Χρήστες diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 2842ad16e7..c2c5b07b65 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -385,6 +385,13 @@ show_only_public = Showing only public issues.in_your_repos = In your repositories +guide_title = No Activity +guide_desc = You are currently not following any repositories or users, so there is no content to display. You can explore repositories or users of interest from the links below. +explore_repos = Explore repositories +explore_users = Explore users +empty_org = There are no organizations yet. +empty_repo = There are no repositories yet. + [explore] repos = Repositories users = Users @@ -1695,7 +1702,9 @@ issues.time_estimate_invalid = Time estimate format is invalid issues.start_tracking_history = started working %s issues.tracker_auto_close = Timer will be stopped automatically when this issue gets closed issues.tracking_already_started = `You have already started time tracking on another issue!` +issues.stop_tracking = Stop Timer issues.stop_tracking_history = worked for %[1]s %[2]s +issues.cancel_tracking = Discard issues.cancel_tracking_history = `canceled time tracking %s` issues.del_time = Delete this time log issues.add_time_history = added spent time %[1]s %[2]s diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index 359f490356..c399b1209c 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -333,6 +333,7 @@ show_only_public=Mostrar sólo repositorios públicos issues.in_your_repos=En tus repositorios + [explore] repos=Repositorios users=Usuarios diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index a84fcd9bd7..3d34e01722 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -256,6 +256,7 @@ show_only_public=نمایش دادن موارد عمومی issues.in_your_repos=در مخازن شما + [explore] repos=مخازن users=کاربران diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index 1c78f049f9..d78a06ae20 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -266,6 +266,7 @@ show_only_public=Näytetään vain julkiset issues.in_your_repos=Repoissasi + [explore] repos=Repot users=Käyttäjät diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 45cf43956a..9d652fabad 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -385,6 +385,13 @@ show_only_public=Afficher uniquement les dépôts publics issues.in_your_repos=Dans vos dépôts +guide_title=Aucune activité +guide_desc=Vous n’êtes actuellement abonné à aucun dépôt ou utilisateur et il n’y a donc aucun contenu à afficher. Les liens ci-dessous vous permettront d’explorer des dépôts ou des utilisateurs susceptibles de vous intéresser. +explore_repos=Explorer des dépôts +explore_users=Explorer des utilisateurs +empty_org=Il n’y a pas encore d’organisations. +empty_repo=Il n’y a pas encore de dépôts. + [explore] repos=Dépôts users=Utilisateurs @@ -2330,6 +2337,8 @@ settings.event_fork=Bifurcation settings.event_fork_desc=Dépôt bifurqué. settings.event_wiki=Wiki settings.event_wiki_desc=Page wiki créée, renommée, modifiée ou supprimée. +settings.event_statuses=Statuts +settings.event_statuses_desc=Statut de validation mis à jour depuis l’API. settings.event_release=Publication settings.event_release_desc=Publication publiée, mise à jour ou supprimée. settings.event_push=Soumission @@ -2877,6 +2886,14 @@ view_as_role=Voir en tant que %s view_as_public_hint=Vous visualisez le README en tant qu’utilisateur public. view_as_member_hint=Vous visualisez le README en tant que membre de cette organisation. +worktime=Temps de travail +worktime.date_range_start=Date de début +worktime.date_range_end=Date de fin +worktime.query=Demande +worktime.time=Durée +worktime.by_repositories=Par dépôts +worktime.by_milestones=Par jalons +worktime.by_members=Par membres [admin] maintenance=Maintenance diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini index 6708b07b97..cc7051fb65 100644 --- a/options/locale/locale_ga-IE.ini +++ b/options/locale/locale_ga-IE.ini @@ -385,6 +385,13 @@ show_only_public=Ag taispeáint poiblí amháin issues.in_your_repos=I do stórais +guide_title=Gan Ghníomhaíocht +guide_desc=Níl aon stórtha nó úsáideoirí á leanúint agat faoi láthair, mar sin níl aon ábhar le taispeáint. Is féidir leat stórtha nó úsáideoirí spéise a iniúchadh ó na naisc thíos. +explore_repos=Déan stórtha a iniúchadh +explore_users=Déan iniúchadh ar úsáideoirí +empty_org=Níl aon eagraíochtaí ann fós. +empty_repo=Níl aon stórtha ann fós. + [explore] repos=Stórais users=Úsáideoirí @@ -2330,6 +2337,8 @@ settings.event_fork=Forc settings.event_fork_desc=Forcadh stóras. settings.event_wiki=Vicí settings.event_wiki_desc=Leathanach Vicí cruthaithe, athainmnithe, curtha in eagar nó scriosta. +settings.event_statuses=Stádais +settings.event_statuses_desc=Nuashonraíodh Stádas Commit ón API. settings.event_release=Scaoileadh settings.event_release_desc=Scaoileadh foilsithe, nuashonraithe nó scriosta i stóras. settings.event_push=Brúigh diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index 579a030567..4767a48547 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -225,6 +225,7 @@ show_only_public=Csak publikus mutatása issues.in_your_repos=A tárolóidban + [explore] repos=Tárolók users=Felhasználók diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index 1fb1efbd16..2beade34ad 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -243,6 +243,7 @@ show_private=Pribadi issues.in_your_repos=Dalam repositori anda + [explore] repos=Repositori users=Pengguna diff --git a/options/locale/locale_is-IS.ini b/options/locale/locale_is-IS.ini index c8bcf9819e..98a8615130 100644 --- a/options/locale/locale_is-IS.ini +++ b/options/locale/locale_is-IS.ini @@ -240,6 +240,7 @@ show_only_public=Að sýna aðeins opinber issues.in_your_repos=Í hugbúnaðarsöfnum þínum + [explore] repos=Hugbúnaðarsöfn users=Notendur diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index f37598eec3..29512f47f3 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -277,6 +277,7 @@ show_only_public=Mostrando solo pubblici issues.in_your_repos=Nei tuoi repository + [explore] repos=Repository users=Utenti diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index df0c2b9524..bc29d530b4 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -385,6 +385,7 @@ show_only_public=公開のみ表示 issues.in_your_repos=あなたのリポジトリ + [explore] repos=リポジトリ users=ユーザー diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index 05a6d21335..a570a05274 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -212,6 +212,7 @@ show_private=비공개 issues.in_your_repos=당신의 저장소에 + [explore] repos=저장소 users=유저 diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 6fc6e53800..d2df0813ae 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -338,6 +338,7 @@ show_only_public=Attēlot tikai publiskos issues.in_your_repos=Jūsu repozitorijos + [explore] repos=Repozitoriji users=Lietotāji diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 9f61d34bc0..c23df29e99 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -276,6 +276,7 @@ show_only_public=Toon alleen opbenbaar issues.in_your_repos=In uw repositories + [explore] repos=Repositories users=Gebruikers diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index 958310a085..d03018c0d9 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -272,6 +272,7 @@ show_only_public=Wyświetlanie tylko publicznych issues.in_your_repos=W Twoich repozytoriach + [explore] repos=Repozytoria users=Użytkownicy diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 9df7e5e956..33aad76023 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -335,6 +335,7 @@ show_only_public=Mostrando somente públicos issues.in_your_repos=Em seus repositórios + [explore] repos=Repositórios users=Usuários diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index c51c0483db..3913e9cb48 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -385,6 +385,13 @@ show_only_public=Apresentando somente os públicos issues.in_your_repos=Nos seus repositórios +guide_title=Sem trabalho +guide_desc=Neste momento não está a seguir repositórios nem utilizadores, por isso não há conteúdo a apresentar. Pode explorar repositórios ou utilizadores de interesse a partir das ligações abaixo. +explore_repos=Explorar repositórios +explore_users=Explorar utilizadores +empty_org=Ainda não há organizações. +empty_repo=Ainda não há repositórios. + [explore] repos=Repositórios users=Utilizadores @@ -2330,6 +2337,8 @@ settings.event_fork=Derivar settings.event_fork_desc=Feita a derivação do repositório. settings.event_wiki=Wiki settings.event_wiki_desc=Página do wiki criada, renomeada, editada ou eliminada. +settings.event_statuses=Estados +settings.event_statuses_desc=Estado do cometimento modificado através da API. settings.event_release=Lançamento settings.event_release_desc=Lançamento publicado, modificado ou eliminado num repositório. settings.event_push=Enviar diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index f2b4e28405..0aa776b78a 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -333,6 +333,7 @@ show_only_public=Показаны только публичные issues.in_your_repos=В ваших репозиториях + [explore] repos=Репозитории users=Пользователи diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index 2a0b9394d5..80db8862fe 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -246,6 +246,7 @@ show_only_public=ප්‍රසිද්ධ පමණක් පෙන්වය issues.in_your_repos=ඔබගේ කෝෂ්ඨවල + [explore] repos=කෝෂ්ඨ users=පරිශීලකයින් diff --git a/options/locale/locale_sk-SK.ini b/options/locale/locale_sk-SK.ini index e323c2befa..53ea17b43e 100644 --- a/options/locale/locale_sk-SK.ini +++ b/options/locale/locale_sk-SK.ini @@ -328,6 +328,7 @@ show_only_public=Zobrazuje sa iba verejné issues.in_your_repos=Vo vašich repozitároch + [explore] repos=Repozitáre users=Používatelia diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index d43cf655ae..0d3d0f5fc4 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -233,6 +233,7 @@ show_only_public=Visar endast publika issues.in_your_repos=I dina utvecklingskataloger + [explore] repos=Utvecklingskataloger users=Användare diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 39f5bd5978..0454512402 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -380,6 +380,7 @@ show_only_public=Yalnızca açık olanlar gösteriliyor issues.in_your_repos=Depolarınızda + [explore] repos=Depolar users=Kullanıcılar diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 011a86abf6..25ebb843a9 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -260,6 +260,7 @@ show_only_public=Показано тільки публічні issues.in_your_repos=В ваших репозиторіях + [explore] repos=Репозиторії users=Користувачі diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index a1c3d75913..3b6aca4e92 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -384,6 +384,7 @@ show_only_public=只显示公开的 issues.in_your_repos=在您的仓库中 + [explore] repos=仓库 users=用户 diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini index eb11ee3a4a..3733d95ec8 100644 --- a/options/locale/locale_zh-HK.ini +++ b/options/locale/locale_zh-HK.ini @@ -118,6 +118,7 @@ show_private=私有庫 issues.in_your_repos=屬於該用戶儲存庫的 + [explore] repos=儲存庫 users=使用者 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index b84aaf1e1f..737f183f73 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -383,6 +383,7 @@ show_only_public=只顯示公開 issues.in_your_repos=在您的儲存庫中 + [explore] repos=儲存庫 users=使用者 diff --git a/package-lock.json b/package-lock.json index ac25c50dde..33b03aafce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,29 +5,29 @@ "packages": { "": { "dependencies": { - "@citation-js/core": "0.7.14", - "@citation-js/plugin-bibtex": "0.7.17", - "@citation-js/plugin-csl": "0.7.14", + "@citation-js/core": "0.7.18", + "@citation-js/plugin-bibtex": "0.7.18", + "@citation-js/plugin-csl": "0.7.18", "@citation-js/plugin-software-formats": "0.6.1", "@github/markdown-toolbar-element": "2.2.3", "@github/relative-time-element": "4.4.5", "@github/text-expander-element": "2.9.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", - "@primer/octicons": "19.14.0", + "@primer/octicons": "19.15.0", "@silverwind/vue3-calendar-heatmap": "2.0.6", "add-asset-webpack-plugin": "3.0.0", "ansi_up": "6.0.2", - "asciinema-player": "3.8.2", + "asciinema-player": "3.9.0", "chart.js": "4.4.7", "chartjs-adapter-dayjs-4": "1.0.4", "chartjs-plugin-zoom": "2.2.0", - "clippie": "4.1.4", + "clippie": "4.1.5", "cropperjs": "1.6.2", "css-loader": "7.1.2", "dayjs": "1.11.13", "dropzone": "6.0.0-beta.2", "easymde": "2.18.0", - "esbuild-loader": "4.2.2", + "esbuild-loader": "4.3.0", "escape-goat": "4.0.0", "fast-glob": "3.3.3", "htmx.org": "2.0.4", @@ -40,13 +40,13 @@ "minimatch": "10.0.1", "monaco-editor": "0.52.2", "monaco-editor-webpack-plugin": "7.1.0", - "pdfobject": "2.3.0", + "pdfobject": "2.3.1", "perfect-debounce": "1.0.0", - "postcss": "8.5.1", + "postcss": "8.5.2", "postcss-loader": "8.1.1", "postcss-nesting": "13.0.1", "sortablejs": "1.15.6", - "swagger-ui-dist": "5.18.2", + "swagger-ui-dist": "5.18.3", "tailwindcss": "3.4.17", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", @@ -60,7 +60,7 @@ "vue-bar-graph": "2.2.0", "vue-chartjs": "5.3.2", "vue-loader": "17.4.2", - "webpack": "5.97.1", + "webpack": "5.98.0", "webpack-cli": "6.0.1", "wrap-ansi": "9.0.0" }, @@ -68,8 +68,8 @@ "@eslint-community/eslint-plugin-eslint-comments": "4.4.1", "@playwright/test": "1.49.1", "@stoplight/spectral-cli": "6.14.2", - "@stylistic/eslint-plugin-js": "2.13.0", - "@stylistic/stylelint-plugin": "3.1.1", + "@stylistic/eslint-plugin-js": "3.1.0", + "@stylistic/stylelint-plugin": "3.1.2", "@types/dropzone": "5.7.9", "@types/jquery": "3.5.32", "@types/katex": "0.16.7", @@ -80,41 +80,40 @@ "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/toastify-js": "1.12.3", - "@typescript-eslint/eslint-plugin": "8.21.0", - "@typescript-eslint/parser": "8.21.0", + "@typescript-eslint/eslint-plugin": "8.24.0", + "@typescript-eslint/parser": "8.24.0", "@vitejs/plugin-vue": "5.2.1", + "@vitest/eslint-plugin": "1.1.31", "eslint": "8.57.0", - "eslint-import-resolver-typescript": "3.7.0", + "eslint-import-resolver-typescript": "3.8.0", "eslint-plugin-array-func": "4.0.0", "eslint-plugin-github": "5.0.2", "eslint-plugin-import-x": "4.6.1", "eslint-plugin-no-jquery": "3.1.0", "eslint-plugin-no-use-extend-native": "0.5.0", - "eslint-plugin-playwright": "2.1.0", + "eslint-plugin-playwright": "2.2.0", "eslint-plugin-regexp": "2.7.0", - "eslint-plugin-sonarjs": "3.0.1", + "eslint-plugin-sonarjs": "3.0.2", "eslint-plugin-unicorn": "56.0.1", - "eslint-plugin-vitest": "0.4.1", - "eslint-plugin-vitest-globals": "1.5.0", "eslint-plugin-vue": "9.32.0", "eslint-plugin-vue-scoped-css": "2.9.0", "eslint-plugin-wc": "2.2.0", - "happy-dom": "16.7.2", - "markdownlint-cli": "0.43.0", + "happy-dom": "17.1.0", + "markdownlint-cli": "0.44.0", "nolyfill": "1.0.43", "postcss-html": "1.8.0", "stylelint": "16.14.1", "stylelint-config-recommended": "15.0.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.7", - "stylelint-define-config": "16.14.0", + "stylelint-define-config": "16.14.1", "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.3.2", - "type-fest": "4.33.0", - "updates": "16.4.1", - "vite-string-plugin": "1.4.3", - "vitest": "3.0.3", - "vue-tsc": "2.2.0" + "type-fest": "4.34.1", + "updates": "16.4.2", + "vite-string-plugin": "1.4.4", + "vitest": "3.0.5", + "vue-tsc": "2.2.2" }, "engines": { "node": ">= 18.0.0" @@ -132,46 +131,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@antfu/install-pkg": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.4.1.tgz", - "integrity": "sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.0.0.tgz", + "integrity": "sha512-xvX6P/lo1B3ej0OsaErAjqgFYzYVcJpamjLAFLYh9vRJngBrMoUG7aVnrGTeqM7yxbyTD5p3F2+0/QUEh8Vzhw==", "license": "MIT", "dependencies": { - "package-manager-detector": "^0.2.0", - "tinyexec": "^0.3.0" + "package-manager-detector": "^0.2.8", + "tinyexec": "^0.3.2" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@antfu/utils": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", - "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-8.1.0.tgz", + "integrity": "sha512-XPR7Jfwp0FFl/dFYPX8ZjpmU4/1mIXTjnZ1ba48BLMyKOV62/tiRjdsFcPs2hsYcSud4tzk7w3a3LjX8Fu3huA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@asyncapi/specs": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-6.8.0.tgz", - "integrity": "sha512-1i6xs8+IOh6U5T7yH+bCMGQBF+m7kP/NpwyAlt++XaDQutoGCgACf24mQBgcDVqDWWoY81evQv+9ABvw0BviVg==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-6.8.1.tgz", + "integrity": "sha512-czHoAk3PeXTLR+X8IUaD+IpT+g+zUvkcgMDJVothBsan+oHN3jfcFcFUNdOPAAFoUCQN1hXF1dWuphWy05THlA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -192,348 +177,11 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz", - "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", - "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.0", - "@babel/generator": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.0", - "@babel/parser": "^7.26.0", - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.26.0", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/eslint-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.25.9.tgz", - "integrity": "sha512-5UXfgpK0j0Xr/xIdgdLEhOFxaDZ0bRPWJJchRpqOSur/3rZoPbqqki5mm0p4NE2cs28krBEiSM2MB7//afRSQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || >=14.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0", - "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10" - } - }, - "node_modules/@babel/eslint-parser/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", - "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.26.5", - "@babel/types": "^7.26.5", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", - "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", - "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.25.9", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", - "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "regexpu-core": "^6.2.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", - "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", - "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-wrap-function": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", - "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.26.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" }, "node_modules/@babel/helper-string-parser": { "version": "7.25.9", @@ -553,52 +201,13 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", - "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", - "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/parser": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.5.tgz", - "integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.8.tgz", + "integrity": "sha512-TZIQ25pkSoaKEYYaHbbxkfL36GNsQ6iFiBbeuzAkLnXayKR1yP1zFe+NxuZWWsUyvt8icPU9CCq0sgWGXR1GEw==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.5" + "@babel/types": "^7.26.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -607,1303 +216,10 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", - "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", - "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", - "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", - "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz", - "integrity": "sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-syntax-decorators": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.9.tgz", - "integrity": "sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-flow": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.26.0.tgz", - "integrity": "sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", - "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", - "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", - "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", - "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", - "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", - "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", - "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", - "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", - "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", - "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", - "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/traverse": "^7.25.9", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", - "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/template": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", - "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", - "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", - "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", - "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", - "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", - "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.26.5.tgz", - "integrity": "sha512-eGK26RsbIkYUns3Y8qKl362juDDYK+wEdPGHGrhzUl6CewZFo55VZ7hg+CyMFU4dd5QQakBN86nBMpRsFpRvbQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/plugin-syntax-flow": "^7.26.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", - "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", - "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", - "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", - "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", - "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", - "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", - "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", - "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", - "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", - "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", - "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.26.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", - "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", - "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", - "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", - "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", - "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", - "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", - "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", - "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", - "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", - "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", - "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", - "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", - "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", - "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "regenerator-transform": "^0.15.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", - "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", - "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", - "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", - "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", - "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", - "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", - "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", - "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", - "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", - "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", - "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", - "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.26.0", - "@babel/plugin-syntax-import-attributes": "^7.26.0", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.25.9", - "@babel/plugin-transform-async-generator-functions": "^7.25.9", - "@babel/plugin-transform-async-to-generator": "^7.25.9", - "@babel/plugin-transform-block-scoped-functions": "^7.25.9", - "@babel/plugin-transform-block-scoping": "^7.25.9", - "@babel/plugin-transform-class-properties": "^7.25.9", - "@babel/plugin-transform-class-static-block": "^7.26.0", - "@babel/plugin-transform-classes": "^7.25.9", - "@babel/plugin-transform-computed-properties": "^7.25.9", - "@babel/plugin-transform-destructuring": "^7.25.9", - "@babel/plugin-transform-dotall-regex": "^7.25.9", - "@babel/plugin-transform-duplicate-keys": "^7.25.9", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-dynamic-import": "^7.25.9", - "@babel/plugin-transform-exponentiation-operator": "^7.25.9", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-for-of": "^7.25.9", - "@babel/plugin-transform-function-name": "^7.25.9", - "@babel/plugin-transform-json-strings": "^7.25.9", - "@babel/plugin-transform-literals": "^7.25.9", - "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", - "@babel/plugin-transform-member-expression-literals": "^7.25.9", - "@babel/plugin-transform-modules-amd": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.25.9", - "@babel/plugin-transform-modules-systemjs": "^7.25.9", - "@babel/plugin-transform-modules-umd": "^7.25.9", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-new-target": "^7.25.9", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", - "@babel/plugin-transform-numeric-separator": "^7.25.9", - "@babel/plugin-transform-object-rest-spread": "^7.25.9", - "@babel/plugin-transform-object-super": "^7.25.9", - "@babel/plugin-transform-optional-catch-binding": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9", - "@babel/plugin-transform-private-methods": "^7.25.9", - "@babel/plugin-transform-private-property-in-object": "^7.25.9", - "@babel/plugin-transform-property-literals": "^7.25.9", - "@babel/plugin-transform-regenerator": "^7.25.9", - "@babel/plugin-transform-regexp-modifiers": "^7.26.0", - "@babel/plugin-transform-reserved-words": "^7.25.9", - "@babel/plugin-transform-shorthand-properties": "^7.25.9", - "@babel/plugin-transform-spread": "^7.25.9", - "@babel/plugin-transform-sticky-regex": "^7.25.9", - "@babel/plugin-transform-template-literals": "^7.25.9", - "@babel/plugin-transform-typeof-symbol": "^7.25.9", - "@babel/plugin-transform-unicode-escapes": "^7.25.9", - "@babel/plugin-transform-unicode-property-regex": "^7.25.9", - "@babel/plugin-transform-unicode-regex": "^7.25.9", - "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.6", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.38.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-flow": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.25.9.tgz", - "integrity": "sha512-EASHsAhE+SSlEzJ4bzfusnXSHiU+JfAYzj+jbw2vgQKgq5HrUr8qs+vgtiEL5dOH6sEweI+PNt2D7AqrDSHyqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-transform-flow-strip-types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/preset-react": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.26.3.tgz", - "integrity": "sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-transform-react-display-name": "^7.25.9", - "@babel/plugin-transform-react-jsx": "^7.25.9", - "@babel/plugin-transform-react-jsx-development": "^7.25.9", - "@babel/plugin-transform-react-pure-annotations": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", - "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz", + "integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==", "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -1912,54 +228,10 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.5.tgz", - "integrity": "sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.5", - "@babel/parser": "^7.26.5", - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.5", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz", - "integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.8.tgz", + "integrity": "sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -2015,9 +287,9 @@ "license": "Apache-2.0" }, "node_modules/@citation-js/core": { - "version": "0.7.14", - "resolved": "https://registry.npmjs.org/@citation-js/core/-/core-0.7.14.tgz", - "integrity": "sha512-dgeGqYDSQmn2MtnWZkwPGpJQPh43yr1lAAr9jl1NJ9pIY1RXUQxtlAUZVur0V9PHdbfQC+kkvB1KC3VpgVV3MA==", + "version": "0.7.18", + "resolved": "https://registry.npmjs.org/@citation-js/core/-/core-0.7.18.tgz", + "integrity": "sha512-EjLuZWA5156dIFGdF7OnyPyWFBW43B8Ckje6Sn/W2RFxHDu0oACvW4/6TNgWT80jhEA4bVFm7ahrZe9MJ2B2UQ==", "license": "MIT", "dependencies": { "@citation-js/date": "^0.5.0", @@ -2048,9 +320,9 @@ } }, "node_modules/@citation-js/plugin-bibtex": { - "version": "0.7.17", - "resolved": "https://registry.npmjs.org/@citation-js/plugin-bibtex/-/plugin-bibtex-0.7.17.tgz", - "integrity": "sha512-pyMW6UR6iMPCk1mVwagNHabprajOCQO+TibxKI6ymdv5VOX3zoqeQF0utwjFnViquL/BZfM5SGUZCQdu+ZZYag==", + "version": "0.7.18", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-bibtex/-/plugin-bibtex-0.7.18.tgz", + "integrity": "sha512-TdsZSMpgpfcx2NMPu0KiulEoecllwT5EtRUzAJl2pDsdPD1tUqqbyj/NBi0l8fwNy1r7WwAqSFGiqGPjQWpFdg==", "license": "MIT", "dependencies": { "@citation-js/date": "^0.5.0", @@ -2078,9 +350,9 @@ } }, "node_modules/@citation-js/plugin-csl": { - "version": "0.7.14", - "resolved": "https://registry.npmjs.org/@citation-js/plugin-csl/-/plugin-csl-0.7.14.tgz", - "integrity": "sha512-7AKB8lMz1IqdtoE33NnWIpteLYMuSl3xqT+Cax7sQKwAIJEoq2HBmb43Ja8xQQ36nREAupQJv1V6XksIAmYnCg==", + "version": "0.7.18", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-csl/-/plugin-csl-0.7.18.tgz", + "integrity": "sha512-cJcOdEZurmtIxNj0d4cOERHpVQJB/mN3YPSDNqfI/xTFRN3bWDpFAsaqubPtMO2ZPpoDS+ZGIP1kggbwCfMmlA==", "license": "MIT", "dependencies": { "@citation-js/date": "^0.5.0", @@ -2248,9 +520,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", "cpu": [ "ppc64" ], @@ -2260,13 +532,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", "cpu": [ "arm" ], @@ -2276,13 +548,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", "cpu": [ "arm64" ], @@ -2292,13 +564,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", "cpu": [ "x64" ], @@ -2308,13 +580,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", "cpu": [ "arm64" ], @@ -2324,13 +596,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", "cpu": [ "x64" ], @@ -2340,13 +612,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", "cpu": [ "arm64" ], @@ -2356,13 +628,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", "cpu": [ "x64" ], @@ -2372,13 +644,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", "cpu": [ "arm" ], @@ -2388,13 +660,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", "cpu": [ "arm64" ], @@ -2404,13 +676,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", "cpu": [ "ia32" ], @@ -2420,13 +692,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", "cpu": [ "loong64" ], @@ -2436,13 +708,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", "cpu": [ "mips64el" ], @@ -2452,13 +724,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", "cpu": [ "ppc64" ], @@ -2468,13 +740,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", "cpu": [ "riscv64" ], @@ -2484,13 +756,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", "cpu": [ "s390x" ], @@ -2500,13 +772,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", "cpu": [ "x64" ], @@ -2516,17 +788,16 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", - "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2537,9 +808,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", "cpu": [ "x64" ], @@ -2549,17 +820,16 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", - "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2570,9 +840,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", "cpu": [ "x64" ], @@ -2582,13 +852,13 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", "cpu": [ "x64" ], @@ -2598,13 +868,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", "cpu": [ "arm64" ], @@ -2614,13 +884,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", "cpu": [ "ia32" ], @@ -2630,13 +900,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", "cpu": [ "x64" ], @@ -2646,7 +916,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-plugin-eslint-comments": { @@ -2928,25 +1198,25 @@ "license": "MIT" }, "node_modules/@iconify/utils": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.2.1.tgz", - "integrity": "sha512-0/7J7hk4PqXmxo5PDBDxmnecw5PxklZJfNjIVG9FM0mEfVrvfudS22rYWsqVk6gR3UJ/mSYS90X4R3znXnqfNA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.3.0.tgz", + "integrity": "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==", "license": "MIT", "dependencies": { - "@antfu/install-pkg": "^0.4.1", - "@antfu/utils": "^0.7.10", + "@antfu/install-pkg": "^1.0.0", + "@antfu/utils": "^8.1.0", "@iconify/types": "^2.0.0", "debug": "^4.4.0", - "globals": "^15.13.0", + "globals": "^15.14.0", "kolorist": "^1.8.0", - "local-pkg": "^0.5.1", - "mlly": "^1.7.3" + "local-pkg": "^1.0.0", + "mlly": "^1.7.4" } }, "node_modules/@iconify/utils/node_modules/globals": { - "version": "15.14.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", - "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", "license": "MIT", "engines": { "node": ">=18" @@ -3243,40 +1513,6 @@ "langium": "3.0.0" } }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-scope": "5.1.1" - } - }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3379,9 +1615,9 @@ } }, "node_modules/@primer/octicons": { - "version": "19.14.0", - "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.14.0.tgz", - "integrity": "sha512-9Ovw/xcUFHC/zbsNhr/Hkp1+m9XnNeQvnGHDHrI5vhlf6PRZVzSsdMnesV2xCzQh7jXP3EVRcaeXsUGlsZrfcA==", + "version": "19.15.0", + "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.15.0.tgz", + "integrity": "sha512-WaZ3Qno+MJZh0skrZ8nEfYJdrqTLuxxHxJqHjhc/QRLNojcHq57njtqOt9H8d0dCGAem2stQJ3nAK8893Hz5pA==", "license": "MIT", "dependencies": { "object-assign": "^4.1.1" @@ -3435,9 +1671,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.31.0.tgz", - "integrity": "sha512-9NrR4033uCbUBRgvLcBrJofa2KY9DzxL2UKZ1/4xA/mnTNyhZCWBuD8X3tPm1n4KxcgaraOYgrFKSgwjASfmlA==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz", + "integrity": "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==", "cpu": [ "arm" ], @@ -3449,9 +1685,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.31.0.tgz", - "integrity": "sha512-iBbODqT86YBFHajxxF8ebj2hwKm1k8PTBQSojSt3d1FFt1gN+xf4CowE47iN0vOSdnd+5ierMHBbu/rHc7nq5g==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz", + "integrity": "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==", "cpu": [ "arm64" ], @@ -3463,9 +1699,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.31.0.tgz", - "integrity": "sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz", + "integrity": "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==", "cpu": [ "arm64" ], @@ -3477,9 +1713,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.31.0.tgz", - "integrity": "sha512-hrWL7uQacTEF8gdrQAqcDy9xllQ0w0zuL1wk1HV8wKGSGbKPVjVUv/DEwT2+Asabf8Dh/As+IvfdU+H8hhzrQQ==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz", + "integrity": "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==", "cpu": [ "x64" ], @@ -3491,9 +1727,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.31.0.tgz", - "integrity": "sha512-S2oCsZ4hJviG1QjPY1h6sVJLBI6ekBeAEssYKad1soRFv3SocsQCzX6cwnk6fID6UQQACTjeIMB+hyYrFacRew==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz", + "integrity": "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==", "cpu": [ "arm64" ], @@ -3505,9 +1741,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.31.0.tgz", - "integrity": "sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz", + "integrity": "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==", "cpu": [ "x64" ], @@ -3519,9 +1755,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.31.0.tgz", - "integrity": "sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz", + "integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==", "cpu": [ "arm" ], @@ -3533,9 +1769,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.31.0.tgz", - "integrity": "sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz", + "integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==", "cpu": [ "arm" ], @@ -3547,9 +1783,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.31.0.tgz", - "integrity": "sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz", + "integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==", "cpu": [ "arm64" ], @@ -3561,9 +1797,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.31.0.tgz", - "integrity": "sha512-kpQXQ0UPFeMPmPYksiBL9WS/BDiQEjRGMfklVIsA0Sng347H8W2iexch+IEwaR7OVSKtr2ZFxggt11zVIlZ25g==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz", + "integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==", "cpu": [ "arm64" ], @@ -3575,9 +1811,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.31.0.tgz", - "integrity": "sha512-pMlxLjt60iQTzt9iBb3jZphFIl55a70wexvo8p+vVFK+7ifTRookdoXX3bOsRdmfD+OKnMozKO6XM4zR0sHRrQ==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz", + "integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==", "cpu": [ "loong64" ], @@ -3589,9 +1825,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.31.0.tgz", - "integrity": "sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz", + "integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==", "cpu": [ "ppc64" ], @@ -3603,9 +1839,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.31.0.tgz", - "integrity": "sha512-wal2Tc8O5lMBtoePLBYRKj2CImUCJ4UNGJlLwspx7QApYny7K1cUYlzQ/4IGQBLmm+y0RS7dwc3TDO/pmcneTw==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz", + "integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==", "cpu": [ "riscv64" ], @@ -3617,9 +1853,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.31.0.tgz", - "integrity": "sha512-O1o5EUI0+RRMkK9wiTVpk2tyzXdXefHtRTIjBbmFREmNMy7pFeYXCFGbhKFwISA3UOExlo5GGUuuj3oMKdK6JQ==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz", + "integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==", "cpu": [ "s390x" ], @@ -3631,9 +1867,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.31.0.tgz", - "integrity": "sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz", + "integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==", "cpu": [ "x64" ], @@ -3645,9 +1881,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.31.0.tgz", - "integrity": "sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz", + "integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==", "cpu": [ "x64" ], @@ -3659,9 +1895,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.31.0.tgz", - "integrity": "sha512-JuhN2xdI/m8Hr+aVO3vspO7OQfUFO6bKLIRTAy0U15vmWjnZDLrEgCZ2s6+scAYaQVpYSh9tZtRijApw9IXyMw==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz", + "integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==", "cpu": [ "arm64" ], @@ -3673,9 +1909,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.31.0.tgz", - "integrity": "sha512-U1xZZXYkvdf5MIWmftU8wrM5PPXzyaY1nGCI4KI4BFfoZxHamsIe+BtnPLIvvPykvQWlVbqUXdLa4aJUuilwLQ==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz", + "integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==", "cpu": [ "ia32" ], @@ -3687,9 +1923,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.31.0.tgz", - "integrity": "sha512-ul8rnCsUumNln5YWwz0ted2ZHFhzhRRnkpBZ+YRuHoRAlUji9KChpOUOndY7uykrPEPXVbHLlsdo6v5yXo/TXw==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz", + "integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==", "cpu": [ "x64" ], @@ -4246,9 +2482,9 @@ } }, "node_modules/@stylistic/eslint-plugin-js": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.13.0.tgz", - "integrity": "sha512-GPPDK4+fcbsQD58a3abbng2Dx+jBoxM5cnYjBM4T24WFZRZdlNSKvR19TxP8CPevzMOodQ9QVzNeqWvMXzfJRA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-3.1.0.tgz", + "integrity": "sha512-lQktsOiCr8S6StG29C5fzXYxLOD6ID1rp4j6TRS+E/qY1xd59Fm7dy5qm9UauJIEoSTlYx6yGsCHYh5UkgXPyg==", "dev": true, "license": "MIT", "dependencies": { @@ -4263,9 +2499,9 @@ } }, "node_modules/@stylistic/stylelint-plugin": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.1.tgz", - "integrity": "sha512-XagAHHIa528EvyGybv8EEYGK5zrVW74cHpsjhtovVATbhDRuJYfE+X4HCaAieW9lCkwbX6L+X0I4CiUG3w/hFw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.2.tgz", + "integrity": "sha512-tylFJGMQo62alGazK74MNxFjMagYOHmBZiePZFOJK2n13JZta0uVkB3Bh5qodUmOLtRH+uxH297EibK14UKm8g==", "dev": true, "license": "MIT", "dependencies": { @@ -4473,9 +2709,9 @@ } }, "node_modules/@types/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", "license": "MIT" }, "node_modules/@types/d3-polygon": { @@ -4497,9 +2733,9 @@ "license": "MIT" }, "node_modules/@types/d3-scale": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", - "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", "license": "MIT", "dependencies": { "@types/d3-time": "*" @@ -4563,6 +2799,16 @@ "@types/d3-selection": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/doctrine": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", @@ -4617,9 +2863,9 @@ "license": "MIT" }, "node_modules/@types/geojson": { - "version": "7946.0.15", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.15.tgz", - "integrity": "sha512-9oSxFzDCT2Rj6DfcHF8G++jxBKS7mBqXl5xrRW+Kbvjry6Uduya2iiwqHPhVXpasAVMBYKkEPGgKhd3+/HZ6xA==", + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "license": "MIT" }, "node_modules/@types/hammerjs": { @@ -4681,10 +2927,17 @@ "integrity": "sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==", "license": "MIT" }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { - "version": "22.10.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", - "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==", + "version": "22.13.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz", + "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" @@ -4793,6 +3046,13 @@ "source-map": "^0.6.1" } }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/urijs": { "version": "1.19.25", "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.25.tgz", @@ -4838,21 +3098,21 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.21.0.tgz", - "integrity": "sha512-eTH+UOR4I7WbdQnG4Z48ebIA6Bgi7WO8HvFEneeYBxG8qCOYgTOFPSg6ek9ITIDvGjDQzWHcoWHCDO2biByNzA==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.0.tgz", + "integrity": "sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.21.0", - "@typescript-eslint/type-utils": "8.21.0", - "@typescript-eslint/utils": "8.21.0", - "@typescript-eslint/visitor-keys": "8.21.0", + "@typescript-eslint/scope-manager": "8.24.0", + "@typescript-eslint/type-utils": "8.24.0", + "@typescript-eslint/utils": "8.24.0", + "@typescript-eslint/visitor-keys": "8.24.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4868,16 +3128,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.21.0.tgz", - "integrity": "sha512-Wy+/sdEH9kI3w9civgACwabHbKl+qIOu0uFZ9IMKzX3Jpv9og0ZBJrZExGrPpFAY7rWsXuxs5e7CPPP17A4eYA==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.0.tgz", + "integrity": "sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.21.0", - "@typescript-eslint/types": "8.21.0", - "@typescript-eslint/typescript-estree": "8.21.0", - "@typescript-eslint/visitor-keys": "8.21.0", + "@typescript-eslint/scope-manager": "8.24.0", + "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/typescript-estree": "8.24.0", + "@typescript-eslint/visitor-keys": "8.24.0", "debug": "^4.3.4" }, "engines": { @@ -4893,14 +3153,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.21.0.tgz", - "integrity": "sha512-G3IBKz0/0IPfdeGRMbp+4rbjfSSdnGkXsM/pFZA8zM9t9klXDnB/YnKOBQ0GoPmoROa4bCq2NeHgJa5ydsQ4mA==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.0.tgz", + "integrity": "sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.21.0", - "@typescript-eslint/visitor-keys": "8.21.0" + "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/visitor-keys": "8.24.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4911,16 +3171,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.21.0.tgz", - "integrity": "sha512-95OsL6J2BtzoBxHicoXHxgk3z+9P3BEcQTpBKriqiYzLKnM2DeSqs+sndMKdamU8FosiadQFT3D+BSL9EKnAJQ==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.0.tgz", + "integrity": "sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.21.0", - "@typescript-eslint/utils": "8.21.0", + "@typescript-eslint/typescript-estree": "8.24.0", + "@typescript-eslint/utils": "8.24.0", "debug": "^4.3.4", - "ts-api-utils": "^2.0.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4935,9 +3195,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.21.0.tgz", - "integrity": "sha512-PAL6LUuQwotLW2a8VsySDBwYMm129vFm4tMVlylzdoTybTHaAi0oBp7Ac6LhSrHHOdLM3efH+nAR6hAWoMF89A==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.0.tgz", + "integrity": "sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==", "dev": true, "license": "MIT", "engines": { @@ -4949,20 +3209,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.21.0.tgz", - "integrity": "sha512-x+aeKh/AjAArSauz0GiQZsjT8ciadNMHdkUSwBB9Z6PrKc/4knM4g3UfHml6oDJmKC88a6//cdxnO/+P2LkMcg==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.0.tgz", + "integrity": "sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.21.0", - "@typescript-eslint/visitor-keys": "8.21.0", + "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/visitor-keys": "8.24.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^2.0.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4992,16 +3252,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.21.0.tgz", - "integrity": "sha512-xcXBfcq0Kaxgj7dwejMbFyq7IOHgpNMtVuDveK7w3ZGwG9owKzhALVwKpTF2yrZmEwl9SWdetf3fxNzJQaVuxw==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.0.tgz", + "integrity": "sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.21.0", - "@typescript-eslint/types": "8.21.0", - "@typescript-eslint/typescript-estree": "8.21.0" + "@typescript-eslint/scope-manager": "8.24.0", + "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/typescript-estree": "8.24.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5016,13 +3276,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.21.0.tgz", - "integrity": "sha512-BkLMNpdV6prozk8LlyK/SOoWLmUFi+ZD+pcqti9ILCbVvHGk1ui1g4jJOc2WDLaeExz2qWwojxlPce5PljcT3w==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.0.tgz", + "integrity": "sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.21.0", + "@typescript-eslint/types": "8.24.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -5034,9 +3294,9 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz", - "integrity": "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "dev": true, "license": "ISC" }, @@ -5054,15 +3314,36 @@ "vue": "^3.2.25" } }, + "node_modules/@vitest/eslint-plugin": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.31.tgz", + "integrity": "sha512-xlsLr+e+AXZ/00eVZCtNmMeCJoJaRCoLDiAgLcxgQjSS1EertieB2MUHf8xIqPKs9lECc/UpL+y1xDcpvi02hw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@typescript-eslint/utils": ">= 8.0", + "eslint": ">= 8.57.0", + "typescript": ">= 5.0.0", + "vitest": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.3.tgz", - "integrity": "sha512-SbRCHU4qr91xguu+dH3RUdI5dC86zm8aZWydbp961aIR7G8OYNN6ZiayFuf9WAngRbFOfdrLHCGgXTj3GtoMRQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.5.tgz", + "integrity": "sha512-nNIOqupgZ4v5jWuQx2DSlHLEs7Q4Oh/7AYwNyE+k0UQzG7tSmjPXShUikn1mpNGzYEN2jJbTvLejwShMitovBA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.3", - "@vitest/utils": "3.0.3", + "@vitest/spy": "3.0.5", + "@vitest/utils": "3.0.5", "chai": "^5.1.2", "tinyrainbow": "^2.0.0" }, @@ -5071,13 +3352,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.3.tgz", - "integrity": "sha512-XT2XBc4AN9UdaxJAeIlcSZ0ILi/GzmG5G8XSly4gaiqIvPV3HMTSIDZWJVX6QRJ0PX1m+W8Cy0K9ByXNb/bPIA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.5.tgz", + "integrity": "sha512-CLPNBFBIE7x6aEGbIjaQAX03ZZlBMaWwAjBdMkIf/cAn6xzLTiM3zYqO/WAbieEjsAZir6tO71mzeHZoodThvw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.3", + "@vitest/spy": "3.0.5", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -5125,9 +3406,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.3.tgz", - "integrity": "sha512-gCrM9F7STYdsDoNjGgYXKPq4SkSxwwIU5nkaQvdUxiQ0EcNlez+PdKOVIsUJvh9P9IeIFmjn4IIREWblOBpP2Q==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.5.tgz", + "integrity": "sha512-CjUtdmpOcm4RVtB+up8r2vVDLR16Mgm/bYdkGFe3Yj/scRfCpbSi2W/BDSDcFK7ohw8UXvjMbOp9H4fByd/cOA==", "dev": true, "license": "MIT", "dependencies": { @@ -5138,29 +3419,29 @@ } }, "node_modules/@vitest/runner": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.3.tgz", - "integrity": "sha512-Rgi2kOAk5ZxWZlwPguRJFOBmWs6uvvyAAR9k3MvjRvYrG7xYvKChZcmnnpJCS98311CBDMqsW9MzzRFsj2gX3g==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.5.tgz", + "integrity": "sha512-BAiZFityFexZQi2yN4OX3OkJC6scwRo8EhRB0Z5HIGGgd2q+Nq29LgHU/+ovCtd0fOfXj5ZI6pwdlUmC5bpi8A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.0.3", - "pathe": "^2.0.1" + "@vitest/utils": "3.0.5", + "pathe": "^2.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.3.tgz", - "integrity": "sha512-kNRcHlI4txBGztuJfPEJ68VezlPAXLRT1u5UCx219TU3kOG2DplNxhWLwDf2h6emwmTPogzLnGVwP6epDaJN6Q==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.5.tgz", + "integrity": "sha512-GJPZYcd7v8QNUJ7vRvLDmRwl+a1fGg4T/54lZXe+UOGy47F9yUfE18hRCtXL5aHN/AONu29NGzIXSVFh9K0feA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.3", + "@vitest/pretty-format": "3.0.5", "magic-string": "^0.30.17", - "pathe": "^2.0.1" + "pathe": "^2.0.2" }, "funding": { "url": "https://opencollective.com/vitest" @@ -5177,9 +3458,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.3.tgz", - "integrity": "sha512-7/dgux8ZBbF7lEIKNnEqQlyRaER9nkAL9eTmdKJkDO3hS8p59ATGwKOCUDHcBLKr7h/oi/6hP+7djQk8049T2A==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.5.tgz", + "integrity": "sha512-5fOzHj0WbUNqPK6blI/8VzZdkBlQLnT25knX0r4dbZI9qoZDf3qAdjoMmDcLG5A83W6oUUFJgUd0EYBc2P5xqg==", "dev": true, "license": "MIT", "dependencies": { @@ -5190,13 +3471,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.3.tgz", - "integrity": "sha512-f+s8CvyzPtMFY1eZKkIHGhPsQgYo5qCm6O8KZoim9qm1/jT64qBgGpO5tHscNH6BzRHM+edLNOP+3vO8+8pE/A==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.5.tgz", + "integrity": "sha512-N9AX0NUoUtVwKwy21JtwzaqR5L5R5A99GAbrHfCCXK1lp593i/3AZAXhSP43wRQuxYsflrdzEfXZFo1reR1Nkg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.3", + "@vitest/pretty-format": "3.0.5", "loupe": "^3.1.2", "tinyrainbow": "^2.0.0" }, @@ -5304,9 +3585,9 @@ } }, "node_modules/@vue/language-core": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.0.tgz", - "integrity": "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.2.tgz", + "integrity": "sha512-QotO41kurE5PLf3vrNgGTk3QswO2PdUFjBwNiOi7zMmGhwb25PSTh9hD1MCgKC06AVv+8sZQvlL3Do4TTVHSiQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5314,7 +3595,7 @@ "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", - "alien-signals": "^0.4.9", + "alien-signals": "^1.0.3", "minimatch": "^9.0.3", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1" @@ -5717,9 +3998,9 @@ } }, "node_modules/alien-signals": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.4.14.tgz", - "integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.3.tgz", + "integrity": "sha512-zQOh3wAYK5ujENxvBBR3CFGF/b6afaSzZ/c9yNhJ1ENrGHETvpUuKQsa93Qrclp0+PzTF93MaZ7scVp1uUozhA==", "dev": true, "license": "MIT" }, @@ -5890,9 +4171,9 @@ } }, "node_modules/asciinema-player": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/asciinema-player/-/asciinema-player-3.8.2.tgz", - "integrity": "sha512-Lgcnj9u/H6sRpGRX1my7Azcay6llLmB/GVkCGcDbPwdTVTisS1ir8SQ9jRWRvjlLUjpSJkN0euruvy3sLRM8tw==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/asciinema-player/-/asciinema-player-3.9.0.tgz", + "integrity": "sha512-SXVFImVzeNr8ZUdNIHABGuzlbnGWTKy245AquAjODsAnv+Lp6vxjYGN0LfA8ns30tnx/ag/bMrTbLq13TpHE6w==", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.21.0", @@ -5982,58 +4263,6 @@ "node": ">= 0.4" } }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.12", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", - "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.3", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", - "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2", - "core-js-compat": "^3.38.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", - "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.3" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -6212,13 +4441,13 @@ } }, "node_modules/cacheable": { - "version": "1.8.7", - "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.8.7.tgz", - "integrity": "sha512-AbfG7dAuYNjYxFUtL1lAqmlWdxczCJ47w7cFjhGcnGnUdwSo6VgmSojfoW3tUI12HUkgTJ5kqj78yyq6TsFtlg==", + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.8.8.tgz", + "integrity": "sha512-OE1/jlarWxROUIpd0qGBSKFLkNsotY8pt4GeiVErUYh/NUeTNrT+SBksUgllQv4m6a0W/VZsLuiHb88maavqEw==", "dev": true, "license": "MIT", "dependencies": { - "hookified": "^1.6.0", + "hookified": "^1.7.0", "keyv": "^5.2.3" } }, @@ -6251,9 +4480,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001695", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001695.tgz", - "integrity": "sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==", + "version": "1.0.30001699", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001699.tgz", + "integrity": "sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==", "funding": [ { "type": "opencollective", @@ -6303,6 +4532,39 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chart.js": { "version": "4.4.7", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.7.tgz", @@ -6468,9 +4730,9 @@ } }, "node_modules/clippie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/clippie/-/clippie-4.1.4.tgz", - "integrity": "sha512-oTjh7B58LKvP8SAIVSQAUQ1QHpZhneOeEbVMORCcTK3/S78YUnppDC4DhuzNEt51oeeVVv8INv08Ua6FzyxYhA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/clippie/-/clippie-4.1.5.tgz", + "integrity": "sha512-ZNGL7+p8HZWM2G8fecb3N7izoagXGktTg7nSYxzBID4OAtOBU7SUFvI9EL/0IZpy9VkU8AY6Zp8AvaH4/Xj7pA==", "license": "BSD-2-Clause" }, "node_modules/cliui": { @@ -6613,13 +4875,6 @@ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", "license": "MIT" }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, "node_modules/core-js-compat": { "version": "3.40.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz", @@ -7396,6 +5651,20 @@ } } }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/decode-uri-component": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", @@ -7452,6 +5721,30 @@ "node": ">= 0.6.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -7544,9 +5837,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.3.tgz", - "integrity": "sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz", + "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -7597,9 +5890,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.84", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.84.tgz", - "integrity": "sha512-I+DQ8xgafao9Ha6y0qjHHvpZ9OfyA1qKlkHkjywxzniORU2awxyz7f/iVJcULmrF2yrM3nHQf+iDjJtbbexd/g==", + "version": "1.5.99", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.99.tgz", + "integrity": "sha512-77c/+fCyL2U+aOyqfIFi89wYLBeSTCs55xCZL0oFH0KjqsvSvyh6AdQ+UIl1vgpnQQE6g+/KK8hOIupH6VwPtg==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -7618,9 +5911,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", - "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -7693,50 +5986,52 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", "hasInstallScript": true, "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" } }, "node_modules/esbuild-loader": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/esbuild-loader/-/esbuild-loader-4.2.2.tgz", - "integrity": "sha512-Mdq/A1L8p37hkibp8jGFwuQTDSWhDmlueAefsrCPRwNWThEOlQmIglV7Gd6GE2mO5bt7ksfxKOMwkuY7jjVTXg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esbuild-loader/-/esbuild-loader-4.3.0.tgz", + "integrity": "sha512-D7HeJNdkDKKMarPQO/3dlJT6RwN2YJO7ENU6RPlpOz5YxSHnUNi2yvW41Bckvi1EVwctIaLzlb0ni5ag2GINYA==", "license": "MIT", "dependencies": { - "esbuild": "^0.21.0", + "esbuild": "^0.25.0", "get-tsconfig": "^4.7.0", "loader-utils": "^2.0.4", "webpack-sources": "^1.4.3" @@ -7891,20 +6186,19 @@ } }, "node_modules/eslint-import-resolver-typescript": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.7.0.tgz", - "integrity": "sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.8.0.tgz", + "integrity": "sha512-fItUrP/+xwpavWgadrn6lsvcMe80s08xIVFXkUXvhR4cZD2ga96kRF/z/iFGDI7ZDnvtlaZ0wGic7Tw+DhgVnA==", "dev": true, "license": "ISC", "dependencies": { "@nolyfill/is-core-module": "1.0.39", "debug": "^4.3.7", "enhanced-resolve": "^5.15.0", - "fast-glob": "^3.3.2", - "get-tsconfig": "^4.7.5", + "get-tsconfig": "^4.10.0", "is-bun-module": "^1.0.2", - "is-glob": "^4.0.3", - "stable-hash": "^0.0.4" + "stable-hash": "^0.0.4", + "tinyglobby": "^0.2.10" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -8294,9 +6588,9 @@ } }, "node_modules/eslint-plugin-playwright": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-2.1.0.tgz", - "integrity": "sha512-wMbHOehofSB1cBdzz2CLaCYaKNLeTQ0YnOW+7AHa281TJqlpEJUBgTHbRUYOUxiXphfWwOyTPvgr6vvEmArbSA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-2.2.0.tgz", + "integrity": "sha512-qSQpAw7RcSzE3zPp8FMGkthaCWovHZ/BsXtpmnGax9vQLIovlh1bsZHEa2+j2lv9DWhnyeLM/qZmp7ffQZfQvg==", "dev": true, "license": "MIT", "workspaces": [ @@ -8366,18 +6660,12 @@ } }, "node_modules/eslint-plugin-sonarjs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-3.0.1.tgz", - "integrity": "sha512-RT6VgdPqizbMLmTryIc3fB169hRjvDFlqieSZEEswGtApPb4Dn9BndmN9qyfBV/By0hbseIX8zQWKBz5E7lyiQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-3.0.2.tgz", + "integrity": "sha512-LxjbfwI7ypENeTmGyKmDyNux3COSkMi7H/6Cal5StSLQ6edf0naP45SZR43OclaNR7WfhVTZdhOn63q3/Y6puQ==", "dev": true, "license": "LGPL-3.0-only", "dependencies": { - "@babel/core": "7.26.0", - "@babel/eslint-parser": "7.25.9", - "@babel/plugin-proposal-decorators": "7.25.9", - "@babel/preset-env": "7.26.0", - "@babel/preset-flow": "7.25.9", - "@babel/preset-react": "7.26.3", "@eslint-community/regexpp": "4.12.1", "builtin-modules": "3.3.0", "bytes": "3.1.2", @@ -8385,7 +6673,7 @@ "jsx-ast-utils": "3.3.5", "minimatch": "9.0.5", "scslre": "0.3.0", - "semver": "7.6.3", + "semver": "7.7.1", "typescript": "^5" }, "peerDependencies": { @@ -8443,9 +6731,9 @@ } }, "node_modules/eslint-plugin-unicorn/node_modules/globals": { - "version": "15.14.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", - "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", "dev": true, "license": "MIT", "engines": { @@ -8455,182 +6743,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-vitest": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.4.1.tgz", - "integrity": "sha512-+PnZ2u/BS+f5FiuHXz4zKsHPcMKHie+K+1Uvu/x91ovkCMEOJqEI8E9Tw1Wzx2QRz4MHOBHYf1ypO8N1K0aNAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/utils": "^7.4.0" - }, - "engines": { - "node": "^18.0.0 || >= 20.0.0" - }, - "peerDependencies": { - "eslint": ">=8.0.0", - "vitest": "*" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - }, - "vitest": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-vitest-globals": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vitest-globals/-/eslint-plugin-vitest-globals-1.5.0.tgz", - "integrity": "sha512-ZSsVOaOIig0oVLzRTyk8lUfBfqzWxr/J3/NFMfGGRIkGQPejJYmDH3gXmSJxAojts77uzAGB/UmVrwi2DC4LYA==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-vitest/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-vitest/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/eslint-plugin-vitest/node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, "node_modules/eslint-plugin-vue": { "version": "9.32.0", "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.32.0.tgz", @@ -8956,6 +7068,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -8998,9 +7111,9 @@ } }, "node_modules/fastq": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", - "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", + "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -9166,16 +7279,6 @@ "dev": true, "license": "MIT" }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -9426,9 +7529,9 @@ } }, "node_modules/happy-dom": { - "version": "16.7.2", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-16.7.2.tgz", - "integrity": "sha512-zOzw0xyYlDaF/ylwbAsduYZZVRTd5u7IwlFkGbEathIeJMLp3vrN3cHm3RS7PZpD9gr/IO16bHEswcgNyWTsqw==", + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.1.0.tgz", + "integrity": "sha512-9tUhXyePCjzUMycaHS/IzrIpF69xiq/laAT7golk4MtZ6t8ft5+Rv7U3lfrs2b4NMH0JTL3EhZzjfahrPmOnaQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9610,9 +7713,9 @@ } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -9709,6 +7812,32 @@ "node": ">=10.13.0" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -9763,6 +7892,17 @@ "node": ">=12.4.0" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -9804,6 +7944,17 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-js-type": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-js-type/-/is-js-type-2.0.0.tgz", @@ -9908,19 +8059,18 @@ } }, "node_modules/jackspeak": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", - "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", - "dev": true, + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": "20 || >=22" - }, "funding": { "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/jest-worker": { @@ -9975,9 +8125,10 @@ "license": "MIT" }, "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, "license": "MIT" }, "node_modules/js-types": { @@ -10380,13 +8531,13 @@ } }, "node_modules/local-pkg": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", - "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.0.0.tgz", + "integrity": "sha512-bbgPw/wmroJsil/GgL4qjDzs5YLTBMQ99weRsok1XCDccQeehbHA/I1oRvk2NPtr7KGZgT/Y5tPRnAtMqeG2Kg==", "license": "MIT", "dependencies": { "mlly": "^1.7.3", - "pkg-types": "^1.2.1" + "pkg-types": "^1.3.0" }, "engines": { "node": ">=14" @@ -10437,13 +8588,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.kebabcase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", @@ -10476,6 +8620,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "deprecated": "This package is deprecated. Use https://socket.dev/npm/package/eta instead.", "license": "MIT", "dependencies": { "lodash._reinterpolate": "^3.0.0", @@ -10513,9 +8658,9 @@ "license": "MIT" }, "node_modules/loupe": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", - "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", "dev": true, "license": "MIT" }, @@ -10530,14 +8675,10 @@ } }, "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" }, "node_modules/magic-string": { "version": "0.25.9", @@ -10575,14 +8716,21 @@ } }, "node_modules/markdownlint": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.36.1.tgz", - "integrity": "sha512-s73fU2CQN7WCgjhaQUQ8wYESQNzGRNOKDd+3xgVqu8kuTEhmwepd/mxOv1LR2oV046ONrTLBFsM7IoKWNvmy5g==", + "version": "0.37.4", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.37.4.tgz", + "integrity": "sha512-u00joA/syf3VhWh6/ybVFkib5Zpj2e5KB/cfCei8fkSRuums6nyisTWGqjTWIOFoFwuXoTBQQiqlB4qFKp8ncQ==", "dev": true, "license": "MIT", "dependencies": { "markdown-it": "14.1.0", - "markdownlint-micromark": "0.1.12" + "micromark": "4.0.1", + "micromark-core-commonmark": "2.0.2", + "micromark-extension-directive": "3.0.2", + "micromark-extension-gfm-autolink-literal": "2.1.0", + "micromark-extension-gfm-footnote": "2.1.0", + "micromark-extension-gfm-table": "2.1.0", + "micromark-extension-math": "3.1.0", + "micromark-util-types": "2.0.1" }, "engines": { "node": ">=18" @@ -10592,20 +8740,20 @@ } }, "node_modules/markdownlint-cli": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.43.0.tgz", - "integrity": "sha512-6vwurKK4B21eyYzwgX6ph13cZS7hE6LZfcS8QyD722CyxVD2RtAvbZK2p7k+FZbbKORulEuwl+hJaEq1l6/hoQ==", + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.44.0.tgz", + "integrity": "sha512-ZJTAONlvF9NkrIBltCdW15DxN9UTbPiKMEqAh2EU2gwIFlrCMavyCEPPO121cqfYOrLUJWW8/XKWongstmmTeQ==", "dev": true, "license": "MIT", "dependencies": { - "commander": "~12.1.0", - "glob": "~11.0.0", - "ignore": "~6.0.2", - "js-yaml": "^4.1.0", + "commander": "~13.1.0", + "glob": "~10.4.5", + "ignore": "~7.0.3", + "js-yaml": "~4.1.0", "jsonc-parser": "~3.3.1", - "jsonpointer": "5.0.1", - "markdownlint": "~0.36.1", - "minimatch": "~10.0.1", + "jsonpointer": "~5.0.1", + "markdownlint": "~0.37.4", + "minimatch": "~9.0.5", "run-con": "~1.3.2", "smol-toml": "~1.3.1" }, @@ -10617,9 +8765,9 @@ } }, "node_modules/markdownlint-cli/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", "dev": true, "license": "MIT", "engines": { @@ -10627,33 +8775,30 @@ } }, "node_modules/markdownlint-cli/node_modules/glob": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", - "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": "20 || >=22" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/markdownlint-cli/node_modules/ignore": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", - "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz", + "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==", "dev": true, "license": "MIT", "engines": { @@ -10667,17 +8812,20 @@ "dev": true, "license": "MIT" }, - "node_modules/markdownlint-micromark": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.12.tgz", - "integrity": "sha512-RlB6EwMGgc0sxcIhOQ2+aq7Zw1V2fBnzbXKGgYK/mVWdT7cz34fteKSwfYeo4rL6+L/q2tyC9QtD/PgZbkdyJQ==", + "node_modules/markdownlint-cli/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">=18" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/sponsors/DavidAnson" + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/marked": { @@ -10785,6 +8933,542 @@ "node": ">= 18" } }, + "node_modules/micromark": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz", + "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.4.tgz", + "integrity": "sha512-N6hXjrin2GTJDe3MVjf5FuXpm12PGm80BrUAeub9XFXca8JZbP+oIwY4LJSVwFUCL1IPm/WwSVUN7goFHmSGGQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -11259,9 +9943,9 @@ "license": "BlueOak-1.0.0" }, "node_modules/package-manager-detector": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.8.tgz", - "integrity": "sha512-ts9KSdroZisdvKMWVAVCXiKqnqNfXz4+IbrBG8/BWx/TR5le+jfenvoBuIZ6UWM9nz47W7AbD9qYfAwfWMIwzA==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.9.tgz", + "integrity": "sha512-+vYvA/Y31l8Zk8dwxHhL3JfTuHPm6tlxM2A3GeQyl7ovYnSp1+mzAxClxaOr0qO1TtPxbQxetI7v5XqKLJZk7Q==", "license": "MIT" }, "node_modules/parent-module": { @@ -11276,6 +9960,26 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -11341,32 +10045,21 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": "20 || >=22" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", - "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -11378,9 +10071,9 @@ } }, "node_modules/pathe": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", - "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, "node_modules/pathval": { @@ -11394,9 +10087,9 @@ } }, "node_modules/pdfobject": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pdfobject/-/pdfobject-2.3.0.tgz", - "integrity": "sha512-w/9pXDXTDs3IDmOri/w8lM/w6LHR0/F4fcBLLzH+4csSoyshQ5su0TE7k0FLHZO7aOjVLDGecqd1M89+PVpVAA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pdfobject/-/pdfobject-2.3.1.tgz", + "integrity": "sha512-vluuGiSDmMGpOvWFGiUY4trNB8aGKLDVxIXuuGHjX0kK3bMxCANUVtLivctE7uejLBScWCnbVarKatFVvdwXaQ==", "license": "MIT" }, "node_modules/perfect-debounce": { @@ -11585,9 +10278,9 @@ } }, "node_modules/postcss": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", - "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz", + "integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==", "funding": [ { "type": "opencollective", @@ -11628,13 +10321,6 @@ "node": "^12 || >=14" } }, - "node_modules/postcss-html/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, "node_modules/postcss-import": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", @@ -11767,9 +10453,9 @@ } }, "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -11795,9 +10481,9 @@ } }, "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -11919,9 +10605,9 @@ } }, "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -12032,9 +10718,9 @@ } }, "node_modules/prettier": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", - "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.1.tgz", + "integrity": "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==", "dev": true, "license": "MIT", "bin": { @@ -12081,6 +10767,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -12281,42 +10968,12 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "license": "MIT" }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, "node_modules/regexp-ast-analysis": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz", @@ -12341,57 +10998,6 @@ "regexp-tree": "bin/regexp-tree" } }, - "node_modules/regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", - "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regexpu-core/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/regexpu-core/node_modules/regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~3.0.2" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "dev": true, - "license": "MIT" - }, "node_modules/regjsparser": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.10.0.tgz", @@ -12687,9 +11293,9 @@ } }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -12708,18 +11314,18 @@ } }, "node_modules/seroval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.2.0.tgz", - "integrity": "sha512-GURoU99ko2UiAgUC3qDCk59Jb3Ss4Po8VIMGkG8j5PFo2Q7y0YSMP8QG9NuL/fJCoTz9V1XZUbpNIMXPOfaGpA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.2.1.tgz", + "integrity": "sha512-yBxFFs3zmkvKNmR0pFSU//rIsYjuX418TnlDmc2weaq5XFDqDIV/NOMPBoLrbxjLH42p4UzRuXHryXh9dYcKcw==", "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/seroval-plugins": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.2.0.tgz", - "integrity": "sha512-hULTbfzSe81jGWLH8TAJjkEvw6JWMqOo9Uq+4V4vg+HNq53hyHldM9ZOfjdzokcFysiTp9aFdV2vJpZFqKeDjQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.2.1.tgz", + "integrity": "sha512-H5vs53+39+x4Udwp4J5rNZfgFuA+Lt+uU+09w1gYBVWomtAl98B+E9w7yC05Xc81/HgLvJdlyqJbU0fJCKCmdw==", "license": "MIT", "engines": { "node": ">=10" @@ -13257,9 +11863,9 @@ } }, "node_modules/stylelint-define-config": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/stylelint-define-config/-/stylelint-define-config-16.14.0.tgz", - "integrity": "sha512-5R7/Vv6awCkNaPcedo1GuUp+7YTFvDnexogO4l/C0i349pBDYbefN6XzsDGsGOhU++maQSh2fp3mWNO0F16IjA==", + "version": "16.14.1", + "resolved": "https://registry.npmjs.org/stylelint-define-config/-/stylelint-define-config-16.14.1.tgz", + "integrity": "sha512-pFMY96De80ZUZldX0iFLnd+FXd/hezIvU4POZNiU+BzcqtvvruTVItc8CJQzSePB5g5AsD5bo28kFa7R4tpS1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -13346,25 +11952,25 @@ "license": "MIT" }, "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "10.0.5", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.0.5.tgz", - "integrity": "sha512-umpQsJrBNsdMDgreSryMEXvJh66XeLtZUwA8Gj7rHGearGufUFv6rB/bcXRFsiGWw/VeSUgUofF4Rf2UKEOrTA==", + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.0.6.tgz", + "integrity": "sha512-0wvv16mVo9nN0Md3k7DMjgAPKG/TY4F/gYMBVb/wMThFRJvzrpaqBFqF6km9wf8QfYTN+mNg5aeaBLfy8k35uA==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^6.1.5" + "flat-cache": "^6.1.6" } }, "node_modules/stylelint/node_modules/flat-cache": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.5.tgz", - "integrity": "sha512-QR+2kN38f8nMfiIQ1LHYjuDEmZNZVjxuxY+HufbS3BW0EX01Q5OnH7iduOYRutmgiXb797HAKcXUeXrvRjjgSQ==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.6.tgz", + "integrity": "sha512-F+CKgSwp0pzLx67u+Zy1aCueVWFAHWbXepvXlZ+bWVTaASbm5SyCnSJ80Fp1ePEmS57wU+Bf6cx6525qtMZ4lQ==", "dev": true, "license": "MIT", "dependencies": { - "cacheable": "^1.8.7", + "cacheable": "^1.8.8", "flatted": "^3.3.2", - "hookified": "^1.6.0" + "hookified": "^1.7.0" } }, "node_modules/stylelint/node_modules/ignore": { @@ -13405,9 +12011,9 @@ } }, "node_modules/stylelint/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", "dependencies": { @@ -13429,9 +12035,9 @@ } }, "node_modules/stylis": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.5.tgz", - "integrity": "sha512-K7npNOKGRYuhAFFzkzMGfxFDpN6gDwf8hcMiE+uveTVbBgm93HrNP3ZDUpKqzZ4pG7TP6fmb+EMAQPjq9FqqvA==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", "license": "MIT" }, "node_modules/stylus": { @@ -13516,27 +12122,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/sucrase/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/sucrase/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, "node_modules/sucrase/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -13552,22 +12137,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/sucrase/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/superstruct": { "version": "0.10.13", "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.10.13.tgz", @@ -13587,9 +12156,9 @@ } }, "node_modules/supports-hyperlinks": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz", - "integrity": "sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", "dev": true, "license": "MIT", "dependencies": { @@ -13600,7 +12169,7 @@ "node": ">=14.18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -13690,9 +12259,9 @@ "license": "CC0-1.0" }, "node_modules/swagger-ui-dist": { - "version": "5.18.2", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.2.tgz", - "integrity": "sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==", + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.3.tgz", + "integrity": "sha512-G33HFW0iFNStfY2x6QXO2JYVMrFruc8AZRX0U/L71aA7WeWfX2E5Nm8E/tsipSZJeIZZbSjUDeynLK/wcuNWIw==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -13792,9 +12361,9 @@ } }, "node_modules/terser": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz", - "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", + "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -13905,6 +12474,48 @@ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", + "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tinypool": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", @@ -13975,9 +12586,9 @@ "license": "MIT" }, "node_modules/ts-api-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", - "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", + "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", "dev": true, "license": "MIT", "engines": { @@ -14049,9 +12660,9 @@ } }, "node_modules/type-fest": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.33.0.tgz", - "integrity": "sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g==", + "version": "4.34.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.34.1.tgz", + "integrity": "sha512-6kSc32kT0rbwxD6QL1CYe8IqdzN/J/ILMrNK+HMQCKH3insCDRY/3ITb0vcBss0a3t72fzh2YSzj8ko1HgwT3g==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -14105,50 +12716,6 @@ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "license": "MIT" }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -14190,9 +12757,9 @@ } }, "node_modules/updates": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/updates/-/updates-16.4.1.tgz", - "integrity": "sha512-dYsXzAISSiTFk6mg2ZB7IOlhIN5CO4qINxpbN7rrE9ffpuycq82SZ8rvLSc2QpBEO98BBY7wKm3d8SdA94o5TQ==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/updates/-/updates-16.4.2.tgz", + "integrity": "sha512-FCnbRnBUWrzhbsatc+RqUoLK7WB88VDGB9Jb1vB2JOx8gN69oXAE4/umtZsUzdeiM2TZ0aVb0kQJ0NbY8K21nw==", "dev": true, "license": "BSD-2-Clause", "bin": { @@ -14206,6 +12773,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -14275,15 +12843,15 @@ "license": "MIT" }, "node_modules/vite": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz", - "integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.0.tgz", + "integrity": "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.24.2", - "postcss": "^8.4.49", - "rollup": "^4.23.0" + "postcss": "^8.5.1", + "rollup": "^4.30.1" }, "bin": { "vite": "bin/vite.js" @@ -14347,16 +12915,16 @@ } }, "node_modules/vite-node": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.3.tgz", - "integrity": "sha512-0sQcwhwAEw/UJGojbhOrnq3HtiZ3tC7BzpAa0lx3QaTX0S3YX70iGcik25UBdB96pmdwjyY2uyKNYruxCDmiEg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.5.tgz", + "integrity": "sha512-02JEJl7SbtwSDJdYS537nU6l+ktdvcREfLksk/NDAqtdKWGqHl+joXzEubHROmS3E6pip+Xgu2tFezMu75jH7A==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", "debug": "^4.4.0", "es-module-lexer": "^1.6.0", - "pathe": "^2.0.1", + "pathe": "^2.0.2", "vite": "^5.0.0 || ^6.0.0" }, "bin": { @@ -14370,9 +12938,9 @@ } }, "node_modules/vite-string-plugin": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/vite-string-plugin/-/vite-string-plugin-1.4.3.tgz", - "integrity": "sha512-srlSRDwWxjG4MwHOmeXA9H8b69VjR3PfQRv2hCkHsotnGbJOLY3gggClSmNTeKUJpnURckgTFeVVVdCXX/Gwpg==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/vite-string-plugin/-/vite-string-plugin-1.4.4.tgz", + "integrity": "sha512-mHcdjsDaJSBnUrsAb4L+AWamWVWSFwDb5JuBbU5hAOt1w/ixaQ0EwWx2gAZiqFfpl5XlSE0WmCp2ShbdwgqsvQ==", "dev": true, "license": "BSD-2-Clause" }, @@ -14665,6 +13233,23 @@ "node": ">=18" } }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/vite/node_modules/@esbuild/netbsd-x64": { "version": "0.24.2", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", @@ -14682,6 +13267,23 @@ "node": ">=18" } }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/vite/node_modules/@esbuild/openbsd-x64": { "version": "0.24.2", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", @@ -14831,9 +13433,9 @@ } }, "node_modules/vite/node_modules/rollup": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.31.0.tgz", - "integrity": "sha512-9cCE8P4rZLx9+PjoyqHLs31V9a9Vpvfo4qNcs6JCiGWYhw2gijSetFbH6SSy1whnkgcefnUwr8sad7tgqsGvnw==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.6.tgz", + "integrity": "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14847,54 +13449,54 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.31.0", - "@rollup/rollup-android-arm64": "4.31.0", - "@rollup/rollup-darwin-arm64": "4.31.0", - "@rollup/rollup-darwin-x64": "4.31.0", - "@rollup/rollup-freebsd-arm64": "4.31.0", - "@rollup/rollup-freebsd-x64": "4.31.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.31.0", - "@rollup/rollup-linux-arm-musleabihf": "4.31.0", - "@rollup/rollup-linux-arm64-gnu": "4.31.0", - "@rollup/rollup-linux-arm64-musl": "4.31.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.31.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.31.0", - "@rollup/rollup-linux-riscv64-gnu": "4.31.0", - "@rollup/rollup-linux-s390x-gnu": "4.31.0", - "@rollup/rollup-linux-x64-gnu": "4.31.0", - "@rollup/rollup-linux-x64-musl": "4.31.0", - "@rollup/rollup-win32-arm64-msvc": "4.31.0", - "@rollup/rollup-win32-ia32-msvc": "4.31.0", - "@rollup/rollup-win32-x64-msvc": "4.31.0", + "@rollup/rollup-android-arm-eabi": "4.34.6", + "@rollup/rollup-android-arm64": "4.34.6", + "@rollup/rollup-darwin-arm64": "4.34.6", + "@rollup/rollup-darwin-x64": "4.34.6", + "@rollup/rollup-freebsd-arm64": "4.34.6", + "@rollup/rollup-freebsd-x64": "4.34.6", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.6", + "@rollup/rollup-linux-arm-musleabihf": "4.34.6", + "@rollup/rollup-linux-arm64-gnu": "4.34.6", + "@rollup/rollup-linux-arm64-musl": "4.34.6", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.6", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.6", + "@rollup/rollup-linux-riscv64-gnu": "4.34.6", + "@rollup/rollup-linux-s390x-gnu": "4.34.6", + "@rollup/rollup-linux-x64-gnu": "4.34.6", + "@rollup/rollup-linux-x64-musl": "4.34.6", + "@rollup/rollup-win32-arm64-msvc": "4.34.6", + "@rollup/rollup-win32-ia32-msvc": "4.34.6", + "@rollup/rollup-win32-x64-msvc": "4.34.6", "fsevents": "~2.3.2" } }, "node_modules/vitest": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.3.tgz", - "integrity": "sha512-dWdwTFUW9rcnL0LyF2F+IfvNQWB0w9DERySCk8VMG75F8k25C7LsZoh6XfCjPvcR8Nb+Lqi9JKr6vnzH7HSrpQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.5.tgz", + "integrity": "sha512-4dof+HvqONw9bvsYxtkfUp2uHsTN9bV2CZIi1pWgoFpL1Lld8LA1ka9q/ONSsoScAKG7NVGf2stJTI7XRkXb2Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "3.0.3", - "@vitest/mocker": "3.0.3", - "@vitest/pretty-format": "^3.0.3", - "@vitest/runner": "3.0.3", - "@vitest/snapshot": "3.0.3", - "@vitest/spy": "3.0.3", - "@vitest/utils": "3.0.3", + "@vitest/expect": "3.0.5", + "@vitest/mocker": "3.0.5", + "@vitest/pretty-format": "^3.0.5", + "@vitest/runner": "3.0.5", + "@vitest/snapshot": "3.0.5", + "@vitest/spy": "3.0.5", + "@vitest/utils": "3.0.5", "chai": "^5.1.2", "debug": "^4.4.0", "expect-type": "^1.1.0", "magic-string": "^0.30.17", - "pathe": "^2.0.1", + "pathe": "^2.0.2", "std-env": "^3.8.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.0.3", + "vite-node": "3.0.5", "why-is-node-running": "^2.3.0" }, "bin": { @@ -14908,9 +13510,10 @@ }, "peerDependencies": { "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.0.3", - "@vitest/ui": "3.0.3", + "@vitest/browser": "3.0.5", + "@vitest/ui": "3.0.5", "happy-dom": "*", "jsdom": "*" }, @@ -14918,6 +13521,9 @@ "@edge-runtime/vm": { "optional": true }, + "@types/debug": { + "optional": true + }, "@types/node": { "optional": true }, @@ -15113,14 +13719,14 @@ } }, "node_modules/vue-tsc": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.0.tgz", - "integrity": "sha512-gtmM1sUuJ8aSb0KoAFmK9yMxb8TxjewmxqTJ1aKphD5Cbu0rULFY6+UQT51zW7SpUcenfPUuflKyVwyx9Qdnxg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.2.tgz", + "integrity": "sha512-1icPKkxAA5KTAaSwg0wVWdE48EdsH8fgvcbAiqojP4jXKl6LEM3soiW1aG/zrWrFt8Mw1ncG2vG1PvpZpVfehA==", "dev": true, "license": "MIT", "dependencies": { "@volar/typescript": "~2.4.11", - "@vue/language-core": "2.2.0" + "@vue/language-core": "2.2.2" }, "bin": { "vue-tsc": "bin/vue-tsc.js" @@ -15153,9 +13759,9 @@ } }, "node_modules/webpack": { - "version": "5.97.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", - "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "version": "5.98.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz", + "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", @@ -15176,9 +13782,9 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", + "schema-utils": "^4.3.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", + "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, @@ -15279,31 +13885,6 @@ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "license": "MIT" }, - "node_modules/webpack/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -15326,30 +13907,6 @@ "node": ">=4.0" } }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/webpack/node_modules/webpack-sources": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", @@ -15570,13 +14127,6 @@ "node": ">=10" } }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, "node_modules/yaml": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", diff --git a/package.json b/package.json index 7eb9000bb5..37be19eca7 100644 --- a/package.json +++ b/package.json @@ -4,29 +4,29 @@ "node": ">= 18.0.0" }, "dependencies": { - "@citation-js/core": "0.7.14", - "@citation-js/plugin-bibtex": "0.7.17", - "@citation-js/plugin-csl": "0.7.14", + "@citation-js/core": "0.7.18", + "@citation-js/plugin-bibtex": "0.7.18", + "@citation-js/plugin-csl": "0.7.18", "@citation-js/plugin-software-formats": "0.6.1", "@github/markdown-toolbar-element": "2.2.3", "@github/relative-time-element": "4.4.5", "@github/text-expander-element": "2.9.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", - "@primer/octicons": "19.14.0", + "@primer/octicons": "19.15.0", "@silverwind/vue3-calendar-heatmap": "2.0.6", "add-asset-webpack-plugin": "3.0.0", "ansi_up": "6.0.2", - "asciinema-player": "3.8.2", + "asciinema-player": "3.9.0", "chart.js": "4.4.7", "chartjs-adapter-dayjs-4": "1.0.4", "chartjs-plugin-zoom": "2.2.0", - "clippie": "4.1.4", + "clippie": "4.1.5", "cropperjs": "1.6.2", "css-loader": "7.1.2", "dayjs": "1.11.13", "dropzone": "6.0.0-beta.2", "easymde": "2.18.0", - "esbuild-loader": "4.2.2", + "esbuild-loader": "4.3.0", "escape-goat": "4.0.0", "fast-glob": "3.3.3", "htmx.org": "2.0.4", @@ -39,13 +39,13 @@ "minimatch": "10.0.1", "monaco-editor": "0.52.2", "monaco-editor-webpack-plugin": "7.1.0", - "pdfobject": "2.3.0", + "pdfobject": "2.3.1", "perfect-debounce": "1.0.0", - "postcss": "8.5.1", + "postcss": "8.5.2", "postcss-loader": "8.1.1", "postcss-nesting": "13.0.1", "sortablejs": "1.15.6", - "swagger-ui-dist": "5.18.2", + "swagger-ui-dist": "5.18.3", "tailwindcss": "3.4.17", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", @@ -59,7 +59,7 @@ "vue-bar-graph": "2.2.0", "vue-chartjs": "5.3.2", "vue-loader": "17.4.2", - "webpack": "5.97.1", + "webpack": "5.98.0", "webpack-cli": "6.0.1", "wrap-ansi": "9.0.0" }, @@ -67,8 +67,8 @@ "@eslint-community/eslint-plugin-eslint-comments": "4.4.1", "@playwright/test": "1.49.1", "@stoplight/spectral-cli": "6.14.2", - "@stylistic/eslint-plugin-js": "2.13.0", - "@stylistic/stylelint-plugin": "3.1.1", + "@stylistic/eslint-plugin-js": "3.1.0", + "@stylistic/stylelint-plugin": "3.1.2", "@types/dropzone": "5.7.9", "@types/jquery": "3.5.32", "@types/katex": "0.16.7", @@ -79,41 +79,40 @@ "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/toastify-js": "1.12.3", - "@typescript-eslint/eslint-plugin": "8.21.0", - "@typescript-eslint/parser": "8.21.0", + "@typescript-eslint/eslint-plugin": "8.24.0", + "@typescript-eslint/parser": "8.24.0", "@vitejs/plugin-vue": "5.2.1", + "@vitest/eslint-plugin": "1.1.31", "eslint": "8.57.0", - "eslint-import-resolver-typescript": "3.7.0", + "eslint-import-resolver-typescript": "3.8.0", "eslint-plugin-array-func": "4.0.0", "eslint-plugin-github": "5.0.2", "eslint-plugin-import-x": "4.6.1", "eslint-plugin-no-jquery": "3.1.0", "eslint-plugin-no-use-extend-native": "0.5.0", - "eslint-plugin-playwright": "2.1.0", + "eslint-plugin-playwright": "2.2.0", "eslint-plugin-regexp": "2.7.0", - "eslint-plugin-sonarjs": "3.0.1", + "eslint-plugin-sonarjs": "3.0.2", "eslint-plugin-unicorn": "56.0.1", - "eslint-plugin-vitest": "0.4.1", - "eslint-plugin-vitest-globals": "1.5.0", "eslint-plugin-vue": "9.32.0", "eslint-plugin-vue-scoped-css": "2.9.0", "eslint-plugin-wc": "2.2.0", - "happy-dom": "16.7.2", - "markdownlint-cli": "0.43.0", + "happy-dom": "17.1.0", + "markdownlint-cli": "0.44.0", "nolyfill": "1.0.43", "postcss-html": "1.8.0", "stylelint": "16.14.1", "stylelint-config-recommended": "15.0.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.7", - "stylelint-define-config": "16.14.0", + "stylelint-define-config": "16.14.1", "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.3.2", - "type-fest": "4.33.0", - "updates": "16.4.1", - "vite-string-plugin": "1.4.3", - "vitest": "3.0.3", - "vue-tsc": "2.2.0" + "type-fest": "4.34.1", + "updates": "16.4.2", + "vite-string-plugin": "1.4.4", + "vitest": "3.0.5", + "vue-tsc": "2.2.2" }, "browserslist": [ "defaults" diff --git a/poetry.lock b/poetry.lock index ab5bcf05ac..16de98b5e2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -29,13 +29,14 @@ files = [ [[package]] name = "cssbeautifier" -version = "1.15.1" +version = "1.15.3" description = "CSS unobfuscator and beautifier." optional = false python-versions = "*" groups = ["dev"] files = [ - {file = "cssbeautifier-1.15.1.tar.gz", hash = "sha256:9f7064362aedd559c55eeecf6b6bed65e05f33488dcbe39044f0403c26e1c006"}, + {file = "cssbeautifier-1.15.3-py3-none-any.whl", hash = "sha256:0dcaf5ce197743a79b3a160b84ea58fcbd9e3e767c96df1171e428125b16d410"}, + {file = "cssbeautifier-1.15.3.tar.gz", hash = "sha256:406b04d09e7d62c0be084fbfa2cba5126fe37359ea0d8d9f7b963a6354fc8303"}, ] [package.dependencies] @@ -102,13 +103,14 @@ files = [ [[package]] name = "jsbeautifier" -version = "1.15.1" +version = "1.15.3" description = "JavaScript unobfuscator and beautifier." optional = false python-versions = "*" groups = ["dev"] files = [ - {file = "jsbeautifier-1.15.1.tar.gz", hash = "sha256:ebd733b560704c602d744eafc839db60a1ee9326e30a2a80c4adb8718adc1b24"}, + {file = "jsbeautifier-1.15.3-py3-none-any.whl", hash = "sha256:b207a15ab7529eee4a35ae7790e9ec4e32a2b5026d51e2d0386c3a65e6ecfc91"}, + {file = "jsbeautifier-1.15.3.tar.gz", hash = "sha256:5f1baf3d4ca6a615bb5417ee861b34b77609eeb12875555f8bbfabd9bf2f3457"}, ] [package.dependencies] diff --git a/public/assets/img/svg/octicon-square-circle.svg b/public/assets/img/svg/octicon-square-circle.svg new file mode 100644 index 0000000000..7ebb020bad --- /dev/null +++ b/public/assets/img/svg/octicon-square-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/routers/api/actions/artifacts.go b/routers/api/actions/artifacts.go index 910edd6d58..0832e52f55 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{ @@ -340,12 +340,12 @@ func (ar artifactRoutes) listArtifacts(ctx *ArtifactContext) { artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{RunID: runID}) 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 } @@ -405,18 +405,18 @@ func (ar artifactRoutes) getDownloadArtifactURL(ctx *ArtifactContext) { }) 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 +460,24 @@ 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 } 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..9d2b69820c 100644 --- a/routers/api/actions/artifacts_chunks.go +++ b/routers/api/actions/artifacts_chunks.go @@ -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..77ce765098 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,7 +37,7 @@ 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 @@ -48,7 +48,7 @@ func validateRunIDV4(ctx *ArtifactContext, rawRunID string) (*actions.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..665156d936 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 // @@ -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 } @@ -451,12 +451,12 @@ func (r *artifactV4Routes) listArtifacts(ctx *ArtifactContext) { artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{RunID: runID}) 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 } @@ -507,7 +507,7 @@ 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 } @@ -535,7 +535,7 @@ 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 } @@ -559,14 +559,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/runner/runner.go b/routers/api/actions/runner/runner.go index c55b30f7eb..f34dfb443b 100644 --- a/routers/api/actions/runner/runner.go +++ b/routers/api/actions/runner/runner.go @@ -156,7 +156,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 { 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/api.go b/routers/api/packages/api.go index 41c3eb95e9..b64306037f 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -49,32 +49,32 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) { if accessMode == 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 { 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 +83,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 +100,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 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..957d593d89 100644 --- a/routers/api/v1/activitypub/reqsignature.go +++ b/routers/api/v1/activitypub/reqsignature.go @@ -89,9 +89,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/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..fb1ea4eab6 100644 --- a/routers/api/v1/admin/hooks.go +++ b/routers/api/v1/admin/hooks.go @@ -59,14 +59,14 @@ func ListHooks(ctx *context.APIContext) { 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 +96,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 +186,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..8808a1587d 100644 --- a/routers/api/v1/admin/org.go +++ b/routers/api/v1/admin/org.go @@ -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 } @@ -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/user.go b/routers/api/v1/admin/user.go index 53eee72631..c4bb85de55 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 } @@ -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 } @@ -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 } @@ -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, fmt.Errorf("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 } @@ -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 } @@ -432,7 +432,7 @@ func SearchUsers(ctx *context.APIContext) { ListOptions: listOptions, }) if err != nil { - ctx.Error(http.StatusInternalServerError, "SearchUsers", err) + ctx.APIErrorInternal(err) return } @@ -473,7 +473,7 @@ 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 } @@ -482,9 +482,9 @@ func RenameUser(ctx *context.APIContext) { // Check if username has been changed if err := user_service.RenameUser(ctx, ctx.ContextUser, newName); err != nil { if user_model.IsErrUserAlreadyExist(err) || db.IsErrNameReserved(err) || db.IsErrNamePatternNotAllowed(err) || db.IsErrNameCharsNotAllowed(err) { - ctx.Error(http.StatusUnprocessableEntity, "", err) + ctx.APIError(http.StatusUnprocessableEntity, err) } else { - ctx.ServerError("ChangeUserName", err) + ctx.APIErrorInternal(err) } return } diff --git a/routers/api/v1/admin/user_badge.go b/routers/api/v1/admin/user_badge.go index 99e20877fd..6d9665a72b 100644 --- a/routers/api/v1/admin/user_badge.go +++ b/routers/api/v1/admin/user_badge.go @@ -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 } @@ -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 } @@ -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 0aa38b8b6a..907a2f08fe 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -66,6 +66,7 @@ package v1 import ( + "errors" "fmt" "net/http" "strings" @@ -116,9 +117,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 +155,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 +176,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 +193,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,20 +208,20 @@ 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) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) + ctx.APIErrorInternal(err) return } } if !ctx.Repo.Permission.HasAnyUnitAccess() { - ctx.NotFound() + ctx.APIErrorNotFound() return } } @@ -229,7 +230,7 @@ func repoAssignment() func(ctx *context.APIContext) { 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 +251,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 } } @@ -316,12 +317,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 +331,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 +351,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") + ctx.APIError(http.StatusUnauthorized, "you must be signed in to search for users") } } } @@ -365,7 +366,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 +377,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 +387,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 +397,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 +407,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 +417,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,7 +427,7 @@ 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 } } @@ -436,7 +437,7 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { 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") + ctx.APIError(http.StatusForbidden, "user should have a permission to write to this branch") return } } @@ -445,7 +446,7 @@ func reqRepoBranchWriter(ctx *context.APIContext) { 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 +456,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 +475,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 +502,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 +546,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 +568,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 +578,7 @@ 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 } } @@ -584,7 +588,7 @@ func reqWebhooksEnabled() func(ctx *context.APIContext) { func reqStarsEnabled() func(ctx *context.APIContext) { return func(ctx *context.APIContext) { if setting.Repository.DisableStars { - ctx.Error(http.StatusForbidden, "", "stars disabled by administrator") + ctx.APIError(http.StatusForbidden, "stars disabled by administrator") return } } @@ -613,12 +617,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 } @@ -629,9 +633,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 } @@ -657,7 +661,7 @@ func mustEnableIssues(ctx *context.APIContext) { ctx.Repo.Permission) } } - ctx.NotFound() + ctx.APIErrorNotFound() return } } @@ -680,7 +684,7 @@ func mustAllowPulls(ctx *context.APIContext) { ctx.Repo.Permission) } } - ctx.NotFound() + ctx.APIErrorNotFound() return } } @@ -706,28 +710,28 @@ 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 } } 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.LogString())) return } } func mustEnableAttachments(ctx *context.APIContext) { if !setting.Attachment.Enabled { - ctx.NotFound() + ctx.APIErrorNotFound() return } } @@ -738,7 +742,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) @@ -766,7 +770,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 @@ -843,12 +847,12 @@ func individualPermsChecker(ctx *context.APIContext) { switch { case ctx.ContextUser.Visibility == 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: if ctx.Doer == nil { - ctx.NotFound("Visit Project", nil) + ctx.APIErrorNotFound("Visit Project", nil) return } } @@ -1155,11 +1159,17 @@ 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("/hooks/git", func() { m.Combo("").Get(repo.ListGitHooks) m.Group("/{id}", func() { @@ -1235,6 +1245,13 @@ func Routes() *web.Router { }, reqToken(), reqAdmin()) m.Group("/actions", func() { m.Get("/tasks", repo.ListActionTasks) + m.Get("/runs/{run}/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). @@ -1395,6 +1412,10 @@ func Routes() *web.Router { }, 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() { @@ -1520,13 +1541,19 @@ 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.Group("/{version}", func() { + m.Get("", packages.GetPackage) + m.Delete("", reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage) + m.Get("/files", packages.ListPackageFiles) + }) + + 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) 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..107d53cad7 100644 --- a/routers/api/v1/misc/licenses.go +++ b/routers/api/v1/misc/licenses.go @@ -59,7 +59,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..0cd4b8c5c5 100644 --- a/routers/api/v1/misc/markup.go +++ b/routers/api/v1/misc/markup.go @@ -38,7 +38,7 @@ 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 } @@ -69,7 +69,7 @@ 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 } @@ -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/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..667396e39c 100644 --- a/routers/api/v1/misc/signing.go +++ b/routers/api/v1/misc/signing.go @@ -5,7 +5,6 @@ package misc import ( "fmt" - "net/http" asymkey_service "code.gitea.io/gitea/services/asymkey" "code.gitea.io/gitea/services/context" @@ -53,11 +52,11 @@ func SigningKey(ctx *context.APIContext) { content, err := asymkey_service.PublicSigningKey(ctx, path) if err != nil { - ctx.Error(http.StatusInternalServerError, "gpg export", err) + ctx.APIErrorInternal(err) return } _, err = ctx.Write([]byte(content)) if err != nil { - ctx.Error(http.StatusInternalServerError, "gpg export", fmt.Errorf("Error writing key content %w", err)) + ctx.APIErrorInternal(fmt.Errorf("Error writing key content %w", err)) } } 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..d9bdb3ab48 100644 --- a/routers/api/v1/org/action.go +++ b/routers/api/v1/org/action.go @@ -54,7 +54,7 @@ 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 } @@ -109,11 +109,11 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) { _, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("secretname"), opt.Data) 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 +156,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 } @@ -223,7 +223,7 @@ func (Action) ListVariables(ctx *context.APIContext) { ListOptions: utils.GetListOptions(ctx), }) if err != nil { - ctx.Error(http.StatusInternalServerError, "FindVariables", err) + ctx.APIErrorInternal(err) return } @@ -273,9 +273,9 @@ 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 } @@ -322,11 +322,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 } @@ -378,19 +378,19 @@ 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 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 } @@ -440,9 +440,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 +450,15 @@ 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 + + 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 } 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/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..2663d78b73 100644 --- a/routers/api/v1/org/member.go +++ b/routers/api/v1/org/member.go @@ -28,13 +28,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 +82,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 } } @@ -150,20 +150,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 } } @@ -200,13 +200,13 @@ 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() } } @@ -241,12 +241,12 @@ func PublicizeMember(ctx *context.APIContext) { return } if userToPublicize.ID != ctx.Doer.ID { - ctx.Error(http.StatusForbidden, "", "Cannot publicize another member") + ctx.APIError(http.StatusForbidden, "Cannot publicize another member") 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) @@ -283,12 +283,12 @@ func ConcealMember(ctx *context.APIContext) { return } if userToConceal.ID != ctx.Doer.ID { - ctx.Error(http.StatusForbidden, "", "Cannot conceal another member") + ctx.APIError(http.StatusForbidden, "Cannot conceal another member") 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) @@ -323,7 +323,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 2fcba0bf1a..c9208f4757 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -35,7 +35,7 @@ func listUserOrgs(ctx *context.APIContext, u *user_model.User) { } 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 } @@ -138,14 +138,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 +164,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 } @@ -209,7 +209,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 +245,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 +271,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 +301,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 } @@ -344,9 +344,9 @@ func Rename(ctx *context.APIContext) { 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.Error(http.StatusUnprocessableEntity, "RenameOrg", err) + ctx.APIError(http.StatusUnprocessableEntity, err) } else { - ctx.ServerError("RenameOrg", err) + ctx.APIErrorInternal(err) } return } @@ -383,7 +383,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 } } @@ -397,7 +397,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 } @@ -424,7 +424,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) @@ -469,7 +469,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 @@ -488,7 +488,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..f70e5dd235 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,7 +134,7 @@ func GetTeam(ctx *context.APIContext) { apiTeam, err := convert.ToTeam(ctx, ctx.Org.Team, true) if err != nil { - ctx.InternalServerError(err) + ctx.APIErrorInternal(err) return } @@ -233,7 +233,7 @@ func CreateTeam(ctx *context.APIContext) { } else if len(form.Units) > 0 { attachTeamUnits(team, 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 +242,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 +285,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 } @@ -332,13 +332,13 @@ func EditTeam(ctx *context.APIContext) { } 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 +363,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 +399,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 +411,7 @@ func GetTeamMembers(ctx *context.APIContext) { TeamID: ctx.Org.Team.ID, }) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetTeamMembers", err) + ctx.APIErrorInternal(err) return } @@ -456,10 +456,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)) @@ -498,9 +498,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 } @@ -538,7 +538,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 +578,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 +630,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 +648,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 +694,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 +746,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 +829,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 +885,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..f869519344 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" @@ -64,13 +67,13 @@ func ListPackages(ctx *context.APIContext) { 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) + ctx.APIErrorInternal(err) return } @@ -78,7 +81,7 @@ func ListPackages(ctx *context.APIContext) { 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) + ctx.APIErrorInternal(err) return } apiPackages = append(apiPackages, apiPackage) @@ -125,7 +128,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 +169,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 +216,122 @@ func ListPackageFiles(ctx *context.APIContext) { ctx.JSON(http.StatusOK, apiPackageFiles) } + +// 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) +} diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index d27e8d2427..6b4ce37fcf 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,7 +77,7 @@ 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 } @@ -124,11 +139,11 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) { _, created, err := secret_service.CreateOrUpdateSecret(ctx, 0, repo.ID, ctx.PathParam("secretname"), opt.Data) 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 } @@ -178,11 +193,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,9 +241,9 @@ 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 } @@ -280,11 +295,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 } @@ -339,19 +354,19 @@ 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 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 } @@ -404,9 +419,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 +429,15 @@ 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 + + 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,7 +484,7 @@ func (Action) ListVariables(ctx *context.APIContext) { ListOptions: utils.GetListOptions(ctx), }) if err != nil { - ctx.Error(http.StatusInternalServerError, "FindVariables", err) + ctx.APIErrorInternal(err) return } @@ -562,7 +581,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 +592,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 +600,649 @@ 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 := actions_service.ListActionWorkflows(ctx) + 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 := actions_service.GetActionWorkflow(ctx, 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) +} + +// 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) +} + +// 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)) + mac.Write([]byte(fmt.Sprint(expires))) + mac.Write([]byte(fmt.Sprint(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/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..d1cb72f5f1 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) + 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..5430c1a266 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -63,28 +63,28 @@ func GetBranch(ctx *context.APIContext) { branch, err := ctx.Repo.GitRepo.GetBranch(branchName) if err != nil { if git.IsErrBranchNotExist(err) { - ctx.NotFound(err) + ctx.APIErrorNotFound(err) } else { - ctx.Error(http.StatusInternalServerError, "GetBranch", err) + ctx.APIErrorInternal(err) } return } c, err := branch.GetCommit() 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()) if err != nil { - ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) + ctx.APIErrorInternal(err) return } @@ -124,12 +124,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 +141,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 +155,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, fmt.Errorf("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, fmt.Errorf("branch protected")) default: - ctx.Error(http.StatusInternalServerError, "DeleteBranch", err) + ctx.APIErrorInternal(err) } return } @@ -206,12 +206,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 +223,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 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 +248,40 @@ 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) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetBranch", err) + ctx.APIErrorInternal(err) return } commit, err := branch.GetCommit() 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) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err) + ctx.APIErrorInternal(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 +325,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 +337,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 +369,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 +433,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 +446,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 +499,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 +535,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)) @@ -590,7 +590,7 @@ func CreateBranchProtection(ctx *context.APIContext) { ruleName = form.BranchName //nolint } 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 } @@ -602,10 +602,10 @@ func CreateBranchProtection(ctx *context.APIContext) { 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 +617,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 +655,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 } } @@ -726,13 +726,13 @@ 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) + ctx.APIErrorInternal(err) return } } else { @@ -740,20 +740,20 @@ func CreateBranchProtection(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 } } // 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) + 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 } } @@ -763,11 +763,11 @@ func CreateBranchProtection(ctx *context.APIContext) { // 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 +817,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 +932,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 +945,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 +958,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 +971,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 +987,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 +1000,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 +1013,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 +1026,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,7 +1048,7 @@ func EditBranchProtection(ctx *context.APIContext) { ApprovalsTeamIDs: approvalsWhitelistTeams, }) if err != nil { - ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err) + ctx.APIErrorInternal(err) return } @@ -1060,7 +1060,7 @@ func EditBranchProtection(ctx *context.APIContext) { 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 +1068,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 +1076,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 +1092,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 +1136,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 +1189,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 } @@ -1228,13 +1228,13 @@ func MergeUpstream(ctx *context.APIContext) { mergeStyle, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, form.Branch) 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..c397d7972b 100644 --- a/routers/api/v1/repo/collaborators.go +++ b/routers/api/v1/repo/collaborators.go @@ -59,7 +59,7 @@ func ListCollaborators(ctx *context.APIContext) { RepoID: ctx.Repo.Repository.ID, }) if err != nil { - ctx.Error(http.StatusInternalServerError, "ListCollaborators", err) + ctx.APIErrorInternal(err) return } @@ -106,21 +106,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() } } @@ -166,15 +166,15 @@ 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 } @@ -185,9 +185,9 @@ func AddOrUpdateCollaborator(ctx *context.APIContext) { 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 +229,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) @@ -275,23 +275,23 @@ func GetRepoPermissions(ctx *context.APIContext) { // "$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") + 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")) 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 +324,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 +362,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..03489d777b 100644 --- a/routers/api/v1/repo/commits.go +++ b/routers/api/v1/repo/commits.go @@ -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, fmt.Sprintf("no valid ref or sha: %s", 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(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) @@ -182,20 +182,20 @@ func GetAllCommits(ctx *context.APIContext) { // no sha supplied - use default branch head, err := ctx.Repo.GitRepo.GetHEADBranch() if err != nil { - ctx.Error(http.StatusInternalServerError, "GetHEADBranch", err) + ctx.APIErrorInternal(err) return } baseCommit, err = ctx.Repo.GitRepo.GetBranchCommit(head.Name) 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 } } @@ -207,14 +207,14 @@ func GetAllCommits(ctx *context.APIContext) { Revision: []string{baseCommit.ID.String()}, }) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetCommitsCount", err) + ctx.APIErrorInternal(err) return } // Query commits commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize, not) if err != nil { - ctx.Error(http.StatusInternalServerError, "CommitsByRange", err) + ctx.APIErrorInternal(err) return } } else { @@ -231,10 +231,10 @@ func GetAllCommits(ctx *context.APIContext) { }) 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 } @@ -246,7 +246,7 @@ func GetAllCommits(ctx *context.APIContext) { Page: listOptions.Page, }) if err != nil { - ctx.Error(http.StatusInternalServerError, "CommitsByFileAndRange", err) + ctx.APIErrorInternal(err) return } } @@ -259,7 +259,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 +317,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(sha) return } - ctx.Error(http.StatusInternalServerError, "DownloadCommitDiffOrPatch", err) + ctx.APIErrorInternal(err) return } } @@ -357,19 +357,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..20901badfb 100644 --- a/routers/api/v1/repo/download.go +++ b/routers/api/v1/repo/download.go @@ -23,7 +23,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, fmt.Sprintf("Unknown archive type: %s", ballType)) return } @@ -31,20 +31,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 045db7a291..7e6a7ef087 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -72,7 +72,7 @@ func GetRawFile(ctx *context.APIContext) { // "$ref": "#/responses/notFound" if ctx.Repo.Repository.IsEmpty { - ctx.NotFound() + ctx.APIErrorNotFound() return } @@ -84,7 +84,7 @@ 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) + ctx.APIErrorInternal(err) } } @@ -125,7 +125,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) { // "$ref": "#/responses/notFound" if ctx.Repo.Repository.IsEmpty { - ctx.NotFound() + ctx.APIErrorNotFound() return } @@ -145,7 +145,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) { // OK not cached - serve! if err := common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified); err != nil { - ctx.ServerError("ServeBlob", err) + ctx.APIErrorInternal(err) } return } @@ -153,7 +153,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) { // 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) dataRc, err := blob.DataAsync() if err != nil { - ctx.ServerError("DataAsync", err) + ctx.APIErrorInternal(err) return } @@ -161,7 +161,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) { buf, err := io.ReadAll(dataRc) if err != nil { _ = dataRc.Close() - ctx.ServerError("DataAsync", err) + ctx.APIErrorInternal(err) return } @@ -197,7 +197,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 +217,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 +229,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 +284,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 +296,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 +339,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() @@ -387,9 +387,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,7 +397,7 @@ 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) @@ -470,7 +470,7 @@ func ChangeFiles(ctx *context.APIContext) { 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 } changeRepoFile := &files_service.ChangeRepoFile{ @@ -570,7 +570,7 @@ func CreateFile(ctx *context.APIContext) { contentReader, err := base64Reader(apiOpts.ContentBase64) if err != nil { - ctx.Error(http.StatusUnprocessableEntity, "Invalid base64 content", err) + ctx.APIError(http.StatusUnprocessableEntity, err) return } @@ -661,7 +661,7 @@ func UpdateFile(ctx *context.APIContext) { // "$ref": "#/responses/repoArchivedError" apiOpts := web.GetForm(ctx).(*api.UpdateFileOptions) if ctx.Repo.Repository.IsEmpty { - ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty")) + ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("repo is empty")) return } @@ -671,7 +671,7 @@ func UpdateFile(ctx *context.APIContext) { contentReader, err := base64Reader(apiOpts.ContentBase64) if err != nil { - ctx.Error(http.StatusUnprocessableEntity, "Invalid base64 content", err) + ctx.APIError(http.StatusUnprocessableEntity, err) return } @@ -723,20 +723,20 @@ func UpdateFile(ctx *context.APIContext) { func handleCreateOrUpdateFileError(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) + ctx.APIError(http.StatusUnprocessableEntity, err) return } if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) { - ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err) + ctx.APIError(http.StatusNotFound, err) return } - ctx.Error(http.StatusInternalServerError, "UpdateFile", err) + ctx.APIErrorInternal(err) } // Called from both CreateFile or UpdateFile to handle both @@ -825,7 +825,7 @@ func DeleteFile(ctx *context.APIContext) { apiOpts := web.GetForm(ctx).(*api.DeleteFileOptions) if !canWriteFiles(ctx, apiOpts.BranchName) { - ctx.Error(http.StatusForbidden, "DeleteFile", repo_model.ErrUserDoesNotHaveAccessToRepo{ + ctx.APIError(http.StatusForbidden, repo_model.ErrUserDoesNotHaveAccessToRepo{ UserID: ctx.Doer.ID, RepoName: ctx.Repo.Repository.LowerName, }) @@ -874,20 +874,20 @@ func DeleteFile(ctx *context.APIContext) { 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) + ctx.APIError(http.StatusNotFound, 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) + ctx.APIError(http.StatusBadRequest, err) return } else if files_service.IsErrUserCannotCommit(err) { - ctx.Error(http.StatusForbidden, "DeleteFile", err) + ctx.APIError(http.StatusForbidden, err) return } - ctx.Error(http.StatusInternalServerError, "DeleteFile", err) + ctx.APIErrorInternal(err) } else { fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0) ctx.JSON(http.StatusOK, fileResponse) // FIXME on APIv2: return http.StatusNoContent @@ -929,7 +929,7 @@ func GetContents(ctx *context.APIContext) { // "$ref": "#/responses/notFound" if !canReadFiles(ctx.Repo) { - ctx.Error(http.StatusInternalServerError, "GetContentsOrList", repo_model.ErrUserDoesNotHaveAccessToRepo{ + ctx.APIErrorInternal(repo_model.ErrUserDoesNotHaveAccessToRepo{ UserID: ctx.Doer.ID, RepoName: ctx.Repo.Repository.LowerName, }) @@ -941,10 +941,10 @@ func GetContents(ctx *context.APIContext) { if fileList, err := files_service.GetContentsOrList(ctx, ctx.Repo.Repository, treePath, ref); err != nil { if git.IsErrNotExist(err) { - ctx.NotFound("GetContentsOrList", err) + ctx.APIErrorNotFound("GetContentsOrList", err) return } - ctx.Error(http.StatusInternalServerError, "GetContentsOrList", err) + ctx.APIErrorInternal(err) } else { ctx.JSON(http.StatusOK, fileList) } diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go index f96c432b92..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,19 @@ 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 } if !ctx.Doer.IsAdmin { isMember, err := org.IsOrgMember(ctx, ctx.Doer.ID) if err != nil { - ctx.Error(http.StatusInternalServerError, "IsOrgMember", err) + ctx.APIErrorInternal(err) return } else if !isMember { - ctx.Error(http.StatusForbidden, "isMemberNot", fmt.Sprintf("User is no Member of Organisation '%s'", org.Name)) + ctx.APIError(http.StatusForbidden, fmt.Sprintf("User is no Member of Organisation '%s'", org.Name)) return } } @@ -159,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/issue.go b/routers/api/v1/repo/issue.go index 86dbcee5f7..c9575ff98a 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 } @@ -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 } } @@ -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) @@ -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 } @@ -896,18 +896,18 @@ func EditIssue(ctx *context.APIContext) { oldMilestoneID := issue.MilestoneID issue.MilestoneID = *form.Milestone 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 +922,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 +963,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 +1019,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 +1042,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..0c572a06a8 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 } @@ -613,9 +613,9 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) comment.Content = form.Body if err := issue_service.UpdateComment(ctx, comment, comment.ContentVersion, ctx.Doer, oldContent); err != nil { if errors.Is(err, user_model.ErrBlockedUser) { - ctx.Error(http.StatusForbidden, "UpdateComment", err) + ctx.APIError(http.StatusForbidden, err) } else { - ctx.Error(http.StatusInternalServerError, "UpdateComment", err) + ctx.APIErrorInternal(err) } return } @@ -699,15 +699,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 +725,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..2048c76ea0 100644 --- a/routers/api/v1/repo/issue_dependency.go +++ b/routers/api/v1/repo/issue_dependency.go @@ -57,23 +57,23 @@ 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 } @@ -98,7 +98,7 @@ func GetIssueDependencies(ctx *context.APIContext) { PageSize: limit, }) if err != nil { - ctx.Error(http.StatusInternalServerError, "BlockedByDependencies", err) + ctx.APIErrorInternal(err) return } @@ -116,7 +116,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,7 +324,7 @@ func GetIssueBlocks(ctx *context.APIContext) { } if !ctx.Repo.Permission.CanReadIssuesOrPulls(issue.IsPull) { - ctx.NotFound() + ctx.APIErrorNotFound() return } @@ -342,7 +342,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 +367,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 +502,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 +523,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 +536,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 +553,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 +563,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 +589,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..f8e14e0490 100644 --- a/routers/api/v1/repo/issue_label.go +++ b/routers/api/v1/repo/issue_label.go @@ -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,15 +312,15 @@ 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") + ctx.APIError(http.StatusForbidden, "write permission is required") return nil, nil, fmt.Errorf("permission denied") } @@ -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") + ctx.APIError(http.StatusBadRequest, "a label must be an integer or a string") return nil, nil, fmt.Errorf("invalid label") } } if len(labelIDs) > 0 && len(labelNames) > 0 { - ctx.Error(http.StatusBadRequest, "InvalidLabels", "labels should be an array of strings or integers") + ctx.APIError(http.StatusBadRequest, "labels should be an array of strings or integers") return nil, nil, fmt.Errorf("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_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..b18e172b37 100644 --- a/routers/api/v1/repo/issue_stopwatch.go +++ b/routers/api/v1/repo/issue_stopwatch.go @@ -55,7 +55,7 @@ func StartIssueStopwatch(ctx *context.APIContext) { } if err := issues_model.CreateIssueStopwatch(ctx, ctx.Doer, issue); err != nil { - ctx.Error(http.StatusInternalServerError, "CreateOrStopIssueStopwatch", err) + ctx.APIErrorInternal(err) return } @@ -104,7 +104,7 @@ func StopIssueStopwatch(ctx *context.APIContext) { } if err := issues_model.FinishIssueStopwatch(ctx, ctx.Doer, issue); err != nil { - ctx.Error(http.StatusInternalServerError, "CreateOrStopIssueStopwatch", err) + ctx.APIErrorInternal(err) return } @@ -153,7 +153,7 @@ func DeleteIssueStopwatch(ctx *context.APIContext) { } if err := issues_model.CancelStopwatch(ctx, ctx.Doer, issue); err != nil { - ctx.Error(http.StatusInternalServerError, "CancelStopwatch", err) + ctx.APIErrorInternal(err) return } @@ -164,9 +164,9 @@ func prepareIssueStopwatch(ctx *context.APIContext, shouldExist bool) (*issues_m 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 @@ -184,10 +184,10 @@ func prepareIssueStopwatch(ctx *context.APIContext, shouldExist bool) (*issues_m 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") + ctx.APIError(http.StatusConflict, "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") + ctx.APIError(http.StatusConflict, "cannot start a stopwatch again if it already exists") err = errors.New("cannot start a stopwatch again if it already exists") } return nil, err @@ -220,19 +220,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..21e549496d 100644 --- a/routers/api/v1/repo/issue_subscription.go +++ b/routers/api/v1/repo/issue_subscription.go @@ -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..dbb2afa920 100644 --- a/routers/api/v1/repo/issue_tracked_time.go +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -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, fmt.Errorf("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(fmt.Errorf("tracked time [%d] already deleted", time.ID)) 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) @@ -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, fmt.Errorf("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, fmt.Errorf("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..d7508684a1 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, fmt.Errorf("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, fmt.Errorf("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) @@ -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..b5f4c12c50 100644 --- a/routers/api/v1/repo/mirror.go +++ b/routers/api/v1/repo/mirror.go @@ -53,20 +53,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 +104,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 +161,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 +169,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 +219,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 +230,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 +280,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 +320,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 +328,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 +339,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,13 +354,13 @@ 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 } @@ -374,22 +374,22 @@ func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirro } 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 +400,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..dcb512256c 100644 --- a/routers/api/v1/repo/notes.go +++ b/routers/api/v1/repo/notes.go @@ -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, fmt.Sprintf("no valid ref or sha: %s", 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(fmt.Errorf("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(), ¬e); err != nil { if git.IsErrNotExist(err) { - ctx.NotFound(identifier) + ctx.APIErrorNotFound(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 95d7631da7..bcf498bf7e 100644 --- a/routers/api/v1/repo/patch.go +++ b/routers/api/v1/repo/patch.go @@ -83,7 +83,7 @@ func ApplyDiffPatch(ctx *context.APIContext) { } if !canWriteFiles(ctx, apiOpts.BranchName) { - ctx.Error(http.StatusInternalServerError, "ApplyPatch", repo_model.ErrUserDoesNotHaveAccessToRepo{ + ctx.APIErrorInternal(repo_model.ErrUserDoesNotHaveAccessToRepo{ UserID: ctx.Doer.ID, RepoName: ctx.Repo.Repository.LowerName, }) @@ -93,19 +93,19 @@ func ApplyDiffPatch(ctx *context.APIContext) { 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) + ctx.APIError(http.StatusForbidden, 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) + ctx.APIError(http.StatusUnprocessableEntity, err) return } if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) { - ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err) + ctx.APIError(http.StatusNotFound, err) return } - ctx.Error(http.StatusInternalServerError, "ApplyPatch", err) + ctx.APIErrorInternal(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..1f61ac031a 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -108,7 +108,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 +116,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 } @@ -134,13 +134,13 @@ func ListPullRequests(ctx *context.APIContext) { PosterID: posterID, }) 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,19 +182,19 @@ 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 } ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer)) @@ -252,9 +252,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,19 +267,19 @@ 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 } ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer)) @@ -327,9 +327,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 +343,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 +388,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 +406,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 +417,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 +429,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 +448,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 +464,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 +504,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 +514,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 +543,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 +606,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 +634,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 +642,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 +661,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 +679,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 } @@ -694,7 +694,7 @@ func EditPullRequest(ctx *context.APIContext) { oldMilestoneID := issue.MilestoneID issue.MilestoneID = form.Milestone if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil { - ctx.Error(http.StatusInternalServerError, "ChangeMilestoneAssign", err) + ctx.APIErrorInternal(err) return } } @@ -702,14 +702,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 +717,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 } @@ -738,21 +738,21 @@ 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)) + 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 +762,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 +774,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 +818,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 +828,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 +876,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 +897,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 +915,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() + ctx.APIErrorNotFound() } else if errors.Is(err, pull_service.ErrUserNotAllowedToMerge) { - ctx.Error(http.StatusMethodNotAllowed, "Merge", "User not allowed to merge PR") + 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") + ctx.APIError(http.StatusMethodNotAllowed, "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, 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 +938,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 +960,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 +974,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 +988,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 +999,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 +1024,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 +1038,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 +1047,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, fmt.Errorf("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, fmt.Errorf("branch protected")) default: - ctx.Error(http.StatusInternalServerError, "DeleteBranch", err) + ctx.APIErrorInternal(err) } return } @@ -1092,14 +1092,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 +1110,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 +1132,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 +1146,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 +1160,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 +1178,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 +1236,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 +1271,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 } @@ -1285,13 +1285,13 @@ func UpdatePullRequest(ctx *context.APIContext) { if err = pull_service.Update(ctx, 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 +1336,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 +1421,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 +1447,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 +1476,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 +1544,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 +1570,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 } @@ -1597,7 +1597,7 @@ func GetPullRequestFiles(ctx *context.APIContext) { WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.FormString("whitespace")), }) if err != nil { - ctx.ServerError("GetDiff", err) + ctx.APIErrorInternal(err) return } diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index 6d7a326370..fb35126a99 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, fmt.Errorf("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, fmt.Errorf("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, fmt.Errorf("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, fmt.Errorf("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..36fff126e1 100644 --- a/routers/api/v1/repo/release.go +++ b/routers/api/v1/repo/release.go @@ -53,16 +53,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 +93,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 +161,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 +175,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 +220,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, fmt.Errorf("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 @@ -248,19 +248,19 @@ func CreateRelease(ctx *context.APIContext) { } if err := release_service.CreateRelease(ctx.Repo.GitRepo, rel, nil, ""); 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 +275,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 +322,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 +349,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 +399,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..d418b8e4b5 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -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 @@ -713,7 +713,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public if visibilityChanged && setting.Repository.ForcePrivate && !*opts.Private && !ctx.Doer.IsAdmin { err := fmt.Errorf("cannot change private repository to public") - ctx.Error(http.StatusUnprocessableEntity, "Force Private enabled", err) + ctx.APIError(http.StatusUnprocessableEntity, err) return err } @@ -728,7 +728,7 @@ 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 } } @@ -738,7 +738,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && (repo.IsEmpty || ctx.Repo.GitRepo.IsBranchExist(*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 } } @@ -782,12 +782,12 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { // 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) + 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) + ctx.APIError(http.StatusUnprocessableEntity, err) return err } @@ -849,7 +849,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { // 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") + 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 } } @@ -1040,13 +1040,13 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e 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) + 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 { @@ -1056,7 +1056,7 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e } 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/star.go b/routers/api/v1/repo/star.go index 46ed17ad91..46218e0e28 100644 --- a/routers/api/v1/repo/star.go +++ b/routers/api/v1/repo/star.go @@ -49,7 +49,7 @@ func ListStargazers(ctx *context.APIContext) { 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..e1dbb25865 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 } @@ -187,7 +187,7 @@ func GetCommitStatusesByRef(ctx *context.APIContext) { func getCommitStatuses(ctx *context.APIContext, sha string) { if len(sha) == 0 { - ctx.Error(http.StatusBadRequest, "ref/sha not given", nil) + ctx.APIError(http.StatusBadRequest, nil) return } sha = utils.MustConvertToSHA1(ctx.Base, ctx.Repo, sha) @@ -203,7 +203,7 @@ func getCommitStatuses(ctx *context.APIContext, sha string) { 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(), sha, ctx.FormInt("page"), err)) return } @@ -266,7 +266,7 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) { statuses, count, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, 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(), sha, err)) return } 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..2e6c1c1023 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) 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(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 bb666f6487..7b890c9e5c 100644 --- a/routers/api/v1/repo/transfer.go +++ b/routers/api/v1/repo/transfer.go @@ -61,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 } } @@ -79,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 } @@ -87,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 } @@ -109,19 +109,19 @@ func Transfer(ctx *context.APIContext) { 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) + ctx.APIError(http.StatusConflict, err) return } if repo_model.IsErrRepoAlreadyExist(err) { - ctx.Error(http.StatusUnprocessableEntity, "StartRepositoryTransfer", err) + ctx.APIError(http.StatusUnprocessableEntity, err) return } if errors.Is(err, user_model.ErrBlockedUser) { - ctx.Error(http.StatusForbidden, "BlockedUser", err) + ctx.APIError(http.StatusForbidden, err) } else { - ctx.InternalServerError(err) + ctx.APIErrorInternal(err) } return } @@ -166,11 +166,11 @@ func AcceptTransfer(ctx *context.APIContext) { if err != nil { switch { case repo_model.IsErrNoPendingTransfer(err): - ctx.Error(http.StatusNotFound, "AcceptTransferOwnership", err) + ctx.APIError(http.StatusNotFound, err) case errors.Is(err, util.ErrPermissionDenied): - ctx.Error(http.StatusForbidden, "AcceptTransferOwnership", err) + ctx.APIError(http.StatusForbidden, err) default: - ctx.Error(http.StatusInternalServerError, "AcceptTransferOwnership", err) + ctx.APIErrorInternal(err) } return } @@ -208,11 +208,11 @@ func RejectTransfer(ctx *context.APIContext) { if err != nil { switch { case repo_model.IsErrNoPendingTransfer(err): - ctx.Error(http.StatusNotFound, "RejectRepositoryTransfer", err) + ctx.APIError(http.StatusNotFound, err) case errors.Is(err, util.ErrPermissionDenied): - ctx.Error(http.StatusForbidden, "RejectRepositoryTransfer", err) + ctx.APIError(http.StatusForbidden, err) default: - ctx.Error(http.StatusInternalServerError, "RejectRepositoryTransfer", err) + ctx.APIErrorInternal(err) } return } 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..8d73383f76 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 } @@ -198,7 +198,7 @@ func getWikiPage(ctx *context.APIContext, wikiName wiki_service.WebPath) *api.Wi // 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 } @@ -312,7 +312,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 +322,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 +330,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)) @@ -447,7 +447,7 @@ func ListPageRevisions(ctx *context.APIContext) { Page: page, }) if err != nil { - ctx.Error(http.StatusInternalServerError, "CommitsByFileAndRange", err) + ctx.APIErrorInternal(err) return } @@ -479,9 +479,9 @@ func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit) wikiRepo, err := gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository) 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 } @@ -489,9 +489,9 @@ func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit) commit, err := wikiRepo.GetBranchCommit("master") 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 } @@ -507,7 +507,7 @@ func wikiContentsByEntry(ctx *context.APIContext, entry *git.TreeEntry) string { } content, err := blob.GetBlobContentBase64() if err != nil { - ctx.Error(http.StatusInternalServerError, "GetBlobContentBase64", err) + ctx.APIErrorInternal(err) return "" } return content @@ -521,10 +521,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/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..f31d9e5d0b 100644 --- a/routers/api/v1/shared/runners.go +++ b/routers/api/v1/shared/runners.go @@ -24,7 +24,7 @@ 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 } 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 353d6de89b..aa5990eb38 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -211,6 +211,9 @@ type swaggerParameterBodies struct { // in:body RenameOrgOption api.RenameOrgOption + // in:body + CreateActionWorkflowDispatch api.CreateActionWorkflowDispatch + // in:body UpdateVariableOption api.UpdateVariableOption } diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go index f754c80a5b..25f137f3bf 100644 --- a/routers/api/v1/swagger/repo.go +++ b/routers/api/v1/swagger/repo.go @@ -443,6 +443,20 @@ type swaggerRepoTasksList struct { Body api.ActionTaskResponse `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"` +} + // 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..1c8e3b6e71 100644 --- a/routers/api/v1/user/action.go +++ b/routers/api/v1/user/action.go @@ -52,11 +52,11 @@ func CreateOrUpdateSecret(ctx *context.APIContext) { _, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Doer.ID, 0, ctx.PathParam("secretname"), opt.Data) 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 +94,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 } @@ -145,19 +145,19 @@ 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 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 } @@ -202,9 +202,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 +212,15 @@ 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 + + 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,9 +292,9 @@ 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 } @@ -334,7 +338,7 @@ func ListVariables(ctx *context.APIContext) { ListOptions: utils.GetListOptions(ctx), }) if err != nil { - ctx.Error(http.StatusInternalServerError, "FindVariables", err) + ctx.APIErrorInternal(err) return } diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index bfbc2ba622..4ca06ca923 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -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 } @@ -105,27 +105,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{ @@ -174,31 +174,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 +235,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 +273,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 +309,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 +342,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 +396,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/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..0d0c0be7e0 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 } @@ -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 } @@ -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() } } @@ -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 } @@ -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..504e74ae10 100644 --- a/routers/api/v1/user/gpg_key.go +++ b/routers/api/v1/user/gpg_key.go @@ -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 } @@ -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", fmt.Errorf("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, 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)) 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", fmt.Errorf("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, 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)) 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, 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)) 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..6295f4753b 100644 --- a/routers/api/v1/user/key.go +++ b/routers/api/v1/user/key.go @@ -81,7 +81,7 @@ func listPublicKeys(ctx *context.APIContext, user *user_model.User) { } if err != nil { - ctx.Error(http.StatusInternalServerError, "ListPublicKeys", err) + ctx.APIErrorInternal(err) return } @@ -182,9 +182,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 +200,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", fmt.Errorf("ssh keys setting is not allowed to be visited")) return } @@ -270,7 +270,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", fmt.Errorf("ssh keys setting is not allowed to be visited")) return } @@ -278,23 +278,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..6aabc4fb90 100644 --- a/routers/api/v1/user/repo.go +++ b/routers/api/v1/user/repo.go @@ -26,12 +26,12 @@ func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) { 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() { @@ -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/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 70e54bc1ae..4b0cb45d67 100644 --- a/routers/api/v1/user/star.go +++ b/routers/api/v1/user/star.go @@ -72,7 +72,7 @@ func GetStarredRepos(ctx *context.APIContext) { 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 } @@ -104,7 +104,7 @@ func GetMyStarredRepos(ctx *context.APIContext) { 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)) @@ -138,7 +138,7 @@ func IsStarring(ctx *context.APIContext) { if repo_model.IsStaring(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) { ctx.Status(http.StatusNoContent) } else { - ctx.NotFound() + ctx.APIErrorNotFound() } } @@ -169,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 } @@ -204,7 +204,7 @@ func Unstar(ctx *context.APIContext) { 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..757a548518 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -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)) @@ -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) @@ -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..76d7c81793 100644 --- a/routers/api/v1/user/watch.go +++ b/routers/api/v1/user/watch.go @@ -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..af672ba147 100644 --- a/routers/api/v1/utils/git.go +++ b/routers/api/v1/utils/git.go @@ -17,7 +17,7 @@ import ( // 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) + ctx.APIError(http.StatusBadRequest, nil) return "" } @@ -26,7 +26,7 @@ func ResolveRefOrSha(ctx *context.APIContext, ref string) string { for _, refType := range []string{"heads", "tags"} { refSHA, lastMethodName, err := searchRefCommitByType(ctx, refType, ref) if err != nil { - ctx.Error(http.StatusInternalServerError, lastMethodName, err) + ctx.APIErrorInternal(fmt.Errorf("%s: %w", lastMethodName, err)) return "" } if refSHA != "" { diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go index 2ad9eeb0ec..9c49819970 100644 --- a/routers/api/v1/utils/hook.go +++ b/routers/api/v1/utils/hook.go @@ -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,17 @@ 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, fmt.Sprintf("Invalid hook type: %s", 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 } return true @@ -102,7 +102,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 +141,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 @@ -169,7 +169,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI 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 @@ -215,19 +215,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 } @@ -238,17 +238,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 @@ -258,21 +258,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) @@ -328,7 +328,7 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh } 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) @@ -343,7 +343,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) @@ -369,7 +369,7 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh err := w.SetHeaderAuthorization(form.AuthorizationHeader) if err != nil { - ctx.Error(http.StatusInternalServerError, "SetHeaderAuthorization", err) + ctx.APIErrorInternal(err) return false } @@ -391,7 +391,7 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh w.HookEvents[webhook_module.HookEventPullRequestSync] = pullHook(form.Events, string(webhook_module.HookEventPullRequestSync)) if err := w.UpdateEvent(); err != nil { - ctx.Error(http.StatusInternalServerError, "UpdateEvent", err) + ctx.APIErrorInternal(err) return false } @@ -400,7 +400,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 @@ -410,9 +410,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/common/codesearch.go b/routers/common/codesearch.go index a14af126e5..7cd01068b0 100644 --- a/routers/common/codesearch.go +++ b/routers/common/codesearch.go @@ -25,7 +25,7 @@ func PrepareCodeSearch(ctx *context.Context) (ret struct { } isFuzzy := ctx.FormOptionalBool("fuzzy").ValueOrDefault(fuzzyDefault) if isFuzzy && !fuzzyAllow { - ctx.Flash.Info("Fuzzy search is disabled by default due to performance reasons") + ctx.Flash.Info("Fuzzy search is disabled by default due to performance reasons", true) isFuzzy = false } diff --git a/routers/common/markup.go b/routers/common/markup.go index 533b546a2a..60bf0dba54 100644 --- a/routers/common/markup.go +++ b/routers/common/markup.go @@ -39,7 +39,7 @@ func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, ur rctx := renderhelper.NewRenderContextSimpleDocument(ctx, baseLink).WithUseAbsoluteLink(true). WithMarkupType(markdown.MarkupName) if err := markdown.RenderRaw(rctx, strings.NewReader(text), ctx.Resp); err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) } return } @@ -88,15 +88,15 @@ func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, ur }) rctx = rctx.WithMarkupType("").WithRelativePath(filePath) // render the repo file content by its extension default: - ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode)) + ctx.HTTPError(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode)) return } rctx = rctx.WithUseAbsoluteLink(true) if err := markup.Render(rctx, strings.NewReader(text), ctx.Resp); err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.Error(http.StatusUnprocessableEntity, err.Error()) + ctx.HTTPError(http.StatusUnprocessableEntity, err.Error()) } else { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) } return } diff --git a/routers/install/install.go b/routers/install/install.go index 8a1d57aa0b..8544717f65 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -64,7 +64,6 @@ func Contexter() func(next http.Handler) http.Handler { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { base := context.NewBaseContext(resp, req) ctx := context.NewWebContext(base, rnd, session.GetSession(req)) - ctx.SetContextValue(context.WebContextKey, ctx) ctx.Data.MergeFrom(middleware.CommonTemplateContextData()) ctx.Data.MergeFrom(reqctx.ContextData{ "Title": ctx.Locale.Tr("install.install"), diff --git a/routers/private/hook_verification.go b/routers/private/hook_verification.go index 764c976fa9..1af875e401 100644 --- a/routers/private/hook_verification.go +++ b/routers/private/hook_verification.go @@ -10,9 +10,9 @@ import ( "io" "os" - asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + asymkey_service "code.gitea.io/gitea/services/asymkey" ) // This file contains commit verification functions for refs passed across in hooks @@ -96,7 +96,7 @@ func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error { if err != nil { return err } - verification := asymkey_model.ParseCommitWithSignature(ctx, commit) + verification := asymkey_service.ParseCommitWithSignature(ctx, commit) if !verification.Verified { cancel() return &errUnverifiedCommit{ diff --git a/routers/private/internal.go b/routers/private/internal.go index a78c76f897..55a11aa3dd 100644 --- a/routers/private/internal.go +++ b/routers/private/internal.go @@ -87,8 +87,8 @@ func Routes() *web.Router { // FIXME: it is not right to use context.Contexter here because all routes here should use PrivateContext // Fortunately, the LFS handlers are able to handle requests without a complete web context common.AddOwnerRepoGitLFSRoutes(r, func(ctx *context.PrivateContext) { - webContext := &context.Context{Base: ctx.Base} - ctx.SetContextValue(context.WebContextKey, webContext) + webContext := &context.Context{Base: ctx.Base} // see above, it shouldn't manually construct the web context + ctx.SetContextValue(context.WebContextKey, webContext) // FIXME: this is not ideal but no other way at the moment }) }) diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 249347e835..2b3bf1f77d 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -291,7 +291,7 @@ func NewAuthSourcePost(ctx *context.Context) { return } default: - ctx.Error(http.StatusBadRequest) + ctx.HTTPError(http.StatusBadRequest) return } ctx.Data["HasTLS"] = hasTLS @@ -413,7 +413,7 @@ func EditAuthSourcePost(ctx *context.Context) { return } default: - ctx.Error(http.StatusBadRequest) + ctx.HTTPError(http.StatusBadRequest) return } diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go index 23ddfa583a..51b3d584f4 100644 --- a/routers/web/admin/emails.go +++ b/routers/web/admin/emails.go @@ -116,7 +116,7 @@ func ActivateEmail(ctx *context.Context) { activate, oka := truefalse[ctx.FormString("activate")] if uid == 0 || len(email) == 0 || !okp || !oka { - ctx.Error(http.StatusBadRequest) + ctx.HTTPError(http.StatusBadRequest) return } diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 363da8f392..f07ef98931 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -192,7 +192,7 @@ func SignIn(ctx *context.Context) { // SignInPost response for sign in request func SignInPost(ctx *context.Context) { if !setting.Service.EnablePasswordSignInForm { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } @@ -456,7 +456,7 @@ func SignUpPost(ctx *context.Context) { // Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true if setting.Service.DisableRegistration || setting.Service.AllowOnlyExternalRegistration { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } @@ -767,7 +767,7 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) { } if err := user_model.UpdateUserCols(ctx, user, "is_active", "rands"); err != nil { if user_model.IsErrUserNotExist(err) { - ctx.NotFound("UpdateUserCols", err) + ctx.NotFound(err) } else { ctx.ServerError("UpdateUser", err) } diff --git a/routers/web/auth/linkaccount.go b/routers/web/auth/linkaccount.go index 386241225e..b3c61946b9 100644 --- a/routers/web/auth/linkaccount.go +++ b/routers/web/auth/linkaccount.go @@ -260,7 +260,7 @@ func LinkAccountPostRegister(ctx *context.Context) { } if setting.Service.DisableRegistration || setting.Service.AllowOnlyInternalRegistration { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } diff --git a/routers/web/auth/oauth2_provider.go b/routers/web/auth/oauth2_provider.go index 6262ad8a6d..00b5b2db52 100644 --- a/routers/web/auth/oauth2_provider.go +++ b/routers/web/auth/oauth2_provider.go @@ -142,7 +142,7 @@ func IntrospectOAuth(ctx *context.Context) { if err != nil && !auth.IsErrOauthClientIDInvalid(err) { // this is likely a database error; log it and respond without details log.Error("Error retrieving client_id: %v", err) - ctx.Error(http.StatusInternalServerError) + ctx.HTTPError(http.StatusInternalServerError) return } clientIDValid = err == nil && app.ValidateClientSecret([]byte(clientSecret)) @@ -363,7 +363,7 @@ func GrantApplicationOAuth(ctx *context.Context) { form := web.GetForm(ctx).(*forms.GrantApplicationForm) if ctx.Session.Get("client_id") != form.ClientID || ctx.Session.Get("state") != form.State || ctx.Session.Get("redirect_uri") != form.RedirectURI { - ctx.Error(http.StatusBadRequest) + ctx.HTTPError(http.StatusBadRequest) return } @@ -440,7 +440,7 @@ func OIDCKeys(ctx *context.Context) { jwk, err := oauth2_provider.DefaultSigningKey.ToJWK() if err != nil { log.Error("Error converting signing key to JWK: %v", err) - ctx.Error(http.StatusInternalServerError) + ctx.HTTPError(http.StatusInternalServerError) return } diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go index 41d37ecb8b..c3415cccac 100644 --- a/routers/web/auth/openid.go +++ b/routers/web/auth/openid.go @@ -337,7 +337,7 @@ func RegisterOpenIDPost(ctx *context.Context) { ctx.Data["OpenID"] = oid if setting.Service.AllowOnlyInternalRegistration { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go index 614e086f77..8dbde85fe6 100644 --- a/routers/web/auth/password.go +++ b/routers/web/auth/password.go @@ -53,7 +53,7 @@ func ForgotPasswdPost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("auth.forgot_password_title") if setting.MailService == nil { - ctx.NotFound("ForgotPasswdPost", nil) + ctx.NotFound(nil) return } ctx.Data["IsResetRequest"] = true @@ -122,7 +122,7 @@ func commonResetPassword(ctx *context.Context) (*user_model.User, *auth.TwoFacto twofa, err := auth.GetTwoFactorByUID(ctx, u.ID) if err != nil { if !auth.IsErrTwoFactorNotEnrolled(err) { - ctx.Error(http.StatusInternalServerError, "CommonResetPassword", err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "CommonResetPassword", err.Error()) return nil, nil } } else { @@ -181,7 +181,7 @@ func ResetPasswdPost(ctx *context.Context) { passcode := ctx.FormString("passcode") ok, err := twofa.ValidateTOTP(passcode) if err != nil { - ctx.Error(http.StatusInternalServerError, "ValidateTOTP", err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "ValidateTOTP", err.Error()) return } if !ok || twofa.LastUsedPasscode == passcode { diff --git a/routers/web/auth/webauthn.go b/routers/web/auth/webauthn.go index 8dbe34b2b1..78f6c3b58e 100644 --- a/routers/web/auth/webauthn.go +++ b/routers/web/auth/webauthn.go @@ -51,7 +51,7 @@ func WebAuthn(ctx *context.Context) { // WebAuthnPasskeyAssertion submits a WebAuthn challenge for the passkey login to the browser func WebAuthnPasskeyAssertion(ctx *context.Context) { if !setting.Service.EnablePasskeyAuth { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } @@ -72,7 +72,7 @@ func WebAuthnPasskeyAssertion(ctx *context.Context) { // WebAuthnPasskeyLogin handles the WebAuthn login process using a Passkey func WebAuthnPasskeyLogin(ctx *context.Context) { if !setting.Service.EnablePasskeyAuth { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } diff --git a/routers/web/devtest/mock_actions.go b/routers/web/devtest/mock_actions.go index f29b8e4046..e6539bb31f 100644 --- a/routers/web/devtest/mock_actions.go +++ b/routers/web/devtest/mock_actions.go @@ -118,7 +118,7 @@ func MockActionsRunsJobs(ctx *context.Context) { } if doErrorResponse { if mathRand.Float64() > 0.5 { - ctx.Error(http.StatusInternalServerError, "devtest mock error response") + ctx.HTTPError(http.StatusInternalServerError, "devtest mock error response") return } } diff --git a/routers/web/explore/topic.go b/routers/web/explore/topic.go index b4507ba28d..be457df587 100644 --- a/routers/web/explore/topic.go +++ b/routers/web/explore/topic.go @@ -25,7 +25,7 @@ func TopicSearch(ctx *context.Context) { topics, total, err := db.FindAndCount[repo_model.Topic](ctx, opts) if err != nil { - ctx.Error(http.StatusInternalServerError) + ctx.HTTPError(http.StatusInternalServerError) return } diff --git a/routers/web/explore/user.go b/routers/web/explore/user.go index b14272c76a..e1e1ec1cfd 100644 --- a/routers/web/explore/user.go +++ b/routers/web/explore/user.go @@ -87,7 +87,7 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions, } if opts.SupportedSortOrders != nil && !opts.SupportedSortOrders.Contains(sortOrder) { - ctx.NotFound("unsupported sort order", nil) + ctx.NotFound(nil) return } diff --git a/routers/web/githttp.go b/routers/web/githttp.go index 5831e6f523..8597ffe795 100644 --- a/routers/web/githttp.go +++ b/routers/web/githttp.go @@ -21,7 +21,7 @@ func addOwnerRepoGitHTTPRouters(m *web.Router) { if !ctx.IsSigned { // TODO: support digit auth - which would be Authorization header with digit ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`) - ctx.Error(http.StatusUnauthorized) + ctx.HTTPError(http.StatusUnauthorized) } } m.Group("/{username}/{reponame}", func() { diff --git a/routers/web/home.go b/routers/web/home.go index 9ad495d54f..208cc36dfb 100644 --- a/routers/web/home.go +++ b/routers/web/home.go @@ -113,5 +113,5 @@ func HomeSitemap(ctx *context.Context) { // NotFound render 404 page func NotFound(ctx *context.Context) { ctx.Data["Title"] = "Page Not Found" - ctx.NotFound("home.NotFound", nil) + ctx.NotFound(nil) } diff --git a/routers/web/org/home.go b/routers/web/org/home.go index 27d1e14d85..e3c2dcf0bd 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -29,7 +29,7 @@ func Home(ctx *context.Context) { uname := ctx.PathParam("username") if strings.HasSuffix(uname, ".keys") || strings.HasSuffix(uname, ".gpg") { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } diff --git a/routers/web/org/members.go b/routers/web/org/members.go index 1665a12302..7d88d6b1ad 100644 --- a/routers/web/org/members.go +++ b/routers/web/org/members.go @@ -41,7 +41,7 @@ func Members(ctx *context.Context) { if ctx.Doer != nil { isMember, err := ctx.Org.Organization.IsOrgMember(ctx, ctx.Doer.ID) if err != nil { - ctx.Error(http.StatusInternalServerError, "IsOrgMember") + ctx.HTTPError(http.StatusInternalServerError, "IsOrgMember") return } opts.IsDoerMember = isMember @@ -50,7 +50,7 @@ func Members(ctx *context.Context) { total, err := organization.CountOrgMembers(ctx, opts) if err != nil { - ctx.Error(http.StatusInternalServerError, "CountOrgMembers") + ctx.HTTPError(http.StatusInternalServerError, "CountOrgMembers") return } @@ -93,19 +93,19 @@ func MembersAction(ctx *context.Context) { switch ctx.PathParam("action") { case "private": if ctx.Doer.ID != member.ID && !ctx.Org.IsOwner { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } err = organization.ChangeOrgUserStatus(ctx, org.ID, member.ID, false) case "public": if ctx.Doer.ID != member.ID && !ctx.Org.IsOwner { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } err = organization.ChangeOrgUserStatus(ctx, org.ID, member.ID, true) case "remove": if !ctx.Org.IsOwner { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } err = org_service.RemoveOrgUser(ctx, org, member) diff --git a/routers/web/org/org_labels.go b/routers/web/org/org_labels.go index 02eae8052e..ccab2131db 100644 --- a/routers/web/org/org_labels.go +++ b/routers/web/org/org_labels.go @@ -64,7 +64,7 @@ func UpdateLabel(ctx *context.Context) { if err != nil { switch { case issues_model.IsErrOrgLabelNotExist(err): - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) default: ctx.ServerError("UpdateLabel", err) } diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go index 32da1b41d1..985fd2ca45 100644 --- a/routers/web/org/projects.go +++ b/routers/web/org/projects.go @@ -36,7 +36,7 @@ const ( // MustEnableProjects check if projects are enabled in settings func MustEnableProjects(ctx *context.Context) { if unit.TypeProjects.UnitGlobalDisabled() { - ctx.NotFound("EnableProjects", nil) + ctx.NotFound(nil) return } } @@ -77,6 +77,11 @@ func Projects(ctx *context.Context) { return } + if err := project_service.LoadIssueNumbersForProjects(ctx, projects, ctx.Doer); err != nil { + ctx.ServerError("LoadIssueNumbersForProjects", err) + return + } + opTotal, err := db.Count[project_model.Project](ctx, project_model.SearchOptions{ OwnerID: ctx.ContextUser.ID, IsClosed: optional.Some(!isShowClosed), @@ -222,7 +227,7 @@ func DeleteProject(ctx *context.Context) { return } if p.OwnerID != ctx.ContextUser.ID { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } @@ -251,7 +256,7 @@ func RenderEditProject(ctx *context.Context) { return } if p.OwnerID != ctx.ContextUser.ID { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } @@ -296,7 +301,7 @@ func EditProjectPost(ctx *context.Context) { return } if p.OwnerID != ctx.ContextUser.ID { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } @@ -324,7 +329,11 @@ func ViewProject(ctx *context.Context) { return } if project.OwnerID != ctx.ContextUser.ID { - ctx.NotFound("", nil) + ctx.NotFound(nil) + return + } + if err := project.LoadOwner(ctx); err != nil { + ctx.ServerError("LoadOwner", err) return } @@ -340,14 +349,21 @@ func ViewProject(ctx *context.Context) { } assigneeID := ctx.FormInt64("assignee") // TODO: use "optional" but not 0 in the future - issuesMap, err := issues_model.LoadIssuesFromColumnList(ctx, columns, &issues_model.IssuesOptions{ + opts := issues_model.IssuesOptions{ LabelIDs: labelIDs, AssigneeID: optional.Some(assigneeID), - }) + Owner: project.Owner, + Doer: ctx.Doer, + } + + issuesMap, err := project_service.LoadIssuesFromProject(ctx, project, &opts) if err != nil { ctx.ServerError("LoadIssuesOfColumns", err) return } + for _, column := range columns { + column.NumIssues = int64(len(issuesMap[column.ID])) + } if project.CardType != project_model.CardTypeTextOnly { issuesAttachmentMap := make(map[int64][]*attachment_model.Attachment) @@ -593,7 +609,7 @@ func MoveIssues(ctx *context.Context) { return } if project.OwnerID != ctx.ContextUser.ID { - ctx.NotFound("InvalidRepoID", nil) + ctx.NotFound(nil) return } @@ -604,7 +620,7 @@ func MoveIssues(ctx *context.Context) { } if column.ProjectID != project.ID { - ctx.NotFound("ColumnNotInProject", nil) + ctx.NotFound(nil) return } diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index 26031029d6..aeea3708b2 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -74,7 +74,7 @@ func TeamsAction(ctx *context.Context) { switch ctx.PathParam("action") { case "join": if !ctx.Org.IsOwner { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } err = org_service.AddTeamMember(ctx, ctx.Org.Team, ctx.Doer) @@ -96,7 +96,7 @@ func TeamsAction(ctx *context.Context) { return case "remove": if !ctx.Org.IsOwner { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } @@ -123,7 +123,7 @@ func TeamsAction(ctx *context.Context) { return case "add": if !ctx.Org.IsOwner { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } uname := strings.ToLower(ctx.FormString("uname")) @@ -167,7 +167,7 @@ func TeamsAction(ctx *context.Context) { page = "team" case "remove_invite": if !ctx.Org.IsOwner { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } @@ -228,7 +228,7 @@ func checkIsOrgMemberAndRedirect(ctx *context.Context, defaultRedirect string) { // TeamsRepoAction operate team's repository func TeamsRepoAction(ctx *context.Context) { if !ctx.Org.IsOwner { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } @@ -568,7 +568,7 @@ func TeamInvite(ctx *context.Context) { invite, org, team, inviter, err := getTeamInviteFromContext(ctx) if err != nil { if org_model.IsErrTeamInviteNotFound(err) { - ctx.NotFound("ErrTeamInviteNotFound", err) + ctx.NotFound(err) } else { ctx.ServerError("getTeamInviteFromContext", err) } @@ -589,7 +589,7 @@ func TeamInvitePost(ctx *context.Context) { invite, org, team, _, err := getTeamInviteFromContext(ctx) if err != nil { if org_model.IsErrTeamInviteNotFound(err) { - ctx.NotFound("ErrTeamInviteNotFound", err) + ctx.NotFound(err) } else { ctx.ServerError("getTeamInviteFromContext", err) } diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index 539c4b6ed0..d07d195713 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -45,18 +45,18 @@ type Workflow struct { // MustEnableActions check if actions are enabled in settings func MustEnableActions(ctx *context.Context) { if !setting.Actions.Enabled { - ctx.NotFound("MustEnableActions", nil) + ctx.NotFound(nil) return } if unit.TypeActions.UnitGlobalDisabled() { - ctx.NotFound("MustEnableActions", nil) + ctx.NotFound(nil) return } if ctx.Repo.Repository != nil { if !ctx.Repo.CanRead(unit.TypeActions) { - ctx.NotFound("MustEnableActions", nil) + ctx.NotFound(nil) return } } @@ -88,7 +88,7 @@ func List(ctx *context.Context) { func WorkflowDispatchInputs(ctx *context.Context) { ref := ctx.FormString("ref") if ref == "" { - ctx.NotFound("WorkflowDispatchInputs: no ref", nil) + ctx.NotFound(nil) return } // get target commit of run from specified ref @@ -306,7 +306,7 @@ func prepareWorkflowList(ctx *context.Context, workflows []Workflow) { } ctx.Data["Actors"] = shared_user.MakeSelfOnTop(ctx.Doer, actors) - ctx.Data["StatusInfoList"] = actions_model.GetStatusInfoList(ctx) + ctx.Data["StatusInfoList"] = actions_model.GetStatusInfoList(ctx, ctx.Locale) pager := context.NewPagination(int(total), opts.PageSize, opts.Page, 5) pager.AddParamFromRequest(ctx.Req) diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index fc346b83b4..b27f8e0e7a 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -20,26 +20,20 @@ import ( actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" - "code.gitea.io/gitea/models/perm" - access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/actions" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "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/templates" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" actions_service "code.gitea.io/gitea/services/actions" context_module "code.gitea.io/gitea/services/context" - "code.gitea.io/gitea/services/convert" - "github.com/nektos/act/pkg/jobparser" "github.com/nektos/act/pkg/model" "xorm.io/builder" ) @@ -283,7 +277,7 @@ func ViewPost(ctx *context_module.Context) { if task != nil { steps, logs, err := convertToViewModel(ctx, req.LogCursors, task) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, steps...) @@ -387,7 +381,7 @@ func Rerun(ctx *context_module.Context) { run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } @@ -405,7 +399,7 @@ func Rerun(ctx *context_module.Context) { run.Started = 0 run.Stopped = 0 if err := actions_model.UpdateRun(ctx, run, "started", "stopped", "previous_duration"); err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } } @@ -420,7 +414,7 @@ func Rerun(ctx *context_module.Context) { // if the job has needs, it should be set to "blocked" status to wait for other jobs shouldBlock := len(j.Needs) > 0 if err := rerunJob(ctx, j, shouldBlock); err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } } @@ -434,7 +428,7 @@ func Rerun(ctx *context_module.Context) { // jobs other than the specified one should be set to "blocked" status shouldBlock := j.JobID != job.JobID if err := rerunJob(ctx, j, shouldBlock); err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } } @@ -476,29 +470,29 @@ func Logs(ctx *context_module.Context) { return } if job.TaskID == 0 { - ctx.Error(http.StatusNotFound, "job is not started") + ctx.HTTPError(http.StatusNotFound, "job is not started") return } err := job.LoadRun(ctx) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } task, err := actions_model.GetTaskByID(ctx, job.TaskID) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } if task.LogExpired { - ctx.Error(http.StatusNotFound, "logs have been cleaned up") + ctx.HTTPError(http.StatusNotFound, "logs have been cleaned up") return } reader, err := actions.OpenLogs(ctx, task.LogInStorage, task.LogFilename) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } defer reader.Close() @@ -548,7 +542,7 @@ func Cancel(ctx *context_module.Context) { } return nil }); err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } @@ -584,7 +578,7 @@ func Approve(ctx *context_module.Context) { } return nil }); err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } @@ -600,20 +594,20 @@ func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (*actions run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex) if err != nil { if errors.Is(err, util.ErrNotExist) { - ctx.Error(http.StatusNotFound, err.Error()) + ctx.HTTPError(http.StatusNotFound, err.Error()) return nil, nil } - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return nil, nil } run.Repo = ctx.Repo.Repository jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return nil, nil } if len(jobs) == 0 { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return nil, nil } @@ -639,7 +633,7 @@ func ArtifactsDeleteView(ctx *context_module.Context) { return } if err = actions_model.SetArtifactNeedDelete(ctx, run.ID, artifactName); err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } ctx.JSON(http.StatusOK, struct{}{}) @@ -652,10 +646,10 @@ func ArtifactsDownloadView(ctx *context_module.Context) { run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex) if err != nil { if errors.Is(err, util.ErrNotExist) { - ctx.Error(http.StatusNotFound, err.Error()) + ctx.HTTPError(http.StatusNotFound, err.Error()) return } - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } @@ -664,41 +658,30 @@ func ArtifactsDownloadView(ctx *context_module.Context) { ArtifactName: artifactName, }) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } if len(artifacts) == 0 { - ctx.Error(http.StatusNotFound, "artifact not found") + ctx.HTTPError(http.StatusNotFound, "artifact not found") return } // if artifacts status is not uploaded-confirmed, treat it as not found for _, art := range artifacts { - if art.Status != int64(actions_model.ArtifactStatusUploadConfirmed) { - ctx.Error(http.StatusNotFound, "artifact not found") + if art.Status != actions_model.ArtifactStatusUploadConfirmed { + ctx.HTTPError(http.StatusNotFound, "artifact not found") return } } ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip; filename*=UTF-8''%s.zip", url.PathEscape(artifactName), artifactName)) - // Artifacts using the v4 backend are stored as a single combined zip file per artifact on the backend - // The v4 backend enshures ContentEncoding is set to "application/zip", which is not the case for the old backend - if len(artifacts) == 1 && artifacts[0].ArtifactName+".zip" == artifacts[0].ArtifactPath && artifacts[0].ContentEncoding == "application/zip" { - art := artifacts[0] - if setting.Actions.ArtifactStorage.ServeDirect() { - u, err := storage.ActionsArtifacts.URL(art.StoragePath, art.ArtifactPath, nil) - if u != nil && err == nil { - ctx.Redirect(u.String()) - return - } - } - f, err := storage.ActionsArtifacts.Open(art.StoragePath) + if len(artifacts) == 1 && actions.IsArtifactV4(artifacts[0]) { + err := actions.DownloadArtifactV4(ctx.Base, artifacts[0]) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } - _, _ = io.Copy(ctx.Resp, f) return } @@ -709,7 +692,7 @@ func ArtifactsDownloadView(ctx *context_module.Context) { for _, art := range artifacts { f, err := storage.ActionsArtifacts.Open(art.StoragePath) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } @@ -717,7 +700,7 @@ func ArtifactsDownloadView(ctx *context_module.Context) { if art.ContentEncoding == "gzip" { r, err = gzip.NewReader(f) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } } else { @@ -727,11 +710,11 @@ func ArtifactsDownloadView(ctx *context_module.Context) { w, err := writer.Create(art.ArtifactPath) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } if _, err := io.Copy(w, r); err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } } @@ -792,142 +775,28 @@ func Run(ctx *context_module.Context) { ctx.ServerError("ref", nil) return } - - // can not rerun job when workflow is disabled - cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions) - cfg := cfgUnit.ActionsConfig() - if cfg.IsWorkflowDisabled(workflowID) { - ctx.Flash.Error(ctx.Tr("actions.workflow.disabled")) - ctx.Redirect(redirectURL) - return - } - - // get target commit of run from specified ref - refName := git.RefName(ref) - var runTargetCommit *git.Commit - var err error - if refName.IsTag() { - runTargetCommit, err = ctx.Repo.GitRepo.GetTagCommit(refName.TagName()) - } else if refName.IsBranch() { - runTargetCommit, err = ctx.Repo.GitRepo.GetBranchCommit(refName.BranchName()) - } else { - ctx.Flash.Error(ctx.Tr("form.git_ref_name_error", ref)) - ctx.Redirect(redirectURL) - return - } - if err != nil { - ctx.Flash.Error(ctx.Tr("form.target_ref_not_exist", ref)) - ctx.Redirect(redirectURL) - return - } - - // get workflow entry from runTargetCommit - entries, err := actions.ListWorkflows(runTargetCommit) - if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) - return - } - - // find workflow from commit - var workflows []*jobparser.SingleWorkflow - for _, entry := range entries { - if entry.Name() == workflowID { - content, err := actions.GetContentFromEntry(entry) - if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) - return - } - workflows, err = jobparser.Parse(content) - if err != nil { - ctx.ServerError("workflow", err) - return - } - break - } - } - - if len(workflows) == 0 { - ctx.Flash.Error(ctx.Tr("actions.workflow.not_found", workflowID)) - ctx.Redirect(redirectURL) - return - } - - // get inputs from post - workflow := &model.Workflow{ - RawOn: workflows[0].RawOn, - } - inputs := make(map[string]any) - if workflowDispatch := workflow.WorkflowDispatchConfig(); workflowDispatch != nil { + err := actions_service.DispatchActionWorkflow(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, workflowID, ref, func(workflowDispatch *model.WorkflowDispatch, inputs map[string]any) error { for name, config := range workflowDispatch.Inputs { value := ctx.Req.PostFormValue(name) if config.Type == "boolean" { - // https://www.w3.org/TR/html401/interact/forms.html - // https://stackoverflow.com/questions/11424037/do-checkbox-inputs-only-post-data-if-theyre-checked - // Checkboxes (and radio buttons) are on/off switches that may be toggled by the user. - // A switch is "on" when the control element's checked attribute is set. - // When a form is submitted, only "on" checkbox controls can become successful. - inputs[name] = strconv.FormatBool(value == "on") + inputs[name] = strconv.FormatBool(ctx.FormBool(name)) } else if value != "" { inputs[name] = value } else { inputs[name] = config.Default } } - } - - // ctx.Req.PostForm -> WorkflowDispatchPayload.Inputs -> ActionRun.EventPayload -> runner: ghc.Event - // https://docs.github.com/en/actions/learn-github-actions/contexts#github-context - // https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_dispatch - workflowDispatchPayload := &api.WorkflowDispatchPayload{ - Workflow: workflowID, - Ref: ref, - Repository: convert.ToRepo(ctx, ctx.Repo.Repository, access_model.Permission{AccessMode: perm.AccessModeNone}), - Inputs: inputs, - Sender: convert.ToUserWithAccessMode(ctx, ctx.Doer, perm.AccessModeNone), - } - var eventPayload []byte - if eventPayload, err = workflowDispatchPayload.JSONPayload(); err != nil { - ctx.ServerError("JSONPayload", err) - return - } - - run := &actions_model.ActionRun{ - Title: strings.SplitN(runTargetCommit.CommitMessage, "\n", 2)[0], - RepoID: ctx.Repo.Repository.ID, - OwnerID: ctx.Repo.Repository.OwnerID, - WorkflowID: workflowID, - TriggerUserID: ctx.Doer.ID, - Ref: ref, - CommitSHA: runTargetCommit.ID.String(), - IsForkPullRequest: false, - Event: "workflow_dispatch", - TriggerEvent: "workflow_dispatch", - EventPayload: string(eventPayload), - Status: actions_model.StatusWaiting, - } - - // cancel running jobs of the same workflow - if err := actions_model.CancelPreviousJobs( - ctx, - run.RepoID, - run.Ref, - run.WorkflowID, - run.Event, - ); err != nil { - log.Error("CancelRunningJobs: %v", err) - } - - // Insert the action run and its associated jobs into the database - if err := actions_model.InsertRun(ctx, run, workflows); err != nil { - ctx.ServerError("workflow", err) - return - } - - alljobs, err := db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{RunID: run.ID}) + return nil + }) if err != nil { - log.Error("FindRunJobs: %v", err) + if errLocale := util.ErrAsLocale(err); errLocale != nil { + ctx.Flash.Error(ctx.Tr(errLocale.TrKey, errLocale.TrArgs...)) + ctx.Redirect(redirectURL) + } else { + ctx.ServerError("DispatchActionWorkflow", err) + } + return } - actions_service.CreateCommitStatus(ctx, alljobs...) ctx.Flash.Success(ctx.Tr("actions.workflow.run_success", workflowID)) ctx.Redirect(redirectURL) diff --git a/routers/web/repo/attachment.go b/routers/web/repo/attachment.go index f8e51521be..9eda926dad 100644 --- a/routers/web/repo/attachment.go +++ b/routers/web/repo/attachment.go @@ -34,13 +34,13 @@ func UploadReleaseAttachment(ctx *context.Context) { // UploadAttachment response for uploading attachments func uploadAttachment(ctx *context.Context, repoID int64, allowedTypes string) { if !setting.Attachment.Enabled { - ctx.Error(http.StatusNotFound, "attachment is not enabled") + ctx.HTTPError(http.StatusNotFound, "attachment is not enabled") return } file, header, err := ctx.Req.FormFile("file") if err != nil { - ctx.Error(http.StatusInternalServerError, fmt.Sprintf("FormFile: %v", err)) + ctx.HTTPError(http.StatusInternalServerError, fmt.Sprintf("FormFile: %v", err)) return } defer file.Close() @@ -52,10 +52,10 @@ func uploadAttachment(ctx *context.Context, repoID int64, allowedTypes string) { }) if err != nil { if upload.IsErrFileTypeForbidden(err) { - ctx.Error(http.StatusBadRequest, err.Error()) + ctx.HTTPError(http.StatusBadRequest, err.Error()) return } - ctx.Error(http.StatusInternalServerError, fmt.Sprintf("NewAttachment: %v", err)) + ctx.HTTPError(http.StatusInternalServerError, fmt.Sprintf("NewAttachment: %v", err)) return } @@ -70,16 +70,16 @@ func DeleteAttachment(ctx *context.Context) { file := ctx.FormString("file") attach, err := repo_model.GetAttachmentByUUID(ctx, file) if err != nil { - ctx.Error(http.StatusBadRequest, err.Error()) + ctx.HTTPError(http.StatusBadRequest, err.Error()) return } if !ctx.IsSigned || (ctx.Doer.ID != attach.UploaderID) { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } err = repo_model.DeleteAttachment(ctx, attach, true) if err != nil { - ctx.Error(http.StatusInternalServerError, fmt.Sprintf("DeleteAttachment: %v", err)) + ctx.HTTPError(http.StatusInternalServerError, fmt.Sprintf("DeleteAttachment: %v", err)) return } ctx.JSON(http.StatusOK, map[string]string{ @@ -92,7 +92,7 @@ func ServeAttachment(ctx *context.Context, uuid string) { attach, err := repo_model.GetAttachmentByUUID(ctx, uuid) if err != nil { if repo_model.IsErrAttachmentNotExist(err) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) } else { ctx.ServerError("GetAttachmentByUUID", err) } @@ -107,17 +107,17 @@ func ServeAttachment(ctx *context.Context, uuid string) { if repository == nil { // If not linked if !(ctx.IsSigned && attach.UploaderID == ctx.Doer.ID) { // We block if not the uploader - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } } else { // If we have the repository we check access perm, err := access_model.GetUserRepoPermission(ctx, repository, ctx.Doer) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "GetUserRepoPermission", err.Error()) return } if !perm.CanRead(unitType) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } } diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index 808d2cb052..1c67a40020 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -42,7 +42,7 @@ type blameRow struct { func RefBlame(ctx *context.Context) { fileName := ctx.Repo.TreePath if len(fileName) == 0 { - ctx.NotFound("Blame FileName", nil) + ctx.NotFound(nil) return } @@ -108,7 +108,7 @@ func RefBlame(ctx *context.Context) { ctx.Data["NumLines"], err = blob.GetBlobLineCount() if err != nil { - ctx.NotFound("GetBlobLineCount", err) + ctx.NotFound(err) return } @@ -116,7 +116,7 @@ func RefBlame(ctx *context.Context) { result, err := performBlame(ctx, ctx.Repo.Repository.RepoPath(), ctx.Repo.Commit, fileName, bypassBlameIgnore) if err != nil { - ctx.NotFound("CreateBlameReader", err) + ctx.NotFound(err) return } @@ -230,7 +230,7 @@ func processBlameParts(ctx *context.Context, blameParts []*git.BlamePart) map[st commit, err = ctx.Repo.GitRepo.GetCommit(sha) if err != nil { if git.IsErrNotExist(err) { - ctx.NotFound("Repo.GitRepo.GetCommit", err) + ctx.NotFound(err) } else { ctx.ServerError("Repo.GitRepo.GetCommit", err) } @@ -243,7 +243,12 @@ func processBlameParts(ctx *context.Context, blameParts []*git.BlamePart) map[st } // populate commit email addresses to later look up avatars. - for _, c := range user_model.ValidateCommitsWithEmails(ctx, commits) { + validatedCommits, err := user_model.ValidateCommitsWithEmails(ctx, commits) + if err != nil { + ctx.ServerError("ValidateCommitsWithEmails", err) + return nil + } + for _, c := range validatedCommits { commitNames[c.ID.String()] = c } diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index 6f8d4d9959..5d963eff66 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -178,7 +178,7 @@ func jsonRedirectBranches(ctx *context.Context) { func CreateBranch(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewBranchForm) if !ctx.Repo.CanCreateBranch() { - ctx.NotFound("CreateBranch", nil) + ctx.NotFound(nil) return } diff --git a/routers/web/repo/cherry_pick.go b/routers/web/repo/cherry_pick.go index 616bb8c6cb..ec50e1435e 100644 --- a/routers/web/repo/cherry_pick.go +++ b/routers/web/repo/cherry_pick.go @@ -28,7 +28,7 @@ func CherryPick(ctx *context.Context) { cherryPickCommit, err := ctx.Repo.GitRepo.GetCommit(ctx.PathParam("sha")) if err != nil { if git.IsErrNotExist(err) { - ctx.NotFound("Missing Commit", err) + ctx.NotFound(err) return } ctx.ServerError("GetCommit", err) @@ -148,7 +148,7 @@ func CherryPickPost(ctx *context.Context) { if form.Revert { if err := git.GetReverseRawDiff(ctx, ctx.Repo.Repository.RepoPath(), sha, buf); err != nil { if git.IsErrNotExist(err) { - ctx.NotFound("GetRawDiff", errors.New("commit "+ctx.PathParam("sha")+" does not exist.")) + ctx.NotFound(errors.New("commit " + ctx.PathParam("sha") + " does not exist.")) return } ctx.ServerError("GetRawDiff", err) @@ -157,7 +157,7 @@ func CherryPickPost(ctx *context.Context) { } else { if err := git.GetRawDiff(ctx.Repo.GitRepo, sha, git.RawDiffType("patch"), buf); err != nil { if git.IsErrNotExist(err) { - ctx.NotFound("GetRawDiff", errors.New("commit "+ctx.PathParam("sha")+" does not exist.")) + ctx.NotFound(errors.New("commit " + ctx.PathParam("sha") + " does not exist.")) return } ctx.ServerError("GetRawDiff", err) diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 8ffda8ae0a..9c12ef9297 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -22,16 +22,18 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/gitgraph" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/util" + asymkey_service "code.gitea.io/gitea/services/asymkey" "code.gitea.io/gitea/services/context" + git_service "code.gitea.io/gitea/services/git" "code.gitea.io/gitea/services/gitdiff" repo_service "code.gitea.io/gitea/services/repository" + "code.gitea.io/gitea/services/repository/gitgraph" ) const ( @@ -57,7 +59,7 @@ func RefCommits(ctx *context.Context) { func Commits(ctx *context.Context) { ctx.Data["PageIsCommits"] = true if ctx.Repo.Commit == nil { - ctx.NotFound("Commit not found", nil) + ctx.NotFound(nil) return } ctx.Data["PageIsViewCode"] = true @@ -80,7 +82,11 @@ func Commits(ctx *context.Context) { ctx.ServerError("CommitsByRange", err) return } - ctx.Data["Commits"] = processGitCommits(ctx, commits) + ctx.Data["Commits"], err = processGitCommits(ctx, commits) + if err != nil { + ctx.ServerError("processGitCommits", err) + return + } commitIDs := make([]string, 0, len(commits)) for _, c := range commits { commitIDs = append(commitIDs, c.ID.String()) @@ -192,7 +198,11 @@ func SearchCommits(ctx *context.Context) { return } ctx.Data["CommitCount"] = len(commits) - ctx.Data["Commits"] = processGitCommits(ctx, commits) + ctx.Data["Commits"], err = processGitCommits(ctx, commits) + if err != nil { + ctx.ServerError("processGitCommits", err) + return + } ctx.Data["Keyword"] = query if all { @@ -216,7 +226,7 @@ func FileHistory(ctx *context.Context) { ctx.ServerError("FileCommitsCount", err) return } else if commitsCount == 0 { - ctx.NotFound("FileCommitsCount", nil) + ctx.NotFound(nil) return } @@ -235,7 +245,11 @@ func FileHistory(ctx *context.Context) { ctx.ServerError("CommitsByFileAndRange", err) return } - ctx.Data["Commits"] = processGitCommits(ctx, commits) + ctx.Data["Commits"], err = processGitCommits(ctx, commits) + if err != nil { + ctx.ServerError("processGitCommits", err) + return + } ctx.Data["Username"] = ctx.Repo.Owner.Name ctx.Data["Reponame"] = ctx.Repo.Repository.Name @@ -283,7 +297,7 @@ func Diff(ctx *context.Context) { commit, err := gitRepo.GetCommit(commitID) if err != nil { if git.IsErrNotExist(err) { - ctx.NotFound("Repo.GitRepo.GetCommit", err) + ctx.NotFound(err) } else { ctx.ServerError("Repo.GitRepo.GetCommit", err) } @@ -310,7 +324,7 @@ func Diff(ctx *context.Context) { FileOnly: fileOnly, }, files...) if err != nil { - ctx.NotFound("GetDiff", err) + ctx.NotFound(err) return } @@ -318,7 +332,7 @@ func Diff(ctx *context.Context) { for i := 0; i < commit.ParentCount(); i++ { sha, err := commit.ParentID(i) if err != nil { - ctx.NotFound("repo.Diff", err) + ctx.NotFound(err) return } parents[i] = sha.String() @@ -333,7 +347,7 @@ func Diff(ctx *context.Context) { if commit.ParentCount() > 0 { parentCommit, err = gitRepo.GetCommit(parents[0]) if err != nil { - ctx.NotFound("GetParentCommit", err) + ctx.NotFound(err) return } } @@ -353,7 +367,7 @@ func Diff(ctx *context.Context) { ctx.Data["CommitStatus"] = git_model.CalcCommitStatus(statuses) ctx.Data["CommitStatuses"] = statuses - verification := asymkey_model.ParseCommitWithSignature(ctx, commit) + verification := asymkey_service.ParseCommitWithSignature(ctx, commit) ctx.Data["Verification"] = verification ctx.Data["Author"] = user_model.ValidateCommitWithEmail(ctx, commit) ctx.Data["Parents"] = parents @@ -407,8 +421,7 @@ func RawDiff(ctx *context.Context) { ctx.Resp, ); err != nil { if git.IsErrNotExist(err) { - ctx.NotFound("GetRawDiff", - errors.New("commit "+ctx.PathParam("sha")+" does not exist.")) + ctx.NotFound(errors.New("commit " + ctx.PathParam("sha") + " does not exist.")) return } ctx.ServerError("GetRawDiff", err) @@ -416,13 +429,16 @@ func RawDiff(ctx *context.Context) { } } -func processGitCommits(ctx *context.Context, gitCommits []*git.Commit) []*git_model.SignCommitWithStatuses { - commits := git_model.ConvertFromGitCommit(ctx, gitCommits, ctx.Repo.Repository) +func processGitCommits(ctx *context.Context, gitCommits []*git.Commit) ([]*git_model.SignCommitWithStatuses, error) { + commits, err := git_service.ConvertFromGitCommit(ctx, gitCommits, ctx.Repo.Repository) + if err != nil { + return nil, err + } if !ctx.Repo.CanRead(unit_model.TypeActions) { for _, commit := range commits { commit.Status.HideActionsURL(ctx) git_model.CommitStatusesHideActionsURL(ctx, commit.Statuses) } } - return commits + return commits, nil } diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index b3c1eb7cb0..71bce759a9 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -258,7 +258,7 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { ci.HeadUser, err = user_model.GetUserByName(ctx, headInfos[0]) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.NotFound("GetUserByName", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetUserByName", err) } @@ -273,7 +273,7 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { ci.HeadRepo, err = repo_model.GetRepositoryByOwnerAndName(ctx, headInfosSplit[0], headInfosSplit[1]) if err != nil { if repo_model.IsErrRepoNotExist(err) { - ctx.NotFound("GetRepositoryByOwnerAndName", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetRepositoryByOwnerAndName", err) } @@ -281,7 +281,7 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { } if err := ci.HeadRepo.LoadOwner(ctx); err != nil { if user_model.IsErrUserNotExist(err) { - ctx.NotFound("GetUserByName", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetUserByName", err) } @@ -292,7 +292,7 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { isSameRepo = ci.HeadRepo.ID == ctx.Repo.Repository.ID } } else { - ctx.NotFound("CompareAndPullRequest", nil) + ctx.NotFound(nil) return nil } ctx.Data["HeadUser"] = ci.HeadUser @@ -318,7 +318,7 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { } return nil } else { - ctx.NotFound("IsRefExist", nil) + ctx.NotFound(nil) return nil } } @@ -408,7 +408,7 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { } defer ci.HeadGitRepo.Close() } else { - ctx.NotFound("ParseCompareInfo", nil) + ctx.NotFound(nil) return nil } @@ -430,7 +430,7 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { baseRepo, permBase) } - ctx.NotFound("ParseCompareInfo", nil) + ctx.NotFound(nil) return nil } @@ -449,7 +449,7 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { ci.HeadRepo, permHead) } - ctx.NotFound("ParseCompareInfo", nil) + ctx.NotFound(nil) return nil } ctx.Data["CanWriteToHeadRepo"] = permHead.CanWrite(unit.TypeCode) @@ -513,7 +513,7 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { ctx.Data["HeadBranch"] = ci.HeadBranch headIsCommit = true } else { - ctx.NotFound("IsRefExist", nil) + ctx.NotFound(nil) return nil } } @@ -533,7 +533,7 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { baseRepo, permBase) } - ctx.NotFound("ParseCompareInfo", nil) + ctx.NotFound(nil) return nil } @@ -647,7 +647,11 @@ func PrepareCompareDiff( return false } - commits := processGitCommits(ctx, ci.CompareInfo.Commits) + commits, err := processGitCommits(ctx, ci.CompareInfo.Commits) + if err != nil { + ctx.ServerError("processGitCommits", err) + return false + } ctx.Data["Commits"] = commits ctx.Data["CommitCount"] = len(commits) @@ -876,7 +880,7 @@ func ExcerptBlob(ctx *context.Context) { chunkSize := gitdiff.BlobExcerptChunkSize commit, err := gitRepo.GetCommit(commitID) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetCommit") + ctx.HTTPError(http.StatusInternalServerError, "GetCommit") return } section := &gitdiff.DiffSection{ @@ -905,7 +909,7 @@ func ExcerptBlob(ctx *context.Context) { idxRight = lastRight } if err != nil { - ctx.Error(http.StatusInternalServerError, "getExcerptLines") + ctx.HTTPError(http.StatusInternalServerError, "getExcerptLines") return } if idxRight > lastRight { diff --git a/routers/web/repo/download.go b/routers/web/repo/download.go index cb1163c70b..060381e9d6 100644 --- a/routers/web/repo/download.go +++ b/routers/web/repo/download.go @@ -85,7 +85,7 @@ func getBlobForEntry(ctx *context.Context) (*git.Blob, *time.Time) { entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath) if err != nil { if git.IsErrNotExist(err) { - ctx.NotFound("GetTreeEntryByPath", err) + ctx.NotFound(err) } else { ctx.ServerError("GetTreeEntryByPath", err) } @@ -93,7 +93,7 @@ func getBlobForEntry(ctx *context.Context) (*git.Blob, *time.Time) { } if entry.IsDir() || entry.IsSubModule() { - ctx.NotFound("getBlobForEntry", nil) + ctx.NotFound(nil) return nil, nil } @@ -136,7 +136,7 @@ func DownloadByID(ctx *context.Context) { blob, err := ctx.Repo.GitRepo.GetBlob(ctx.PathParam("sha")) if err != nil { if git.IsErrNotExist(err) { - ctx.NotFound("GetBlob", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetBlob", err) } @@ -152,7 +152,7 @@ func DownloadByIDOrLFS(ctx *context.Context) { blob, err := ctx.Repo.GitRepo.GetBlob(ctx.PathParam("sha")) if err != nil { if git.IsErrNotExist(err) { - ctx.NotFound("GetBlob", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetBlob", err) } diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index cc4ffc698d..3107d7b849 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -141,19 +141,19 @@ func editFile(ctx *context.Context, isNewFile bool) { // No way to edit a directory online. if entry.IsDir() { - ctx.NotFound("entry.IsDir", nil) + ctx.NotFound(nil) return } blob := entry.Blob() if blob.Size() >= setting.UI.MaxDisplayFileSize { - ctx.NotFound("blob.Size", err) + ctx.NotFound(err) return } dataRc, err := blob.DataAsync() if err != nil { - ctx.NotFound("blob.Data", err) + ctx.NotFound(err) return } @@ -168,7 +168,7 @@ func editFile(ctx *context.Context, isNewFile bool) { // Only some file types are editable online as text. if !typesniffer.DetectContentType(buf).IsRepresentableAsText() { - ctx.NotFound("typesniffer.IsRepresentableAsText", nil) + ctx.NotFound(nil) return } @@ -324,7 +324,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", fileErr.Path), tplEditFile, &form) } } else { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) } } else if files_service.IsErrRepoFileAlreadyExists(err) { ctx.Data["Err_TreePath"] = true @@ -334,7 +334,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b if branchErr, ok := err.(git.ErrBranchNotExist); ok { ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplEditFile, &form) } else { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) } } else if git_model.IsErrBranchAlreadyExists(err) { // For when a user specifies a new branch that already exists @@ -342,7 +342,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b if branchErr, ok := err.(git_model.ErrBranchAlreadyExists); ok { ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form) } else { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) } } else if files_service.IsErrCommitIDDoesNotMatch(err) { ctx.RenderWithErr(ctx.Tr("repo.editor.commit_id_not_matching"), tplEditFile, &form) @@ -404,22 +404,22 @@ func DiffPreviewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.EditPreviewDiffForm) treePath := cleanUploadFileName(ctx.Repo.TreePath) if len(treePath) == 0 { - ctx.Error(http.StatusInternalServerError, "file name to diff is invalid") + ctx.HTTPError(http.StatusInternalServerError, "file name to diff is invalid") return } entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treePath) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetTreeEntryByPath: "+err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "GetTreeEntryByPath: "+err.Error()) return } else if entry.IsDir() { - ctx.Error(http.StatusUnprocessableEntity) + ctx.HTTPError(http.StatusUnprocessableEntity) return } diff, err := files_service.GetDiffPreview(ctx, ctx.Repo.Repository, ctx.Repo.BranchName, treePath, form.Content) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetDiffPreview: "+err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "GetDiffPreview: "+err.Error()) return } @@ -545,14 +545,14 @@ func DeleteFilePost(ctx *context.Context) { if branchErr, ok := err.(git.ErrBranchNotExist); ok { ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplDeleteFile, &form) } else { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) } } else if git_model.IsErrBranchAlreadyExists(err) { // For when a user specifies a new branch that already exists if branchErr, ok := err.(git_model.ErrBranchAlreadyExists); ok { ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplDeleteFile, &form) } else { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) } } else if files_service.IsErrCommitIDDoesNotMatch(err) || git.IsErrPushOutOfDate(err) { ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_deleting", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(form.LastCommit)+"..."+util.PathEscapeSegments(ctx.Repo.CommitID)), tplDeleteFile, &form) @@ -751,7 +751,7 @@ func UploadFilePost(ctx *context.Context) { case git.EntryModeBlob: ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path), tplUploadFile, &form) default: - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) } } else if files_service.IsErrRepoFileAlreadyExists(err) { ctx.Data["Err_TreePath"] = true @@ -815,7 +815,7 @@ func cleanUploadFileName(name string) string { func UploadFileToServer(ctx *context.Context) { file, header, err := ctx.Req.FormFile("file") if err != nil { - ctx.Error(http.StatusInternalServerError, fmt.Sprintf("FormFile: %v", err)) + ctx.HTTPError(http.StatusInternalServerError, fmt.Sprintf("FormFile: %v", err)) return } defer file.Close() @@ -828,19 +828,19 @@ func UploadFileToServer(ctx *context.Context) { err = upload.Verify(buf, header.Filename, setting.Repository.Upload.AllowedTypes) if err != nil { - ctx.Error(http.StatusBadRequest, err.Error()) + ctx.HTTPError(http.StatusBadRequest, err.Error()) return } name := cleanUploadFileName(header.Filename) if len(name) == 0 { - ctx.Error(http.StatusInternalServerError, "Upload file name is invalid") + ctx.HTTPError(http.StatusInternalServerError, "Upload file name is invalid") return } upload, err := repo_model.NewUpload(ctx, name, buf, file) if err != nil { - ctx.Error(http.StatusInternalServerError, fmt.Sprintf("NewUpload: %v", err)) + ctx.HTTPError(http.StatusInternalServerError, fmt.Sprintf("NewUpload: %v", err)) return } @@ -859,7 +859,7 @@ func RemoveUploadFileFromServer(ctx *context.Context) { } if err := repo_model.DeleteUploadByUUID(ctx, form.File); err != nil { - ctx.Error(http.StatusInternalServerError, fmt.Sprintf("DeleteUploadByUUID: %v", err)) + ctx.HTTPError(http.StatusInternalServerError, fmt.Sprintf("DeleteUploadByUUID: %v", err)) return } diff --git a/routers/web/repo/fork.go b/routers/web/repo/fork.go index 786b5d7e43..36e64bfee3 100644 --- a/routers/web/repo/fork.go +++ b/routers/web/repo/fork.go @@ -37,7 +37,7 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository { if forkRepo.IsEmpty { log.Trace("Empty repository %-v", forkRepo) - ctx.NotFound("getForkRepository", nil) + ctx.NotFound(nil) return nil } @@ -189,7 +189,7 @@ func ForkPost(ctx *context.Context) { ctx.ServerError("CanCreateOrgRepo", err) return } else if !isAllowedToFork { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } } diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index 6b2a7fd076..e27040edc6 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -147,7 +147,7 @@ func httpBase(ctx *context.Context) *serviceHandler { if !ctx.IsSigned { // TODO: support digit auth - which would be Authorization header with digit ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`) - ctx.Error(http.StatusUnauthorized) + ctx.HTTPError(http.StatusUnauthorized) return nil } diff --git a/routers/web/repo/helper.go b/routers/web/repo/helper.go index 3bf064c1e3..7b92cba0fd 100644 --- a/routers/web/repo/helper.go +++ b/routers/web/repo/helper.go @@ -12,7 +12,7 @@ func HandleGitError(ctx *context.Context, msg string, err error) { if git.IsErrNotExist(err) { ctx.Data["NotFoundPrompt"] = ctx.Locale.Tr("repo.tree_path_not_found", ctx.Repo.TreePath, ctx.Repo.RefTypeNameSubURL()) ctx.Data["NotFoundGoBackURL"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL() - ctx.NotFound(msg, err) + ctx.NotFound(err) } else { ctx.ServerError(msg, err) } diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index f150897a2d..dbbe29a3c3 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -91,7 +91,7 @@ func MustAllowUserComment(ctx *context.Context) { func MustEnableIssues(ctx *context.Context) { if !ctx.Repo.CanRead(unit.TypeIssues) && !ctx.Repo.CanRead(unit.TypeExternalTracker) { - ctx.NotFound("MustEnableIssues", nil) + ctx.NotFound(nil) return } @@ -105,7 +105,7 @@ func MustEnableIssues(ctx *context.Context) { // MustAllowPulls check if repository enable pull requests and user have right to do that func MustAllowPulls(ctx *context.Context) { if !ctx.Repo.Repository.CanEnablePulls() || !ctx.Repo.CanRead(unit.TypePullRequests) { - ctx.NotFound("MustAllowPulls", nil) + ctx.NotFound(nil) return } @@ -201,7 +201,7 @@ func GetActionIssue(ctx *context.Context) *issues_model.Issue { func checkIssueRights(ctx *context.Context, issue *issues_model.Issue) { if issue.IsPull && !ctx.Repo.CanRead(unit.TypePullRequests) || !issue.IsPull && !ctx.Repo.CanRead(unit.TypeIssues) { - ctx.NotFound("IssueOrPullRequestUnitNotAllowed", nil) + ctx.NotFound(nil) } } @@ -229,11 +229,11 @@ func getActionIssues(ctx *context.Context) issues_model.IssueList { prUnitEnabled := ctx.Repo.CanRead(unit.TypePullRequests) for _, issue := range issues { if issue.RepoID != ctx.Repo.Repository.ID { - ctx.NotFound("some issue's RepoID is incorrect", errors.New("some issue's RepoID is incorrect")) + ctx.NotFound(errors.New("some issue's RepoID is incorrect")) return nil } if issue.IsPull && !prUnitEnabled || !issue.IsPull && !issueUnitEnabled { - ctx.NotFound("IssueOrPullRequestUnitNotAllowed", nil) + ctx.NotFound(nil) return nil } if err = issue.LoadAttributes(ctx); err != nil { @@ -249,9 +249,9 @@ func GetIssueInfo(ctx *context.Context) { issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { if issues_model.IsErrIssueNotExist(err) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) } else { - ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "GetIssueByIndex", err.Error()) } return } @@ -259,13 +259,13 @@ func GetIssueInfo(ctx *context.Context) { if issue.IsPull { // Need to check if Pulls are enabled and we can read Pulls if !ctx.Repo.Repository.CanEnablePulls() || !ctx.Repo.CanRead(unit.TypePullRequests) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } } else { // Need to check if Issues are enabled and we can read Issues if !ctx.Repo.CanRead(unit.TypeIssues) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } } @@ -284,13 +284,13 @@ func UpdateIssueTitle(ctx *context.Context) { } if !ctx.IsSigned || (!issue.IsPoster(ctx.Doer.ID) && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } title := ctx.FormTrim("title") if len(title) == 0 { - ctx.Error(http.StatusNoContent) + ctx.HTTPError(http.StatusNoContent) return } @@ -312,7 +312,7 @@ func UpdateIssueRef(ctx *context.Context) { } if !ctx.IsSigned || (!issue.IsPoster(ctx.Doer.ID) && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) || issue.IsPull { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } @@ -336,7 +336,7 @@ func UpdateIssueContent(ctx *context.Context) { } if !ctx.IsSigned || (ctx.Doer.ID != issue.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } @@ -382,21 +382,21 @@ func UpdateIssueDeadline(ctx *context.Context) { issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { if issues_model.IsErrIssueNotExist(err) { - ctx.NotFound("GetIssueByIndex", err) + ctx.NotFound(err) } else { - ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "GetIssueByIndex", err.Error()) } return } if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { - ctx.Error(http.StatusForbidden, "", "Not repo writer") + ctx.HTTPError(http.StatusForbidden, "", "Not repo writer") return } deadlineUnix, _ := common.ParseDeadlineDateToEndOfDay(ctx.FormString("deadline")) if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil { - ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "UpdateIssueDeadline", err.Error()) return } @@ -497,7 +497,7 @@ func ChangeIssueReaction(ctx *context.Context) { } } - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } @@ -540,7 +540,7 @@ func ChangeIssueReaction(ctx *context.Context) { log.Trace("Reaction for issue removed: %d/%d", ctx.Repo.Repository.ID, issue.ID) default: - ctx.NotFound(fmt.Sprintf("Unknown action %s", ctx.PathParam("action")), nil) + ctx.NotFound(nil) return } diff --git a/routers/web/repo/issue_comment.go b/routers/web/repo/issue_comment.go index 8564c613de..bc84950701 100644 --- a/routers/web/repo/issue_comment.go +++ b/routers/web/repo/issue_comment.go @@ -53,7 +53,7 @@ func NewComment(ctx *context.Context) { } } - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } @@ -224,17 +224,17 @@ func UpdateCommentContent(ctx *context.Context) { } if comment.Issue.RepoID != ctx.Repo.Repository.ID { - ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{}) + ctx.NotFound(issues_model.ErrCommentNotExist{}) return } if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } if !comment.Type.HasContentSupport() { - ctx.Error(http.StatusNoContent) + ctx.HTTPError(http.StatusNoContent) return } @@ -302,15 +302,15 @@ func DeleteComment(ctx *context.Context) { } if comment.Issue.RepoID != ctx.Repo.Repository.ID { - ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{}) + ctx.NotFound(issues_model.ErrCommentNotExist{}) return } if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } else if !comment.Type.HasContentSupport() { - ctx.Error(http.StatusNoContent) + ctx.HTTPError(http.StatusNoContent) return } @@ -337,7 +337,7 @@ func ChangeCommentReaction(ctx *context.Context) { } if comment.Issue.RepoID != ctx.Repo.Repository.ID { - ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{}) + ctx.NotFound(issues_model.ErrCommentNotExist{}) return } @@ -360,12 +360,12 @@ func ChangeCommentReaction(ctx *context.Context) { } } - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } if !comment.Type.HasContentSupport() { - ctx.Error(http.StatusNoContent) + ctx.HTTPError(http.StatusNoContent) return } @@ -403,7 +403,7 @@ func ChangeCommentReaction(ctx *context.Context) { log.Trace("Reaction for comment removed: %d/%d/%d", ctx.Repo.Repository.ID, comment.Issue.ID, comment.ID) default: - ctx.NotFound(fmt.Sprintf("Unknown action %s", ctx.PathParam("action")), nil) + ctx.NotFound(nil) return } @@ -442,12 +442,12 @@ func GetCommentAttachments(ctx *context.Context) { } if comment.Issue.RepoID != ctx.Repo.Repository.ID { - ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{}) + ctx.NotFound(issues_model.ErrCommentNotExist{}) return } if !ctx.Repo.Permission.CanReadIssuesOrPulls(comment.Issue.IsPull) { - ctx.NotFound("CanReadIssuesOrPulls", issues_model.ErrCommentNotExist{}) + ctx.NotFound(issues_model.ErrCommentNotExist{}) return } diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go index 13c6d89b6e..c2c208736c 100644 --- a/routers/web/repo/issue_content_history.go +++ b/routers/web/repo/issue_content_history.go @@ -186,7 +186,7 @@ func SoftDeleteContentHistory(ctx *context.Context) { return } if ctx.Doer == nil { - ctx.NotFound("Require SignIn", nil) + ctx.NotFound(nil) return } @@ -202,12 +202,12 @@ func SoftDeleteContentHistory(ctx *context.Context) { return } if history.IssueID != issue.ID { - ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{}) + ctx.NotFound(issues_model.ErrCommentNotExist{}) return } if commentID != 0 { if history.CommentID != commentID { - ctx.NotFound("CompareCommentID", issues_model.ErrCommentNotExist{}) + ctx.NotFound(issues_model.ErrCommentNotExist{}) return } @@ -216,7 +216,7 @@ func SoftDeleteContentHistory(ctx *context.Context) { return } if comment.IssueID != issue.ID { - ctx.NotFound("CompareIssueID", issues_model.ErrCommentNotExist{}) + ctx.NotFound(issues_model.ErrCommentNotExist{}) return } } diff --git a/routers/web/repo/issue_dependency.go b/routers/web/repo/issue_dependency.go index 0f6787386d..73298958c0 100644 --- a/routers/web/repo/issue_dependency.go +++ b/routers/web/repo/issue_dependency.go @@ -23,7 +23,7 @@ func AddDependency(ctx *context.Context) { // Check if the Repo is allowed to have dependencies if !ctx.Repo.CanCreateIssueDependencies(ctx, ctx.Doer, issue.IsPull) { - ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies") + ctx.HTTPError(http.StatusForbidden, "CanCreateIssueDependencies") return } @@ -97,7 +97,7 @@ func RemoveDependency(ctx *context.Context) { // Check if the Repo is allowed to have dependencies if !ctx.Repo.CanCreateIssueDependencies(ctx, ctx.Doer, issue.IsPull) { - ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies") + ctx.HTTPError(http.StatusForbidden, "CanCreateIssueDependencies") return } @@ -119,7 +119,7 @@ func RemoveDependency(ctx *context.Context) { case "blocking": depType = issues_model.DependencyTypeBlocking default: - ctx.Error(http.StatusBadRequest, "GetDependecyType") + ctx.HTTPError(http.StatusBadRequest, "GetDependecyType") return } diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go index 5ef6b09faa..62c0128f19 100644 --- a/routers/web/repo/issue_label.go +++ b/routers/web/repo/issue_label.go @@ -131,7 +131,7 @@ func UpdateLabel(ctx *context.Context) { if err != nil { switch { case issues_model.IsErrRepoLabelNotExist(err): - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) default: ctx.ServerError("UpdateLabel", err) } @@ -180,7 +180,7 @@ func UpdateIssueLabel(ctx *context.Context) { label, err := issues_model.GetLabelByID(ctx, ctx.FormInt64("id")) if err != nil { if issues_model.IsErrRepoLabelNotExist(err) { - ctx.Error(http.StatusNotFound, "GetLabelByID") + ctx.HTTPError(http.StatusNotFound, "GetLabelByID") } else { ctx.ServerError("GetLabelByID", err) } @@ -221,7 +221,7 @@ func UpdateIssueLabel(ctx *context.Context) { } default: log.Warn("Unrecognized action: %s", action) - ctx.Error(http.StatusInternalServerError) + ctx.HTTPError(http.StatusInternalServerError) return } diff --git a/routers/web/repo/issue_list.go b/routers/web/repo/issue_list.go index 2f615a100e..a65ae77795 100644 --- a/routers/web/repo/issue_list.go +++ b/routers/web/repo/issue_list.go @@ -46,7 +46,7 @@ func retrieveProjectsForIssueList(ctx *context.Context, repo *repo_model.Reposit func SearchIssues(ctx *context.Context) { before, since, err := context.GetQueryBeforeSince(ctx.Base) if err != nil { - ctx.Error(http.StatusUnprocessableEntity, err.Error()) + ctx.HTTPError(http.StatusUnprocessableEntity, err.Error()) return } @@ -84,9 +84,9 @@ func SearchIssues(ctx *context.Context) { 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.Error()) + ctx.HTTPError(http.StatusBadRequest, "Owner not found", err.Error()) } else { - ctx.Error(http.StatusInternalServerError, "GetUserByName", err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "GetUserByName", err.Error()) } return } @@ -97,15 +97,15 @@ func SearchIssues(ctx *context.Context) { } if ctx.FormString("team") != "" { if ctx.FormString("owner") == "" { - ctx.Error(http.StatusBadRequest, "", "Owner organisation is required for filtering on team") + ctx.HTTPError(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.Error()) + ctx.HTTPError(http.StatusBadRequest, "Team not found", err.Error()) } else { - ctx.Error(http.StatusInternalServerError, "GetUserByName", err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "GetUserByName", err.Error()) } return } @@ -118,7 +118,7 @@ func SearchIssues(ctx *context.Context) { } repoIDs, _, err = repo_model.SearchRepositoryIDs(ctx, opts) if err != nil { - ctx.Error(http.StatusInternalServerError, "SearchRepositoryIDs", err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "SearchRepositoryIDs", err.Error()) return } if len(repoIDs) == 0 { @@ -149,7 +149,7 @@ func SearchIssues(ctx *context.Context) { } includedAnyLabels, err = issues_model.GetLabelIDsByNames(ctx, includedLabelNames) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetLabelIDsByNames", err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "GetLabelIDsByNames", err.Error()) return } } @@ -163,7 +163,7 @@ func SearchIssues(ctx *context.Context) { } includedMilestones, err = issues_model.GetMilestoneIDsByNames(ctx, includedMilestoneNames) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetMilestoneIDsByNames", err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "GetMilestoneIDsByNames", err.Error()) return } } @@ -230,12 +230,12 @@ func SearchIssues(ctx *context.Context) { ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt) if err != nil { - ctx.Error(http.StatusInternalServerError, "SearchIssues", err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "SearchIssues", err.Error()) return } issues, err := issues_model.GetIssuesByIDs(ctx, ids, true) if err != nil { - ctx.Error(http.StatusInternalServerError, "FindIssuesByIDs", err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "FindIssuesByIDs", err.Error()) return } @@ -251,12 +251,12 @@ func getUserIDForFilter(ctx *context.Context, queryName string) int64 { user, err := user_model.GetUserByName(ctx, userName) if user_model.IsErrUserNotExist(err) { - ctx.NotFound("", err) + ctx.NotFound(err) return 0 } if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return 0 } @@ -269,7 +269,7 @@ func getUserIDForFilter(ctx *context.Context, queryName string) int64 { func SearchRepoIssuesJSON(ctx *context.Context) { before, since, err := context.GetQueryBeforeSince(ctx.Base) if err != nil { - ctx.Error(http.StatusUnprocessableEntity, err.Error()) + ctx.HTTPError(http.StatusUnprocessableEntity, err.Error()) return } @@ -299,7 +299,7 @@ func SearchRepoIssuesJSON(ctx *context.Context) { continue } if !issues_model.IsErrMilestoneNotExist(err) { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } id, err := strconv.ParseInt(part[i], 10, 64) @@ -314,7 +314,7 @@ func SearchRepoIssuesJSON(ctx *context.Context) { if issues_model.IsErrMilestoneNotExist(err) { continue } - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) } } @@ -384,12 +384,12 @@ func SearchRepoIssuesJSON(ctx *context.Context) { ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt) if err != nil { - ctx.Error(http.StatusInternalServerError, "SearchIssues", err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "SearchIssues", err.Error()) return } issues, err := issues_model.GetIssuesByIDs(ctx, ids, true) if err != nil { - ctx.Error(http.StatusInternalServerError, "FindIssuesByIDs", err.Error()) + ctx.HTTPError(http.StatusInternalServerError, "FindIssuesByIDs", err.Error()) return } diff --git a/routers/web/repo/issue_new.go b/routers/web/repo/issue_new.go index 32daa3e48f..9f52396414 100644 --- a/routers/web/repo/issue_new.go +++ b/routers/web/repo/issue_new.go @@ -255,7 +255,7 @@ func ValidateRepoMetasForNewIssue(ctx *context.Context, form forms.CreateIssueFo inputLabelIDs, _ := base.StringsToInt64s(strings.Split(form.LabelIDs, ",")) candidateLabels := toSet(pageMetaData.LabelsData.AllLabels, func(label *issues_model.Label) int64 { return label.ID }) if len(inputLabelIDs) > 0 && !candidateLabels.Contains(inputLabelIDs...) { - ctx.NotFound("", nil) + ctx.NotFound(nil) return ret } pageMetaData.LabelsData.SetSelectedLabelIDs(inputLabelIDs) @@ -263,7 +263,7 @@ func ValidateRepoMetasForNewIssue(ctx *context.Context, form forms.CreateIssueFo allMilestones := append(slices.Clone(pageMetaData.MilestonesData.OpenMilestones), pageMetaData.MilestonesData.ClosedMilestones...) candidateMilestones := toSet(allMilestones, func(milestone *issues_model.Milestone) int64 { return milestone.ID }) if form.MilestoneID > 0 && !candidateMilestones.Contains(form.MilestoneID) { - ctx.NotFound("", nil) + ctx.NotFound(nil) return ret } pageMetaData.MilestonesData.SelectedMilestoneID = form.MilestoneID @@ -271,18 +271,21 @@ func ValidateRepoMetasForNewIssue(ctx *context.Context, form forms.CreateIssueFo allProjects := append(slices.Clone(pageMetaData.ProjectsData.OpenProjects), pageMetaData.ProjectsData.ClosedProjects...) candidateProjects := toSet(allProjects, func(project *project_model.Project) int64 { return project.ID }) if form.ProjectID > 0 && !candidateProjects.Contains(form.ProjectID) { - ctx.NotFound("", nil) + ctx.NotFound(nil) return ret } pageMetaData.ProjectsData.SelectedProjectID = form.ProjectID + // prepare assignees candidateAssignees := toSet(pageMetaData.AssigneesData.CandidateAssignees, func(user *user_model.User) int64 { return user.ID }) inputAssigneeIDs, _ := base.StringsToInt64s(strings.Split(form.AssigneeIDs, ",")) - if len(inputAssigneeIDs) > 0 && !candidateAssignees.Contains(inputAssigneeIDs...) { - ctx.NotFound("", nil) - return ret + var assigneeIDStrings []string + for _, inputAssigneeID := range inputAssigneeIDs { + if candidateAssignees.Contains(inputAssigneeID) { + assigneeIDStrings = append(assigneeIDStrings, strconv.FormatInt(inputAssigneeID, 10)) + } } - pageMetaData.AssigneesData.SelectedAssigneeIDs = form.AssigneeIDs + pageMetaData.AssigneesData.SelectedAssigneeIDs = strings.Join(assigneeIDStrings, ",") // Check if the passed reviewers (user/team) actually exist var reviewers []*user_model.User @@ -301,14 +304,14 @@ func ValidateRepoMetasForNewIssue(ctx *context.Context, form forms.CreateIssueFo if rID < 0 { // negative reviewIDs represent team requests team, ok := teamReviewersMap[-rID] if !ok { - ctx.NotFound("", nil) + ctx.NotFound(nil) return ret } teamReviewers = append(teamReviewers, team) } else { user, ok := userReviewersMap[rID] if !ok { - ctx.NotFound("", nil) + ctx.NotFound(nil) return ret } reviewers = append(reviewers, user) @@ -346,7 +349,7 @@ func NewIssuePost(ctx *context.Context) { if projectID > 0 { if !ctx.Repo.CanRead(unit.TypeProjects) { // User must also be able to see the project. - ctx.Error(http.StatusBadRequest, "user hasn't permissions to read projects") + ctx.HTTPError(http.StatusBadRequest, "user hasn't permissions to read projects") return } } @@ -385,7 +388,7 @@ func NewIssuePost(ctx *context.Context) { if err := issue_service.NewIssue(ctx, repo, issue, labelIDs, attachments, assigneeIDs, projectID); err != nil { if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) { - ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error()) + ctx.HTTPError(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error()) } else if errors.Is(err, user_model.ErrBlockedUser) { ctx.JSONError(ctx.Tr("repo.issues.new.blocked_user")) } else { diff --git a/routers/web/repo/issue_page_meta.go b/routers/web/repo/issue_page_meta.go index 272343f460..93cc38bffa 100644 --- a/routers/web/repo/issue_page_meta.go +++ b/routers/web/repo/issue_page_meta.go @@ -79,16 +79,29 @@ func retrieveRepoIssueMetaData(ctx *context.Context, repo *repo_model.Repository return data } - data.CanModifyIssueOrPull = ctx.Repo.CanWriteIssuesOrPulls(isPull) && !ctx.Repo.Repository.IsArchived - if !data.CanModifyIssueOrPull { + // it sets "Branches" template data, + // it is used to render the "edit PR target branches" dropdown, and the "branch selector" in the issue's sidebar. + PrepareBranchList(ctx) + if ctx.Written() { return data } - data.retrieveAssigneesDataForIssueWriter(ctx) + // it sets the "Assignees" template data, and the data is also used to "mention" users. + data.retrieveAssigneesData(ctx) if ctx.Written() { return data } + // TODO: the issue/pull permissions are quite complex and unclear + // A reader could create an issue/PR with setting some meta (eg: assignees from issue template, reviewers, target branch) + // A reader(creator) could update some meta (eg: target branch), but can't change assignees anymore. + // For non-creator users, only writers could update some meta (eg: assignees, milestone, project) + // Need to clarify the logic and add some tests in the future + data.CanModifyIssueOrPull = ctx.Repo.CanWriteIssuesOrPulls(isPull) && !ctx.Repo.Repository.IsArchived + if !data.CanModifyIssueOrPull { + return data + } + data.retrieveMilestonesDataForIssueWriter(ctx) if ctx.Written() { return data @@ -99,11 +112,6 @@ func retrieveRepoIssueMetaData(ctx *context.Context, repo *repo_model.Repository return data } - PrepareBranchList(ctx) - if ctx.Written() { - return data - } - ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx, ctx.Doer, isPull) return data } @@ -131,7 +139,7 @@ func (d *IssuePageMetaData) retrieveMilestonesDataForIssueWriter(ctx *context.Co } } -func (d *IssuePageMetaData) retrieveAssigneesDataForIssueWriter(ctx *context.Context) { +func (d *IssuePageMetaData) retrieveAssigneesData(ctx *context.Context) { var err error d.AssigneesData.CandidateAssignees, err = repo_model.GetRepoAssignees(ctx, d.Repository) if err != nil { diff --git a/routers/web/repo/issue_pin.go b/routers/web/repo/issue_pin.go index d7d3205c37..8d3de90d25 100644 --- a/routers/web/repo/issue_pin.go +++ b/routers/web/repo/issue_pin.go @@ -6,6 +6,7 @@ package repo import ( "net/http" + "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" @@ -22,15 +23,29 @@ func IssuePinOrUnpin(ctx *context.Context) { // 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.Status(http.StatusInternalServerError) - log.Error(err.Error()) + ctx.ServerError("LoadRepo", err) return } - err = issue.PinOrUnpin(ctx, ctx.Doer) + // PinOrUnpin pins or unpins a Issue + _, err = issues_model.GetIssuePin(ctx, issue) + if err != nil && !db.IsErrNotExist(err) { + ctx.ServerError("GetIssuePin", err) + return + } + + if db.IsErrNotExist(err) { + err = issues_model.PinIssue(ctx, issue, ctx.Doer) + } else { + err = issues_model.UnpinIssue(ctx, issue, ctx.Doer) + } + if err != nil { - ctx.Status(http.StatusInternalServerError) - log.Error(err.Error()) + if issues_model.IsErrIssueMaxPinReached(err) { + ctx.JSONError(ctx.Tr("repo.issues.max_pinned")) + } else { + ctx.ServerError("Pin/Unpin failed", err) + } return } @@ -41,23 +56,20 @@ func IssuePinOrUnpin(ctx *context.Context) { func IssueUnpin(ctx *context.Context) { issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { - ctx.Status(http.StatusInternalServerError) - log.Error(err.Error()) + ctx.ServerError("GetIssueByIndex", err) return } // 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.Status(http.StatusInternalServerError) - log.Error(err.Error()) + ctx.ServerError("LoadRepo", err) return } - err = issue.Unpin(ctx, ctx.Doer) + err = issues_model.UnpinIssue(ctx, issue, ctx.Doer) if err != nil { - ctx.Status(http.StatusInternalServerError) - log.Error(err.Error()) + ctx.ServerError("UnpinIssue", err) return } @@ -78,15 +90,13 @@ func IssuePinMove(ctx *context.Context) { form := &movePinIssueForm{} if err := json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil { - ctx.Status(http.StatusInternalServerError) - log.Error(err.Error()) + ctx.ServerError("Decode", err) return } issue, err := issues_model.GetIssueByID(ctx, form.ID) if err != nil { - ctx.Status(http.StatusInternalServerError) - log.Error(err.Error()) + ctx.ServerError("GetIssueByID", err) return } @@ -96,10 +106,9 @@ func IssuePinMove(ctx *context.Context) { return } - err = issue.MovePin(ctx, form.Position) + err = issues_model.MovePin(ctx, issue, form.Position) if err != nil { - ctx.Status(http.StatusInternalServerError) - log.Error(err.Error()) + ctx.ServerError("MovePin", err) return } diff --git a/routers/web/repo/issue_stopwatch.go b/routers/web/repo/issue_stopwatch.go index 5d6e723311..73e279e0a6 100644 --- a/routers/web/repo/issue_stopwatch.go +++ b/routers/web/repo/issue_stopwatch.go @@ -26,7 +26,7 @@ func IssueStopwatch(c *context.Context) { } if !c.Repo.CanUseTimetracker(c, issue, c.Doer) { - c.NotFound("CanUseTimetracker", nil) + c.NotFound(nil) return } @@ -49,7 +49,7 @@ func CancelStopwatch(c *context.Context) { return } if !c.Repo.CanUseTimetracker(c, issue, c.Doer) { - c.NotFound("CanUseTimetracker", nil) + c.NotFound(nil) return } diff --git a/routers/web/repo/issue_suggestions.go b/routers/web/repo/issue_suggestions.go index 46e9f339a5..9ef3942504 100644 --- a/routers/web/repo/issue_suggestions.go +++ b/routers/web/repo/issue_suggestions.go @@ -6,13 +6,10 @@ package repo import ( "net/http" - "code.gitea.io/gitea/models/db" - issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/unit" - issue_indexer "code.gitea.io/gitea/modules/indexer/issues" "code.gitea.io/gitea/modules/optional" - "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/services/context" + issue_service "code.gitea.io/gitea/services/issue" ) // IssueSuggestions returns a list of issue suggestions @@ -29,54 +26,11 @@ func IssueSuggestions(ctx *context.Context) { isPull = optional.Some(false) } - searchOpt := &issue_indexer.SearchOptions{ - Paginator: &db.ListOptions{ - Page: 0, - PageSize: 5, - }, - Keyword: keyword, - RepoIDs: []int64{ctx.Repo.Repository.ID}, - IsPull: isPull, - IsClosed: nil, - SortBy: issue_indexer.SortByUpdatedDesc, - } - - ids, _, err := issue_indexer.SearchIssues(ctx, searchOpt) + suggestions, err := issue_service.GetSuggestion(ctx, ctx.Repo.Repository, isPull, keyword) if err != nil { - ctx.ServerError("SearchIssues", err) + ctx.ServerError("GetSuggestion", err) return } - issues, err := issues_model.GetIssuesByIDs(ctx, ids, true) - if err != nil { - ctx.ServerError("FindIssuesByIDs", err) - return - } - - suggestions := make([]*structs.Issue, 0, len(issues)) - - for _, issue := range issues { - suggestion := &structs.Issue{ - ID: issue.ID, - Index: issue.Index, - Title: issue.Title, - State: issue.State(), - } - - if issue.IsPull { - if err := issue.LoadPullRequest(ctx); err != nil { - ctx.ServerError("LoadPullRequest", err) - return - } - if issue.PullRequest != nil { - suggestion.PullRequest = &structs.PullRequestMeta{ - HasMerged: issue.PullRequest.HasMerged, - IsWorkInProgress: issue.PullRequest.IsWorkInProgress(ctx), - } - } - } - - suggestions = append(suggestions, suggestion) - } ctx.JSON(http.StatusOK, suggestions) } diff --git a/routers/web/repo/issue_timetrack.go b/routers/web/repo/issue_timetrack.go index 134ded82d1..985bfd6698 100644 --- a/routers/web/repo/issue_timetrack.go +++ b/routers/web/repo/issue_timetrack.go @@ -25,7 +25,7 @@ func AddTimeManually(c *context.Context) { return } if !c.Repo.CanUseTimetracker(c, issue, c.Doer) { - c.NotFound("CanUseTimetracker", nil) + c.NotFound(nil) return } @@ -56,23 +56,23 @@ func DeleteTime(c *context.Context) { return } if !c.Repo.CanUseTimetracker(c, issue, c.Doer) { - c.NotFound("CanUseTimetracker", nil) + c.NotFound(nil) return } t, err := issues_model.GetTrackedTimeByID(c, c.PathParamInt64("timeid")) if err != nil { if db.IsErrNotExist(err) { - c.NotFound("time not found", err) + c.NotFound(err) return } - c.Error(http.StatusInternalServerError, "GetTrackedTimeByID", err.Error()) + c.HTTPError(http.StatusInternalServerError, "GetTrackedTimeByID", err.Error()) return } // only OP or admin may delete if !c.IsSigned || (!c.IsUserSiteAdmin() && c.Doer.ID != t.UserID) { - c.Error(http.StatusForbidden, "not allowed") + c.HTTPError(http.StatusForbidden, "not allowed") return } @@ -92,7 +92,7 @@ func UpdateIssueTimeEstimate(ctx *context.Context) { } if !ctx.IsSigned || (!issue.IsPoster(ctx.Doer.ID) && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go index aa49d2e1e8..37e1b27931 100644 --- a/routers/web/repo/issue_view.go +++ b/routers/web/repo/issue_view.go @@ -4,7 +4,6 @@ package repo import ( - stdCtx "context" "fmt" "math/big" "net/http" @@ -40,86 +39,80 @@ import ( ) // roleDescriptor returns the role descriptor for a comment in/with the given repo, poster and issue -func roleDescriptor(ctx stdCtx.Context, repo *repo_model.Repository, poster *user_model.User, permsCache map[int64]access_model.Permission, issue *issues_model.Issue, hasOriginalAuthor bool) (issues_model.RoleDescriptor, error) { - roleDescriptor := issues_model.RoleDescriptor{} - +func roleDescriptor(ctx *context.Context, repo *repo_model.Repository, poster *user_model.User, permsCache map[int64]access_model.Permission, issue *issues_model.Issue, hasOriginalAuthor bool) (roleDesc issues_model.RoleDescriptor, err error) { if hasOriginalAuthor { - return roleDescriptor, nil + // the poster is a migrated user, so no need to detect the role + return roleDesc, nil } - var perm access_model.Permission - var err error - if permsCache != nil { - var ok bool - perm, ok = permsCache[poster.ID] - if !ok { - perm, err = access_model.GetUserRepoPermission(ctx, repo, poster) - if err != nil { - return roleDescriptor, err - } - } - permsCache[poster.ID] = perm - } else { + if poster.IsGhost() || !poster.IsIndividual() { + return roleDesc, nil + } + + roleDesc.IsPoster = issue.IsPoster(poster.ID) // check whether the comment's poster is the issue's poster + + // Guess the role of the poster in the repo by permission + perm, hasPermCache := permsCache[poster.ID] + if !hasPermCache { perm, err = access_model.GetUserRepoPermission(ctx, repo, poster) if err != nil { - return roleDescriptor, err + return roleDesc, err } } - - // If the poster is the actual poster of the issue, enable Poster role. - roleDescriptor.IsPoster = issue.IsPoster(poster.ID) + if permsCache != nil { + permsCache[poster.ID] = perm + } // Check if the poster is owner of the repo. if perm.IsOwner() { - // If the poster isn't an admin, enable the owner role. + // If the poster isn't a site admin, then is must be the repo's owner if !poster.IsAdmin { - roleDescriptor.RoleInRepo = issues_model.RoleRepoOwner - return roleDescriptor, nil + roleDesc.RoleInRepo = issues_model.RoleRepoOwner + return roleDesc, nil } - - // Otherwise check if poster is the real repo admin. - ok, err := access_model.IsUserRealRepoAdmin(ctx, repo, poster) + // Otherwise (poster is site admin), check if poster is the real repo admin. + isRealRepoAdmin, err := access_model.IsUserRealRepoAdmin(ctx, repo, poster) if err != nil { - return roleDescriptor, err + return roleDesc, err } - if ok { - roleDescriptor.RoleInRepo = issues_model.RoleRepoOwner - return roleDescriptor, nil + if isRealRepoAdmin { + roleDesc.RoleInRepo = issues_model.RoleRepoOwner + return roleDesc, nil } } // If repo is organization, check Member role - if err := repo.LoadOwner(ctx); err != nil { - return roleDescriptor, err + if err = repo.LoadOwner(ctx); err != nil { + return roleDesc, err } if repo.Owner.IsOrganization() { if isMember, err := organization.IsOrganizationMember(ctx, repo.Owner.ID, poster.ID); err != nil { - return roleDescriptor, err + return roleDesc, err } else if isMember { - roleDescriptor.RoleInRepo = issues_model.RoleRepoMember - return roleDescriptor, nil + roleDesc.RoleInRepo = issues_model.RoleRepoMember + return roleDesc, nil } } // If the poster is the collaborator of the repo if isCollaborator, err := repo_model.IsCollaborator(ctx, repo.ID, poster.ID); err != nil { - return roleDescriptor, err + return roleDesc, err } else if isCollaborator { - roleDescriptor.RoleInRepo = issues_model.RoleRepoCollaborator - return roleDescriptor, nil + roleDesc.RoleInRepo = issues_model.RoleRepoCollaborator + return roleDesc, nil } hasMergedPR, err := issues_model.HasMergedPullRequestInRepo(ctx, repo.ID, poster.ID) if err != nil { - return roleDescriptor, err + return roleDesc, err } else if hasMergedPR { - roleDescriptor.RoleInRepo = issues_model.RoleRepoContributor + roleDesc.RoleInRepo = issues_model.RoleRepoContributor } else if issue.IsPull { // only display first time contributor in the first opening pull request - roleDescriptor.RoleInRepo = issues_model.RoleRepoFirstTimeContributor + roleDesc.RoleInRepo = issues_model.RoleRepoFirstTimeContributor } - return roleDescriptor, nil + return roleDesc, nil } func getBranchData(ctx *context.Context, issue *issues_model.Issue) { @@ -304,7 +297,7 @@ func ViewIssue(ctx *context.Context) { issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { if issues_model.IsErrIssueNotExist(err) { - ctx.NotFound("GetIssueByIndex", err) + ctx.NotFound(err) } else { ctx.ServerError("GetIssueByIndex", err) } @@ -550,7 +543,11 @@ func preparePullViewDeleteBranch(ctx *context.Context, issue *issues_model.Issue func prepareIssueViewSidebarPin(ctx *context.Context, issue *issues_model.Issue) { var pinAllowed bool - if !issue.IsPinned() { + if err := issue.LoadPinOrder(ctx); err != nil { + ctx.ServerError("LoadPinOrder", err) + return + } + if issue.PinOrder == 0 { var err error pinAllowed, err = issues_model.IsNewPinAllowed(ctx, issue.RepoID, issue.IsPull) if err != nil { @@ -723,8 +720,8 @@ func prepareIssueViewCommentsAndSidebarParticipants(ctx *context.Context, issue } } else if comment.Type == issues_model.CommentTypePullRequestPush { participants = addParticipant(comment.Poster, participants) - if err = comment.LoadPushCommits(ctx); err != nil { - ctx.ServerError("LoadPushCommits", err) + if err = issue_service.LoadCommentPushCommits(ctx, comment); err != nil { + ctx.ServerError("LoadCommentPushCommits", err) return } if !ctx.Repo.CanRead(unit.TypeActions) { diff --git a/routers/web/repo/issue_watch.go b/routers/web/repo/issue_watch.go index e7d55e5555..dfa3491786 100644 --- a/routers/web/repo/issue_watch.go +++ b/routers/web/repo/issue_watch.go @@ -42,7 +42,7 @@ func IssueWatch(ctx *context.Context) { log.Trace("Permission Denied: Not logged in") } } - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } diff --git a/routers/web/repo/migrate.go b/routers/web/repo/migrate.go index 3a7dc29466..ea15e90e5c 100644 --- a/routers/web/repo/migrate.go +++ b/routers/web/repo/migrate.go @@ -35,7 +35,7 @@ const ( // Migrate render migration of repository page func Migrate(ctx *context.Context) { if setting.Repository.DisableMigrations { - ctx.Error(http.StatusForbidden, "Migrate: the site administrator has disabled migrations") + ctx.HTTPError(http.StatusForbidden, "Migrate: the site administrator has disabled migrations") return } @@ -72,7 +72,7 @@ func Migrate(ctx *context.Context) { func handleMigrateError(ctx *context.Context, owner *user_model.User, err error, name string, tpl templates.TplName, form *forms.MigrateRepoForm) { if setting.Repository.DisableMigrations { - ctx.Error(http.StatusForbidden, "MigrateError: the site administrator has disabled migrations") + ctx.HTTPError(http.StatusForbidden, "MigrateError: the site administrator has disabled migrations") return } @@ -152,12 +152,12 @@ func handleMigrateRemoteAddrError(ctx *context.Context, err error, tpl templates func MigratePost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.MigrateRepoForm) if setting.Repository.DisableMigrations { - ctx.Error(http.StatusForbidden, "MigratePost: the site administrator has disabled migrations") + ctx.HTTPError(http.StatusForbidden, "MigratePost: the site administrator has disabled migrations") return } if form.Mirror && setting.Mirror.DisableNewPull { - ctx.Error(http.StatusBadRequest, "MigratePost: the site administrator has disabled creation of new mirrors") + ctx.HTTPError(http.StatusBadRequest, "MigratePost: the site administrator has disabled creation of new mirrors") return } diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go index 6a0e6b25a9..f1d0a857ea 100644 --- a/routers/web/repo/milestone.go +++ b/routers/web/repo/milestone.go @@ -148,7 +148,7 @@ func EditMilestone(ctx *context.Context) { m, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id")) if err != nil { if issues_model.IsErrMilestoneNotExist(err) { - ctx.NotFound("", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetMilestoneByRepoID", err) } @@ -184,7 +184,7 @@ func EditMilestonePost(ctx *context.Context) { m, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id")) if err != nil { if issues_model.IsErrMilestoneNotExist(err) { - ctx.NotFound("", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetMilestoneByRepoID", err) } @@ -218,7 +218,7 @@ func ChangeMilestoneStatus(ctx *context.Context) { if err := issues_model.ChangeMilestoneStatusByRepoIDAndID(ctx, ctx.Repo.Repository.ID, id, toClose); err != nil { if issues_model.IsErrMilestoneNotExist(err) { - ctx.NotFound("", err) + ctx.NotFound(err) } else { ctx.ServerError("ChangeMilestoneStatusByIDAndRepoID", err) } @@ -245,7 +245,7 @@ func MilestoneIssuesAndPulls(ctx *context.Context) { milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID) if err != nil { if issues_model.IsErrMilestoneNotExist(err) { - ctx.NotFound("GetMilestoneByID", err) + ctx.NotFound(err) return } diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index 346132102f..5b81a5e4d1 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -39,14 +39,14 @@ const ( // MustEnableRepoProjects check if repo projects are enabled in settings func MustEnableRepoProjects(ctx *context.Context) { if unit.TypeProjects.UnitGlobalDisabled() { - ctx.NotFound("EnableRepoProjects", nil) + ctx.NotFound(nil) return } if ctx.Repo.Repository != nil { projectsUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeProjects) if !ctx.Repo.CanRead(unit.TypeProjects) || !projectsUnit.ProjectsConfig().IsProjectsAllowed(repo_model.ProjectsModeRepo) { - ctx.NotFound("MustEnableRepoProjects", nil) + ctx.NotFound(nil) return } } @@ -92,6 +92,11 @@ func Projects(ctx *context.Context) { return } + if err := project_service.LoadIssueNumbersForProjects(ctx, projects, ctx.Doer); err != nil { + ctx.ServerError("LoadIssueNumbersForProjects", err) + return + } + for i := range projects { rctx := renderhelper.NewRenderContextRepoComment(ctx, repo) projects[i].RenderedContent, err = markdown.RenderString(rctx, projects[i].Description) @@ -189,14 +194,14 @@ func DeleteProject(ctx *context.Context) { p, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id")) if err != nil { if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetProjectByID", err) } return } if p.RepoID != ctx.Repo.Repository.ID { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } @@ -219,14 +224,14 @@ func RenderEditProject(ctx *context.Context) { p, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id")) if err != nil { if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetProjectByID", err) } return } if p.RepoID != ctx.Repo.Repository.ID { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } @@ -259,14 +264,14 @@ func EditProjectPost(ctx *context.Context) { p, err := project_model.GetProjectByID(ctx, projectID) if err != nil { if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetProjectByID", err) } return } if p.RepoID != ctx.Repo.Repository.ID { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } @@ -291,14 +296,14 @@ func ViewProject(ctx *context.Context) { project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id")) if err != nil { if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetProjectByID", err) } return } if project.RepoID != ctx.Repo.Repository.ID { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } @@ -312,7 +317,8 @@ func ViewProject(ctx *context.Context) { assigneeID := ctx.FormInt64("assignee") // TODO: use "optional" but not 0 in the future - issuesMap, err := issues_model.LoadIssuesFromColumnList(ctx, columns, &issues_model.IssuesOptions{ + issuesMap, err := project_service.LoadIssuesFromProject(ctx, project, &issues_model.IssuesOptions{ + RepoIDs: []int64{ctx.Repo.Repository.ID}, LabelIDs: labelIDs, AssigneeID: optional.Some(assigneeID), }) @@ -320,6 +326,9 @@ func ViewProject(ctx *context.Context) { ctx.ServerError("LoadIssuesOfColumns", err) return } + for _, column := range columns { + column.NumIssues = int64(len(issuesMap[column.ID])) + } if project.CardType != project_model.CardTypeTextOnly { issuesAttachmentMap := make(map[int64][]*repo_model.Attachment) @@ -471,7 +480,7 @@ func DeleteProjectColumn(ctx *context.Context) { project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id")) if err != nil { if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetProjectByID", err) } @@ -518,7 +527,7 @@ func AddColumnToProjectPost(ctx *context.Context) { project, err := project_model.GetProjectForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id")) if err != nil { if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetProjectByID", err) } @@ -556,7 +565,7 @@ func checkProjectColumnChangePermissions(ctx *context.Context) (*project_model.P project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id")) if err != nil { if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetProjectByID", err) } @@ -642,21 +651,21 @@ func MoveIssues(ctx *context.Context) { project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id")) if err != nil { if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("ProjectNotExist", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetProjectByID", err) } return } if project.RepoID != ctx.Repo.Repository.ID { - ctx.NotFound("InvalidRepoID", nil) + ctx.NotFound(nil) return } column, err := project_model.GetColumn(ctx, ctx.PathParamInt64("columnID")) if err != nil { if project_model.IsErrProjectColumnNotExist(err) { - ctx.NotFound("ProjectColumnNotExist", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetProjectColumn", err) } @@ -664,7 +673,7 @@ func MoveIssues(ctx *context.Context) { } if column.ProjectID != project.ID { - ctx.NotFound("ColumnNotInProject", nil) + ctx.NotFound(nil) return } @@ -689,7 +698,7 @@ func MoveIssues(ctx *context.Context) { movedIssues, err := issues_model.GetIssuesByIDs(ctx, issueIDs) if err != nil { if issues_model.IsErrIssueNotExist(err) { - ctx.NotFound("IssueNotExisting", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetIssueByID", err) } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index e6fb492d6e..223f8d017e 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -81,7 +81,7 @@ func getRepository(ctx *context.Context, repoID int64) *repo_model.Repository { repo, err := repo_model.GetRepositoryByID(ctx, repoID) if err != nil { if repo_model.IsErrRepoNotExist(err) { - ctx.NotFound("GetRepositoryByID", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetRepositoryByID", err) } @@ -101,7 +101,7 @@ func getRepository(ctx *context.Context, repoID int64) *repo_model.Repository { unit.TypeCode, ctx.Repo, perm) - ctx.NotFound("getRepository", nil) + ctx.NotFound(nil) return nil } return repo @@ -111,7 +111,7 @@ func getPullInfo(ctx *context.Context) (issue *issues_model.Issue, ok bool) { issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { if issues_model.IsErrIssueNotExist(err) { - ctx.NotFound("GetIssueByIndex", err) + ctx.NotFound(err) } else { ctx.ServerError("GetIssueByIndex", err) } @@ -129,7 +129,7 @@ func getPullInfo(ctx *context.Context) (issue *issues_model.Issue, ok bool) { ctx.Data["Issue"] = issue if !issue.IsPull { - ctx.NotFound("ViewPullCommits", nil) + ctx.NotFound(nil) return nil, false } @@ -190,7 +190,7 @@ func GetPullDiffStats(ctx *context.Context) { mergeBaseCommitID := GetMergedBaseCommitID(ctx, issue) if mergeBaseCommitID == "" { - ctx.NotFound("PullFiles", nil) + ctx.NotFound(nil) return } @@ -624,14 +624,18 @@ func ViewPullCommits(ctx *context.Context) { if ctx.Written() { return } else if prInfo == nil { - ctx.NotFound("ViewPullCommits", nil) + ctx.NotFound(nil) return } ctx.Data["Username"] = ctx.Repo.Owner.Name ctx.Data["Reponame"] = ctx.Repo.Repository.Name - commits := processGitCommits(ctx, prInfo.Commits) + commits, err := processGitCommits(ctx, prInfo.Commits) + if err != nil { + ctx.ServerError("processGitCommits", err) + return + } ctx.Data["Commits"] = commits ctx.Data["CommitCount"] = len(commits) @@ -668,7 +672,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi if ctx.Written() { return } else if prInfo == nil { - ctx.NotFound("ViewPullFiles", nil) + ctx.NotFound(nil) return } @@ -693,7 +697,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi } if !(foundStartCommit && foundEndCommit) { - ctx.NotFound("Given SHA1 not found for this PR", nil) + ctx.NotFound(nil) return } } @@ -784,18 +788,18 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi return } + allComments := issues_model.CommentList{} for _, file := range diff.Files { for _, section := range file.Sections { for _, line := range section.Lines { - for _, comment := range line.Comments { - if err := comment.LoadAttachments(ctx); err != nil { - ctx.ServerError("LoadAttachments", err) - return - } - } + allComments = append(allComments, line.Comments...) } } } + if err := allComments.LoadAttachments(ctx); err != nil { + ctx.ServerError("LoadAttachments", err) + return + } pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.BaseRepoID, pull.BaseBranch) if err != nil { @@ -931,11 +935,11 @@ func UpdatePullRequest(ctx *context.Context) { return } if issue.IsClosed { - ctx.NotFound("MergePullRequest", nil) + ctx.NotFound(nil) return } if issue.PullRequest.HasMerged { - ctx.NotFound("MergePullRequest", nil) + ctx.NotFound(nil) return } @@ -1334,7 +1338,7 @@ func CompareAndPullRequestPost(ctx *context.Context) { if err := pull_service.NewPullRequest(ctx, prOpts); err != nil { switch { case repo_model.IsErrUserDoesNotHaveAccessToRepo(err): - ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error()) + ctx.HTTPError(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error()) case git.IsErrPushRejected(err): pushrejErr := err.(*git.ErrPushRejected) message := pushrejErr.Message @@ -1405,7 +1409,7 @@ func CleanUpPullRequest(ctx *context.Context) { // Don't cleanup unmerged and unclosed PRs and agit PRs if !pr.HasMerged && !issue.IsClosed && pr.Flow != issues_model.PullRequestFlowGithub { - ctx.NotFound("CleanUpPullRequest", nil) + ctx.NotFound(nil) return } @@ -1416,7 +1420,7 @@ func CleanUpPullRequest(ctx *context.Context) { return } if exist { - ctx.NotFound("CleanUpPullRequest", nil) + ctx.NotFound(nil) return } @@ -1425,7 +1429,7 @@ func CleanUpPullRequest(ctx *context.Context) { return } else if pr.HeadRepo == nil { // Forked repository has already been deleted - ctx.NotFound("CleanUpPullRequest", nil) + ctx.NotFound(nil) return } else if err = pr.LoadBaseRepo(ctx); err != nil { ctx.ServerError("LoadBaseRepo", err) @@ -1437,7 +1441,7 @@ func CleanUpPullRequest(ctx *context.Context) { if err := repo_service.CanDeleteBranch(ctx, pr.HeadRepo, pr.HeadBranch, ctx.Doer); err != nil { if errors.Is(err, util.ErrPermissionDenied) { - ctx.NotFound("CanDeleteBranch", nil) + ctx.NotFound(nil) } else { ctx.ServerError("CanDeleteBranch", err) } @@ -1537,7 +1541,7 @@ func DownloadPullDiffOrPatch(ctx *context.Context, patch bool) { 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.NotFound(err) } else { ctx.ServerError("GetPullRequestByIndex", err) } @@ -1560,18 +1564,18 @@ func UpdatePullRequestTarget(ctx *context.Context) { } pr := issue.PullRequest if !issue.IsPull { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } if !ctx.IsSigned || (!issue.IsPoster(ctx.Doer.ID) && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } targetBranch := ctx.FormTrim("target_branch") if len(targetBranch) == 0 { - ctx.Error(http.StatusNoContent) + ctx.HTTPError(http.StatusNoContent) return } @@ -1630,7 +1634,7 @@ func SetAllowEdits(ctx *context.Context) { 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.NotFound(err) } else { ctx.ServerError("GetPullRequestByIndex", err) } @@ -1639,7 +1643,7 @@ func SetAllowEdits(ctx *context.Context) { if err := pull_service.SetAllowEdits(ctx, ctx.Doer, pr, form.AllowMaintainerEdit); err != nil { if errors.Is(err, pull_service.ErrUserHasNoPermissionForAction) { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } ctx.ServerError("SetAllowEdits", err) diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go index 3e9e615b15..fb92d24394 100644 --- a/routers/web/repo/pull_review.go +++ b/routers/web/repo/pull_review.go @@ -133,7 +133,7 @@ func UpdateResolveConversation(ctx *context.Context) { } if comment.Issue.RepoID != ctx.Repo.Repository.ID { - ctx.NotFound("comment's repoID is incorrect", errors.New("comment's repoID is incorrect")) + ctx.NotFound(errors.New("comment's repoID is incorrect")) return } @@ -143,12 +143,12 @@ func UpdateResolveConversation(ctx *context.Context) { return } if !permResult { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } if !comment.Issue.IsPull { - ctx.Error(http.StatusBadRequest) + ctx.HTTPError(http.StatusBadRequest) return } @@ -159,7 +159,7 @@ func UpdateResolveConversation(ctx *context.Context) { return } } else { - ctx.Error(http.StatusBadRequest) + ctx.HTTPError(http.StatusBadRequest) return } @@ -214,7 +214,7 @@ func renderConversation(ctx *context.Context, comment *issues_model.Comment, ori } else if origin == "timeline" { ctx.HTML(http.StatusOK, tplTimelineConversation) } else { - ctx.Error(http.StatusBadRequest, "Unknown origin: "+origin) + ctx.HTTPError(http.StatusBadRequest, "Unknown origin: "+origin) } } diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 284fd27abf..2ad0bf7e3a 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -285,7 +285,7 @@ func SingleRelease(ctx *context.Context) { return } if len(releases) != 1 { - ctx.NotFound("SingleRelease", err) + ctx.NotFound(err) return } @@ -311,7 +311,7 @@ func LatestRelease(ctx *context.Context) { release, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID) if err != nil { if repo_model.IsErrReleaseNotExist(err) { - ctx.NotFound("LatestRelease", err) + ctx.NotFound(err) return } ctx.ServerError("GetLatestReleaseByRepoID", err) @@ -525,7 +525,7 @@ func EditRelease(ctx *context.Context) { rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tagName) if err != nil { if repo_model.IsErrReleaseNotExist(err) { - ctx.NotFound("GetRelease", err) + ctx.NotFound(err) } else { ctx.ServerError("GetRelease", err) } @@ -568,14 +568,14 @@ func EditReleasePost(ctx *context.Context) { rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tagName) if err != nil { if repo_model.IsErrReleaseNotExist(err) { - ctx.NotFound("GetRelease", err) + ctx.NotFound(err) } else { ctx.ServerError("GetRelease", err) } return } if rel.IsTag { - ctx.NotFound("GetRelease", err) + ctx.NotFound(err) return } ctx.Data["tag_name"] = rel.TagName @@ -639,7 +639,7 @@ func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) { rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id")) if err != nil { if repo_model.IsErrReleaseNotExist(err) { - ctx.NotFound("GetReleaseForRepoByID", err) + ctx.NotFound(err) } else { ctx.Flash.Error("DeleteReleaseByID: " + err.Error()) redirect() diff --git a/routers/web/repo/render.go b/routers/web/repo/render.go index efd271c01d..689174dfa1 100644 --- a/routers/web/repo/render.go +++ b/routers/web/repo/render.go @@ -30,7 +30,7 @@ func RenderFile(ctx *context.Context) { } if err != nil { if git.IsErrNotExist(err) { - ctx.NotFound("GetBlobByPath", err) + ctx.NotFound(err) } else { ctx.ServerError("GetBlobByPath", err) } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index e82a60c043..f5f4a081c5 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -47,14 +47,14 @@ const ( // MustBeNotEmpty render when a repo is a empty git dir func MustBeNotEmpty(ctx *context.Context) { if ctx.Repo.Repository.IsEmpty { - ctx.NotFound("MustBeNotEmpty", nil) + ctx.NotFound(nil) } } // MustBeEditable check that repo can be edited func MustBeEditable(ctx *context.Context) { if !ctx.Repo.Repository.CanEnableEditor() { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } } @@ -62,7 +62,7 @@ func MustBeEditable(ctx *context.Context) { // MustBeAbleToUpload check that repo can be uploaded to func MustBeAbleToUpload(ctx *context.Context) { if !setting.Repository.Upload.Enabled { - ctx.NotFound("", nil) + ctx.NotFound(nil) } } @@ -118,7 +118,7 @@ func checkContextUser(ctx *context.Context, uid int64) *user_model.User { // Check ownership of organization. if !org.IsOrganization() { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return nil } if !ctx.Doer.IsAdmin { @@ -127,7 +127,7 @@ func checkContextUser(ctx *context.Context, uid int64) *user_model.User { ctx.ServerError("CanCreateOrgRepo", err) return nil } else if !canCreate { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return nil } } else { @@ -310,7 +310,7 @@ func handleActionError(ctx *context.Context, err error) { if errors.Is(err, user_model.ErrBlockedUser) { ctx.Flash.Error(ctx.Tr("repo.action.blocked_user")) } else if errors.Is(err, util.ErrPermissionDenied) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) } else { ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam("action")), err) } @@ -337,7 +337,7 @@ func RedirectDownload(ctx *context.Context) { release := releases[0] att, err := repo_model.GetAttachmentByReleaseIDFileName(ctx, release.ID, fileName) if err != nil { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } if att != nil { @@ -349,12 +349,12 @@ func RedirectDownload(ctx *context.Context) { // We only fetch the latest release if the tag is "latest" and no release with the tag "latest" exists release, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID) if err != nil { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } att, err := repo_model.GetAttachmentByReleaseIDFileName(ctx, release.ID, fileName) if err != nil { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } if att != nil { @@ -362,7 +362,7 @@ func RedirectDownload(ctx *context.Context) { return } } - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) } // Download an archive of a repository @@ -370,9 +370,9 @@ func Download(ctx *context.Context) { 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, err.Error()) + ctx.HTTPError(http.StatusBadRequest, err.Error()) } else if errors.Is(err, archiver_service.RepoRefNotFoundError{}) { - ctx.Error(http.StatusNotFound, err.Error()) + ctx.HTTPError(http.StatusNotFound, err.Error()) } else { ctx.ServerError("archiver_service.NewRequest", err) } @@ -427,11 +427,11 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep func InitiateDownload(ctx *context.Context) { aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*")) if err != nil { - ctx.Error(http.StatusBadRequest, "invalid archive request") + ctx.HTTPError(http.StatusBadRequest, "invalid archive request") return } if aReq == nil { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } @@ -503,7 +503,7 @@ func SearchRepo(ctx *context.Context) { opts.Collaborate = optional.Some(true) case "": default: - ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid search mode: \"%s\"", mode)) + ctx.HTTPError(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid search mode: \"%s\"", mode)) return } @@ -525,11 +525,11 @@ func SearchRepo(ctx *context.Context) { if orderBy, ok := searchModeMap[sortMode]; ok { opts.OrderBy = orderBy } else { - ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid sort mode: \"%s\"", sortMode)) + ctx.HTTPError(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid sort mode: \"%s\"", sortMode)) return } } else { - ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid sort order: \"%s\"", sortOrder)) + ctx.HTTPError(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid sort order: \"%s\"", sortOrder)) return } } diff --git a/routers/web/repo/search.go b/routers/web/repo/search.go index bbbe5c1081..ea40e64bbb 100644 --- a/routers/web/repo/search.go +++ b/routers/web/repo/search.go @@ -5,11 +5,11 @@ package repo import ( "net/http" - "strings" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" code_indexer "code.gitea.io/gitea/modules/indexer/code" + "code.gitea.io/gitea/modules/indexer/code/gitgrep" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/routers/common" @@ -18,16 +18,6 @@ import ( const tplSearch templates.TplName = "repo/search" -func indexSettingToGitGrepPathspecList() (list []string) { - for _, expr := range setting.Indexer.IncludePatterns { - list = append(list, ":(glob)"+expr.PatternString()) - } - for _, expr := range setting.Indexer.ExcludePatterns { - list = append(list, ":(glob,exclude)"+expr.PatternString()) - } - return list -} - // Search render repository search page func Search(ctx *context.Context) { ctx.Data["PageIsViewCode"] = true @@ -67,38 +57,14 @@ func Search(ctx *context.Context) { ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx) } } else { - searchRefName := git.RefNameFromBranch(ctx.Repo.Repository.DefaultBranch) // BranchName should be default branch or the first existing branch - res, err := git.GrepSearch(ctx, ctx.Repo.GitRepo, prepareSearch.Keyword, git.GrepOptions{ - ContextLineNumber: 1, - IsFuzzy: prepareSearch.IsFuzzy, - RefName: searchRefName.String(), - PathspecList: indexSettingToGitGrepPathspecList(), - }) + var err error + // ref should be default branch or the first existing branch + searchRef := git.RefNameFromBranch(ctx.Repo.Repository.DefaultBranch) + searchResults, total, err = gitgrep.PerformSearch(ctx, page, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, searchRef, prepareSearch.Keyword, prepareSearch.IsFuzzy) if err != nil { - // TODO: if no branch exists, it reports: exit status 128, fatal: this operation must be run in a work tree. - ctx.ServerError("GrepSearch", err) + ctx.ServerError("gitgrep.PerformSearch", err) return } - commitID, err := ctx.Repo.GitRepo.GetRefCommitID(searchRefName.String()) - if err != nil { - ctx.ServerError("GetRefCommitID", err) - return - } - total = len(res) - pageStart := min((page-1)*setting.UI.RepoSearchPagingNum, len(res)) - pageEnd := min(page*setting.UI.RepoSearchPagingNum, len(res)) - res = res[pageStart:pageEnd] - for _, r := range res { - searchResults = append(searchResults, &code_indexer.Result{ - RepoID: ctx.Repo.Repository.ID, - Filename: r.Filename, - CommitID: commitID, - // UpdatedUnix: not supported yet - // Language: not supported yet - // Color: not supported yet - Lines: code_indexer.HighlightSearchResultCode(r.Filename, "", r.LineNumbers, strings.Join(r.LineCodes, "\n")), - }) - } } ctx.Data["Repo"] = ctx.Repo.Repository diff --git a/routers/web/repo/setting/default_branch.go b/routers/web/repo/setting/default_branch.go index 881d148afc..e0822ba540 100644 --- a/routers/web/repo/setting/default_branch.go +++ b/routers/web/repo/setting/default_branch.go @@ -49,6 +49,6 @@ func SetDefaultBranchPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) default: - ctx.NotFound("", nil) + ctx.NotFound(nil) } } diff --git a/routers/web/repo/setting/git_hooks.go b/routers/web/repo/setting/git_hooks.go index 1d92211303..ba4b5e85b6 100644 --- a/routers/web/repo/setting/git_hooks.go +++ b/routers/web/repo/setting/git_hooks.go @@ -34,7 +34,7 @@ func GitHooksEdit(ctx *context.Context) { hook, err := ctx.Repo.GitRepo.GetHook(name) if err != nil { if err == git.ErrNotValidHook { - ctx.NotFound("GetHook", err) + ctx.NotFound(err) } else { ctx.ServerError("GetHook", err) } @@ -50,7 +50,7 @@ func GitHooksEditPost(ctx *context.Context) { hook, err := ctx.Repo.GitRepo.GetHook(name) if err != nil { if err == git.ErrNotValidHook { - ctx.NotFound("GetHook", err) + ctx.NotFound(err) } else { ctx.ServerError("GetHook", err) } diff --git a/routers/web/repo/setting/lfs.go b/routers/web/repo/setting/lfs.go index 2df483fa34..655291d25c 100644 --- a/routers/web/repo/setting/lfs.go +++ b/routers/web/repo/setting/lfs.go @@ -41,7 +41,7 @@ const ( // LFSFiles shows a repository's LFS files func LFSFiles(ctx *context.Context) { if !setting.LFS.StartServer { - ctx.NotFound("LFSFiles", nil) + ctx.NotFound(nil) return } page := ctx.FormInt("page") @@ -71,7 +71,7 @@ func LFSFiles(ctx *context.Context) { // LFSLocks shows a repository's LFS locks func LFSLocks(ctx *context.Context) { if !setting.LFS.StartServer { - ctx.NotFound("LFSLocks", nil) + ctx.NotFound(nil) return } ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs" @@ -197,7 +197,7 @@ func LFSLocks(ctx *context.Context) { // LFSLockFile locks a file func LFSLockFile(ctx *context.Context) { if !setting.LFS.StartServer { - ctx.NotFound("LFSLocks", nil) + ctx.NotFound(nil) return } originalPath := ctx.FormString("path") @@ -238,7 +238,7 @@ func LFSLockFile(ctx *context.Context) { // LFSUnlock forcibly unlocks an LFS lock func LFSUnlock(ctx *context.Context) { if !setting.LFS.StartServer { - ctx.NotFound("LFSUnlock", nil) + ctx.NotFound(nil) return } _, err := git_model.DeleteLFSLockByID(ctx, ctx.PathParamInt64("lid"), ctx.Repo.Repository, ctx.Doer, true) @@ -252,7 +252,7 @@ func LFSUnlock(ctx *context.Context) { // LFSFileGet serves a single LFS file func LFSFileGet(ctx *context.Context) { if !setting.LFS.StartServer { - ctx.NotFound("LFSFileGet", nil) + ctx.NotFound(nil) return } ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs" @@ -260,7 +260,7 @@ func LFSFileGet(ctx *context.Context) { p := lfs.Pointer{Oid: oid} if !p.IsValid() { - ctx.NotFound("LFSFileGet", nil) + ctx.NotFound(nil) return } @@ -269,7 +269,7 @@ func LFSFileGet(ctx *context.Context) { meta, err := git_model.GetLFSMetaObjectByOid(ctx, ctx.Repo.Repository.ID, oid) if err != nil { if err == git_model.ErrLFSObjectNotExist { - ctx.NotFound("LFSFileGet", nil) + ctx.NotFound(nil) return } ctx.ServerError("LFSFileGet", err) @@ -350,13 +350,13 @@ func LFSFileGet(ctx *context.Context) { // LFSDelete disassociates the provided oid from the repository and if the lfs file is no longer associated with any repositories - deletes it func LFSDelete(ctx *context.Context) { if !setting.LFS.StartServer { - ctx.NotFound("LFSDelete", nil) + ctx.NotFound(nil) return } oid := ctx.PathParam("oid") p := lfs.Pointer{Oid: oid} if !p.IsValid() { - ctx.NotFound("LFSDelete", nil) + ctx.NotFound(nil) return } @@ -381,13 +381,13 @@ func LFSDelete(ctx *context.Context) { // LFSFileFind guesses a sha for the provided oid (or uses the provided sha) and then finds the commits that contain this sha func LFSFileFind(ctx *context.Context) { if !setting.LFS.StartServer { - ctx.NotFound("LFSFind", nil) + ctx.NotFound(nil) return } oid := ctx.FormString("oid") size := ctx.FormInt64("size") if len(oid) == 0 || size == 0 { - ctx.NotFound("LFSFind", nil) + ctx.NotFound(nil) return } sha := ctx.FormString("sha") @@ -421,7 +421,7 @@ func LFSFileFind(ctx *context.Context) { // LFSPointerFiles will search the repository for pointer files and report which are missing LFS files in the content store func LFSPointerFiles(ctx *context.Context) { if !setting.LFS.StartServer { - ctx.NotFound("LFSFileGet", nil) + ctx.NotFound(nil) return } ctx.Data["PageIsSettingsLFS"] = true @@ -532,7 +532,7 @@ func LFSPointerFiles(ctx *context.Context) { // LFSAutoAssociate auto associates accessible lfs files func LFSAutoAssociate(ctx *context.Context) { if !setting.LFS.StartServer { - ctx.NotFound("LFSAutoAssociate", nil) + ctx.NotFound(nil) return } oids := ctx.FormStrings("oid") diff --git a/routers/web/repo/setting/protected_branch.go b/routers/web/repo/setting/protected_branch.go index 06a9e69507..6709bd115c 100644 --- a/routers/web/repo/setting/protected_branch.go +++ b/routers/web/repo/setting/protected_branch.go @@ -340,7 +340,7 @@ func RenameBranchPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.RenameBranchForm) if !ctx.Repo.CanCreateBranch() { - ctx.NotFound("RenameBranch", nil) + ctx.NotFound(nil) return } diff --git a/routers/web/repo/setting/protected_tag.go b/routers/web/repo/setting/protected_tag.go index 1730ad4a8b..33692778d5 100644 --- a/routers/web/repo/setting/protected_tag.go +++ b/routers/web/repo/setting/protected_tag.go @@ -183,7 +183,7 @@ func selectProtectedTagByContext(ctx *context.Context) *git_model.ProtectedTag { return tag } - ctx.NotFound("", fmt.Errorf("ProtectedTag[%v] not associated to repository %v", id, ctx.Repo.Repository)) + ctx.NotFound(fmt.Errorf("ProtectedTag[%v] not associated to repository %v", id, ctx.Repo.Repository)) return nil } diff --git a/routers/web/repo/setting/runners.go b/routers/web/repo/setting/runners.go deleted file mode 100644 index 94f2ae7a0c..0000000000 --- a/routers/web/repo/setting/runners.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package setting - -import ( - "errors" - "net/http" - "net/url" - - actions_model "code.gitea.io/gitea/models/actions" - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/templates" - actions_shared "code.gitea.io/gitea/routers/web/shared/actions" - shared_user "code.gitea.io/gitea/routers/web/shared/user" - "code.gitea.io/gitea/services/context" -) - -const ( - // TODO: Separate secrets from runners when layout is ready - tplRepoRunners templates.TplName = "repo/settings/actions" - tplOrgRunners templates.TplName = "org/settings/actions" - tplAdminRunners templates.TplName = "admin/actions" - tplUserRunners templates.TplName = "user/settings/actions" - tplRepoRunnerEdit templates.TplName = "repo/settings/runner_edit" - tplOrgRunnerEdit templates.TplName = "org/settings/runners_edit" - tplAdminRunnerEdit templates.TplName = "admin/runners/edit" - tplUserRunnerEdit templates.TplName = "user/settings/runner_edit" -) - -type runnersCtx struct { - OwnerID int64 - RepoID int64 - IsRepo bool - IsOrg bool - IsAdmin bool - IsUser bool - RunnersTemplate templates.TplName - RunnerEditTemplate templates.TplName - RedirectLink string -} - -func getRunnersCtx(ctx *context.Context) (*runnersCtx, error) { - if ctx.Data["PageIsRepoSettings"] == true { - return &runnersCtx{ - RepoID: ctx.Repo.Repository.ID, - OwnerID: 0, - IsRepo: true, - RunnersTemplate: tplRepoRunners, - RunnerEditTemplate: tplRepoRunnerEdit, - RedirectLink: ctx.Repo.RepoLink + "/settings/actions/runners/", - }, nil - } - - if ctx.Data["PageIsOrgSettings"] == true { - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) - return nil, nil - } - return &runnersCtx{ - RepoID: 0, - OwnerID: ctx.Org.Organization.ID, - IsOrg: true, - RunnersTemplate: tplOrgRunners, - RunnerEditTemplate: tplOrgRunnerEdit, - RedirectLink: ctx.Org.OrgLink + "/settings/actions/runners/", - }, nil - } - - if ctx.Data["PageIsAdmin"] == true { - return &runnersCtx{ - RepoID: 0, - OwnerID: 0, - IsAdmin: true, - RunnersTemplate: tplAdminRunners, - RunnerEditTemplate: tplAdminRunnerEdit, - RedirectLink: setting.AppSubURL + "/-/admin/actions/runners/", - }, nil - } - - if ctx.Data["PageIsUserSettings"] == true { - return &runnersCtx{ - OwnerID: ctx.Doer.ID, - RepoID: 0, - IsUser: true, - RunnersTemplate: tplUserRunners, - RunnerEditTemplate: tplUserRunnerEdit, - RedirectLink: setting.AppSubURL + "/user/settings/actions/runners/", - }, nil - } - - return nil, errors.New("unable to set Runners context") -} - -// Runners render settings/actions/runners page for repo level -func Runners(ctx *context.Context) { - ctx.Data["PageIsSharedSettingsRunners"] = true - ctx.Data["Title"] = ctx.Tr("actions.actions") - ctx.Data["PageType"] = "runners" - - rCtx, err := getRunnersCtx(ctx) - if err != nil { - ctx.ServerError("getRunnersCtx", err) - return - } - - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } - - opts := actions_model.FindRunnerOptions{ - ListOptions: db.ListOptions{ - Page: page, - PageSize: 100, - }, - Sort: ctx.Req.URL.Query().Get("sort"), - Filter: ctx.Req.URL.Query().Get("q"), - } - if rCtx.IsRepo { - opts.RepoID = rCtx.RepoID - opts.WithAvailable = true - } else if rCtx.IsOrg || rCtx.IsUser { - opts.OwnerID = rCtx.OwnerID - opts.WithAvailable = true - } - actions_shared.RunnersList(ctx, opts) - - ctx.HTML(http.StatusOK, rCtx.RunnersTemplate) -} - -// RunnersEdit renders runner edit page for repository level -func RunnersEdit(ctx *context.Context) { - ctx.Data["PageIsSharedSettingsRunners"] = true - ctx.Data["Title"] = ctx.Tr("actions.runners.edit_runner") - rCtx, err := getRunnersCtx(ctx) - if err != nil { - ctx.ServerError("getRunnersCtx", err) - return - } - - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } - - actions_shared.RunnerDetails(ctx, page, - ctx.PathParamInt64("runnerid"), rCtx.OwnerID, rCtx.RepoID, - ) - ctx.HTML(http.StatusOK, rCtx.RunnerEditTemplate) -} - -func RunnersEditPost(ctx *context.Context) { - rCtx, err := getRunnersCtx(ctx) - if err != nil { - ctx.ServerError("getRunnersCtx", err) - return - } - actions_shared.RunnerDetailsEditPost(ctx, ctx.PathParamInt64("runnerid"), - rCtx.OwnerID, rCtx.RepoID, - rCtx.RedirectLink+url.PathEscape(ctx.PathParam("runnerid"))) -} - -func ResetRunnerRegistrationToken(ctx *context.Context) { - rCtx, err := getRunnersCtx(ctx) - if err != nil { - ctx.ServerError("getRunnersCtx", err) - return - } - actions_shared.RunnerResetRegistrationToken(ctx, rCtx.OwnerID, rCtx.RepoID, rCtx.RedirectLink) -} - -// RunnerDeletePost response for deleting runner -func RunnerDeletePost(ctx *context.Context) { - rCtx, err := getRunnersCtx(ctx) - if err != nil { - ctx.ServerError("getRunnersCtx", err) - return - } - actions_shared.RunnerDeletePost(ctx, ctx.PathParamInt64("runnerid"), rCtx.RedirectLink, rCtx.RedirectLink+url.PathEscape(ctx.PathParam("runnerid"))) -} - -func RedirectToDefaultSetting(ctx *context.Context) { - ctx.Redirect(ctx.Repo.RepoLink + "/settings/actions/runners") -} diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index be37d0a9c4..768484a21c 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -189,13 +189,13 @@ func SettingsPost(ctx *context.Context) { case "mirror": if !setting.Mirror.Enabled || !repo.IsMirror || repo.IsArchived { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } pullMirror, err := repo_model.GetMirrorByRepoID(ctx, ctx.Repo.Repository.ID) if err == repo_model.ErrMirrorNotExist { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } if err != nil { @@ -283,7 +283,7 @@ func SettingsPost(ctx *context.Context) { case "mirror-sync": if !setting.Mirror.Enabled || !repo.IsMirror || repo.IsArchived { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } @@ -294,13 +294,13 @@ func SettingsPost(ctx *context.Context) { case "push-mirror-sync": if !setting.Mirror.Enabled { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } m, _, _ := repo_model.GetPushMirrorByIDAndRepoID(ctx, form.PushMirrorID, repo.ID) if m == nil { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } @@ -311,7 +311,7 @@ func SettingsPost(ctx *context.Context) { case "push-mirror-update": if !setting.Mirror.Enabled || repo.IsArchived { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } @@ -327,7 +327,7 @@ func SettingsPost(ctx *context.Context) { m, _, _ := repo_model.GetPushMirrorByIDAndRepoID(ctx, form.PushMirrorID, repo.ID) if m == nil { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } @@ -349,7 +349,7 @@ func SettingsPost(ctx *context.Context) { case "push-mirror-remove": if !setting.Mirror.Enabled || repo.IsArchived { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } @@ -359,7 +359,7 @@ func SettingsPost(ctx *context.Context) { m, _, _ := repo_model.GetPushMirrorByIDAndRepoID(ctx, form.PushMirrorID, repo.ID) if m == nil { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } @@ -378,7 +378,7 @@ func SettingsPost(ctx *context.Context) { case "push-mirror-add": if setting.Mirror.DisableNewPush || repo.IsArchived { - ctx.NotFound("", nil) + ctx.NotFound(nil) return } @@ -650,7 +650,7 @@ func SettingsPost(ctx *context.Context) { case "admin": if !ctx.Doer.IsAdmin { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } @@ -670,7 +670,7 @@ func SettingsPost(ctx *context.Context) { case "admin_index": if !ctx.Doer.IsAdmin { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } @@ -682,12 +682,12 @@ func SettingsPost(ctx *context.Context) { } case "code": if !setting.Indexer.RepoIndexerEnabled { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } code.UpdateRepoIndexer(ctx.Repo.Repository) default: - ctx.NotFound("", nil) + ctx.NotFound(nil) return } @@ -698,7 +698,7 @@ func SettingsPost(ctx *context.Context) { case "convert": if !ctx.Repo.IsOwner() { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } if repo.Name != form.RepoName { @@ -707,7 +707,7 @@ func SettingsPost(ctx *context.Context) { } if !repo.IsMirror { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } repo.IsMirror = false @@ -725,7 +725,7 @@ func SettingsPost(ctx *context.Context) { case "convert_fork": if !ctx.Repo.IsOwner() { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } if err := repo.LoadOwner(ctx); err != nil { @@ -738,7 +738,7 @@ func SettingsPost(ctx *context.Context) { } if !repo.IsFork { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } @@ -762,7 +762,7 @@ func SettingsPost(ctx *context.Context) { case "transfer": if !ctx.Repo.IsOwner() { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } if repo.Name != form.RepoName { @@ -820,7 +820,7 @@ func SettingsPost(ctx *context.Context) { case "cancel_transfer": if !ctx.Repo.IsOwner() { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } @@ -846,7 +846,7 @@ func SettingsPost(ctx *context.Context) { case "delete": if !ctx.Repo.IsOwner() { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } if repo.Name != form.RepoName { @@ -870,7 +870,7 @@ func SettingsPost(ctx *context.Context) { case "delete-wiki": if !ctx.Repo.IsOwner() { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } if repo.Name != form.RepoName { @@ -889,7 +889,7 @@ func SettingsPost(ctx *context.Context) { case "archive": if !ctx.Repo.IsOwner() { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } @@ -920,7 +920,7 @@ func SettingsPost(ctx *context.Context) { case "unarchive": if !ctx.Repo.IsOwner() { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } @@ -979,7 +979,7 @@ func SettingsPost(ctx *context.Context) { ctx.Redirect(ctx.Repo.RepoLink + "/settings") default: - ctx.NotFound("", nil) + ctx.NotFound(nil) } } diff --git a/routers/web/repo/setting/variables.go b/routers/web/repo/setting/variables.go deleted file mode 100644 index 9b5453f043..0000000000 --- a/routers/web/repo/setting/variables.go +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package setting - -import ( - "errors" - "net/http" - - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/templates" - shared "code.gitea.io/gitea/routers/web/shared/actions" - shared_user "code.gitea.io/gitea/routers/web/shared/user" - "code.gitea.io/gitea/services/context" -) - -const ( - tplRepoVariables templates.TplName = "repo/settings/actions" - tplOrgVariables templates.TplName = "org/settings/actions" - tplUserVariables templates.TplName = "user/settings/actions" - tplAdminVariables templates.TplName = "admin/actions" -) - -type variablesCtx struct { - OwnerID int64 - RepoID int64 - IsRepo bool - IsOrg bool - IsUser bool - IsGlobal bool - VariablesTemplate templates.TplName - RedirectLink string -} - -func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) { - if ctx.Data["PageIsRepoSettings"] == true { - return &variablesCtx{ - OwnerID: 0, - RepoID: ctx.Repo.Repository.ID, - IsRepo: true, - VariablesTemplate: tplRepoVariables, - RedirectLink: ctx.Repo.RepoLink + "/settings/actions/variables", - }, nil - } - - if ctx.Data["PageIsOrgSettings"] == true { - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) - return nil, nil - } - return &variablesCtx{ - OwnerID: ctx.ContextUser.ID, - RepoID: 0, - IsOrg: true, - VariablesTemplate: tplOrgVariables, - RedirectLink: ctx.Org.OrgLink + "/settings/actions/variables", - }, nil - } - - if ctx.Data["PageIsUserSettings"] == true { - return &variablesCtx{ - OwnerID: ctx.Doer.ID, - RepoID: 0, - IsUser: true, - VariablesTemplate: tplUserVariables, - RedirectLink: setting.AppSubURL + "/user/settings/actions/variables", - }, nil - } - - if ctx.Data["PageIsAdmin"] == true { - return &variablesCtx{ - OwnerID: 0, - RepoID: 0, - IsGlobal: true, - VariablesTemplate: tplAdminVariables, - RedirectLink: setting.AppSubURL + "/-/admin/actions/variables", - }, nil - } - - return nil, errors.New("unable to set Variables context") -} - -func Variables(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("actions.variables") - ctx.Data["PageType"] = "variables" - ctx.Data["PageIsSharedSettingsVariables"] = true - - vCtx, err := getVariablesCtx(ctx) - if err != nil { - ctx.ServerError("getVariablesCtx", err) - return - } - - shared.SetVariablesContext(ctx, vCtx.OwnerID, vCtx.RepoID) - if ctx.Written() { - return - } - - ctx.HTML(http.StatusOK, vCtx.VariablesTemplate) -} - -func VariableCreate(ctx *context.Context) { - vCtx, err := getVariablesCtx(ctx) - if err != nil { - ctx.ServerError("getVariablesCtx", err) - return - } - - if ctx.HasError() { // form binding validation error - ctx.JSONError(ctx.GetErrMsg()) - return - } - - shared.CreateVariable(ctx, vCtx.OwnerID, vCtx.RepoID, vCtx.RedirectLink) -} - -func VariableUpdate(ctx *context.Context) { - vCtx, err := getVariablesCtx(ctx) - if err != nil { - ctx.ServerError("getVariablesCtx", err) - return - } - - if ctx.HasError() { // form binding validation error - ctx.JSONError(ctx.GetErrMsg()) - return - } - - shared.UpdateVariable(ctx, vCtx.RedirectLink) -} - -func VariableDelete(ctx *context.Context) { - vCtx, err := getVariablesCtx(ctx) - if err != nil { - ctx.ServerError("getVariablesCtx", err) - return - } - shared.DeleteVariable(ctx, vCtx.RedirectLink) -} diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go index 4ff2467041..6875584d0b 100644 --- a/routers/web/repo/setting/webhook.go +++ b/routers/web/repo/setting/webhook.go @@ -112,7 +112,7 @@ func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) { func checkHookType(ctx *context.Context) string { hookType := strings.ToLower(ctx.PathParam("type")) if !util.SliceContainsString(setting.Webhook.Types, hookType, true) { - ctx.NotFound("checkHookType", nil) + ctx.NotFound(nil) return "" } return hookType @@ -601,7 +601,7 @@ func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) { } if err != nil || w == nil { if webhook.IsErrWebhookNotExist(err) { - ctx.NotFound("GetWebhookByID", nil) + ctx.NotFound(nil) } else { ctx.ServerError("GetWebhookByID", err) } @@ -718,7 +718,7 @@ func ReplayWebhook(ctx *context.Context) { if err := webhook_service.ReplayHookTask(ctx, w, hookTaskUUID); err != nil { if webhook.IsErrHookTaskNotExist(err) { - ctx.NotFound("ReplayHookTask", nil) + ctx.NotFound(nil) } else { ctx.ServerError("ReplayHookTask", err) } diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 550f1203fd..4d172c9c20 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -38,6 +38,7 @@ import ( "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/typesniffer" "code.gitea.io/gitea/modules/util" + asymkey_service "code.gitea.io/gitea/services/asymkey" "code.gitea.io/gitea/services/context" repo_service "code.gitea.io/gitea/services/repository" @@ -117,7 +118,7 @@ func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool { // or of directory if not in root directory. ctx.Data["LatestCommit"] = latestCommit if latestCommit != nil { - verification := asymkey_model.ParseCommitWithSignature(ctx, latestCommit) + verification := asymkey_service.ParseCommitWithSignature(ctx, latestCommit) if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) { return repo_model.IsOwnerMemberCollaborator(ctx, ctx.Repo.Repository, user.ID) @@ -216,7 +217,7 @@ func checkHomeCodeViewable(ctx *context.Context) { } } - ctx.NotFound("Home", errors.New(ctx.Locale.TrString("units.error.no_unit_allowed_repo"))) + ctx.NotFound(errors.New(ctx.Locale.TrString("units.error.no_unit_allowed_repo"))) } // LastCommit returns lastCommit data for the provided branch/tag/commit and directory (in url) and filenames in body diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 19366c0104..98c84b6993 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -14,7 +14,6 @@ import ( "path/filepath" "strings" - git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/renderhelper" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" @@ -33,6 +32,7 @@ import ( "code.gitea.io/gitea/routers/common" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" + git_service "code.gitea.io/gitea/services/git" notify_service "code.gitea.io/gitea/services/notify" wiki_service "code.gitea.io/gitea/services/wiki" ) @@ -58,7 +58,7 @@ func MustEnableWiki(ctx *context.Context) { ctx.Repo.Repository, ctx.Repo.Permission) } - ctx.NotFound("MustEnableWiki", nil) + ctx.NotFound(nil) return } @@ -437,7 +437,14 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) ctx.ServerError("CommitsByFileAndRange", err) return nil, nil } - ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commitsHistory, ctx.Repo.Repository) + ctx.Data["Commits"], err = git_service.ConvertFromGitCommit(ctx, commitsHistory, ctx.Repo.Repository) + if err != nil { + if wikiRepo != nil { + wikiRepo.Close() + } + ctx.ServerError("ConvertFromGitCommit", err) + return nil, nil + } pager := context.NewPagination(int(commitsCount), setting.Git.CommitsRangeSize, page, 5) pager.AddParamFromRequest(ctx.Req) @@ -478,7 +485,7 @@ func renderEditPage(ctx *context.Context) { ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages") } if isRaw { - ctx.Error(http.StatusForbidden, "Editing of raw wiki files is not allowed") + ctx.HTTPError(http.StatusForbidden, "Editing of raw wiki files is not allowed") } if entry == nil || ctx.Written() { return @@ -502,14 +509,14 @@ func WikiPost(ctx *context.Context) { switch ctx.FormString("action") { case "_new": if !ctx.Repo.CanWrite(unit.TypeWiki) { - ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + ctx.NotFound(nil) return } NewWikiPost(ctx) return case "_delete": if !ctx.Repo.CanWrite(unit.TypeWiki) { - ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + ctx.NotFound(nil) return } DeleteWikiPagePost(ctx) @@ -517,7 +524,7 @@ func WikiPost(ctx *context.Context) { } if !ctx.Repo.CanWrite(unit.TypeWiki) { - ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + ctx.NotFound(nil) return } EditWikiPost(ctx) @@ -536,14 +543,14 @@ func Wiki(ctx *context.Context) { return case "_edit": if !ctx.Repo.CanWrite(unit.TypeWiki) { - ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + ctx.NotFound(nil) return } EditWiki(ctx) return case "_new": if !ctx.Repo.CanWrite(unit.TypeWiki) { - ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + ctx.NotFound(nil) return } NewWiki(ctx) @@ -703,7 +710,7 @@ func WikiRaw(ctx *context.Context) { if err != nil { if git.IsErrNotExist(err) { - ctx.NotFound("findEntryForFile", nil) + ctx.NotFound(nil) return } ctx.ServerError("findEntryForfile", err) @@ -739,7 +746,7 @@ func WikiRaw(ctx *context.Context) { return } - ctx.NotFound("findEntryForFile", nil) + ctx.NotFound(nil) } // NewWiki render wiki create page diff --git a/routers/web/shared/actions/runners.go b/routers/web/shared/actions/runners.go index 6d77bdd2fa..444bd960db 100644 --- a/routers/web/shared/actions/runners.go +++ b/routers/web/shared/actions/runners.go @@ -5,18 +5,131 @@ package actions import ( "errors" + "net/http" + "net/url" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" + shared_user "code.gitea.io/gitea/routers/web/shared/user" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" ) -// RunnersList prepares data for runners list -func RunnersList(ctx *context.Context, opts actions_model.FindRunnerOptions) { +const ( + // TODO: Separate secrets from runners when layout is ready + tplRepoRunners templates.TplName = "repo/settings/actions" + tplOrgRunners templates.TplName = "org/settings/actions" + tplAdminRunners templates.TplName = "admin/actions" + tplUserRunners templates.TplName = "user/settings/actions" + tplRepoRunnerEdit templates.TplName = "repo/settings/runner_edit" + tplOrgRunnerEdit templates.TplName = "org/settings/runners_edit" + tplAdminRunnerEdit templates.TplName = "admin/runners/edit" + tplUserRunnerEdit templates.TplName = "user/settings/runner_edit" +) + +type runnersCtx struct { + OwnerID int64 + RepoID int64 + IsRepo bool + IsOrg bool + IsAdmin bool + IsUser bool + RunnersTemplate templates.TplName + RunnerEditTemplate templates.TplName + RedirectLink string +} + +func getRunnersCtx(ctx *context.Context) (*runnersCtx, error) { + if ctx.Data["PageIsRepoSettings"] == true { + return &runnersCtx{ + RepoID: ctx.Repo.Repository.ID, + OwnerID: 0, + IsRepo: true, + RunnersTemplate: tplRepoRunners, + RunnerEditTemplate: tplRepoRunnerEdit, + RedirectLink: ctx.Repo.RepoLink + "/settings/actions/runners/", + }, nil + } + + if ctx.Data["PageIsOrgSettings"] == true { + err := shared_user.LoadHeaderCount(ctx) + if err != nil { + ctx.ServerError("LoadHeaderCount", err) + return nil, nil + } + return &runnersCtx{ + RepoID: 0, + OwnerID: ctx.Org.Organization.ID, + IsOrg: true, + RunnersTemplate: tplOrgRunners, + RunnerEditTemplate: tplOrgRunnerEdit, + RedirectLink: ctx.Org.OrgLink + "/settings/actions/runners/", + }, nil + } + + if ctx.Data["PageIsAdmin"] == true { + return &runnersCtx{ + RepoID: 0, + OwnerID: 0, + IsAdmin: true, + RunnersTemplate: tplAdminRunners, + RunnerEditTemplate: tplAdminRunnerEdit, + RedirectLink: setting.AppSubURL + "/-/admin/actions/runners/", + }, nil + } + + if ctx.Data["PageIsUserSettings"] == true { + return &runnersCtx{ + OwnerID: ctx.Doer.ID, + RepoID: 0, + IsUser: true, + RunnersTemplate: tplUserRunners, + RunnerEditTemplate: tplUserRunnerEdit, + RedirectLink: setting.AppSubURL + "/user/settings/actions/runners/", + }, nil + } + + return nil, errors.New("unable to set Runners context") +} + +// Runners render settings/actions/runners page for repo level +func Runners(ctx *context.Context) { + ctx.Data["PageIsSharedSettingsRunners"] = true + ctx.Data["Title"] = ctx.Tr("actions.actions") + ctx.Data["PageType"] = "runners" + + rCtx, err := getRunnersCtx(ctx) + if err != nil { + ctx.ServerError("getRunnersCtx", err) + return + } + + page := ctx.FormInt("page") + if page <= 1 { + page = 1 + } + + opts := actions_model.FindRunnerOptions{ + ListOptions: db.ListOptions{ + Page: page, + PageSize: 100, + }, + Sort: ctx.Req.URL.Query().Get("sort"), + Filter: ctx.Req.URL.Query().Get("q"), + } + if rCtx.IsRepo { + opts.RepoID = rCtx.RepoID + opts.WithAvailable = true + } else if rCtx.IsOrg || rCtx.IsUser { + opts.OwnerID = rCtx.OwnerID + opts.WithAvailable = true + } + runners, count, err := db.FindAndCount[actions_model.ActionRunner](ctx, opts) if err != nil { ctx.ServerError("CountRunners", err) @@ -53,10 +166,29 @@ func RunnersList(ctx *context.Context, opts actions_model.FindRunnerOptions) { pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5) ctx.Data["Page"] = pager + + ctx.HTML(http.StatusOK, rCtx.RunnersTemplate) } -// RunnerDetails prepares data for runners edit page -func RunnerDetails(ctx *context.Context, page int, runnerID, ownerID, repoID int64) { +// RunnersEdit renders runner edit page for repository level +func RunnersEdit(ctx *context.Context) { + ctx.Data["PageIsSharedSettingsRunners"] = true + ctx.Data["Title"] = ctx.Tr("actions.runners.edit_runner") + rCtx, err := getRunnersCtx(ctx) + if err != nil { + ctx.ServerError("getRunnersCtx", err) + return + } + + page := ctx.FormInt("page") + if page <= 1 { + page = 1 + } + + runnerID := ctx.PathParamInt64("runnerid") + ownerID := rCtx.OwnerID + repoID := rCtx.RepoID + runner, err := actions_model.GetRunnerByID(ctx, runnerID) if err != nil { ctx.ServerError("GetRunnerByID", err) @@ -68,7 +200,7 @@ func RunnerDetails(ctx *context.Context, page int, runnerID, ownerID, repoID int } if !runner.Editable(ownerID, repoID) { err = errors.New("no permission to edit this runner") - ctx.NotFound("RunnerDetails", err) + ctx.NotFound(err) return } @@ -97,10 +229,22 @@ func RunnerDetails(ctx *context.Context, page int, runnerID, ownerID, repoID int ctx.Data["Tasks"] = tasks pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5) ctx.Data["Page"] = pager + + ctx.HTML(http.StatusOK, rCtx.RunnerEditTemplate) } -// RunnerDetailsEditPost response for edit runner details -func RunnerDetailsEditPost(ctx *context.Context, runnerID, ownerID, repoID int64, redirectTo string) { +func RunnersEditPost(ctx *context.Context) { + rCtx, err := getRunnersCtx(ctx) + if err != nil { + ctx.ServerError("getRunnersCtx", err) + return + } + + runnerID := ctx.PathParamInt64("runnerid") + ownerID := rCtx.OwnerID + repoID := rCtx.RepoID + redirectTo := rCtx.RedirectLink + runner, err := actions_model.GetRunnerByID(ctx, runnerID) if err != nil { log.Warn("RunnerDetailsEditPost.GetRunnerByID failed: %v, url: %s", err, ctx.Req.URL) @@ -108,7 +252,7 @@ func RunnerDetailsEditPost(ctx *context.Context, runnerID, ownerID, repoID int64 return } if !runner.Editable(ownerID, repoID) { - ctx.NotFound("RunnerDetailsEditPost.Editable", util.NewPermissionDeniedErrorf("no permission to edit this runner")) + ctx.NotFound(util.NewPermissionDeniedErrorf("no permission to edit this runner")) return } @@ -129,10 +273,18 @@ func RunnerDetailsEditPost(ctx *context.Context, runnerID, ownerID, repoID int64 ctx.Redirect(redirectTo) } -// RunnerResetRegistrationToken reset registration token -func RunnerResetRegistrationToken(ctx *context.Context, ownerID, repoID int64, redirectTo string) { - _, err := actions_model.NewRunnerToken(ctx, ownerID, repoID) +func ResetRunnerRegistrationToken(ctx *context.Context) { + rCtx, err := getRunnersCtx(ctx) if err != nil { + ctx.ServerError("getRunnersCtx", err) + return + } + + ownerID := rCtx.OwnerID + repoID := rCtx.RepoID + redirectTo := rCtx.RedirectLink + + if _, err := actions_model.NewRunnerToken(ctx, ownerID, repoID); err != nil { ctx.ServerError("ResetRunnerRegistrationToken", err) return } @@ -140,11 +292,28 @@ func RunnerResetRegistrationToken(ctx *context.Context, ownerID, repoID int64, r ctx.JSONRedirect(redirectTo) } -// RunnerDeletePost response for deleting a runner -func RunnerDeletePost(ctx *context.Context, runnerID int64, - successRedirectTo, failedRedirectTo string, -) { - if err := actions_model.DeleteRunner(ctx, runnerID); err != nil { +// RunnerDeletePost response for deleting runner +func RunnerDeletePost(ctx *context.Context) { + rCtx, err := getRunnersCtx(ctx) + if err != nil { + ctx.ServerError("getRunnersCtx", err) + return + } + + runner := findActionsRunner(ctx, rCtx) + if ctx.Written() { + return + } + + if !runner.Editable(rCtx.OwnerID, rCtx.RepoID) { + ctx.NotFound(util.NewPermissionDeniedErrorf("no permission to delete this runner")) + return + } + + successRedirectTo := rCtx.RedirectLink + failedRedirectTo := rCtx.RedirectLink + url.PathEscape(ctx.PathParam("runnerid")) + + if err := actions_model.DeleteRunner(ctx, runner.ID); err != nil { log.Warn("DeleteRunnerPost.UpdateRunner failed: %v, url: %s", err, ctx.Req.URL) ctx.Flash.Warning(ctx.Tr("actions.runners.delete_runner_failed")) @@ -158,3 +327,41 @@ func RunnerDeletePost(ctx *context.Context, runnerID int64, ctx.JSONRedirect(successRedirectTo) } + +func RedirectToDefaultSetting(ctx *context.Context) { + ctx.Redirect(ctx.Repo.RepoLink + "/settings/actions/runners") +} + +func findActionsRunner(ctx *context.Context, rCtx *runnersCtx) *actions_model.ActionRunner { + runnerID := ctx.PathParamInt64("runnerid") + opts := &actions_model.FindRunnerOptions{ + IDs: []int64{runnerID}, + } + switch { + case rCtx.IsRepo: + opts.RepoID = rCtx.RepoID + if opts.RepoID == 0 { + panic("repoID is 0") + } + case rCtx.IsOrg, rCtx.IsUser: + opts.OwnerID = rCtx.OwnerID + if opts.OwnerID == 0 { + panic("ownerID is 0") + } + case rCtx.IsAdmin: + // do nothing + default: + panic("invalid actions runner context") + } + + got, err := db.Find[actions_model.ActionRunner](ctx, opts) + if err != nil { + ctx.ServerError("FindRunner", err) + return nil + } else if len(got) == 0 { + ctx.NotFound(errors.New("runner not found")) + return nil + } + + return got[0] +} diff --git a/routers/web/shared/actions/variables.go b/routers/web/shared/actions/variables.go index f895475748..7e8100b948 100644 --- a/routers/web/shared/actions/variables.go +++ b/routers/web/shared/actions/variables.go @@ -4,31 +4,127 @@ package actions import ( + "errors" + "net/http" + actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/web" + shared_user "code.gitea.io/gitea/routers/web/shared/user" actions_service "code.gitea.io/gitea/services/actions" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" ) -func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) { +const ( + tplRepoVariables templates.TplName = "repo/settings/actions" + tplOrgVariables templates.TplName = "org/settings/actions" + tplUserVariables templates.TplName = "user/settings/actions" + tplAdminVariables templates.TplName = "admin/actions" +) + +type variablesCtx struct { + OwnerID int64 + RepoID int64 + IsRepo bool + IsOrg bool + IsUser bool + IsGlobal bool + VariablesTemplate templates.TplName + RedirectLink string +} + +func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) { + if ctx.Data["PageIsRepoSettings"] == true { + return &variablesCtx{ + OwnerID: 0, + RepoID: ctx.Repo.Repository.ID, + IsRepo: true, + VariablesTemplate: tplRepoVariables, + RedirectLink: ctx.Repo.RepoLink + "/settings/actions/variables", + }, nil + } + + if ctx.Data["PageIsOrgSettings"] == true { + err := shared_user.LoadHeaderCount(ctx) + if err != nil { + ctx.ServerError("LoadHeaderCount", err) + return nil, nil + } + return &variablesCtx{ + OwnerID: ctx.ContextUser.ID, + RepoID: 0, + IsOrg: true, + VariablesTemplate: tplOrgVariables, + RedirectLink: ctx.Org.OrgLink + "/settings/actions/variables", + }, nil + } + + if ctx.Data["PageIsUserSettings"] == true { + return &variablesCtx{ + OwnerID: ctx.Doer.ID, + RepoID: 0, + IsUser: true, + VariablesTemplate: tplUserVariables, + RedirectLink: setting.AppSubURL + "/user/settings/actions/variables", + }, nil + } + + if ctx.Data["PageIsAdmin"] == true { + return &variablesCtx{ + OwnerID: 0, + RepoID: 0, + IsGlobal: true, + VariablesTemplate: tplAdminVariables, + RedirectLink: setting.AppSubURL + "/-/admin/actions/variables", + }, nil + } + + return nil, errors.New("unable to set Variables context") +} + +func Variables(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("actions.variables") + ctx.Data["PageType"] = "variables" + ctx.Data["PageIsSharedSettingsVariables"] = true + + vCtx, err := getVariablesCtx(ctx) + if err != nil { + ctx.ServerError("getVariablesCtx", err) + return + } + variables, err := db.Find[actions_model.ActionVariable](ctx, actions_model.FindVariablesOpts{ - OwnerID: ownerID, - RepoID: repoID, + OwnerID: vCtx.OwnerID, + RepoID: vCtx.RepoID, }) if err != nil { ctx.ServerError("FindVariables", err) return } ctx.Data["Variables"] = variables + + ctx.HTML(http.StatusOK, vCtx.VariablesTemplate) } -func CreateVariable(ctx *context.Context, ownerID, repoID int64, redirectURL string) { +func VariableCreate(ctx *context.Context) { + vCtx, err := getVariablesCtx(ctx) + if err != nil { + ctx.ServerError("getVariablesCtx", err) + return + } + + if ctx.HasError() { // form binding validation error + ctx.JSONError(ctx.GetErrMsg()) + return + } + form := web.GetForm(ctx).(*forms.EditVariableForm) - v, err := actions_service.CreateVariable(ctx, ownerID, repoID, form.Name, form.Data) + v, err := actions_service.CreateVariable(ctx, vCtx.OwnerID, vCtx.RepoID, form.Name, form.Data) if err != nil { log.Error("CreateVariable: %v", err) ctx.JSONError(ctx.Tr("actions.variables.creation.failed")) @@ -36,30 +132,92 @@ func CreateVariable(ctx *context.Context, ownerID, repoID int64, redirectURL str } ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name)) - ctx.JSONRedirect(redirectURL) + ctx.JSONRedirect(vCtx.RedirectLink) } -func UpdateVariable(ctx *context.Context, redirectURL string) { - id := ctx.PathParamInt64("variable_id") - form := web.GetForm(ctx).(*forms.EditVariableForm) +func VariableUpdate(ctx *context.Context) { + vCtx, err := getVariablesCtx(ctx) + if err != nil { + ctx.ServerError("getVariablesCtx", err) + return + } - if ok, err := actions_service.UpdateVariable(ctx, id, form.Name, form.Data); err != nil || !ok { + if ctx.HasError() { // form binding validation error + ctx.JSONError(ctx.GetErrMsg()) + return + } + + id := ctx.PathParamInt64("variable_id") + + variable := findActionsVariable(ctx, id, vCtx) + if ctx.Written() { + return + } + + form := web.GetForm(ctx).(*forms.EditVariableForm) + variable.Name = form.Name + variable.Data = form.Data + + if ok, err := actions_service.UpdateVariableNameData(ctx, variable); err != nil || !ok { log.Error("UpdateVariable: %v", err) ctx.JSONError(ctx.Tr("actions.variables.update.failed")) return } ctx.Flash.Success(ctx.Tr("actions.variables.update.success")) - ctx.JSONRedirect(redirectURL) + ctx.JSONRedirect(vCtx.RedirectLink) } -func DeleteVariable(ctx *context.Context, redirectURL string) { +func findActionsVariable(ctx *context.Context, id int64, vCtx *variablesCtx) *actions_model.ActionVariable { + opts := actions_model.FindVariablesOpts{ + IDs: []int64{id}, + } + switch { + case vCtx.IsRepo: + opts.RepoID = vCtx.RepoID + if opts.RepoID == 0 { + panic("RepoID is 0") + } + case vCtx.IsOrg, vCtx.IsUser: + opts.OwnerID = vCtx.OwnerID + if opts.OwnerID == 0 { + panic("OwnerID is 0") + } + case vCtx.IsGlobal: + // do nothing + default: + panic("invalid actions variable") + } + + got, err := actions_model.FindVariables(ctx, opts) + if err != nil { + ctx.ServerError("FindVariables", err) + return nil + } else if len(got) == 0 { + ctx.NotFound(nil) + return nil + } + return got[0] +} + +func VariableDelete(ctx *context.Context) { + vCtx, err := getVariablesCtx(ctx) + if err != nil { + ctx.ServerError("getVariablesCtx", err) + return + } + id := ctx.PathParamInt64("variable_id") - if err := actions_service.DeleteVariableByID(ctx, id); err != nil { + variable := findActionsVariable(ctx, id, vCtx) + if ctx.Written() { + return + } + + if err := actions_service.DeleteVariableByID(ctx, variable.ID); err != nil { log.Error("Delete variable [%d] failed: %v", id, err) ctx.JSONError(ctx.Tr("actions.variables.deletion.failed")) return } ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success")) - ctx.JSONRedirect(redirectURL) + ctx.JSONRedirect(vCtx.RedirectLink) } diff --git a/routers/web/shared/packages/packages.go b/routers/web/shared/packages/packages.go index a1bcb09850..3d1795b42c 100644 --- a/routers/web/shared/packages/packages.go +++ b/routers/web/shared/packages/packages.go @@ -210,7 +210,7 @@ func getCleanupRuleByContext(ctx *context.Context, owner *user_model.User) *pack pcr, err := packages_model.GetCleanupRuleByID(ctx, id) if err != nil { if err == packages_model.ErrPackageCleanupRuleNotExist { - ctx.NotFound("", err) + ctx.NotFound(err) } else { ctx.ServerError("GetCleanupRuleByID", err) } @@ -221,7 +221,7 @@ func getCleanupRuleByContext(ctx *context.Context, owner *user_model.User) *pack return pcr } - ctx.NotFound("", fmt.Errorf("PackageCleanupRule[%v] not associated to owner %v", id, owner)) + ctx.NotFound(fmt.Errorf("PackageCleanupRule[%v] not associated to owner %v", id, owner)) return nil } diff --git a/routers/web/shared/project/column.go b/routers/web/shared/project/column.go index 141c80716f..b6ffaea7f8 100644 --- a/routers/web/shared/project/column.go +++ b/routers/web/shared/project/column.go @@ -17,7 +17,7 @@ func MoveColumns(ctx *context.Context) { return } if !project.CanBeAccessedByOwnerRepo(ctx.ContextUser.ID, ctx.Repo.Repository) { - ctx.NotFound("CanBeAccessedByOwnerRepo", nil) + ctx.NotFound(nil) return } diff --git a/routers/web/user/home.go b/routers/web/user/home.go index ff9334da6e..dc78950cf2 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -417,7 +417,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { IsPull: optional.Some(isPullList), SortType: sortType, IsArchived: optional.Some(false), - User: ctx.Doer, + Doer: ctx.Doer, } // -------------------------------------------------------------------------- // Build opts (IssuesOptions), which contains filter information. @@ -429,7 +429,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { // Get repository IDs where User/Org/Team has access. if ctx.Org != nil && ctx.Org.Organization != nil { - opts.Org = ctx.Org.Organization + opts.Owner = ctx.Org.Organization.AsUser() opts.Team = ctx.Org.Team issue.PrepareFilterIssueLabels(ctx, 0, ctx.Org.Organization.AsUser()) @@ -724,7 +724,7 @@ func UsernameSubRoute(ctx *context.Context) { // check view permissions if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) { - ctx.NotFound("user", fmt.Errorf("%s", ctx.ContextUser.Name)) + ctx.NotFound(fmt.Errorf("%s", ctx.ContextUser.Name)) return false } return true @@ -744,7 +744,7 @@ func UsernameSubRoute(ctx *context.Context) { } case strings.HasSuffix(username, ".rss"): if !setting.Other.EnableFeed { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } if reloadParam(".rss") { @@ -752,7 +752,7 @@ func UsernameSubRoute(ctx *context.Context) { } case strings.HasSuffix(username, ".atom"): if !setting.Other.EnableFeed { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } if reloadParam(".atom") { diff --git a/routers/web/user/package.go b/routers/web/user/package.go index 1f75faf1c6..c01bc96e2b 100644 --- a/routers/web/user/package.go +++ b/routers/web/user/package.go @@ -139,7 +139,7 @@ func RedirectToLastVersion(ctx *context.Context) { p, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.Type(ctx.PathParam("type")), ctx.PathParam("name")) if err != nil { if err == packages_model.ErrPackageNotExist { - ctx.NotFound("GetPackageByName", err) + ctx.NotFound(err) } else { ctx.ServerError("GetPackageByName", err) } @@ -155,7 +155,7 @@ func RedirectToLastVersion(ctx *context.Context) { return } if len(pvs) == 0 { - ctx.NotFound("", err) + ctx.NotFound(err) return } @@ -317,7 +317,7 @@ func ListPackageVersions(ctx *context.Context) { p, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.Type(ctx.PathParam("type")), ctx.PathParam("name")) if err != nil { if err == packages_model.ErrPackageNotExist { - ctx.NotFound("GetPackageByName", err) + ctx.NotFound(err) } else { ctx.ServerError("GetPackageByName", err) } @@ -496,7 +496,7 @@ func DownloadPackageFile(ctx *context.Context) { pf, err := packages_model.GetFileForVersionByID(ctx, ctx.Package.Descriptor.Version.ID, ctx.PathParamInt64("fileid")) if err != nil { if err == packages_model.ErrPackageFileNotExist { - ctx.NotFound("", err) + ctx.NotFound(err) } else { ctx.ServerError("GetFileForVersionByID", err) } diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 7cda3c038c..39f066a53c 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -56,7 +56,7 @@ func OwnerProfile(ctx *context.Context) { func userProfile(ctx *context.Context) { // check view permissions if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) { - ctx.NotFound("user", fmt.Errorf("%s", ctx.ContextUser.Name)) + ctx.NotFound(fmt.Errorf("%s", ctx.ContextUser.Name)) return } @@ -325,7 +325,7 @@ func ActionUserFollow(ctx *context.Context) { if err != nil { log.Error("Failed to apply action %q: %v", ctx.FormString("action"), err) - ctx.Error(http.StatusBadRequest, fmt.Sprintf("Action %q failed", ctx.FormString("action"))) + ctx.HTTPError(http.StatusBadRequest, fmt.Sprintf("Action %q failed", ctx.FormString("action"))) return } @@ -340,5 +340,5 @@ func ActionUserFollow(ctx *context.Context) { return } log.Error("Failed to apply action %q: unsupported context user type: %s", ctx.FormString("action"), ctx.ContextUser.Type) - ctx.Error(http.StatusBadRequest, fmt.Sprintf("Action %q failed", ctx.FormString("action"))) + ctx.HTTPError(http.StatusBadRequest, fmt.Sprintf("Action %q failed", ctx.FormString("action"))) } diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index 3acc3c7a54..94577832a9 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -37,7 +37,7 @@ const ( // Account renders change user's password, user's email and user suicide page func Account(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials, setting.UserFeatureDeletion) && !setting.Service.EnableNotifyMail { - ctx.NotFound("Not Found", fmt.Errorf("account setting are not allowed to be changed")) + ctx.NotFound(fmt.Errorf("account setting are not allowed to be changed")) return } @@ -54,7 +54,7 @@ func Account(ctx *context.Context) { // AccountPost response for change user's password func AccountPost(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { - ctx.NotFound("Not Found", fmt.Errorf("password setting is not allowed to be changed")) + ctx.NotFound(fmt.Errorf("password setting is not allowed to be changed")) return } @@ -105,7 +105,7 @@ func AccountPost(ctx *context.Context) { // EmailPost response for change user's email func EmailPost(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { - ctx.NotFound("Not Found", fmt.Errorf("emails are not allowed to be changed")) + ctx.NotFound(fmt.Errorf("emails are not allowed to be changed")) return } @@ -239,7 +239,7 @@ func EmailPost(ctx *context.Context) { // DeleteEmail response for delete user's email func DeleteEmail(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { - ctx.NotFound("Not Found", fmt.Errorf("emails are not allowed to be changed")) + ctx.NotFound(fmt.Errorf("emails are not allowed to be changed")) return } email, err := user_model.GetEmailAddressByID(ctx, ctx.Doer.ID, ctx.FormInt64("id")) @@ -261,7 +261,7 @@ func DeleteEmail(ctx *context.Context) { // DeleteAccount render user suicide page and response for delete user himself func DeleteAccount(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureDeletion) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index 127aed9845..17e32f5403 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -26,7 +26,7 @@ const ( // Keys render user's SSH/GPG public keys page func Keys(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys, setting.UserFeatureManageGPGKeys) { - ctx.NotFound("Not Found", fmt.Errorf("keys setting is not allowed to be changed")) + ctx.NotFound(fmt.Errorf("keys setting is not allowed to be changed")) return } @@ -87,7 +87,7 @@ func KeysPost(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "gpg": if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { - ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited")) + ctx.NotFound(fmt.Errorf("gpg keys setting is not allowed to be visited")) return } @@ -168,7 +168,7 @@ func KeysPost(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "ssh": if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { - ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) + ctx.NotFound(fmt.Errorf("ssh keys setting is not allowed to be visited")) return } @@ -212,7 +212,7 @@ func KeysPost(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "verify_ssh": if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { - ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) + ctx.NotFound(fmt.Errorf("ssh keys setting is not allowed to be visited")) return } @@ -249,7 +249,7 @@ func DeleteKey(ctx *context.Context) { switch ctx.FormString("type") { case "gpg": if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { - ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited")) + ctx.NotFound(fmt.Errorf("gpg keys setting is not allowed to be visited")) return } if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.FormInt64("id")); err != nil { @@ -259,7 +259,7 @@ func DeleteKey(ctx *context.Context) { } case "ssh": if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { - ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) + ctx.NotFound(fmt.Errorf("ssh keys setting is not allowed to be visited")) return } diff --git a/routers/web/user/setting/oauth2_common.go b/routers/web/user/setting/oauth2_common.go index 783deca710..d4da468a85 100644 --- a/routers/web/user/setting/oauth2_common.go +++ b/routers/web/user/setting/oauth2_common.go @@ -76,14 +76,14 @@ func (oa *OAuth2CommonHandlers) EditShow(ctx *context.Context) { app, err := auth.GetOAuth2ApplicationByID(ctx, ctx.PathParamInt64("id")) if err != nil { if auth.IsErrOAuthApplicationNotFound(err) { - ctx.NotFound("Application not found", err) + ctx.NotFound(err) return } ctx.ServerError("GetOAuth2ApplicationByID", err) return } if app.UID != oa.OwnerID { - ctx.NotFound("Application not found", nil) + ctx.NotFound(nil) return } ctx.Data["App"] = app @@ -98,14 +98,14 @@ func (oa *OAuth2CommonHandlers) EditSave(ctx *context.Context) { app, err := auth.GetOAuth2ApplicationByID(ctx, ctx.PathParamInt64("id")) if err != nil { if auth.IsErrOAuthApplicationNotFound(err) { - ctx.NotFound("Application not found", err) + ctx.NotFound(err) return } ctx.ServerError("GetOAuth2ApplicationByID", err) return } if app.UID != oa.OwnerID { - ctx.NotFound("Application not found", nil) + ctx.NotFound(nil) return } ctx.Data["App"] = app @@ -135,14 +135,14 @@ func (oa *OAuth2CommonHandlers) RegenerateSecret(ctx *context.Context) { app, err := auth.GetOAuth2ApplicationByID(ctx, ctx.PathParamInt64("id")) if err != nil { if auth.IsErrOAuthApplicationNotFound(err) { - ctx.NotFound("Application not found", err) + ctx.NotFound(err) return } ctx.ServerError("GetOAuth2ApplicationByID", err) return } if app.UID != oa.OwnerID { - ctx.NotFound("Application not found", nil) + ctx.NotFound(nil) return } ctx.Data["App"] = app diff --git a/routers/web/user/setting/security/2fa.go b/routers/web/user/setting/security/2fa.go index 7bb10248e8..e5315efc74 100644 --- a/routers/web/user/setting/security/2fa.go +++ b/routers/web/user/setting/security/2fa.go @@ -27,7 +27,7 @@ import ( // RegenerateScratchTwoFactor regenerates the user's 2FA scratch code. func RegenerateScratchTwoFactor(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } @@ -63,7 +63,7 @@ func RegenerateScratchTwoFactor(ctx *context.Context) { // DisableTwoFactor deletes the user's 2FA settings. func DisableTwoFactor(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } @@ -157,7 +157,7 @@ func twofaGenerateSecretAndQr(ctx *context.Context) bool { // EnrollTwoFactor shows the page where the user can enroll into 2FA. func EnrollTwoFactor(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } @@ -187,7 +187,7 @@ func EnrollTwoFactor(ctx *context.Context) { // EnrollTwoFactorPost handles enrolling the user into 2FA. func EnrollTwoFactorPost(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } diff --git a/routers/web/user/setting/security/openid.go b/routers/web/user/setting/security/openid.go index 30eb6f63f8..9a57657912 100644 --- a/routers/web/user/setting/security/openid.go +++ b/routers/web/user/setting/security/openid.go @@ -18,7 +18,7 @@ import ( // OpenIDPost response for change user's openid func OpenIDPost(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } @@ -111,7 +111,7 @@ func settingsOpenIDVerify(ctx *context.Context) { // DeleteOpenID response for delete user's openid func DeleteOpenID(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } @@ -128,7 +128,7 @@ func DeleteOpenID(ctx *context.Context) { // ToggleOpenIDVisibility response for toggle visibility of user's openid func ToggleOpenIDVisibility(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } diff --git a/routers/web/user/setting/security/security.go b/routers/web/user/setting/security/security.go index 38d1910e2d..cc4c44993a 100644 --- a/routers/web/user/setting/security/security.go +++ b/routers/web/user/setting/security/security.go @@ -27,7 +27,7 @@ const ( func Security(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA, setting.UserFeatureManageCredentials) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } @@ -47,7 +47,7 @@ func Security(ctx *context.Context) { // DeleteAccountLink delete a single account link func DeleteAccountLink(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } diff --git a/routers/web/user/setting/security/webauthn.go b/routers/web/user/setting/security/webauthn.go index 70bfaac6e0..63721343df 100644 --- a/routers/web/user/setting/security/webauthn.go +++ b/routers/web/user/setting/security/webauthn.go @@ -25,7 +25,7 @@ import ( // WebAuthnRegister initializes the webauthn registration procedure func WebAuthnRegister(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } @@ -41,7 +41,7 @@ func WebAuthnRegister(ctx *context.Context) { return } if cred != nil { - ctx.Error(http.StatusConflict, "Name already taken") + ctx.HTTPError(http.StatusConflict, "Name already taken") return } @@ -72,7 +72,7 @@ func WebAuthnRegister(ctx *context.Context) { // WebauthnRegisterPost receives the response of the security key func WebauthnRegisterPost(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } @@ -109,7 +109,7 @@ func WebauthnRegisterPost(ctx *context.Context) { return } if dbCred != nil { - ctx.Error(http.StatusConflict, "Name already taken") + ctx.HTTPError(http.StatusConflict, "Name already taken") return } @@ -127,7 +127,7 @@ func WebauthnRegisterPost(ctx *context.Context) { // WebauthnDelete deletes an security key by id func WebauthnDelete(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } diff --git a/routers/web/user/stop_watch.go b/routers/web/user/stop_watch.go index 38f74ea455..1d1cc61cc9 100644 --- a/routers/web/user/stop_watch.go +++ b/routers/web/user/stop_watch.go @@ -19,19 +19,19 @@ func GetStopwatches(ctx *context.Context) { PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), }) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } count, err := issues_model.CountUserStopwatches(ctx, ctx.Doer.ID) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } apiSWs, err := convert.ToStopWatches(ctx, sws) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.HTTPError(http.StatusInternalServerError, err.Error()) return } diff --git a/routers/web/web.go b/routers/web/web.go index 08d4fa99a9..bba31ffb3a 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -37,6 +37,7 @@ import ( "code.gitea.io/gitea/routers/web/repo" "code.gitea.io/gitea/routers/web/repo/actions" repo_setting "code.gitea.io/gitea/routers/web/repo/setting" + shared_actions "code.gitea.io/gitea/routers/web/shared/actions" "code.gitea.io/gitea/routers/web/shared/project" "code.gitea.io/gitea/routers/web/user" user_setting "code.gitea.io/gitea/routers/web/user/setting" @@ -118,7 +119,7 @@ func webAuth(authMethod auth_service.Method) func(*context.Context) { ar, err := common.AuthShared(ctx.Base, ctx.Session, authMethod) if err != nil { log.Error("Failed to verify user: %v", err) - ctx.Error(http.StatusUnauthorized, "Failed to authenticate user") + ctx.HTTPError(http.StatusUnauthorized, "Failed to authenticate user") return } ctx.Doer = ar.Doer @@ -153,7 +154,7 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Cont if ctx.Doer.MustChangePassword { if ctx.Req.URL.Path != "/user/settings/change_password" { if strings.HasPrefix(ctx.Req.UserAgent(), "git") { - ctx.Error(http.StatusUnauthorized, ctx.Locale.TrString("auth.must_change_password")) + ctx.HTTPError(http.StatusUnauthorized, ctx.Locale.TrString("auth.must_change_password")) return } ctx.Data["Title"] = ctx.Tr("auth.must_change_password") @@ -210,7 +211,7 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Cont if options.AdminRequired { if !ctx.Doer.IsAdmin { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } ctx.Data["PageIsAdmin"] = true @@ -306,35 +307,35 @@ func registerRoutes(m *web.Router) { linkAccountEnabled := func(ctx *context.Context) { if !setting.Service.EnableOpenIDSignIn && !setting.Service.EnableOpenIDSignUp && !setting.OAuth2.Enabled { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } } openIDSignInEnabled := func(ctx *context.Context) { if !setting.Service.EnableOpenIDSignIn { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } } openIDSignUpEnabled := func(ctx *context.Context) { if !setting.Service.EnableOpenIDSignUp { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } } oauth2Enabled := func(ctx *context.Context) { if !setting.OAuth2.Enabled { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } } reqMilestonesDashboardPageEnabled := func(ctx *context.Context) { if !setting.Service.ShowMilestonesDashboardPage { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } } @@ -342,56 +343,56 @@ func registerRoutes(m *web.Router) { // webhooksEnabled requires webhooks to be enabled by admin. webhooksEnabled := func(ctx *context.Context) { if setting.DisableWebhooks { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } } starsEnabled := func(ctx *context.Context) { if setting.Repository.DisableStars { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } } lfsServerEnabled := func(ctx *context.Context) { if !setting.LFS.StartServer { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } } federationEnabled := func(ctx *context.Context) { if !setting.Federation.Enabled { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } } dlSourceEnabled := func(ctx *context.Context) { if setting.Repository.DisableDownloadSourceArchives { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } } sitemapEnabled := func(ctx *context.Context) { if !setting.Other.EnableSitemap { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } } packagesEnabled := func(ctx *context.Context) { if !setting.Packages.Enabled { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } } feedEnabled := func(ctx *context.Context) { if !setting.Other.EnableFeed { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } } @@ -400,18 +401,18 @@ func registerRoutes(m *web.Router) { return func(ctx *context.Context) { // only check global disabled units when ignoreGlobal is false if !ignoreGlobal && unitType.UnitGlobalDisabled() { - ctx.NotFound("Repo unit is is disabled: "+unitType.LogString(), nil) + ctx.NotFound(nil) return } if ctx.ContextUser == nil { - ctx.NotFound("ContextUser is nil", nil) + ctx.NotFound(nil) return } if ctx.ContextUser.IsOrganization() { if ctx.Org.Organization.UnitPermission(ctx, ctx.Doer, unitType) < accessMode { - ctx.NotFound("ContextUser is org but doer has no access to unit", nil) + ctx.NotFound(nil) return } } @@ -449,10 +450,10 @@ func registerRoutes(m *web.Router) { addSettingsVariablesRoutes := func() { m.Group("/variables", func() { - m.Get("", repo_setting.Variables) - m.Post("/new", web.Bind(forms.EditVariableForm{}), repo_setting.VariableCreate) - m.Post("/{variable_id}/edit", web.Bind(forms.EditVariableForm{}), repo_setting.VariableUpdate) - m.Post("/{variable_id}/delete", repo_setting.VariableDelete) + m.Get("", shared_actions.Variables) + m.Post("/new", web.Bind(forms.EditVariableForm{}), shared_actions.VariableCreate) + m.Post("/{variable_id}/edit", web.Bind(forms.EditVariableForm{}), shared_actions.VariableUpdate) + m.Post("/{variable_id}/delete", shared_actions.VariableDelete) }) } @@ -466,11 +467,11 @@ func registerRoutes(m *web.Router) { addSettingsRunnersRoutes := func() { m.Group("/runners", func() { - m.Get("", repo_setting.Runners) - m.Combo("/{runnerid}").Get(repo_setting.RunnersEdit). - Post(web.Bind(forms.EditRunnerForm{}), repo_setting.RunnersEditPost) - m.Post("/{runnerid}/delete", repo_setting.RunnerDeletePost) - m.Post("/reset_registration_token", repo_setting.ResetRunnerRegistrationToken) + m.Get("", shared_actions.Runners) + m.Combo("/{runnerid}").Get(shared_actions.RunnersEdit). + Post(web.Bind(forms.EditRunnerForm{}), shared_actions.RunnersEditPost) + m.Post("/{runnerid}/delete", shared_actions.RunnerDeletePost) + m.Post("/reset_registration_token", shared_actions.ResetRunnerRegistrationToken) }) } @@ -505,7 +506,7 @@ func registerRoutes(m *web.Router) { m.Get("/organizations", explore.Organizations) m.Get("/code", func(ctx *context.Context) { if unit.TypeCode.UnitGlobalDisabled() { - ctx.NotFound("Repo unit code is disabled", nil) + ctx.NotFound(nil) return } }, explore.Code) @@ -846,7 +847,7 @@ func registerRoutes(m *web.Router) { reqPackageAccess := func(accessMode perm.AccessMode) func(ctx *context.Context) { return func(ctx *context.Context) { if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() { - ctx.NotFound("", nil) + ctx.NotFound(nil) } } } @@ -857,12 +858,12 @@ func registerRoutes(m *web.Router) { switch { case ctx.ContextUser.Visibility == structs.VisibleTypePrivate: if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) { - ctx.NotFound("Visit Project", nil) + ctx.NotFound(nil) return } case ctx.ContextUser.Visibility == structs.VisibleTypeLimited: if ctx.Doer == nil { - ctx.NotFound("Visit Project", nil) + ctx.NotFound(nil) return } } @@ -1052,7 +1053,7 @@ func registerRoutes(m *web.Router) { }) }, reqSignIn, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), func(ctx *context.Context) { if ctx.ContextUser.IsIndividual() && ctx.ContextUser.ID != ctx.Doer.ID { - ctx.NotFound("NewProject", nil) + ctx.NotFound(nil) return } }) @@ -1147,7 +1148,7 @@ func registerRoutes(m *web.Router) { }) }) m.Group("/actions", func() { - m.Get("", repo_setting.RedirectToDefaultSetting) + m.Get("", shared_actions.RedirectToDefaultSetting) addSettingsRunnersRoutes() addSettingsSecretsRoutes() addSettingsVariablesRoutes() @@ -1201,6 +1202,10 @@ func registerRoutes(m *web.Router) { }) }) } + // FIXME: many "pulls" requests are sent to "issues" endpoints correctly, so the issue endpoints have to tolerate pull request permissions at the moment + m.Group("/{username}/{reponame}/{type:issues}", addIssuesPullsViewRoutes, optSignIn, context.RepoAssignment, context.RequireUnitReader(unit.TypeIssues, unit.TypePullRequests)) + m.Group("/{username}/{reponame}/{type:pulls}", addIssuesPullsViewRoutes, optSignIn, context.RepoAssignment, reqUnitPullsReader) + m.Group("/{username}/{reponame}", func() { m.Get("/comments/{id}/attachments", repo.GetCommentAttachments) m.Get("/labels", repo.RetrieveLabelsForList, repo.Labels) @@ -1208,9 +1213,6 @@ func registerRoutes(m *web.Router) { m.Get("/milestone/{id}", context.RepoRef(), repo.MilestoneIssuesAndPulls) m.Get("/issues/suggestions", repo.IssueSuggestions) }, optSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) // issue/pull attachments, labels, milestones - - m.Group("/{username}/{reponame}/{type:issues}", addIssuesPullsViewRoutes, optSignIn, context.RepoAssignment, reqUnitIssuesReader) - m.Group("/{username}/{reponame}/{type:pulls}", addIssuesPullsViewRoutes, optSignIn, context.RepoAssignment, reqUnitPullsReader) // end "/{username}/{reponame}": view milestone, label, issue, pull, etc m.Group("/{username}/{reponame}/{type:issues}", func() { @@ -1229,7 +1231,7 @@ func registerRoutes(m *web.Router) { m.Get("/search", repo.SearchRepoIssuesJSON) }, reqUnitIssuesReader) - addIssuesPullsRoutes := func() { + addIssuesPullsUpdateRoutes := func() { // for "/{username}/{reponame}/issues" or "/{username}/{reponame}/pulls" m.Group("/{index}", func() { m.Post("/title", repo.UpdateIssueTitle) @@ -1272,8 +1274,9 @@ func registerRoutes(m *web.Router) { m.Delete("/unpin/{index}", reqRepoAdmin, repo.IssueUnpin) m.Post("/move_pin", reqRepoAdmin, repo.IssuePinMove) } - m.Group("/{type:issues}", addIssuesPullsRoutes, reqUnitIssuesReader, context.RepoMustNotBeArchived()) - m.Group("/{type:pulls}", addIssuesPullsRoutes, reqUnitPullsReader, context.RepoMustNotBeArchived()) + // FIXME: many "pulls" requests are sent to "issues" endpoints incorrectly, so the issue endpoints have to tolerate pull request permissions at the moment + m.Group("/{type:issues}", addIssuesPullsUpdateRoutes, context.RequireUnitReader(unit.TypeIssues, unit.TypePullRequests), context.RepoMustNotBeArchived()) + m.Group("/{type:pulls}", addIssuesPullsUpdateRoutes, reqUnitPullsReader, context.RepoMustNotBeArchived()) m.Group("/comments/{id}", func() { m.Post("", repo.UpdateCommentContent) @@ -1297,7 +1300,7 @@ func registerRoutes(m *web.Router) { m.Post("/delete", repo.DeleteMilestone) }, reqRepoIssuesOrPullsWriter, context.RepoRef()) - // FIXME: need to move these routes to the proper place + // FIXME: many "pulls" requests are sent to "issues" endpoints incorrectly, need to move these routes to the proper place m.Group("/issues", func() { m.Post("/request_review", repo.UpdatePullReviewRequest) m.Post("/dismiss_review", reqRepoAdmin, web.Bind(forms.DismissReviewForm{}), repo.DismissReview) @@ -1642,8 +1645,8 @@ func registerRoutes(m *web.Router) { } m.NotFound(func(w http.ResponseWriter, req *http.Request) { - ctx := context.GetWebContext(req) + ctx := context.GetWebContext(req.Context()) defer routing.RecordFuncInfo(ctx, routing.GetFuncInfo(ctx.NotFound, "WebNotFound"))() - ctx.NotFound("", nil) + ctx.NotFound(nil) }) } diff --git a/routers/web/webfinger.go b/routers/web/webfinger.go index a87c426b3b..afcfdc8252 100644 --- a/routers/web/webfinger.go +++ b/routers/web/webfinger.go @@ -39,7 +39,7 @@ func WebfingerQuery(ctx *context.Context) { resource, err := url.Parse(ctx.FormTrim("resource")) if err != nil { - ctx.Error(http.StatusBadRequest) + ctx.HTTPError(http.StatusBadRequest) return } @@ -50,11 +50,11 @@ func WebfingerQuery(ctx *context.Context) { // allow only the current host parts := strings.SplitN(resource.Opaque, "@", 2) if len(parts) != 2 { - ctx.Error(http.StatusBadRequest) + ctx.HTTPError(http.StatusBadRequest) return } if parts[1] != appURL.Host { - ctx.Error(http.StatusBadRequest) + ctx.HTTPError(http.StatusBadRequest) return } @@ -65,21 +65,21 @@ func WebfingerQuery(ctx *context.Context) { err = user_model.ErrUserNotExist{} } default: - ctx.Error(http.StatusBadRequest) + ctx.HTTPError(http.StatusBadRequest) return } if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) } else { log.Error("Error getting user: %s Error: %v", resource.Opaque, err) - ctx.Error(http.StatusInternalServerError) + ctx.HTTPError(http.StatusInternalServerError) } return } if !user_model.IsUserVisibleToViewer(ctx, u, ctx.Doer) { - ctx.Error(http.StatusNotFound) + ctx.HTTPError(http.StatusNotFound) return } diff --git a/services/actions/task.go b/services/actions/task.go new file mode 100644 index 0000000000..bc54ade347 --- /dev/null +++ b/services/actions/task.go @@ -0,0 +1,107 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "context" + "fmt" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/db" + secret_model "code.gitea.io/gitea/models/secret" + + 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) { + var ( + task *runnerv1.Task + job *actions_model.ActionRunJob + ) + + if err := db.WithTx(ctx, func(ctx context.Context) error { + t, ok, err := actions_model.CreateTaskForRunner(ctx, runner) + if err != nil { + return fmt.Errorf("CreateTaskForRunner: %w", err) + } + if !ok { + return nil + } + + if err := t.LoadAttributes(ctx); err != nil { + return fmt.Errorf("task LoadAttributes: %w", err) + } + job = t.Job + + secrets, err := secret_model.GetSecretsOfTask(ctx, t) + if err != nil { + return fmt.Errorf("GetSecretsOfTask: %w", err) + } + + vars, err := actions_model.GetVariablesOfRun(ctx, t.Job.Run) + if err != nil { + return fmt.Errorf("GetVariablesOfRun: %w", err) + } + + needs, err := findTaskNeeds(ctx, job) + if err != nil { + return fmt.Errorf("findTaskNeeds: %w", err) + } + + taskContext, err := generateTaskContext(t) + if err != nil { + return fmt.Errorf("generateTaskContext: %w", err) + } + + task = &runnerv1.Task{ + Id: t.ID, + WorkflowPayload: t.Job.WorkflowPayload, + Context: taskContext, + Secrets: secrets, + Vars: vars, + Needs: needs, + } + + return nil + }); err != nil { + return nil, false, err + } + + if task == nil { + return nil, false, nil + } + + CreateCommitStatus(ctx, job) + + return task, true, nil +} + +func generateTaskContext(t *actions_model.ActionTask) (*structpb.Struct, error) { + giteaRuntimeToken, err := CreateAuthorizationToken(t.ID, t.Job.RunID, t.JobID) + if err != nil { + return nil, err + } + + gitCtx := GenerateGiteaContext(t.Job.Run, t.Job) + gitCtx["token"] = t.Token + gitCtx["gitea_runtime_token"] = giteaRuntimeToken + + return structpb.NewStruct(gitCtx) +} + +func findTaskNeeds(ctx context.Context, taskJob *actions_model.ActionRunJob) (map[string]*runnerv1.TaskNeed, error) { + taskNeeds, err := FindTaskNeeds(ctx, taskJob) + 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/services/actions/variables.go b/services/actions/variables.go index 8dde9c4af5..95f088dbd3 100644 --- a/services/actions/variables.go +++ b/services/actions/variables.go @@ -6,7 +6,6 @@ package actions import ( "context" "regexp" - "strings" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/modules/log" @@ -31,20 +30,18 @@ func CreateVariable(ctx context.Context, ownerID, repoID int64, name, data strin return v, nil } -func UpdateVariable(ctx context.Context, variableID int64, name, data string) (bool, error) { - if err := secret_service.ValidateName(name); err != nil { +func UpdateVariableNameData(ctx context.Context, variable *actions_model.ActionVariable) (bool, error) { + if err := secret_service.ValidateName(variable.Name); err != nil { return false, err } - if err := envNameCIRegexMatch(name); err != nil { + if err := envNameCIRegexMatch(variable.Name); err != nil { return false, err } - return actions_model.UpdateVariable(ctx, &actions_model.ActionVariable{ - ID: variableID, - Name: strings.ToUpper(name), - Data: util.ReserveLineBreakForTextarea(data), - }) + variable.Data = util.ReserveLineBreakForTextarea(variable.Data) + + return actions_model.UpdateVariableCols(ctx, variable, "name", "data") } func DeleteVariableByID(ctx context.Context, variableID int64) error { diff --git a/services/actions/workflow.go b/services/actions/workflow.go new file mode 100644 index 0000000000..9aecad171b --- /dev/null +++ b/services/actions/workflow.go @@ -0,0 +1,281 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "fmt" + "net/http" + "net/url" + "path" + "strings" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/perm" + access_model "code.gitea.io/gitea/models/perm/access" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/actions" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/reqctx" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/services/convert" + + "github.com/nektos/act/pkg/jobparser" + "github.com/nektos/act/pkg/model" +) + +func getActionWorkflowPath(commit *git.Commit) string { + paths := []string{".gitea/workflows", ".github/workflows"} + for _, treePath := range paths { + if _, err := commit.SubTree(treePath); err == nil { + return treePath + } + } + return "" +} + +func getActionWorkflowEntry(ctx *context.APIContext, commit *git.Commit, folder string, entry *git.TreeEntry) *api.ActionWorkflow { + cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions) + cfg := cfgUnit.ActionsConfig() + + defaultBranch, _ := commit.GetBranchName() + + workflowURL := fmt.Sprintf("%s/actions/workflows/%s", ctx.Repo.Repository.APIURL(), url.PathEscape(entry.Name())) + workflowRepoURL := fmt.Sprintf("%s/src/branch/%s/%s/%s", ctx.Repo.Repository.HTMLURL(ctx), util.PathEscapeSegments(defaultBranch), util.PathEscapeSegments(folder), url.PathEscape(entry.Name())) + badgeURL := fmt.Sprintf("%s/actions/workflows/%s/badge.svg?branch=%s", ctx.Repo.Repository.HTMLURL(ctx), url.PathEscape(entry.Name()), url.QueryEscape(ctx.Repo.Repository.DefaultBranch)) + + // See https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#get-a-workflow + // State types: + // - active + // - deleted + // - disabled_fork + // - disabled_inactivity + // - disabled_manually + state := "active" + if cfg.IsWorkflowDisabled(entry.Name()) { + state = "disabled_manually" + } + + // The CreatedAt and UpdatedAt fields currently reflect the timestamp of the latest commit, which can later be refined + // by retrieving the first and last commits for the file history. The first commit would indicate the creation date, + // while the last commit would represent the modification date. The DeletedAt could be determined by identifying + // the last commit where the file existed. However, this implementation has not been done here yet, as it would likely + // cause a significant performance degradation. + createdAt := commit.Author.When + updatedAt := commit.Author.When + + return &api.ActionWorkflow{ + ID: entry.Name(), + Name: entry.Name(), + Path: path.Join(folder, entry.Name()), + State: state, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + URL: workflowURL, + HTMLURL: workflowRepoURL, + BadgeURL: badgeURL, + } +} + +func EnableOrDisableWorkflow(ctx *context.APIContext, workflowID string, isEnable bool) error { + workflow, err := GetActionWorkflow(ctx, workflowID) + if err != nil { + return err + } + + cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions) + cfg := cfgUnit.ActionsConfig() + + if isEnable { + cfg.EnableWorkflow(workflow.ID) + } else { + cfg.DisableWorkflow(workflow.ID) + } + + return repo_model.UpdateRepoUnit(ctx, cfgUnit) +} + +func ListActionWorkflows(ctx *context.APIContext) ([]*api.ActionWorkflow, error) { + defaultBranchCommit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) + if err != nil { + ctx.APIErrorInternal(err) + return nil, err + } + + entries, err := actions.ListWorkflows(defaultBranchCommit) + if err != nil { + ctx.APIError(http.StatusNotFound, err.Error()) + return nil, err + } + + folder := getActionWorkflowPath(defaultBranchCommit) + + workflows := make([]*api.ActionWorkflow, len(entries)) + for i, entry := range entries { + workflows[i] = getActionWorkflowEntry(ctx, defaultBranchCommit, folder, entry) + } + + return workflows, nil +} + +func GetActionWorkflow(ctx *context.APIContext, workflowID string) (*api.ActionWorkflow, error) { + entries, err := ListActionWorkflows(ctx) + if err != nil { + return nil, err + } + + for _, entry := range entries { + if entry.Name == workflowID { + return entry, nil + } + } + + return nil, util.NewNotExistErrorf("workflow %q not found", workflowID) +} + +func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, workflowID, ref string, processInputs func(model *model.WorkflowDispatch, inputs map[string]any) error) error { + if workflowID == "" { + return util.ErrWrapLocale( + util.NewNotExistErrorf("workflowID is empty"), + "actions.workflow.not_found", workflowID, + ) + } + + if ref == "" { + return util.ErrWrapLocale( + util.NewNotExistErrorf("ref is empty"), + "form.target_ref_not_exist", ref, + ) + } + + // can not rerun job when workflow is disabled + cfgUnit := repo.MustGetUnit(ctx, unit.TypeActions) + cfg := cfgUnit.ActionsConfig() + if cfg.IsWorkflowDisabled(workflowID) { + return util.ErrWrapLocale( + util.NewPermissionDeniedErrorf("workflow is disabled"), + "actions.workflow.disabled", + ) + } + + // get target commit of run from specified ref + refName := git.RefName(ref) + var runTargetCommit *git.Commit + var err error + if refName.IsTag() { + runTargetCommit, err = gitRepo.GetTagCommit(refName.TagName()) + } else if refName.IsBranch() { + runTargetCommit, err = gitRepo.GetBranchCommit(refName.BranchName()) + } else { + refName = git.RefNameFromBranch(ref) + runTargetCommit, err = gitRepo.GetBranchCommit(ref) + } + if err != nil { + return util.ErrWrapLocale( + util.NewNotExistErrorf("ref %q doesn't exist", ref), + "form.target_ref_not_exist", ref, + ) + } + + // get workflow entry from runTargetCommit + entries, err := actions.ListWorkflows(runTargetCommit) + if err != nil { + return err + } + + // find workflow from commit + var workflows []*jobparser.SingleWorkflow + for _, entry := range entries { + if entry.Name() != workflowID { + continue + } + + content, err := actions.GetContentFromEntry(entry) + if err != nil { + return err + } + workflows, err = jobparser.Parse(content) + if err != nil { + return err + } + break + } + + if len(workflows) == 0 { + return util.ErrWrapLocale( + util.NewNotExistErrorf("workflow %q doesn't exist", workflowID), + "actions.workflow.not_found", workflowID, + ) + } + + // get inputs from post + workflow := &model.Workflow{ + RawOn: workflows[0].RawOn, + } + inputsWithDefaults := make(map[string]any) + if workflowDispatch := workflow.WorkflowDispatchConfig(); workflowDispatch != nil { + if err = processInputs(workflowDispatch, inputsWithDefaults); err != nil { + return err + } + } + + // ctx.Req.PostForm -> WorkflowDispatchPayload.Inputs -> ActionRun.EventPayload -> runner: ghc.Event + // https://docs.github.com/en/actions/learn-github-actions/contexts#github-context + // https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_dispatch + workflowDispatchPayload := &api.WorkflowDispatchPayload{ + Workflow: workflowID, + Ref: ref, + Repository: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeNone}), + Inputs: inputsWithDefaults, + Sender: convert.ToUserWithAccessMode(ctx, doer, perm.AccessModeNone), + } + var eventPayload []byte + if eventPayload, err = workflowDispatchPayload.JSONPayload(); err != nil { + return fmt.Errorf("JSONPayload: %w", err) + } + + run := &actions_model.ActionRun{ + Title: strings.SplitN(runTargetCommit.CommitMessage, "\n", 2)[0], + RepoID: repo.ID, + OwnerID: repo.OwnerID, + WorkflowID: workflowID, + TriggerUserID: doer.ID, + Ref: string(refName), + CommitSHA: runTargetCommit.ID.String(), + IsForkPullRequest: false, + Event: "workflow_dispatch", + TriggerEvent: "workflow_dispatch", + EventPayload: string(eventPayload), + Status: actions_model.StatusWaiting, + } + + // cancel running jobs of the same workflow + if err := actions_model.CancelPreviousJobs( + ctx, + run.RepoID, + run.Ref, + run.WorkflowID, + run.Event, + ); err != nil { + log.Error("CancelRunningJobs: %v", err) + } + + // Insert the action run and its associated jobs into the database + if err := actions_model.InsertRun(ctx, run, workflows); err != nil { + return fmt.Errorf("InsertRun: %w", err) + } + + allJobs, err := db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{RunID: run.ID}) + if err != nil { + log.Error("FindRunJobs: %v", err) + } + CreateCommitStatus(ctx, allJobs...) + + return nil +} diff --git a/services/asymkey/commit.go b/services/asymkey/commit.go new file mode 100644 index 0000000000..df29133972 --- /dev/null +++ b/services/asymkey/commit.go @@ -0,0 +1,363 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package asymkey + +import ( + "context" + "strings" + + asymkey_model "code.gitea.io/gitea/models/asymkey" + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + + "github.com/ProtonMail/go-crypto/openpgp/packet" +) + +// ParseCommitWithSignature check if signature is good against keystore. +func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *asymkey_model.CommitVerification { + var committer *user_model.User + if c.Committer != nil { + var err error + // Find Committer account + committer, err = user_model.GetUserByEmail(ctx, c.Committer.Email) // This finds the user by primary email or activated email so commit will not be valid if email is not + if err != nil { // Skipping not user for committer + committer = &user_model.User{ + Name: c.Committer.Name, + Email: c.Committer.Email, + } + // We can expect this to often be an ErrUserNotExist. in the case + // it is not, however, it is important to log it. + if !user_model.IsErrUserNotExist(err) { + log.Error("GetUserByEmail: %v", err) + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, + Reason: "gpg.error.no_committer_account", + } + } + } + } + + return ParseCommitWithSignatureCommitter(ctx, c, committer) +} + +func ParseCommitWithSignatureCommitter(ctx context.Context, c *git.Commit, committer *user_model.User) *asymkey_model.CommitVerification { + // If no signature just report the committer + if c.Signature == nil { + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, // Default value + Reason: "gpg.error.not_signed_commit", // Default value + } + } + + // If this a SSH signature handle it differently + if strings.HasPrefix(c.Signature.Signature, "-----BEGIN SSH SIGNATURE-----") { + return asymkey_model.ParseCommitWithSSHSignature(ctx, c, committer) + } + + // Parsing signature + sig, err := asymkey_model.ExtractSignature(c.Signature.Signature) + if err != nil { // Skipping failed to extract sign + log.Error("SignatureRead err: %v", err) + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, + Reason: "gpg.error.extract_sign", + } + } + + keyID := asymkey_model.TryGetKeyIDFromSignature(sig) + defaultReason := asymkey_model.NoKeyFound + + // First check if the sig has a keyID and if so just look at that + if commitVerification := HashAndVerifyForKeyID( + ctx, + sig, + c.Signature.Payload, + committer, + keyID, + setting.AppName, + ""); commitVerification != nil { + if commitVerification.Reason == asymkey_model.BadSignature { + defaultReason = asymkey_model.BadSignature + } else { + return commitVerification + } + } + + // Now try to associate the signature with the committer, if present + if committer.ID != 0 { + keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{ + OwnerID: committer.ID, + }) + if err != nil { // Skipping failed to get gpg keys of user + log.Error("ListGPGKeys: %v", err) + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, + Reason: "gpg.error.failed_retrieval_gpg_keys", + } + } + + if err := asymkey_model.GPGKeyList(keys).LoadSubKeys(ctx); err != nil { + log.Error("LoadSubKeys: %v", err) + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, + Reason: "gpg.error.failed_retrieval_gpg_keys", + } + } + + committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID) + activated := false + for _, e := range committerEmailAddresses { + if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) { + activated = true + break + } + } + + for _, k := range keys { + // Pre-check (& optimization) that emails attached to key can be attached to the committer email and can validate + canValidate := false + email := "" + if k.Verified && activated { + canValidate = true + email = c.Committer.Email + } + if !canValidate { + for _, e := range k.Emails { + if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) { + canValidate = true + email = e.Email + break + } + } + } + if !canValidate { + continue // Skip this key + } + + commitVerification := asymkey_model.HashAndVerifyWithSubKeysCommitVerification(sig, c.Signature.Payload, k, committer, committer, email) + if commitVerification != nil { + return commitVerification + } + } + } + + if setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" { + // OK we should try the default key + gpgSettings := git.GPGSettings{ + Sign: true, + KeyID: setting.Repository.Signing.SigningKey, + Name: setting.Repository.Signing.SigningName, + Email: setting.Repository.Signing.SigningEmail, + } + if err := gpgSettings.LoadPublicKeyContent(); err != nil { + log.Error("Error getting default signing key: %s %v", gpgSettings.KeyID, err) + } else if commitVerification := VerifyWithGPGSettings(ctx, &gpgSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil { + if commitVerification.Reason == asymkey_model.BadSignature { + defaultReason = asymkey_model.BadSignature + } else { + return commitVerification + } + } + } + + defaultGPGSettings, err := c.GetRepositoryDefaultPublicGPGKey(false) + if err != nil { + log.Error("Error getting default public gpg key: %v", err) + } else if defaultGPGSettings == nil { + log.Warn("Unable to get defaultGPGSettings for unattached commit: %s", c.ID.String()) + } else if defaultGPGSettings.Sign { + if commitVerification := VerifyWithGPGSettings(ctx, defaultGPGSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil { + if commitVerification.Reason == asymkey_model.BadSignature { + defaultReason = asymkey_model.BadSignature + } else { + return commitVerification + } + } + } + + return &asymkey_model.CommitVerification{ // Default at this stage + CommittingUser: committer, + Verified: false, + Warning: defaultReason != asymkey_model.NoKeyFound, + Reason: defaultReason, + SigningKey: &asymkey_model.GPGKey{ + KeyID: keyID, + }, + } +} + +func checkKeyEmails(ctx context.Context, email string, keys ...*asymkey_model.GPGKey) (bool, string) { + uid := int64(0) + var userEmails []*user_model.EmailAddress + var user *user_model.User + for _, key := range keys { + for _, e := range key.Emails { + if e.IsActivated && (email == "" || strings.EqualFold(e.Email, email)) { + return true, e.Email + } + } + if key.Verified && key.OwnerID != 0 { + if uid != key.OwnerID { + userEmails, _ = user_model.GetEmailAddresses(ctx, key.OwnerID) + uid = key.OwnerID + user = &user_model.User{ID: uid} + _, _ = user_model.GetUser(ctx, user) + } + for _, e := range userEmails { + if e.IsActivated && (email == "" || strings.EqualFold(e.Email, email)) { + return true, e.Email + } + } + if user.KeepEmailPrivate && strings.EqualFold(email, user.GetEmail()) { + return true, user.GetEmail() + } + } + } + return false, email +} + +func HashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload string, committer *user_model.User, keyID, name, email string) *asymkey_model.CommitVerification { + if keyID == "" { + return nil + } + keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{ + KeyID: keyID, + IncludeSubKeys: true, + }) + if err != nil { + log.Error("GetGPGKeysByKeyID: %v", err) + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, + Reason: "gpg.error.failed_retrieval_gpg_keys", + } + } + if len(keys) == 0 { + return nil + } + for _, key := range keys { + var primaryKeys []*asymkey_model.GPGKey + if key.PrimaryKeyID != "" { + primaryKeys, err = db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{ + KeyID: key.PrimaryKeyID, + IncludeSubKeys: true, + }) + if err != nil { + log.Error("GetGPGKeysByKeyID: %v", err) + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, + Reason: "gpg.error.failed_retrieval_gpg_keys", + } + } + } + + activated, email := checkKeyEmails(ctx, email, append([]*asymkey_model.GPGKey{key}, primaryKeys...)...) + if !activated { + continue + } + + signer := &user_model.User{ + Name: name, + Email: email, + } + if key.OwnerID != 0 { + owner, err := user_model.GetUserByID(ctx, key.OwnerID) + if err == nil { + signer = owner + } else if !user_model.IsErrUserNotExist(err) { + log.Error("Failed to user_model.GetUserByID: %d for key ID: %d (%s) %v", key.OwnerID, key.ID, key.KeyID, err) + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, + Reason: "gpg.error.no_committer_account", + } + } + } + commitVerification := asymkey_model.HashAndVerifyWithSubKeysCommitVerification(sig, payload, key, committer, signer, email) + if commitVerification != nil { + return commitVerification + } + } + // This is a bad situation ... We have a key id that is in our database but the signature doesn't match. + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, + Warning: true, + Reason: asymkey_model.BadSignature, + } +} + +func VerifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, sig *packet.Signature, payload string, committer *user_model.User, keyID string) *asymkey_model.CommitVerification { + // First try to find the key in the db + if commitVerification := HashAndVerifyForKeyID(ctx, sig, payload, committer, gpgSettings.KeyID, gpgSettings.Name, gpgSettings.Email); commitVerification != nil { + return commitVerification + } + + // Otherwise we have to parse the key + ekeys, err := asymkey_model.CheckArmoredGPGKeyString(gpgSettings.PublicKeyContent) + if err != nil { + log.Error("Unable to get default signing key: %v", err) + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, + Reason: "gpg.error.generate_hash", + } + } + for _, ekey := range ekeys { + pubkey := ekey.PrimaryKey + content, err := asymkey_model.Base64EncPubKey(pubkey) + if err != nil { + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, + Reason: "gpg.error.generate_hash", + } + } + k := &asymkey_model.GPGKey{ + Content: content, + CanSign: pubkey.CanSign(), + KeyID: pubkey.KeyIdString(), + } + for _, subKey := range ekey.Subkeys { + content, err := asymkey_model.Base64EncPubKey(subKey.PublicKey) + if err != nil { + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, + Reason: "gpg.error.generate_hash", + } + } + k.SubsKey = append(k.SubsKey, &asymkey_model.GPGKey{ + Content: content, + CanSign: subKey.PublicKey.CanSign(), + KeyID: subKey.PublicKey.KeyIdString(), + }) + } + if commitVerification := asymkey_model.HashAndVerifyWithSubKeysCommitVerification(sig, payload, k, committer, &user_model.User{ + Name: gpgSettings.Name, + Email: gpgSettings.Email, + }, gpgSettings.Email); commitVerification != nil { + return commitVerification + } + if keyID == k.KeyID { + // This is a bad situation ... We have a key id that matches our default key but the signature doesn't match. + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, + Warning: true, + Reason: asymkey_model.BadSignature, + } + } + } + return nil +} diff --git a/services/asymkey/sign.go b/services/asymkey/sign.go index 2f5d76a293..4fef91ac15 100644 --- a/services/asymkey/sign.go +++ b/services/asymkey/sign.go @@ -216,7 +216,7 @@ Loop: if commit.Signature == nil { return false, "", nil, &ErrWontSign{parentSigned} } - verification := asymkey_model.ParseCommitWithSignature(ctx, commit) + verification := ParseCommitWithSignature(ctx, commit) if !verification.Verified { return false, "", nil, &ErrWontSign{parentSigned} } @@ -272,7 +272,7 @@ Loop: if commit.Signature == nil { return false, "", nil, &ErrWontSign{parentSigned} } - verification := asymkey_model.ParseCommitWithSignature(ctx, commit) + verification := ParseCommitWithSignature(ctx, commit) if !verification.Verified { return false, "", nil, &ErrWontSign{parentSigned} } @@ -347,7 +347,7 @@ Loop: if err != nil { return false, "", nil, err } - verification := asymkey_model.ParseCommitWithSignature(ctx, commit) + verification := ParseCommitWithSignature(ctx, commit) if !verification.Verified { return false, "", nil, &ErrWontSign{baseSigned} } @@ -363,7 +363,7 @@ Loop: if err != nil { return false, "", nil, err } - verification := asymkey_model.ParseCommitWithSignature(ctx, commit) + verification := ParseCommitWithSignature(ctx, commit) if !verification.Verified { return false, "", nil, &ErrWontSign{headSigned} } @@ -379,7 +379,7 @@ Loop: if err != nil { return false, "", nil, err } - verification := asymkey_model.ParseCommitWithSignature(ctx, commit) + verification := ParseCommitWithSignature(ctx, commit) if !verification.Verified { return false, "", nil, &ErrWontSign{commitsSigned} } @@ -393,7 +393,7 @@ Loop: return false, "", nil, err } for _, commit := range commitList { - verification := asymkey_model.ParseCommitWithSignature(ctx, commit) + verification := ParseCommitWithSignature(ctx, commit) if !verification.Verified { return false, "", nil, &ErrWontSign{commitsSigned} } diff --git a/services/auth/auth.go b/services/auth/auth.go index 7deca9bc3d..f7deeb4c50 100644 --- a/services/auth/auth.go +++ b/services/auth/auth.go @@ -149,7 +149,7 @@ func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore middleware.SetLocaleCookie(resp, user.Language, 0) // force to generate a new CSRF token - if ctx := gitea_context.GetWebContext(req); ctx != nil { + if ctx := gitea_context.GetWebContext(req.Context()); ctx != nil { ctx.Csrf.PrepareForSessionUser(ctx) } } diff --git a/services/auth/sspi.go b/services/auth/sspi.go index 3882740ae3..8cb39886c4 100644 --- a/services/auth/sspi.go +++ b/services/auth/sspi.go @@ -88,7 +88,7 @@ func (s *SSPI) Verify(req *http.Request, w http.ResponseWriter, store DataStore, store.GetData()["EnableSSPI"] = true // in this case, the Verify function is called in Gitea's web context // FIXME: it doesn't look good to render the page here, why not redirect? - gitea_context.GetWebContext(req).HTML(http.StatusUnauthorized, tplSignIn) + gitea_context.GetWebContext(req.Context()).HTML(http.StatusUnauthorized, tplSignIn) return nil, err } if outToken != "" { diff --git a/services/context/api.go b/services/context/api.go index bdeff0af63..230c3456d1 100644 --- a/services/context/api.go +++ b/services/context/api.go @@ -5,6 +5,7 @@ package context import ( + "errors" "fmt" "net/http" "net/url" @@ -17,11 +18,15 @@ import ( "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" web_types "code.gitea.io/gitea/modules/web/types" ) // APIContext is a specific context for API service +// ATTENTION: This struct should never be manually constructed in routes/services, +// it has many internal details which should be carefully prepared by the framework. +// If it is abused, it would cause strange bugs like panic/resource-leak. type APIContext struct { *Base @@ -103,14 +108,28 @@ type APIRepoArchivedError struct { APIError } -// ServerError responds with error message, status is 500 -func (ctx *APIContext) ServerError(title string, err error) { - ctx.Error(http.StatusInternalServerError, title, err) +// APIErrorInternal responds with error message, status is 500 +func (ctx *APIContext) APIErrorInternal(err error) { + ctx.apiErrorInternal(1, err) } -// Error responds with an error message to client with given obj as the message. +func (ctx *APIContext) apiErrorInternal(skip int, err error) { + log.ErrorWithSkip(skip+1, "InternalServerError: %v", err) + + var message string + if !setting.IsProd || (ctx.Doer != nil && ctx.Doer.IsAdmin) { + message = err.Error() + } + + ctx.JSON(http.StatusInternalServerError, APIError{ + Message: message, + URL: setting.API.SwaggerURL, + }) +} + +// APIError responds with an error message to client with given obj as the message. // If status is 500, also it prints error to log. -func (ctx *APIContext) Error(status int, title string, obj any) { +func (ctx *APIContext) APIError(status int, obj any) { var message string if err, ok := obj.(error); ok { message = err.Error() @@ -119,7 +138,7 @@ func (ctx *APIContext) Error(status int, title string, obj any) { } if status == http.StatusInternalServerError { - log.ErrorWithSkip(1, "%s: %s", title, message) + log.ErrorWithSkip(1, "APIError: %s", message) if setting.IsProd && !(ctx.Doer != nil && ctx.Doer.IsAdmin) { message = "" @@ -132,22 +151,6 @@ func (ctx *APIContext) Error(status int, title string, obj any) { }) } -// InternalServerError responds with an error message to the client with the error as a message -// and the file and line of the caller. -func (ctx *APIContext) InternalServerError(err error) { - log.ErrorWithSkip(1, "InternalServerError: %v", err) - - var message string - if !setting.IsProd || (ctx.Doer != nil && ctx.Doer.IsAdmin) { - message = err.Error() - } - - ctx.JSON(http.StatusInternalServerError, APIError{ - Message: message, - URL: setting.API.SwaggerURL, - }) -} - type apiContextKeyType struct{} var apiContextKey = apiContextKeyType{} @@ -207,7 +210,7 @@ func (ctx *APIContext) SetLinkHeader(total, pageSize int) { } } -// APIContexter returns apicontext as middleware +// APIContexter returns APIContext middleware func APIContexter() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { @@ -224,7 +227,7 @@ func APIContexter() func(http.Handler) http.Handler { // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid. if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size - ctx.InternalServerError(err) + ctx.APIErrorInternal(err) return } } @@ -237,9 +240,9 @@ func APIContexter() func(http.Handler) http.Handler { } } -// NotFound handles 404s for APIContext +// APIErrorNotFound handles 404s for APIContext // String will replace message, errors will be added to a slice -func (ctx *APIContext) NotFound(objs ...any) { +func (ctx *APIContext) APIErrorNotFound(objs ...any) { message := ctx.Locale.TrString("error.not_found") var errors []string for _, obj := range objs { @@ -276,7 +279,7 @@ func ReferencesGitRepo(allowEmpty ...bool) func(ctx *APIContext) { var err error ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository) if err != nil { - ctx.Error(http.StatusInternalServerError, fmt.Sprintf("Open Repository %v failed", ctx.Repo.Repository.FullName()), err) + ctx.APIErrorInternal(err) return } } @@ -289,7 +292,7 @@ func RepoRefForAPI(next http.Handler) http.Handler { ctx := GetAPIContext(req) if ctx.Repo.GitRepo == nil { - ctx.InternalServerError(fmt.Errorf("no open git repo")) + ctx.APIErrorInternal(fmt.Errorf("no open git repo")) return } @@ -299,14 +302,14 @@ func RepoRefForAPI(next http.Handler) http.Handler { if ctx.Repo.GitRepo.IsBranchExist(refName) { ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName) if err != nil { - ctx.InternalServerError(err) + ctx.APIErrorInternal(err) return } ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() } else if ctx.Repo.GitRepo.IsTagExist(refName) { ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName) if err != nil { - ctx.InternalServerError(err) + ctx.APIErrorInternal(err) return } ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() @@ -314,11 +317,11 @@ func RepoRefForAPI(next http.Handler) http.Handler { ctx.Repo.CommitID = refName ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName) if err != nil { - ctx.NotFound("GetCommit", err) + ctx.APIErrorNotFound("GetCommit", err) return } } else { - ctx.NotFound(fmt.Errorf("not exist: '%s'", ctx.PathParam("*"))) + ctx.APIErrorNotFound(fmt.Errorf("not exist: '%s'", ctx.PathParam("*"))) return } @@ -347,12 +350,12 @@ func (ctx *APIContext) GetErrMsg() string { // NotFoundOrServerError use error check function to determine if the error // is about not found. It responds with 404 status code for not found error, // or error context description for logging purpose of 500 server error. -func (ctx *APIContext) NotFoundOrServerError(logMsg string, errCheck func(error) bool, logErr error) { - if errCheck(logErr) { +func (ctx *APIContext) NotFoundOrServerError(err error) { + if errors.Is(err, util.ErrNotExist) { ctx.JSON(http.StatusNotFound, nil) return } - ctx.Error(http.StatusInternalServerError, "NotFoundOrServerError", logMsg) + ctx.APIErrorInternal(err) } // IsUserSiteAdmin returns true if current user is a site admin @@ -365,7 +368,7 @@ func (ctx *APIContext) IsUserRepoAdmin() bool { return ctx.Repo.IsAdmin() } -// IsUserRepoWriter returns true if current user has write privilege in current repo +// IsUserRepoWriter returns true if current user has "write" privilege in current repo func (ctx *APIContext) IsUserRepoWriter(unitTypes []unit.Type) bool { for _, unitType := range unitTypes { if ctx.Repo.CanWrite(unitType) { diff --git a/services/context/base.go b/services/context/base.go index 5db84f42a5..3701668bf6 100644 --- a/services/context/base.go +++ b/services/context/base.go @@ -23,6 +23,10 @@ type BaseContextKeyType struct{} var BaseContextKey BaseContextKeyType +// Base is the base context for all web handlers +// ATTENTION: This struct should never be manually constructed in routes/services, +// it has many internal details which should be carefully prepared by the framework. +// If it is abused, it would cause strange bugs like panic/resource-leak. type Base struct { reqctx.RequestContext @@ -77,8 +81,8 @@ func (b *Base) RespHeader() http.Header { return b.Resp.Header() } -// Error returned an error to web browser -func (b *Base) Error(status int, contents ...string) { +// HTTPError returned an error to web browser +func (b *Base) HTTPError(status int, contents ...string) { v := http.StatusText(status) if len(contents) > 0 { v = contents[0] diff --git a/services/context/context.go b/services/context/context.go index 5b16f9be98..5e08fba442 100644 --- a/services/context/context.go +++ b/services/context/context.go @@ -34,7 +34,10 @@ type Render interface { HTML(w io.Writer, status int, name templates.TplName, data any, templateCtx context.Context) error } -// Context represents context of a request. +// Context represents context of a web request. +// ATTENTION: This struct should never be manually constructed in routes/services, +// it has many internal details which should be carefully prepared by the framework. +// If it is abused, it would cause strange bugs like panic/resource-leak. type Context struct { *Base @@ -76,9 +79,9 @@ type webContextKeyType struct{} var WebContextKey = webContextKeyType{} -func GetWebContext(req *http.Request) *Context { - ctx, _ := req.Context().Value(WebContextKey).(*Context) - return ctx +func GetWebContext(ctx context.Context) *Context { + webCtx, _ := ctx.Value(WebContextKey).(*Context) + return webCtx } // ValidateContext is a special context for form validation middleware. It may be different from other contexts. @@ -132,6 +135,7 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context { } ctx.TemplateContext = NewTemplateContextForWeb(ctx) ctx.Flash = &middleware.Flash{DataStore: ctx, Values: url.Values{}} + ctx.SetContextValue(WebContextKey, ctx) return ctx } @@ -162,7 +166,6 @@ func Contexter() func(next http.Handler) http.Handler { ctx.PageData = map[string]any{} ctx.Data["PageData"] = ctx.PageData - ctx.Base.SetContextValue(WebContextKey, ctx) ctx.Csrf = NewCSRFProtector(csrfOpts) // get the last flash message from cookie diff --git a/services/context/context_response.go b/services/context/context_response.go index c7044791eb..61b432395a 100644 --- a/services/context/context_response.go +++ b/services/context/context_response.go @@ -28,7 +28,7 @@ import ( func RedirectToUser(ctx *Base, userName string, redirectUserID int64) { user, err := user_model.GetUserByID(ctx, redirectUserID) if err != nil { - ctx.Error(http.StatusInternalServerError, "unable to get user") + ctx.HTTPError(http.StatusInternalServerError, "unable to get user") return } @@ -122,8 +122,8 @@ func (ctx *Context) RenderWithErr(msg any, tpl templates.TplName, form any) { } // NotFound displays a 404 (Not Found) page and prints the given error, if any. -func (ctx *Context) NotFound(logMsg string, logErr error) { - ctx.notFoundInternal(logMsg, logErr) +func (ctx *Context) NotFound(logErr error) { + ctx.notFoundInternal("", logErr) } func (ctx *Context) notFoundInternal(logMsg string, logErr error) { diff --git a/services/context/org.go b/services/context/org.go index 3f73165076..992a48afa0 100644 --- a/services/context/org.go +++ b/services/context/org.go @@ -51,7 +51,7 @@ func GetOrganizationByParams(ctx *Context) { if err == nil { RedirectToUser(ctx.Base, orgName, redirectUserID) } else if user_model.IsErrUserRedirectNotExist(err) { - ctx.NotFound("GetUserByName", err) + ctx.NotFound(err) } else { ctx.ServerError("LookupUserRedirect", err) } @@ -93,7 +93,7 @@ func OrgAssignment(opts OrgAssignmentOptions) func(ctx *Context) { // Handle Visibility if org.Visibility != structs.VisibleTypePublic && !ctx.IsSigned { // We must be signed in to see limited or private organizations - ctx.NotFound("OrgAssignment", err) + ctx.NotFound(err) return } @@ -142,7 +142,7 @@ func OrgAssignment(opts OrgAssignmentOptions) func(ctx *Context) { ctx.Data["SignedUser"] = &user_model.User{} } if (opts.RequireMember && !ctx.Org.IsMember) || (opts.RequireOwner && !ctx.Org.IsOwner) { - ctx.NotFound("OrgAssignment", err) + ctx.NotFound(err) return } ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner @@ -218,20 +218,20 @@ func OrgAssignment(opts OrgAssignmentOptions) func(ctx *Context) { } if !teamExists { - ctx.NotFound("OrgAssignment", err) + ctx.NotFound(err) return } ctx.Data["IsTeamMember"] = ctx.Org.IsTeamMember if opts.RequireTeamMember && !ctx.Org.IsTeamMember { - ctx.NotFound("OrgAssignment", err) + ctx.NotFound(err) return } ctx.Org.IsTeamAdmin = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.AccessMode >= perm.AccessModeAdmin ctx.Data["IsTeamAdmin"] = ctx.Org.IsTeamAdmin if opts.RequireTeamAdmin && !ctx.Org.IsTeamAdmin { - ctx.NotFound("OrgAssignment", err) + ctx.NotFound(err) return } } diff --git a/services/context/package.go b/services/context/package.go index e98e01acbb..64bb4f3ecd 100644 --- a/services/context/package.go +++ b/services/context/package.go @@ -33,15 +33,15 @@ type packageAssignmentCtx struct { // PackageAssignment returns a middleware to handle Context.Package assignment func PackageAssignment() func(ctx *Context) { return func(ctx *Context) { - errorFn := func(status int, title string, obj any) { + errorFn := func(status int, obj any) { err, ok := obj.(error) if !ok { err = fmt.Errorf("%s", obj) } if status == http.StatusNotFound { - ctx.NotFound(title, err) + ctx.NotFound(err) } else { - ctx.ServerError(title, err) + ctx.ServerError("PackageAssignment", err) } } paCtx := &packageAssignmentCtx{Base: ctx.Base, Doer: ctx.Doer, ContextUser: ctx.ContextUser} @@ -53,18 +53,18 @@ func PackageAssignment() func(ctx *Context) { func PackageAssignmentAPI() func(ctx *APIContext) { return func(ctx *APIContext) { paCtx := &packageAssignmentCtx{Base: ctx.Base, Doer: ctx.Doer, ContextUser: ctx.ContextUser} - ctx.Package = packageAssignment(paCtx, ctx.Error) + ctx.Package = packageAssignment(paCtx, ctx.APIError) } } -func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, string, any)) *Package { +func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, any)) *Package { pkg := &Package{ Owner: ctx.ContextUser, } var err error pkg.AccessMode, err = determineAccessMode(ctx.Base, pkg, ctx.Doer) if err != nil { - errCb(http.StatusInternalServerError, "determineAccessMode", err) + errCb(http.StatusInternalServerError, fmt.Errorf("determineAccessMode: %w", err)) return pkg } @@ -75,16 +75,16 @@ func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, string, any)) pv, err := packages_model.GetVersionByNameAndVersion(ctx, pkg.Owner.ID, packages_model.Type(packageType), name, version) if err != nil { if err == packages_model.ErrPackageNotExist { - errCb(http.StatusNotFound, "GetVersionByNameAndVersion", err) + errCb(http.StatusNotFound, fmt.Errorf("GetVersionByNameAndVersion: %w", err)) } else { - errCb(http.StatusInternalServerError, "GetVersionByNameAndVersion", err) + errCb(http.StatusInternalServerError, fmt.Errorf("GetVersionByNameAndVersion: %w", err)) } return pkg } pkg.Descriptor, err = packages_model.GetPackageDescriptor(ctx, pv) if err != nil { - errCb(http.StatusInternalServerError, "GetPackageDescriptor", err) + errCb(http.StatusInternalServerError, fmt.Errorf("GetPackageDescriptor: %w", err)) return pkg } } @@ -154,9 +154,9 @@ func PackageContexter() func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { base := NewBaseContext(resp, req) - // it is still needed when rendering 500 page in a package handler + // FIXME: web Context is still needed when rendering 500 page in a package handler + // It should be refactored to use new error handling mechanisms ctx := NewWebContext(base, renderer, nil) - ctx.SetContextValue(WebContextKey, ctx) next.ServeHTTP(ctx.Resp, ctx.Req) }) } diff --git a/services/context/permission.go b/services/context/permission.go index 0d69ccc4a4..7055f798da 100644 --- a/services/context/permission.go +++ b/services/context/permission.go @@ -15,7 +15,7 @@ import ( func RequireRepoAdmin() func(ctx *Context) { return func(ctx *Context) { if !ctx.IsSigned || !ctx.Repo.IsAdmin() { - ctx.NotFound("RequireRepoAdmin denies the request", nil) + ctx.NotFound(nil) return } } @@ -25,7 +25,7 @@ func RequireRepoAdmin() func(ctx *Context) { func CanWriteToBranch() func(ctx *Context) { return func(ctx *Context) { if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) { - ctx.NotFound("CanWriteToBranch denies permission", nil) + ctx.NotFound(nil) return } } @@ -39,7 +39,7 @@ func RequireUnitWriter(unitTypes ...unit.Type) func(ctx *Context) { return } } - ctx.NotFound("RequireUnitWriter denies the request", nil) + ctx.NotFound(nil) } } @@ -54,7 +54,7 @@ func RequireUnitReader(unitTypes ...unit.Type) func(ctx *Context) { return } } - ctx.NotFound("RequireUnitReader denies the request", nil) + ctx.NotFound(nil) } } @@ -78,7 +78,7 @@ func CheckRepoScopedToken(ctx *Context, repo *repo_model.Repository, level auth_ } if publicOnly && repo.IsPrivate { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } @@ -89,7 +89,7 @@ func CheckRepoScopedToken(ctx *Context, repo *repo_model.Repository, level auth_ } if !scopeMatched { - ctx.Error(http.StatusForbidden) + ctx.HTTPError(http.StatusForbidden) return } } diff --git a/services/context/repo.go b/services/context/repo.go index 1cb35b9b83..e63e3e5317 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -89,7 +89,7 @@ func (r *Repository) GetObjectFormat() git.ObjectFormat { func RepoMustNotBeArchived() func(ctx *Context) { return func(ctx *Context) { if ctx.Repo.Repository.IsArchived { - ctx.NotFound("IsArchived", errors.New(ctx.Locale.TrString("repo.archive.title"))) + ctx.NotFound(errors.New(ctx.Locale.TrString("repo.archive.title"))) } } } @@ -315,7 +315,7 @@ func RedirectToRepo(ctx *Base, redirectRepoID int64) { repo, err := repo_model.GetRepositoryByID(ctx, redirectRepoID) if err != nil { log.Error("GetRepositoryByID: %v", err) - ctx.Error(http.StatusInternalServerError, "GetRepositoryByID") + ctx.HTTPError(http.StatusInternalServerError, "GetRepositoryByID") return } @@ -349,7 +349,7 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) { EarlyResponseForGoGetMeta(ctx) return } - ctx.NotFound("no access right", nil) + ctx.NotFound(nil) return } ctx.Data["Permission"] = &ctx.Repo.Permission @@ -402,7 +402,7 @@ func RepoAssignment(ctx *Context) { if redirectUserID, err := user_model.LookupUserRedirect(ctx, userName); err == nil { RedirectToUser(ctx.Base, userName, redirectUserID) } else if user_model.IsErrUserRedirectNotExist(err) { - ctx.NotFound("GetUserByName", nil) + ctx.NotFound(nil) } else { ctx.ServerError("LookupUserRedirect", err) } @@ -447,7 +447,7 @@ func RepoAssignment(ctx *Context) { EarlyResponseForGoGetMeta(ctx) return } - ctx.NotFound("GetRepositoryByName", nil) + ctx.NotFound(nil) } else { ctx.ServerError("LookupRepoRedirect", err) } @@ -870,7 +870,7 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) { ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refShortName) if err != nil { if git.IsErrNotExist(err) { - ctx.NotFound("GetTagCommit", err) + ctx.NotFound(err) return } ctx.ServerError("GetTagCommit", err) @@ -883,7 +883,7 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) { ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refShortName) if err != nil { - ctx.NotFound("GetCommit", err) + ctx.NotFound(err) return } // If short commit ID add canonical link header @@ -892,7 +892,7 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) { ctx.RespHeader().Set("Link", fmt.Sprintf(`<%s>; rel="canonical"`, canonicalURL)) } } else { - ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refShortName)) + ctx.NotFound(fmt.Errorf("branch or tag not exist: %s", refShortName)) return } @@ -945,7 +945,7 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) { func GitHookService() func(ctx *Context) { return func(ctx *Context) { if !ctx.Doer.CanEditGitHook() { - ctx.NotFound("GitHookService", nil) + ctx.NotFound(nil) return } } diff --git a/services/context/user.go b/services/context/user.go index dbc35e198d..c09ded8339 100644 --- a/services/context/user.go +++ b/services/context/user.go @@ -14,15 +14,15 @@ import ( // UserAssignmentWeb returns a middleware to handle context-user assignment for web routes func UserAssignmentWeb() func(ctx *Context) { return func(ctx *Context) { - errorFn := func(status int, title string, obj any) { + errorFn := func(status int, obj any) { err, ok := obj.(error) if !ok { err = fmt.Errorf("%s", obj) } if status == http.StatusNotFound { - ctx.NotFound(title, err) + ctx.NotFound(err) } else { - ctx.ServerError(title, err) + ctx.ServerError("UserAssignmentWeb", err) } } ctx.ContextUser = userAssignment(ctx.Base, ctx.Doer, errorFn) @@ -42,9 +42,9 @@ func UserIDAssignmentAPI() func(ctx *APIContext) { ctx.ContextUser, err = user_model.GetUserByID(ctx, userID) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.Error(http.StatusNotFound, "GetUserByID", err) + ctx.APIError(http.StatusNotFound, err) } else { - ctx.Error(http.StatusInternalServerError, "GetUserByID", err) + ctx.APIErrorInternal(err) } } } @@ -54,11 +54,11 @@ func UserIDAssignmentAPI() func(ctx *APIContext) { // UserAssignmentAPI returns a middleware to handle context-user assignment for api routes func UserAssignmentAPI() func(ctx *APIContext) { return func(ctx *APIContext) { - ctx.ContextUser = userAssignment(ctx.Base, ctx.Doer, ctx.Error) + ctx.ContextUser = userAssignment(ctx.Base, ctx.Doer, ctx.APIError) } } -func userAssignment(ctx *Base, doer *user_model.User, errCb func(int, string, any)) (contextUser *user_model.User) { +func userAssignment(ctx *Base, doer *user_model.User, errCb func(int, any)) (contextUser *user_model.User) { username := ctx.PathParam("username") if doer != nil && doer.LowerName == strings.ToLower(username) { @@ -71,12 +71,12 @@ func userAssignment(ctx *Base, doer *user_model.User, errCb func(int, string, an if redirectUserID, err := user_model.LookupUserRedirect(ctx, username); err == nil { RedirectToUser(ctx, username, redirectUserID) } else if user_model.IsErrUserRedirectNotExist(err) { - errCb(http.StatusNotFound, "GetUserByName", err) + errCb(http.StatusNotFound, err) } else { - errCb(http.StatusInternalServerError, "LookupUserRedirect", err) + errCb(http.StatusInternalServerError, fmt.Errorf("LookupUserRedirect: %w", err)) } } else { - errCb(http.StatusInternalServerError, "GetUserByName", err) + errCb(http.StatusInternalServerError, fmt.Errorf("GetUserByName: %w", err)) } } } diff --git a/services/contexttest/context_tests.go b/services/contexttest/context_tests.go index b0f71cad20..98b8bdd63e 100644 --- a/services/contexttest/context_tests.go +++ b/services/contexttest/context_tests.go @@ -67,7 +67,6 @@ func MockContext(t *testing.T, reqPath string, opts ...MockContextOption) (*cont chiCtx := chi.NewRouteContext() ctx := context.NewWebContext(base, opt.Render, nil) - ctx.SetContextValue(context.WebContextKey, ctx) ctx.SetContextValue(chi.RouteCtxKey, chiCtx) if opt.SessionStore != nil { ctx.SetContextValue(session.MockStoreContextKey, opt.SessionStore) diff --git a/services/convert/convert.go b/services/convert/convert.go index c8cad2a2ad..ac2680766c 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -28,6 +28,7 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" + asymkey_service "code.gitea.io/gitea/services/asymkey" "code.gitea.io/gitea/services/gitdiff" ) @@ -229,9 +230,31 @@ func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.Action }, nil } +// ToActionArtifact convert a actions_model.ActionArtifact to an api.ActionArtifact +func ToActionArtifact(repo *repo_model.Repository, art *actions_model.ActionArtifact) (*api.ActionArtifact, error) { + url := fmt.Sprintf("%s/actions/artifacts/%d", repo.APIURL(), art.ID) + + return &api.ActionArtifact{ + ID: art.ID, + Name: art.ArtifactName, + SizeInBytes: art.FileSize, + Expired: art.Status == actions_model.ArtifactStatusExpired, + URL: url, + ArchiveDownloadURL: url + "/zip", + CreatedAt: art.CreatedUnix.AsLocalTime(), + UpdatedAt: art.UpdatedUnix.AsLocalTime(), + ExpiresAt: art.ExpiredUnix.AsLocalTime(), + WorkflowRun: &api.ActionWorkflowRun{ + ID: art.RunID, + RepositoryID: art.RepoID, + HeadSha: art.CommitSHA, + }, + }, nil +} + // ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification { - verif := asymkey_model.ParseCommitWithSignature(ctx, c) + verif := asymkey_service.ParseCommitWithSignature(ctx, c) commitVerification := &api.PayloadCommitVerification{ Verified: verif.Verified, Reason: verif.Reason, diff --git a/services/convert/issue.go b/services/convert/issue.go index 37935accca..7f386e6293 100644 --- a/services/convert/issue.go +++ b/services/convert/issue.go @@ -41,6 +41,9 @@ func toIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Iss if err := issue.LoadAttachments(ctx); err != nil { return &api.Issue{} } + if err := issue.LoadPinOrder(ctx); err != nil { + return &api.Issue{} + } apiIssue := &api.Issue{ ID: issue.ID, @@ -55,7 +58,7 @@ func toIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Iss Comments: issue.NumComments, Created: issue.CreatedUnix.AsTime(), Updated: issue.UpdatedUnix.AsTime(), - PinOrder: issue.PinOrder, + PinOrder: util.Iif(issue.PinOrder == -1, 0, issue.PinOrder), // -1 means loaded with no pin order } if issue.Repo != nil { @@ -67,7 +70,7 @@ func toIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Iss if err := issue.LoadLabels(ctx); err != nil { return &api.Issue{} } - apiIssue.Labels = ToLabelList(issue.Labels, issue.Repo, issue.Repo.Owner) + apiIssue.Labels = util.SliceNilAsEmpty(ToLabelList(issue.Labels, issue.Repo, issue.Repo.Owner)) apiIssue.Repo = &api.RepositoryMeta{ ID: issue.Repo.ID, Name: issue.Repo.Name, @@ -122,6 +125,7 @@ func toIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Iss // ToIssueList converts an IssueList to API format func ToIssueList(ctx context.Context, doer *user_model.User, il issues_model.IssueList) []*api.Issue { result := make([]*api.Issue, len(il)) + _ = il.LoadPinOrder(ctx) for i := range il { result[i] = ToIssue(ctx, doer, il[i]) } @@ -131,6 +135,7 @@ func ToIssueList(ctx context.Context, doer *user_model.User, il issues_model.Iss // ToAPIIssueList converts an IssueList to API format func ToAPIIssueList(ctx context.Context, doer *user_model.User, il issues_model.IssueList) []*api.Issue { result := make([]*api.Issue, len(il)) + _ = il.LoadPinOrder(ctx) for i := range il { result[i] = ToAPIIssue(ctx, doer, il[i]) } diff --git a/services/convert/pull.go b/services/convert/pull.go index a1ab7eeb8e..ad4f08fa91 100644 --- a/services/convert/pull.go +++ b/services/convert/pull.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" ) // ToAPIPullRequest assumes following fields have been assigned with valid values: @@ -77,7 +78,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u Labels: apiIssue.Labels, Milestone: apiIssue.Milestone, Assignee: apiIssue.Assignee, - Assignees: apiIssue.Assignees, + Assignees: util.SliceNilAsEmpty(apiIssue.Assignees), State: apiIssue.State, Draft: pr.IsWorkInProgress(ctx), IsLocked: apiIssue.IsLocked, @@ -92,7 +93,11 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u Deadline: apiIssue.Deadline, Created: pr.Issue.CreatedUnix.AsTimePtr(), Updated: pr.Issue.UpdatedUnix.AsTimePtr(), - PinOrder: apiIssue.PinOrder, + PinOrder: util.Iif(apiIssue.PinOrder == -1, 0, apiIssue.PinOrder), + + // output "[]" rather than null to align to github outputs + RequestedReviewers: []*api.User{}, + RequestedReviewersTeams: []*api.Team{}, AllowMaintainerEdit: pr.AllowMaintainerEdit, @@ -299,6 +304,9 @@ func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs if err := issueList.LoadAssignees(ctx); err != nil { return nil, err } + if err = issueList.LoadPinOrder(ctx); err != nil { + return nil, err + } reviews, err := prs.LoadReviews(ctx) if err != nil { @@ -363,7 +371,7 @@ func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs Deadline: apiIssue.Deadline, Created: pr.Issue.CreatedUnix.AsTimePtr(), Updated: pr.Issue.UpdatedUnix.AsTimePtr(), - PinOrder: apiIssue.PinOrder, + PinOrder: util.Iif(apiIssue.PinOrder == -1, 0, apiIssue.PinOrder), AllowMaintainerEdit: pr.AllowMaintainerEdit, diff --git a/services/convert/repository.go b/services/convert/repository.go index 632b6392d5..7dfdfd2179 100644 --- a/services/convert/repository.go +++ b/services/convert/repository.go @@ -14,6 +14,7 @@ import ( unit_model "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" ) // ToRepo converts a Repository to api.Repository @@ -242,7 +243,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR MirrorInterval: mirrorInterval, MirrorUpdated: mirrorUpdated, RepoTransfer: transfer, - Topics: repo.Topics, + Topics: util.SliceNilAsEmpty(repo.Topics), ObjectFormatName: repo.ObjectFormatName, Licenses: repoLicenses.StringList(), } diff --git a/services/cron/tasks_basic.go b/services/cron/tasks_basic.go index fb5938745e..841981787d 100644 --- a/services/cron/tasks_basic.go +++ b/services/cron/tasks_basic.go @@ -54,7 +54,7 @@ func registerRepoHealthCheck() { RunAtStart: false, Schedule: "@midnight", }, - Timeout: 60 * time.Second, + Timeout: time.Duration(setting.Git.Timeout.Default) * time.Second, Args: []string{}, }, func(ctx context.Context, _ *user_model.User, config Config) error { rhcConfig := config.(*RepoHealthCheckConfig) diff --git a/services/git/commit.go b/services/git/commit.go new file mode 100644 index 0000000000..8ab8f3d369 --- /dev/null +++ b/services/git/commit.go @@ -0,0 +1,95 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "context" + + asymkey_model "code.gitea.io/gitea/models/asymkey" + "code.gitea.io/gitea/models/db" + git_model "code.gitea.io/gitea/models/git" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/git" + asymkey_service "code.gitea.io/gitea/services/asymkey" +) + +// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys. +func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error)) ([]*asymkey_model.SignCommit, error) { + newCommits := make([]*asymkey_model.SignCommit, 0, len(oldCommits)) + keyMap := map[string]bool{} + + emails := make(container.Set[string]) + for _, c := range oldCommits { + if c.Committer != nil { + emails.Add(c.Committer.Email) + } + } + + emailUsers, err := user_model.GetUsersByEmails(ctx, emails.Values()) + if err != nil { + return nil, err + } + + for _, c := range oldCommits { + committer, ok := emailUsers[c.Committer.Email] + if !ok && c.Committer != nil { + committer = &user_model.User{ + Name: c.Committer.Name, + Email: c.Committer.Email, + } + } + + signCommit := &asymkey_model.SignCommit{ + UserCommit: c, + Verification: asymkey_service.ParseCommitWithSignatureCommitter(ctx, c.Commit, committer), + } + + _ = asymkey_model.CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap) + + newCommits = append(newCommits, signCommit) + } + return newCommits, nil +} + +// ConvertFromGitCommit converts git commits into SignCommitWithStatuses +func ConvertFromGitCommit(ctx context.Context, commits []*git.Commit, repo *repo_model.Repository) ([]*git_model.SignCommitWithStatuses, error) { + validatedCommits, err := user_model.ValidateCommitsWithEmails(ctx, commits) + if err != nil { + return nil, err + } + signedCommits, err := ParseCommitsWithSignature( + ctx, + validatedCommits, + repo.GetTrustModel(), + func(user *user_model.User) (bool, error) { + return repo_model.IsOwnerMemberCollaborator(ctx, repo, user.ID) + }, + ) + if err != nil { + return nil, err + } + return ParseCommitsWithStatus(ctx, signedCommits, repo) +} + +// ParseCommitsWithStatus checks commits latest statuses and calculates its worst status state +func ParseCommitsWithStatus(ctx context.Context, oldCommits []*asymkey_model.SignCommit, repo *repo_model.Repository) ([]*git_model.SignCommitWithStatuses, error) { + newCommits := make([]*git_model.SignCommitWithStatuses, 0, len(oldCommits)) + + for _, c := range oldCommits { + commit := &git_model.SignCommitWithStatuses{ + SignCommit: c, + } + statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, commit.ID.String(), db.ListOptions{}) + if err != nil { + return nil, err + } + + commit.Statuses = statuses + commit.Status = git_model.CalcCommitStatus(statuses) + newCommits = append(newCommits, commit) + } + return newCommits, nil +} diff --git a/services/gitdiff/git_diff_tree.go b/services/gitdiff/git_diff_tree.go new file mode 100644 index 0000000000..8039de145d --- /dev/null +++ b/services/gitdiff/git_diff_tree.go @@ -0,0 +1,249 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitdiff + +import ( + "bufio" + "context" + "fmt" + "io" + "strconv" + "strings" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" +) + +type DiffTree struct { + Files []*DiffTreeRecord +} + +type DiffTreeRecord struct { + // Status is one of 'added', 'deleted', 'modified', 'renamed', 'copied', 'typechanged', 'unmerged', 'unknown' + Status string + + // For renames and copies, the percentage of similarity between the source and target of the move/rename. + Score uint8 + + HeadPath string + BasePath string + HeadMode git.EntryMode + BaseMode git.EntryMode + HeadBlobID string + BaseBlobID string +} + +// GetDiffTree returns the list of path of the files that have changed between the two commits. +// If useMergeBase is true, the diff will be calculated using the merge base of the two commits. +// This is the same behavior as using a three-dot diff in git diff. +func GetDiffTree(ctx context.Context, gitRepo *git.Repository, useMergeBase bool, baseSha, headSha string) (*DiffTree, error) { + gitDiffTreeRecords, err := runGitDiffTree(ctx, gitRepo, useMergeBase, baseSha, headSha) + if err != nil { + return nil, err + } + + return &DiffTree{ + Files: gitDiffTreeRecords, + }, nil +} + +func runGitDiffTree(ctx context.Context, gitRepo *git.Repository, useMergeBase bool, baseSha, headSha string) ([]*DiffTreeRecord, error) { + useMergeBase, baseCommitID, headCommitID, err := validateGitDiffTreeArguments(gitRepo, useMergeBase, baseSha, headSha) + if err != nil { + return nil, err + } + + cmd := git.NewCommand(ctx, "diff-tree", "--raw", "-r", "--find-renames", "--root") + if useMergeBase { + cmd.AddArguments("--merge-base") + } + cmd.AddDynamicArguments(baseCommitID, headCommitID) + stdout, _, runErr := cmd.RunStdString(&git.RunOpts{Dir: gitRepo.Path}) + if runErr != nil { + log.Warn("git diff-tree: %v", runErr) + return nil, runErr + } + + return parseGitDiffTree(strings.NewReader(stdout)) +} + +func validateGitDiffTreeArguments(gitRepo *git.Repository, useMergeBase bool, baseSha, headSha string) (shouldUseMergeBase bool, resolvedBaseSha, resolvedHeadSha string, err error) { + // if the head is empty its an error + if headSha == "" { + return false, "", "", fmt.Errorf("headSha is empty") + } + + // if the head commit doesn't exist its and error + headCommit, err := gitRepo.GetCommit(headSha) + if err != nil { + return false, "", "", fmt.Errorf("failed to get commit headSha: %v", err) + } + headCommitID := headCommit.ID.String() + + // if the base is empty we should use the parent of the head commit + if baseSha == "" { + // if the headCommit has no parent we should use an empty commit + // this can happen when we are generating a diff against an orphaned commit + if headCommit.ParentCount() == 0 { + objectFormat, err := gitRepo.GetObjectFormat() + if err != nil { + return false, "", "", err + } + + // We set use merge base to false because we have no base commit + return false, objectFormat.EmptyTree().String(), headCommitID, nil + } + + baseCommit, err := headCommit.Parent(0) + if err != nil { + return false, "", "", fmt.Errorf("baseSha is '', attempted to use parent of commit %s, got error: %v", headCommit.ID.String(), err) + } + return useMergeBase, baseCommit.ID.String(), headCommitID, nil + } + + // try and get the base commit + baseCommit, err := gitRepo.GetCommit(baseSha) + // propagate the error if we couldn't get the base commit + if err != nil { + return useMergeBase, "", "", fmt.Errorf("failed to get base commit %s: %v", baseSha, err) + } + + return useMergeBase, baseCommit.ID.String(), headCommit.ID.String(), nil +} + +func parseGitDiffTree(gitOutput io.Reader) ([]*DiffTreeRecord, error) { + /* + The output of `git diff-tree --raw -r --find-renames` is of the form: + + : \t + + or for renames: + + : \t\t + + See: for more details + */ + results := make([]*DiffTreeRecord, 0) + + lines := bufio.NewScanner(gitOutput) + for lines.Scan() { + line := lines.Text() + + if len(line) == 0 { + continue + } + + record, err := parseGitDiffTreeLine(line) + if err != nil { + return nil, err + } + + results = append(results, record) + } + + if err := lines.Err(); err != nil { + return nil, err + } + + return results, nil +} + +func parseGitDiffTreeLine(line string) (*DiffTreeRecord, error) { + line = strings.TrimPrefix(line, ":") + splitSections := strings.SplitN(line, "\t", 2) + if len(splitSections) < 2 { + return nil, fmt.Errorf("unparsable output for diff-tree --raw: `%s`)", line) + } + + fields := strings.Fields(splitSections[0]) + if len(fields) < 5 { + return nil, fmt.Errorf("unparsable output for diff-tree --raw: `%s`, expected 5 space delimited values got %d)", line, len(fields)) + } + + baseMode, err := git.ParseEntryMode(fields[0]) + if err != nil { + return nil, err + } + + headMode, err := git.ParseEntryMode(fields[1]) + if err != nil { + return nil, err + } + + baseBlobID := fields[2] + headBlobID := fields[3] + + status, score, err := statusFromLetter(fields[4]) + if err != nil { + return nil, fmt.Errorf("unparsable output for diff-tree --raw: %s, error: %s", line, err) + } + + filePaths := strings.Split(splitSections[1], "\t") + + var headPath, basePath string + if status == "renamed" { + if len(filePaths) != 2 { + return nil, fmt.Errorf("unparsable output for diff-tree --raw: `%s`, expected 2 paths found %d", line, len(filePaths)) + } + basePath = filePaths[0] + headPath = filePaths[1] + } else { + basePath = filePaths[0] + headPath = filePaths[0] + } + + return &DiffTreeRecord{ + Status: status, + Score: score, + BaseMode: baseMode, + HeadMode: headMode, + BaseBlobID: baseBlobID, + HeadBlobID: headBlobID, + BasePath: basePath, + HeadPath: headPath, + }, nil +} + +func statusFromLetter(rawStatus string) (status string, score uint8, err error) { + if len(rawStatus) < 1 { + return "", 0, fmt.Errorf("empty status letter") + } + switch rawStatus[0] { + case 'A': + return "added", 0, nil + case 'D': + return "deleted", 0, nil + case 'M': + return "modified", 0, nil + case 'R': + score, err = tryParseStatusScore(rawStatus) + return "renamed", score, err + case 'C': + score, err = tryParseStatusScore(rawStatus) + return "copied", score, err + case 'T': + return "typechanged", 0, nil + case 'U': + return "unmerged", 0, nil + case 'X': + return "unknown", 0, nil + default: + return "", 0, fmt.Errorf("unknown status letter: '%s'", rawStatus) + } +} + +func tryParseStatusScore(rawStatus string) (uint8, error) { + if len(rawStatus) < 2 { + return 0, fmt.Errorf("status score missing") + } + + score, err := strconv.ParseUint(rawStatus[1:], 10, 8) + if err != nil { + return 0, fmt.Errorf("failed to parse status score: %w", err) + } else if score > 100 { + return 0, fmt.Errorf("status score out of range: %d", score) + } + + return uint8(score), nil +} diff --git a/services/gitdiff/git_diff_tree_test.go b/services/gitdiff/git_diff_tree_test.go new file mode 100644 index 0000000000..313d279e95 --- /dev/null +++ b/services/gitdiff/git_diff_tree_test.go @@ -0,0 +1,427 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitdiff + +import ( + "strings" + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/git" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGitDiffTree(t *testing.T) { + test := []struct { + Name string + RepoPath string + BaseSha string + HeadSha string + useMergeBase bool + Expected *DiffTree + }{ + { + Name: "happy path", + RepoPath: "../../modules/git/tests/repos/repo5_pulls", + BaseSha: "72866af952e98d02a73003501836074b286a78f6", + HeadSha: "d8e0bbb45f200e67d9a784ce55bd90821af45ebd", + Expected: &DiffTree{ + Files: []*DiffTreeRecord{ + { + Status: "modified", + HeadPath: "LICENSE", + BasePath: "LICENSE", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeBlob, + HeadBlobID: "ee469963e76ae1bb7ee83d7510df2864e6c8c640", + BaseBlobID: "c996f4725be8fc8c1d1c776e58c97ddc5d03b336", + }, + { + Status: "modified", + HeadPath: "README.md", + BasePath: "README.md", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeBlob, + HeadBlobID: "9dfc0a6257d8eff526f0cfaf6a8ea950f55a9dba", + BaseBlobID: "074e590b8e64898b02beef03ece83f962c94f54c", + }, + }, + }, + }, + { + Name: "first commit (no parent)", + RepoPath: "../../modules/git/tests/repos/repo5_pulls", + HeadSha: "72866af952e98d02a73003501836074b286a78f6", + Expected: &DiffTree{ + Files: []*DiffTreeRecord{ + { + Status: "added", + HeadPath: ".gitignore", + BasePath: ".gitignore", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeNoEntry, + HeadBlobID: "f1c181ec9c5c921245027c6b452ecfc1d3626364", + BaseBlobID: "0000000000000000000000000000000000000000", + }, + { + Status: "added", + HeadPath: "LICENSE", + BasePath: "LICENSE", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeNoEntry, + HeadBlobID: "c996f4725be8fc8c1d1c776e58c97ddc5d03b336", + BaseBlobID: "0000000000000000000000000000000000000000", + }, + { + Status: "added", + HeadPath: "README.md", + BasePath: "README.md", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeNoEntry, + HeadBlobID: "074e590b8e64898b02beef03ece83f962c94f54c", + BaseBlobID: "0000000000000000000000000000000000000000", + }, + }, + }, + }, + { + Name: "first commit (no parent), merge base = true", + RepoPath: "../../modules/git/tests/repos/repo5_pulls", + HeadSha: "72866af952e98d02a73003501836074b286a78f6", + useMergeBase: true, + Expected: &DiffTree{ + Files: []*DiffTreeRecord{ + { + Status: "added", + HeadPath: ".gitignore", + BasePath: ".gitignore", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeNoEntry, + HeadBlobID: "f1c181ec9c5c921245027c6b452ecfc1d3626364", + BaseBlobID: "0000000000000000000000000000000000000000", + }, + { + Status: "added", + HeadPath: "LICENSE", + BasePath: "LICENSE", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeNoEntry, + HeadBlobID: "c996f4725be8fc8c1d1c776e58c97ddc5d03b336", + BaseBlobID: "0000000000000000000000000000000000000000", + }, + { + Status: "added", + HeadPath: "README.md", + BasePath: "README.md", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeNoEntry, + HeadBlobID: "074e590b8e64898b02beef03ece83f962c94f54c", + BaseBlobID: "0000000000000000000000000000000000000000", + }, + }, + }, + }, + { + Name: "base and head same", + RepoPath: "../../modules/git/tests/repos/repo5_pulls", + BaseSha: "ed8f4d2fa5b2420706580d191f5dd50c4e491f3f", + HeadSha: "ed8f4d2fa5b2420706580d191f5dd50c4e491f3f", + Expected: &DiffTree{ + Files: []*DiffTreeRecord{}, + }, + }, + { + Name: "useMergeBase false", + RepoPath: "../../modules/git/tests/repos/repo5_pulls", + BaseSha: "ed8f4d2fa5b2420706580d191f5dd50c4e491f3f", + HeadSha: "111cac04bd7d20301964e27a93698aabb5781b80", // this commit can be found on the update-readme branch + useMergeBase: false, + Expected: &DiffTree{ + Files: []*DiffTreeRecord{ + { + Status: "modified", + HeadPath: "LICENSE", + BasePath: "LICENSE", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeBlob, + HeadBlobID: "c996f4725be8fc8c1d1c776e58c97ddc5d03b336", + BaseBlobID: "ed5119b3c1f45547b6785bc03eac7f87570fa17f", + }, + + { + Status: "modified", + HeadPath: "README.md", + BasePath: "README.md", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeBlob, + HeadBlobID: "fb39771a8865c9a67f2ab9b616c854805664553c", + BaseBlobID: "9dfc0a6257d8eff526f0cfaf6a8ea950f55a9dba", + }, + }, + }, + }, + { + Name: "useMergeBase true", + RepoPath: "../../modules/git/tests/repos/repo5_pulls", + BaseSha: "ed8f4d2fa5b2420706580d191f5dd50c4e491f3f", + HeadSha: "111cac04bd7d20301964e27a93698aabb5781b80", // this commit can be found on the update-readme branch + useMergeBase: true, + Expected: &DiffTree{ + Files: []*DiffTreeRecord{ + { + Status: "modified", + HeadPath: "README.md", + BasePath: "README.md", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeBlob, + HeadBlobID: "fb39771a8865c9a67f2ab9b616c854805664553c", + BaseBlobID: "9dfc0a6257d8eff526f0cfaf6a8ea950f55a9dba", + }, + }, + }, + }, + { + Name: "no base set", + RepoPath: "../../modules/git/tests/repos/repo5_pulls", + HeadSha: "d8e0bbb45f200e67d9a784ce55bd90821af45ebd", // this commit can be found on the update-readme branch + useMergeBase: false, + Expected: &DiffTree{ + Files: []*DiffTreeRecord{ + { + Status: "modified", + HeadPath: "LICENSE", + BasePath: "LICENSE", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeBlob, + HeadBlobID: "ee469963e76ae1bb7ee83d7510df2864e6c8c640", + BaseBlobID: "ed5119b3c1f45547b6785bc03eac7f87570fa17f", + }, + }, + }, + }, + } + + for _, tt := range test { + t.Run(tt.Name, func(t *testing.T) { + gitRepo, err := git.OpenRepository(git.DefaultContext, tt.RepoPath) + assert.NoError(t, err) + defer gitRepo.Close() + + diffPaths, err := GetDiffTree(db.DefaultContext, gitRepo, tt.useMergeBase, tt.BaseSha, tt.HeadSha) + require.NoError(t, err) + + assert.Equal(t, tt.Expected, diffPaths) + }) + } +} + +func TestParseGitDiffTree(t *testing.T) { + test := []struct { + Name string + GitOutput string + Expected []*DiffTreeRecord + }{ + { + Name: "file change", + GitOutput: ":100644 100644 64e43d23bcd08db12563a0a4d84309cadb437e1a 5dbc7792b5bb228647cfcc8dfe65fc649119dedc M\tResources/views/curriculum/edit.blade.php", + Expected: []*DiffTreeRecord{ + { + Status: "modified", + HeadPath: "Resources/views/curriculum/edit.blade.php", + BasePath: "Resources/views/curriculum/edit.blade.php", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeBlob, + HeadBlobID: "5dbc7792b5bb228647cfcc8dfe65fc649119dedc", + BaseBlobID: "64e43d23bcd08db12563a0a4d84309cadb437e1a", + }, + }, + }, + { + Name: "file added", + GitOutput: ":000000 100644 0000000000000000000000000000000000000000 0063162fb403db15ceb0517b34ab782e4e58b619 A\tResources/views/class/index.blade.php", + Expected: []*DiffTreeRecord{ + { + Status: "added", + HeadPath: "Resources/views/class/index.blade.php", + BasePath: "Resources/views/class/index.blade.php", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeNoEntry, + HeadBlobID: "0063162fb403db15ceb0517b34ab782e4e58b619", + BaseBlobID: "0000000000000000000000000000000000000000", + }, + }, + }, + { + Name: "file deleted", + GitOutput: ":100644 000000 bac4286303c8c0017ea2f0a48c561ddcc0330a14 0000000000000000000000000000000000000000 D\tResources/views/classes/index.blade.php", + Expected: []*DiffTreeRecord{ + { + Status: "deleted", + HeadPath: "Resources/views/classes/index.blade.php", + BasePath: "Resources/views/classes/index.blade.php", + HeadMode: git.EntryModeNoEntry, + BaseMode: git.EntryModeBlob, + HeadBlobID: "0000000000000000000000000000000000000000", + BaseBlobID: "bac4286303c8c0017ea2f0a48c561ddcc0330a14", + }, + }, + }, + { + Name: "file renamed", + GitOutput: ":100644 100644 c8a055cfb45cd39747292983ad1797ceab40f5b1 97248f79a90aaf81fe7fd74b33c1cb182dd41783 R087\tDatabase/Seeders/AdminDatabaseSeeder.php\tDatabase/Seeders/AcademicDatabaseSeeder.php", + Expected: []*DiffTreeRecord{ + { + Status: "renamed", + Score: 87, + HeadPath: "Database/Seeders/AcademicDatabaseSeeder.php", + BasePath: "Database/Seeders/AdminDatabaseSeeder.php", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeBlob, + HeadBlobID: "97248f79a90aaf81fe7fd74b33c1cb182dd41783", + BaseBlobID: "c8a055cfb45cd39747292983ad1797ceab40f5b1", + }, + }, + }, + { + Name: "no changes", + GitOutput: ``, + Expected: []*DiffTreeRecord{}, + }, + { + Name: "multiple changes", + GitOutput: ":000000 100644 0000000000000000000000000000000000000000 db736b44533a840981f1f17b7029d0f612b69550 A\tHttp/Controllers/ClassController.php\n" + + ":100644 000000 9a4d2344d4d0145db7c91b3f3e123c74367d4ef4 0000000000000000000000000000000000000000 D\tHttp/Controllers/ClassesController.php\n" + + ":100644 100644 f060d6aede65d423f49e7dc248dfa0d8835ef920 b82c8e39a3602dedadb44669956d6eb5b6a7cc86 M\tHttp/Controllers/ProgramDirectorController.php\n", + Expected: []*DiffTreeRecord{ + { + Status: "added", + HeadPath: "Http/Controllers/ClassController.php", + BasePath: "Http/Controllers/ClassController.php", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeNoEntry, + HeadBlobID: "db736b44533a840981f1f17b7029d0f612b69550", + BaseBlobID: "0000000000000000000000000000000000000000", + }, + { + Status: "deleted", + HeadPath: "Http/Controllers/ClassesController.php", + BasePath: "Http/Controllers/ClassesController.php", + HeadMode: git.EntryModeNoEntry, + BaseMode: git.EntryModeBlob, + HeadBlobID: "0000000000000000000000000000000000000000", + BaseBlobID: "9a4d2344d4d0145db7c91b3f3e123c74367d4ef4", + }, + { + Status: "modified", + HeadPath: "Http/Controllers/ProgramDirectorController.php", + BasePath: "Http/Controllers/ProgramDirectorController.php", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeBlob, + HeadBlobID: "b82c8e39a3602dedadb44669956d6eb5b6a7cc86", + BaseBlobID: "f060d6aede65d423f49e7dc248dfa0d8835ef920", + }, + }, + }, + { + Name: "spaces in file path", + GitOutput: ":000000 100644 0000000000000000000000000000000000000000 db736b44533a840981f1f17b7029d0f612b69550 A\tHttp /Controllers/Class Controller.php\n" + + ":100644 000000 9a4d2344d4d0145db7c91b3f3e123c74367d4ef4 0000000000000000000000000000000000000000 D\tHttp/Cont rollers/Classes Controller.php\n" + + ":100644 100644 f060d6aede65d423f49e7dc248dfa0d8835ef920 b82c8e39a3602dedadb44669956d6eb5b6a7cc86 R010\tHttp/Controllers/Program Director Controller.php\tHttp/Cont rollers/ProgramDirectorController.php\n", + Expected: []*DiffTreeRecord{ + { + Status: "added", + HeadPath: "Http /Controllers/Class Controller.php", + BasePath: "Http /Controllers/Class Controller.php", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeNoEntry, + HeadBlobID: "db736b44533a840981f1f17b7029d0f612b69550", + BaseBlobID: "0000000000000000000000000000000000000000", + }, + { + Status: "deleted", + HeadPath: "Http/Cont rollers/Classes Controller.php", + BasePath: "Http/Cont rollers/Classes Controller.php", + HeadMode: git.EntryModeNoEntry, + BaseMode: git.EntryModeBlob, + HeadBlobID: "0000000000000000000000000000000000000000", + BaseBlobID: "9a4d2344d4d0145db7c91b3f3e123c74367d4ef4", + }, + { + Status: "renamed", + Score: 10, + HeadPath: "Http/Cont rollers/ProgramDirectorController.php", + BasePath: "Http/Controllers/Program Director Controller.php", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeBlob, + HeadBlobID: "b82c8e39a3602dedadb44669956d6eb5b6a7cc86", + BaseBlobID: "f060d6aede65d423f49e7dc248dfa0d8835ef920", + }, + }, + }, + { + Name: "file type changed", + GitOutput: ":100644 120000 344e0ca8aa791cc4164fb0ea645f334fd40d00f0 a7c2973de00bfdc6ca51d315f401b5199fe01dc3 T\twebpack.mix.js", + Expected: []*DiffTreeRecord{ + { + Status: "typechanged", + HeadPath: "webpack.mix.js", + BasePath: "webpack.mix.js", + HeadMode: git.EntryModeSymlink, + BaseMode: git.EntryModeBlob, + HeadBlobID: "a7c2973de00bfdc6ca51d315f401b5199fe01dc3", + BaseBlobID: "344e0ca8aa791cc4164fb0ea645f334fd40d00f0", + }, + }, + }, + } + + for _, tt := range test { + t.Run(tt.Name, func(t *testing.T) { + entries, err := parseGitDiffTree(strings.NewReader(tt.GitOutput)) + assert.NoError(t, err) + assert.Equal(t, tt.Expected, entries) + }) + } +} + +func TestGitDiffTreeErrors(t *testing.T) { + test := []struct { + Name string + RepoPath string + BaseSha string + HeadSha string + }{ + { + Name: "head doesn't exist", + RepoPath: "../../modules/git/tests/repos/repo5_pulls", + BaseSha: "f32b0a9dfd09a60f616f29158f772cedd89942d2", + HeadSha: "asdfasdfasdf", + }, + { + Name: "base doesn't exist", + RepoPath: "../../modules/git/tests/repos/repo5_pulls", + BaseSha: "asdfasdfasdf", + HeadSha: "f32b0a9dfd09a60f616f29158f772cedd89942d2", + }, + { + Name: "head not set", + RepoPath: "../../modules/git/tests/repos/repo5_pulls", + BaseSha: "f32b0a9dfd09a60f616f29158f772cedd89942d2", + }, + } + + for _, tt := range test { + t.Run(tt.Name, func(t *testing.T) { + gitRepo, err := git.OpenRepository(git.DefaultContext, tt.RepoPath) + assert.NoError(t, err) + defer gitRepo.Close() + + diffPaths, err := GetDiffTree(db.DefaultContext, gitRepo, true, tt.BaseSha, tt.HeadSha) + assert.Error(t, err) + assert.Nil(t, diffPaths) + }) + } +} diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index f046e59678..064f05cfce 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -80,7 +80,7 @@ type DiffLine struct { Match int Type DiffLineType Content string - Comments []*issues_model.Comment + Comments issues_model.CommentList SectionInfo *DiffLineSectionInfo } diff --git a/services/gitdiff/gitdiff_test.go b/services/gitdiff/gitdiff_test.go index 1017d188dd..ca9b5a6f4e 100644 --- a/services/gitdiff/gitdiff_test.go +++ b/services/gitdiff/gitdiff_test.go @@ -5,6 +5,7 @@ package gitdiff import ( + "context" "strconv" "strings" "testing" @@ -628,23 +629,25 @@ func TestDiffLine_GetCommentSide(t *testing.T) { } func TestGetDiffRangeWithWhitespaceBehavior(t *testing.T) { - gitRepo, err := git.OpenRepository(git.DefaultContext, "./testdata/academic-module") + gitRepo, err := git.OpenRepository(context.Background(), "../../modules/git/tests/repos/repo5_pulls") require.NoError(t, err) defer gitRepo.Close() for _, behavior := range []git.TrustedCmdArgs{{"-w"}, {"--ignore-space-at-eol"}, {"-b"}, nil} { - diffs, err := GetDiff(db.DefaultContext, gitRepo, + diffs, err := GetDiff(context.Background(), gitRepo, &DiffOptions{ - AfterCommitID: "bd7063cc7c04689c4d082183d32a604ed27a24f9", - BeforeCommitID: "559c156f8e0178b71cb44355428f24001b08fc68", + AfterCommitID: "d8e0bbb45f200e67d9a784ce55bd90821af45ebd", + BeforeCommitID: "72866af952e98d02a73003501836074b286a78f6", MaxLines: setting.Git.MaxGitDiffLines, MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters, - MaxFiles: setting.Git.MaxGitDiffFiles, + MaxFiles: 1, WhitespaceBehavior: behavior, }) - assert.NoError(t, err, "Error when diff with %s", behavior) + require.NoError(t, err, "Error when diff with WhitespaceBehavior=%s", behavior) + assert.True(t, diffs.IsIncomplete) + assert.Len(t, diffs.Files, 1) for _, f := range diffs.Files { - assert.NotEmpty(t, f.Sections, "%s should have sections", f.Name) + assert.NotEmpty(t, f.Sections, "Diff file %q should have sections", f.Name) } } } diff --git a/services/gitdiff/testdata/academic-module/HEAD b/services/gitdiff/testdata/academic-module/HEAD deleted file mode 100644 index cb089cd89a..0000000000 --- a/services/gitdiff/testdata/academic-module/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/heads/master diff --git a/services/gitdiff/testdata/academic-module/config b/services/gitdiff/testdata/academic-module/config deleted file mode 100644 index 1bc26be514..0000000000 --- a/services/gitdiff/testdata/academic-module/config +++ /dev/null @@ -1,10 +0,0 @@ -[core] - repositoryformatversion = 0 - filemode = true - bare = false - logallrefupdates = true - ignorecase = true - precomposeunicode = true -[branch "master"] - remote = origin - merge = refs/heads/master diff --git a/services/gitdiff/testdata/academic-module/index b/services/gitdiff/testdata/academic-module/index deleted file mode 100644 index e712c906e3..0000000000 Binary files a/services/gitdiff/testdata/academic-module/index and /dev/null differ diff --git a/services/gitdiff/testdata/academic-module/logs/HEAD b/services/gitdiff/testdata/academic-module/logs/HEAD deleted file mode 100644 index 16b2e1c0f6..0000000000 --- a/services/gitdiff/testdata/academic-module/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 bd7063cc7c04689c4d082183d32a604ed27a24f9 Lunny Xiao 1574829684 +0800 clone: from https://try.gitea.io/shemgp-aiias/academic-module diff --git a/services/gitdiff/testdata/academic-module/logs/refs/heads/master b/services/gitdiff/testdata/academic-module/logs/refs/heads/master deleted file mode 100644 index 16b2e1c0f6..0000000000 --- a/services/gitdiff/testdata/academic-module/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 bd7063cc7c04689c4d082183d32a604ed27a24f9 Lunny Xiao 1574829684 +0800 clone: from https://try.gitea.io/shemgp-aiias/academic-module diff --git a/services/gitdiff/testdata/academic-module/logs/refs/remotes/origin/HEAD b/services/gitdiff/testdata/academic-module/logs/refs/remotes/origin/HEAD deleted file mode 100644 index 16b2e1c0f6..0000000000 --- a/services/gitdiff/testdata/academic-module/logs/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 bd7063cc7c04689c4d082183d32a604ed27a24f9 Lunny Xiao 1574829684 +0800 clone: from https://try.gitea.io/shemgp-aiias/academic-module diff --git a/services/gitdiff/testdata/academic-module/objects/pack/pack-597efbc3613c7ba790e33b178fd9fc1fe17b4245.idx b/services/gitdiff/testdata/academic-module/objects/pack/pack-597efbc3613c7ba790e33b178fd9fc1fe17b4245.idx deleted file mode 100644 index 4d759aa504..0000000000 Binary files a/services/gitdiff/testdata/academic-module/objects/pack/pack-597efbc3613c7ba790e33b178fd9fc1fe17b4245.idx and /dev/null differ diff --git a/services/gitdiff/testdata/academic-module/objects/pack/pack-597efbc3613c7ba790e33b178fd9fc1fe17b4245.pack b/services/gitdiff/testdata/academic-module/objects/pack/pack-597efbc3613c7ba790e33b178fd9fc1fe17b4245.pack deleted file mode 100644 index 2dc49cfded..0000000000 Binary files a/services/gitdiff/testdata/academic-module/objects/pack/pack-597efbc3613c7ba790e33b178fd9fc1fe17b4245.pack and /dev/null differ diff --git a/services/gitdiff/testdata/academic-module/packed-refs b/services/gitdiff/testdata/academic-module/packed-refs deleted file mode 100644 index 13b5611650..0000000000 --- a/services/gitdiff/testdata/academic-module/packed-refs +++ /dev/null @@ -1,2 +0,0 @@ -# pack-refs with: peeled fully-peeled sorted -bd7063cc7c04689c4d082183d32a604ed27a24f9 refs/remotes/origin/master diff --git a/services/gitdiff/testdata/academic-module/refs/heads/master b/services/gitdiff/testdata/academic-module/refs/heads/master deleted file mode 100644 index bd2b56eaf4..0000000000 --- a/services/gitdiff/testdata/academic-module/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -bd7063cc7c04689c4d082183d32a604ed27a24f9 diff --git a/services/gitdiff/testdata/academic-module/refs/remotes/origin/HEAD b/services/gitdiff/testdata/academic-module/refs/remotes/origin/HEAD deleted file mode 100644 index 6efe28fff8..0000000000 --- a/services/gitdiff/testdata/academic-module/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/remotes/origin/master diff --git a/services/issue/comments.go b/services/issue/comments.go index 33b5702a00..46f92f7cd2 100644 --- a/services/issue/comments.go +++ b/services/issue/comments.go @@ -12,7 +12,10 @@ import ( access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/timeutil" + git_service "code.gitea.io/gitea/services/git" notify_service "code.gitea.io/gitea/services/notify" ) @@ -139,3 +142,40 @@ func DeleteComment(ctx context.Context, doer *user_model.User, comment *issues_m return nil } + +// LoadCommentPushCommits Load push commits +func LoadCommentPushCommits(ctx context.Context, c *issues_model.Comment) (err error) { + if c.Content == "" || c.Commits != nil || c.Type != issues_model.CommentTypePullRequestPush { + return nil + } + + var data issues_model.PushActionContent + err = json.Unmarshal([]byte(c.Content), &data) + if err != nil { + return err + } + + c.IsForcePush = data.IsForcePush + + if c.IsForcePush { + if len(data.CommitIDs) != 2 { + return nil + } + c.OldCommit = data.CommitIDs[0] + c.NewCommit = data.CommitIDs[1] + } else { + gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, c.Issue.Repo) + if err != nil { + return err + } + defer closer.Close() + + c.Commits, err = git_service.ConvertFromGitCommit(ctx, gitRepo.GetCommitsFromIDs(data.CommitIDs), c.Issue.Repo) + if err != nil { + return err + } + c.CommitsNum = int64(len(c.Commits)) + } + + return err +} diff --git a/services/issue/issue.go b/services/issue/issue.go index 091b7c02d7..586b6031c8 100644 --- a/services/issue/issue.go +++ b/services/issue/issue.go @@ -197,13 +197,6 @@ func DeleteIssue(ctx context.Context, doer *user_model.User, gitRepo *git.Reposi } } - // If the Issue is pinned, we should unpin it before deletion to avoid problems with other pinned Issues - if issue.IsPinned() { - if err := issue.Unpin(ctx, doer); err != nil { - return err - } - } - notify_service.DeleteIssue(ctx, doer, issue) return nil @@ -319,6 +312,7 @@ func deleteIssue(ctx context.Context, issue *issues_model.Issue) error { &issues_model.Comment{RefIssueID: issue.ID}, &issues_model.IssueDependency{DependencyID: issue.ID}, &issues_model.Comment{DependentIssueID: issue.ID}, + &issues_model.IssuePin{IssueID: issue.ID}, ); err != nil { return err } diff --git a/services/issue/suggestion.go b/services/issue/suggestion.go new file mode 100644 index 0000000000..22eddb1904 --- /dev/null +++ b/services/issue/suggestion.go @@ -0,0 +1,73 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package issue + +import ( + "context" + "strconv" + + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/optional" + "code.gitea.io/gitea/modules/structs" +) + +func GetSuggestion(ctx context.Context, repo *repo_model.Repository, isPull optional.Option[bool], keyword string) ([]*structs.Issue, error) { + var issues issues_model.IssueList + var err error + pageSize := 5 + if keyword == "" { + issues, err = issues_model.FindLatestUpdatedIssues(ctx, repo.ID, isPull, pageSize) + if err != nil { + return nil, err + } + } else { + indexKeyword, _ := strconv.ParseInt(keyword, 10, 64) + var issueByIndex *issues_model.Issue + var excludedID int64 + if indexKeyword > 0 { + issueByIndex, err = issues_model.GetIssueByIndex(ctx, repo.ID, indexKeyword) + if err != nil && !issues_model.IsErrIssueNotExist(err) { + return nil, err + } + if issueByIndex != nil { + excludedID = issueByIndex.ID + pageSize-- + } + } + + issues, err = issues_model.FindIssuesSuggestionByKeyword(ctx, repo.ID, keyword, isPull, excludedID, pageSize) + if err != nil { + return nil, err + } + + if issueByIndex != nil { + issues = append([]*issues_model.Issue{issueByIndex}, issues...) + } + } + + if err := issues.LoadPullRequests(ctx); err != nil { + return nil, err + } + + suggestions := make([]*structs.Issue, 0, len(issues)) + for _, issue := range issues { + suggestion := &structs.Issue{ + ID: issue.ID, + Index: issue.Index, + Title: issue.Title, + State: issue.State(), + } + + if issue.IsPull && issue.PullRequest != nil { + suggestion.PullRequest = &structs.PullRequestMeta{ + HasMerged: issue.PullRequest.HasMerged, + IsWorkInProgress: issue.PullRequest.IsWorkInProgress(ctx), + } + } + suggestions = append(suggestions, suggestion) + } + + return suggestions, nil +} diff --git a/services/issue/suggestion_test.go b/services/issue/suggestion_test.go new file mode 100644 index 0000000000..84cfd520ac --- /dev/null +++ b/services/issue/suggestion_test.go @@ -0,0 +1,57 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package issue + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/optional" + + "github.com/stretchr/testify/assert" +) + +func Test_Suggestion(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + testCases := []struct { + keyword string + isPull optional.Option[bool] + expectedIndexes []int64 + }{ + { + keyword: "", + expectedIndexes: []int64{5, 1, 4, 2, 3}, + }, + { + keyword: "1", + expectedIndexes: []int64{1}, + }, + { + keyword: "issue", + expectedIndexes: []int64{4, 1, 2, 3}, + }, + { + keyword: "pull", + expectedIndexes: []int64{5}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.keyword, func(t *testing.T) { + issues, err := GetSuggestion(db.DefaultContext, repo1, testCase.isPull, testCase.keyword) + assert.NoError(t, err) + + issueIndexes := make([]int64, 0, len(issues)) + for _, issue := range issues { + issueIndexes = append(issueIndexes, issue.Index) + } + assert.EqualValues(t, testCase.expectedIndexes, issueIndexes) + }) + } +} diff --git a/services/mailer/notify.go b/services/mailer/notify.go index e48b5d399d..a27177e8f5 100644 --- a/services/mailer/notify.go +++ b/services/mailer/notify.go @@ -12,6 +12,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" + issue_service "code.gitea.io/gitea/services/issue" notify_service "code.gitea.io/gitea/services/notify" ) @@ -169,7 +170,7 @@ func (m *mailNotifier) PullRequestPushCommits(ctx context.Context, doer *user_mo log.Error("comment.Issue.PullRequest.LoadBaseRepo: %v", err) return } - if err := comment.LoadPushCommits(ctx); err != nil { + if err := issue_service.LoadCommentPushCommits(ctx, comment); err != nil { log.Error("comment.LoadPushCommits: %v", err) } m.CreateIssueComment(ctx, doer, comment.Issue.Repo, comment.Issue, comment, nil) diff --git a/services/markup/renderhelper.go b/services/markup/renderhelper.go index 4b9852b48b..ea494146a7 100644 --- a/services/markup/renderhelper.go +++ b/services/markup/renderhelper.go @@ -21,8 +21,8 @@ func FormalRenderHelperFuncs() *markup.RenderHelperFuncs { return false } - giteaCtx, ok := ctx.(*gitea_context.Context) - if !ok { + giteaCtx := gitea_context.GetWebContext(ctx) + if giteaCtx == nil { // when using general context, use user's visibility to check return mentionedUser.Visibility.IsPublic() } diff --git a/services/markup/renderhelper_codepreview.go b/services/markup/renderhelper_codepreview.go index 170c70c409..d638af7ff0 100644 --- a/services/markup/renderhelper_codepreview.go +++ b/services/markup/renderhelper_codepreview.go @@ -36,8 +36,8 @@ func renderRepoFileCodePreview(ctx context.Context, opts markup.RenderCodePrevie return "", err } - webCtx, ok := ctx.Value(gitea_context.WebContextKey).(*gitea_context.Context) - if !ok { + webCtx := gitea_context.GetWebContext(ctx) + if webCtx == nil { return "", fmt.Errorf("context is not a web context") } doer := webCtx.Doer diff --git a/services/markup/renderhelper_issueicontitle.go b/services/markup/renderhelper_issueicontitle.go index 53a508e908..fd8f9d43fa 100644 --- a/services/markup/renderhelper_issueicontitle.go +++ b/services/markup/renderhelper_issueicontitle.go @@ -18,8 +18,8 @@ import ( ) func renderRepoIssueIconTitle(ctx context.Context, opts markup.RenderIssueIconTitleOptions) (_ template.HTML, err error) { - webCtx, ok := ctx.Value(gitea_context.WebContextKey).(*gitea_context.Context) - if !ok { + webCtx := gitea_context.GetWebContext(ctx) + if webCtx == nil { return "", fmt.Errorf("context is not a web context") } diff --git a/services/migrations/codebase.go b/services/migrations/codebase.go index 492fc908e9..880dd21497 100644 --- a/services/migrations/codebase.go +++ b/services/migrations/codebase.go @@ -66,7 +66,6 @@ type codebaseUser struct { // from Codebase type CodebaseDownloader struct { base.NullDownloader - ctx context.Context client *http.Client baseURL *url.URL projectURL *url.URL @@ -77,17 +76,11 @@ type CodebaseDownloader struct { commitMap map[string]string } -// SetContext set context -func (d *CodebaseDownloader) SetContext(ctx context.Context) { - d.ctx = ctx -} - // NewCodebaseDownloader creates a new downloader -func NewCodebaseDownloader(ctx context.Context, projectURL *url.URL, project, repoName, username, password string) *CodebaseDownloader { +func NewCodebaseDownloader(_ context.Context, projectURL *url.URL, project, repoName, username, password string) *CodebaseDownloader { baseURL, _ := url.Parse("https://api3.codebasehq.com") downloader := &CodebaseDownloader{ - ctx: ctx, baseURL: baseURL, projectURL: projectURL, project: project, @@ -127,7 +120,7 @@ func (d *CodebaseDownloader) FormatCloneURL(opts base.MigrateOptions, remoteAddr return opts.CloneAddr, nil } -func (d *CodebaseDownloader) callAPI(endpoint string, parameter map[string]string, result any) error { +func (d *CodebaseDownloader) callAPI(ctx context.Context, endpoint string, parameter map[string]string, result any) error { u, err := d.baseURL.Parse(endpoint) if err != nil { return err @@ -141,7 +134,7 @@ func (d *CodebaseDownloader) callAPI(endpoint string, parameter map[string]strin u.RawQuery = query.Encode() } - req, err := http.NewRequestWithContext(d.ctx, "GET", u.String(), nil) + req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) if err != nil { return err } @@ -158,7 +151,7 @@ func (d *CodebaseDownloader) callAPI(endpoint string, parameter map[string]strin // GetRepoInfo returns repository information // https://support.codebasehq.com/kb/projects -func (d *CodebaseDownloader) GetRepoInfo() (*base.Repository, error) { +func (d *CodebaseDownloader) GetRepoInfo(ctx context.Context) (*base.Repository, error) { var rawRepository struct { XMLName xml.Name `xml:"repository"` Name string `xml:"name"` @@ -169,6 +162,7 @@ func (d *CodebaseDownloader) GetRepoInfo() (*base.Repository, error) { } err := d.callAPI( + ctx, fmt.Sprintf("/%s/%s", d.project, d.repoName), nil, &rawRepository, @@ -187,7 +181,7 @@ func (d *CodebaseDownloader) GetRepoInfo() (*base.Repository, error) { // GetMilestones returns milestones // https://support.codebasehq.com/kb/tickets-and-milestones/milestones -func (d *CodebaseDownloader) GetMilestones() ([]*base.Milestone, error) { +func (d *CodebaseDownloader) GetMilestones(ctx context.Context) ([]*base.Milestone, error) { var rawMilestones struct { XMLName xml.Name `xml:"ticketing-milestone"` Type string `xml:"type,attr"` @@ -209,6 +203,7 @@ func (d *CodebaseDownloader) GetMilestones() ([]*base.Milestone, error) { } err := d.callAPI( + ctx, fmt.Sprintf("/%s/milestones", d.project), nil, &rawMilestones, @@ -245,7 +240,7 @@ func (d *CodebaseDownloader) GetMilestones() ([]*base.Milestone, error) { // GetLabels returns labels // https://support.codebasehq.com/kb/tickets-and-milestones/statuses-priorities-and-categories -func (d *CodebaseDownloader) GetLabels() ([]*base.Label, error) { +func (d *CodebaseDownloader) GetLabels(ctx context.Context) ([]*base.Label, error) { var rawTypes struct { XMLName xml.Name `xml:"ticketing-types"` Type string `xml:"type,attr"` @@ -259,6 +254,7 @@ func (d *CodebaseDownloader) GetLabels() ([]*base.Label, error) { } err := d.callAPI( + ctx, fmt.Sprintf("/%s/tickets/types", d.project), nil, &rawTypes, @@ -284,7 +280,7 @@ type codebaseIssueContext struct { // GetIssues returns issues, limits are not supported // https://support.codebasehq.com/kb/tickets-and-milestones // https://support.codebasehq.com/kb/tickets-and-milestones/updating-tickets -func (d *CodebaseDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) { +func (d *CodebaseDownloader) GetIssues(ctx context.Context, _, _ int) ([]*base.Issue, bool, error) { var rawIssues struct { XMLName xml.Name `xml:"tickets"` Type string `xml:"type,attr"` @@ -324,6 +320,7 @@ func (d *CodebaseDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, } err := d.callAPI( + ctx, fmt.Sprintf("/%s/tickets", d.project), nil, &rawIssues, @@ -358,6 +355,7 @@ func (d *CodebaseDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, } `xml:"ticket-note"` } err := d.callAPI( + ctx, fmt.Sprintf("/%s/tickets/%d/notes", d.project, issue.TicketID.Value), nil, ¬es, @@ -370,7 +368,7 @@ func (d *CodebaseDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, if len(note.Content) == 0 { continue } - poster := d.tryGetUser(note.UserID.Value) + poster := d.tryGetUser(ctx, note.UserID.Value) comments = append(comments, &base.Comment{ IssueIndex: issue.TicketID.Value, Index: note.ID.Value, @@ -390,7 +388,7 @@ func (d *CodebaseDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, if issue.Status.TreatAsClosed.Value { state = "closed" } - poster := d.tryGetUser(issue.ReporterID.Value) + poster := d.tryGetUser(ctx, issue.ReporterID.Value) issues = append(issues, &base.Issue{ Title: issue.Summary, Number: issue.TicketID.Value, @@ -419,7 +417,7 @@ func (d *CodebaseDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, } // GetComments returns comments -func (d *CodebaseDownloader) GetComments(commentable base.Commentable) ([]*base.Comment, bool, error) { +func (d *CodebaseDownloader) GetComments(_ context.Context, commentable base.Commentable) ([]*base.Comment, bool, error) { context, ok := commentable.GetContext().(codebaseIssueContext) if !ok { return nil, false, fmt.Errorf("unexpected context: %+v", commentable.GetContext()) @@ -430,7 +428,7 @@ func (d *CodebaseDownloader) GetComments(commentable base.Commentable) ([]*base. // GetPullRequests returns pull requests // https://support.codebasehq.com/kb/repositories/merge-requests -func (d *CodebaseDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) { +func (d *CodebaseDownloader) GetPullRequests(ctx context.Context, page, perPage int) ([]*base.PullRequest, bool, error) { var rawMergeRequests struct { XMLName xml.Name `xml:"merge-requests"` Type string `xml:"type,attr"` @@ -443,6 +441,7 @@ func (d *CodebaseDownloader) GetPullRequests(page, perPage int) ([]*base.PullReq } err := d.callAPI( + ctx, fmt.Sprintf("/%s/%s/merge_requests", d.project, d.repoName), map[string]string{ "query": `"Target Project" is "` + d.repoName + `"`, @@ -503,6 +502,7 @@ func (d *CodebaseDownloader) GetPullRequests(page, perPage int) ([]*base.PullReq } `xml:"comments"` } err := d.callAPI( + ctx, fmt.Sprintf("/%s/%s/merge_requests/%d", d.project, d.repoName, mr.ID.Value), nil, &rawMergeRequest, @@ -531,7 +531,7 @@ func (d *CodebaseDownloader) GetPullRequests(page, perPage int) ([]*base.PullReq } continue } - poster := d.tryGetUser(comment.UserID.Value) + poster := d.tryGetUser(ctx, comment.UserID.Value) comments = append(comments, &base.Comment{ IssueIndex: number, Index: comment.ID.Value, @@ -547,7 +547,7 @@ func (d *CodebaseDownloader) GetPullRequests(page, perPage int) ([]*base.PullReq comments = append(comments, &base.Comment{}) } - poster := d.tryGetUser(rawMergeRequest.UserID.Value) + poster := d.tryGetUser(ctx, rawMergeRequest.UserID.Value) pullRequests = append(pullRequests, &base.PullRequest{ Title: rawMergeRequest.Subject, @@ -563,12 +563,12 @@ func (d *CodebaseDownloader) GetPullRequests(page, perPage int) ([]*base.PullReq MergedTime: mergedTime, Head: base.PullRequestBranch{ Ref: rawMergeRequest.SourceRef, - SHA: d.getHeadCommit(rawMergeRequest.SourceRef), + SHA: d.getHeadCommit(ctx, rawMergeRequest.SourceRef), RepoName: d.repoName, }, Base: base.PullRequestBranch{ Ref: rawMergeRequest.TargetRef, - SHA: d.getHeadCommit(rawMergeRequest.TargetRef), + SHA: d.getHeadCommit(ctx, rawMergeRequest.TargetRef), RepoName: d.repoName, }, ForeignIndex: rawMergeRequest.ID.Value, @@ -584,7 +584,7 @@ func (d *CodebaseDownloader) GetPullRequests(page, perPage int) ([]*base.PullReq return pullRequests, true, nil } -func (d *CodebaseDownloader) tryGetUser(userID int64) *codebaseUser { +func (d *CodebaseDownloader) tryGetUser(ctx context.Context, userID int64) *codebaseUser { if len(d.userMap) == 0 { var rawUsers struct { XMLName xml.Name `xml:"users"` @@ -602,6 +602,7 @@ func (d *CodebaseDownloader) tryGetUser(userID int64) *codebaseUser { } err := d.callAPI( + ctx, "/users", nil, &rawUsers, @@ -627,7 +628,7 @@ func (d *CodebaseDownloader) tryGetUser(userID int64) *codebaseUser { return user } -func (d *CodebaseDownloader) getHeadCommit(ref string) string { +func (d *CodebaseDownloader) getHeadCommit(ctx context.Context, ref string) string { commitRef, ok := d.commitMap[ref] if !ok { var rawCommits struct { @@ -638,6 +639,7 @@ func (d *CodebaseDownloader) getHeadCommit(ref string) string { } `xml:"commit"` } err := d.callAPI( + ctx, fmt.Sprintf("/%s/%s/commits/%s", d.project, d.repoName, ref), nil, &rawCommits, diff --git a/services/migrations/codebase_test.go b/services/migrations/codebase_test.go index 68721e0641..ec4da1bff5 100644 --- a/services/migrations/codebase_test.go +++ b/services/migrations/codebase_test.go @@ -30,9 +30,9 @@ func TestCodebaseDownloadRepo(t *testing.T) { if cloneUser != "" { u.User = url.UserPassword(cloneUser, clonePassword) } - + ctx := context.Background() factory := &CodebaseDownloaderFactory{} - downloader, err := factory.New(context.Background(), base.MigrateOptions{ + downloader, err := factory.New(ctx, base.MigrateOptions{ CloneAddr: u.String(), AuthUsername: apiUser, AuthPassword: apiPassword, @@ -40,7 +40,7 @@ func TestCodebaseDownloadRepo(t *testing.T) { if err != nil { t.Fatalf("Error creating Codebase downloader: %v", err) } - repo, err := downloader.GetRepoInfo() + repo, err := downloader.GetRepoInfo(ctx) assert.NoError(t, err) assertRepositoryEqual(t, &base.Repository{ Name: "test", @@ -50,7 +50,7 @@ func TestCodebaseDownloadRepo(t *testing.T) { OriginalURL: cloneAddr, }, repo) - milestones, err := downloader.GetMilestones() + milestones, err := downloader.GetMilestones(ctx) assert.NoError(t, err) assertMilestonesEqual(t, []*base.Milestone{ { @@ -65,11 +65,11 @@ func TestCodebaseDownloadRepo(t *testing.T) { }, }, milestones) - labels, err := downloader.GetLabels() + labels, err := downloader.GetLabels(ctx) assert.NoError(t, err) assert.Len(t, labels, 4) - issues, isEnd, err := downloader.GetIssues(1, 2) + issues, isEnd, err := downloader.GetIssues(ctx, 1, 2) assert.NoError(t, err) assert.True(t, isEnd) assertIssuesEqual(t, []*base.Issue{ @@ -106,7 +106,7 @@ func TestCodebaseDownloadRepo(t *testing.T) { }, }, issues) - comments, _, err := downloader.GetComments(issues[0]) + comments, _, err := downloader.GetComments(ctx, issues[0]) assert.NoError(t, err) assertCommentsEqual(t, []*base.Comment{ { @@ -119,7 +119,7 @@ func TestCodebaseDownloadRepo(t *testing.T) { }, }, comments) - prs, _, err := downloader.GetPullRequests(1, 1) + prs, _, err := downloader.GetPullRequests(ctx, 1, 1) assert.NoError(t, err) assertPullRequestsEqual(t, []*base.PullRequest{ { @@ -144,7 +144,7 @@ func TestCodebaseDownloadRepo(t *testing.T) { }, }, prs) - rvs, err := downloader.GetReviews(prs[0]) + rvs, err := downloader.GetReviews(ctx, prs[0]) assert.NoError(t, err) assert.Empty(t, rvs) } diff --git a/services/migrations/codecommit.go b/services/migrations/codecommit.go index fead527f5b..c45f9e5943 100644 --- a/services/migrations/codecommit.go +++ b/services/migrations/codecommit.go @@ -62,9 +62,8 @@ func (c *CodeCommitDownloaderFactory) GitServiceType() structs.GitServiceType { return structs.CodeCommitService } -func NewCodeCommitDownloader(ctx context.Context, repoName, baseURL, accessKeyID, secretAccessKey, region string) *CodeCommitDownloader { +func NewCodeCommitDownloader(_ context.Context, repoName, baseURL, accessKeyID, secretAccessKey, region string) *CodeCommitDownloader { downloader := CodeCommitDownloader{ - ctx: ctx, repoName: repoName, baseURL: baseURL, codeCommitClient: codecommit.New(codecommit.Options{ @@ -79,21 +78,15 @@ func NewCodeCommitDownloader(ctx context.Context, repoName, baseURL, accessKeyID // CodeCommitDownloader implements a downloader for AWS CodeCommit type CodeCommitDownloader struct { base.NullDownloader - ctx context.Context codeCommitClient *codecommit.Client repoName string baseURL string allPullRequestIDs []string } -// SetContext set context -func (c *CodeCommitDownloader) SetContext(ctx context.Context) { - c.ctx = ctx -} - // GetRepoInfo returns a repository information -func (c *CodeCommitDownloader) GetRepoInfo() (*base.Repository, error) { - output, err := c.codeCommitClient.GetRepository(c.ctx, &codecommit.GetRepositoryInput{ +func (c *CodeCommitDownloader) GetRepoInfo(ctx context.Context) (*base.Repository, error) { + output, err := c.codeCommitClient.GetRepository(ctx, &codecommit.GetRepositoryInput{ RepositoryName: util.ToPointer(c.repoName), }) if err != nil { @@ -117,14 +110,14 @@ func (c *CodeCommitDownloader) GetRepoInfo() (*base.Repository, error) { } // GetComments returns comments of an issue or PR -func (c *CodeCommitDownloader) GetComments(commentable base.Commentable) ([]*base.Comment, bool, error) { +func (c *CodeCommitDownloader) GetComments(ctx context.Context, commentable base.Commentable) ([]*base.Comment, bool, error) { var ( nextToken *string comments []*base.Comment ) for { - resp, err := c.codeCommitClient.GetCommentsForPullRequest(c.ctx, &codecommit.GetCommentsForPullRequestInput{ + resp, err := c.codeCommitClient.GetCommentsForPullRequest(ctx, &codecommit.GetCommentsForPullRequestInput{ NextToken: nextToken, PullRequestId: util.ToPointer(strconv.FormatInt(commentable.GetForeignIndex(), 10)), }) @@ -155,8 +148,8 @@ func (c *CodeCommitDownloader) GetComments(commentable base.Commentable) ([]*bas } // GetPullRequests returns pull requests according page and perPage -func (c *CodeCommitDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) { - allPullRequestIDs, err := c.getAllPullRequestIDs() +func (c *CodeCommitDownloader) GetPullRequests(ctx context.Context, page, perPage int) ([]*base.PullRequest, bool, error) { + allPullRequestIDs, err := c.getAllPullRequestIDs(ctx) if err != nil { return nil, false, err } @@ -170,7 +163,7 @@ func (c *CodeCommitDownloader) GetPullRequests(page, perPage int) ([]*base.PullR prs := make([]*base.PullRequest, 0, len(batch)) for _, id := range batch { - output, err := c.codeCommitClient.GetPullRequest(c.ctx, &codecommit.GetPullRequestInput{ + output, err := c.codeCommitClient.GetPullRequest(ctx, &codecommit.GetPullRequestInput{ PullRequestId: util.ToPointer(id), }) if err != nil { @@ -231,7 +224,7 @@ func (c *CodeCommitDownloader) FormatCloneURL(opts MigrateOptions, remoteAddr st return u.String(), nil } -func (c *CodeCommitDownloader) getAllPullRequestIDs() ([]string, error) { +func (c *CodeCommitDownloader) getAllPullRequestIDs(ctx context.Context) ([]string, error) { if len(c.allPullRequestIDs) > 0 { return c.allPullRequestIDs, nil } @@ -242,7 +235,7 @@ func (c *CodeCommitDownloader) getAllPullRequestIDs() ([]string, error) { ) for { - output, err := c.codeCommitClient.ListPullRequests(c.ctx, &codecommit.ListPullRequestsInput{ + output, err := c.codeCommitClient.ListPullRequests(ctx, &codecommit.ListPullRequestsInput{ RepositoryName: util.ToPointer(c.repoName), NextToken: nextToken, }) diff --git a/services/migrations/dump.go b/services/migrations/dump.go index 07812002af..11efc18163 100644 --- a/services/migrations/dump.go +++ b/services/migrations/dump.go @@ -32,7 +32,6 @@ var _ base.Uploader = &RepositoryDumper{} // RepositoryDumper implements an Uploader to the local directory type RepositoryDumper struct { - ctx context.Context baseDir string repoOwner string repoName string @@ -56,7 +55,6 @@ func NewRepositoryDumper(ctx context.Context, baseDir, repoOwner, repoName strin return nil, err } return &RepositoryDumper{ - ctx: ctx, opts: opts, baseDir: baseDir, repoOwner: repoOwner, @@ -105,7 +103,7 @@ func (g *RepositoryDumper) setURLToken(remoteAddr string) (string, error) { } // CreateRepo creates a repository -func (g *RepositoryDumper) CreateRepo(repo *base.Repository, opts base.MigrateOptions) error { +func (g *RepositoryDumper) CreateRepo(ctx context.Context, repo *base.Repository, opts base.MigrateOptions) error { f, err := os.Create(filepath.Join(g.baseDir, "repo.yml")) if err != nil { return err @@ -149,7 +147,7 @@ func (g *RepositoryDumper) CreateRepo(repo *base.Repository, opts base.MigrateOp return err } - err = git.Clone(g.ctx, remoteAddr, repoPath, git.CloneRepoOptions{ + err = git.Clone(ctx, remoteAddr, repoPath, git.CloneRepoOptions{ Mirror: true, Quiet: true, Timeout: migrateTimeout, @@ -158,19 +156,19 @@ func (g *RepositoryDumper) CreateRepo(repo *base.Repository, opts base.MigrateOp if err != nil { return fmt.Errorf("Clone: %w", err) } - if err := git.WriteCommitGraph(g.ctx, repoPath); err != nil { + if err := git.WriteCommitGraph(ctx, repoPath); err != nil { return err } if opts.Wiki { wikiPath := g.wikiPath() - wikiRemotePath := repository.WikiRemoteURL(g.ctx, remoteAddr) + wikiRemotePath := repository.WikiRemoteURL(ctx, remoteAddr) if len(wikiRemotePath) > 0 { if err := os.MkdirAll(wikiPath, os.ModePerm); err != nil { return fmt.Errorf("Failed to remove %s: %w", wikiPath, err) } - if err := git.Clone(g.ctx, wikiRemotePath, wikiPath, git.CloneRepoOptions{ + if err := git.Clone(ctx, wikiRemotePath, wikiPath, git.CloneRepoOptions{ Mirror: true, Quiet: true, Timeout: migrateTimeout, @@ -181,13 +179,13 @@ func (g *RepositoryDumper) CreateRepo(repo *base.Repository, opts base.MigrateOp if err := os.RemoveAll(wikiPath); err != nil { return fmt.Errorf("Failed to remove %s: %w", wikiPath, err) } - } else if err := git.WriteCommitGraph(g.ctx, wikiPath); err != nil { + } else if err := git.WriteCommitGraph(ctx, wikiPath); err != nil { return err } } } - g.gitRepo, err = git.OpenRepository(g.ctx, g.gitPath()) + g.gitRepo, err = git.OpenRepository(ctx, g.gitPath()) return err } @@ -220,7 +218,7 @@ func (g *RepositoryDumper) Close() { } // CreateTopics creates topics -func (g *RepositoryDumper) CreateTopics(topics ...string) error { +func (g *RepositoryDumper) CreateTopics(_ context.Context, topics ...string) error { f, err := os.Create(filepath.Join(g.baseDir, "topic.yml")) if err != nil { return err @@ -242,7 +240,7 @@ func (g *RepositoryDumper) CreateTopics(topics ...string) error { } // CreateMilestones creates milestones -func (g *RepositoryDumper) CreateMilestones(milestones ...*base.Milestone) error { +func (g *RepositoryDumper) CreateMilestones(_ context.Context, milestones ...*base.Milestone) error { var err error if g.milestoneFile == nil { g.milestoneFile, err = os.Create(filepath.Join(g.baseDir, "milestone.yml")) @@ -264,7 +262,7 @@ func (g *RepositoryDumper) CreateMilestones(milestones ...*base.Milestone) error } // CreateLabels creates labels -func (g *RepositoryDumper) CreateLabels(labels ...*base.Label) error { +func (g *RepositoryDumper) CreateLabels(_ context.Context, labels ...*base.Label) error { var err error if g.labelFile == nil { g.labelFile, err = os.Create(filepath.Join(g.baseDir, "label.yml")) @@ -286,7 +284,7 @@ func (g *RepositoryDumper) CreateLabels(labels ...*base.Label) error { } // CreateReleases creates releases -func (g *RepositoryDumper) CreateReleases(releases ...*base.Release) error { +func (g *RepositoryDumper) CreateReleases(_ context.Context, releases ...*base.Release) error { if g.opts.ReleaseAssets { for _, release := range releases { attachDir := filepath.Join("release_assets", release.TagName) @@ -354,12 +352,12 @@ func (g *RepositoryDumper) CreateReleases(releases ...*base.Release) error { } // SyncTags syncs releases with tags in the database -func (g *RepositoryDumper) SyncTags() error { +func (g *RepositoryDumper) SyncTags(ctx context.Context) error { return nil } // CreateIssues creates issues -func (g *RepositoryDumper) CreateIssues(issues ...*base.Issue) error { +func (g *RepositoryDumper) CreateIssues(_ context.Context, issues ...*base.Issue) error { var err error if g.issueFile == nil { g.issueFile, err = os.Create(filepath.Join(g.baseDir, "issue.yml")) @@ -412,7 +410,7 @@ func (g *RepositoryDumper) encodeItems(number int64, items []any, dir string, it } // CreateComments creates comments of issues -func (g *RepositoryDumper) CreateComments(comments ...*base.Comment) error { +func (g *RepositoryDumper) CreateComments(_ context.Context, comments ...*base.Comment) error { commentsMap := make(map[int64][]any, len(comments)) for _, comment := range comments { commentsMap[comment.IssueIndex] = append(commentsMap[comment.IssueIndex], comment) @@ -421,7 +419,7 @@ func (g *RepositoryDumper) CreateComments(comments ...*base.Comment) error { return g.createItems(g.commentDir(), g.commentFiles, commentsMap) } -func (g *RepositoryDumper) handlePullRequest(pr *base.PullRequest) error { +func (g *RepositoryDumper) handlePullRequest(ctx context.Context, pr *base.PullRequest) error { // SECURITY: this pr must have been ensured safe if !pr.EnsuredSafe { log.Error("PR #%d in %s/%s has not been checked for safety ... We will ignore this.", pr.Number, g.repoOwner, g.repoName) @@ -490,7 +488,7 @@ func (g *RepositoryDumper) handlePullRequest(pr *base.PullRequest) error { if pr.Head.CloneURL == "" || pr.Head.Ref == "" { // Set head information if pr.Head.SHA is available if pr.Head.SHA != "" { - _, _, err = git.NewCommand(g.ctx, "update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.gitPath()}) + _, _, err = git.NewCommand(ctx, "update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.gitPath()}) if err != nil { log.Error("PR #%d in %s/%s unable to update-ref for pr HEAD: %v", pr.Number, g.repoOwner, g.repoName, err) } @@ -520,7 +518,7 @@ func (g *RepositoryDumper) handlePullRequest(pr *base.PullRequest) error { if !ok { // Set head information if pr.Head.SHA is available if pr.Head.SHA != "" { - _, _, err = git.NewCommand(g.ctx, "update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.gitPath()}) + _, _, err = git.NewCommand(ctx, "update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.gitPath()}) if err != nil { log.Error("PR #%d in %s/%s unable to update-ref for pr HEAD: %v", pr.Number, g.repoOwner, g.repoName, err) } @@ -555,7 +553,7 @@ func (g *RepositoryDumper) handlePullRequest(pr *base.PullRequest) error { fetchArg = git.BranchPrefix + fetchArg } - _, _, err = git.NewCommand(g.ctx, "fetch", "--no-tags").AddDashesAndList(remote, fetchArg).RunStdString(&git.RunOpts{Dir: g.gitPath()}) + _, _, err = git.NewCommand(ctx, "fetch", "--no-tags").AddDashesAndList(remote, fetchArg).RunStdString(&git.RunOpts{Dir: g.gitPath()}) if err != nil { log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err) // We need to continue here so that the Head.Ref is reset and we attempt to set the gitref for the PR @@ -579,7 +577,7 @@ func (g *RepositoryDumper) handlePullRequest(pr *base.PullRequest) error { pr.Head.SHA = headSha } if pr.Head.SHA != "" { - _, _, err = git.NewCommand(g.ctx, "update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.gitPath()}) + _, _, err = git.NewCommand(ctx, "update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.gitPath()}) if err != nil { log.Error("unable to set %s as the local head for PR #%d from %s in %s/%s. Error: %v", pr.Head.SHA, pr.Number, pr.Head.Ref, g.repoOwner, g.repoName, err) } @@ -589,7 +587,7 @@ func (g *RepositoryDumper) handlePullRequest(pr *base.PullRequest) error { } // CreatePullRequests creates pull requests -func (g *RepositoryDumper) CreatePullRequests(prs ...*base.PullRequest) error { +func (g *RepositoryDumper) CreatePullRequests(ctx context.Context, prs ...*base.PullRequest) error { var err error if g.pullrequestFile == nil { if err := os.MkdirAll(g.baseDir, os.ModePerm); err != nil { @@ -607,7 +605,7 @@ func (g *RepositoryDumper) CreatePullRequests(prs ...*base.PullRequest) error { count := 0 for i := 0; i < len(prs); i++ { pr := prs[i] - if err := g.handlePullRequest(pr); err != nil { + if err := g.handlePullRequest(ctx, pr); err != nil { log.Error("PR #%d in %s/%s failed - skipping", pr.Number, g.repoOwner, g.repoName, err) continue } @@ -620,7 +618,7 @@ func (g *RepositoryDumper) CreatePullRequests(prs ...*base.PullRequest) error { } // CreateReviews create pull request reviews -func (g *RepositoryDumper) CreateReviews(reviews ...*base.Review) error { +func (g *RepositoryDumper) CreateReviews(_ context.Context, reviews ...*base.Review) error { reviewsMap := make(map[int64][]any, len(reviews)) for _, review := range reviews { reviewsMap[review.IssueIndex] = append(reviewsMap[review.IssueIndex], review) @@ -636,7 +634,7 @@ func (g *RepositoryDumper) Rollback() error { } // Finish when migrating succeed, this will update something. -func (g *RepositoryDumper) Finish() error { +func (g *RepositoryDumper) Finish(_ context.Context) error { return nil } diff --git a/services/migrations/git.go b/services/migrations/git.go index 22ffd5e765..1ed99499a1 100644 --- a/services/migrations/git.go +++ b/services/migrations/git.go @@ -28,12 +28,8 @@ func NewPlainGitDownloader(ownerName, repoName, remoteURL string) *PlainGitDownl } } -// SetContext set context -func (g *PlainGitDownloader) SetContext(ctx context.Context) { -} - // GetRepoInfo returns a repository information -func (g *PlainGitDownloader) GetRepoInfo() (*base.Repository, error) { +func (g *PlainGitDownloader) GetRepoInfo(_ context.Context) (*base.Repository, error) { // convert github repo to stand Repo return &base.Repository{ Owner: g.ownerName, @@ -43,6 +39,6 @@ func (g *PlainGitDownloader) GetRepoInfo() (*base.Repository, error) { } // GetTopics return empty string slice -func (g PlainGitDownloader) GetTopics() ([]string, error) { +func (g PlainGitDownloader) GetTopics(_ context.Context) ([]string, error) { return []string{}, nil } diff --git a/services/migrations/gitea_downloader.go b/services/migrations/gitea_downloader.go index 272bf02e11..f92f318293 100644 --- a/services/migrations/gitea_downloader.go +++ b/services/migrations/gitea_downloader.go @@ -67,7 +67,6 @@ func (f *GiteaDownloaderFactory) GitServiceType() structs.GitServiceType { // GiteaDownloader implements a Downloader interface to get repository information's type GiteaDownloader struct { base.NullDownloader - ctx context.Context client *gitea_sdk.Client baseURL string repoOwner string @@ -114,7 +113,6 @@ func NewGiteaDownloader(ctx context.Context, baseURL, repoPath, username, passwo } return &GiteaDownloader{ - ctx: ctx, client: giteaClient, baseURL: baseURL, repoOwner: path[0], @@ -124,11 +122,6 @@ func NewGiteaDownloader(ctx context.Context, baseURL, repoPath, username, passwo }, nil } -// SetContext set context -func (g *GiteaDownloader) SetContext(ctx context.Context) { - g.ctx = ctx -} - // String implements Stringer func (g *GiteaDownloader) String() string { return fmt.Sprintf("migration from gitea server %s %s/%s", g.baseURL, g.repoOwner, g.repoName) @@ -142,7 +135,7 @@ func (g *GiteaDownloader) LogString() string { } // GetRepoInfo returns a repository information -func (g *GiteaDownloader) GetRepoInfo() (*base.Repository, error) { +func (g *GiteaDownloader) GetRepoInfo(_ context.Context) (*base.Repository, error) { if g == nil { return nil, errors.New("error: GiteaDownloader is nil") } @@ -164,19 +157,19 @@ func (g *GiteaDownloader) GetRepoInfo() (*base.Repository, error) { } // GetTopics return gitea topics -func (g *GiteaDownloader) GetTopics() ([]string, error) { +func (g *GiteaDownloader) GetTopics(_ context.Context) ([]string, error) { topics, _, err := g.client.ListRepoTopics(g.repoOwner, g.repoName, gitea_sdk.ListRepoTopicsOptions{}) return topics, err } // GetMilestones returns milestones -func (g *GiteaDownloader) GetMilestones() ([]*base.Milestone, error) { +func (g *GiteaDownloader) GetMilestones(ctx context.Context) ([]*base.Milestone, error) { milestones := make([]*base.Milestone, 0, g.maxPerPage) for i := 1; ; i++ { // make sure gitea can shutdown gracefully select { - case <-g.ctx.Done(): + case <-ctx.Done(): return nil, nil default: } @@ -235,13 +228,13 @@ func (g *GiteaDownloader) convertGiteaLabel(label *gitea_sdk.Label) *base.Label } // GetLabels returns labels -func (g *GiteaDownloader) GetLabels() ([]*base.Label, error) { +func (g *GiteaDownloader) GetLabels(ctx context.Context) ([]*base.Label, error) { labels := make([]*base.Label, 0, g.maxPerPage) for i := 1; ; i++ { // make sure gitea can shutdown gracefully select { - case <-g.ctx.Done(): + case <-ctx.Done(): return nil, nil default: } @@ -323,13 +316,13 @@ func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Rele } // GetReleases returns releases -func (g *GiteaDownloader) GetReleases() ([]*base.Release, error) { +func (g *GiteaDownloader) GetReleases(ctx context.Context) ([]*base.Release, error) { releases := make([]*base.Release, 0, g.maxPerPage) for i := 1; ; i++ { // make sure gitea can shutdown gracefully select { - case <-g.ctx.Done(): + case <-ctx.Done(): return nil, nil default: } @@ -395,7 +388,7 @@ func (g *GiteaDownloader) getCommentReactions(commentID int64) ([]*base.Reaction } // GetIssues returns issues according start and limit -func (g *GiteaDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) { +func (g *GiteaDownloader) GetIssues(_ context.Context, page, perPage int) ([]*base.Issue, bool, error) { if perPage > g.maxPerPage { perPage = g.maxPerPage } @@ -458,13 +451,13 @@ func (g *GiteaDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, err } // GetComments returns comments according issueNumber -func (g *GiteaDownloader) GetComments(commentable base.Commentable) ([]*base.Comment, bool, error) { +func (g *GiteaDownloader) GetComments(ctx context.Context, commentable base.Commentable) ([]*base.Comment, bool, error) { allComments := make([]*base.Comment, 0, g.maxPerPage) for i := 1; ; i++ { // make sure gitea can shutdown gracefully select { - case <-g.ctx.Done(): + case <-ctx.Done(): return nil, false, nil default: } @@ -504,7 +497,7 @@ func (g *GiteaDownloader) GetComments(commentable base.Commentable) ([]*base.Com } // GetPullRequests returns pull requests according page and perPage -func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) { +func (g *GiteaDownloader) GetPullRequests(_ context.Context, page, perPage int) ([]*base.PullRequest, bool, error) { if perPage > g.maxPerPage { perPage = g.maxPerPage } @@ -624,7 +617,7 @@ func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullReques } // GetReviews returns pull requests review -func (g *GiteaDownloader) GetReviews(reviewable base.Reviewable) ([]*base.Review, error) { +func (g *GiteaDownloader) GetReviews(ctx context.Context, reviewable base.Reviewable) ([]*base.Review, error) { if err := g.client.CheckServerVersionConstraint(">=1.12"); err != nil { log.Info("GiteaDownloader: instance to old, skip GetReviews") return nil, nil @@ -635,7 +628,7 @@ func (g *GiteaDownloader) GetReviews(reviewable base.Reviewable) ([]*base.Review for i := 1; ; i++ { // make sure gitea can shutdown gracefully select { - case <-g.ctx.Done(): + case <-ctx.Done(): return nil, nil default: } diff --git a/services/migrations/gitea_downloader_test.go b/services/migrations/gitea_downloader_test.go index 6f6ef99d96..3dccc4017e 100644 --- a/services/migrations/gitea_downloader_test.go +++ b/services/migrations/gitea_downloader_test.go @@ -28,12 +28,12 @@ func TestGiteaDownloadRepo(t *testing.T) { if err != nil || resp.StatusCode != http.StatusOK { t.Skipf("Can't reach https://gitea.com, skipping %s", t.Name()) } - - downloader, err := NewGiteaDownloader(context.Background(), "https://gitea.com", "gitea/test_repo", "", "", giteaToken) + ctx := context.Background() + downloader, err := NewGiteaDownloader(ctx, "https://gitea.com", "gitea/test_repo", "", "", giteaToken) require.NoError(t, err, "NewGiteaDownloader error occur") require.NotNil(t, downloader, "NewGiteaDownloader is nil") - repo, err := downloader.GetRepoInfo() + repo, err := downloader.GetRepoInfo(ctx) assert.NoError(t, err) assertRepositoryEqual(t, &base.Repository{ Name: "test_repo", @@ -45,12 +45,12 @@ func TestGiteaDownloadRepo(t *testing.T) { DefaultBranch: "master", }, repo) - topics, err := downloader.GetTopics() + topics, err := downloader.GetTopics(ctx) assert.NoError(t, err) sort.Strings(topics) assert.EqualValues(t, []string{"ci", "gitea", "migration", "test"}, topics) - labels, err := downloader.GetLabels() + labels, err := downloader.GetLabels(ctx) assert.NoError(t, err) assertLabelsEqual(t, []*base.Label{ { @@ -80,7 +80,7 @@ func TestGiteaDownloadRepo(t *testing.T) { }, }, labels) - milestones, err := downloader.GetMilestones() + milestones, err := downloader.GetMilestones(ctx) assert.NoError(t, err) assertMilestonesEqual(t, []*base.Milestone{ { @@ -100,7 +100,7 @@ func TestGiteaDownloadRepo(t *testing.T) { }, }, milestones) - releases, err := downloader.GetReleases() + releases, err := downloader.GetReleases(ctx) assert.NoError(t, err) assertReleasesEqual(t, []*base.Release{ { @@ -131,13 +131,13 @@ func TestGiteaDownloadRepo(t *testing.T) { }, }, releases) - issues, isEnd, err := downloader.GetIssues(1, 50) + issues, isEnd, err := downloader.GetIssues(ctx, 1, 50) assert.NoError(t, err) assert.True(t, isEnd) assert.Len(t, issues, 7) assert.EqualValues(t, "open", issues[0].State) - issues, isEnd, err = downloader.GetIssues(3, 2) + issues, isEnd, err = downloader.GetIssues(ctx, 3, 2) assert.NoError(t, err) assert.False(t, isEnd) @@ -194,7 +194,7 @@ func TestGiteaDownloadRepo(t *testing.T) { }, }, issues) - comments, _, err := downloader.GetComments(&base.Issue{Number: 4, ForeignIndex: 4}) + comments, _, err := downloader.GetComments(ctx, &base.Issue{Number: 4, ForeignIndex: 4}) assert.NoError(t, err) assertCommentsEqual(t, []*base.Comment{ { @@ -217,11 +217,11 @@ func TestGiteaDownloadRepo(t *testing.T) { }, }, comments) - prs, isEnd, err := downloader.GetPullRequests(1, 50) + prs, isEnd, err := downloader.GetPullRequests(ctx, 1, 50) assert.NoError(t, err) assert.True(t, isEnd) assert.Len(t, prs, 6) - prs, isEnd, err = downloader.GetPullRequests(1, 3) + prs, isEnd, err = downloader.GetPullRequests(ctx, 1, 3) assert.NoError(t, err) assert.False(t, isEnd) assert.Len(t, prs, 3) @@ -259,7 +259,7 @@ func TestGiteaDownloadRepo(t *testing.T) { PatchURL: "https://gitea.com/gitea/test_repo/pulls/12.patch", }, prs[1]) - reviews, err := downloader.GetReviews(&base.Issue{Number: 7, ForeignIndex: 7}) + reviews, err := downloader.GetReviews(ctx, &base.Issue{Number: 7, ForeignIndex: 7}) assert.NoError(t, err) assertReviewsEqual(t, []*base.Review{ { diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index 9e06b77b66..eb16d6cb42 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -41,7 +41,6 @@ var _ base.Uploader = &GiteaLocalUploader{} // GiteaLocalUploader implements an Uploader to gitea sites type GiteaLocalUploader struct { - ctx context.Context doer *user_model.User repoOwner string repoName string @@ -58,9 +57,8 @@ type GiteaLocalUploader struct { } // NewGiteaLocalUploader creates an gitea Uploader via gitea API v1 -func NewGiteaLocalUploader(ctx context.Context, doer *user_model.User, repoOwner, repoName string) *GiteaLocalUploader { +func NewGiteaLocalUploader(_ context.Context, doer *user_model.User, repoOwner, repoName string) *GiteaLocalUploader { return &GiteaLocalUploader{ - ctx: ctx, doer: doer, repoOwner: repoOwner, repoName: repoName, @@ -93,15 +91,15 @@ func (g *GiteaLocalUploader) MaxBatchInsertSize(tp string) int { } // CreateRepo creates a repository -func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.MigrateOptions) error { - owner, err := user_model.GetUserByName(g.ctx, g.repoOwner) +func (g *GiteaLocalUploader) CreateRepo(ctx context.Context, repo *base.Repository, opts base.MigrateOptions) error { + owner, err := user_model.GetUserByName(ctx, g.repoOwner) if err != nil { return err } var r *repo_model.Repository if opts.MigrateToRepoID <= 0 { - r, err = repo_service.CreateRepositoryDirectly(g.ctx, g.doer, owner, repo_service.CreateRepoOptions{ + r, err = repo_service.CreateRepositoryDirectly(ctx, g.doer, owner, repo_service.CreateRepoOptions{ Name: g.repoName, Description: repo.Description, OriginalURL: repo.OriginalURL, @@ -111,7 +109,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate Status: repo_model.RepositoryBeingMigrated, }) } else { - r, err = repo_model.GetRepositoryByID(g.ctx, opts.MigrateToRepoID) + r, err = repo_model.GetRepositoryByID(ctx, opts.MigrateToRepoID) } if err != nil { return err @@ -119,7 +117,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate r.DefaultBranch = repo.DefaultBranch r.Description = repo.Description - r, err = repo_service.MigrateRepositoryGitData(g.ctx, owner, r, base.MigrateOptions{ + r, err = repo_service.MigrateRepositoryGitData(ctx, owner, r, base.MigrateOptions{ RepoName: g.repoName, Description: repo.Description, OriginalURL: repo.OriginalURL, @@ -139,7 +137,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate if err != nil { return err } - g.gitRepo, err = gitrepo.OpenRepository(g.ctx, g.repo) + g.gitRepo, err = gitrepo.OpenRepository(ctx, g.repo) if err != nil { return err } @@ -150,7 +148,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate return err } g.repo.ObjectFormatName = objectFormat.Name() - return repo_model.UpdateRepositoryCols(g.ctx, g.repo, "object_format_name") + return repo_model.UpdateRepositoryCols(ctx, g.repo, "object_format_name") } // Close closes this uploader @@ -161,7 +159,7 @@ func (g *GiteaLocalUploader) Close() { } // CreateTopics creates topics -func (g *GiteaLocalUploader) CreateTopics(topics ...string) error { +func (g *GiteaLocalUploader) CreateTopics(ctx context.Context, topics ...string) error { // Ignore topics too long for the db c := 0 for _, topic := range topics { @@ -173,11 +171,11 @@ func (g *GiteaLocalUploader) CreateTopics(topics ...string) error { c++ } topics = topics[:c] - return repo_model.SaveTopics(g.ctx, g.repo.ID, topics...) + return repo_model.SaveTopics(ctx, g.repo.ID, topics...) } // CreateMilestones creates milestones -func (g *GiteaLocalUploader) CreateMilestones(milestones ...*base.Milestone) error { +func (g *GiteaLocalUploader) CreateMilestones(ctx context.Context, milestones ...*base.Milestone) error { mss := make([]*issues_model.Milestone, 0, len(milestones)) for _, milestone := range milestones { var deadline timeutil.TimeStamp @@ -216,7 +214,7 @@ func (g *GiteaLocalUploader) CreateMilestones(milestones ...*base.Milestone) err mss = append(mss, &ms) } - err := issues_model.InsertMilestones(g.ctx, mss...) + err := issues_model.InsertMilestones(ctx, mss...) if err != nil { return err } @@ -228,7 +226,7 @@ func (g *GiteaLocalUploader) CreateMilestones(milestones ...*base.Milestone) err } // CreateLabels creates labels -func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error { +func (g *GiteaLocalUploader) CreateLabels(ctx context.Context, labels ...*base.Label) error { lbs := make([]*issues_model.Label, 0, len(labels)) for _, l := range labels { if color, err := label.NormalizeColor(l.Color); err != nil { @@ -247,7 +245,7 @@ func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error { }) } - err := issues_model.NewLabels(g.ctx, lbs...) + err := issues_model.NewLabels(ctx, lbs...) if err != nil { return err } @@ -258,7 +256,7 @@ func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error { } // CreateReleases creates releases -func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error { +func (g *GiteaLocalUploader) CreateReleases(ctx context.Context, releases ...*base.Release) error { rels := make([]*repo_model.Release, 0, len(releases)) for _, release := range releases { if release.Created.IsZero() { @@ -292,7 +290,7 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error { CreatedUnix: timeutil.TimeStamp(release.Created.Unix()), } - if err := g.remapUser(release, &rel); err != nil { + if err := g.remapUser(ctx, release, &rel); err != nil { return err } @@ -361,16 +359,16 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error { rels = append(rels, &rel) } - return repo_model.InsertReleases(g.ctx, rels...) + return repo_model.InsertReleases(ctx, rels...) } // SyncTags syncs releases with tags in the database -func (g *GiteaLocalUploader) SyncTags() error { - return repo_module.SyncReleasesWithTags(g.ctx, g.repo, g.gitRepo) +func (g *GiteaLocalUploader) SyncTags(ctx context.Context) error { + return repo_module.SyncReleasesWithTags(ctx, g.repo, g.gitRepo) } // CreateIssues creates issues -func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error { +func (g *GiteaLocalUploader) CreateIssues(ctx context.Context, issues ...*base.Issue) error { iss := make([]*issues_model.Issue, 0, len(issues)) for _, issue := range issues { var labels []*issues_model.Label @@ -419,7 +417,7 @@ func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error { UpdatedUnix: timeutil.TimeStamp(issue.Updated.Unix()), } - if err := g.remapUser(issue, &is); err != nil { + if err := g.remapUser(ctx, issue, &is); err != nil { return err } @@ -432,7 +430,7 @@ func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error { Type: reaction.Content, CreatedUnix: timeutil.TimeStampNow(), } - if err := g.remapUser(reaction, &res); err != nil { + if err := g.remapUser(ctx, reaction, &res); err != nil { return err } is.Reactions = append(is.Reactions, &res) @@ -441,7 +439,7 @@ func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error { } if len(iss) > 0 { - if err := issues_model.InsertIssues(g.ctx, iss...); err != nil { + if err := issues_model.InsertIssues(ctx, iss...); err != nil { return err } @@ -454,7 +452,7 @@ func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error { } // CreateComments creates comments of issues -func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error { +func (g *GiteaLocalUploader) CreateComments(ctx context.Context, comments ...*base.Comment) error { cms := make([]*issues_model.Comment, 0, len(comments)) for _, comment := range comments { var issue *issues_model.Issue @@ -513,7 +511,7 @@ func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error { default: } - if err := g.remapUser(comment, &cm); err != nil { + if err := g.remapUser(ctx, comment, &cm); err != nil { return err } @@ -523,7 +521,7 @@ func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error { Type: reaction.Content, CreatedUnix: timeutil.TimeStampNow(), } - if err := g.remapUser(reaction, &res); err != nil { + if err := g.remapUser(ctx, reaction, &res); err != nil { return err } cm.Reactions = append(cm.Reactions, &res) @@ -535,35 +533,35 @@ func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error { if len(cms) == 0 { return nil } - return issues_model.InsertIssueComments(g.ctx, cms) + return issues_model.InsertIssueComments(ctx, cms) } // CreatePullRequests creates pull requests -func (g *GiteaLocalUploader) CreatePullRequests(prs ...*base.PullRequest) error { +func (g *GiteaLocalUploader) CreatePullRequests(ctx context.Context, prs ...*base.PullRequest) error { gprs := make([]*issues_model.PullRequest, 0, len(prs)) for _, pr := range prs { - gpr, err := g.newPullRequest(pr) + gpr, err := g.newPullRequest(ctx, pr) if err != nil { return err } - if err := g.remapUser(pr, gpr.Issue); err != nil { + if err := g.remapUser(ctx, pr, gpr.Issue); err != nil { return err } gprs = append(gprs, gpr) } - if err := issues_model.InsertPullRequests(g.ctx, gprs...); err != nil { + if err := issues_model.InsertPullRequests(ctx, gprs...); err != nil { return err } for _, pr := range gprs { g.issues[pr.Issue.Index] = pr.Issue - pull.AddToTaskQueue(g.ctx, pr) + pull.AddToTaskQueue(ctx, pr) } return nil } -func (g *GiteaLocalUploader) updateGitForPullRequest(pr *base.PullRequest) (head string, err error) { +func (g *GiteaLocalUploader) updateGitForPullRequest(ctx context.Context, pr *base.PullRequest) (head string, err error) { // SECURITY: this pr must have been must have been ensured safe if !pr.EnsuredSafe { log.Error("PR #%d in %s/%s has not been checked for safety.", pr.Number, g.repoOwner, g.repoName) @@ -664,7 +662,7 @@ func (g *GiteaLocalUploader) updateGitForPullRequest(pr *base.PullRequest) (head fetchArg = git.BranchPrefix + fetchArg } - _, _, err = git.NewCommand(g.ctx, "fetch", "--no-tags").AddDashesAndList(remote, fetchArg).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) + _, _, err = git.NewCommand(ctx, "fetch", "--no-tags").AddDashesAndList(remote, fetchArg).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) if err != nil { log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err) return head, nil @@ -683,7 +681,7 @@ func (g *GiteaLocalUploader) updateGitForPullRequest(pr *base.PullRequest) (head pr.Head.SHA = headSha } - _, _, err = git.NewCommand(g.ctx, "update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) + _, _, err = git.NewCommand(ctx, "update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) if err != nil { return "", err } @@ -700,13 +698,13 @@ func (g *GiteaLocalUploader) updateGitForPullRequest(pr *base.PullRequest) (head // The SHA is empty log.Warn("Empty reference, no pull head for PR #%d in %s/%s", pr.Number, g.repoOwner, g.repoName) } else { - _, _, err = git.NewCommand(g.ctx, "rev-list", "--quiet", "-1").AddDynamicArguments(pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) + _, _, err = git.NewCommand(ctx, "rev-list", "--quiet", "-1").AddDynamicArguments(pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) if err != nil { // Git update-ref remove bad references with a relative path log.Warn("Deprecated local head %s for PR #%d in %s/%s, removing %s", pr.Head.SHA, pr.Number, g.repoOwner, g.repoName, pr.GetGitRefName()) } else { // set head information - _, _, err = git.NewCommand(g.ctx, "update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) + _, _, err = git.NewCommand(ctx, "update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) if err != nil { log.Error("unable to set %s as the local head for PR #%d from %s in %s/%s. Error: %v", pr.Head.SHA, pr.Number, pr.Head.Ref, g.repoOwner, g.repoName, err) } @@ -716,7 +714,7 @@ func (g *GiteaLocalUploader) updateGitForPullRequest(pr *base.PullRequest) (head return head, nil } -func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*issues_model.PullRequest, error) { +func (g *GiteaLocalUploader) newPullRequest(ctx context.Context, pr *base.PullRequest) (*issues_model.PullRequest, error) { var labels []*issues_model.Label for _, label := range pr.Labels { lb, ok := g.labels[label.Name] @@ -727,7 +725,7 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*issues_model milestoneID := g.milestones[pr.Milestone] - head, err := g.updateGitForPullRequest(pr) + head, err := g.updateGitForPullRequest(ctx, pr) if err != nil { return nil, fmt.Errorf("updateGitForPullRequest: %w", err) } @@ -779,7 +777,7 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*issues_model UpdatedUnix: timeutil.TimeStamp(pr.Updated.Unix()), } - if err := g.remapUser(pr, &issue); err != nil { + if err := g.remapUser(ctx, pr, &issue); err != nil { return nil, err } @@ -789,7 +787,7 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*issues_model Type: reaction.Content, CreatedUnix: timeutil.TimeStampNow(), } - if err := g.remapUser(reaction, &res); err != nil { + if err := g.remapUser(ctx, reaction, &res); err != nil { return nil, err } issue.Reactions = append(issue.Reactions, &res) @@ -839,7 +837,7 @@ func convertReviewState(state string) issues_model.ReviewType { } // CreateReviews create pull request reviews of currently migrated issues -func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error { +func (g *GiteaLocalUploader) CreateReviews(ctx context.Context, reviews ...*base.Review) error { cms := make([]*issues_model.Review, 0, len(reviews)) for _, review := range reviews { var issue *issues_model.Issue @@ -860,7 +858,7 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error { UpdatedUnix: timeutil.TimeStamp(review.CreatedAt.Unix()), } - if err := g.remapUser(review, &cm); err != nil { + if err := g.remapUser(ctx, review, &cm); err != nil { return err } @@ -870,7 +868,7 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error { pr, ok := g.prCache[issue.ID] if !ok { var err error - pr, err = issues_model.GetPullRequestByIssueIDWithNoAttributes(g.ctx, issue.ID) + pr, err = issues_model.GetPullRequestByIssueIDWithNoAttributes(ctx, issue.ID) if err != nil { return err } @@ -940,7 +938,7 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error { UpdatedUnix: timeutil.TimeStamp(comment.UpdatedAt.Unix()), } - if err := g.remapUser(review, &c); err != nil { + if err := g.remapUser(ctx, review, &c); err != nil { return err } @@ -948,7 +946,7 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error { } } - return issues_model.InsertReviews(g.ctx, cms) + return issues_model.InsertReviews(ctx, cms) } // Rollback when migrating failed, this will rollback all the changes. @@ -962,31 +960,31 @@ func (g *GiteaLocalUploader) Rollback() error { } // Finish when migrating success, this will do some status update things. -func (g *GiteaLocalUploader) Finish() error { +func (g *GiteaLocalUploader) Finish(ctx context.Context) error { if g.repo == nil || g.repo.ID <= 0 { return ErrRepoNotCreated } // update issue_index - if err := issues_model.RecalculateIssueIndexForRepo(g.ctx, g.repo.ID); err != nil { + if err := issues_model.RecalculateIssueIndexForRepo(ctx, g.repo.ID); err != nil { return err } - if err := models.UpdateRepoStats(g.ctx, g.repo.ID); err != nil { + if err := models.UpdateRepoStats(ctx, g.repo.ID); err != nil { return err } g.repo.Status = repo_model.RepositoryReady - return repo_model.UpdateRepositoryCols(g.ctx, g.repo, "status") + return repo_model.UpdateRepositoryCols(ctx, g.repo, "status") } -func (g *GiteaLocalUploader) remapUser(source user_model.ExternalUserMigrated, target user_model.ExternalUserRemappable) error { +func (g *GiteaLocalUploader) remapUser(ctx context.Context, source user_model.ExternalUserMigrated, target user_model.ExternalUserRemappable) error { var userID int64 var err error if g.sameApp { - userID, err = g.remapLocalUser(source) + userID, err = g.remapLocalUser(ctx, source) } else { - userID, err = g.remapExternalUser(source) + userID, err = g.remapExternalUser(ctx, source) } if err != nil { return err @@ -998,10 +996,10 @@ func (g *GiteaLocalUploader) remapUser(source user_model.ExternalUserMigrated, t return target.RemapExternalUser(source.GetExternalName(), source.GetExternalID(), g.doer.ID) } -func (g *GiteaLocalUploader) remapLocalUser(source user_model.ExternalUserMigrated) (int64, error) { +func (g *GiteaLocalUploader) remapLocalUser(ctx context.Context, source user_model.ExternalUserMigrated) (int64, error) { userid, ok := g.userMap[source.GetExternalID()] if !ok { - name, err := user_model.GetUserNameByID(g.ctx, source.GetExternalID()) + name, err := user_model.GetUserNameByID(ctx, source.GetExternalID()) if err != nil { return 0, err } @@ -1016,10 +1014,10 @@ func (g *GiteaLocalUploader) remapLocalUser(source user_model.ExternalUserMigrat return userid, nil } -func (g *GiteaLocalUploader) remapExternalUser(source user_model.ExternalUserMigrated) (userid int64, err error) { +func (g *GiteaLocalUploader) remapExternalUser(ctx context.Context, source user_model.ExternalUserMigrated) (userid int64, err error) { userid, ok := g.userMap[source.GetExternalID()] if !ok { - userid, err = user_model.GetUserIDByExternalUserID(g.ctx, g.gitServiceType.Name(), fmt.Sprintf("%d", source.GetExternalID())) + userid, err = user_model.GetUserIDByExternalUserID(ctx, g.gitServiceType.Name(), fmt.Sprintf("%d", source.GetExternalID())) if err != nil { log.Error("GetUserIDByExternalUserID: %v", err) return 0, err diff --git a/services/migrations/gitea_uploader_test.go b/services/migrations/gitea_uploader_test.go index f2379dadf8..18d1171597 100644 --- a/services/migrations/gitea_uploader_test.go +++ b/services/migrations/gitea_uploader_test.go @@ -132,8 +132,9 @@ func TestGiteaUploadRemapLocalUser(t *testing.T) { doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + ctx := context.Background() repoName := "migrated" - uploader := NewGiteaLocalUploader(context.Background(), doer, doer.Name, repoName) + uploader := NewGiteaLocalUploader(ctx, doer, doer.Name, repoName) // call remapLocalUser uploader.sameApp = true @@ -150,7 +151,7 @@ func TestGiteaUploadRemapLocalUser(t *testing.T) { // target := repo_model.Release{} uploader.userMap = make(map[int64]int64) - err := uploader.remapUser(&source, &target) + err := uploader.remapUser(ctx, &source, &target) assert.NoError(t, err) assert.EqualValues(t, doer.ID, target.GetUserID()) @@ -161,7 +162,7 @@ func TestGiteaUploadRemapLocalUser(t *testing.T) { source.PublisherID = user.ID target = repo_model.Release{} uploader.userMap = make(map[int64]int64) - err = uploader.remapUser(&source, &target) + err = uploader.remapUser(ctx, &source, &target) assert.NoError(t, err) assert.EqualValues(t, doer.ID, target.GetUserID()) @@ -172,7 +173,7 @@ func TestGiteaUploadRemapLocalUser(t *testing.T) { source.PublisherName = user.Name target = repo_model.Release{} uploader.userMap = make(map[int64]int64) - err = uploader.remapUser(&source, &target) + err = uploader.remapUser(ctx, &source, &target) assert.NoError(t, err) assert.EqualValues(t, user.ID, target.GetUserID()) } @@ -180,9 +181,9 @@ func TestGiteaUploadRemapLocalUser(t *testing.T) { func TestGiteaUploadRemapExternalUser(t *testing.T) { unittest.PrepareTestEnv(t) doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - + ctx := context.Background() repoName := "migrated" - uploader := NewGiteaLocalUploader(context.Background(), doer, doer.Name, repoName) + uploader := NewGiteaLocalUploader(ctx, doer, doer.Name, repoName) uploader.gitServiceType = structs.GiteaService // call remapExternalUser uploader.sameApp = false @@ -200,7 +201,7 @@ func TestGiteaUploadRemapExternalUser(t *testing.T) { // uploader.userMap = make(map[int64]int64) target := repo_model.Release{} - err := uploader.remapUser(&source, &target) + err := uploader.remapUser(ctx, &source, &target) assert.NoError(t, err) assert.EqualValues(t, doer.ID, target.GetUserID()) @@ -223,7 +224,7 @@ func TestGiteaUploadRemapExternalUser(t *testing.T) { // uploader.userMap = make(map[int64]int64) target = repo_model.Release{} - err = uploader.remapUser(&source, &target) + err = uploader.remapUser(ctx, &source, &target) assert.NoError(t, err) assert.EqualValues(t, linkedUser.ID, target.GetUserID()) } @@ -301,11 +302,12 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { assert.NoError(t, err) toRepoName := "migrated" - uploader := NewGiteaLocalUploader(context.Background(), fromRepoOwner, fromRepoOwner.Name, toRepoName) + ctx := context.Background() + uploader := NewGiteaLocalUploader(ctx, fromRepoOwner, fromRepoOwner.Name, toRepoName) uploader.gitServiceType = structs.GiteaService assert.NoError(t, repo_service.Init(context.Background())) - assert.NoError(t, uploader.CreateRepo(&base.Repository{ + assert.NoError(t, uploader.CreateRepo(ctx, &base.Repository{ Description: "description", OriginalURL: fromRepo.RepoPath(), CloneURL: fromRepo.RepoPath(), @@ -505,7 +507,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { testCase.pr.EnsuredSafe = true - head, err := uploader.updateGitForPullRequest(&testCase.pr) + head, err := uploader.updateGitForPullRequest(ctx, &testCase.pr) assert.NoError(t, err) assert.EqualValues(t, testCase.head, head) diff --git a/services/migrations/github.go b/services/migrations/github.go index 604ab84b39..b00d6ed27f 100644 --- a/services/migrations/github.go +++ b/services/migrations/github.go @@ -64,7 +64,6 @@ func (f *GithubDownloaderV3Factory) GitServiceType() structs.GitServiceType { // from github via APIv3 type GithubDownloaderV3 struct { base.NullDownloader - ctx context.Context clients []*github.Client baseURL string repoOwner string @@ -79,12 +78,11 @@ type GithubDownloaderV3 struct { } // NewGithubDownloaderV3 creates a github Downloader via github v3 API -func NewGithubDownloaderV3(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GithubDownloaderV3 { +func NewGithubDownloaderV3(_ context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GithubDownloaderV3 { downloader := GithubDownloaderV3{ userName: userName, baseURL: baseURL, password: password, - ctx: ctx, repoOwner: repoOwner, repoName: repoName, maxPerPage: 100, @@ -141,12 +139,7 @@ func (g *GithubDownloaderV3) addClient(client *http.Client, baseURL string) { g.rates = append(g.rates, nil) } -// SetContext set context -func (g *GithubDownloaderV3) SetContext(ctx context.Context) { - g.ctx = ctx -} - -func (g *GithubDownloaderV3) waitAndPickClient() { +func (g *GithubDownloaderV3) waitAndPickClient(ctx context.Context) { var recentIdx int var maxRemaining int for i := 0; i < len(g.clients); i++ { @@ -160,13 +153,13 @@ func (g *GithubDownloaderV3) waitAndPickClient() { for g.rates[g.curClientIdx] != nil && g.rates[g.curClientIdx].Remaining <= GithubLimitRateRemaining { timer := time.NewTimer(time.Until(g.rates[g.curClientIdx].Reset.Time)) select { - case <-g.ctx.Done(): + case <-ctx.Done(): timer.Stop() return case <-timer.C: } - err := g.RefreshRate() + err := g.RefreshRate(ctx) if err != nil { log.Error("g.getClient().RateLimit.Get: %s", err) } @@ -174,8 +167,8 @@ func (g *GithubDownloaderV3) waitAndPickClient() { } // RefreshRate update the current rate (doesn't count in rate limit) -func (g *GithubDownloaderV3) RefreshRate() error { - rates, _, err := g.getClient().RateLimit.Get(g.ctx) +func (g *GithubDownloaderV3) RefreshRate(ctx context.Context) error { + rates, _, err := g.getClient().RateLimit.Get(ctx) if err != nil { // if rate limit is not enabled, ignore it if strings.Contains(err.Error(), "404") { @@ -198,9 +191,9 @@ func (g *GithubDownloaderV3) setRate(rate *github.Rate) { } // GetRepoInfo returns a repository information -func (g *GithubDownloaderV3) GetRepoInfo() (*base.Repository, error) { - g.waitAndPickClient() - gr, resp, err := g.getClient().Repositories.Get(g.ctx, g.repoOwner, g.repoName) +func (g *GithubDownloaderV3) GetRepoInfo(ctx context.Context) (*base.Repository, error) { + g.waitAndPickClient(ctx) + gr, resp, err := g.getClient().Repositories.Get(ctx, g.repoOwner, g.repoName) if err != nil { return nil, err } @@ -219,9 +212,9 @@ func (g *GithubDownloaderV3) GetRepoInfo() (*base.Repository, error) { } // GetTopics return github topics -func (g *GithubDownloaderV3) GetTopics() ([]string, error) { - g.waitAndPickClient() - r, resp, err := g.getClient().Repositories.Get(g.ctx, g.repoOwner, g.repoName) +func (g *GithubDownloaderV3) GetTopics(ctx context.Context) ([]string, error) { + g.waitAndPickClient(ctx) + r, resp, err := g.getClient().Repositories.Get(ctx, g.repoOwner, g.repoName) if err != nil { return nil, err } @@ -230,12 +223,12 @@ func (g *GithubDownloaderV3) GetTopics() ([]string, error) { } // GetMilestones returns milestones -func (g *GithubDownloaderV3) GetMilestones() ([]*base.Milestone, error) { +func (g *GithubDownloaderV3) GetMilestones(ctx context.Context) ([]*base.Milestone, error) { perPage := g.maxPerPage milestones := make([]*base.Milestone, 0, perPage) for i := 1; ; i++ { - g.waitAndPickClient() - ms, resp, err := g.getClient().Issues.ListMilestones(g.ctx, g.repoOwner, g.repoName, + g.waitAndPickClient(ctx) + ms, resp, err := g.getClient().Issues.ListMilestones(ctx, g.repoOwner, g.repoName, &github.MilestoneListOptions{ State: "all", ListOptions: github.ListOptions{ @@ -279,12 +272,12 @@ func convertGithubLabel(label *github.Label) *base.Label { } // GetLabels returns labels -func (g *GithubDownloaderV3) GetLabels() ([]*base.Label, error) { +func (g *GithubDownloaderV3) GetLabels(ctx context.Context) ([]*base.Label, error) { perPage := g.maxPerPage labels := make([]*base.Label, 0, perPage) for i := 1; ; i++ { - g.waitAndPickClient() - ls, resp, err := g.getClient().Issues.ListLabels(g.ctx, g.repoOwner, g.repoName, + g.waitAndPickClient(ctx) + ls, resp, err := g.getClient().Issues.ListLabels(ctx, g.repoOwner, g.repoName, &github.ListOptions{ Page: i, PerPage: perPage, @@ -304,7 +297,7 @@ func (g *GithubDownloaderV3) GetLabels() ([]*base.Label, error) { return labels, nil } -func (g *GithubDownloaderV3) convertGithubRelease(rel *github.RepositoryRelease) *base.Release { +func (g *GithubDownloaderV3) convertGithubRelease(ctx context.Context, rel *github.RepositoryRelease) *base.Release { // GitHub allows commitish to be a reference. // In this case, we need to remove the prefix, i.e. convert "refs/heads/main" to "main". targetCommitish := strings.TrimPrefix(rel.GetTargetCommitish(), git.BranchPrefix) @@ -339,12 +332,12 @@ func (g *GithubDownloaderV3) convertGithubRelease(rel *github.RepositoryRelease) Created: asset.CreatedAt.Time, Updated: asset.UpdatedAt.Time, DownloadFunc: func() (io.ReadCloser, error) { - g.waitAndPickClient() - readCloser, redirectURL, err := g.getClient().Repositories.DownloadReleaseAsset(g.ctx, g.repoOwner, g.repoName, assetID, nil) + g.waitAndPickClient(ctx) + readCloser, redirectURL, err := g.getClient().Repositories.DownloadReleaseAsset(ctx, g.repoOwner, g.repoName, assetID, nil) if err != nil { return nil, err } - if err := g.RefreshRate(); err != nil { + if err := g.RefreshRate(ctx); err != nil { log.Error("g.getClient().RateLimits: %s", err) } @@ -364,13 +357,13 @@ func (g *GithubDownloaderV3) convertGithubRelease(rel *github.RepositoryRelease) return io.NopCloser(strings.NewReader(redirectURL)), nil } - g.waitAndPickClient() - req, err := http.NewRequestWithContext(g.ctx, "GET", redirectURL, nil) + g.waitAndPickClient(ctx) + req, err := http.NewRequestWithContext(ctx, "GET", redirectURL, nil) if err != nil { return nil, err } resp, err := httpClient.Do(req) - err1 := g.RefreshRate() + err1 := g.RefreshRate(ctx) if err1 != nil { log.Error("g.RefreshRate(): %s", err1) } @@ -385,12 +378,12 @@ func (g *GithubDownloaderV3) convertGithubRelease(rel *github.RepositoryRelease) } // GetReleases returns releases -func (g *GithubDownloaderV3) GetReleases() ([]*base.Release, error) { +func (g *GithubDownloaderV3) GetReleases(ctx context.Context) ([]*base.Release, error) { perPage := g.maxPerPage releases := make([]*base.Release, 0, perPage) for i := 1; ; i++ { - g.waitAndPickClient() - ls, resp, err := g.getClient().Repositories.ListReleases(g.ctx, g.repoOwner, g.repoName, + g.waitAndPickClient(ctx) + ls, resp, err := g.getClient().Repositories.ListReleases(ctx, g.repoOwner, g.repoName, &github.ListOptions{ Page: i, PerPage: perPage, @@ -401,7 +394,7 @@ func (g *GithubDownloaderV3) GetReleases() ([]*base.Release, error) { g.setRate(&resp.Rate) for _, release := range ls { - releases = append(releases, g.convertGithubRelease(release)) + releases = append(releases, g.convertGithubRelease(ctx, release)) } if len(ls) < perPage { break @@ -411,7 +404,7 @@ func (g *GithubDownloaderV3) GetReleases() ([]*base.Release, error) { } // GetIssues returns issues according start and limit -func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool, error) { +func (g *GithubDownloaderV3) GetIssues(ctx context.Context, page, perPage int) ([]*base.Issue, bool, error) { if perPage > g.maxPerPage { perPage = g.maxPerPage } @@ -426,8 +419,8 @@ func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool, } allIssues := make([]*base.Issue, 0, perPage) - g.waitAndPickClient() - issues, resp, err := g.getClient().Issues.ListByRepo(g.ctx, g.repoOwner, g.repoName, opt) + g.waitAndPickClient(ctx) + issues, resp, err := g.getClient().Issues.ListByRepo(ctx, g.repoOwner, g.repoName, opt) if err != nil { return nil, false, fmt.Errorf("error while listing repos: %w", err) } @@ -447,8 +440,8 @@ func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool, var reactions []*base.Reaction if !g.SkipReactions { for i := 1; ; i++ { - g.waitAndPickClient() - res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, issue.GetNumber(), &github.ListOptions{ + g.waitAndPickClient(ctx) + res, resp, err := g.getClient().Reactions.ListIssueReactions(ctx, g.repoOwner, g.repoName, issue.GetNumber(), &github.ListOptions{ Page: i, PerPage: perPage, }) @@ -503,12 +496,12 @@ func (g *GithubDownloaderV3) SupportGetRepoComments() bool { } // GetComments returns comments according issueNumber -func (g *GithubDownloaderV3) GetComments(commentable base.Commentable) ([]*base.Comment, bool, error) { - comments, err := g.getComments(commentable) +func (g *GithubDownloaderV3) GetComments(ctx context.Context, commentable base.Commentable) ([]*base.Comment, bool, error) { + comments, err := g.getComments(ctx, commentable) return comments, false, err } -func (g *GithubDownloaderV3) getComments(commentable base.Commentable) ([]*base.Comment, error) { +func (g *GithubDownloaderV3) getComments(ctx context.Context, commentable base.Commentable) ([]*base.Comment, error) { var ( allComments = make([]*base.Comment, 0, g.maxPerPage) created = "created" @@ -522,8 +515,8 @@ func (g *GithubDownloaderV3) getComments(commentable base.Commentable) ([]*base. }, } for { - g.waitAndPickClient() - comments, resp, err := g.getClient().Issues.ListComments(g.ctx, g.repoOwner, g.repoName, int(commentable.GetForeignIndex()), opt) + g.waitAndPickClient(ctx) + comments, resp, err := g.getClient().Issues.ListComments(ctx, g.repoOwner, g.repoName, int(commentable.GetForeignIndex()), opt) if err != nil { return nil, fmt.Errorf("error while listing repos: %w", err) } @@ -533,8 +526,8 @@ func (g *GithubDownloaderV3) getComments(commentable base.Commentable) ([]*base. var reactions []*base.Reaction if !g.SkipReactions { for i := 1; ; i++ { - g.waitAndPickClient() - res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{ + g.waitAndPickClient(ctx) + res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{ Page: i, PerPage: g.maxPerPage, }) @@ -576,7 +569,7 @@ func (g *GithubDownloaderV3) getComments(commentable base.Commentable) ([]*base. } // GetAllComments returns repository comments according page and perPageSize -func (g *GithubDownloaderV3) GetAllComments(page, perPage int) ([]*base.Comment, bool, error) { +func (g *GithubDownloaderV3) GetAllComments(ctx context.Context, page, perPage int) ([]*base.Comment, bool, error) { var ( allComments = make([]*base.Comment, 0, perPage) created = "created" @@ -594,8 +587,8 @@ func (g *GithubDownloaderV3) GetAllComments(page, perPage int) ([]*base.Comment, }, } - g.waitAndPickClient() - comments, resp, err := g.getClient().Issues.ListComments(g.ctx, g.repoOwner, g.repoName, 0, opt) + g.waitAndPickClient(ctx) + comments, resp, err := g.getClient().Issues.ListComments(ctx, g.repoOwner, g.repoName, 0, opt) if err != nil { return nil, false, fmt.Errorf("error while listing repos: %w", err) } @@ -608,8 +601,8 @@ func (g *GithubDownloaderV3) GetAllComments(page, perPage int) ([]*base.Comment, var reactions []*base.Reaction if !g.SkipReactions { for i := 1; ; i++ { - g.waitAndPickClient() - res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{ + g.waitAndPickClient(ctx) + res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{ Page: i, PerPage: g.maxPerPage, }) @@ -648,7 +641,7 @@ func (g *GithubDownloaderV3) GetAllComments(page, perPage int) ([]*base.Comment, } // GetPullRequests returns pull requests according page and perPage -func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) { +func (g *GithubDownloaderV3) GetPullRequests(ctx context.Context, page, perPage int) ([]*base.PullRequest, bool, error) { if perPage > g.maxPerPage { perPage = g.maxPerPage } @@ -662,8 +655,8 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq }, } allPRs := make([]*base.PullRequest, 0, perPage) - g.waitAndPickClient() - prs, resp, err := g.getClient().PullRequests.List(g.ctx, g.repoOwner, g.repoName, opt) + g.waitAndPickClient(ctx) + prs, resp, err := g.getClient().PullRequests.List(ctx, g.repoOwner, g.repoName, opt) if err != nil { return nil, false, fmt.Errorf("error while listing repos: %w", err) } @@ -679,8 +672,8 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq var reactions []*base.Reaction if !g.SkipReactions { for i := 1; ; i++ { - g.waitAndPickClient() - res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, pr.GetNumber(), &github.ListOptions{ + g.waitAndPickClient(ctx) + res, resp, err := g.getClient().Reactions.ListIssueReactions(ctx, g.repoOwner, g.repoName, pr.GetNumber(), &github.ListOptions{ Page: i, PerPage: perPage, }) @@ -702,7 +695,7 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq } // download patch and saved as tmp file - g.waitAndPickClient() + g.waitAndPickClient(ctx) allPRs = append(allPRs, &base.PullRequest{ Title: pr.GetTitle(), @@ -759,15 +752,15 @@ func convertGithubReview(r *github.PullRequestReview) *base.Review { } } -func (g *GithubDownloaderV3) convertGithubReviewComments(cs []*github.PullRequestComment) ([]*base.ReviewComment, error) { +func (g *GithubDownloaderV3) convertGithubReviewComments(ctx context.Context, cs []*github.PullRequestComment) ([]*base.ReviewComment, error) { rcs := make([]*base.ReviewComment, 0, len(cs)) for _, c := range cs { // get reactions var reactions []*base.Reaction if !g.SkipReactions { for i := 1; ; i++ { - g.waitAndPickClient() - res, resp, err := g.getClient().Reactions.ListPullRequestCommentReactions(g.ctx, g.repoOwner, g.repoName, c.GetID(), &github.ListOptions{ + g.waitAndPickClient(ctx) + res, resp, err := g.getClient().Reactions.ListPullRequestCommentReactions(ctx, g.repoOwner, g.repoName, c.GetID(), &github.ListOptions{ Page: i, PerPage: g.maxPerPage, }) @@ -806,7 +799,7 @@ func (g *GithubDownloaderV3) convertGithubReviewComments(cs []*github.PullReques } // GetReviews returns pull requests review -func (g *GithubDownloaderV3) GetReviews(reviewable base.Reviewable) ([]*base.Review, error) { +func (g *GithubDownloaderV3) GetReviews(ctx context.Context, reviewable base.Reviewable) ([]*base.Review, error) { allReviews := make([]*base.Review, 0, g.maxPerPage) if g.SkipReviews { return allReviews, nil @@ -816,8 +809,8 @@ func (g *GithubDownloaderV3) GetReviews(reviewable base.Reviewable) ([]*base.Rev } // Get approve/request change reviews for { - g.waitAndPickClient() - reviews, resp, err := g.getClient().PullRequests.ListReviews(g.ctx, g.repoOwner, g.repoName, int(reviewable.GetForeignIndex()), opt) + g.waitAndPickClient(ctx) + reviews, resp, err := g.getClient().PullRequests.ListReviews(ctx, g.repoOwner, g.repoName, int(reviewable.GetForeignIndex()), opt) if err != nil { return nil, fmt.Errorf("error while listing repos: %w", err) } @@ -830,14 +823,14 @@ func (g *GithubDownloaderV3) GetReviews(reviewable base.Reviewable) ([]*base.Rev PerPage: g.maxPerPage, } for { - g.waitAndPickClient() - reviewComments, resp, err := g.getClient().PullRequests.ListReviewComments(g.ctx, g.repoOwner, g.repoName, int(reviewable.GetForeignIndex()), review.GetID(), opt2) + g.waitAndPickClient(ctx) + reviewComments, resp, err := g.getClient().PullRequests.ListReviewComments(ctx, g.repoOwner, g.repoName, int(reviewable.GetForeignIndex()), review.GetID(), opt2) if err != nil { return nil, fmt.Errorf("error while listing repos: %w", err) } g.setRate(&resp.Rate) - cs, err := g.convertGithubReviewComments(reviewComments) + cs, err := g.convertGithubReviewComments(ctx, reviewComments) if err != nil { return nil, err } @@ -856,8 +849,8 @@ func (g *GithubDownloaderV3) GetReviews(reviewable base.Reviewable) ([]*base.Rev } // Get requested reviews for { - g.waitAndPickClient() - reviewers, resp, err := g.getClient().PullRequests.ListReviewers(g.ctx, g.repoOwner, g.repoName, int(reviewable.GetForeignIndex()), opt) + g.waitAndPickClient(ctx) + reviewers, resp, err := g.getClient().PullRequests.ListReviewers(ctx, g.repoOwner, g.repoName, int(reviewable.GetForeignIndex()), opt) if err != nil { return nil, fmt.Errorf("error while listing repos: %w", err) } diff --git a/services/migrations/github_test.go b/services/migrations/github_test.go index 2b89e6dc0f..899f9fe52c 100644 --- a/services/migrations/github_test.go +++ b/services/migrations/github_test.go @@ -21,11 +21,12 @@ func TestGitHubDownloadRepo(t *testing.T) { if token == "" { t.Skip("Skipping GitHub migration test because GITHUB_READ_TOKEN is empty") } - downloader := NewGithubDownloaderV3(context.Background(), "https://github.com", "", "", token, "go-gitea", "test_repo") - err := downloader.RefreshRate() + ctx := context.Background() + downloader := NewGithubDownloaderV3(ctx, "https://github.com", "", "", token, "go-gitea", "test_repo") + err := downloader.RefreshRate(ctx) assert.NoError(t, err) - repo, err := downloader.GetRepoInfo() + repo, err := downloader.GetRepoInfo(ctx) assert.NoError(t, err) assertRepositoryEqual(t, &base.Repository{ Name: "test_repo", @@ -36,11 +37,11 @@ func TestGitHubDownloadRepo(t *testing.T) { DefaultBranch: "master", }, repo) - topics, err := downloader.GetTopics() + topics, err := downloader.GetTopics(ctx) assert.NoError(t, err) assert.Contains(t, topics, "gitea") - milestones, err := downloader.GetMilestones() + milestones, err := downloader.GetMilestones(ctx) assert.NoError(t, err) assertMilestonesEqual(t, []*base.Milestone{ { @@ -63,7 +64,7 @@ func TestGitHubDownloadRepo(t *testing.T) { }, }, milestones) - labels, err := downloader.GetLabels() + labels, err := downloader.GetLabels(ctx) assert.NoError(t, err) assertLabelsEqual(t, []*base.Label{ { @@ -113,7 +114,7 @@ func TestGitHubDownloadRepo(t *testing.T) { }, }, labels) - releases, err := downloader.GetReleases() + releases, err := downloader.GetReleases(ctx) assert.NoError(t, err) assertReleasesEqual(t, []*base.Release{ { @@ -129,7 +130,7 @@ func TestGitHubDownloadRepo(t *testing.T) { }, releases) // downloader.GetIssues() - issues, isEnd, err := downloader.GetIssues(1, 2) + issues, isEnd, err := downloader.GetIssues(ctx, 1, 2) assert.NoError(t, err) assert.False(t, isEnd) assertIssuesEqual(t, []*base.Issue{ @@ -218,7 +219,7 @@ func TestGitHubDownloadRepo(t *testing.T) { }, issues) // downloader.GetComments() - comments, _, err := downloader.GetComments(&base.Issue{Number: 2, ForeignIndex: 2}) + comments, _, err := downloader.GetComments(ctx, &base.Issue{Number: 2, ForeignIndex: 2}) assert.NoError(t, err) assertCommentsEqual(t, []*base.Comment{ { @@ -248,7 +249,7 @@ func TestGitHubDownloadRepo(t *testing.T) { }, comments) // downloader.GetPullRequests() - prs, _, err := downloader.GetPullRequests(1, 2) + prs, _, err := downloader.GetPullRequests(ctx, 1, 2) assert.NoError(t, err) assertPullRequestsEqual(t, []*base.PullRequest{ { @@ -338,7 +339,7 @@ func TestGitHubDownloadRepo(t *testing.T) { }, }, prs) - reviews, err := downloader.GetReviews(&base.PullRequest{Number: 3, ForeignIndex: 3}) + reviews, err := downloader.GetReviews(ctx, &base.PullRequest{Number: 3, ForeignIndex: 3}) assert.NoError(t, err) assertReviewsEqual(t, []*base.Review{ { @@ -370,7 +371,7 @@ func TestGitHubDownloadRepo(t *testing.T) { }, }, reviews) - reviews, err = downloader.GetReviews(&base.PullRequest{Number: 4, ForeignIndex: 4}) + reviews, err = downloader.GetReviews(ctx, &base.PullRequest{Number: 4, ForeignIndex: 4}) assert.NoError(t, err) assertReviewsEqual(t, []*base.Review{ { diff --git a/services/migrations/gitlab.go b/services/migrations/gitlab.go index 07d5040b5b..efc5b960cf 100644 --- a/services/migrations/gitlab.go +++ b/services/migrations/gitlab.go @@ -80,7 +80,6 @@ func (r *gitlabIIDResolver) generatePullRequestNumber(mrIID int) int64 { // because Gitlab has individual Issue and Pull Request numbers. type GitlabDownloader struct { base.NullDownloader - ctx context.Context client *gitlab.Client baseURL string repoID int @@ -143,7 +142,6 @@ func NewGitlabDownloader(ctx context.Context, baseURL, repoPath, username, passw } return &GitlabDownloader{ - ctx: ctx, client: gitlabClient, baseURL: baseURL, repoID: gr.ID, @@ -164,14 +162,9 @@ func (g *GitlabDownloader) LogString() string { return fmt.Sprintf("", g.baseURL, g.repoID, g.repoName) } -// SetContext set context -func (g *GitlabDownloader) SetContext(ctx context.Context) { - g.ctx = ctx -} - // GetRepoInfo returns a repository information -func (g *GitlabDownloader) GetRepoInfo() (*base.Repository, error) { - gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil, gitlab.WithContext(g.ctx)) +func (g *GitlabDownloader) GetRepoInfo(ctx context.Context) (*base.Repository, error) { + gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil, gitlab.WithContext(ctx)) if err != nil { return nil, err } @@ -207,8 +200,8 @@ func (g *GitlabDownloader) GetRepoInfo() (*base.Repository, error) { } // GetTopics return gitlab topics -func (g *GitlabDownloader) GetTopics() ([]string, error) { - gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil, gitlab.WithContext(g.ctx)) +func (g *GitlabDownloader) GetTopics(ctx context.Context) ([]string, error) { + gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil, gitlab.WithContext(ctx)) if err != nil { return nil, err } @@ -216,7 +209,7 @@ func (g *GitlabDownloader) GetTopics() ([]string, error) { } // GetMilestones returns milestones -func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) { +func (g *GitlabDownloader) GetMilestones(ctx context.Context) ([]*base.Milestone, error) { perPage := g.maxPerPage state := "all" milestones := make([]*base.Milestone, 0, perPage) @@ -227,7 +220,7 @@ func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) { Page: i, PerPage: perPage, }, - }, nil, gitlab.WithContext(g.ctx)) + }, nil, gitlab.WithContext(ctx)) if err != nil { return nil, err } @@ -288,14 +281,14 @@ func (g *GitlabDownloader) normalizeColor(val string) string { } // GetLabels returns labels -func (g *GitlabDownloader) GetLabels() ([]*base.Label, error) { +func (g *GitlabDownloader) GetLabels(ctx context.Context) ([]*base.Label, error) { perPage := g.maxPerPage labels := make([]*base.Label, 0, perPage) for i := 1; ; i++ { ls, _, err := g.client.Labels.ListLabels(g.repoID, &gitlab.ListLabelsOptions{ListOptions: gitlab.ListOptions{ Page: i, PerPage: perPage, - }}, nil, gitlab.WithContext(g.ctx)) + }}, nil, gitlab.WithContext(ctx)) if err != nil { return nil, err } @@ -314,7 +307,7 @@ func (g *GitlabDownloader) GetLabels() ([]*base.Label, error) { return labels, nil } -func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Release { +func (g *GitlabDownloader) convertGitlabRelease(ctx context.Context, rel *gitlab.Release) *base.Release { var zero int r := &base.Release{ TagName: rel.TagName, @@ -337,7 +330,7 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea Size: &zero, DownloadCount: &zero, DownloadFunc: func() (io.ReadCloser, error) { - link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, rel.TagName, assetID, gitlab.WithContext(g.ctx)) + link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, rel.TagName, assetID, gitlab.WithContext(ctx)) if err != nil { return nil, err } @@ -351,7 +344,7 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea if err != nil { return nil, err } - req = req.WithContext(g.ctx) + req = req.WithContext(ctx) resp, err := httpClient.Do(req) if err != nil { return nil, err @@ -366,7 +359,7 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea } // GetReleases returns releases -func (g *GitlabDownloader) GetReleases() ([]*base.Release, error) { +func (g *GitlabDownloader) GetReleases(ctx context.Context) ([]*base.Release, error) { perPage := g.maxPerPage releases := make([]*base.Release, 0, perPage) for i := 1; ; i++ { @@ -375,13 +368,13 @@ func (g *GitlabDownloader) GetReleases() ([]*base.Release, error) { Page: i, PerPage: perPage, }, - }, nil, gitlab.WithContext(g.ctx)) + }, nil, gitlab.WithContext(ctx)) if err != nil { return nil, err } for _, release := range ls { - releases = append(releases, g.convertGitlabRelease(release)) + releases = append(releases, g.convertGitlabRelease(ctx, release)) } if len(ls) < perPage { break @@ -397,7 +390,7 @@ type gitlabIssueContext struct { // GetIssues returns issues according start and limit // // Note: issue label description and colors are not supported by the go-gitlab library at this time -func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) { +func (g *GitlabDownloader) GetIssues(ctx context.Context, page, perPage int) ([]*base.Issue, bool, error) { state := "all" sort := "asc" @@ -416,7 +409,7 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er allIssues := make([]*base.Issue, 0, perPage) - issues, _, err := g.client.Issues.ListProjectIssues(g.repoID, opt, nil, gitlab.WithContext(g.ctx)) + issues, _, err := g.client.Issues.ListProjectIssues(g.repoID, opt, nil, gitlab.WithContext(ctx)) if err != nil { return nil, false, fmt.Errorf("error while listing issues: %w", err) } @@ -436,7 +429,7 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er var reactions []*gitlab.AwardEmoji awardPage := 1 for { - awards, _, err := g.client.AwardEmoji.ListIssueAwardEmoji(g.repoID, issue.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx)) + awards, _, err := g.client.AwardEmoji.ListIssueAwardEmoji(g.repoID, issue.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(ctx)) if err != nil { return nil, false, fmt.Errorf("error while listing issue awards: %w", err) } @@ -477,7 +470,7 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er // GetComments returns comments according issueNumber // TODO: figure out how to transfer comment reactions -func (g *GitlabDownloader) GetComments(commentable base.Commentable) ([]*base.Comment, bool, error) { +func (g *GitlabDownloader) GetComments(ctx context.Context, commentable base.Commentable) ([]*base.Comment, bool, error) { context, ok := commentable.GetContext().(gitlabIssueContext) if !ok { return nil, false, fmt.Errorf("unexpected context: %+v", commentable.GetContext()) @@ -495,12 +488,12 @@ func (g *GitlabDownloader) GetComments(commentable base.Commentable) ([]*base.Co comments, resp, err = g.client.Discussions.ListIssueDiscussions(g.repoID, int(commentable.GetForeignIndex()), &gitlab.ListIssueDiscussionsOptions{ Page: page, PerPage: g.maxPerPage, - }, nil, gitlab.WithContext(g.ctx)) + }, nil, gitlab.WithContext(ctx)) } else { comments, resp, err = g.client.Discussions.ListMergeRequestDiscussions(g.repoID, int(commentable.GetForeignIndex()), &gitlab.ListMergeRequestDiscussionsOptions{ Page: page, PerPage: g.maxPerPage, - }, nil, gitlab.WithContext(g.ctx)) + }, nil, gitlab.WithContext(ctx)) } if err != nil { @@ -528,14 +521,14 @@ func (g *GitlabDownloader) GetComments(commentable base.Commentable) ([]*base.Co Page: page, PerPage: g.maxPerPage, }, - }, nil, gitlab.WithContext(g.ctx)) + }, nil, gitlab.WithContext(ctx)) } else { stateEvents, resp, err = g.client.ResourceStateEvents.ListIssueStateEvents(g.repoID, int(commentable.GetForeignIndex()), &gitlab.ListStateEventsOptions{ ListOptions: gitlab.ListOptions{ Page: page, PerPage: g.maxPerPage, }, - }, nil, gitlab.WithContext(g.ctx)) + }, nil, gitlab.WithContext(ctx)) } if err != nil { return nil, false, fmt.Errorf("error while listing state events: %v %w", g.repoID, err) @@ -604,7 +597,7 @@ func (g *GitlabDownloader) convertNoteToComment(localIndex int64, note *gitlab.N } // GetPullRequests returns pull requests according page and perPage -func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) { +func (g *GitlabDownloader) GetPullRequests(ctx context.Context, page, perPage int) ([]*base.PullRequest, bool, error) { if perPage > g.maxPerPage { perPage = g.maxPerPage } @@ -620,7 +613,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque allPRs := make([]*base.PullRequest, 0, perPage) - prs, _, err := g.client.MergeRequests.ListProjectMergeRequests(g.repoID, opt, nil, gitlab.WithContext(g.ctx)) + prs, _, err := g.client.MergeRequests.ListProjectMergeRequests(g.repoID, opt, nil, gitlab.WithContext(ctx)) if err != nil { return nil, false, fmt.Errorf("error while listing merge requests: %w", err) } @@ -673,7 +666,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque var reactions []*gitlab.AwardEmoji awardPage := 1 for { - awards, _, err := g.client.AwardEmoji.ListMergeRequestAwardEmoji(g.repoID, pr.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx)) + awards, _, err := g.client.AwardEmoji.ListMergeRequestAwardEmoji(g.repoID, pr.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(ctx)) if err != nil { return nil, false, fmt.Errorf("error while listing merge requests awards: %w", err) } @@ -733,8 +726,8 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque } // GetReviews returns pull requests review -func (g *GitlabDownloader) GetReviews(reviewable base.Reviewable) ([]*base.Review, error) { - approvals, resp, err := g.client.MergeRequestApprovals.GetConfiguration(g.repoID, int(reviewable.GetForeignIndex()), gitlab.WithContext(g.ctx)) +func (g *GitlabDownloader) GetReviews(ctx context.Context, reviewable base.Reviewable) ([]*base.Review, error) { + approvals, resp, err := g.client.MergeRequestApprovals.GetConfiguration(g.repoID, int(reviewable.GetForeignIndex()), gitlab.WithContext(ctx)) if err != nil { if resp != nil && resp.StatusCode == http.StatusNotFound { log.Error(fmt.Sprintf("GitlabDownloader: while migrating a error occurred: '%s'", err.Error())) diff --git a/services/migrations/gitlab_test.go b/services/migrations/gitlab_test.go index 556fe771c5..223a3b86d7 100644 --- a/services/migrations/gitlab_test.go +++ b/services/migrations/gitlab_test.go @@ -31,12 +31,12 @@ func TestGitlabDownloadRepo(t *testing.T) { if err != nil || resp.StatusCode != http.StatusOK { t.Skipf("Can't access test repo, skipping %s", t.Name()) } - - downloader, err := NewGitlabDownloader(context.Background(), "https://gitlab.com", "gitea/test_repo", "", "", gitlabPersonalAccessToken) + ctx := context.Background() + downloader, err := NewGitlabDownloader(ctx, "https://gitlab.com", "gitea/test_repo", "", "", gitlabPersonalAccessToken) if err != nil { t.Fatalf("NewGitlabDownloader is nil: %v", err) } - repo, err := downloader.GetRepoInfo() + repo, err := downloader.GetRepoInfo(ctx) assert.NoError(t, err) // Repo Owner is blank in Gitlab Group repos assertRepositoryEqual(t, &base.Repository{ @@ -48,12 +48,12 @@ func TestGitlabDownloadRepo(t *testing.T) { DefaultBranch: "master", }, repo) - topics, err := downloader.GetTopics() + topics, err := downloader.GetTopics(ctx) assert.NoError(t, err) assert.Len(t, topics, 2) assert.EqualValues(t, []string{"migration", "test"}, topics) - milestones, err := downloader.GetMilestones() + milestones, err := downloader.GetMilestones(ctx) assert.NoError(t, err) assertMilestonesEqual(t, []*base.Milestone{ { @@ -71,7 +71,7 @@ func TestGitlabDownloadRepo(t *testing.T) { }, }, milestones) - labels, err := downloader.GetLabels() + labels, err := downloader.GetLabels(ctx) assert.NoError(t, err) assertLabelsEqual(t, []*base.Label{ { @@ -112,7 +112,7 @@ func TestGitlabDownloadRepo(t *testing.T) { }, }, labels) - releases, err := downloader.GetReleases() + releases, err := downloader.GetReleases(ctx) assert.NoError(t, err) assertReleasesEqual(t, []*base.Release{ { @@ -126,7 +126,7 @@ func TestGitlabDownloadRepo(t *testing.T) { }, }, releases) - issues, isEnd, err := downloader.GetIssues(1, 2) + issues, isEnd, err := downloader.GetIssues(ctx, 1, 2) assert.NoError(t, err) assert.False(t, isEnd) @@ -214,7 +214,7 @@ func TestGitlabDownloadRepo(t *testing.T) { }, }, issues) - comments, _, err := downloader.GetComments(&base.Issue{ + comments, _, err := downloader.GetComments(ctx, &base.Issue{ Number: 2, ForeignIndex: 2, Context: gitlabIssueContext{IsMergeRequest: false}, @@ -255,7 +255,7 @@ func TestGitlabDownloadRepo(t *testing.T) { }, }, comments) - prs, _, err := downloader.GetPullRequests(1, 1) + prs, _, err := downloader.GetPullRequests(ctx, 1, 1) assert.NoError(t, err) assertPullRequestsEqual(t, []*base.PullRequest{ { @@ -304,7 +304,7 @@ func TestGitlabDownloadRepo(t *testing.T) { }, }, prs) - rvs, err := downloader.GetReviews(&base.PullRequest{Number: 1, ForeignIndex: 1}) + rvs, err := downloader.GetReviews(ctx, &base.PullRequest{Number: 1, ForeignIndex: 1}) assert.NoError(t, err) assertReviewsEqual(t, []*base.Review{ { @@ -323,7 +323,7 @@ func TestGitlabDownloadRepo(t *testing.T) { }, }, rvs) - rvs, err = downloader.GetReviews(&base.PullRequest{Number: 2, ForeignIndex: 2}) + rvs, err = downloader.GetReviews(ctx, &base.PullRequest{Number: 2, ForeignIndex: 2}) assert.NoError(t, err) assertReviewsEqual(t, []*base.Review{ { @@ -423,9 +423,8 @@ func TestGitlabGetReviews(t *testing.T) { defer gitlabClientMockTeardown(server) repoID := 1324 - + ctx := context.Background() downloader := &GitlabDownloader{ - ctx: context.Background(), client: client, repoID: repoID, } @@ -465,7 +464,7 @@ func TestGitlabGetReviews(t *testing.T) { mux.HandleFunc(fmt.Sprintf("/api/v4/projects/%d/merge_requests/%d/approvals", testCase.repoID, testCase.prID), mock) id := int64(testCase.prID) - rvs, err := downloader.GetReviews(&base.Issue{Number: id, ForeignIndex: id}) + rvs, err := downloader.GetReviews(ctx, &base.Issue{Number: id, ForeignIndex: id}) assert.NoError(t, err) assertReviewsEqual(t, []*base.Review{&review}, rvs) } diff --git a/services/migrations/gogs.go b/services/migrations/gogs.go index 72c52d180b..a4f84dbf72 100644 --- a/services/migrations/gogs.go +++ b/services/migrations/gogs.go @@ -13,7 +13,6 @@ import ( "code.gitea.io/gitea/modules/log" base "code.gitea.io/gitea/modules/migration" - "code.gitea.io/gitea/modules/proxy" "code.gitea.io/gitea/modules/structs" "github.com/gogs/go-gogs-client" @@ -60,16 +59,14 @@ func (f *GogsDownloaderFactory) GitServiceType() structs.GitServiceType { // from gogs via API type GogsDownloader struct { base.NullDownloader - ctx context.Context - client *gogs.Client baseURL string repoOwner string repoName string userName string password string + token string openIssuesFinished bool openIssuesPages int - transport http.RoundTripper } // String implements Stringer @@ -84,53 +81,45 @@ func (g *GogsDownloader) LogString() string { return fmt.Sprintf("", g.baseURL, g.repoOwner, g.repoName) } -// SetContext set context -func (g *GogsDownloader) SetContext(ctx context.Context) { - g.ctx = ctx -} - // NewGogsDownloader creates a gogs Downloader via gogs API -func NewGogsDownloader(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GogsDownloader { +func NewGogsDownloader(_ context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GogsDownloader { downloader := GogsDownloader{ - ctx: ctx, baseURL: baseURL, userName: userName, password: password, + token: token, repoOwner: repoOwner, repoName: repoName, } - - var client *gogs.Client - if len(token) != 0 { - client = gogs.NewClient(baseURL, token) - downloader.userName = token - } else { - transport := NewMigrationHTTPTransport() - transport.Proxy = func(req *http.Request) (*url.URL, error) { - req.SetBasicAuth(userName, password) - return proxy.Proxy()(req) - } - downloader.transport = transport - - client = gogs.NewClient(baseURL, "") - client.SetHTTPClient(&http.Client{ - Transport: &downloader, - }) - } - - downloader.client = client return &downloader } -// RoundTrip wraps the provided request within this downloader's context and passes it to our internal http.Transport. -// This implements http.RoundTripper and makes the gogs client requests cancellable even though it is not cancellable itself -func (g *GogsDownloader) RoundTrip(req *http.Request) (*http.Response, error) { - return g.transport.RoundTrip(req.WithContext(g.ctx)) +type roundTripperFunc func(req *http.Request) (*http.Response, error) + +func (rt roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { + return rt(r) +} + +func (g *GogsDownloader) client(ctx context.Context) *gogs.Client { + // Gogs client lacks the context support, so we use a custom transport + // Then each request uses a dedicated client with its own context + httpTransport := NewMigrationHTTPTransport() + gogsClient := gogs.NewClient(g.baseURL, g.token) + gogsClient.SetHTTPClient(&http.Client{ + Transport: roundTripperFunc(func(req *http.Request) (*http.Response, error) { + if g.password != "" { + // Gogs client lacks the support for basic auth, this is the only way to set it + req.SetBasicAuth(g.userName, g.password) + } + return httpTransport.RoundTrip(req.WithContext(ctx)) + }), + }) + return gogsClient } // GetRepoInfo returns a repository information -func (g *GogsDownloader) GetRepoInfo() (*base.Repository, error) { - gr, err := g.client.GetRepo(g.repoOwner, g.repoName) +func (g *GogsDownloader) GetRepoInfo(ctx context.Context) (*base.Repository, error) { + gr, err := g.client(ctx).GetRepo(g.repoOwner, g.repoName) if err != nil { return nil, err } @@ -148,11 +137,11 @@ func (g *GogsDownloader) GetRepoInfo() (*base.Repository, error) { } // GetMilestones returns milestones -func (g *GogsDownloader) GetMilestones() ([]*base.Milestone, error) { +func (g *GogsDownloader) GetMilestones(ctx context.Context) ([]*base.Milestone, error) { perPage := 100 milestones := make([]*base.Milestone, 0, perPage) - ms, err := g.client.ListRepoMilestones(g.repoOwner, g.repoName) + ms, err := g.client(ctx).ListRepoMilestones(g.repoOwner, g.repoName) if err != nil { return nil, err } @@ -171,10 +160,10 @@ func (g *GogsDownloader) GetMilestones() ([]*base.Milestone, error) { } // GetLabels returns labels -func (g *GogsDownloader) GetLabels() ([]*base.Label, error) { +func (g *GogsDownloader) GetLabels(ctx context.Context) ([]*base.Label, error) { perPage := 100 labels := make([]*base.Label, 0, perPage) - ls, err := g.client.ListRepoLabels(g.repoOwner, g.repoName) + ls, err := g.client(ctx).ListRepoLabels(g.repoOwner, g.repoName) if err != nil { return nil, err } @@ -187,7 +176,7 @@ func (g *GogsDownloader) GetLabels() ([]*base.Label, error) { } // GetIssues returns issues according start and limit, perPage is not supported -func (g *GogsDownloader) GetIssues(page, _ int) ([]*base.Issue, bool, error) { +func (g *GogsDownloader) GetIssues(ctx context.Context, page, _ int) ([]*base.Issue, bool, error) { var state string if g.openIssuesFinished { state = string(gogs.STATE_CLOSED) @@ -197,7 +186,7 @@ func (g *GogsDownloader) GetIssues(page, _ int) ([]*base.Issue, bool, error) { g.openIssuesPages = page } - issues, isEnd, err := g.getIssues(page, state) + issues, isEnd, err := g.getIssues(ctx, page, state) if err != nil { return nil, false, err } @@ -212,10 +201,10 @@ func (g *GogsDownloader) GetIssues(page, _ int) ([]*base.Issue, bool, error) { return issues, false, nil } -func (g *GogsDownloader) getIssues(page int, state string) ([]*base.Issue, bool, error) { +func (g *GogsDownloader) getIssues(ctx context.Context, page int, state string) ([]*base.Issue, bool, error) { allIssues := make([]*base.Issue, 0, 10) - issues, err := g.client.ListRepoIssues(g.repoOwner, g.repoName, gogs.ListIssueOption{ + issues, err := g.client(ctx).ListRepoIssues(g.repoOwner, g.repoName, gogs.ListIssueOption{ Page: page, State: state, }) @@ -234,10 +223,10 @@ func (g *GogsDownloader) getIssues(page int, state string) ([]*base.Issue, bool, } // GetComments returns comments according issueNumber -func (g *GogsDownloader) GetComments(commentable base.Commentable) ([]*base.Comment, bool, error) { +func (g *GogsDownloader) GetComments(ctx context.Context, commentable base.Commentable) ([]*base.Comment, bool, error) { allComments := make([]*base.Comment, 0, 100) - comments, err := g.client.ListIssueComments(g.repoOwner, g.repoName, commentable.GetForeignIndex()) + comments, err := g.client(ctx).ListIssueComments(g.repoOwner, g.repoName, commentable.GetForeignIndex()) if err != nil { return nil, false, fmt.Errorf("error while listing repos: %w", err) } @@ -261,7 +250,7 @@ func (g *GogsDownloader) GetComments(commentable base.Commentable) ([]*base.Comm } // GetTopics return repository topics -func (g *GogsDownloader) GetTopics() ([]string, error) { +func (g *GogsDownloader) GetTopics(_ context.Context) ([]string, error) { return []string{}, nil } diff --git a/services/migrations/gogs_test.go b/services/migrations/gogs_test.go index 610af183de..91c36bdcc6 100644 --- a/services/migrations/gogs_test.go +++ b/services/migrations/gogs_test.go @@ -28,9 +28,9 @@ func TestGogsDownloadRepo(t *testing.T) { t.Skipf("visit test repo failed, ignored") return } - - downloader := NewGogsDownloader(context.Background(), "https://try.gogs.io", "", "", gogsPersonalAccessToken, "lunnytest", "TESTREPO") - repo, err := downloader.GetRepoInfo() + ctx := context.Background() + downloader := NewGogsDownloader(ctx, "https://try.gogs.io", "", "", gogsPersonalAccessToken, "lunnytest", "TESTREPO") + repo, err := downloader.GetRepoInfo(ctx) assert.NoError(t, err) assertRepositoryEqual(t, &base.Repository{ @@ -42,7 +42,7 @@ func TestGogsDownloadRepo(t *testing.T) { DefaultBranch: "master", }, repo) - milestones, err := downloader.GetMilestones() + milestones, err := downloader.GetMilestones(ctx) assert.NoError(t, err) assertMilestonesEqual(t, []*base.Milestone{ { @@ -51,7 +51,7 @@ func TestGogsDownloadRepo(t *testing.T) { }, }, milestones) - labels, err := downloader.GetLabels() + labels, err := downloader.GetLabels(ctx) assert.NoError(t, err) assertLabelsEqual(t, []*base.Label{ { @@ -85,7 +85,7 @@ func TestGogsDownloadRepo(t *testing.T) { }, labels) // downloader.GetIssues() - issues, isEnd, err := downloader.GetIssues(1, 8) + issues, isEnd, err := downloader.GetIssues(ctx, 1, 8) assert.NoError(t, err) assert.False(t, isEnd) assertIssuesEqual(t, []*base.Issue{ @@ -110,7 +110,7 @@ func TestGogsDownloadRepo(t *testing.T) { }, issues) // downloader.GetComments() - comments, _, err := downloader.GetComments(&base.Issue{Number: 1, ForeignIndex: 1}) + comments, _, err := downloader.GetComments(ctx, &base.Issue{Number: 1, ForeignIndex: 1}) assert.NoError(t, err) assertCommentsEqual(t, []*base.Comment{ { @@ -134,6 +134,6 @@ func TestGogsDownloadRepo(t *testing.T) { }, comments) // downloader.GetPullRequests() - _, _, err = downloader.GetPullRequests(1, 3) + _, _, err = downloader.GetPullRequests(ctx, 1, 3) assert.Error(t, err) } diff --git a/services/migrations/migrate.go b/services/migrations/migrate.go index 51b22d6111..8319fd541b 100644 --- a/services/migrations/migrate.go +++ b/services/migrations/migrate.go @@ -176,12 +176,12 @@ func newDownloader(ctx context.Context, ownerName string, opts base.MigrateOptio // migrateRepository will download information and then upload it to Uploader, this is a simple // process for small repository. For a big repository, save all the data to disk // before upload is better -func migrateRepository(_ context.Context, doer *user_model.User, downloader base.Downloader, uploader base.Uploader, opts base.MigrateOptions, messenger base.Messenger) error { +func migrateRepository(ctx context.Context, doer *user_model.User, downloader base.Downloader, uploader base.Uploader, opts base.MigrateOptions, messenger base.Messenger) error { if messenger == nil { messenger = base.NilMessenger } - repo, err := downloader.GetRepoInfo() + repo, err := downloader.GetRepoInfo(ctx) if err != nil { if !base.IsErrNotSupported(err) { return err @@ -220,14 +220,14 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base log.Trace("migrating git data from %s", repo.CloneURL) messenger("repo.migrate.migrating_git") - if err = uploader.CreateRepo(repo, opts); err != nil { + if err = uploader.CreateRepo(ctx, repo, opts); err != nil { return err } defer uploader.Close() log.Trace("migrating topics") messenger("repo.migrate.migrating_topics") - topics, err := downloader.GetTopics() + topics, err := downloader.GetTopics(ctx) if err != nil { if !base.IsErrNotSupported(err) { return err @@ -235,7 +235,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base log.Warn("migrating topics is not supported, ignored") } if len(topics) != 0 { - if err = uploader.CreateTopics(topics...); err != nil { + if err = uploader.CreateTopics(ctx, topics...); err != nil { return err } } @@ -243,7 +243,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base if opts.Milestones { log.Trace("migrating milestones") messenger("repo.migrate.migrating_milestones") - milestones, err := downloader.GetMilestones() + milestones, err := downloader.GetMilestones(ctx) if err != nil { if !base.IsErrNotSupported(err) { return err @@ -256,7 +256,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base msBatchSize = len(milestones) } - if err := uploader.CreateMilestones(milestones[:msBatchSize]...); err != nil { + if err := uploader.CreateMilestones(ctx, milestones[:msBatchSize]...); err != nil { return err } milestones = milestones[msBatchSize:] @@ -266,7 +266,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base if opts.Labels { log.Trace("migrating labels") messenger("repo.migrate.migrating_labels") - labels, err := downloader.GetLabels() + labels, err := downloader.GetLabels(ctx) if err != nil { if !base.IsErrNotSupported(err) { return err @@ -280,7 +280,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base lbBatchSize = len(labels) } - if err := uploader.CreateLabels(labels[:lbBatchSize]...); err != nil { + if err := uploader.CreateLabels(ctx, labels[:lbBatchSize]...); err != nil { return err } labels = labels[lbBatchSize:] @@ -290,7 +290,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base if opts.Releases { log.Trace("migrating releases") messenger("repo.migrate.migrating_releases") - releases, err := downloader.GetReleases() + releases, err := downloader.GetReleases(ctx) if err != nil { if !base.IsErrNotSupported(err) { return err @@ -304,14 +304,14 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base relBatchSize = len(releases) } - if err = uploader.CreateReleases(releases[:relBatchSize]...); err != nil { + if err = uploader.CreateReleases(ctx, releases[:relBatchSize]...); err != nil { return err } releases = releases[relBatchSize:] } // Once all releases (if any) are inserted, sync any remaining non-release tags - if err = uploader.SyncTags(); err != nil { + if err = uploader.SyncTags(ctx); err != nil { return err } } @@ -329,7 +329,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base issueBatchSize := uploader.MaxBatchInsertSize("issue") for i := 1; ; i++ { - issues, isEnd, err := downloader.GetIssues(i, issueBatchSize) + issues, isEnd, err := downloader.GetIssues(ctx, i, issueBatchSize) if err != nil { if !base.IsErrNotSupported(err) { return err @@ -338,7 +338,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base break } - if err := uploader.CreateIssues(issues...); err != nil { + if err := uploader.CreateIssues(ctx, issues...); err != nil { return err } @@ -346,7 +346,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base allComments := make([]*base.Comment, 0, commentBatchSize) for _, issue := range issues { log.Trace("migrating issue %d's comments", issue.Number) - comments, _, err := downloader.GetComments(issue) + comments, _, err := downloader.GetComments(ctx, issue) if err != nil { if !base.IsErrNotSupported(err) { return err @@ -357,7 +357,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base allComments = append(allComments, comments...) if len(allComments) >= commentBatchSize { - if err = uploader.CreateComments(allComments[:commentBatchSize]...); err != nil { + if err = uploader.CreateComments(ctx, allComments[:commentBatchSize]...); err != nil { return err } @@ -366,7 +366,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base } if len(allComments) > 0 { - if err = uploader.CreateComments(allComments...); err != nil { + if err = uploader.CreateComments(ctx, allComments...); err != nil { return err } } @@ -383,7 +383,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base messenger("repo.migrate.migrating_pulls") prBatchSize := uploader.MaxBatchInsertSize("pullrequest") for i := 1; ; i++ { - prs, isEnd, err := downloader.GetPullRequests(i, prBatchSize) + prs, isEnd, err := downloader.GetPullRequests(ctx, i, prBatchSize) if err != nil { if !base.IsErrNotSupported(err) { return err @@ -392,7 +392,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base break } - if err := uploader.CreatePullRequests(prs...); err != nil { + if err := uploader.CreatePullRequests(ctx, prs...); err != nil { return err } @@ -402,7 +402,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base allComments := make([]*base.Comment, 0, commentBatchSize) for _, pr := range prs { log.Trace("migrating pull request %d's comments", pr.Number) - comments, _, err := downloader.GetComments(pr) + comments, _, err := downloader.GetComments(ctx, pr) if err != nil { if !base.IsErrNotSupported(err) { return err @@ -413,14 +413,14 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base allComments = append(allComments, comments...) if len(allComments) >= commentBatchSize { - if err = uploader.CreateComments(allComments[:commentBatchSize]...); err != nil { + if err = uploader.CreateComments(ctx, allComments[:commentBatchSize]...); err != nil { return err } allComments = allComments[commentBatchSize:] } } if len(allComments) > 0 { - if err = uploader.CreateComments(allComments...); err != nil { + if err = uploader.CreateComments(ctx, allComments...); err != nil { return err } } @@ -429,7 +429,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base // migrate reviews allReviews := make([]*base.Review, 0, reviewBatchSize) for _, pr := range prs { - reviews, err := downloader.GetReviews(pr) + reviews, err := downloader.GetReviews(ctx, pr) if err != nil { if !base.IsErrNotSupported(err) { return err @@ -441,14 +441,14 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base allReviews = append(allReviews, reviews...) if len(allReviews) >= reviewBatchSize { - if err = uploader.CreateReviews(allReviews[:reviewBatchSize]...); err != nil { + if err = uploader.CreateReviews(ctx, allReviews[:reviewBatchSize]...); err != nil { return err } allReviews = allReviews[reviewBatchSize:] } } if len(allReviews) > 0 { - if err = uploader.CreateReviews(allReviews...); err != nil { + if err = uploader.CreateReviews(ctx, allReviews...); err != nil { return err } } @@ -463,12 +463,12 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base if opts.Comments && supportAllComments { log.Trace("migrating comments") for i := 1; ; i++ { - comments, isEnd, err := downloader.GetAllComments(i, commentBatchSize) + comments, isEnd, err := downloader.GetAllComments(ctx, i, commentBatchSize) if err != nil { return err } - if err := uploader.CreateComments(comments...); err != nil { + if err := uploader.CreateComments(ctx, comments...); err != nil { return err } @@ -478,7 +478,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base } } - return uploader.Finish() + return uploader.Finish(ctx) } // Init migrations service diff --git a/services/migrations/onedev.go b/services/migrations/onedev.go index e2f7b771f3..4ce35dd12e 100644 --- a/services/migrations/onedev.go +++ b/services/migrations/onedev.go @@ -71,7 +71,6 @@ type onedevUser struct { // from OneDev type OneDevDownloader struct { base.NullDownloader - ctx context.Context client *http.Client baseURL *url.URL repoName string @@ -81,15 +80,9 @@ type OneDevDownloader struct { milestoneMap map[int64]string } -// SetContext set context -func (d *OneDevDownloader) SetContext(ctx context.Context) { - d.ctx = ctx -} - // NewOneDevDownloader creates a new downloader -func NewOneDevDownloader(ctx context.Context, baseURL *url.URL, username, password, repoName string) *OneDevDownloader { +func NewOneDevDownloader(_ context.Context, baseURL *url.URL, username, password, repoName string) *OneDevDownloader { downloader := &OneDevDownloader{ - ctx: ctx, baseURL: baseURL, repoName: repoName, client: &http.Client{ @@ -121,7 +114,7 @@ func (d *OneDevDownloader) LogString() string { return fmt.Sprintf("", d.baseURL, d.repoID, d.repoName) } -func (d *OneDevDownloader) callAPI(endpoint string, parameter map[string]string, result any) error { +func (d *OneDevDownloader) callAPI(ctx context.Context, endpoint string, parameter map[string]string, result any) error { u, err := d.baseURL.Parse(endpoint) if err != nil { return err @@ -135,7 +128,7 @@ func (d *OneDevDownloader) callAPI(endpoint string, parameter map[string]string, u.RawQuery = query.Encode() } - req, err := http.NewRequestWithContext(d.ctx, "GET", u.String(), nil) + req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) if err != nil { return err } @@ -151,7 +144,7 @@ func (d *OneDevDownloader) callAPI(endpoint string, parameter map[string]string, } // GetRepoInfo returns repository information -func (d *OneDevDownloader) GetRepoInfo() (*base.Repository, error) { +func (d *OneDevDownloader) GetRepoInfo(ctx context.Context) (*base.Repository, error) { info := make([]struct { ID int64 `json:"id"` Name string `json:"name"` @@ -159,6 +152,7 @@ func (d *OneDevDownloader) GetRepoInfo() (*base.Repository, error) { }, 0, 1) err := d.callAPI( + ctx, "/api/projects", map[string]string{ "query": `"Name" is "` + d.repoName + `"`, @@ -194,7 +188,7 @@ func (d *OneDevDownloader) GetRepoInfo() (*base.Repository, error) { } // GetMilestones returns milestones -func (d *OneDevDownloader) GetMilestones() ([]*base.Milestone, error) { +func (d *OneDevDownloader) GetMilestones(ctx context.Context) ([]*base.Milestone, error) { rawMilestones := make([]struct { ID int64 `json:"id"` Name string `json:"name"` @@ -209,6 +203,7 @@ func (d *OneDevDownloader) GetMilestones() ([]*base.Milestone, error) { offset := 0 for { err := d.callAPI( + ctx, endpoint, map[string]string{ "offset": strconv.Itoa(offset), @@ -243,7 +238,7 @@ func (d *OneDevDownloader) GetMilestones() ([]*base.Milestone, error) { } // GetLabels returns labels -func (d *OneDevDownloader) GetLabels() ([]*base.Label, error) { +func (d *OneDevDownloader) GetLabels(_ context.Context) ([]*base.Label, error) { return []*base.Label{ { Name: "Bug", @@ -277,7 +272,7 @@ type onedevIssueContext struct { } // GetIssues returns issues -func (d *OneDevDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) { +func (d *OneDevDownloader) GetIssues(ctx context.Context, page, perPage int) ([]*base.Issue, bool, error) { rawIssues := make([]struct { ID int64 `json:"id"` Number int64 `json:"number"` @@ -289,6 +284,7 @@ func (d *OneDevDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er }, 0, perPage) err := d.callAPI( + ctx, "/api/issues", map[string]string{ "query": `"Project" is "` + d.repoName + `"`, @@ -308,6 +304,7 @@ func (d *OneDevDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er Value string `json:"value"` }, 0, 10) err := d.callAPI( + ctx, fmt.Sprintf("/api/issues/%d/fields", issue.ID), nil, &fields, @@ -329,6 +326,7 @@ func (d *OneDevDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er Name string `json:"name"` }, 0, 10) err = d.callAPI( + ctx, fmt.Sprintf("/api/issues/%d/milestones", issue.ID), nil, &milestones, @@ -345,7 +343,7 @@ func (d *OneDevDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er if state == "released" { state = "closed" } - poster := d.tryGetUser(issue.SubmitterID) + poster := d.tryGetUser(ctx, issue.SubmitterID) issues = append(issues, &base.Issue{ Title: issue.Title, Number: issue.Number, @@ -370,7 +368,7 @@ func (d *OneDevDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er } // GetComments returns comments -func (d *OneDevDownloader) GetComments(commentable base.Commentable) ([]*base.Comment, bool, error) { +func (d *OneDevDownloader) GetComments(ctx context.Context, commentable base.Commentable) ([]*base.Comment, bool, error) { context, ok := commentable.GetContext().(onedevIssueContext) if !ok { return nil, false, fmt.Errorf("unexpected context: %+v", commentable.GetContext()) @@ -391,6 +389,7 @@ func (d *OneDevDownloader) GetComments(commentable base.Commentable) ([]*base.Co } err := d.callAPI( + ctx, endpoint, nil, &rawComments, @@ -412,6 +411,7 @@ func (d *OneDevDownloader) GetComments(commentable base.Commentable) ([]*base.Co } err = d.callAPI( + ctx, endpoint, nil, &rawChanges, @@ -425,7 +425,7 @@ func (d *OneDevDownloader) GetComments(commentable base.Commentable) ([]*base.Co if len(comment.Content) == 0 { continue } - poster := d.tryGetUser(comment.UserID) + poster := d.tryGetUser(ctx, comment.UserID) comments = append(comments, &base.Comment{ IssueIndex: commentable.GetLocalIndex(), Index: comment.ID, @@ -450,7 +450,7 @@ func (d *OneDevDownloader) GetComments(commentable base.Commentable) ([]*base.Co continue } - poster := d.tryGetUser(change.UserID) + poster := d.tryGetUser(ctx, change.UserID) comments = append(comments, &base.Comment{ IssueIndex: commentable.GetLocalIndex(), PosterID: poster.ID, @@ -466,7 +466,7 @@ func (d *OneDevDownloader) GetComments(commentable base.Commentable) ([]*base.Co } // GetPullRequests returns pull requests -func (d *OneDevDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) { +func (d *OneDevDownloader) GetPullRequests(ctx context.Context, page, perPage int) ([]*base.PullRequest, bool, error) { rawPullRequests := make([]struct { ID int64 `json:"id"` Number int64 `json:"number"` @@ -484,6 +484,7 @@ func (d *OneDevDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque }, 0, perPage) err := d.callAPI( + ctx, "/api/pull-requests", map[string]string{ "query": `"Target Project" is "` + d.repoName + `"`, @@ -505,6 +506,7 @@ func (d *OneDevDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque MergeCommitHash string `json:"mergeCommitHash"` } err := d.callAPI( + ctx, fmt.Sprintf("/api/pull-requests/%d/merge-preview", pr.ID), nil, &mergePreview, @@ -525,7 +527,7 @@ func (d *OneDevDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque mergedTime = pr.CloseInfo.Date } } - poster := d.tryGetUser(pr.SubmitterID) + poster := d.tryGetUser(ctx, pr.SubmitterID) number := pr.Number + d.maxIssueIndex pullRequests = append(pullRequests, &base.PullRequest{ @@ -562,7 +564,7 @@ func (d *OneDevDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque } // GetReviews returns pull requests reviews -func (d *OneDevDownloader) GetReviews(reviewable base.Reviewable) ([]*base.Review, error) { +func (d *OneDevDownloader) GetReviews(ctx context.Context, reviewable base.Reviewable) ([]*base.Review, error) { rawReviews := make([]struct { ID int64 `json:"id"` UserID int64 `json:"userId"` @@ -574,6 +576,7 @@ func (d *OneDevDownloader) GetReviews(reviewable base.Reviewable) ([]*base.Revie }, 0, 100) err := d.callAPI( + ctx, fmt.Sprintf("/api/pull-requests/%d/reviews", reviewable.GetForeignIndex()), nil, &rawReviews, @@ -596,7 +599,7 @@ func (d *OneDevDownloader) GetReviews(reviewable base.Reviewable) ([]*base.Revie } } - poster := d.tryGetUser(review.UserID) + poster := d.tryGetUser(ctx, review.UserID) reviews = append(reviews, &base.Review{ IssueIndex: reviewable.GetLocalIndex(), ReviewerID: poster.ID, @@ -610,14 +613,15 @@ func (d *OneDevDownloader) GetReviews(reviewable base.Reviewable) ([]*base.Revie } // GetTopics return repository topics -func (d *OneDevDownloader) GetTopics() ([]string, error) { +func (d *OneDevDownloader) GetTopics(_ context.Context) ([]string, error) { return []string{}, nil } -func (d *OneDevDownloader) tryGetUser(userID int64) *onedevUser { +func (d *OneDevDownloader) tryGetUser(ctx context.Context, userID int64) *onedevUser { user, ok := d.userMap[userID] if !ok { err := d.callAPI( + ctx, fmt.Sprintf("/api/users/%d", userID), nil, &user, diff --git a/services/migrations/onedev_test.go b/services/migrations/onedev_test.go index 48412fec64..0a4b05446d 100644 --- a/services/migrations/onedev_test.go +++ b/services/migrations/onedev_test.go @@ -22,11 +22,12 @@ func TestOneDevDownloadRepo(t *testing.T) { } u, _ := url.Parse("https://code.onedev.io") - downloader := NewOneDevDownloader(context.Background(), u, "", "", "go-gitea-test_repo") + ctx := context.Background() + downloader := NewOneDevDownloader(ctx, u, "", "", "go-gitea-test_repo") if err != nil { t.Fatalf("NewOneDevDownloader is nil: %v", err) } - repo, err := downloader.GetRepoInfo() + repo, err := downloader.GetRepoInfo(ctx) assert.NoError(t, err) assertRepositoryEqual(t, &base.Repository{ Name: "go-gitea-test_repo", @@ -36,7 +37,7 @@ func TestOneDevDownloadRepo(t *testing.T) { OriginalURL: "https://code.onedev.io/projects/go-gitea-test_repo", }, repo) - milestones, err := downloader.GetMilestones() + milestones, err := downloader.GetMilestones(ctx) assert.NoError(t, err) deadline := time.Unix(1620086400, 0) assertMilestonesEqual(t, []*base.Milestone{ @@ -51,11 +52,11 @@ func TestOneDevDownloadRepo(t *testing.T) { }, }, milestones) - labels, err := downloader.GetLabels() + labels, err := downloader.GetLabels(ctx) assert.NoError(t, err) assert.Len(t, labels, 6) - issues, isEnd, err := downloader.GetIssues(1, 2) + issues, isEnd, err := downloader.GetIssues(ctx, 1, 2) assert.NoError(t, err) assert.False(t, isEnd) assertIssuesEqual(t, []*base.Issue{ @@ -94,7 +95,7 @@ func TestOneDevDownloadRepo(t *testing.T) { }, }, issues) - comments, _, err := downloader.GetComments(&base.Issue{ + comments, _, err := downloader.GetComments(ctx, &base.Issue{ Number: 4, ForeignIndex: 398, Context: onedevIssueContext{IsPullRequest: false}, @@ -110,7 +111,7 @@ func TestOneDevDownloadRepo(t *testing.T) { }, }, comments) - prs, _, err := downloader.GetPullRequests(1, 1) + prs, _, err := downloader.GetPullRequests(ctx, 1, 1) assert.NoError(t, err) assertPullRequestsEqual(t, []*base.PullRequest{ { @@ -136,7 +137,7 @@ func TestOneDevDownloadRepo(t *testing.T) { }, }, prs) - rvs, err := downloader.GetReviews(&base.PullRequest{Number: 5, ForeignIndex: 186}) + rvs, err := downloader.GetReviews(ctx, &base.PullRequest{Number: 5, ForeignIndex: 186}) assert.NoError(t, err) assertReviewsEqual(t, []*base.Review{ { diff --git a/services/migrations/restore.go b/services/migrations/restore.go index fd337b22c7..5686285935 100644 --- a/services/migrations/restore.go +++ b/services/migrations/restore.go @@ -18,7 +18,6 @@ import ( // RepositoryRestorer implements an Downloader from the local directory type RepositoryRestorer struct { base.NullDownloader - ctx context.Context baseDir string repoOwner string repoName string @@ -26,13 +25,12 @@ type RepositoryRestorer struct { } // NewRepositoryRestorer creates a repository restorer which could restore repository from a dumped folder -func NewRepositoryRestorer(ctx context.Context, baseDir, owner, repoName string, validation bool) (*RepositoryRestorer, error) { +func NewRepositoryRestorer(_ context.Context, baseDir, owner, repoName string, validation bool) (*RepositoryRestorer, error) { baseDir, err := filepath.Abs(baseDir) if err != nil { return nil, err } return &RepositoryRestorer{ - ctx: ctx, baseDir: baseDir, repoOwner: owner, repoName: repoName, @@ -48,11 +46,6 @@ func (r *RepositoryRestorer) reviewDir() string { return filepath.Join(r.baseDir, "reviews") } -// SetContext set context -func (r *RepositoryRestorer) SetContext(ctx context.Context) { - r.ctx = ctx -} - func (r *RepositoryRestorer) getRepoOptions() (map[string]string, error) { p := filepath.Join(r.baseDir, "repo.yml") bs, err := os.ReadFile(p) @@ -69,7 +62,7 @@ func (r *RepositoryRestorer) getRepoOptions() (map[string]string, error) { } // GetRepoInfo returns a repository information -func (r *RepositoryRestorer) GetRepoInfo() (*base.Repository, error) { +func (r *RepositoryRestorer) GetRepoInfo(_ context.Context) (*base.Repository, error) { opts, err := r.getRepoOptions() if err != nil { return nil, err @@ -89,7 +82,7 @@ func (r *RepositoryRestorer) GetRepoInfo() (*base.Repository, error) { } // GetTopics return github topics -func (r *RepositoryRestorer) GetTopics() ([]string, error) { +func (r *RepositoryRestorer) GetTopics(_ context.Context) ([]string, error) { p := filepath.Join(r.baseDir, "topic.yml") topics := struct { @@ -112,7 +105,7 @@ func (r *RepositoryRestorer) GetTopics() ([]string, error) { } // GetMilestones returns milestones -func (r *RepositoryRestorer) GetMilestones() ([]*base.Milestone, error) { +func (r *RepositoryRestorer) GetMilestones(_ context.Context) ([]*base.Milestone, error) { milestones := make([]*base.Milestone, 0, 10) p := filepath.Join(r.baseDir, "milestone.yml") err := base.Load(p, &milestones, r.validation) @@ -127,7 +120,7 @@ func (r *RepositoryRestorer) GetMilestones() ([]*base.Milestone, error) { } // GetReleases returns releases -func (r *RepositoryRestorer) GetReleases() ([]*base.Release, error) { +func (r *RepositoryRestorer) GetReleases(_ context.Context) ([]*base.Release, error) { releases := make([]*base.Release, 0, 10) p := filepath.Join(r.baseDir, "release.yml") _, err := os.Stat(p) @@ -158,7 +151,7 @@ func (r *RepositoryRestorer) GetReleases() ([]*base.Release, error) { } // GetLabels returns labels -func (r *RepositoryRestorer) GetLabels() ([]*base.Label, error) { +func (r *RepositoryRestorer) GetLabels(_ context.Context) ([]*base.Label, error) { labels := make([]*base.Label, 0, 10) p := filepath.Join(r.baseDir, "label.yml") _, err := os.Stat(p) @@ -182,7 +175,7 @@ func (r *RepositoryRestorer) GetLabels() ([]*base.Label, error) { } // GetIssues returns issues according start and limit -func (r *RepositoryRestorer) GetIssues(page, perPage int) ([]*base.Issue, bool, error) { +func (r *RepositoryRestorer) GetIssues(_ context.Context, _, _ int) ([]*base.Issue, bool, error) { issues := make([]*base.Issue, 0, 10) p := filepath.Join(r.baseDir, "issue.yml") err := base.Load(p, &issues, r.validation) @@ -196,7 +189,7 @@ func (r *RepositoryRestorer) GetIssues(page, perPage int) ([]*base.Issue, bool, } // GetComments returns comments according issueNumber -func (r *RepositoryRestorer) GetComments(commentable base.Commentable) ([]*base.Comment, bool, error) { +func (r *RepositoryRestorer) GetComments(_ context.Context, commentable base.Commentable) ([]*base.Comment, bool, error) { comments := make([]*base.Comment, 0, 10) p := filepath.Join(r.commentDir(), fmt.Sprintf("%d.yml", commentable.GetForeignIndex())) _, err := os.Stat(p) @@ -220,7 +213,7 @@ func (r *RepositoryRestorer) GetComments(commentable base.Commentable) ([]*base. } // GetPullRequests returns pull requests according page and perPage -func (r *RepositoryRestorer) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) { +func (r *RepositoryRestorer) GetPullRequests(_ context.Context, page, perPage int) ([]*base.PullRequest, bool, error) { pulls := make([]*base.PullRequest, 0, 10) p := filepath.Join(r.baseDir, "pull_request.yml") _, err := os.Stat(p) @@ -248,7 +241,7 @@ func (r *RepositoryRestorer) GetPullRequests(page, perPage int) ([]*base.PullReq } // GetReviews returns pull requests review -func (r *RepositoryRestorer) GetReviews(reviewable base.Reviewable) ([]*base.Review, error) { +func (r *RepositoryRestorer) GetReviews(ctx context.Context, reviewable base.Reviewable) ([]*base.Review, error) { reviews := make([]*base.Review, 0, 10) p := filepath.Join(r.reviewDir(), fmt.Sprintf("%d.yml", reviewable.GetForeignIndex())) _, err := os.Stat(p) diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index 24605cfae0..4f40913f67 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -126,7 +126,9 @@ func parseRemoteUpdateOutput(output, remoteName string) []*mirrorSyncResult { case strings.HasPrefix(lines[i], " - "): // Delete reference isTag := !strings.HasPrefix(refName, remoteName+"/") var refFullName git.RefName - if isTag { + if strings.HasPrefix(refName, "refs/") { + refFullName = git.RefName(refName) + } else if isTag { refFullName = git.RefNameFromTag(refName) } else { refFullName = git.RefNameFromBranch(strings.TrimPrefix(refName, remoteName+"/")) @@ -149,8 +151,15 @@ func parseRemoteUpdateOutput(output, remoteName string) []*mirrorSyncResult { log.Error("Expect two SHAs but not what found: %q", lines[i]) continue } + var refFullName git.RefName + if strings.HasPrefix(refName, "refs/") { + refFullName = git.RefName(refName) + } else { + refFullName = git.RefNameFromBranch(strings.TrimPrefix(refName, remoteName+"/")) + } + results = append(results, &mirrorSyncResult{ - refName: git.RefNameFromBranch(strings.TrimPrefix(refName, remoteName+"/")), + refName: refFullName, oldCommitID: shas[0], newCommitID: shas[1], }) diff --git a/services/mirror/mirror_push.go b/services/mirror/mirror_push.go index 02ff97b1f0..89ebda60f2 100644 --- a/services/mirror/mirror_push.go +++ b/services/mirror/mirror_push.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" + "code.gitea.io/gitea/modules/proxy" "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -161,11 +162,13 @@ func runPushSync(ctx context.Context, m *repo_model.PushMirror) error { log.Trace("Pushing %s mirror[%d] remote %s", path, m.ID, m.RemoteName) + envs := proxy.EnvWithProxy(remoteURL.URL) if err := git.Push(ctx, path, git.PushOptions{ Remote: m.RemoteName, Force: true, Mirror: true, Timeout: timeout, + Env: envs, }); err != nil { log.Error("Error pushing %s mirror[%d] remote %s: %v", path, m.ID, m.RemoteName, err) diff --git a/services/mirror/mirror_test.go b/services/mirror/mirror_test.go index 8ad524b608..76632b6872 100644 --- a/services/mirror/mirror_test.go +++ b/services/mirror/mirror_test.go @@ -17,9 +17,13 @@ func Test_parseRemoteUpdateOutput(t *testing.T) { - [deleted] (none) -> tag1 + f895a1e...957a993 test2 -> origin/test2 (forced update) 957a993..a87ba5f test3 -> origin/test3 + * [new ref] refs/pull/26595/head -> refs/pull/26595/head + * [new ref] refs/pull/26595/merge -> refs/pull/26595/merge + e0639e38fb..6db2410489 refs/pull/25873/head -> refs/pull/25873/head + + 1c97ebc746...976d27d52f refs/pull/25873/merge -> refs/pull/25873/merge (forced update) ` results := parseRemoteUpdateOutput(output, "origin") - assert.Len(t, results, 6) + assert.Len(t, results, 10) assert.EqualValues(t, "refs/tags/v0.1.8", results[0].refName.String()) assert.EqualValues(t, gitShortEmptySha, results[0].oldCommitID) assert.EqualValues(t, "", results[0].newCommitID) @@ -43,4 +47,20 @@ func Test_parseRemoteUpdateOutput(t *testing.T) { assert.EqualValues(t, "refs/heads/test3", results[5].refName.String()) assert.EqualValues(t, "957a993", results[5].oldCommitID) assert.EqualValues(t, "a87ba5f", results[5].newCommitID) + + assert.EqualValues(t, "refs/pull/26595/head", results[6].refName.String()) + assert.EqualValues(t, gitShortEmptySha, results[6].oldCommitID) + assert.EqualValues(t, "", results[6].newCommitID) + + assert.EqualValues(t, "refs/pull/26595/merge", results[7].refName.String()) + assert.EqualValues(t, gitShortEmptySha, results[7].oldCommitID) + assert.EqualValues(t, "", results[7].newCommitID) + + assert.EqualValues(t, "refs/pull/25873/head", results[8].refName.String()) + assert.EqualValues(t, "e0639e38fb", results[8].oldCommitID) + assert.EqualValues(t, "6db2410489", results[8].newCommitID) + + assert.EqualValues(t, "refs/pull/25873/merge", results[9].refName.String()) + assert.EqualValues(t, "1c97ebc746", results[9].oldCommitID) + assert.EqualValues(t, "976d27d52f", results[9].newCommitID) } diff --git a/services/packages/arch/repository.go b/services/packages/arch/repository.go index 7fb4222cf6..a12af82ba5 100644 --- a/services/packages/arch/repository.go +++ b/services/packages/arch/repository.go @@ -235,6 +235,28 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package return packages_service.DeletePackageFile(ctx, pf) } + vpfs := make(map[int64]*entryOptions) + for _, pf := range pfs { + current := &entryOptions{ + File: pf, + } + current.Version, err = packages_model.GetVersionByID(ctx, pf.VersionID) + if err != nil { + return err + } + + // here we compare the versions but not using SearchLatestVersions because we shouldn't allow "downgrading" to a older version by "latest" one. + // https://wiki.archlinux.org/title/Downgrading_packages : randomly downgrading can mess up dependencies: + // If a downgrade involves a soname change, all dependencies may need downgrading or rebuilding too. + if old, ok := vpfs[current.Version.PackageID]; ok { + if compareVersions(old.Version.Version, current.Version.Version) == -1 { + vpfs[current.Version.PackageID] = current + } + } else { + vpfs[current.Version.PackageID] = current + } + } + indexContent, _ := packages_module.NewHashedBuffer() defer indexContent.Close() @@ -243,15 +265,7 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package cache := make(map[int64]*packages_model.Package) - for _, pf := range pfs { - opts := &entryOptions{ - File: pf, - } - - opts.Version, err = packages_model.GetVersionByID(ctx, pf.VersionID) - if err != nil { - return err - } + for _, opts := range vpfs { if err := json.Unmarshal([]byte(opts.Version.MetadataJSON), &opts.VersionMetadata); err != nil { return err } @@ -263,12 +277,12 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package } cache[opts.Package.ID] = opts.Package } - opts.Blob, err = packages_model.GetBlobByID(ctx, pf.BlobID) + opts.Blob, err = packages_model.GetBlobByID(ctx, opts.File.BlobID) if err != nil { return err } - sig, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeFile, pf.ID, arch_module.PropertySignature) + sig, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeFile, opts.File.ID, arch_module.PropertySignature) if err != nil { return err } @@ -277,7 +291,7 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package } opts.Signature = sig[0].Value - meta, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeFile, pf.ID, arch_module.PropertyMetadata) + meta, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeFile, opts.File.ID, arch_module.PropertyMetadata) if err != nil { return err } diff --git a/services/packages/arch/vercmp.go b/services/packages/arch/vercmp.go new file mode 100644 index 0000000000..0d33dda0f1 --- /dev/null +++ b/services/packages/arch/vercmp.go @@ -0,0 +1,113 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package arch + +import ( + "strings" + "unicode" +) + +// https://gitlab.archlinux.org/pacman/pacman/-/blob/d55b47e5512808b67bc944feb20c2bcc6c1a4c45/lib/libalpm/version.c + +import ( + "strconv" +) + +func parseEVR(evr string) (epoch, version, release string) { + if before, after, f := strings.Cut(evr, ":"); f { + epoch = before + evr = after + } else { + epoch = "0" + } + + if before, after, f := strings.Cut(evr, "-"); f { + version = before + release = after + } else { + version = evr + release = "1" + } + return epoch, version, release +} + +func compareSegments(a, b []string) int { + lenA, lenB := len(a), len(b) + var l int + if lenA > lenB { + l = lenB + } else { + l = lenA + } + for i := 0; i < l; i++ { + if r := compare(a[i], b[i]); r != 0 { + return r + } + } + if lenA == lenB { + return 0 + } else if l == lenA { + return -1 + } + return 1 +} + +func compare(a, b string) int { + if a == b { + return 0 + } + + aNumeric := isNumeric(a) + bNumeric := isNumeric(b) + + if aNumeric && bNumeric { + aInt, _ := strconv.Atoi(a) + bInt, _ := strconv.Atoi(b) + switch { + case aInt < bInt: + return -1 + case aInt > bInt: + return 1 + default: + return 0 + } + } + + if aNumeric { + return 1 + } + if bNumeric { + return -1 + } + + return strings.Compare(a, b) +} + +func isNumeric(s string) bool { + for _, c := range s { + if !unicode.IsDigit(c) { + return false + } + } + return true +} + +func compareVersions(a, b string) int { + if a == b { + return 0 + } + + epochA, versionA, releaseA := parseEVR(a) + epochB, versionB, releaseB := parseEVR(b) + + if res := compareSegments([]string{epochA}, []string{epochB}); res != 0 { + return res + } + + if res := compareSegments(strings.Split(versionA, "."), strings.Split(versionB, ".")); res != 0 { + return res + } + + return compareSegments([]string{releaseA}, []string{releaseB}) +} diff --git a/services/packages/arch/vercmp_test.go b/services/packages/arch/vercmp_test.go new file mode 100644 index 0000000000..2014a6d429 --- /dev/null +++ b/services/packages/arch/vercmp_test.go @@ -0,0 +1,27 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package arch + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCompareVersions(t *testing.T) { + // https://man.archlinux.org/man/vercmp.8.en + checks := [][]string{ + {"1.0a", "1.0b", "1.0beta", "1.0p", "1.0pre", "1.0rc", "1.0", "1.0.a", "1.0.1"}, + {"1", "1.0", "1.1", "1.1.1", "1.2", "2.0", "3.0.0"}, + } + for _, check := range checks { + for i := 0; i < len(check)-1; i++ { + require.Equal(t, -1, compareVersions(check[i], check[i+1])) + require.Equal(t, 1, compareVersions(check[i+1], check[i])) + } + } + require.Equal(t, 1, compareVersions("1.0-2", "1.0")) + require.Equal(t, 0, compareVersions("0:1.0-1", "1.0")) + require.Equal(t, 1, compareVersions("1:1.0-1", "2.0")) +} diff --git a/services/packages/package_update.go b/services/packages/package_update.go new file mode 100644 index 0000000000..8d851fac53 --- /dev/null +++ b/services/packages/package_update.go @@ -0,0 +1,78 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package packages + +import ( + "context" + "fmt" + + org_model "code.gitea.io/gitea/models/organization" + packages_model "code.gitea.io/gitea/models/packages" + access_model "code.gitea.io/gitea/models/perm/access" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/util" +) + +func LinkToRepository(ctx context.Context, pkg *packages_model.Package, repo *repo_model.Repository, doer *user_model.User) error { + if pkg.OwnerID != repo.OwnerID { + return util.ErrPermissionDenied + } + if pkg.RepoID > 0 { + return util.ErrInvalidArgument + } + + perms, err := access_model.GetUserRepoPermission(ctx, repo, doer) + if err != nil { + return fmt.Errorf("error getting permissions for user %d on repository %d: %w", doer.ID, repo.ID, err) + } + if !perms.CanWrite(unit.TypePackages) { + return util.ErrPermissionDenied + } + + if err := packages_model.SetRepositoryLink(ctx, pkg.ID, repo.ID); err != nil { + return fmt.Errorf("error while linking package '%v' to repo '%v' : %w", pkg.Name, repo.FullName(), err) + } + return nil +} + +func UnlinkFromRepository(ctx context.Context, pkg *packages_model.Package, doer *user_model.User) error { + if pkg.RepoID == 0 { + return util.ErrInvalidArgument + } + + repo, err := repo_model.GetRepositoryByID(ctx, pkg.RepoID) + if err != nil { + return fmt.Errorf("error getting repository %d: %w", pkg.RepoID, err) + } + + perms, err := access_model.GetUserRepoPermission(ctx, repo, doer) + if err != nil { + return fmt.Errorf("error getting permissions for user %d on repository %d: %w", doer.ID, repo.ID, err) + } + if !perms.CanWrite(unit.TypePackages) { + return util.ErrPermissionDenied + } + + user, err := user_model.GetUserByID(ctx, pkg.OwnerID) + if err != nil { + return err + } + if !doer.IsAdmin { + if !user.IsOrganization() { + if doer.ID != pkg.OwnerID { + return fmt.Errorf("no permission to unlink package '%v' from its repository, or packages are disabled", pkg.Name) + } + } else { + isOrgAdmin, err := org_model.OrgFromUser(user).IsOrgAdmin(ctx, doer.ID) + if err != nil { + return err + } else if !isOrgAdmin { + return fmt.Errorf("no permission to unlink package '%v' from its repository, or packages are disabled", pkg.Name) + } + } + } + return packages_model.UnlinkRepository(ctx, pkg.ID) +} diff --git a/services/projects/issue.go b/services/projects/issue.go index db1621a39f..090d19d2f4 100644 --- a/services/projects/issue.go +++ b/services/projects/issue.go @@ -11,6 +11,7 @@ import ( issues_model "code.gitea.io/gitea/models/issues" project_model "code.gitea.io/gitea/models/project" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/optional" ) // MoveIssuesOnProjectColumn moves or keeps issues in a column and sorts them inside that column @@ -55,25 +56,152 @@ func MoveIssuesOnProjectColumn(ctx context.Context, doer *user_model.User, colum continue } - _, err = db.Exec(ctx, "UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", column.ID, sorting, issueID) + projectColumnID, err := curIssue.ProjectColumnID(ctx) if err != nil { return err } - // add timeline to issue - if _, err := issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{ - Type: issues_model.CommentTypeProjectColumn, - Doer: doer, - Repo: curIssue.Repo, - Issue: curIssue, - ProjectID: column.ProjectID, - ProjectTitle: project.Title, - ProjectColumnID: column.ID, - ProjectColumnTitle: column.Title, - }); err != nil { + if projectColumnID != column.ID { + // add timeline to issue + if _, err := issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{ + Type: issues_model.CommentTypeProjectColumn, + Doer: doer, + Repo: curIssue.Repo, + Issue: curIssue, + ProjectID: column.ProjectID, + ProjectTitle: project.Title, + ProjectColumnID: column.ID, + ProjectColumnTitle: column.Title, + }); err != nil { + return err + } + } + + _, err = db.Exec(ctx, "UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", column.ID, sorting, issueID) + if err != nil { return err } } return nil }) } + +// LoadIssuesFromProject load issues assigned to each project column inside the given project +func LoadIssuesFromProject(ctx context.Context, project *project_model.Project, opts *issues_model.IssuesOptions) (map[int64]issues_model.IssueList, error) { + issueList, err := issues_model.Issues(ctx, opts.Copy(func(o *issues_model.IssuesOptions) { + o.ProjectID = project.ID + o.SortType = "project-column-sorting" + })) + if err != nil { + return nil, err + } + + if err := issueList.LoadComments(ctx); err != nil { + return nil, err + } + + defaultColumn, err := project.MustDefaultColumn(ctx) + if err != nil { + return nil, err + } + + issueColumnMap, err := issues_model.LoadProjectIssueColumnMap(ctx, project.ID, defaultColumn.ID) + if err != nil { + return nil, err + } + + results := make(map[int64]issues_model.IssueList) + for _, issue := range issueList { + projectColumnID, ok := issueColumnMap[issue.ID] + if !ok { + continue + } + if _, ok := results[projectColumnID]; !ok { + results[projectColumnID] = make(issues_model.IssueList, 0) + } + results[projectColumnID] = append(results[projectColumnID], issue) + } + return results, nil +} + +// NumClosedIssues return counter of closed issues assigned to a project +func loadNumClosedIssues(ctx context.Context, p *project_model.Project) error { + cnt, err := db.GetEngine(ctx).Table("project_issue"). + Join("INNER", "issue", "project_issue.issue_id=issue.id"). + Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true). + Cols("issue_id"). + Count() + if err != nil { + return err + } + p.NumClosedIssues = cnt + return nil +} + +// NumOpenIssues return counter of open issues assigned to a project +func loadNumOpenIssues(ctx context.Context, p *project_model.Project) error { + cnt, err := db.GetEngine(ctx).Table("project_issue"). + Join("INNER", "issue", "project_issue.issue_id=issue.id"). + Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false). + Cols("issue_id"). + Count() + if err != nil { + return err + } + p.NumOpenIssues = cnt + return nil +} + +func LoadIssueNumbersForProjects(ctx context.Context, projects []*project_model.Project, doer *user_model.User) error { + for _, project := range projects { + if err := LoadIssueNumbersForProject(ctx, project, doer); err != nil { + return err + } + } + return nil +} + +func LoadIssueNumbersForProject(ctx context.Context, project *project_model.Project, doer *user_model.User) error { + // for repository project, just get the numbers + if project.OwnerID == 0 { + if err := loadNumClosedIssues(ctx, project); err != nil { + return err + } + if err := loadNumOpenIssues(ctx, project); err != nil { + return err + } + project.NumIssues = project.NumClosedIssues + project.NumOpenIssues + return nil + } + + if err := project.LoadOwner(ctx); err != nil { + return err + } + + // for user or org projects, we need to check access permissions + opts := issues_model.IssuesOptions{ + ProjectID: project.ID, + Doer: doer, + AllPublic: doer == nil, + Owner: project.Owner, + } + + var err error + project.NumOpenIssues, err = issues_model.CountIssues(ctx, opts.Copy(func(o *issues_model.IssuesOptions) { + o.IsClosed = optional.Some(false) + })) + if err != nil { + return err + } + + project.NumClosedIssues, err = issues_model.CountIssues(ctx, opts.Copy(func(o *issues_model.IssuesOptions) { + o.IsClosed = optional.Some(true) + })) + if err != nil { + return err + } + + project.NumIssues = project.NumClosedIssues + project.NumOpenIssues + + return nil +} diff --git a/services/projects/issue_test.go b/services/projects/issue_test.go new file mode 100644 index 0000000000..b6f0b1dae1 --- /dev/null +++ b/services/projects/issue_test.go @@ -0,0 +1,210 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package project + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + org_model "code.gitea.io/gitea/models/organization" + project_model "code.gitea.io/gitea/models/project" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + + "github.com/stretchr/testify/assert" +) + +func Test_Projects(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + userAdmin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org3 := unittest.AssertExistsAndLoadBean(t, &org_model.Organization{ID: 3}) + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + + t.Run("User projects", func(t *testing.T) { + pi1 := project_model.ProjectIssue{ + ProjectID: 4, + IssueID: 1, + ProjectColumnID: 4, + } + err := db.Insert(db.DefaultContext, &pi1) + assert.NoError(t, err) + defer func() { + _, err = db.DeleteByID[project_model.ProjectIssue](db.DefaultContext, pi1.ID) + assert.NoError(t, err) + }() + + pi2 := project_model.ProjectIssue{ + ProjectID: 4, + IssueID: 4, + ProjectColumnID: 4, + } + err = db.Insert(db.DefaultContext, &pi2) + assert.NoError(t, err) + defer func() { + _, err = db.DeleteByID[project_model.ProjectIssue](db.DefaultContext, pi2.ID) + assert.NoError(t, err) + }() + + projects, err := db.Find[project_model.Project](db.DefaultContext, project_model.SearchOptions{ + OwnerID: user2.ID, + }) + assert.NoError(t, err) + assert.Len(t, projects, 3) + assert.EqualValues(t, 4, projects[0].ID) + + t.Run("Authenticated user", func(t *testing.T) { + columnIssues, err := LoadIssuesFromProject(db.DefaultContext, projects[0], &issues_model.IssuesOptions{ + Owner: user2, + Doer: user2, + }) + assert.NoError(t, err) + assert.Len(t, columnIssues, 1) // 4 has 2 issues, 6 will not contains here because 0 issues + assert.Len(t, columnIssues[4], 2) // user2 can visit both issues, one from public repository one from private repository + }) + + t.Run("Anonymous user", func(t *testing.T) { + columnIssues, err := LoadIssuesFromProject(db.DefaultContext, projects[0], &issues_model.IssuesOptions{ + AllPublic: true, + }) + assert.NoError(t, err) + assert.Len(t, columnIssues, 1) + assert.Len(t, columnIssues[4], 1) // anonymous user can only visit public repo issues + }) + + t.Run("Authenticated user with no permission to the private repo", func(t *testing.T) { + columnIssues, err := LoadIssuesFromProject(db.DefaultContext, projects[0], &issues_model.IssuesOptions{ + Owner: user2, + Doer: user4, + }) + assert.NoError(t, err) + assert.Len(t, columnIssues, 1) + assert.Len(t, columnIssues[4], 1) // user4 can only visit public repo issues + }) + }) + + t.Run("Org projects", func(t *testing.T) { + project1 := project_model.Project{ + Title: "project in an org", + OwnerID: org3.ID, + Type: project_model.TypeOrganization, + TemplateType: project_model.TemplateTypeBasicKanban, + } + err := project_model.NewProject(db.DefaultContext, &project1) + assert.NoError(t, err) + defer func() { + err := project_model.DeleteProjectByID(db.DefaultContext, project1.ID) + assert.NoError(t, err) + }() + + column1 := project_model.Column{ + Title: "column 1", + ProjectID: project1.ID, + } + err = project_model.NewColumn(db.DefaultContext, &column1) + assert.NoError(t, err) + + column2 := project_model.Column{ + Title: "column 2", + ProjectID: project1.ID, + } + err = project_model.NewColumn(db.DefaultContext, &column2) + assert.NoError(t, err) + + // issue 6 belongs to private repo 3 under org 3 + issue6 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 6}) + err = issues_model.IssueAssignOrRemoveProject(db.DefaultContext, issue6, user2, project1.ID, column1.ID) + assert.NoError(t, err) + + // issue 16 belongs to public repo 16 under org 3 + issue16 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 16}) + err = issues_model.IssueAssignOrRemoveProject(db.DefaultContext, issue16, user2, project1.ID, column1.ID) + assert.NoError(t, err) + + projects, err := db.Find[project_model.Project](db.DefaultContext, project_model.SearchOptions{ + OwnerID: org3.ID, + }) + assert.NoError(t, err) + assert.Len(t, projects, 1) + assert.EqualValues(t, project1.ID, projects[0].ID) + + t.Run("Authenticated user", func(t *testing.T) { + columnIssues, err := LoadIssuesFromProject(db.DefaultContext, projects[0], &issues_model.IssuesOptions{ + Owner: org3.AsUser(), + Doer: userAdmin, + }) + assert.NoError(t, err) + assert.Len(t, columnIssues, 1) // column1 has 2 issues, 6 will not contains here because 0 issues + assert.Len(t, columnIssues[column1.ID], 2) // user2 can visit both issues, one from public repository one from private repository + }) + + t.Run("Anonymous user", func(t *testing.T) { + columnIssues, err := LoadIssuesFromProject(db.DefaultContext, projects[0], &issues_model.IssuesOptions{ + AllPublic: true, + }) + assert.NoError(t, err) + assert.Len(t, columnIssues, 1) + assert.Len(t, columnIssues[column1.ID], 1) // anonymous user can only visit public repo issues + }) + + t.Run("Authenticated user with no permission to the private repo", func(t *testing.T) { + columnIssues, err := LoadIssuesFromProject(db.DefaultContext, projects[0], &issues_model.IssuesOptions{ + Owner: org3.AsUser(), + Doer: user2, + }) + assert.NoError(t, err) + assert.Len(t, columnIssues, 1) + assert.Len(t, columnIssues[column1.ID], 1) // user4 can only visit public repo issues + }) + }) + + t.Run("Repository projects", func(t *testing.T) { + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + projects, err := db.Find[project_model.Project](db.DefaultContext, project_model.SearchOptions{ + RepoID: repo1.ID, + }) + assert.NoError(t, err) + assert.Len(t, projects, 1) + assert.EqualValues(t, 1, projects[0].ID) + + t.Run("Authenticated user", func(t *testing.T) { + columnIssues, err := LoadIssuesFromProject(db.DefaultContext, projects[0], &issues_model.IssuesOptions{ + RepoIDs: []int64{repo1.ID}, + Doer: userAdmin, + }) + assert.NoError(t, err) + assert.Len(t, columnIssues, 3) + assert.Len(t, columnIssues[1], 2) + assert.Len(t, columnIssues[2], 1) + assert.Len(t, columnIssues[3], 1) + }) + + t.Run("Anonymous user", func(t *testing.T) { + columnIssues, err := LoadIssuesFromProject(db.DefaultContext, projects[0], &issues_model.IssuesOptions{ + AllPublic: true, + }) + assert.NoError(t, err) + assert.Len(t, columnIssues, 3) + assert.Len(t, columnIssues[1], 2) + assert.Len(t, columnIssues[2], 1) + assert.Len(t, columnIssues[3], 1) + }) + + t.Run("Authenticated user with no permission to the private repo", func(t *testing.T) { + columnIssues, err := LoadIssuesFromProject(db.DefaultContext, projects[0], &issues_model.IssuesOptions{ + RepoIDs: []int64{repo1.ID}, + Doer: user2, + }) + assert.NoError(t, err) + assert.Len(t, columnIssues, 3) + assert.Len(t, columnIssues[1], 2) + assert.Len(t, columnIssues[2], 1) + assert.Len(t, columnIssues[3], 1) + }) + }) +} diff --git a/services/projects/main_test.go b/services/projects/main_test.go new file mode 100644 index 0000000000..d39c82a140 --- /dev/null +++ b/services/projects/main_test.go @@ -0,0 +1,17 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package project + +import ( + "testing" + + "code.gitea.io/gitea/models/unittest" + + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/activities" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m) +} diff --git a/services/repository/adopt.go b/services/repository/adopt.go index e37909e7ab..0038d9525a 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -52,8 +52,9 @@ func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateR IsEmpty: !opts.AutoInit, } + repoPath := repo_model.RepoPath(u.Name, repo.Name) + if err := db.WithTx(ctx, func(ctx context.Context) error { - repoPath := repo_model.RepoPath(u.Name, repo.Name) isExist, err := util.IsExist(repoPath) if err != nil { log.Error("Unable to check if %s exists. Error: %v", repoPath, err) @@ -75,7 +76,12 @@ func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateR if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil { return fmt.Errorf("getRepositoryByID: %w", err) } + return nil + }); err != nil { + return nil, err + } + if err := func() error { if err := adoptRepository(ctx, repoPath, repo, opts.DefaultBranch); err != nil { return fmt.Errorf("adoptRepository: %w", err) } @@ -84,23 +90,18 @@ func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateR return fmt.Errorf("checkDaemonExportOK: %w", err) } - // Initialize Issue Labels if selected - if len(opts.IssueLabels) > 0 { - if err := repo_module.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil { - return fmt.Errorf("InitializeLabels: %w", err) - } - } - if stdout, _, err := git.NewCommand(ctx, "update-server-info"). RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) return fmt.Errorf("CreateRepository(git update-server-info): %w", err) } return nil - }); err != nil { + }(); err != nil { + if errDel := DeleteRepository(ctx, doer, repo, false /* no notify */); errDel != nil { + log.Error("Failed to delete repository %s that could not be adopted: %v", repo.FullName(), errDel) + } return nil, err } - notify_service.AdoptRepository(ctx, doer, u, repo) return repo, nil diff --git a/services/repository/delete.go b/services/repository/delete.go index 2166b4dd5c..fb3fffdca7 100644 --- a/services/repository/delete.go +++ b/services/repository/delete.go @@ -158,6 +158,7 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID &actions_model.ActionSchedule{RepoID: repoID}, &actions_model.ActionArtifact{RepoID: repoID}, &actions_model.ActionRunnerToken{RepoID: repoID}, + &issues_model.IssuePin{RepoID: repoID}, ); err != nil { return fmt.Errorf("deleteBeans: %w", err) } diff --git a/services/repository/files/commit.go b/services/repository/files/commit.go index e0dad29273..3cc326d065 100644 --- a/services/repository/files/commit.go +++ b/services/repository/files/commit.go @@ -6,10 +6,10 @@ package files import ( "context" - asymkey_model "code.gitea.io/gitea/models/asymkey" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/structs" + asymkey_service "code.gitea.io/gitea/services/asymkey" ) // CountDivergingCommits determines how many commits a branch is ahead or behind the repository's base branch @@ -24,7 +24,7 @@ func CountDivergingCommits(ctx context.Context, repo *repo_model.Repository, bra // GetPayloadCommitVerification returns the verification information of a commit func GetPayloadCommitVerification(ctx context.Context, commit *git.Commit) *structs.PayloadCommitVerification { verification := &structs.PayloadCommitVerification{} - commitVerification := asymkey_model.ParseCommitWithSignature(ctx, commit) + commitVerification := asymkey_service.ParseCommitWithSignature(ctx, commit) if commit.Signature != nil { verification.Signature = commit.Signature.Signature verification.Payload = commit.Signature.Payload diff --git a/modules/gitgraph/graph.go b/services/repository/gitgraph/graph.go similarity index 100% rename from modules/gitgraph/graph.go rename to services/repository/gitgraph/graph.go diff --git a/modules/gitgraph/graph_models.go b/services/repository/gitgraph/graph_models.go similarity index 98% rename from modules/gitgraph/graph_models.go rename to services/repository/gitgraph/graph_models.go index 191b0b3afc..c45662836b 100644 --- a/modules/gitgraph/graph_models.go +++ b/services/repository/gitgraph/graph_models.go @@ -17,6 +17,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + asymkey_service "code.gitea.io/gitea/services/asymkey" ) // NewGraph creates a basic graph @@ -114,7 +115,7 @@ func (graph *Graph) LoadAndProcessCommits(ctx context.Context, repository *repo_ } } - c.Verification = asymkey_model.ParseCommitWithSignature(ctx, c.Commit) + c.Verification = asymkey_service.ParseCommitWithSignature(ctx, c.Commit) _ = asymkey_model.CalculateTrustStatus(c.Verification, repository.GetTrustModel(), func(user *user_model.User) (bool, error) { return repo_model.IsOwnerMemberCollaborator(ctx, repository, user.ID) diff --git a/modules/gitgraph/graph_test.go b/services/repository/gitgraph/graph_test.go similarity index 100% rename from modules/gitgraph/graph_test.go rename to services/repository/gitgraph/graph_test.go diff --git a/modules/gitgraph/parser.go b/services/repository/gitgraph/parser.go similarity index 100% rename from modules/gitgraph/parser.go rename to services/repository/gitgraph/parser.go diff --git a/services/webhook/notifier.go b/services/webhook/notifier.go index 6c691c21f4..76d6fd3472 100644 --- a/services/webhook/notifier.go +++ b/services/webhook/notifier.go @@ -15,6 +15,7 @@ import ( 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/httplib" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" @@ -871,6 +872,11 @@ func (m *webhookNotifier) CreateCommitStatus(ctx context.Context, repo *repo_mod return } + // as a webhook url, target should be an absolute url. But for internal actions target url + // the target url is a url path with no host and port to make it easy to be visited + // from multiple hosts. So we need to convert it to an absolute url here. + target := httplib.MakeAbsoluteURL(ctx, status.TargetURL) + payload := api.CommitStatusPayload{ Context: status.Context, CreatedAt: status.CreatedUnix.AsTime().UTC(), @@ -878,7 +884,7 @@ func (m *webhookNotifier) CreateCommitStatus(ctx context.Context, repo *repo_mod ID: status.ID, SHA: commit.Sha1, State: status.State.String(), - TargetURL: status.TargetURL, + TargetURL: target, Commit: apiCommit, Repo: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}), diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c4c31968e0..596f703131 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -44,7 +44,7 @@ parts: source: . stage-packages: [ git, sqlite3, openssh-client ] build-packages: [ git, libpam0g-dev, libsqlite3-dev, build-essential] - build-snaps: [ go/1.23/stable, node/22/stable ] + build-snaps: [ go/1.24/stable, node/22/stable ] build-environment: - LDFLAGS: "" override-pull: | diff --git a/stylelint.config.js b/stylelint.config.js index 1153bf7308..c57c1fd3a3 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -123,7 +123,6 @@ export default defineConfig({ 'declaration-block-no-redundant-longhand-properties': [true, {ignoreShorthands: ['flex-flow', 'overflow', 'grid-template']}], // @ts-expect-error - https://github.com/stylelint-types/stylelint-define-config/issues/1 'declaration-property-unit-disallowed-list': {'line-height': ['em']}, - // @ts-expect-error - https://github.com/stylelint-types/stylelint-define-config/issues/1 'declaration-property-value-disallowed-list': {'word-break': ['break-word']}, 'font-family-name-quotes': 'always-where-recommended', 'function-name-case': 'lower', diff --git a/templates/admin/auth/list.tmpl b/templates/admin/auth/list.tmpl index 7931014b1a..a1e72b742f 100644 --- a/templates/admin/auth/list.tmpl +++ b/templates/admin/auth/list.tmpl @@ -30,6 +30,8 @@ {{DateUtils.AbsoluteShort .CreatedUnix}} {{svg "octicon-pencil"}} + {{else}} + {{ctx.Locale.Tr "no_results_found"}} {{end}} diff --git a/templates/admin/emails/list.tmpl b/templates/admin/emails/list.tmpl index 0dc1fb9d03..b4335aeeec 100644 --- a/templates/admin/emails/list.tmpl +++ b/templates/admin/emails/list.tmpl @@ -67,6 +67,8 @@ >{{svg "octicon-trash"}} + {{else}} + {{ctx.Locale.Tr "no_results_found"}} {{end}} diff --git a/templates/admin/notice.tmpl b/templates/admin/notice.tmpl index fd475d7157..a4c9dc53fb 100644 --- a/templates/admin/notice.tmpl +++ b/templates/admin/notice.tmpl @@ -24,6 +24,8 @@ {{DateUtils.AbsoluteShort .CreatedUnix}} {{svg "octicon-note" 16}} + {{else}} + {{ctx.Locale.Tr "no_results_found"}} {{end}} {{if .Notices}} diff --git a/templates/admin/org/list.tmpl b/templates/admin/org/list.tmpl index d5e09939c5..137c42b45d 100644 --- a/templates/admin/org/list.tmpl +++ b/templates/admin/org/list.tmpl @@ -66,6 +66,8 @@ {{DateUtils.AbsoluteShort .CreatedUnix}} {{svg "octicon-pencil"}} + {{else}} + {{ctx.Locale.Tr "no_results_found"}} {{end}} diff --git a/templates/admin/packages/list.tmpl b/templates/admin/packages/list.tmpl index 08c11442bc..0c6889b599 100644 --- a/templates/admin/packages/list.tmpl +++ b/templates/admin/packages/list.tmpl @@ -74,6 +74,8 @@ {{DateUtils.AbsoluteShort .Version.CreatedUnix}} {{svg "octicon-trash"}} + {{else}} + {{ctx.Locale.Tr "no_results_found"}} {{end}} diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl index 08fd893e76..762013af47 100644 --- a/templates/admin/repo/list.tmpl +++ b/templates/admin/repo/list.tmpl @@ -86,6 +86,8 @@ {{DateUtils.AbsoluteShort .CreatedUnix}} {{svg "octicon-trash"}} + {{else}} + {{ctx.Locale.Tr "no_results_found"}} {{end}} diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl index d591a645d8..c04d332660 100644 --- a/templates/admin/user/edit.tmpl +++ b/templates/admin/user/edit.tmpl @@ -195,8 +195,7 @@
- - + {{template "shared/avatar_upload_crop" dict "LabelText" (ctx.Locale.Tr "settings.choose_new_avatar")}}
diff --git a/templates/admin/user/list.tmpl b/templates/admin/user/list.tmpl index 7e4c8854f5..eb3f6cd720 100644 --- a/templates/admin/user/list.tmpl +++ b/templates/admin/user/list.tmpl @@ -109,6 +109,8 @@
+ {{else}} + {{ctx.Locale.Tr "no_results_found"}} {{end}} diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index baf37494b9..eab6c00840 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -175,11 +175,13 @@ {{else}} {{if .ShowRegistrationButton}} - {{svg "octicon-person"}} {{ctx.Locale.Tr "register"}} + {{svg "octicon-person"}} + {{ctx.Locale.Tr "register"}} {{end}} - {{svg "octicon-sign-in"}} {{ctx.Locale.Tr "sign_in"}} + {{svg "octicon-sign-in"}} + {{ctx.Locale.Tr "sign_in"}} {{end}} diff --git a/templates/org/home.tmpl b/templates/org/home.tmpl index db750692bf..826642db42 100644 --- a/templates/org/home.tmpl +++ b/templates/org/home.tmpl @@ -16,10 +16,10 @@ {{if .ShowMemberAndTeamTab}}
{{if .CanCreateOrgRepo}} -
- {{ctx.Locale.Tr "new_repo"}} +
@@ -32,7 +32,6 @@ {{svg "octicon-eye"}} {{ctx.Locale.Tr "org.view_as_role" $viewAsRole}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}