Count downloads for tag archives
This commit is contained in:
parent
f8a5d6872c
commit
613e5387c5
22 changed files with 469 additions and 95 deletions
87
models/repo/archive_download_count.go
Normal file
87
models/repo/archive_download_count.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RepoArchiveDownloadCount counts all archive downloads for a tag
|
||||||
|
type RepoArchiveDownloadCount struct { //nolint:revive
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
RepoID int64 `xorm:"index unique(s)"`
|
||||||
|
ReleaseID int64 `xorm:"index unique(s)"`
|
||||||
|
Type git.ArchiveType `xorm:"unique(s)"`
|
||||||
|
Count int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
db.RegisterModel(new(RepoArchiveDownloadCount))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountArchiveDownload adds one download the the given archive
|
||||||
|
func CountArchiveDownload(ctx context.Context, repoID, releaseID int64, tp git.ArchiveType) error {
|
||||||
|
updateCount, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).And("release_id = ?", releaseID).And("`type` = ?", tp).Incr("count").Update(new(RepoArchiveDownloadCount))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateCount != 0 {
|
||||||
|
// The count was updated, so we can exit
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The archive does not esxists in the databse, so let's add it
|
||||||
|
newCounter := &RepoArchiveDownloadCount{
|
||||||
|
RepoID: repoID,
|
||||||
|
ReleaseID: releaseID,
|
||||||
|
Type: tp,
|
||||||
|
Count: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.GetEngine(ctx).Insert(newCounter)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetArchiveDownloadCount returns the download count of a tag
|
||||||
|
func GetArchiveDownloadCount(ctx context.Context, repoID, releaseID int64) (*api.TagArchiveDownloadCount, error) {
|
||||||
|
downloadCountList := make([]RepoArchiveDownloadCount, 0)
|
||||||
|
err := db.GetEngine(ctx).Where("repo_id = ?", repoID).And("release_id = ?", releaseID).Find(&downloadCountList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tagCounter := new(api.TagArchiveDownloadCount)
|
||||||
|
|
||||||
|
for _, singleCount := range downloadCountList {
|
||||||
|
switch singleCount.Type {
|
||||||
|
case git.ZIP:
|
||||||
|
tagCounter.Zip = singleCount.Count
|
||||||
|
case git.TARGZ:
|
||||||
|
tagCounter.TarGz = singleCount.Count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tagCounter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDownloadCountForTagName returns the download count of a tag with the given name
|
||||||
|
func GetArchiveDownloadCountForTagName(ctx context.Context, repoID int64, tagName string) (*api.TagArchiveDownloadCount, error) {
|
||||||
|
release, err := GetRelease(ctx, repoID, tagName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetArchiveDownloadCount(ctx, repoID, release.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteArchiveDownloadCountForRelease deletes the release from the repo_archive_download_count table
|
||||||
|
func DeleteArchiveDownloadCountForRelease(ctx context.Context, releaseID int64) error {
|
||||||
|
_, err := db.GetEngine(ctx).Delete(&RepoArchiveDownloadCount{ReleaseID: releaseID})
|
||||||
|
return err
|
||||||
|
}
|
65
models/repo/archive_download_count_test.go
Normal file
65
models/repo/archive_download_count_test.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepoArchiveDownloadCount(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
release, err := repo_model.GetReleaseByID(db.DefaultContext, 1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// We have no count, so it should return 0
|
||||||
|
downloadCount, err := repo_model.GetArchiveDownloadCount(db.DefaultContext, release.RepoID, release.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(0), downloadCount.Zip)
|
||||||
|
assert.Equal(t, int64(0), downloadCount.TarGz)
|
||||||
|
|
||||||
|
// Set the TarGz counter to 1
|
||||||
|
err = repo_model.CountArchiveDownload(db.DefaultContext, release.RepoID, release.ID, git.TARGZ)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
downloadCount, err = repo_model.GetArchiveDownloadCountForTagName(db.DefaultContext, release.RepoID, release.TagName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(0), downloadCount.Zip)
|
||||||
|
assert.Equal(t, int64(1), downloadCount.TarGz)
|
||||||
|
|
||||||
|
// Set the TarGz counter to 2
|
||||||
|
err = repo_model.CountArchiveDownload(db.DefaultContext, release.RepoID, release.ID, git.TARGZ)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
downloadCount, err = repo_model.GetArchiveDownloadCountForTagName(db.DefaultContext, release.RepoID, release.TagName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(0), downloadCount.Zip)
|
||||||
|
assert.Equal(t, int64(2), downloadCount.TarGz)
|
||||||
|
|
||||||
|
// Set the Zip counter to 1
|
||||||
|
err = repo_model.CountArchiveDownload(db.DefaultContext, release.RepoID, release.ID, git.ZIP)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
downloadCount, err = repo_model.GetArchiveDownloadCountForTagName(db.DefaultContext, release.RepoID, release.TagName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(1), downloadCount.Zip)
|
||||||
|
assert.Equal(t, int64(2), downloadCount.TarGz)
|
||||||
|
|
||||||
|
// Delete the count
|
||||||
|
err = repo_model.DeleteArchiveDownloadCountForRelease(db.DefaultContext, release.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
downloadCount, err = repo_model.GetArchiveDownloadCountForTagName(db.DefaultContext, release.RepoID, release.TagName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(0), downloadCount.Zip)
|
||||||
|
assert.Equal(t, int64(0), downloadCount.TarGz)
|
||||||
|
}
|
|
@ -35,6 +35,7 @@ type RepoArchiver struct { //revive:disable-line:exported
|
||||||
Status ArchiverStatus
|
Status ArchiverStatus
|
||||||
CommitID string `xorm:"VARCHAR(64) unique(s)"`
|
CommitID string `xorm:"VARCHAR(64) unique(s)"`
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"`
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"`
|
||||||
|
ReleaseID int64 `xorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -65,28 +65,29 @@ func (err ErrReleaseNotExist) Unwrap() error {
|
||||||
|
|
||||||
// Release represents a release of repository.
|
// Release represents a release of repository.
|
||||||
type Release struct {
|
type Release struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
RepoID int64 `xorm:"INDEX UNIQUE(n)"`
|
RepoID int64 `xorm:"INDEX UNIQUE(n)"`
|
||||||
Repo *Repository `xorm:"-"`
|
Repo *Repository `xorm:"-"`
|
||||||
PublisherID int64 `xorm:"INDEX"`
|
PublisherID int64 `xorm:"INDEX"`
|
||||||
Publisher *user_model.User `xorm:"-"`
|
Publisher *user_model.User `xorm:"-"`
|
||||||
TagName string `xorm:"INDEX UNIQUE(n)"`
|
TagName string `xorm:"INDEX UNIQUE(n)"`
|
||||||
OriginalAuthor string
|
OriginalAuthor string
|
||||||
OriginalAuthorID int64 `xorm:"index"`
|
OriginalAuthorID int64 `xorm:"index"`
|
||||||
LowerTagName string
|
LowerTagName string
|
||||||
Target string
|
Target string
|
||||||
TargetBehind string `xorm:"-"` // to handle non-existing or empty target
|
TargetBehind string `xorm:"-"` // to handle non-existing or empty target
|
||||||
Title string
|
Title string
|
||||||
Sha1 string `xorm:"VARCHAR(64)"`
|
Sha1 string `xorm:"VARCHAR(64)"`
|
||||||
NumCommits int64
|
NumCommits int64
|
||||||
NumCommitsBehind int64 `xorm:"-"`
|
NumCommitsBehind int64 `xorm:"-"`
|
||||||
Note string `xorm:"TEXT"`
|
Note string `xorm:"TEXT"`
|
||||||
RenderedNote template.HTML `xorm:"-"`
|
RenderedNote template.HTML `xorm:"-"`
|
||||||
IsDraft bool `xorm:"NOT NULL DEFAULT false"`
|
IsDraft bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
IsPrerelease bool `xorm:"NOT NULL DEFAULT false"`
|
IsPrerelease bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
IsTag bool `xorm:"NOT NULL DEFAULT false"` // will be true only if the record is a tag and has no related releases
|
IsTag bool `xorm:"NOT NULL DEFAULT false"` // will be true only if the record is a tag and has no related releases
|
||||||
Attachments []*Attachment `xorm:"-"`
|
Attachments []*Attachment `xorm:"-"`
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX"`
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX"`
|
||||||
|
ArchiveDownloadCount *structs.TagArchiveDownloadCount `xorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -112,9 +113,22 @@ func (r *Release) LoadAttributes(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = r.LoadArchiveDownloadCount(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return GetReleaseAttachments(ctx, r)
|
return GetReleaseAttachments(ctx, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadArchiveDownloadCount loads the download count for the source archives
|
||||||
|
func (r *Release) LoadArchiveDownloadCount(ctx context.Context) error {
|
||||||
|
var err error
|
||||||
|
r.ArchiveDownloadCount, err = GetArchiveDownloadCount(ctx, r.RepoID, r.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// APIURL the api url for a release. release must have attributes loaded
|
// APIURL the api url for a release. release must have attributes loaded
|
||||||
func (r *Release) APIURL() string {
|
func (r *Release) APIURL() string {
|
||||||
return r.Repo.APIURL() + "/releases/" + strconv.FormatInt(r.ID, 10)
|
return r.Repo.APIURL() + "/releases/" + strconv.FormatInt(r.ID, 10)
|
||||||
|
@ -447,6 +461,18 @@ func PushUpdateDeleteTagsContext(ctx context.Context, repo *Repository, tags []s
|
||||||
lowerTags = append(lowerTags, strings.ToLower(tag))
|
lowerTags = append(lowerTags, strings.ToLower(tag))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, tag := range tags {
|
||||||
|
release, err := GetRelease(ctx, repo.ID, tag)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("GetRelease: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = DeleteArchiveDownloadCountForRelease(ctx, release.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("DeleteTagArchiveDownloadCount: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := db.GetEngine(ctx).
|
if _, err := db.GetEngine(ctx).
|
||||||
Where("repo_id = ? AND is_tag = ?", repo.ID, true).
|
Where("repo_id = ? AND is_tag = ?", repo.ID, true).
|
||||||
In("lower_tag_name", lowerTags).
|
In("lower_tag_name", lowerTags).
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,13 +21,14 @@ const (
|
||||||
|
|
||||||
// Tag represents a Git tag.
|
// Tag represents a Git tag.
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
Name string
|
Name string
|
||||||
ID ObjectID
|
ID ObjectID
|
||||||
Object ObjectID // The id of this commit object
|
Object ObjectID // The id of this commit object
|
||||||
Type string
|
Type string
|
||||||
Tagger *Signature
|
Tagger *Signature
|
||||||
Message string
|
Message string
|
||||||
Signature *ObjectSignature
|
Signature *ObjectSignature
|
||||||
|
ArchiveDownloadCount *api.TagArchiveDownloadCount
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit return the commit of the tag reference
|
// Commit return the commit of the tag reference
|
||||||
|
|
|
@ -24,9 +24,10 @@ type Release struct {
|
||||||
// swagger:strfmt date-time
|
// swagger:strfmt date-time
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
// swagger:strfmt date-time
|
// swagger:strfmt date-time
|
||||||
PublishedAt time.Time `json:"published_at"`
|
PublishedAt time.Time `json:"published_at"`
|
||||||
Publisher *User `json:"author"`
|
Publisher *User `json:"author"`
|
||||||
Attachments []*Attachment `json:"assets"`
|
Attachments []*Attachment `json:"assets"`
|
||||||
|
ArchiveDownloadCount *TagArchiveDownloadCount `json:"archive_download_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateReleaseOption options when creating a release
|
// CreateReleaseOption options when creating a release
|
||||||
|
|
|
@ -5,23 +5,25 @@ package structs
|
||||||
|
|
||||||
// Tag represents a repository tag
|
// Tag represents a repository tag
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Commit *CommitMeta `json:"commit"`
|
Commit *CommitMeta `json:"commit"`
|
||||||
ZipballURL string `json:"zipball_url"`
|
ZipballURL string `json:"zipball_url"`
|
||||||
TarballURL string `json:"tarball_url"`
|
TarballURL string `json:"tarball_url"`
|
||||||
|
ArchiveDownloadCount *TagArchiveDownloadCount `json:"archive_download_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnnotatedTag represents an annotated tag
|
// AnnotatedTag represents an annotated tag
|
||||||
type AnnotatedTag struct {
|
type AnnotatedTag struct {
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
SHA string `json:"sha"`
|
SHA string `json:"sha"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Tagger *CommitUser `json:"tagger"`
|
Tagger *CommitUser `json:"tagger"`
|
||||||
Object *AnnotatedTagObject `json:"object"`
|
Object *AnnotatedTagObject `json:"object"`
|
||||||
Verification *PayloadCommitVerification `json:"verification"`
|
Verification *PayloadCommitVerification `json:"verification"`
|
||||||
|
ArchiveDownloadCount *TagArchiveDownloadCount `json:"archive_download_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnnotatedTagObject contains meta information of the tag object
|
// AnnotatedTagObject contains meta information of the tag object
|
||||||
|
@ -38,3 +40,9 @@ type CreateTagOption struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Target string `json:"target"`
|
Target string `json:"target"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TagArchiveDownloadCount counts how many times a archive was downloaded
|
||||||
|
type TagArchiveDownloadCount struct {
|
||||||
|
Zip int64 `json:"zip"`
|
||||||
|
TarGz int64 `json:"tar_gz"`
|
||||||
|
}
|
||||||
|
|
|
@ -302,7 +302,7 @@ func GetArchive(ctx *context.APIContext) {
|
||||||
|
|
||||||
func archiveDownload(ctx *context.APIContext) {
|
func archiveDownload(ctx *context.APIContext) {
|
||||||
uri := ctx.Params("*")
|
uri := ctx.Params("*")
|
||||||
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri)
|
aReq, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
|
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
|
||||||
ctx.Error(http.StatusBadRequest, "unknown archive format", err)
|
ctx.Error(http.StatusBadRequest, "unknown archive format", err)
|
||||||
|
|
|
@ -60,6 +60,12 @@ func ListTags(ctx *context.APIContext) {
|
||||||
|
|
||||||
apiTags := make([]*api.Tag, len(tags))
|
apiTags := make([]*api.Tag, len(tags))
|
||||||
for i := range tags {
|
for i := range tags {
|
||||||
|
tags[i].ArchiveDownloadCount, err = repo_model.GetArchiveDownloadCountForTagName(ctx, ctx.Repo.Repository.ID, tags[i].Name)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetTagArchiveDownloadCountForName", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
apiTags[i] = convert.ToTag(ctx.Repo.Repository, tags[i])
|
apiTags[i] = convert.ToTag(ctx.Repo.Repository, tags[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,6 +117,13 @@ func GetAnnotatedTag(ctx *context.APIContext) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusBadRequest, "GetAnnotatedTag", err)
|
ctx.Error(http.StatusBadRequest, "GetAnnotatedTag", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tag.ArchiveDownloadCount, err = repo_model.GetArchiveDownloadCountForTagName(ctx, ctx.Repo.Repository.ID, tag.Name)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetTagArchiveDownloadCountForName", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, convert.ToAnnotatedTag(ctx, ctx.Repo.Repository, tag, commit))
|
ctx.JSON(http.StatusOK, convert.ToAnnotatedTag(ctx, ctx.Repo.Repository, tag, commit))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,6 +163,13 @@ func GetTag(ctx *context.APIContext) {
|
||||||
ctx.NotFound(tagName)
|
ctx.NotFound(tagName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tag.ArchiveDownloadCount, err = repo_model.GetArchiveDownloadCountForTagName(ctx, ctx.Repo.Repository.ID, tag.Name)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetTagArchiveDownloadCountForName", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, convert.ToTag(ctx.Repo.Repository, tag))
|
ctx.JSON(http.StatusOK, convert.ToTag(ctx.Repo.Repository, tag))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,6 +238,13 @@ func CreateTag(ctx *context.APIContext) {
|
||||||
ctx.InternalServerError(err)
|
ctx.InternalServerError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tag.ArchiveDownloadCount, err = repo_model.GetArchiveDownloadCountForTagName(ctx, ctx.Repo.Repository.ID, tag.Name)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetTagArchiveDownloadCountForName", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusCreated, convert.ToTag(ctx.Repo.Repository, tag))
|
ctx.JSON(http.StatusCreated, convert.ToTag(ctx.Repo.Repository, tag))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -127,6 +127,11 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = r.LoadArchiveDownloadCount(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if !r.IsDraft {
|
if !r.IsDraft {
|
||||||
if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil {
|
if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -355,6 +360,12 @@ func SingleRelease(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = release.Title
|
ctx.Data["Title"] = release.Title
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = release.LoadArchiveDownloadCount(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("LoadArchiveDownloadCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ctx.Data["Releases"] = releases
|
ctx.Data["Releases"] = releases
|
||||||
ctx.HTML(http.StatusOK, tplReleasesList)
|
ctx.HTML(http.StatusOK, tplReleasesList)
|
||||||
}
|
}
|
||||||
|
|
|
@ -456,7 +456,7 @@ func RedirectDownload(ctx *context.Context) {
|
||||||
// Download an archive of a repository
|
// Download an archive of a repository
|
||||||
func Download(ctx *context.Context) {
|
func Download(ctx *context.Context) {
|
||||||
uri := ctx.Params("*")
|
uri := ctx.Params("*")
|
||||||
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri)
|
aReq, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
|
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
|
||||||
ctx.Error(http.StatusBadRequest, err.Error())
|
ctx.Error(http.StatusBadRequest, err.Error())
|
||||||
|
@ -485,6 +485,14 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep
|
||||||
// If we have a signed url (S3, object storage), redirect to this directly.
|
// If we have a signed url (S3, object storage), redirect to this directly.
|
||||||
u, err := storage.RepoArchives.URL(rPath, downloadName)
|
u, err := storage.RepoArchives.URL(rPath, downloadName)
|
||||||
if u != nil && err == nil {
|
if u != nil && err == nil {
|
||||||
|
if archiver.ReleaseID != 0 {
|
||||||
|
err = repo_model.CountArchiveDownload(ctx, ctx.Repo.Repository.ID, archiver.ReleaseID, archiver.Type)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("CountArchiveDownload", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.Redirect(u.String())
|
ctx.Redirect(u.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -498,6 +506,14 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep
|
||||||
}
|
}
|
||||||
defer fr.Close()
|
defer fr.Close()
|
||||||
|
|
||||||
|
if archiver.ReleaseID != 0 {
|
||||||
|
err = repo_model.CountArchiveDownload(ctx, ctx.Repo.Repository.ID, archiver.ReleaseID, archiver.Type)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("CountArchiveDownload", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.ServeContent(fr, &context.ServeHeaderOptions{
|
ctx.ServeContent(fr, &context.ServeHeaderOptions{
|
||||||
Filename: downloadName,
|
Filename: downloadName,
|
||||||
LastModified: archiver.CreatedUnix.AsLocalTime(),
|
LastModified: archiver.CreatedUnix.AsLocalTime(),
|
||||||
|
@ -509,7 +525,7 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep
|
||||||
// kind of drop it on the floor if this is the case.
|
// kind of drop it on the floor if this is the case.
|
||||||
func InitiateDownload(ctx *context.Context) {
|
func InitiateDownload(ctx *context.Context) {
|
||||||
uri := ctx.Params("*")
|
uri := ctx.Params("*")
|
||||||
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri)
|
aReq, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("archiver_service.NewRequest", err)
|
ctx.ServerError("archiver_service.NewRequest", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -171,12 +171,13 @@ func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch) *api
|
||||||
// ToTag convert a git.Tag to an api.Tag
|
// ToTag convert a git.Tag to an api.Tag
|
||||||
func ToTag(repo *repo_model.Repository, t *git.Tag) *api.Tag {
|
func ToTag(repo *repo_model.Repository, t *git.Tag) *api.Tag {
|
||||||
return &api.Tag{
|
return &api.Tag{
|
||||||
Name: t.Name,
|
Name: t.Name,
|
||||||
Message: strings.TrimSpace(t.Message),
|
Message: strings.TrimSpace(t.Message),
|
||||||
ID: t.ID.String(),
|
ID: t.ID.String(),
|
||||||
Commit: ToCommitMeta(repo, t),
|
Commit: ToCommitMeta(repo, t),
|
||||||
ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"),
|
ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"),
|
||||||
TarballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".tar.gz"),
|
TarballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".tar.gz"),
|
||||||
|
ArchiveDownloadCount: t.ArchiveDownloadCount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,13 +350,14 @@ func ToTeams(ctx context.Context, teams []*organization.Team, loadOrgs bool) ([]
|
||||||
// ToAnnotatedTag convert git.Tag to api.AnnotatedTag
|
// ToAnnotatedTag convert git.Tag to api.AnnotatedTag
|
||||||
func ToAnnotatedTag(ctx context.Context, repo *repo_model.Repository, t *git.Tag, c *git.Commit) *api.AnnotatedTag {
|
func ToAnnotatedTag(ctx context.Context, repo *repo_model.Repository, t *git.Tag, c *git.Commit) *api.AnnotatedTag {
|
||||||
return &api.AnnotatedTag{
|
return &api.AnnotatedTag{
|
||||||
Tag: t.Name,
|
Tag: t.Name,
|
||||||
SHA: t.ID.String(),
|
SHA: t.ID.String(),
|
||||||
Object: ToAnnotatedTagObject(repo, c),
|
Object: ToAnnotatedTagObject(repo, c),
|
||||||
Message: t.Message,
|
Message: t.Message,
|
||||||
URL: util.URLJoin(repo.APIURL(), "git/tags", t.ID.String()),
|
URL: util.URLJoin(repo.APIURL(), "git/tags", t.ID.String()),
|
||||||
Tagger: ToCommitUser(t.Tagger),
|
Tagger: ToCommitUser(t.Tagger),
|
||||||
Verification: ToVerification(ctx, c),
|
Verification: ToVerification(ctx, c),
|
||||||
|
ArchiveDownloadCount: t.ArchiveDownloadCount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,21 +13,22 @@ import (
|
||||||
// ToAPIRelease convert a repo_model.Release to 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 {
|
func ToAPIRelease(ctx context.Context, repo *repo_model.Repository, r *repo_model.Release) *api.Release {
|
||||||
return &api.Release{
|
return &api.Release{
|
||||||
ID: r.ID,
|
ID: r.ID,
|
||||||
TagName: r.TagName,
|
TagName: r.TagName,
|
||||||
Target: r.Target,
|
Target: r.Target,
|
||||||
Title: r.Title,
|
Title: r.Title,
|
||||||
Note: r.Note,
|
Note: r.Note,
|
||||||
URL: r.APIURL(),
|
URL: r.APIURL(),
|
||||||
HTMLURL: r.HTMLURL(),
|
HTMLURL: r.HTMLURL(),
|
||||||
TarURL: r.TarURL(),
|
TarURL: r.TarURL(),
|
||||||
ZipURL: r.ZipURL(),
|
ZipURL: r.ZipURL(),
|
||||||
UploadURL: r.APIUploadURL(),
|
UploadURL: r.APIUploadURL(),
|
||||||
IsDraft: r.IsDraft,
|
IsDraft: r.IsDraft,
|
||||||
IsPrerelease: r.IsPrerelease,
|
IsPrerelease: r.IsPrerelease,
|
||||||
CreatedAt: r.CreatedUnix.AsTime(),
|
CreatedAt: r.CreatedUnix.AsTime(),
|
||||||
PublishedAt: r.CreatedUnix.AsTime(),
|
PublishedAt: r.CreatedUnix.AsTime(),
|
||||||
Publisher: ToUser(ctx, r.Publisher, nil),
|
Publisher: ToUser(ctx, r.Publisher, nil),
|
||||||
Attachments: ToAPIAttachments(repo, r.Attachments),
|
Attachments: ToAPIAttachments(repo, r.Attachments),
|
||||||
|
ArchiveDownloadCount: r.ArchiveDownloadCount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -227,6 +227,9 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
|
||||||
// find redirects without existing user.
|
// find redirects without existing user.
|
||||||
genericOrphanCheck("Orphaned Redirects without existing redirect user",
|
genericOrphanCheck("Orphaned Redirects without existing redirect user",
|
||||||
"user_redirect", "user", "user_redirect.redirect_user_id=`user`.id"),
|
"user_redirect", "user", "user_redirect.redirect_user_id=`user`.id"),
|
||||||
|
// find archive download count without existing release
|
||||||
|
genericOrphanCheck("Archive download count without existing Release",
|
||||||
|
"repo_archive_download_count", "release", "repo_archive_download_count.release_id=release.id"),
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, c := range consistencyChecks {
|
for _, c := range consistencyChecks {
|
||||||
|
|
|
@ -318,6 +318,11 @@ func DeleteReleaseByID(ctx context.Context, repo *repo_model.Repository, rel *re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = repo_model.DeleteArchiveDownloadCountForRelease(ctx, rel.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if stdout, _, err := git.NewCommand(ctx, "tag", "-d").AddDashesAndList(rel.TagName).
|
if stdout, _, err := git.NewCommand(ctx, "tag", "-d").AddDashesAndList(rel.TagName).
|
||||||
SetDescription(fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID)).
|
SetDescription(fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID)).
|
||||||
RunStdString(&git.RunOpts{Dir: repo.RepoPath()}); err != nil && !strings.Contains(err.Error(), "not found") {
|
RunStdString(&git.RunOpts{Dir: repo.RepoPath()}); err != nil && !strings.Contains(err.Error(), "not found") {
|
||||||
|
|
|
@ -30,10 +30,11 @@ import (
|
||||||
// This is entirely opaque to external entities, though, and mostly used as a
|
// This is entirely opaque to external entities, though, and mostly used as a
|
||||||
// handle elsewhere.
|
// handle elsewhere.
|
||||||
type ArchiveRequest struct {
|
type ArchiveRequest struct {
|
||||||
RepoID int64
|
RepoID int64
|
||||||
refName string
|
refName string
|
||||||
Type git.ArchiveType
|
Type git.ArchiveType
|
||||||
CommitID string
|
CommitID string
|
||||||
|
ReleaseID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrUnknownArchiveFormat request archive format is not supported
|
// ErrUnknownArchiveFormat request archive format is not supported
|
||||||
|
@ -70,7 +71,7 @@ func (e RepoRefNotFoundError) Is(err error) bool {
|
||||||
// NewRequest creates an archival request, based on the URI. The
|
// NewRequest creates an archival request, based on the URI. The
|
||||||
// resulting ArchiveRequest is suitable for being passed to ArchiveRepository()
|
// resulting ArchiveRequest is suitable for being passed to ArchiveRepository()
|
||||||
// if it's determined that the request still needs to be satisfied.
|
// if it's determined that the request still needs to be satisfied.
|
||||||
func NewRequest(repoID int64, repo *git.Repository, uri string) (*ArchiveRequest, error) {
|
func NewRequest(ctx context.Context, repoID int64, repo *git.Repository, uri string) (*ArchiveRequest, error) {
|
||||||
r := &ArchiveRequest{
|
r := &ArchiveRequest{
|
||||||
RepoID: repoID,
|
RepoID: repoID,
|
||||||
}
|
}
|
||||||
|
@ -99,6 +100,17 @@ func NewRequest(repoID int64, repo *git.Repository, uri string) (*ArchiveRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
r.CommitID = commitID.String()
|
r.CommitID = commitID.String()
|
||||||
|
|
||||||
|
release, err := repo_model.GetRelease(ctx, repoID, r.refName)
|
||||||
|
if err != nil {
|
||||||
|
if !repo_model.IsErrReleaseNotExist(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if release != nil {
|
||||||
|
r.ReleaseID = release.ID
|
||||||
|
}
|
||||||
|
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +132,10 @@ func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver
|
||||||
return nil, fmt.Errorf("models.GetRepoArchiver: %w", err)
|
return nil, fmt.Errorf("models.GetRepoArchiver: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if archiver != nil {
|
||||||
|
archiver.ReleaseID = aReq.ReleaseID
|
||||||
|
}
|
||||||
|
|
||||||
if archiver != nil && archiver.Status == repo_model.ArchiverReady {
|
if archiver != nil && archiver.Status == repo_model.ArchiverReady {
|
||||||
// Archive already generated, we're done.
|
// Archive already generated, we're done.
|
||||||
return archiver, nil
|
return archiver, nil
|
||||||
|
@ -145,6 +161,7 @@ func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver
|
||||||
return nil, fmt.Errorf("repo_model.GetRepoArchiver: %w", err)
|
return nil, fmt.Errorf("repo_model.GetRepoArchiver: %w", err)
|
||||||
}
|
}
|
||||||
if archiver != nil && archiver.Status == repo_model.ArchiverReady {
|
if archiver != nil && archiver.Status == repo_model.ArchiverReady {
|
||||||
|
archiver.ReleaseID = aReq.ReleaseID
|
||||||
return archiver, nil
|
return archiver, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,47 +31,47 @@ func TestArchive_Basic(t *testing.T) {
|
||||||
contexttest.LoadGitRepo(t, ctx)
|
contexttest.LoadGitRepo(t, ctx)
|
||||||
defer ctx.Repo.GitRepo.Close()
|
defer ctx.Repo.GitRepo.Close()
|
||||||
|
|
||||||
bogusReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
bogusReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, bogusReq)
|
assert.NotNil(t, bogusReq)
|
||||||
assert.EqualValues(t, firstCommit+".zip", bogusReq.GetArchiveName())
|
assert.EqualValues(t, firstCommit+".zip", bogusReq.GetArchiveName())
|
||||||
|
|
||||||
// Check a series of bogus requests.
|
// Check a series of bogus requests.
|
||||||
// Step 1, valid commit with a bad extension.
|
// Step 1, valid commit with a bad extension.
|
||||||
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".dilbert")
|
bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".dilbert")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, bogusReq)
|
assert.Nil(t, bogusReq)
|
||||||
|
|
||||||
// Step 2, missing commit.
|
// Step 2, missing commit.
|
||||||
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "dbffff.zip")
|
bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "dbffff.zip")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, bogusReq)
|
assert.Nil(t, bogusReq)
|
||||||
|
|
||||||
// Step 3, doesn't look like branch/tag/commit.
|
// Step 3, doesn't look like branch/tag/commit.
|
||||||
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "db.zip")
|
bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "db.zip")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, bogusReq)
|
assert.Nil(t, bogusReq)
|
||||||
|
|
||||||
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "master.zip")
|
bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "master.zip")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, bogusReq)
|
assert.NotNil(t, bogusReq)
|
||||||
assert.EqualValues(t, "master.zip", bogusReq.GetArchiveName())
|
assert.EqualValues(t, "master.zip", bogusReq.GetArchiveName())
|
||||||
|
|
||||||
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "test/archive.zip")
|
bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "test/archive.zip")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, bogusReq)
|
assert.NotNil(t, bogusReq)
|
||||||
assert.EqualValues(t, "test-archive.zip", bogusReq.GetArchiveName())
|
assert.EqualValues(t, "test-archive.zip", bogusReq.GetArchiveName())
|
||||||
|
|
||||||
// Now two valid requests, firstCommit with valid extensions.
|
// Now two valid requests, firstCommit with valid extensions.
|
||||||
zipReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
zipReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, zipReq)
|
assert.NotNil(t, zipReq)
|
||||||
|
|
||||||
tgzReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".tar.gz")
|
tgzReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".tar.gz")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, tgzReq)
|
assert.NotNil(t, tgzReq)
|
||||||
|
|
||||||
secondReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".zip")
|
secondReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".zip")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, secondReq)
|
assert.NotNil(t, secondReq)
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ func TestArchive_Basic(t *testing.T) {
|
||||||
// Sleep two seconds to make sure the queue doesn't change.
|
// Sleep two seconds to make sure the queue doesn't change.
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
zipReq2, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
zipReq2, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
// This zipReq should match what's sitting in the queue, as we haven't
|
// This zipReq should match what's sitting in the queue, as we haven't
|
||||||
// let it release yet. From the consumer's point of view, this looks like
|
// let it release yet. From the consumer's point of view, this looks like
|
||||||
|
@ -106,12 +106,12 @@ func TestArchive_Basic(t *testing.T) {
|
||||||
// Now we'll submit a request and TimedWaitForCompletion twice, before and
|
// Now we'll submit a request and TimedWaitForCompletion twice, before and
|
||||||
// after we release it. We should trigger both the timeout and non-timeout
|
// after we release it. We should trigger both the timeout and non-timeout
|
||||||
// cases.
|
// cases.
|
||||||
timedReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".tar.gz")
|
timedReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".tar.gz")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, timedReq)
|
assert.NotNil(t, timedReq)
|
||||||
ArchiveRepository(db.DefaultContext, timedReq)
|
ArchiveRepository(db.DefaultContext, timedReq)
|
||||||
|
|
||||||
zipReq2, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
zipReq2, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
// Now, we're guaranteed to have released the original zipReq from the queue.
|
// Now, we're guaranteed to have released the original zipReq from the queue.
|
||||||
// Ensure that we don't get handed back the released entry somehow, but they
|
// Ensure that we don't get handed back the released entry somehow, but they
|
||||||
|
|
|
@ -162,6 +162,7 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID
|
||||||
&actions_model.ActionScheduleSpec{RepoID: repoID},
|
&actions_model.ActionScheduleSpec{RepoID: repoID},
|
||||||
&actions_model.ActionSchedule{RepoID: repoID},
|
&actions_model.ActionSchedule{RepoID: repoID},
|
||||||
&actions_model.ActionArtifact{RepoID: repoID},
|
&actions_model.ActionArtifact{RepoID: repoID},
|
||||||
|
&repo_model.RepoArchiveDownloadCount{RepoID: repoID},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("deleteBeans: %w", err)
|
return fmt.Errorf("deleteBeans: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,13 +71,16 @@
|
||||||
{{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
|
{{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
|
||||||
<li>
|
<li>
|
||||||
<a class="archive-link" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong></a>
|
<a class="archive-link" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong></a>
|
||||||
|
<span class="ui middle aligned right" data-tooltip-content="{{ctx.Locale.Tr "repo.release.download_count" (ctx.Locale.PrettyNumber .Release.ArchiveDownloadCount.Zip)}}">
|
||||||
|
{{svg "octicon-info"}}
|
||||||
|
</span>
|
||||||
<span data-tooltip-content="{{ctx.Locale.Tr "repo.release.system_generated"}}">
|
<span data-tooltip-content="{{ctx.Locale.Tr "repo.release.system_generated"}}">
|
||||||
{{svg "octicon-info"}}
|
{{svg "octicon-info"}}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="{{if $hasReleaseAttachment}}start-gap{{end}}">
|
<li class="{{if $hasReleaseAttachment}}start-gap{{end}}">
|
||||||
<a class="archive-link" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong></a>
|
<a class="archive-link" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong></a>
|
||||||
<span data-tooltip-content="{{ctx.Locale.Tr "repo.release.system_generated"}}">
|
<span class="ui middle aligned right" data-tooltip-content="{{ctx.Locale.Tr "repo.release.download_count" (ctx.Locale.PrettyNumber .Release.ArchiveDownloadCount.TarGz)}}">
|
||||||
{{svg "octicon-info"}}
|
{{svg "octicon-info"}}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
26
templates/swagger/v1_json.tmpl
generated
26
templates/swagger/v1_json.tmpl
generated
|
@ -17606,6 +17606,9 @@
|
||||||
"description": "AnnotatedTag represents an annotated tag",
|
"description": "AnnotatedTag represents an annotated tag",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"archive_download_count": {
|
||||||
|
"$ref": "#/definitions/TagArchiveDownloadCount"
|
||||||
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "Message"
|
"x-go-name": "Message"
|
||||||
|
@ -22755,6 +22758,9 @@
|
||||||
"description": "Release represents a repository release",
|
"description": "Release represents a repository release",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"archive_download_count": {
|
||||||
|
"$ref": "#/definitions/TagArchiveDownloadCount"
|
||||||
|
},
|
||||||
"assets": {
|
"assets": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
|
@ -23330,6 +23336,9 @@
|
||||||
"description": "Tag represents a repository tag",
|
"description": "Tag represents a repository tag",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"archive_download_count": {
|
||||||
|
"$ref": "#/definitions/TagArchiveDownloadCount"
|
||||||
|
},
|
||||||
"commit": {
|
"commit": {
|
||||||
"$ref": "#/definitions/CommitMeta"
|
"$ref": "#/definitions/CommitMeta"
|
||||||
},
|
},
|
||||||
|
@ -23356,6 +23365,23 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"TagArchiveDownloadCount": {
|
||||||
|
"description": "TagArchiveDownloadCount counts how many times a archive was downloaded",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"tar_gz": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "TarGz"
|
||||||
|
},
|
||||||
|
"zip": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "Zip"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"Team": {
|
"Team": {
|
||||||
"description": "Team represents a team in an organization",
|
"description": "Team represents a team in an organization",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
|
@ -319,3 +319,39 @@ func TestAPIUploadAssetRelease(t *testing.T) {
|
||||||
assert.EqualValues(t, 104, attachment.Size)
|
assert.EqualValues(t, 104, attachment.Size)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIGetReleaseArchiveDownloadCount(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
session := loginUser(t, owner.LowerName)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
name := "ReleaseDownloadCount"
|
||||||
|
|
||||||
|
createNewReleaseUsingAPI(t, session, token, owner, repo, name, "", name, "test")
|
||||||
|
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, name)
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", urlStr)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
var release *api.Release
|
||||||
|
DecodeJSON(t, resp, &release)
|
||||||
|
|
||||||
|
// Check if everything defaults to 0
|
||||||
|
assert.Equal(t, int64(0), release.ArchiveDownloadCount.TarGz)
|
||||||
|
assert.Equal(t, int64(0), release.ArchiveDownloadCount.Zip)
|
||||||
|
|
||||||
|
// Download the tarball to increase the count
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", release.TarURL), http.StatusOK)
|
||||||
|
|
||||||
|
// Check if the count has increased
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
DecodeJSON(t, resp, &release)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1), release.ArchiveDownloadCount.TarGz)
|
||||||
|
assert.Equal(t, int64(0), release.ArchiveDownloadCount.Zip)
|
||||||
|
}
|
||||||
|
|
|
@ -85,3 +85,39 @@ func createNewTagUsingAPI(t *testing.T, session *TestSession, token, ownerName,
|
||||||
DecodeJSON(t, resp, &respObj)
|
DecodeJSON(t, resp, &respObj)
|
||||||
return &respObj
|
return &respObj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIGetTagArchiveDownloadCount(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
// Login as User2.
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
repoName := "repo1"
|
||||||
|
tagName := "TagDownloadCount"
|
||||||
|
|
||||||
|
createNewTagUsingAPI(t, session, token, user.Name, repoName, tagName, "", "")
|
||||||
|
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/tags/%s?token=%s", user.Name, repoName, tagName, token)
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", urlStr)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
var tagInfo *api.Tag
|
||||||
|
DecodeJSON(t, resp, &tagInfo)
|
||||||
|
|
||||||
|
// Check if everything defaults to 0
|
||||||
|
assert.Equal(t, int64(0), tagInfo.ArchiveDownloadCount.TarGz)
|
||||||
|
assert.Equal(t, int64(0), tagInfo.ArchiveDownloadCount.Zip)
|
||||||
|
|
||||||
|
// Download the tarball to increase the count
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", tagInfo.TarballURL), http.StatusOK)
|
||||||
|
|
||||||
|
// Check if the count has increased
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
DecodeJSON(t, resp, &tagInfo)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1), tagInfo.ArchiveDownloadCount.TarGz)
|
||||||
|
assert.Equal(t, int64(0), tagInfo.ArchiveDownloadCount.Zip)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue