mirror of
https://github.com/go-gitea/gitea.git
synced 2025-02-20 11:43:57 +08:00
Compare commits
30 Commits
7ec38c5b53
...
0d9f0759ab
Author | SHA1 | Date | |
---|---|---|---|
|
0d9f0759ab | ||
|
c2e23d3301 | ||
|
84d2159ef6 | ||
|
ce65613690 | ||
|
748b731612 | ||
|
241f799edf | ||
|
9f560d47c9 | ||
|
15e020eec8 | ||
|
7df09e31fa | ||
|
f5a81f9636 | ||
|
f35850f48e | ||
|
69de5a65c2 | ||
|
5df9fd3e9c | ||
|
50a5d6bf5d | ||
|
3bbacac62c | ||
|
37c4f3760c | ||
|
58c124cc4f | ||
|
62389dd08b | ||
|
950abfe8ee | ||
|
fc1b383da9 | ||
|
2b8cfb557d | ||
|
01bf8da02e | ||
|
57997f1518 | ||
|
1ba7cbbfd6 | ||
|
8aede14b1d | ||
|
70327d6a92 | ||
|
f232d8f530 | ||
|
b426e383fe | ||
|
d88b012525 | ||
|
25d25b45ee |
2
.mailmap
Normal file
2
.mailmap
Normal file
@ -0,0 +1,2 @@
|
||||
Unknwon <u@gogs.io> <joe2010xtmf@163.com>
|
||||
Unknwon <u@gogs.io> 无闻 <u@gogs.io>
|
@ -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}
|
||||
|
@ -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}
|
||||
|
4
Makefile
4
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
|
||||
@ -146,7 +146,7 @@ WEB_DIRS := web_src/js web_src/css
|
||||
|
||||
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)
|
||||
|
64
README.md
64
README.md
@ -150,10 +150,64 @@ for the full license text.
|
||||
<details>
|
||||
<summary>Looking for an overview of the interface? Check it out!</summary>
|
||||
|
||||
||||
|
||||
|:---:|:---:|:---:|
|
||||
||||
|
||||
||||
|
||||
||||
|
||||
### Login/Register Page
|
||||
|
||||

|
||||

|
||||
|
||||
### User Dashboard
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
### User Profile
|
||||
|
||||

|
||||
|
||||
### Explore
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
### Repository
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
#### Repository Issue
|
||||
|
||||

|
||||

|
||||
|
||||
#### Repository Pull Requests
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
#### Repository Actions
|
||||
|
||||

|
||||

