From bbec567c910a0596f1b32901291cd8a60e34e3c6 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Sat, 12 Aug 2023 12:23:40 +0200 Subject: [PATCH 1/4] Show package count in header --- models/packages/package_version.go | 17 +++++++++-- modules/context/repo.go | 11 +++++++ modules/structs/org.go | 1 + modules/structs/repo.go | 1 + modules/structs/user.go | 1 + routers/web/shared/user/header.go | 10 +++++++ services/convert/convert.go | 4 +++ services/convert/repository.go | 9 ++++++ services/convert/user.go | 5 ++++ templates/org/menu.tmpl | 3 ++ templates/repo/header.tmpl | 3 ++ templates/swagger/v1_json.tmpl | 15 ++++++++++ templates/user/overview/header.tmpl | 3 ++ tests/integration/api_org_test.go | 39 +++++++++++++++++++++++++ tests/integration/api_user_info_test.go | 32 ++++++++++++++++++++ 15 files changed, 152 insertions(+), 2 deletions(-) diff --git a/models/packages/package_version.go b/models/packages/package_version.go index ab1bcddae5818..2e6a033e6d3bc 100644 --- a/models/packages/package_version.go +++ b/models/packages/package_version.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/util" "xorm.io/builder" + "xorm.io/xorm" ) // ErrDuplicatePackageVersion indicates a duplicated package version error @@ -298,8 +299,7 @@ func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*Package return pvs, count, err } -// SearchLatestVersions gets the latest version of every package matching the search options -func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) { +func getLatestVersions(ctx context.Context, opts *PackageSearchOptions) *xorm.Session { cond := opts.toConds(). And(builder.Expr("pv2.id IS NULL")) @@ -314,6 +314,13 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P Join("INNER", "package", "package.id = package_version.package_id"). Where(cond) + return sess +} + +// SearchLatestVersions gets the latest version of every package matching the search options +func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) { + sess := getLatestVersions(ctx, opts) + opts.configureOrderBy(sess) if opts.Paginator != nil { @@ -325,6 +332,12 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P return pvs, count, err } +// CountLatestVersions counts the latest version of every package matching the search options +func CountLatestVersions(ctx context.Context, opts *PackageSearchOptions) (int64, error) { + sess := getLatestVersions(ctx, opts) + return sess.Count(new(PackageVersion)) +} + // ExistVersion checks if a version matching the search options exist func ExistVersion(ctx context.Context, opts *PackageSearchOptions) (bool, error) { return db.GetEngine(ctx). diff --git a/modules/context/repo.go b/modules/context/repo.go index f5c56cf833234..c43e7f479ddfe 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" + packages_model "code.gitea.io/gitea/models/packages" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" @@ -550,6 +551,16 @@ func RepoAssignment(ctx *Context) context.CancelFunc { return nil } + ctx.Data["NumPackages"], err = packages_model.CountLatestVersions(ctx, &packages_model.PackageSearchOptions{ + OwnerID: ctx.ContextUser.ID, + RepoID: ctx.Repo.Repository.ID, + IsInternal: util.OptionalBoolFalse, + }) + if err != nil { + ctx.ServerError("CountLatestPackagesVersions", err) + return nil + } + ctx.Data["Title"] = owner.Name + "/" + repo.Name ctx.Data["Repository"] = repo ctx.Data["Owner"] = ctx.Repo.Repository.Owner diff --git a/modules/structs/org.go b/modules/structs/org.go index c0a545ac1c3fa..491e893282e37 100644 --- a/modules/structs/org.go +++ b/modules/structs/org.go @@ -15,6 +15,7 @@ type Organization struct { Location string `json:"location"` Visibility string `json:"visibility"` RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"` + Packages int `json:"package_count"` // deprecated UserName string `json:"username"` } diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 6a2ba4836bb93..d23b0a67ae3e2 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -74,6 +74,7 @@ type Repository struct { OpenIssues int `json:"open_issues_count"` OpenPulls int `json:"open_pr_counter"` Releases int `json:"release_counter"` + Packages int `json:"package_counter"` DefaultBranch string `json:"default_branch"` Archived bool `json:"archived"` // swagger:strfmt date-time diff --git a/modules/structs/user.go b/modules/structs/user.go index 0df67894b0397..f89583c6e6487 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -52,6 +52,7 @@ type User struct { Followers int `json:"followers_count"` Following int `json:"following_count"` StarredRepos int `json:"starred_repos_count"` + Packages int `json:"packages_count"` } // MarshalJSON implements the json.Marshaler interface for User, adding field(s) for backward compatibility diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go index 9b1918ed16b48..5473119e8afb7 100644 --- a/routers/web/shared/user/header.go +++ b/routers/web/shared/user/header.go @@ -6,6 +6,7 @@ package user import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" + packages_model "code.gitea.io/gitea/models/packages" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" @@ -127,5 +128,14 @@ func LoadHeaderCount(ctx *context.Context) error { } ctx.Data["RepoCount"] = repoCount + packageCount, err := packages_model.CountLatestVersions(ctx, &packages_model.PackageSearchOptions{ + OwnerID: ctx.ContextUser.ID, + IsInternal: util.OptionalBoolFalse, + }) + if err != nil { + return err + } + ctx.Data["PackageCount"] = packageCount + return nil } diff --git a/services/convert/convert.go b/services/convert/convert.go index a7a777e8bd6ba..0531cae0abe54 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -17,6 +17,7 @@ import ( git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" + packages_model "code.gitea.io/gitea/models/packages" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" @@ -283,6 +284,8 @@ func ToDeployKey(apiLink string, key *asymkey_model.DeployKey) *api.DeployKey { // ToOrganization convert user_model.User to api.Organization func ToOrganization(ctx context.Context, org *organization.Organization) *api.Organization { + numPackages, _ := packages_model.CountLatestVersions(ctx, &packages_model.PackageSearchOptions{OwnerID: org.ID, IsInternal: util.OptionalBoolFalse}) + return &api.Organization{ ID: org.ID, AvatarURL: org.AsUser().AvatarLink(ctx), @@ -295,6 +298,7 @@ func ToOrganization(ctx context.Context, org *organization.Organization) *api.Or Location: org.Location, Visibility: org.Visibility.String(), RepoAdminChangeTeamAccess: org.RepoAdminChangeTeamAccess, + Packages: int(numPackages), } } diff --git a/services/convert/repository.go b/services/convert/repository.go index 6f77b4932e4fe..42f2cd3408cd5 100644 --- a/services/convert/repository.go +++ b/services/convert/repository.go @@ -8,12 +8,14 @@ import ( "time" "code.gitea.io/gitea/models" + packages_model "code.gitea.io/gitea/models/packages" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" ) // ToRepo converts a Repository to api.Repository @@ -135,6 +137,12 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR numReleases, _ := repo_model.GetReleaseCountByRepoID(ctx, repo.ID, repo_model.FindReleasesOptions{IncludeDrafts: false, IncludeTags: false}) + numPackages, _ := packages_model.CountLatestVersions(ctx, &packages_model.PackageSearchOptions{ + OwnerID: repo.Owner.ID, + RepoID: repo.ID, + IsInternal: util.OptionalBoolFalse, + }) + mirrorInterval := "" var mirrorUpdated time.Time if repo.IsMirror { @@ -193,6 +201,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR OpenIssues: repo.NumOpenIssues, OpenPulls: repo.NumOpenPulls, Releases: int(numReleases), + Packages: int(numPackages), DefaultBranch: repo.DefaultBranch, Created: repo.CreatedUnix.AsTime(), Updated: repo.UpdatedUnix.AsTime(), diff --git a/services/convert/user.go b/services/convert/user.go index 3521dd2f905c3..d27cc11fcf097 100644 --- a/services/convert/user.go +++ b/services/convert/user.go @@ -6,9 +6,11 @@ package convert import ( "context" + packages_model "code.gitea.io/gitea/models/packages" "code.gitea.io/gitea/models/perm" user_model "code.gitea.io/gitea/models/user" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" ) // ToUser convert user_model.User to api.User @@ -47,6 +49,8 @@ func ToUserWithAccessMode(ctx context.Context, user *user_model.User, accessMode // toUser convert user_model.User to api.User // signed shall only be set if requester is logged in. authed shall only be set if user is site admin or user himself func toUser(ctx context.Context, user *user_model.User, signed, authed bool) *api.User { + numPackages, _ := packages_model.CountLatestVersions(ctx, &packages_model.PackageSearchOptions{OwnerID: user.ID, IsInternal: util.OptionalBoolFalse}) + result := &api.User{ ID: user.ID, UserName: user.Name, @@ -62,6 +66,7 @@ func toUser(ctx context.Context, user *user_model.User, signed, authed bool) *ap Followers: user.NumFollowers, Following: user.NumFollowing, StarredRepos: user.NumStars, + Packages: int(numPackages), } result.Visibility = user.Visibility.String() diff --git a/templates/org/menu.tmpl b/templates/org/menu.tmpl index a4f602730a503..92bbb2e7bc0d0 100644 --- a/templates/org/menu.tmpl +++ b/templates/org/menu.tmpl @@ -14,6 +14,9 @@ {{if and .IsPackageEnabled .CanReadPackages}} {{svg "octicon-package"}} {{.locale.Tr "packages.title"}} + {{if .PackageCount}} +
{{.PackageCount}}
+ {{end}}
{{end}} {{if and .IsRepoIndexerEnabled .CanReadCode}} diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index 984e9f044eb06..a57d56b2b6a2e 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -191,6 +191,9 @@ {{if .Permission.CanRead $.UnitTypePackages}} {{svg "octicon-package"}} {{.locale.Tr "packages.title"}} + {{if .NumPackages}} + {{CountFmt .NumPackages}} + {{end}} {{end}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 8cf5332bafc48..0bdfc665dbd45 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -20122,6 +20122,11 @@ "type": "string", "x-go-name": "Name" }, + "package_count": { + "type": "integer", + "format": "int64", + "x-go-name": "Packages" + }, "repo_admin_change_team_access": { "type": "boolean", "x-go-name": "RepoAdminChangeTeamAccess" @@ -21176,6 +21181,11 @@ "owner": { "$ref": "#/definitions/User" }, + "package_counter": { + "type": "integer", + "format": "int64", + "x-go-name": "Packages" + }, "parent": { "$ref": "#/definitions/Repository" }, @@ -21847,6 +21857,11 @@ "default": "empty", "x-go-name": "LoginName" }, + "packages_count": { + "type": "integer", + "format": "int64", + "x-go-name": "Packages" + }, "prohibit_login": { "description": "Is user login prohibited", "type": "boolean", diff --git a/templates/user/overview/header.tmpl b/templates/user/overview/header.tmpl index 5134449084d84..db4473df8402f 100644 --- a/templates/user/overview/header.tmpl +++ b/templates/user/overview/header.tmpl @@ -18,6 +18,9 @@ {{if and .IsPackageEnabled (or .ContextUser.IsIndividual (and .ContextUser.IsOrganization .CanReadPackages))}} {{svg "octicon-package"}} {{.locale.Tr "packages.title"}} + {{if .PackageCount}} +
{{.PackageCount}}
+ {{end}}
{{end}} {{if and .IsRepoIndexerEnabled (or .ContextUser.IsIndividual (and .ContextUser.IsOrganization .CanReadCode))}} diff --git a/tests/integration/api_org_test.go b/tests/integration/api_org_test.go index 83a101716b099..1922419073c1a 100644 --- a/tests/integration/api_org_test.go +++ b/tests/integration/api_org_test.go @@ -4,6 +4,7 @@ package integration import ( + "bytes" "fmt" "net/http" "net/url" @@ -218,3 +219,41 @@ func TestAPIOrgSearchEmptyTeam(t *testing.T) { } }) } + +func TestAPIOrgPackageCount(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteOrganization) + orgName := "PackageOrg" + + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs?token="+token, &api.CreateOrgOption{ + UserName: orgName, + }) + MakeRequest(t, req, http.StatusCreated) + + orgUrl := "api/v1/orgs/" + orgName + req = NewRequest(t, "GET", orgUrl) + resp := MakeRequest(t, req, http.StatusOK) + + var orgInfo *api.Organization + DecodeJSON(t, resp, &orgInfo) + + assert.Equal(t, 0, orgInfo.Packages) + + packageName := "test-package" + packageVersion := "1.0.3" + filename := "file.bin" + + url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", orgName, packageName, packageVersion, filename) + req = NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{})) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "GET", orgUrl) + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &orgInfo) + + assert.Equal(t, 1, orgInfo.Packages) +} diff --git a/tests/integration/api_user_info_test.go b/tests/integration/api_user_info_test.go index f4edfd8941cf3..92e4310ed5b7c 100644 --- a/tests/integration/api_user_info_test.go +++ b/tests/integration/api_user_info_test.go @@ -4,6 +4,7 @@ package integration import ( + "bytes" "fmt" "net/http" "testing" @@ -65,3 +66,34 @@ func TestAPIUserInfo(t *testing.T) { assert.Equal(t, user, u.UserName) }) } + +func TestAPIUserPackageCount(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + userUrl := "api/v1/users/" + user.Name + req := NewRequest(t, "GET", userUrl) + resp := MakeRequest(t, req, http.StatusOK) + + var userInfo *api.User + DecodeJSON(t, resp, &userInfo) + + assert.Equal(t, 0, userInfo.Packages) + + packageName := "test-package" + packageVersion := "1.0.3" + filename := "file.bin" + + url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, packageVersion, filename) + req = NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{})) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "GET", userUrl) + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &userInfo) + + assert.Equal(t, 1, userInfo.Packages) +} From 4d19b4cb82a8b46bf6e9a1886d7f9ff1dbf2af04 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Sat, 12 Aug 2023 12:41:50 +0200 Subject: [PATCH 2/4] Make linter happy --- tests/integration/api_org_test.go | 6 +++--- tests/integration/api_user_info_test.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/integration/api_org_test.go b/tests/integration/api_org_test.go index 1922419073c1a..ae907e6411618 100644 --- a/tests/integration/api_org_test.go +++ b/tests/integration/api_org_test.go @@ -232,8 +232,8 @@ func TestAPIOrgPackageCount(t *testing.T) { }) MakeRequest(t, req, http.StatusCreated) - orgUrl := "api/v1/orgs/" + orgName - req = NewRequest(t, "GET", orgUrl) + orgURL := "api/v1/orgs/" + orgName + req = NewRequest(t, "GET", orgURL) resp := MakeRequest(t, req, http.StatusOK) var orgInfo *api.Organization @@ -250,7 +250,7 @@ func TestAPIOrgPackageCount(t *testing.T) { AddBasicAuthHeader(req, user.Name) MakeRequest(t, req, http.StatusCreated) - req = NewRequest(t, "GET", orgUrl) + req = NewRequest(t, "GET", orgURL) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &orgInfo) diff --git a/tests/integration/api_user_info_test.go b/tests/integration/api_user_info_test.go index 92e4310ed5b7c..c12d693cd5cff 100644 --- a/tests/integration/api_user_info_test.go +++ b/tests/integration/api_user_info_test.go @@ -72,8 +72,8 @@ func TestAPIUserPackageCount(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - userUrl := "api/v1/users/" + user.Name - req := NewRequest(t, "GET", userUrl) + userURL := "api/v1/users/" + user.Name + req := NewRequest(t, "GET", userURL) resp := MakeRequest(t, req, http.StatusOK) var userInfo *api.User @@ -90,7 +90,7 @@ func TestAPIUserPackageCount(t *testing.T) { AddBasicAuthHeader(req, user.Name) MakeRequest(t, req, http.StatusCreated) - req = NewRequest(t, "GET", userUrl) + req = NewRequest(t, "GET", userURL) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &userInfo) From 67657f2888b9dffe839b87d2f9265ffd70fd9858 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Tue, 15 Aug 2023 00:15:49 +0200 Subject: [PATCH 3/4] Rename getLatestVersions to buildLatestVersionsSession --- models/packages/package_version.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/models/packages/package_version.go b/models/packages/package_version.go index 2e6a033e6d3bc..f28d399f148b2 100644 --- a/models/packages/package_version.go +++ b/models/packages/package_version.go @@ -299,7 +299,7 @@ func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*Package return pvs, count, err } -func getLatestVersions(ctx context.Context, opts *PackageSearchOptions) *xorm.Session { +func buildLatestVersionsSession(ctx context.Context, opts *PackageSearchOptions) *xorm.Session { cond := opts.toConds(). And(builder.Expr("pv2.id IS NULL")) @@ -319,7 +319,7 @@ func getLatestVersions(ctx context.Context, opts *PackageSearchOptions) *xorm.Se // SearchLatestVersions gets the latest version of every package matching the search options func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) { - sess := getLatestVersions(ctx, opts) + sess := buildLatestVersionsSession(ctx, opts) opts.configureOrderBy(sess) @@ -334,7 +334,7 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P // CountLatestVersions counts the latest version of every package matching the search options func CountLatestVersions(ctx context.Context, opts *PackageSearchOptions) (int64, error) { - sess := getLatestVersions(ctx, opts) + sess := buildLatestVersionsSession(ctx, opts) return sess.Count(new(PackageVersion)) } From 36785f23ebeebee225f316049cd84eceadfce3b6 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Thu, 31 Aug 2023 10:44:40 +0200 Subject: [PATCH 4/4] I hate mrge conflicts --- models/packages/package_version.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/models/packages/package_version.go b/models/packages/package_version.go index 85edf0b03d9f2..f648819fde63e 100644 --- a/models/packages/package_version.go +++ b/models/packages/package_version.go @@ -299,13 +299,8 @@ func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*Package return pvs, count, err } - func buildLatestVersionsSession(ctx context.Context, opts *PackageSearchOptions) *xorm.Session { cond := opts.ToConds(). - -// SearchLatestVersions gets the latest version of every package matching the search options -func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) { - cond := opts.ToConds(). And(builder.Expr("pv2.id IS NULL")) joinCond := builder.Expr("package_version.package_id = pv2.package_id AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))")