From 4f597b1866954ff94999a52af1a41fed06f0143e Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Sat, 18 Apr 2020 09:47:15 -0500 Subject: [PATCH] Add single release page and latest redirect (#11102) * Add single release and latest release routes Signed-off-by: jolheiser * Update API and move latest search to models Signed-off-by: jolheiser * Fix swagger Signed-off-by: jolheiser Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> --- models/release.go | 28 +++++++++++++++ modules/structs/release.go | 1 + routers/repo/release.go | 59 ++++++++++++++++++++++++++++++++ routers/routes/routes.go | 4 ++- templates/repo/release/list.tmpl | 2 +- templates/swagger/v1_json.tmpl | 4 +++ 6 files changed, 96 insertions(+), 2 deletions(-) diff --git a/models/release.go b/models/release.go index 0f670f374..0c76d17f4 100644 --- a/models/release.go +++ b/models/release.go @@ -80,6 +80,11 @@ func (r *Release) TarURL() string { return fmt.Sprintf("%s/archive/%s.tar.gz", r.Repo.HTMLURL(), r.TagName) } +// HTMLURL the url for a release on the web UI. release must have attributes loaded +func (r *Release) HTMLURL() string { + return fmt.Sprintf("%s/releases/tag/%s", r.Repo.HTMLURL(), r.TagName) +} + // APIFormat convert a Release to api.Release func (r *Release) APIFormat() *api.Release { assets := make([]*api.Attachment, 0) @@ -93,6 +98,7 @@ func (r *Release) APIFormat() *api.Release { Title: r.Title, Note: r.Note, URL: r.APIURL(), + HTMLURL: r.HTMLURL(), TarURL: r.TarURL(), ZipURL: r.ZipURL(), IsDraft: r.IsDraft, @@ -217,6 +223,28 @@ func GetReleasesByRepoID(repoID int64, opts FindReleasesOptions) ([]*Release, er return rels, sess.Find(&rels) } +// GetLatestReleaseByRepoID returns the latest release for a repository +func GetLatestReleaseByRepoID(repoID int64) (*Release, error) { + cond := builder.NewCond(). + And(builder.Eq{"repo_id": repoID}). + And(builder.Eq{"is_draft": false}). + And(builder.Eq{"is_prerelease": false}). + And(builder.Eq{"is_tag": false}) + + rel := new(Release) + has, err := x. + Desc("created_unix", "id"). + Where(cond). + Get(rel) + if err != nil { + return nil, err + } else if !has { + return nil, ErrReleaseNotExist{0, "latest"} + } + + return rel, nil +} + // GetReleasesByRepoIDAndNames returns a list of releases of repository according repoID and tagNames. func GetReleasesByRepoIDAndNames(ctx DBContext, repoID int64, tagNames []string) (rels []*Release, err error) { err = ctx.e. diff --git a/modules/structs/release.go b/modules/structs/release.go index b7575af39..38ce60bbe 100644 --- a/modules/structs/release.go +++ b/modules/structs/release.go @@ -16,6 +16,7 @@ type Release struct { Title string `json:"name"` Note string `json:"body"` URL string `json:"url"` + HTMLURL string `json:"html_url"` TarURL string `json:"tarball_url"` ZipURL string `json:"zipball_url"` IsDraft bool `json:"draft"` diff --git a/routers/repo/release.go b/routers/repo/release.go index 545419518..1eac3dce9 100644 --- a/routers/repo/release.go +++ b/routers/repo/release.go @@ -131,6 +131,65 @@ func Releases(ctx *context.Context) { ctx.HTML(200, tplReleases) } +// SingleRelease renders a single release's page +func SingleRelease(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("repo.release.releases") + ctx.Data["PageIsReleaseList"] = true + + writeAccess := ctx.Repo.CanWrite(models.UnitTypeReleases) + ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived + + release, err := models.GetRelease(ctx.Repo.Repository.ID, ctx.Params("tag")) + if err != nil { + ctx.ServerError("GetReleasesByRepoID", err) + return + } + + err = models.GetReleaseAttachments(release) + if err != nil { + ctx.ServerError("GetReleaseAttachments", err) + return + } + + release.Publisher, err = models.GetUserByID(release.PublisherID) + if err != nil { + if models.IsErrUserNotExist(err) { + release.Publisher = models.NewGhostUser() + } else { + ctx.ServerError("GetUserByID", err) + return + } + } + if err := calReleaseNumCommitsBehind(ctx.Repo, release, make(map[string]int64)); err != nil { + ctx.ServerError("calReleaseNumCommitsBehind", err) + return + } + release.Note = markdown.RenderString(release.Note, ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()) + + ctx.Data["Releases"] = []*models.Release{release} + ctx.HTML(200, tplReleases) +} + +// LatestRelease redirects to the latest release +func LatestRelease(ctx *context.Context) { + release, err := models.GetLatestReleaseByRepoID(ctx.Repo.Repository.ID) + if err != nil { + if models.IsErrReleaseNotExist(err) { + ctx.NotFound("LatestRelease", err) + return + } + ctx.ServerError("GetLatestReleaseByRepoID", err) + return + } + + if err := release.LoadAttributes(); err != nil { + ctx.ServerError("LoadAttributes", err) + return + } + + ctx.Redirect(release.HTMLURL()) +} + // NewRelease render creating release page func NewRelease(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.release.new_release") diff --git a/routers/routes/routes.go b/routers/routes/routes.go index e2514054b..a7828885b 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -805,7 +805,9 @@ func RegisterRoutes(m *macaron.Macaron) { // Releases m.Group("/:username/:reponame", func() { m.Group("/releases", func() { - m.Get("/", repo.MustBeNotEmpty, repo.Releases) + m.Get("/", repo.Releases) + m.Get("/tag/:tag", repo.SingleRelease) + m.Get("/latest", repo.LatestRelease) }, repo.MustBeNotEmpty, context.RepoRef()) m.Group("/releases", func() { m.Get("/new", repo.NewRelease) diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl index 22aea09f7..d0b160a1c 100644 --- a/templates/repo/release/list.tmpl +++ b/templates/repo/release/list.tmpl @@ -49,7 +49,7 @@ {{else}}

- {{.Title}} + {{.Title}} {{if $.CanCreateRelease}}({{$.i18n.Tr "repo.release.edit"}}){{end}}

diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index e9368a7d2..e87af4f5c 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -13107,6 +13107,10 @@ "type": "boolean", "x-go-name": "IsDraft" }, + "html_url": { + "type": "string", + "x-go-name": "HTMLURL" + }, "id": { "type": "integer", "format": "int64",