|
||||
|
||||
#### Repository Activity
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
### Organization
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
64
README_ZH.md
64
README_ZH.md
@ -93,10 +93,64 @@ Gitea 提供官方的 [go-sdk](https://gitea.com/gitea/go-sdk),以及名为 [t
|
||||
<details>
|
||||
<summary>截图</summary>
|
||||
|
||||
||||
|
||||
|:---:|:---:|:---:|
|
||||
||||
|
||||
||||
|
||||
||||
|
||||
### 登录界面
|
||||
|
||||

|
||||

|
||||
|
||||
### 用户首页
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
### 用户资料
|
||||
|
||||

|
||||
|
||||
### 探索
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
### 仓库
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
#### 仓库工单
|
||||
|
||||

|
||||

|
||||
|
||||
#### 仓库合并请求
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
#### 仓库 Actions
|
||||
|
||||

|
||||

|
||||
|
||||
#### 仓库动态
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
### 组织
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
6
flake.lock
generated
6
flake.lock
generated
@ -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": {
|
||||
|
@ -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"
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
2
go.mod
2
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:
|
||||
|
@ -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,10 +108,11 @@ 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 {
|
||||
@ -134,6 +135,10 @@ 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
|
||||
}
|
||||
@ -172,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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 <sapk@sapk.fr> 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},
|
||||
}))
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
6
models/fixtures/issue_pin.yml
Normal file
6
models/fixtures/issue_pin.yml
Normal file
@ -0,0 +1,6 @@
|
||||
-
|
||||
id: 1
|
||||
repo_id: 2
|
||||
issue_id: 4
|
||||
is_pull: false
|
||||
pin_order: 1
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -97,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"`
|
||||
|
||||
@ -291,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 {
|
||||
@ -330,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
|
||||
}
|
||||
@ -342,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
|
||||
@ -720,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)
|
||||
|
@ -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 {
|
||||
|
246
models/issues/issue_pin.go
Normal file
246
models/issues/issue_pin.go
Normal file
@ -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
|
||||
}
|
@ -49,6 +49,21 @@ func (issue *Issue) ProjectColumnID(ctx context.Context) (int64, error) {
|
||||
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
|
||||
func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *IssuesOptions) (IssueList, error) {
|
||||
issueList, err := Issues(ctx, opts.Copy(func(o *IssuesOptions) {
|
||||
@ -61,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
|
||||
}
|
||||
@ -79,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 {
|
||||
@ -112,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
|
||||
}
|
||||
|
@ -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
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
31
models/migrations/v1_24/v313.go
Normal file
31
models/migrations/v1_24/v313.go
Normal file
@ -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")
|
||||
}
|
@ -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{})
|
||||
|
@ -152,7 +152,7 @@ func (p *Permission) ReadableUnitTypes() []unit.Type {
|
||||
}
|
||||
|
||||
func (p *Permission) LogString() string {
|
||||
format := "<Permission AccessMode=%s, %d Units, %d UnitsMode(s): [ "
|
||||
format := "<Permission AccessMode=%s, %d Units, %d UnitsMode(s): ["
|
||||
args := []any{p.AccessMode.ToString(), len(p.units), len(p.unitsMode)}
|
||||
|
||||
for i, u := range p.units {
|
||||
@ -164,14 +164,16 @@ func (p *Permission) LogString() string {
|
||||
config = err.Error()
|
||||
}
|
||||
}
|
||||
format += "\nUnits[%d]: ID: %d RepoID: %d Type: %s Config: %s"
|
||||
format += "\n\tunits[%d]: ID=%d RepoID=%d Type=%s Config=%s"
|
||||
args = append(args, i, u.ID, u.RepoID, u.Type.LogString(), config)
|
||||
}
|
||||
for key, value := range p.unitsMode {
|
||||
format += "\nUnitMode[%-v]: %-v"
|
||||
format += "\n\tunitsMode[%-v]: %-v"
|
||||
args = append(args, key.LogString(), value.LogString())
|
||||
}
|
||||
format += " ]>"
|
||||
format += "\n\teveryoneAccessMode: %-v"
|
||||
args = append(args, p.everyoneAccessMode)
|
||||
format += "\n\t]>"
|
||||
return fmt.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,8 @@ type Column struct {
|
||||
ProjectID int64 `xorm:"INDEX NOT NULL"`
|
||||
CreatorID int64 `xorm:"NOT NULL"`
|
||||
|
||||
NumIssues int64 `xorm:"-"`
|
||||
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
}
|
||||
@ -57,20 +59,6 @@ func (Column) TableName() string {
|
||||
return "project_board" // TODO: the legacy table name should be project_column
|
||||
}
|
||||
|
||||
// NumIssues return counter of all issues assigned to the column
|
||||
func (c *Column) NumIssues(ctx context.Context) int {
|
||||
total, err := db.GetEngine(ctx).Table("project_issue").
|
||||
Where("project_id=?", c.ProjectID).
|
||||
And("project_board_id=?", c.ID).
|
||||
GroupBy("issue_id").
|
||||
Cols("issue_id").
|
||||
Count()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return int(total)
|
||||
}
|
||||
|
||||
func (c *Column) GetIssues(ctx context.Context) ([]*ProjectIssue, error) {
|
||||
issues := make([]*ProjectIssue, 0, 5)
|
||||
if err := db.GetEngine(ctx).Where("project_id=?", c.ProjectID).
|
||||
@ -192,7 +180,7 @@ func deleteColumnByID(ctx context.Context, columnID int64) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defaultColumn, err := project.GetDefaultColumn(ctx)
|
||||
defaultColumn, err := project.MustDefaultColumn(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -257,8 +245,8 @@ func (p *Project) GetColumns(ctx context.Context) (ColumnList, error) {
|
||||
return columns, nil
|
||||
}
|
||||
|
||||
// GetDefaultColumn return default column and ensure only one exists
|
||||
func (p *Project) GetDefaultColumn(ctx context.Context) (*Column, error) {
|
||||
// getDefaultColumn return default column and ensure only one exists
|
||||
func (p *Project) getDefaultColumn(ctx context.Context) (*Column, error) {
|
||||
var column Column
|
||||
has, err := db.GetEngine(ctx).
|
||||
Where("project_id=? AND `default` = ?", p.ID, true).
|
||||
@ -270,6 +258,33 @@ func (p *Project) GetDefaultColumn(ctx context.Context) (*Column, error) {
|
||||
if has {
|
||||
return &column, nil
|
||||
}
|
||||
return nil, ErrProjectColumnNotExist{ColumnID: 0}
|
||||
}
|
||||
|
||||
// MustDefaultColumn returns the default column for a project.
|
||||
// If one exists, it is returned
|
||||
// If none exists, the first column will be elevated to the default column of this project
|
||||
func (p *Project) MustDefaultColumn(ctx context.Context) (*Column, error) {
|
||||
c, err := p.getDefaultColumn(ctx)
|
||||
if err != nil && !IsErrProjectColumnNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
if c != nil {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
var column Column
|
||||
has, err := db.GetEngine(ctx).Where("project_id=?", p.ID).OrderBy("sorting, id").Get(&column)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if has {
|
||||
column.Default = true
|
||||
if _, err := db.GetEngine(ctx).ID(column.ID).Cols("`default`").Update(&column); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &column, nil
|
||||
}
|
||||
|
||||
// create a default column if none is found
|
||||
column = Column{
|
||||
|
@ -20,19 +20,19 @@ func TestGetDefaultColumn(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check if default column was added
|
||||
column, err := projectWithoutDefault.GetDefaultColumn(db.DefaultContext)
|
||||
column, err := projectWithoutDefault.MustDefaultColumn(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(5), column.ProjectID)
|
||||
assert.Equal(t, "Uncategorized", column.Title)
|
||||
assert.Equal(t, "Done", column.Title)
|
||||
|
||||
projectWithMultipleDefaults, err := GetProjectByID(db.DefaultContext, 6)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check if multiple defaults were removed
|
||||
column, err = projectWithMultipleDefaults.GetDefaultColumn(db.DefaultContext)
|
||||
column, err = projectWithMultipleDefaults.MustDefaultColumn(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(6), column.ProjectID)
|
||||
assert.Equal(t, int64(9), column.ID)
|
||||
assert.Equal(t, int64(9), column.ID) // there are 2 default columns in the test data, use the latest one
|
||||
|
||||
// set 8 as default column
|
||||
assert.NoError(t, SetDefaultColumn(db.DefaultContext, column.ProjectID, 8))
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
@ -34,48 +33,6 @@ func deleteProjectIssuesByProjectID(ctx context.Context, projectID int64) error
|
||||
return err
|
||||
}
|
||||
|
||||
// NumIssues return counter of all issues assigned to a project
|
||||
func (p *Project) NumIssues(ctx context.Context) int {
|
||||
c, err := db.GetEngine(ctx).Table("project_issue").
|
||||
Where("project_id=?", p.ID).
|
||||
GroupBy("issue_id").
|
||||
Cols("issue_id").
|
||||
Count()
|
||||
if err != nil {
|
||||
log.Error("NumIssues: %v", err)
|
||||
return 0
|
||||
}
|
||||
return int(c)
|
||||
}
|
||||
|
||||
// NumClosedIssues return counter of closed issues assigned to a project
|
||||
func (p *Project) NumClosedIssues(ctx context.Context) int {
|
||||
c, 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 {
|
||||
log.Error("NumClosedIssues: %v", err)
|
||||
return 0
|
||||
}
|
||||
return int(c)
|
||||
}
|
||||
|
||||
// NumOpenIssues return counter of open issues assigned to a project
|
||||
func (p *Project) NumOpenIssues(ctx context.Context) int {
|
||||
c, 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 {
|
||||
log.Error("NumOpenIssues: %v", err)
|
||||
return 0
|
||||
}
|
||||
return int(c)
|
||||
}
|
||||
|
||||
func (c *Column) moveIssuesToAnotherColumn(ctx context.Context, newColumn *Column) error {
|
||||
if c.ProjectID != newColumn.ProjectID {
|
||||
return fmt.Errorf("columns have to be in the same project")
|
||||
|
@ -97,6 +97,9 @@ type Project struct {
|
||||
Type Type
|
||||
|
||||
RenderedContent template.HTML `xorm:"-"`
|
||||
NumOpenIssues int64 `xorm:"-"`
|
||||
NumClosedIssues int64 `xorm:"-"`
|
||||
NumIssues int64 `xorm:"-"`
|
||||
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
|
@ -1129,28 +1129,89 @@ func ValidateCommitWithEmail(ctx context.Context, c *git.Commit) *User {
|
||||
}
|
||||
|
||||
// ValidateCommitsWithEmails checks if authors' e-mails of commits are corresponding to users.
|
||||
func ValidateCommitsWithEmails(ctx context.Context, oldCommits []*git.Commit) []*UserCommit {
|
||||
func ValidateCommitsWithEmails(ctx context.Context, oldCommits []*git.Commit) ([]*UserCommit, error) {
|
||||
var (
|
||||
emails = make(map[string]*User)
|
||||
newCommits = make([]*UserCommit, 0, len(oldCommits))
|
||||
emailSet = make(container.Set[string])
|
||||
)
|
||||
for _, c := range oldCommits {
|
||||
var u *User
|
||||
if c.Author != nil {
|
||||
if v, ok := emails[c.Author.Email]; !ok {
|
||||
u, _ = GetUserByEmail(ctx, c.Author.Email)
|
||||
emails[c.Author.Email] = u
|
||||
} else {
|
||||
u = v
|
||||
emailSet.Add(c.Author.Email)
|
||||
}
|
||||
}
|
||||
|
||||
emailUserMap, err := GetUsersByEmails(ctx, emailSet.Values())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, c := range oldCommits {
|
||||
user, ok := emailUserMap[c.Author.Email]
|
||||
if !ok {
|
||||
user = &User{
|
||||
Name: c.Author.Name,
|
||||
Email: c.Author.Email,
|
||||
}
|
||||
}
|
||||
|
||||
newCommits = append(newCommits, &UserCommit{
|
||||
User: u,
|
||||
User: user,
|
||||
Commit: c,
|
||||
})
|
||||
}
|
||||
return newCommits
|
||||
return newCommits, nil
|
||||
}
|
||||
|
||||
func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, error) {
|
||||
if len(emails) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
needCheckEmails := make(container.Set[string])
|
||||
needCheckUserNames := make(container.Set[string])
|
||||
for _, email := range emails {
|
||||
if strings.HasSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) {
|
||||
username := strings.TrimSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress))
|
||||
needCheckUserNames.Add(username)
|
||||
} else {
|
||||
needCheckEmails.Add(strings.ToLower(email))
|
||||
}
|
||||
}
|
||||
|
||||
emailAddresses := make([]*EmailAddress, 0, len(needCheckEmails))
|
||||
if err := db.GetEngine(ctx).In("lower_email", needCheckEmails.Values()).
|
||||
And("is_activated=?", true).
|
||||
Find(&emailAddresses); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userIDs := make(container.Set[int64])
|
||||
for _, email := range emailAddresses {
|
||||
userIDs.Add(email.UID)
|
||||
}
|
||||
users, err := GetUsersMapByIDs(ctx, userIDs.Values())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := make(map[string]*User, len(emails))
|
||||
for _, email := range emailAddresses {
|
||||
user := users[email.UID]
|
||||
if user != nil {
|
||||
if user.KeepEmailPrivate {
|
||||
results[user.LowerName+"@"+setting.Service.NoReplyAddress] = user
|
||||
} else {
|
||||
results[email.Email] = user
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
users = make(map[int64]*User, len(needCheckUserNames))
|
||||
if err := db.GetEngine(ctx).In("lower_name", needCheckUserNames.Values()).Find(&users); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, user := range users {
|
||||
results[user.LowerName+"@"+setting.Service.NoReplyAddress] = user
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// GetUserByEmail returns the user object by given e-mail if exists.
|
||||
|
48
modules/actions/artifacts.go
Normal file
48
modules/actions/artifacts.go
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
// Artifacts using the v4 backend are stored as a single combined zip file per artifact on the backend
|
||||
// The v4 backend ensures ContentEncoding is set to "application/zip", which is not the case for the old backend
|
||||
func IsArtifactV4(art *actions_model.ActionArtifact) bool {
|
||||
return art.ArtifactName+".zip" == art.ArtifactPath && art.ContentEncoding == "application/zip"
|
||||
}
|
||||
|
||||
func DownloadArtifactV4ServeDirectOnly(ctx *context.Base, art *actions_model.ActionArtifact) (bool, error) {
|
||||
if setting.Actions.ArtifactStorage.ServeDirect() {
|
||||
u, err := storage.ActionsArtifacts.URL(art.StoragePath, art.ArtifactPath, nil)
|
||||
if u != nil && err == nil {
|
||||
ctx.Redirect(u.String(), http.StatusFound)
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func DownloadArtifactV4Fallback(ctx *context.Base, art *actions_model.ActionArtifact) error {
|
||||
f, err := storage.ActionsArtifacts.Open(art.StoragePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
http.ServeContent(ctx.Resp, ctx.Req, art.ArtifactName+".zip", art.CreatedUnix.AsLocalTime(), f)
|
||||
return nil
|
||||
}
|
||||
|
||||
func DownloadArtifactV4(ctx *context.Base, art *actions_model.ActionArtifact) error {
|
||||
ok, err := DownloadArtifactV4ServeDirectOnly(ctx, art)
|
||||
if ok || err != nil {
|
||||
return err
|
||||
}
|
||||
return DownloadArtifactV4Fallback(ctx, art)
|
||||
}
|
@ -260,17 +260,28 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
|
||||
var (
|
||||
indexerQuery query.Query
|
||||
keywordQuery query.Query
|
||||
contentQuery query.Query
|
||||
)
|
||||
|
||||
pathQuery := bleve.NewPrefixQuery(strings.ToLower(opts.Keyword))
|
||||
pathQuery.FieldVal = "Filename"
|
||||
pathQuery.SetBoost(10)
|
||||
|
||||
contentQuery := bleve.NewMatchQuery(opts.Keyword)
|
||||
contentQuery.FieldVal = "Content"
|
||||
|
||||
if opts.IsKeywordFuzzy {
|
||||
contentQuery.Fuzziness = inner_bleve.GuessFuzzinessByKeyword(opts.Keyword)
|
||||
keywordAsPhrase, isPhrase := internal.ParseKeywordAsPhrase(opts.Keyword)
|
||||
if isPhrase {
|
||||
q := bleve.NewMatchPhraseQuery(keywordAsPhrase)
|
||||
q.FieldVal = "Content"
|
||||
if opts.IsKeywordFuzzy {
|
||||
q.Fuzziness = inner_bleve.GuessFuzzinessByKeyword(keywordAsPhrase)
|
||||
}
|
||||
contentQuery = q
|
||||
} else {
|
||||
q := bleve.NewMatchQuery(opts.Keyword)
|
||||
q.FieldVal = "Content"
|
||||
if opts.IsKeywordFuzzy {
|
||||
q.Fuzziness = inner_bleve.GuessFuzzinessByKeyword(opts.Keyword)
|
||||
}
|
||||
contentQuery = q
|
||||
}
|
||||
|
||||
keywordQuery = bleve.NewDisjunctionQuery(contentQuery, pathQuery)
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/go-enry/go-enry/v2"
|
||||
"github.com/olivere/elastic/v7"
|
||||
@ -359,13 +360,19 @@ func extractAggs(searchResult *elastic.SearchResult) []*internal.SearchResultLan
|
||||
|
||||
// Search searches for codes and language stats by given conditions.
|
||||
func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) {
|
||||
searchType := esMultiMatchTypePhrasePrefix
|
||||
if opts.IsKeywordFuzzy {
|
||||
searchType = esMultiMatchTypeBestFields
|
||||
var contentQuery elastic.Query
|
||||
keywordAsPhrase, isPhrase := internal.ParseKeywordAsPhrase(opts.Keyword)
|
||||
if isPhrase {
|
||||
contentQuery = elastic.NewMatchPhraseQuery("content", keywordAsPhrase)
|
||||
} else {
|
||||
// TODO: this is the old logic, but not really using "fuzziness"
|
||||
// * IsKeywordFuzzy=true: "best_fields"
|
||||
// * IsKeywordFuzzy=false: "phrase_prefix"
|
||||
contentQuery = elastic.NewMultiMatchQuery("content", opts.Keyword).
|
||||
Type(util.Iif(opts.IsKeywordFuzzy, esMultiMatchTypeBestFields, esMultiMatchTypePhrasePrefix))
|
||||
}
|
||||
|
||||
kwQuery := elastic.NewBoolQuery().Should(
|
||||
elastic.NewMultiMatchQuery(opts.Keyword, "content").Type(searchType),
|
||||
contentQuery,
|
||||
elastic.NewMultiMatchQuery(opts.Keyword, "filename^10").Type(esMultiMatchTypePhrasePrefix),
|
||||
)
|
||||
query := elastic.NewBoolQuery()
|
||||
|
59
modules/indexer/code/gitgrep/gitgrep.go
Normal file
59
modules/indexer/code/gitgrep/gitgrep.go
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package gitgrep
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
code_indexer "code.gitea.io/gitea/modules/indexer/code"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func PerformSearch(ctx context.Context, page int, repoID int64, gitRepo *git.Repository, ref git.RefName, keyword string, isFuzzy bool) (searchResults []*code_indexer.Result, total int, err error) {
|
||||
// TODO: it should also respect ParseKeywordAsPhrase and clarify the "fuzzy" behavior
|
||||
res, err := git.GrepSearch(ctx, gitRepo, keyword, git.GrepOptions{
|
||||
ContextLineNumber: 1,
|
||||
IsFuzzy: isFuzzy,
|
||||
RefName: ref.String(),
|
||||
PathspecList: indexSettingToGitGrepPathspecList(),
|
||||
})
|
||||
if err != nil {
|
||||
// TODO: if no branch exists, it reports: exit status 128, fatal: this operation must be run in a work tree.
|
||||
return nil, 0, fmt.Errorf("git.GrepSearch: %w", err)
|
||||
}
|
||||
commitID, err := gitRepo.GetRefCommitID(ref.String())
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("gitRepo.GetRefCommitID: %w", err)
|
||||
}
|
||||
|
||||
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: repoID,
|
||||
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")),
|
||||
})
|
||||
}
|
||||
return searchResults, total, nil
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
package gitgrep
|
||||
|
||||
import (
|
||||
"testing"
|
@ -29,13 +29,11 @@ var (
|
||||
// When the real indexer is not ready, it will be a dummy indexer which will return error to explain it's not ready.
|
||||
// So it's always safe use it as *globalIndexer.Load() and call its methods.
|
||||
globalIndexer atomic.Pointer[internal.Indexer]
|
||||
dummyIndexer *internal.Indexer
|
||||
)
|
||||
|
||||
func init() {
|
||||
i := internal.NewDummyIndexer()
|
||||
dummyIndexer = &i
|
||||
globalIndexer.Store(dummyIndexer)
|
||||
dummyIndexer := internal.NewDummyIndexer()
|
||||
globalIndexer.Store(&dummyIndexer)
|
||||
}
|
||||
|
||||
func index(ctx context.Context, indexer internal.Indexer, repoID int64) error {
|
||||
|
@ -35,7 +35,7 @@ func FilenameOfIndexerID(indexerID string) string {
|
||||
return indexerID[index+1:]
|
||||
}
|
||||
|
||||
// Given the contents of file, returns the boundaries of its first seven lines.
|
||||
// FilenameMatchIndexPos returns the boundaries of its first seven lines.
|
||||
func FilenameMatchIndexPos(content string) (int, int) {
|
||||
count := 1
|
||||
for i, c := range content {
|
||||
@ -48,3 +48,11 @@ func FilenameMatchIndexPos(content string) (int, int) {
|
||||
}
|
||||
return 0, len(content)
|
||||
}
|
||||
|
||||
func ParseKeywordAsPhrase(keyword string) (string, bool) {
|
||||
if strings.HasPrefix(keyword, `"`) && strings.HasSuffix(keyword, `"`) && len(keyword) > 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
|
||||
}
|
||||
|
30
modules/indexer/code/internal/util_test.go
Normal file
30
modules/indexer/code/internal/util_test.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -65,3 +65,34 @@ 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"`
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -1702,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 <a href="%s">another issue</a>!`
|
||||
issues.stop_tracking = Stop Timer
|
||||
issues.stop_tracking_history = worked for <b>%[1]s</b> %[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 <b>%[1]s</b> %[2]s
|
||||
|
@ -385,6 +385,12 @@ 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
|
||||
@ -2331,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
|
||||
@ -2878,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
|
||||
|
@ -385,6 +385,12 @@ 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
|
||||
|
4086
package-lock.json
generated
4086
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
54
package.json
54
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,40 +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.30",
|
||||
"@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-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"
|
||||
|
10
poetry.lock
generated
10
poetry.lock
generated
@ -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]
|
||||
|
1
public/assets/img/svg/octicon-square-circle.svg
generated
Normal file
1
public/assets/img/svg/octicon-square-circle.svg
generated
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-square-circle" width="16" height="16" aria-hidden="true"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16m0-1.5a6.5 6.5 0 1 0 0-13 6.5 6.5 0 0 0 0 13"/><path d="M5 5.75A.75.75 0 0 1 5.75 5h4.5a.75.75 0 0 1 .75.75v4.5a.75.75 0 0 1-.75.75h-4.5a.75.75 0 0 1-.75-.75Z"/></svg>
|
After Width: | Height: | Size: 346 B |
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ package actions
|
||||
// 1.3. Continue Upload Zip Content to Blobstorage (unauthenticated request), repeat until everything is uploaded
|
||||
// PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=appendBlock
|
||||
// 1.4. BlockList xml payload to Blobstorage (unauthenticated request)
|
||||
// Files of about 800MB are parallel in parallel and / or out of order, this file is needed to enshure the correct order
|
||||
// Files of about 800MB are parallel in parallel and / or out of order, this file is needed to ensure the correct order
|
||||
// PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=blockList
|
||||
// Request
|
||||
// <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -356,10 +356,6 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// keep download count on overwrite
|
||||
_pv.DownloadCount = pv.DownloadCount
|
||||
|
||||
@ -418,12 +414,10 @@ func createFileFromBlobReference(ctx context.Context, pv, uploadVersion *package
|
||||
}
|
||||
var err error
|
||||
if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil {
|
||||
if errors.Is(err, packages_model.ErrDuplicatePackageFile) {
|
||||
// Skip this blob because the manifest contains the same filesystem layer multiple times.
|
||||
return nil
|
||||
if !errors.Is(err, packages_model.ErrDuplicatePackageFile) {
|
||||
log.Error("Error inserting package file: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Error("Error inserting package file: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
props := map[string]string{
|
||||
@ -437,13 +431,6 @@ func createFileFromBlobReference(ctx context.Context, pv, uploadVersion *package
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the file from the blob upload version
|
||||
if uploadVersion != nil && ref.File.File != nil && uploadVersion.ID == ref.File.File.VersionID {
|
||||
if err := packages_service.DeletePackageFile(ctx, ref.File.File); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -42,7 +42,7 @@ func GetAllEmails(ctx *context.APIContext) {
|
||||
ListOptions: listOptions,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetAllEmails", err)
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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.IsTokenAccessAllowed() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
|
||||
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public users")
|
||||
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.IsTokenAccessAllowed() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
|
||||
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public activitypub")
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1241,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).
|
||||
@ -1401,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() {
|
||||
@ -1526,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)
|
||||
|
@ -48,7 +48,7 @@ func GetGitignoreTemplateInfo(ctx *context.APIContext) {
|
||||
|
||||
text, err := options.Gitignore(name)
|
||||
if err != nil {
|
||||
ctx.NotFound()
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ func GetLabelTemplate(ctx *context.APIContext) {
|
||||
|
||||
labels, err := repo_module.LoadTemplateLabelsByDisplayName(name)
|
||||
if err != nil {
|
||||
ctx.NotFound()
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ func GetLicenseTemplateInfo(ctx *context.APIContext) {
|
||||
|
||||
text, err := options.License(name)
|
||||
if err != nil {
|
||||
ctx.NotFound()
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
@ -456,9 +456,9 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -4,13 +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"
|
||||
@ -65,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
|
||||
}
|
||||
|
||||
@ -127,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
|
||||
}
|
||||
@ -181,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
|
||||
}
|
||||
@ -229,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
|
||||
}
|
||||
@ -283,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
|
||||
}
|
||||
@ -342,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
|
||||
}
|
||||
@ -407,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
|
||||
}
|
||||
@ -423,9 +435,9 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
|
||||
|
||||
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
|
||||
}
|
||||
@ -472,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
|
||||
}
|
||||
|
||||
@ -569,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
|
||||
}
|
||||
|
||||
@ -580,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
|
||||
@ -622,7 +634,7 @@ func ActionsListRepositoryWorkflows(ctx *context.APIContext) {
|
||||
|
||||
workflows, err := actions_service.ListActionWorkflows(ctx)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ListActionWorkflows", err)
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -669,9 +681,9 @@ func ActionsGetWorkflow(ctx *context.APIContext) {
|
||||
workflow, err := actions_service.GetActionWorkflow(ctx, workflowID)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusNotFound, "GetActionWorkflow", err)
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "GetActionWorkflow", err)
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -717,9 +729,9 @@ func ActionsDisableWorkflow(ctx *context.APIContext) {
|
||||
err := actions_service.EnableOrDisableWorkflow(ctx, workflowID, false)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusNotFound, "DisableActionWorkflow", err)
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "DisableActionWorkflow", err)
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -768,7 +780,7 @@ func ActionsDispatchWorkflow(ctx *context.APIContext) {
|
||||
workflowID := ctx.PathParam("workflow_id")
|
||||
opt := web.GetForm(ctx).(*api.CreateActionWorkflowDispatch)
|
||||
if opt.Ref == "" {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "MissingWorkflowParameter", util.NewInvalidArgumentErrorf("ref is required parameter"))
|
||||
ctx.APIError(http.StatusUnprocessableEntity, util.NewInvalidArgumentErrorf("ref is required parameter"))
|
||||
return
|
||||
}
|
||||
|
||||
@ -794,11 +806,11 @@ func ActionsDispatchWorkflow(ctx *context.APIContext) {
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusNotFound, "DispatchActionWorkflow", err)
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else if errors.Is(err, util.ErrPermissionDenied) {
|
||||
ctx.Error(http.StatusForbidden, "DispatchActionWorkflow", err)
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "DispatchActionWorkflow", err)
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -846,12 +858,391 @@ func ActionsEnableWorkflow(ctx *context.APIContext) {
|
||||
err := actions_service.EnableOrDisableWorkflow(ctx, workflowID, true)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusNotFound, "EnableActionWorkflow", err)
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "EnableActionWorkflow", err)
|
||||
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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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})
|
||||
|
@ -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))
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user