From 0fd1672ae49a5f69fca7d90336ae75be83a21014 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 10 Jul 2023 17:31:19 +0800 Subject: [PATCH 1/6] For API attachments, use API URL (#25639) Fix #25257 --------- Co-authored-by: Giteabot --- models/repo/attachment.go | 7 ---- routers/api/v1/api.go | 6 +-- routers/api/v1/repo/issue_attachment.go | 8 ++-- routers/api/v1/repo/issue_comment.go | 12 +++--- .../api/v1/repo/issue_comment_attachment.go | 10 ++--- routers/api/v1/repo/release.go | 10 ++--- routers/api/v1/repo/release_attachment.go | 14 +++---- routers/api/v1/repo/release_tags.go | 2 +- routers/web/repo/issue.go | 10 ++--- services/actions/notifier.go | 4 +- services/actions/notifier_helper.go | 2 +- services/convert/activity.go | 2 +- services/convert/attachment.go | 40 ++++++++++++++++--- services/convert/issue.go | 19 ++++++++- services/convert/issue_comment.go | 10 ++--- services/convert/release.go | 6 +-- services/webhook/notifier.go | 8 ++-- .../api_comment_attachment_test.go | 3 +- tests/integration/api_comment_test.go | 2 +- 19 files changed, 108 insertions(+), 67 deletions(-) diff --git a/models/repo/attachment.go b/models/repo/attachment.go index 93b83aae8a8d..df3b9cd2137b 100644 --- a/models/repo/attachment.go +++ b/models/repo/attachment.go @@ -65,13 +65,6 @@ func (a *Attachment) DownloadURL() string { return setting.AppURL + "attachments/" + url.PathEscape(a.UUID) } -// _____ __ __ .__ __ -// / _ \_/ |__/ |______ ____ | |__ _____ ____ _____/ |_ -// / /_\ \ __\ __\__ \ _/ ___\| | \ / \_/ __ \ / \ __\ -// / | \ | | | / __ \\ \___| Y \ Y Y \ ___/| | \ | -// \____|__ /__| |__| (____ /\___ >___| /__|_| /\___ >___| /__| -// \/ \/ \/ \/ \/ \/ \/ - // ErrAttachmentNotExist represents a "AttachmentNotExist" kind of error. type ErrAttachmentNotExist struct { ID int64 diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 0e28bde68309..eee97cbc2093 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1034,7 +1034,7 @@ func Routes() *web.Route { m.Group("/assets", func() { m.Combo("").Get(repo.ListReleaseAttachments). Post(reqToken(), reqRepoWriter(unit.TypeReleases), repo.CreateReleaseAttachment) - m.Combo("/{asset}").Get(repo.GetReleaseAttachment). + m.Combo("/{attachment_id}").Get(repo.GetReleaseAttachment). Patch(reqToken(), reqRepoWriter(unit.TypeReleases), bind(api.EditAttachmentOptions{}), repo.EditReleaseAttachment). Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseAttachment) }) @@ -1179,7 +1179,7 @@ func Routes() *web.Route { m.Combo(""). Get(repo.ListIssueCommentAttachments). Post(reqToken(), mustNotBeArchived, repo.CreateIssueCommentAttachment) - m.Combo("/{asset}"). + m.Combo("/{attachment_id}"). Get(repo.GetIssueCommentAttachment). Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueCommentAttachment). Delete(reqToken(), mustNotBeArchived, repo.DeleteIssueCommentAttachment) @@ -1231,7 +1231,7 @@ func Routes() *web.Route { m.Combo(""). Get(repo.ListIssueAttachments). Post(reqToken(), mustNotBeArchived, repo.CreateIssueAttachment) - m.Combo("/{asset}"). + m.Combo("/{attachment_id}"). Get(repo.GetIssueAttachment). Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueAttachment). Delete(reqToken(), mustNotBeArchived, repo.DeleteIssueAttachment) diff --git a/routers/api/v1/repo/issue_attachment.go b/routers/api/v1/repo/issue_attachment.go index 92e11386882c..779da9fd7f80 100644 --- a/routers/api/v1/repo/issue_attachment.go +++ b/routers/api/v1/repo/issue_attachment.go @@ -64,7 +64,7 @@ func GetIssueAttachment(ctx *context.APIContext) { return } - ctx.JSON(http.StatusOK, convert.ToAttachment(attach)) + ctx.JSON(http.StatusOK, convert.ToAPIAttachment(ctx.Repo.Repository, attach)) } // ListIssueAttachments lists all attachments of the issue @@ -194,7 +194,7 @@ func CreateIssueAttachment(ctx *context.APIContext) { return } - ctx.JSON(http.StatusCreated, convert.ToAttachment(attachment)) + ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attachment)) } // EditIssueAttachment updates the given attachment @@ -254,7 +254,7 @@ func EditIssueAttachment(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "UpdateAttachment", err) } - ctx.JSON(http.StatusCreated, convert.ToAttachment(attachment)) + ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attachment)) } // DeleteIssueAttachment delete a given attachment @@ -332,7 +332,7 @@ func getIssueAttachmentSafeWrite(ctx *context.APIContext) *repo_model.Attachment } func getIssueAttachmentSafeRead(ctx *context.APIContext, issue *issues_model.Issue) *repo_model.Attachment { - attachment, err := repo_model.GetAttachmentByID(ctx, ctx.ParamsInt64("asset")) + attachment, err := repo_model.GetAttachmentByID(ctx, ctx.ParamsInt64("attachment_id")) if err != nil { ctx.NotFoundOrServerError("GetAttachmentByID", repo_model.IsErrAttachmentNotExist, err) return nil diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go index 5616e255ad8e..c2392126db90 100644 --- a/routers/api/v1/repo/issue_comment.go +++ b/routers/api/v1/repo/issue_comment.go @@ -103,7 +103,7 @@ func ListIssueComments(ctx *context.APIContext) { apiComments := make([]*api.Comment, len(comments)) for i, comment := range comments { comment.Issue = issue - apiComments[i] = convert.ToComment(ctx, comments[i]) + apiComments[i] = convert.ToAPIComment(ctx, ctx.Repo.Repository, comments[i]) } ctx.SetTotalCountHeader(totalCount) @@ -191,7 +191,7 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) { for _, comment := range comments { if comment.Type != issues_model.CommentTypeCode && isXRefCommentAccessible(ctx, ctx.Doer, comment, issue.RepoID) { comment.Issue = issue - apiComments = append(apiComments, convert.ToTimelineComment(ctx, comment, ctx.Doer)) + apiComments = append(apiComments, convert.ToTimelineComment(ctx, issue.Repo, comment, ctx.Doer)) } } @@ -308,7 +308,7 @@ func ListRepoIssueComments(ctx *context.APIContext) { return } for i := range comments { - apiComments[i] = convert.ToComment(ctx, comments[i]) + apiComments[i] = convert.ToAPIComment(ctx, ctx.Repo.Repository, comments[i]) } ctx.SetTotalCountHeader(totalCount) @@ -368,7 +368,7 @@ func CreateIssueComment(ctx *context.APIContext) { return } - ctx.JSON(http.StatusCreated, convert.ToComment(ctx, comment)) + ctx.JSON(http.StatusCreated, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment)) } // GetIssueComment Get a comment by ID @@ -436,7 +436,7 @@ func GetIssueComment(ctx *context.APIContext) { return } - ctx.JSON(http.StatusOK, convert.ToComment(ctx, comment)) + ctx.JSON(http.StatusOK, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment)) } // EditIssueComment modify a comment of an issue @@ -561,7 +561,7 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) return } - ctx.JSON(http.StatusOK, convert.ToComment(ctx, comment)) + ctx.JSON(http.StatusOK, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment)) } // DeleteIssueComment delete a comment from an issue diff --git a/routers/api/v1/repo/issue_comment_attachment.go b/routers/api/v1/repo/issue_comment_attachment.go index 6fe4dbc97715..121e3f10e0df 100644 --- a/routers/api/v1/repo/issue_comment_attachment.go +++ b/routers/api/v1/repo/issue_comment_attachment.go @@ -68,7 +68,7 @@ func GetIssueCommentAttachment(ctx *context.APIContext) { return } - ctx.JSON(http.StatusOK, convert.ToAttachment(attachment)) + ctx.JSON(http.StatusOK, convert.ToAPIAttachment(ctx.Repo.Repository, attachment)) } // ListIssueCommentAttachments lists all attachments of the comment @@ -110,7 +110,7 @@ func ListIssueCommentAttachments(ctx *context.APIContext) { return } - ctx.JSON(http.StatusOK, convert.ToAttachments(comment.Attachments)) + ctx.JSON(http.StatusOK, convert.ToAPIAttachments(ctx.Repo.Repository, comment.Attachments)) } // CreateIssueCommentAttachment creates an attachment and saves the given file @@ -201,7 +201,7 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) { return } - ctx.JSON(http.StatusCreated, convert.ToAttachment(attachment)) + ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attachment)) } // EditIssueCommentAttachment updates the given attachment @@ -259,7 +259,7 @@ func EditIssueCommentAttachment(ctx *context.APIContext) { if err := repo_model.UpdateAttachment(ctx, attach); err != nil { ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach) } - ctx.JSON(http.StatusCreated, convert.ToAttachment(attach)) + ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach)) } // DeleteIssueCommentAttachment delete a given attachment @@ -352,7 +352,7 @@ func canUserWriteIssueCommentAttachment(ctx *context.APIContext, comment *issues } func getIssueCommentAttachmentSafeRead(ctx *context.APIContext, comment *issues_model.Comment) *repo_model.Attachment { - attachment, err := repo_model.GetAttachmentByID(ctx, ctx.ParamsInt64("asset")) + attachment, err := repo_model.GetAttachmentByID(ctx, ctx.ParamsInt64("attachment_id")) if err != nil { ctx.NotFoundOrServerError("GetAttachmentByID", repo_model.IsErrAttachmentNotExist, err) return nil diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go index e9693dd053bf..af7199d1d63d 100644 --- a/routers/api/v1/repo/release.go +++ b/routers/api/v1/repo/release.go @@ -64,7 +64,7 @@ func GetRelease(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) return } - ctx.JSON(http.StatusOK, convert.ToRelease(ctx, release)) + ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release)) } // GetLatestRelease gets the most recent non-prerelease, non-draft release of a repository, sorted by created_at @@ -105,7 +105,7 @@ func GetLatestRelease(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) return } - ctx.JSON(http.StatusOK, convert.ToRelease(ctx, release)) + ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release)) } // ListReleases list a repository's releases @@ -174,7 +174,7 @@ func ListReleases(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) return } - rels[i] = convert.ToRelease(ctx, release) + rels[i] = convert.ToAPIRelease(ctx, ctx.Repo.Repository, release) } filteredCount, err := repo_model.CountReleasesByRepoID(ctx.Repo.Repository.ID, opts) @@ -272,7 +272,7 @@ func CreateRelease(ctx *context.APIContext) { return } } - ctx.JSON(http.StatusCreated, convert.ToRelease(ctx, rel)) + ctx.JSON(http.StatusCreated, convert.ToAPIRelease(ctx, ctx.Repo.Repository, rel)) } // EditRelease edit a release @@ -357,7 +357,7 @@ func EditRelease(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) return } - ctx.JSON(http.StatusOK, convert.ToRelease(ctx, rel)) + ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, rel)) } // DeleteRelease delete a release from a repository diff --git a/routers/api/v1/repo/release_attachment.go b/routers/api/v1/repo/release_attachment.go index 305b2808df54..a7d73acceb92 100644 --- a/routers/api/v1/repo/release_attachment.go +++ b/routers/api/v1/repo/release_attachment.go @@ -52,7 +52,7 @@ func GetReleaseAttachment(ctx *context.APIContext) { // "$ref": "#/responses/Attachment" releaseID := ctx.ParamsInt64(":id") - attachID := ctx.ParamsInt64(":asset") + attachID := ctx.ParamsInt64(":attachment_id") attach, err := repo_model.GetAttachmentByID(ctx, attachID) if err != nil { if repo_model.IsErrAttachmentNotExist(err) { @@ -68,7 +68,7 @@ func GetReleaseAttachment(ctx *context.APIContext) { return } // FIXME Should prove the existence of the given repo, but results in unnecessary database requests - ctx.JSON(http.StatusOK, convert.ToAttachment(attach)) + ctx.JSON(http.StatusOK, convert.ToAPIAttachment(ctx.Repo.Repository, attach)) } // ListReleaseAttachments lists all attachments of the release @@ -117,7 +117,7 @@ func ListReleaseAttachments(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) return } - ctx.JSON(http.StatusOK, convert.ToRelease(ctx, release).Attachments) + ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release).Attachments) } // CreateReleaseAttachment creates an attachment and saves the given file @@ -209,7 +209,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) { return } - ctx.JSON(http.StatusCreated, convert.ToAttachment(attach)) + ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach)) } // EditReleaseAttachment updates the given attachment @@ -256,7 +256,7 @@ func EditReleaseAttachment(ctx *context.APIContext) { // Check if release exists an load release releaseID := ctx.ParamsInt64(":id") - attachID := ctx.ParamsInt64(":asset") + attachID := ctx.ParamsInt64(":attachment_id") attach, err := repo_model.GetAttachmentByID(ctx, attachID) if err != nil { if repo_model.IsErrAttachmentNotExist(err) { @@ -279,7 +279,7 @@ func EditReleaseAttachment(ctx *context.APIContext) { if err := repo_model.UpdateAttachment(ctx, attach); err != nil { ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach) } - ctx.JSON(http.StatusCreated, convert.ToAttachment(attach)) + ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach)) } // DeleteReleaseAttachment delete a given attachment @@ -318,7 +318,7 @@ func DeleteReleaseAttachment(ctx *context.APIContext) { // Check if release exists an load release releaseID := ctx.ParamsInt64(":id") - attachID := ctx.ParamsInt64(":asset") + attachID := ctx.ParamsInt64(":attachment_id") attach, err := repo_model.GetAttachmentByID(ctx, attachID) if err != nil { if repo_model.IsErrAttachmentNotExist(err) { diff --git a/routers/api/v1/repo/release_tags.go b/routers/api/v1/repo/release_tags.go index 051ee8f22ec0..a03edfafcf50 100644 --- a/routers/api/v1/repo/release_tags.go +++ b/routers/api/v1/repo/release_tags.go @@ -63,7 +63,7 @@ func GetReleaseByTag(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) return } - ctx.JSON(http.StatusOK, convert.ToRelease(ctx, release)) + ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release)) } // DeleteReleaseByTag delete a release from a repository by tag name diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 317f762fb1a2..31bcbd7c21c0 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -2058,7 +2058,7 @@ func GetIssueInfo(ctx *context.Context) { } } - ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, issue)) + ctx.JSON(http.StatusOK, convert.ToIssue(ctx, issue)) } // UpdateIssueTitle change issue's title @@ -2563,7 +2563,7 @@ func SearchIssues(ctx *context.Context) { } ctx.SetTotalCountHeader(filteredCount) - ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, issues)) + ctx.JSON(http.StatusOK, convert.ToIssueList(ctx, issues)) } func getUserIDForFilter(ctx *context.Context, queryName string) int64 { @@ -2724,7 +2724,7 @@ func ListIssues(ctx *context.Context) { } ctx.SetTotalCountHeader(filteredCount) - ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, issues)) + ctx.JSON(http.StatusOK, convert.ToIssueList(ctx, issues)) } func BatchDeleteIssues(ctx *context.Context) { @@ -3290,7 +3290,7 @@ func GetIssueAttachments(ctx *context.Context) { } attachments := make([]*api.Attachment, len(issue.Attachments)) for i := 0; i < len(issue.Attachments); i++ { - attachments[i] = convert.ToAttachment(issue.Attachments[i]) + attachments[i] = convert.ToAttachment(ctx.Repo.Repository, issue.Attachments[i]) } ctx.JSON(http.StatusOK, attachments) } @@ -3314,7 +3314,7 @@ func GetCommentAttachments(ctx *context.Context) { return } for i := 0; i < len(comment.Attachments); i++ { - attachments = append(attachments, convert.ToAttachment(comment.Attachments[i])) + attachments = append(attachments, convert.ToAttachment(ctx.Repo.Repository, comment.Attachments[i])) } ctx.JSON(http.StatusOK, attachments) } diff --git a/services/actions/notifier.go b/services/actions/notifier.go index 507eeaacf6cc..cfe2e284dacf 100644 --- a/services/actions/notifier.go +++ b/services/actions/notifier.go @@ -171,7 +171,7 @@ func (n *actionsNotifier) NotifyCreateIssueComment(ctx context.Context, doer *us WithPayload(&api.IssueCommentPayload{ Action: api.HookIssueCommentCreated, Issue: convert.ToAPIIssue(ctx, issue), - Comment: convert.ToComment(ctx, comment), + Comment: convert.ToAPIComment(ctx, repo, comment), Repository: convert.ToRepo(ctx, repo, permission), Sender: convert.ToUser(ctx, doer, nil), IsPull: true, @@ -185,7 +185,7 @@ func (n *actionsNotifier) NotifyCreateIssueComment(ctx context.Context, doer *us WithPayload(&api.IssueCommentPayload{ Action: api.HookIssueCommentCreated, Issue: convert.ToAPIIssue(ctx, issue), - Comment: convert.ToComment(ctx, comment), + Comment: convert.ToAPIComment(ctx, repo, comment), Repository: convert.ToRepo(ctx, repo, permission), Sender: convert.ToUser(ctx, doer, nil), IsPull: false, diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go index c4c2a0df29b7..90ad3001bad2 100644 --- a/services/actions/notifier_helper.go +++ b/services/actions/notifier_helper.go @@ -260,7 +260,7 @@ func notifyRelease(ctx context.Context, doer *user_model.User, rel *repo_model.R WithRef(git.RefNameFromTag(rel.TagName).String()). WithPayload(&api.ReleasePayload{ Action: action, - Release: convert.ToRelease(ctx, rel), + Release: convert.ToAPIRelease(ctx, rel.Repo, rel), Repository: convert.ToRepo(ctx, rel.Repo, permission), Sender: convert.ToUser(ctx, doer, nil), }). diff --git a/services/convert/activity.go b/services/convert/activity.go index 71a2722a493f..01fef73e589f 100644 --- a/services/convert/activity.go +++ b/services/convert/activity.go @@ -37,7 +37,7 @@ func ToActivity(ctx context.Context, ac *activities_model.Action, doer *user_mod if ac.Comment != nil { result.CommentID = ac.CommentID - result.Comment = ToComment(ctx, ac.Comment) + result.Comment = ToAPIComment(ctx, ac.Repo, ac.Comment) } return result diff --git a/services/convert/attachment.go b/services/convert/attachment.go index ddba181a1204..ab36a1c57785 100644 --- a/services/convert/attachment.go +++ b/services/convert/attachment.go @@ -4,12 +4,38 @@ package convert import ( + "strconv" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" ) -// ToAttachment converts models.Attachment to api.Attachment -func ToAttachment(a *repo_model.Attachment) *api.Attachment { +func WebAssetDownloadURL(repo *repo_model.Repository, attach *repo_model.Attachment) string { + return attach.DownloadURL() +} + +func APIAssetDownloadURL(repo *repo_model.Repository, attach *repo_model.Attachment) string { + if attach.CustomDownloadURL != "" { + return attach.CustomDownloadURL + } + + // /repos/{owner}/{repo}/releases/{id}/assets/{attachment_id} + return setting.AppURL + "api/repos/" + repo.FullName() + "/releases/" + strconv.FormatInt(attach.ReleaseID, 10) + "/assets/" + strconv.FormatInt(attach.ID, 10) +} + +// ToAttachment converts models.Attachment to api.Attachment for API usage +func ToAttachment(repo *repo_model.Repository, a *repo_model.Attachment) *api.Attachment { + return toAttachment(repo, a, WebAssetDownloadURL) +} + +// ToAPIAttachment converts models.Attachment to api.Attachment for API usage +func ToAPIAttachment(repo *repo_model.Repository, a *repo_model.Attachment) *api.Attachment { + return toAttachment(repo, a, APIAssetDownloadURL) +} + +// toAttachment converts models.Attachment to api.Attachment for API usage +func toAttachment(repo *repo_model.Repository, a *repo_model.Attachment, getDownloadURL func(repo *repo_model.Repository, attach *repo_model.Attachment) string) *api.Attachment { return &api.Attachment{ ID: a.ID, Name: a.Name, @@ -17,14 +43,18 @@ func ToAttachment(a *repo_model.Attachment) *api.Attachment { DownloadCount: a.DownloadCount, Size: a.Size, UUID: a.UUID, - DownloadURL: a.DownloadURL(), + DownloadURL: getDownloadURL(repo, a), // for web request json and api request json, return different download urls } } -func ToAttachments(attachments []*repo_model.Attachment) []*api.Attachment { +func ToAPIAttachments(repo *repo_model.Repository, attachments []*repo_model.Attachment) []*api.Attachment { + return toAttachments(repo, attachments, APIAssetDownloadURL) +} + +func toAttachments(repo *repo_model.Repository, attachments []*repo_model.Attachment, getDownloadURL func(repo *repo_model.Repository, attach *repo_model.Attachment) string) []*api.Attachment { converted := make([]*api.Attachment, 0, len(attachments)) for _, attachment := range attachments { - converted = append(converted, ToAttachment(attachment)) + converted = append(converted, toAttachment(repo, attachment, getDownloadURL)) } return converted } diff --git a/services/convert/issue.go b/services/convert/issue.go index bcb1618e8faf..d81840f02521 100644 --- a/services/convert/issue.go +++ b/services/convert/issue.go @@ -19,11 +19,19 @@ import ( api "code.gitea.io/gitea/modules/structs" ) +func ToIssue(ctx context.Context, issue *issues_model.Issue) *api.Issue { + return toIssue(ctx, issue, WebAssetDownloadURL) +} + // ToAPIIssue converts an Issue to API format // it assumes some fields assigned with values: // Required - Poster, Labels, // Optional - Milestone, Assignee, PullRequest func ToAPIIssue(ctx context.Context, issue *issues_model.Issue) *api.Issue { + return toIssue(ctx, issue, APIAssetDownloadURL) +} + +func toIssue(ctx context.Context, issue *issues_model.Issue, getDownloadURL func(repo *repo_model.Repository, attach *repo_model.Attachment) string) *api.Issue { if err := issue.LoadLabels(ctx); err != nil { return &api.Issue{} } @@ -40,7 +48,7 @@ func ToAPIIssue(ctx context.Context, issue *issues_model.Issue) *api.Issue { Poster: ToUser(ctx, issue.Poster, nil), Title: issue.Title, Body: issue.Content, - Attachments: ToAttachments(issue.Attachments), + Attachments: toAttachments(issue.Repo, issue.Attachments, getDownloadURL), Ref: issue.Ref, State: issue.State(), IsLocked: issue.IsLocked, @@ -105,6 +113,15 @@ func ToAPIIssue(ctx context.Context, issue *issues_model.Issue) *api.Issue { return apiIssue } +// ToIssueList converts an IssueList to API format +func ToIssueList(ctx context.Context, il issues_model.IssueList) []*api.Issue { + result := make([]*api.Issue, len(il)) + for i := range il { + result[i] = ToIssue(ctx, il[i]) + } + return result +} + // ToAPIIssueList converts an IssueList to API format func ToAPIIssueList(ctx context.Context, il issues_model.IssueList) []*api.Issue { result := make([]*api.Issue, len(il)) diff --git a/services/convert/issue_comment.go b/services/convert/issue_comment.go index db48faa69e69..b0145c38e639 100644 --- a/services/convert/issue_comment.go +++ b/services/convert/issue_comment.go @@ -14,8 +14,8 @@ import ( "code.gitea.io/gitea/modules/util" ) -// ToComment converts a issues_model.Comment to the api.Comment format -func ToComment(ctx context.Context, c *issues_model.Comment) *api.Comment { +// ToAPIComment converts a issues_model.Comment to the api.Comment format for API usage +func ToAPIComment(ctx context.Context, repo *repo_model.Repository, c *issues_model.Comment) *api.Comment { return &api.Comment{ ID: c.ID, Poster: ToUser(ctx, c.Poster, nil), @@ -23,14 +23,14 @@ func ToComment(ctx context.Context, c *issues_model.Comment) *api.Comment { IssueURL: c.IssueURL(), PRURL: c.PRURL(), Body: c.Content, - Attachments: ToAttachments(c.Attachments), + Attachments: ToAPIAttachments(repo, c.Attachments), Created: c.CreatedUnix.AsTime(), Updated: c.UpdatedUnix.AsTime(), } } // ToTimelineComment converts a issues_model.Comment to the api.TimelineComment format -func ToTimelineComment(ctx context.Context, c *issues_model.Comment, doer *user_model.User) *api.TimelineComment { +func ToTimelineComment(ctx context.Context, repo *repo_model.Repository, c *issues_model.Comment, doer *user_model.User) *api.TimelineComment { err := c.LoadMilestone(ctx) if err != nil { log.Error("LoadMilestone: %v", err) @@ -143,7 +143,7 @@ func ToTimelineComment(ctx context.Context, c *issues_model.Comment, doer *user_ log.Error("LoadPoster: %v", err) return nil } - comment.RefComment = ToComment(ctx, com) + comment.RefComment = ToAPIComment(ctx, repo, com) } if c.Label != nil { diff --git a/services/convert/release.go b/services/convert/release.go index ca28aa0d6b07..d8aa46d4326a 100644 --- a/services/convert/release.go +++ b/services/convert/release.go @@ -10,8 +10,8 @@ import ( api "code.gitea.io/gitea/modules/structs" ) -// ToRelease convert a repo_model.Release to api.Release -func ToRelease(ctx context.Context, r *repo_model.Release) *api.Release { +// ToAPIRelease convert a repo_model.Release to api.Release +func ToAPIRelease(ctx context.Context, repo *repo_model.Repository, r *repo_model.Release) *api.Release { return &api.Release{ ID: r.ID, TagName: r.TagName, @@ -27,6 +27,6 @@ func ToRelease(ctx context.Context, r *repo_model.Release) *api.Release { CreatedAt: r.CreatedUnix.AsTime(), PublishedAt: r.CreatedUnix.AsTime(), Publisher: ToUser(ctx, r.Publisher, nil), - Attachments: ToAttachments(r.Attachments), + Attachments: ToAPIAttachments(repo, r.Attachments), } } diff --git a/services/webhook/notifier.go b/services/webhook/notifier.go index 3332d5d4aa1e..23080a5a3514 100644 --- a/services/webhook/notifier.go +++ b/services/webhook/notifier.go @@ -386,7 +386,7 @@ func (m *webhookNotifier) NotifyUpdateComment(ctx context.Context, doer *user_mo if err := PrepareWebhooks(ctx, EventSource{Repository: c.Issue.Repo}, eventType, &api.IssueCommentPayload{ Action: api.HookIssueCommentEdited, Issue: convert.ToAPIIssue(ctx, c.Issue), - Comment: convert.ToComment(ctx, c), + Comment: convert.ToAPIComment(ctx, c.Issue.Repo, c), Changes: &api.ChangesPayload{ Body: &api.ChangesFromPayload{ From: oldContent, @@ -414,7 +414,7 @@ func (m *webhookNotifier) NotifyCreateIssueComment(ctx context.Context, doer *us if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, eventType, &api.IssueCommentPayload{ Action: api.HookIssueCommentCreated, Issue: convert.ToAPIIssue(ctx, issue), - Comment: convert.ToComment(ctx, comment), + Comment: convert.ToAPIComment(ctx, repo, comment), Repository: convert.ToRepo(ctx, repo, permission), Sender: convert.ToUser(ctx, doer, nil), IsPull: issue.IsPull, @@ -451,7 +451,7 @@ func (m *webhookNotifier) NotifyDeleteComment(ctx context.Context, doer *user_mo if err := PrepareWebhooks(ctx, EventSource{Repository: comment.Issue.Repo}, eventType, &api.IssueCommentPayload{ Action: api.HookIssueCommentDeleted, Issue: convert.ToAPIIssue(ctx, comment.Issue), - Comment: convert.ToComment(ctx, comment), + Comment: convert.ToAPIComment(ctx, comment.Issue.Repo, comment), Repository: convert.ToRepo(ctx, comment.Issue.Repo, permission), Sender: convert.ToUser(ctx, doer, nil), IsPull: comment.Issue.IsPull, @@ -808,7 +808,7 @@ func sendReleaseHook(ctx context.Context, doer *user_model.User, rel *repo_model permission, _ := access_model.GetUserRepoPermission(ctx, rel.Repo, doer) if err := PrepareWebhooks(ctx, EventSource{Repository: rel.Repo}, webhook_module.HookEventRelease, &api.ReleasePayload{ Action: action, - Release: convert.ToRelease(ctx, rel), + Release: convert.ToAPIRelease(ctx, rel.Repo, rel), Repository: convert.ToRepo(ctx, rel.Repo, permission), Sender: convert.ToUser(ctx, doer, nil), }); err != nil { diff --git a/tests/integration/api_comment_attachment_test.go b/tests/integration/api_comment_attachment_test.go index 8aa73dd3688a..e211376c3c88 100644 --- a/tests/integration/api_comment_attachment_test.go +++ b/tests/integration/api_comment_attachment_test.go @@ -45,11 +45,12 @@ func TestAPIGetCommentAttachment(t *testing.T) { var apiAttachment api.Attachment DecodeJSON(t, resp, &apiAttachment) - expect := convert.ToAttachment(attachment) + expect := convert.ToAPIAttachment(repo, attachment) assert.Equal(t, expect.ID, apiAttachment.ID) assert.Equal(t, expect.Name, apiAttachment.Name) assert.Equal(t, expect.UUID, apiAttachment.UUID) assert.Equal(t, expect.Created.Unix(), apiAttachment.Created.Unix()) + assert.Equal(t, expect.DownloadURL, apiAttachment.DownloadURL) } func TestAPIListCommentAttachments(t *testing.T) { diff --git a/tests/integration/api_comment_test.go b/tests/integration/api_comment_test.go index c773afab3d65..ee648210e5e4 100644 --- a/tests/integration/api_comment_test.go +++ b/tests/integration/api_comment_test.go @@ -128,7 +128,7 @@ func TestAPIGetComment(t *testing.T) { DecodeJSON(t, resp, &apiComment) assert.NoError(t, comment.LoadPoster(db.DefaultContext)) - expect := convert.ToComment(db.DefaultContext, comment) + expect := convert.ToAPIComment(db.DefaultContext, repo, comment) assert.Equal(t, expect.ID, apiComment.ID) assert.Equal(t, expect.Poster.FullName, apiComment.Poster.FullName) From 2f31d2d56c22400b2e79b279a5d0e845febba137 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 10 Jul 2023 19:18:55 +0800 Subject: [PATCH 2/6] Exclude default branch from pushed branch hint (#25795) When pushing to default branch, no pushing hint should be prompt. Fix #25778 --------- Co-authored-by: Giteabot --- models/git/branch.go | 4 +++- routers/web/repo/view.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/models/git/branch.go b/models/git/branch.go index 97891f01ebb4..d57b72719ce8 100644 --- a/models/git/branch.go +++ b/models/git/branch.go @@ -382,7 +382,8 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str } // FindRecentlyPushedNewBranches return at most 2 new branches pushed by the user in 6 hours which has no opened PRs created -func FindRecentlyPushedNewBranches(ctx context.Context, repoID, userID int64) (BranchList, error) { +// except the indicate branch +func FindRecentlyPushedNewBranches(ctx context.Context, repoID, userID int64, excludeBranchName string) (BranchList, error) { branches := make(BranchList, 0, 2) subQuery := builder.Select("head_branch").From("pull_request"). InnerJoin("issue", "issue.id = pull_request.issue_id"). @@ -392,6 +393,7 @@ func FindRecentlyPushedNewBranches(ctx context.Context, repoID, userID int64) (B }) err := db.GetEngine(ctx). Where("pusher_id=? AND is_deleted=?", userID, false). + And("name <> ?", excludeBranchName). And("updated_unix >= ?", time.Now().Add(-time.Hour*6).Unix()). NotIn("name", subQuery). OrderBy("branch.updated_unix DESC"). diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index acea08d6297e..ece2ec5416a5 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -982,7 +982,7 @@ func renderCode(ctx *context.Context) { ctx.ServerError("GetBaseRepo", err) return } - ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID) + ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID, ctx.Repo.Repository.DefaultBranch) if err != nil { ctx.ServerError("GetRecentlyPushedBranches", err) return From 491cc06ffe3491242ad9ff6227423d99e673d0c2 Mon Sep 17 00:00:00 2001 From: caicandong <50507092+CaiCandong@users.noreply.github.com> Date: Tue, 11 Jul 2023 10:04:28 +0800 Subject: [PATCH 3/6] Fix the error message when the token is incorrect (#25701) we refactored `userIDFromToken` for the token parsing part into a new function `parseToken`. `parseToken` returns the string `token` from request, and a boolean `ok` representing whether the token exists or not. So we can distinguish between token non-existence and token inconsistency in the `verfity` function, thus solving the problem of no proper error message when the token is inconsistent. close #24439 related #22119 --------- Co-authored-by: Jason Song Co-authored-by: Giteabot --- services/auth/group.go | 15 +++++++-- services/auth/oauth2.go | 50 +++++++++++++++++------------- tests/integration/api_repo_test.go | 11 +++++++ 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/services/auth/group.go b/services/auth/group.go index a1ff65f20385..7193dfcf49e2 100644 --- a/services/auth/group.go +++ b/services/auth/group.go @@ -49,12 +49,22 @@ func (b *Group) Name() string { // Verify extracts and validates func (b *Group) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) { // Try to sign in with each of the enabled plugins + var retErr error for _, ssoMethod := range b.methods { user, err := ssoMethod.Verify(req, w, store, sess) if err != nil { - return nil, err + if retErr == nil { + retErr = err + } + // Try other methods if this one failed. + // Some methods may share the same protocol to detect if they are matched. + // For example, OAuth2 and conan.Auth both read token from "Authorization: Bearer " header, + // If OAuth2 returns error, we should give conan.Auth a chance to try. + continue } + // If any method returns a user, we can stop trying. + // Return the user and ignore any error returned by previous methods. if user != nil { if store.GetData()["AuthedMethod"] == nil { if named, ok := ssoMethod.(Named); ok { @@ -65,5 +75,6 @@ func (b *Group) Verify(req *http.Request, w http.ResponseWriter, store DataStore } } - return nil, nil + // If no method returns a user, return the error returned by the first method. + return nil, retErr } diff --git a/services/auth/oauth2.go b/services/auth/oauth2.go index b70f84da9b63..0dd7a12d2c43 100644 --- a/services/auth/oauth2.go +++ b/services/auth/oauth2.go @@ -59,31 +59,32 @@ func (o *OAuth2) Name() string { return "oauth2" } -// userIDFromToken returns the user id corresponding to the OAuth token. -// It will set 'IsApiToken' to true if the token is an API token and -// set 'ApiTokenScope' to the scope of the access token -func (o *OAuth2) userIDFromToken(req *http.Request, store DataStore) int64 { +// parseToken returns the token from request, and a boolean value +// representing whether the token exists or not +func parseToken(req *http.Request) (string, bool) { _ = req.ParseForm() - + // Check token. + if token := req.Form.Get("token"); token != "" { + return token, true + } // Check access token. - tokenSHA := req.Form.Get("token") - if len(tokenSHA) == 0 { - tokenSHA = req.Form.Get("access_token") - } - if len(tokenSHA) == 0 { - // Well, check with header again. - auHead := req.Header.Get("Authorization") - if len(auHead) > 0 { - auths := strings.Fields(auHead) - if len(auths) == 2 && (auths[0] == "token" || strings.ToLower(auths[0]) == "bearer") { - tokenSHA = auths[1] - } - } + if token := req.Form.Get("access_token"); token != "" { + return token, true } - if len(tokenSHA) == 0 { - return 0 + // check header token + if auHead := req.Header.Get("Authorization"); auHead != "" { + auths := strings.Fields(auHead) + if len(auths) == 2 && (auths[0] == "token" || strings.ToLower(auths[0]) == "bearer") { + return auths[1], true + } } + return "", false +} +// userIDFromToken returns the user id corresponding to the OAuth token. +// It will set 'IsApiToken' to true if the token is an API token and +// set 'ApiTokenScope' to the scope of the access token +func (o *OAuth2) userIDFromToken(tokenSHA string, store DataStore) int64 { // Let's see if token is valid. if strings.Contains(tokenSHA, ".") { uid := CheckOAuthAccessToken(tokenSHA) @@ -129,10 +130,15 @@ func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStor return nil, nil } - id := o.userIDFromToken(req, store) + token, ok := parseToken(req) + if !ok { + return nil, nil + } + + id := o.userIDFromToken(token, store) if id <= 0 && id != -2 { // -2 means actions, so we need to allow it. - return nil, nil + return nil, user_model.ErrUserNotExist{} } log.Trace("OAuth2 Authorization: Found token for user[%d]", id) diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index 0f387192eb09..fae141556869 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -41,6 +41,17 @@ func TestAPIUserReposNotLogin(t *testing.T) { } } +func TestAPIUserReposWithWrongToken(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + wrongToken := fmt.Sprintf("Bearer %s", "wrong_token") + req := NewRequestf(t, "GET", "/api/v1/users/%s/repos", user.Name) + req = addTokenAuthHeader(req, wrongToken) + resp := MakeRequest(t, req, http.StatusUnauthorized) + + assert.Contains(t, resp.Body.String(), "user does not exist") +} + func TestAPISearchRepo(t *testing.T) { defer tests.PrepareTestEnv(t)() const keyword = "test" From 44572e924347420d3e11823b93921e01a5529959 Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Wed, 12 Jul 2023 03:47:50 +0900 Subject: [PATCH 4/6] Fix incorrect oldest sort in project list (#25806) sort type `oldest` should be `Asc`. Added a test for this. --- models/fixtures/project.yml | 8 +++++++ models/project/project.go | 28 ++++++++++++------------ models/project/project_test.go | 39 ++++++++++++++++++++++++++++++++++ routers/web/org/projects.go | 2 +- routers/web/repo/projects.go | 2 +- 5 files changed, 64 insertions(+), 15 deletions(-) diff --git a/models/fixtures/project.yml b/models/fixtures/project.yml index 3fa4286c53a0..1bf8030f6aa5 100644 --- a/models/fixtures/project.yml +++ b/models/fixtures/project.yml @@ -7,6 +7,8 @@ creator_id: 2 board_type: 1 type: 2 + created_unix: 1688973030 + updated_unix: 1688973030 - id: 2 @@ -17,6 +19,8 @@ creator_id: 3 board_type: 1 type: 2 + created_unix: 1688973010 + updated_unix: 1688973010 - id: 3 @@ -27,6 +31,8 @@ creator_id: 5 board_type: 1 type: 2 + created_unix: 1688973020 + updated_unix: 1688973020 - id: 4 @@ -37,3 +43,5 @@ creator_id: 2 board_type: 1 type: 2 + created_unix: 1688973000 + updated_unix: 1688973000 diff --git a/models/project/project.go b/models/project/project.go index 44609e60b2ea..6fa2adf604dc 100644 --- a/models/project/project.go +++ b/models/project/project.go @@ -196,7 +196,7 @@ type SearchOptions struct { RepoID int64 Page int IsClosed util.OptionalBool - SortType string + OrderBy db.SearchOrderBy Type Type } @@ -226,26 +226,28 @@ func CountProjects(ctx context.Context, opts SearchOptions) (int64, error) { return db.GetEngine(ctx).Where(opts.toConds()).Count(new(Project)) } +func GetSearchOrderByBySortType(sortType string) db.SearchOrderBy { + switch sortType { + case "oldest": + return db.SearchOrderByOldest + case "recentupdate": + return db.SearchOrderByRecentUpdated + case "leastupdate": + return db.SearchOrderByLeastUpdated + default: + return db.SearchOrderByNewest + } +} + // FindProjects returns a list of all projects that have been created in the repository func FindProjects(ctx context.Context, opts SearchOptions) ([]*Project, int64, error) { - e := db.GetEngine(ctx).Where(opts.toConds()) + e := db.GetEngine(ctx).Where(opts.toConds()).OrderBy(opts.OrderBy.String()) projects := make([]*Project, 0, setting.UI.IssuePagingNum) if opts.Page > 0 { e = e.Limit(setting.UI.IssuePagingNum, (opts.Page-1)*setting.UI.IssuePagingNum) } - switch opts.SortType { - case "oldest": - e.Desc("created_unix") - case "recentupdate": - e.Desc("updated_unix") - case "leastupdate": - e.Asc("updated_unix") - default: - e.Asc("created_unix") - } - count, err := e.FindAndCount(&projects) return projects, count, err } diff --git a/models/project/project_test.go b/models/project/project_test.go index 71ceda7aa5ad..d1a471565367 100644 --- a/models/project/project_test.go +++ b/models/project/project_test.go @@ -82,3 +82,42 @@ func TestProject(t *testing.T) { assert.True(t, projectFromDB.IsClosed) } + +func TestProjectsSort(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + tests := []struct { + sortType string + wants []int64 + }{ + { + sortType: "default", + wants: []int64{1, 3, 2, 4}, + }, + { + sortType: "oldest", + wants: []int64{4, 2, 3, 1}, + }, + { + sortType: "recentupdate", + wants: []int64{1, 3, 2, 4}, + }, + { + sortType: "leastupdate", + wants: []int64{4, 2, 3, 1}, + }, + } + + for _, tt := range tests { + projects, count, err := FindProjects(db.DefaultContext, SearchOptions{ + OrderBy: GetSearchOrderByBySortType(tt.sortType), + }) + assert.NoError(t, err) + assert.EqualValues(t, int64(4), count) + if assert.Len(t, projects, 4) { + for i := range projects { + assert.EqualValues(t, tt.wants[i], projects[i].ID) + } + } + } +} diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go index 21cb23000d2e..60032c777d05 100644 --- a/routers/web/org/projects.go +++ b/routers/web/org/projects.go @@ -62,7 +62,7 @@ func Projects(ctx *context.Context) { OwnerID: ctx.ContextUser.ID, Page: page, IsClosed: util.OptionalBoolOf(isShowClosed), - SortType: sortType, + OrderBy: project_model.GetSearchOrderByBySortType(sortType), Type: projectType, }) if err != nil { diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index 066cdbc5fd88..6bfb21c134cb 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -74,7 +74,7 @@ func Projects(ctx *context.Context) { RepoID: repo.ID, Page: page, IsClosed: util.OptionalBoolOf(isShowClosed), - SortType: sortType, + OrderBy: project_model.GetSearchOrderByBySortType(sortType), Type: project_model.TypeRepository, }) if err != nil { From cee352bb38517a361cd6513a05dfe18e6a453e90 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 12 Jul 2023 06:09:23 +0800 Subject: [PATCH 5/6] Show correct SSL Mode on "install page" (#25818) --- routers/install/install.go | 1 + templates/install.tmpl | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/routers/install/install.go b/routers/install/install.go index a2e89d3dac48..6a8f56127135 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -102,6 +102,7 @@ func Install(ctx *context.Context) { form.DbName = setting.Database.Name form.DbPath = setting.Database.Path form.DbSchema = setting.Database.Schema + form.SSLMode = setting.Database.SSLMode curDBType := setting.Database.Type.String() var isCurDBTypeSupported bool diff --git a/templates/install.tmpl b/templates/install.tmpl index b5caab148981..1716d0a5e41e 100644 --- a/templates/install.tmpl +++ b/templates/install.tmpl @@ -28,7 +28,7 @@ -
+
@@ -47,7 +47,7 @@
-
+
-
+
From d1e066f5d6e1ba91f45118de835c3777eee0811f Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Wed, 12 Jul 2023 00:32:23 +0000 Subject: [PATCH 6/6] [skip ci] Updated translations via Crowdin --- options/locale/locale_de-DE.ini | 238 +++++++++++++++++++++++++++++--- 1 file changed, 221 insertions(+), 17 deletions(-) diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 0cffa2493812..f478f10c2538 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -57,6 +57,7 @@ new_mirror=Neuer Mirror new_fork=Neuer Fork new_org=Neue Organisation new_project=Neues Projekt +new_project_column=Neue Spalte manage_org=Organisationen verwalten admin_panel=Administration account_settings=Kontoeinstellungen @@ -78,11 +79,14 @@ milestones=Meilensteine ok=OK cancel=Abbrechen +rerun=Neu starten +rerun_all=Alle Jobs neu starten save=Speichern add=Hinzufügen add_all=Alle hinzufügen remove=Löschen remove_all=Alle entfernen +remove_label_str=Element "%s " entfernen edit=Bearbeiten enabled=Aktiviert @@ -90,6 +94,7 @@ disabled=Deaktiviert copy=Kopieren copy_url=URL kopieren +copy_content=Inhalt kopieren copy_branch=Branchenname kopieren copy_success=Kopiert! copy_error=Kopieren fehlgeschlagen @@ -110,32 +115,45 @@ unknown=Unbekannt rss_feed=RSS Feed +pin=Anheften +unpin=Loslösen artifacts=Artefakte concept_system_global=Global +concept_user_individual=Individuum concept_code_repository=Repository concept_user_organization=Organisation +show_timestamps=Zeitstempel anzeigen show_log_seconds=Sekunden anzeigen +show_full_screen=Vollbild anzeigen [aria] +navbar=Navigationsleiste +footer=Fußzeile footer.software=Über die Software footer.links=Links [heatmap] +number_of_contributions_in_the_last_12_months=%s Beiträge in den letzten 12 Monaten +no_contributions=Keine Beiträge less=Weniger more=Mehr [editor] buttons.heading.tooltip=Titel hinzufügen +buttons.bold.tooltip=Fettschrift hinzufügen +buttons.italic.tooltip=Kursivschrift hinzufügen buttons.quote.tooltip=Text zitieren buttons.code.tooltip=Code hinzufügen buttons.link.tooltip=Link hinzufügen +buttons.list.unordered.tooltip=Liste hinzufügen buttons.list.ordered.tooltip=Nummerierte Liste hinzufügen buttons.list.task.tooltip=Aufgabenliste hinzufügen buttons.mention.tooltip=Benutzer oder Team erwähnen buttons.ref.tooltip=Issue oder Pull-Request referenzieren +buttons.switch_to_legacy.tooltip=Legacy-Editor verwenden buttons.enable_monospace_font=Monospace-Schrift aktivieren buttons.disable_monospace_font=Monospace-Schrift deaktivieren @@ -240,6 +258,7 @@ openid_signup_popup=OpenID-basierte Selbstregistrierung aktivieren. enable_captcha=Registrierungs-Captcha aktivieren enable_captcha_popup=Captcha-Eingabe bei der Registrierung erforderlich. require_sign_in_view=Ansehen erfordert Anmeldung +require_sign_in_view_popup=Seitenzugriff auf angemeldete Benutzer beschränken. Besucher sehen nur die Anmelde- und Registrierungsseite. admin_setting_desc=Das Erstellen eines Administrator-Kontos ist optional. Der erste registrierte Benutzer wird automatisch Administrator. admin_title=Administratoreinstellungen admin_name=Administrator-Benutzername @@ -269,6 +288,9 @@ no_reply_address=Versteckte E-Mail-Domain no_reply_address_helper=Domain-Name für Benutzer mit einer versteckten Emailadresse. Zum Beispiel wird der Benutzername „Joe“ in Git als „joe@noreply.example.org“ protokolliert, wenn die versteckte E-Mail-Domain „noreply.example.org“ festgelegt ist. password_algorithm=Passwort Hashing Algorithmus invalid_password_algorithm=Ungültiger Passwort-Hash-Algorithmus +password_algorithm_helper=Legen Sie einen Passwort-Hashing-Algorithmus fest. Algorithmen haben unterschiedliche Anforderungen und Stärken. Der argon2-Algorithmus ist ziemlich sicher, aber er verbraucht viel Speicher und kann für kleine Systeme ungeeignet sein. +enable_update_checker=Aktualisierungsprüfung aktivieren +enable_update_checker_helper=Stellt regelmäßig eine Verbindung zu gitea.io her, um nach neuen Versionen zu prüfen. [home] uname_holder=E-Mail-Adresse oder Benutzername @@ -302,15 +324,19 @@ repos=Repositorys users=Benutzer organizations=Organisationen search=Suche +go_to=Gehe zu code=Code search.type.tooltip=Suchmodus search.fuzzy=Ähnlich +search.fuzzy.tooltip=Zeige auch Ergebnisse, die dem Suchbegriff ähneln search.match=Genau +search.match.tooltip=Zeige nur Ergebnisse, die exakt mit dem Suchbegriff übereinstimmen code_search_unavailable=Derzeit ist die Code-Suche nicht verfügbar. Bitte wende dich an den Website-Administrator. repo_no_results=Keine passenden Repositorys gefunden. user_no_results=Keine passenden Benutzer gefunden. org_no_results=Keine passenden Organisationen gefunden. code_no_results=Es konnte kein passender Code für deinen Suchbegriff gefunden werden. +code_search_results=`Suchergebnisse für "%s"` code_last_indexed_at=Zuletzt indexiert %s relevant_repositories_tooltip=Repositorys, die Forks sind oder die kein Thema, kein Symbol und keine Beschreibung haben, werden ausgeblendet. relevant_repositories=Es werden nur relevante Repositorys angezeigt, zeigt ungefilterte Ergebnisse an. @@ -343,6 +369,7 @@ email_not_associate=Diese E-Mail-Adresse ist mit keinem Konto verknüpft. send_reset_mail=Wiederherstellungs-E-Mail senden reset_password=Kontowiederherstellung invalid_code=Dein Bestätigungs-Code ist ungültig oder abgelaufen. +invalid_password=Ihr Passwort stimmt nicht mit dem Passwort überein, das zur Erstellung des Kontos verwendet wurde. reset_password_helper=Konto wiederherstellen reset_password_wrong_user=Du bist angemeldet als %s, aber der Link zur Kontowiederherstellung ist für %s password_too_short=Das Passwort muss mindestens %d Zeichen lang sein. @@ -378,10 +405,15 @@ authorize_redirect_notice=Du wirst zu %s weitergeleitet, wenn du diese Anwendung authorize_application_created_by=Diese Anwendung wurde von %s erstellt. authorize_application_description=Wenn du diese Anwendung autorisierst, wird sie die Berechtigung erhalten, alle Informationen zu deinem Account zu bearbeiten oder zu lesen. Dies beinhaltet auch private Repositorys und Organisationen. authorize_title=`"%s" den Zugriff auf deinen Account gestatten?` +authorization_failed=Autorisierung fehlgeschlagen +authorization_failed_desc=Die Autorisierung ist fehlgeschlagen, da wir eine ungültige Anfrage festgestellt haben. Bitte kontaktiere den Betreuer der Anwendung, die du gerade autorisieren wolltest. +sspi_auth_failed=SSPI-Authentifizierung fehlgeschlagen +password_pwned=Das von dir gewählte Passwort ist auf einer Liste von gestohlenen Passwörtern die zuvor bei öffentlichen Datenschutzverletzungen aufgedeckt wurden. Bitte versuche es erneut mit einem anderen Passwort. password_pwned_err=Anfrage an HaveIBeenPwned konnte nicht abgeschlossen werden [mail] view_it_on=Auf %s ansehen +reply=oder antworte direkt auf diese E-Mail link_not_working_do_paste=Link funktioniert nicht? Versuche ihn zu kopieren und im Browser einzufügen. hi_user_x=Hallo %s, @@ -440,10 +472,15 @@ repo.transfer.body=Um es anzunehmen oder abzulehnen, öffne %s, oder ignoriere e repo.collaborator.added.subject=%s hat dich zu %s hinzugefügt repo.collaborator.added.text=Du wurdest als Mitarbeiter für folgendes Repository hinzugefügt: +team_invite.subject=%[1]s hat dich eingeladen, der Organisation %[2]s beizutreten +team_invite.text_1=%[1]s hat dich eingeladen, dem Team %[2]s in der Organisation %[3]s beizutreten. +team_invite.text_2=Bitte klicke auf den folgenden Link, um dem Team beizutreten: +team_invite.text_3=Hinweis: Diese Einladung war für %[1]s gedacht. Wenn du diese Einladung nicht erwartet hast, kannst du diese E-Mail ignorieren. [modal] yes=Ja no=Abbrechen +confirm=Bestätigen cancel=Abbrechen modify=Aktualisieren @@ -478,8 +515,12 @@ size_error=` muss die Größe %s haben.` min_size_error=` muss mindestens %s Zeichen enthalten.` max_size_error=` darf höchstens %s Zeichen enthalten.` email_error=` ist keine gültige E-Mail-Adresse.` +url_error=`"%s" ist keine gültige URL.` +include_error=` muss den Text "%s" enthalten.` glob_pattern_error=` Der Glob Pattern ist ungültig: %s.` regex_pattern_error=` regex ist ungültig: %s.` +username_error=` darf nur alphanumerische Zeichen ('0-9','a-z','A-Z'), Bindestriche ('-'), Unterstriche ('_') und Punkte ('.') enthalten. Es kann nicht mit nicht-alphanumerischen Zeichen beginnen oder enden und aufeinanderfolgende nicht-alphanumerische Zeichen sind ebenfalls verboten.` +invalid_group_team_map_error=` Zuordnung ist ungültig: %s` unknown_error=Unbekannter Fehler: captcha_incorrect=Der eingegebene CAPTCHA-Code ist falsch. password_not_match=Die Passwörter stimmen nicht überein. @@ -487,6 +528,7 @@ lang_select_error=Wähle eine Sprache aus der Liste aus. username_been_taken=Der Benutzername ist bereits vergeben. username_change_not_local_user=Nicht-lokale Benutzer dürfen ihren Nutzernamen nicht ändern. +username_has_not_been_changed=Benutzername wurde nicht geändert repo_name_been_taken=Der Repository-Name wird schon verwendet. repository_force_private=Privat erzwingen ist aktiviert: Private Repositorys können nicht veröffentlicht werden. repository_files_already_exist=Dateien für dieses Repository sind bereits vorhanden. Kontaktiere den Systemadministrator. @@ -500,6 +542,7 @@ team_name_been_taken=Der Teamname ist bereits vergeben. team_no_units_error=Das Team muss auf mindestens einen Bereich Zugriff haben. email_been_used=Die E-Mail-Adresse wird bereits verwendet. email_invalid=Die E-Mail-Adresse ist ungültig. +openid_been_used=Die OpenID-Adresse "%s" wird bereits verwendet. username_password_incorrect=Benutzername oder Passwort ist falsch. password_complexity=Das Passwort erfüllt nicht die Komplexitätsanforderungen: password_lowercase_one=Mindestens ein Kleinbuchstabe @@ -514,17 +557,27 @@ user_not_exist=Dieser Benutzer ist nicht vorhanden. team_not_exist=Dieses Team existiert nicht. last_org_owner=Du kannst den letzten Benutzer nicht aus dem 'Besitzer'-Team entfernen. Es muss mindestens einen Besitzer in einer Organisation geben. cannot_add_org_to_team=Eine Organisation kann nicht als Teammitglied hinzugefügt werden. +duplicate_invite_to_team=Der Benutzer wurde bereits als Teammitglied eingeladen. +organization_leave_success=Sie haben die Organisation %s erfolgreich verlassen. invalid_ssh_key=Dein SSH-Key kann nicht überprüft werden: %s invalid_gpg_key=Dein GPG-Key kann nicht überprüft werden: %s invalid_ssh_principal=Ungültige Identität: %s +must_use_public_key=Der von Ihnen bereitgestellte Key ist ein privater Key. Bitte laden Sie Ihren privaten Key nirgendwo hoch. Verwenden Sie stattdessen Ihren öffentlichen Key. +unable_verify_ssh_key=Der SSH-Schlüssel kann nicht verifiziert werden, überprüfe ihn auf Fehler. auth_failed=Authentifizierung fehlgeschlagen: %v +still_own_repo=Ihr Konto besitzt ein oder mehrere Repositories. Diese müssen erst gelöscht oder übertragen werden. +still_has_org=Dein Konto ist Mitglied einer oder mehrerer Organisationen, verlasse diese zuerst. +still_own_packages=Dein Konto besitzt ein oder mehrere Pakete, lösche diese zuerst. +org_still_own_repo=Diese Organisation besitzt noch ein oder mehrere Repositories. Diese müssen zuerst gelöscht oder übertragen werden. +org_still_own_packages=Diese Organisation besitzt noch ein oder mehrere Pakete, lösche diese zuerst. target_branch_not_exist=Der Ziel-Branch existiert nicht. [user] change_avatar=Profilbild ändern… +joined_on=Beigetreten am %s repositories=Repositorys activity=Öffentliche Aktivität followers=Follower @@ -539,7 +592,12 @@ unfollow=Nicht mehr folgen heatmap.loading=Heatmap wird geladen… user_bio=Biografie disabled_public_activity=Dieser Benutzer hat die öffentliche Sichtbarkeit der Aktivität deaktiviert. +email_visibility.limited=Ihre E-Mail-Adresse ist für alle authentifizierten Benutzer sichtbar +email_visibility.private=Ihre E-Mail-Adresse ist nur für Sie und Administratoren sichtbar +form.name_reserved=Der Benutzername "%s" ist reserviert. +form.name_pattern_not_allowed=Das Muster "%s" ist nicht in einem Benutzernamen erlaubt. +form.name_chars_not_allowed=Benutzername "%s" enthält ungültige Zeichen. [settings] profile=Profil @@ -581,6 +639,9 @@ cancel=Abbrechen language=Sprache ui=Theme hidden_comment_types=Ausgeblendeter Kommentartypen +hidden_comment_types_description=Die hier markierten Kommentartypen werden nicht innerhalb der Issue-Seiten angezeigt. Das Überprüfen von "Label" entfernt beispielsweise alle " hinzugefügt/entfernt