fix: correct repo/attatchment absolute url and release layout (#39178)

This commit is contained in:
wxiaoguang
2026-08-31 23:31:17 +00:00
committed by GitHub
parent fc9800b383
commit 4875bb3b2b
21 changed files with 72 additions and 80 deletions
+5 -3
View File
@@ -12,6 +12,7 @@ import (
"path"
"gitea.dev/models/db"
"gitea.dev/modules/httplib"
"gitea.dev/modules/log"
"gitea.dev/modules/setting"
"gitea.dev/modules/storage"
@@ -62,12 +63,13 @@ func (a *Attachment) RelativePath() string {
}
// DownloadURL returns the download url of the attached file
func (a *Attachment) DownloadURL() string {
func (a *Attachment) DownloadURL(optCtx ...context.Context) string {
// mail template doesn't have context, so we need to use a default one
ctx := util.OptionalArg(optCtx, context.TODO())
if a.CustomDownloadURL != "" {
return a.CustomDownloadURL
}
return setting.AppURL + "attachments/" + url.PathEscape(a.UUID)
return httplib.MakeAbsoluteURL(ctx, setting.AppSubURL+"/attachments/"+url.PathEscape(a.UUID))
}
// ErrAttachmentNotExist represents a "AttachmentNotExist" kind of error.
+1 -1
View File
@@ -74,7 +74,7 @@ func TestAttachment_DownloadURL(t *testing.T) {
UUID: "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
ID: 1,
}
assert.Equal(t, "https://try.gitea.io/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attach.DownloadURL())
assert.Equal(t, "https://try.gitea.io/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attach.DownloadURL(t.Context()))
}
func TestUpdateAttachment(t *testing.T) {
+1 -1
View File
@@ -433,7 +433,7 @@ func GetReleaseAttachments(ctx context.Context, rels ...*Release) (err error) {
// If the names unique, use the URL with the Name instead of the UUID
if !hasDuplicateName(release.Attachments) {
for _, attachment := range release.Attachments {
attachment.CustomDownloadURL = release.Repo.HTMLURL() + "/releases/download/" + url.PathEscape(release.TagName) + "/" + url.PathEscape(attachment.Name)
attachment.CustomDownloadURL = release.Repo.HTMLURL(ctx) + "/releases/download/" + url.PathEscape(release.TagName) + "/" + url.PathEscape(attachment.Name)
}
}
}
+1 -1
View File
@@ -124,7 +124,7 @@ func createPackageMetadataResponse(ctx *context.Context, registryURL string, pds
log.Error("GetDoerRepoPermission[%d]: %v", pd.Repository.ID, err)
} else if permission.HasAnyUnitAccessOrPublicAccess() {
pkg.Source = Source{
URL: pd.Repository.HTMLURL(),
URL: pd.Repository.HTMLURL(ctx),
Type: "git",
Reference: pd.Version.Version,
}
+3 -3
View File
@@ -67,7 +67,7 @@ func GetIssueAttachment(ctx *context.APIContext) {
return
}
ctx.JSON(http.StatusOK, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
ctx.JSON(http.StatusOK, convert.ToAPIAttachment(ctx, ctx.Repo.Repository, attach))
}
// ListIssueAttachments lists all attachments of the issue
@@ -210,7 +210,7 @@ func CreateIssueAttachment(ctx *context.APIContext) {
return
}
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attachment))
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx, ctx.Repo.Repository, attachment))
}
// EditIssueAttachment updates the given attachment
@@ -279,7 +279,7 @@ func EditIssueAttachment(ctx *context.APIContext) {
return
}
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attachment))
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx, ctx.Repo.Repository, attachment))
}
// DeleteIssueAttachment delete a given attachment
@@ -72,7 +72,7 @@ func GetIssueCommentAttachment(ctx *context.APIContext) {
return
}
ctx.JSON(http.StatusOK, convert.ToAPIAttachment(ctx.Repo.Repository, attachment))
ctx.JSON(http.StatusOK, convert.ToAPIAttachment(ctx, ctx.Repo.Repository, attachment))
}
// ListIssueCommentAttachments lists all attachments of the comment
@@ -114,7 +114,7 @@ func ListIssueCommentAttachments(ctx *context.APIContext) {
return
}
ctx.JSON(http.StatusOK, convert.ToAPIAttachments(ctx.Repo.Repository, comment.Attachments))
ctx.JSON(http.StatusOK, convert.ToAPIAttachments(ctx, ctx.Repo.Repository, comment.Attachments))
}
// CreateIssueCommentAttachment creates an attachment and saves the given file
@@ -225,7 +225,7 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
return
}
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attachment))
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx, ctx.Repo.Repository, attachment))
}
// EditIssueCommentAttachment updates the given attachment
@@ -291,7 +291,7 @@ func EditIssueCommentAttachment(ctx *context.APIContext) {
ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx, ctx.Repo.Repository, attach))
}
// DeleteIssueCommentAttachment delete a given attachment
+3 -3
View File
@@ -98,7 +98,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.ToAPIAttachment(ctx.Repo.Repository, attach))
ctx.JSON(http.StatusOK, convert.ToAPIAttachment(ctx, ctx.Repo.Repository, attach))
}
// ListReleaseAttachments lists all attachments of the release
@@ -263,7 +263,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
return
}
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx, ctx.Repo.Repository, attach))
}
// EditReleaseAttachment updates the given attachment
@@ -346,7 +346,7 @@ func EditReleaseAttachment(ctx *context.APIContext) {
ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx, ctx.Repo.Repository, attach))
}
// DeleteReleaseAttachment delete a given attachment
+1 -1
View File
@@ -581,7 +581,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(ctx.Repo.Repository, issue.Attachments[i])
attachments[i] = convert.ToAttachment(ctx, ctx.Repo.Repository, issue.Attachments[i])
}
ctx.JSON(http.StatusOK, attachments)
}
+1 -1
View File
@@ -444,7 +444,7 @@ func GetCommentAttachments(ctx *context.Context) {
return
}
for i := 0; i < len(comment.Attachments); i++ {
attachments = append(attachments, convert.ToAttachment(ctx.Repo.Repository, comment.Attachments[i]))
attachments = append(attachments, convert.ToAttachment(ctx, ctx.Repo.Repository, comment.Attachments[i]))
}
ctx.JSON(http.StatusOK, attachments)
}
+1 -1
View File
@@ -679,7 +679,7 @@ func TestWebhook(ctx *context.Context) {
apiCommit := &api.PayloadCommit{
ID: commit.ID.String(),
Message: commit.MessageUTF8(),
URL: ctx.Repo.Repository.HTMLURL() + "/commit/" + url.PathEscape(commit.ID.String()),
URL: ctx.Repo.Repository.HTMLURL(ctx) + "/commit/" + url.PathEscape(commit.ID.String()),
Author: &api.PayloadUser{
Name: commit.Author.Name,
Email: commit.Author.Email,
+16 -14
View File
@@ -4,30 +4,32 @@
package convert
import (
"context"
repo_model "gitea.dev/models/repo"
api "gitea.dev/modules/structs"
)
func WebAssetDownloadURL(repo *repo_model.Repository, attach *repo_model.Attachment) string {
return attach.DownloadURL()
func WebAssetDownloadURL(ctx context.Context, repo *repo_model.Repository, attach *repo_model.Attachment) string {
return attach.DownloadURL(ctx)
}
func APIAssetDownloadURL(repo *repo_model.Repository, attach *repo_model.Attachment) string {
return attach.DownloadURL()
func APIAssetDownloadURL(ctx context.Context, repo *repo_model.Repository, attach *repo_model.Attachment) string {
return attach.DownloadURL(ctx)
}
// 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)
func ToAttachment(ctx context.Context, repo *repo_model.Repository, a *repo_model.Attachment) *api.Attachment {
return toAttachment(ctx, 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)
func ToAPIAttachment(ctx context.Context, repo *repo_model.Repository, a *repo_model.Attachment) *api.Attachment {
return toAttachment(ctx, 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 {
func toAttachment(ctx context.Context, repo *repo_model.Repository, a *repo_model.Attachment, getDownloadURL func(ctx context.Context, repo *repo_model.Repository, attach *repo_model.Attachment) string) *api.Attachment {
return &api.Attachment{
ID: a.ID,
Name: a.Name,
@@ -35,18 +37,18 @@ func toAttachment(repo *repo_model.Repository, a *repo_model.Attachment, getDown
DownloadCount: a.DownloadCount,
Size: a.Size,
UUID: a.UUID,
DownloadURL: getDownloadURL(repo, a), // for web request json and api request json, return different download urls
DownloadURL: getDownloadURL(ctx, repo, a), // for web/api requests, return different download URLs
}
}
func ToAPIAttachments(repo *repo_model.Repository, attachments []*repo_model.Attachment) []*api.Attachment {
return toAttachments(repo, attachments, APIAssetDownloadURL)
func ToAPIAttachments(ctx context.Context, repo *repo_model.Repository, attachments []*repo_model.Attachment) []*api.Attachment {
return toAttachments(ctx, 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 {
func toAttachments(ctx context.Context, repo *repo_model.Repository, attachments []*repo_model.Attachment, getDownloadURL func(ctx context.Context, 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(repo, attachment, getDownloadURL))
converted = append(converted, toAttachment(ctx, repo, attachment, getDownloadURL))
}
return converted
}
+2 -2
View File
@@ -33,7 +33,7 @@ func ToAPIIssue(ctx context.Context, doer *user_model.User, issue *issues_model.
return toIssue(ctx, doer, issue, APIAssetDownloadURL)
}
func toIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, getDownloadURL func(repo *repo_model.Repository, attach *repo_model.Attachment) string) *api.Issue {
func toIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, getDownloadURL func(ctx context.Context, repo *repo_model.Repository, attach *repo_model.Attachment) string) *api.Issue {
if err := issue.LoadPoster(ctx); err != nil {
return &api.Issue{}
}
@@ -53,7 +53,7 @@ func toIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Iss
Poster: ToUser(ctx, issue.Poster, doer),
Title: issue.Title,
Body: issue.Content,
Attachments: toAttachments(issue.Repo, issue.Attachments, getDownloadURL),
Attachments: toAttachments(ctx, issue.Repo, issue.Attachments, getDownloadURL),
Ref: issue.Ref,
State: issue.State(),
IsLocked: issue.IsLocked,
+1 -1
View File
@@ -23,7 +23,7 @@ func ToAPIComment(ctx context.Context, repo *repo_model.Repository, c *issues_mo
IssueURL: c.IssueURL(ctx),
PRURL: c.PRURL(ctx),
Body: c.Content,
Attachments: ToAPIAttachments(repo, c.Attachments),
Attachments: ToAPIAttachments(ctx, repo, c.Attachments),
Created: c.CreatedUnix.AsTime(),
Updated: c.UpdatedUnix.AsTime(),
}
+1 -1
View File
@@ -29,6 +29,6 @@ func ToAPIRelease(ctx context.Context, repo *repo_model.Repository, r *repo_mode
CreatedAt: r.CreatedUnix.AsTime(),
PublishedAt: util.Iif(r.IsDraft, nil, r.PublishedUnix.AsTimePtr()),
Publisher: ToUser(ctx, r.Publisher, nil),
Attachments: ToAPIAttachments(repo, r.Attachments),
Attachments: ToAPIAttachments(ctx, repo, r.Attachments),
}
}
+8 -20
View File
@@ -202,29 +202,17 @@ func getFileContentsByEntryInternal(ctx context.Context, repo *repo_model.Reposi
}
// Handle links
if entry.IsRegular() || entry.IsLink() || entry.IsExecutable() {
downloadURL, err := url.Parse(repo.HTMLURL() + "/raw/" + refCommit.RefName.RefWebLinkPath() + "/" + util.PathEscapeSegments(opts.TreePath))
if err != nil {
return nil, err
}
downloadURLString := downloadURL.String()
contentsResponse.DownloadURL = &downloadURLString
downloadURL := repo.HTMLURL(ctx) + "/raw/" + refCommit.RefName.RefWebLinkPath() + "/" + util.PathEscapeSegments(opts.TreePath)
contentsResponse.DownloadURL = &downloadURL
}
if !entry.IsSubModule() {
htmlURL, err := url.Parse(repo.HTMLURL() + "/src/" + refCommit.RefName.RefWebLinkPath() + "/" + util.PathEscapeSegments(opts.TreePath))
if err != nil {
return nil, err
}
htmlURLString := htmlURL.String()
contentsResponse.HTMLURL = &htmlURLString
contentsResponse.Links.HTMLURL = &htmlURLString
htmlURL := repo.HTMLURL(ctx) + "/src/" + refCommit.RefName.RefWebLinkPath() + "/" + util.PathEscapeSegments(opts.TreePath)
contentsResponse.HTMLURL = &htmlURL
contentsResponse.Links.HTMLURL = &htmlURL
gitURL, err := url.Parse(repo.APIURL() + "/git/blobs/" + url.PathEscape(entry.ID.String()))
if err != nil {
return nil, err
}
gitURLString := gitURL.String()
contentsResponse.GitURL = &gitURLString
contentsResponse.Links.GitURL = &gitURLString
gitURL := repo.APIURL(ctx) + "/git/blobs/" + url.PathEscape(entry.ID.String())
contentsResponse.GitURL = &gitURL
contentsResponse.Links.GitURL = &gitURL
}
return contentsResponse, nil
+2 -2
View File
@@ -89,13 +89,13 @@ func GetFileCommitResponse(ctx context.Context, repo *repo_model.Repository, git
}
}
}
commitHTMLURL, _ := url.Parse(repo.HTMLURL() + "/commit/" + url.PathEscape(commit.ID.String()))
commitHTMLURL := repo.HTMLURL(ctx) + "/commit/" + url.PathEscape(commit.ID.String())
fileCommit := &api.FileCommitResponse{
CommitMeta: api.CommitMeta{
SHA: commit.ID.String(),
URL: commitURL.String(),
},
HTMLURL: commitHTMLURL.String(),
HTMLURL: commitHTMLURL,
Author: &api.CommitUser{
Identity: api.Identity{
Name: commit.Author.Name,
+2 -2
View File
@@ -3,8 +3,8 @@
{{$attachments := index $.Page.issuesAttachmentMap .ID}}
{{if $attachments}}
<div class="card-attachment-images">
{{range $attachments}}
<img loading="lazy" src="{{.DownloadURL}}" alt="{{.Name}}" />
{{range $att := $attachments}}
<img loading="lazy" src="{{$att.DownloadURL ctx}}" alt="{{$att.Name}}" />
{{end}}
</div>
{{end}}
@@ -3,23 +3,23 @@
<div class="divider"></div>
{{end}}
{{$hasThumbnails := false}}
{{- range .Attachments -}}
{{- range $att := .Attachments -}}
<div class="tw-flex">
<div class="tw-flex-1 tw-p-2">
<a target="_blank" href="{{.DownloadURL}}" title="{{ctx.Locale.Tr "repo.issues.attachment.open_tab" .Name}}">
{{if FilenameIsImage .Name}}
{{if not (StringUtils.Contains (StringUtils.ToString $.RenderedContent) .UUID)}}
<a target="_blank" href="{{$att.DownloadURL ctx}}" title="{{ctx.Locale.Tr "repo.issues.attachment.open_tab" $att.Name}}">
{{if FilenameIsImage $att.Name}}
{{if not (StringUtils.Contains (StringUtils.ToString $.RenderedContent) $att.UUID)}}
{{$hasThumbnails = true}}
{{end}}
{{svg "octicon-file"}}
{{else}}
{{svg "octicon-desktop-download"}}
{{end}}
<span><strong>{{.Name}}</strong></span>
<span><strong>{{$att.Name}}</strong></span>
</a>
</div>
<div class="flex-text-block tw-p-2">
<span class="ui tw-text-text-light">{{.Size | FormatByteSize}}</span>
<span class="ui tw-text-text-light">{{$att.Size | FormatByteSize}}</span>
</div>
</div>
{{end -}}
@@ -30,8 +30,8 @@
{{- range .Attachments -}}
{{if FilenameIsImage .Name}}
{{if not (StringUtils.Contains (StringUtils.ToString $.RenderedContent) .UUID)}}
<a target="_blank" href="{{.DownloadURL}}">
<img loading="lazy" alt="{{.Name}}" src="{{.DownloadURL}}" title="{{ctx.Locale.Tr "repo.issues.attachment.open_tab" .Name}}">
<a target="_blank" href="{{.DownloadURL ctx}}">
<img loading="lazy" alt="{{.Name}}" src="{{.DownloadURL ctx}}" title="{{ctx.Locale.Tr "repo.issues.attachment.open_tab" .Name}}">
</a>
{{end}}
{{end}}
+1 -1
View File
@@ -80,7 +80,7 @@
<ul class="ui divided list attachment-list">
{{range $att := $release.Attachments}}
<li class="item">
<a target="_blank" class="tw-flex-1 gt-ellipsis" rel="nofollow" download href="{{$att.DownloadURL}}">
<a target="_blank" class="tw-flex-1 gt-ellipsis" rel="nofollow" download href="{{$att.DownloadURL ctx}}">
<strong class="flex-text-inline">{{svg "octicon-package" 16 "download-icon"}}<span class="gt-ellipsis">{{$att.Name}}</span></strong>
</a>
<div class="attachment-right-info flex-text-inline">
+9 -9
View File
@@ -2,15 +2,14 @@
{{$canReadCode := $.Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
{{if $canReadReleases}}
<div class="flex-text-block">
<div class="tw-flex-1 tw-flex tw-items-center">
<h2 class="ui compact small menu small-menu-items">
<a class="{{if and .PageIsReleaseList (not .PageIsSingleTag)}}active {{end}}item" href="{{.RepoLink}}/releases">{{ctx.Locale.PrettyNumber .NumReleases}} {{ctx.Locale.TrN .NumReleases "repo.release" "repo.releases"}}</a>
{{if $canReadCode}}
<a class="{{if or .PageIsTagList .PageIsSingleTag}}active {{end}}item" href="{{.RepoLink}}/tags">{{ctx.Locale.PrettyNumber .NumTags}} {{ctx.Locale.TrN .NumTags "repo.tag" "repo.tags"}}</a>
{{end}}
</h2>
</div>
<div class="flex-left-right">
<h2 class="ui compact small menu small-menu-items">
<a class="{{if and .PageIsReleaseList (not .PageIsSingleTag)}}active {{end}}item" href="{{.RepoLink}}/releases">{{ctx.Locale.PrettyNumber .NumReleases}} {{ctx.Locale.TrN .NumReleases "repo.release" "repo.releases"}}</a>
{{if $canReadCode}}
<a class="{{if or .PageIsTagList .PageIsSingleTag}}active {{end}}item" href="{{.RepoLink}}/tags">{{ctx.Locale.PrettyNumber .NumTags}} {{ctx.Locale.TrN .NumTags "repo.tag" "repo.tags"}}</a>
{{end}}
</h2>
<div class="flex-text-block">
{{if .EnableFeed}}
<a class="ui small button" href="{{.RepoLink}}/{{if .PageIsTagList}}tags{{else}}releases{{end}}.rss">
{{svg "octicon-rss" 16}} {{ctx.Locale.Tr "rss_feed"}}
@@ -21,6 +20,7 @@
{{ctx.Locale.Tr "repo.release.new_release"}}
</a>
{{end}}
</div>
</div>
<div class="divider"></div>
{{else if $canReadCode}}
@@ -52,7 +52,7 @@ func TestAPIGetCommentAttachment(t *testing.T) {
apiAttachment := DecodeJSON(t, resp, &api.Attachment{})
expect := convert.ToAPIAttachment(repo, attachment)
expect := convert.ToAPIAttachment(t.Context(), repo, attachment)
assert.Equal(t, expect.ID, apiAttachment.ID)
assert.Equal(t, expect.Name, apiAttachment.Name)
assert.Equal(t, expect.UUID, apiAttachment.UUID)