Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix NuGet search endpoints #25613

Merged
merged 13 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ require (
mvdan.cc/xurls/v2 v2.5.0
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
xorm.io/builder v0.3.12
xorm.io/xorm v1.3.3-0.20230623150031-18f8e7a86c75
xorm.io/xorm v1.3.3-0.20230714073535-9b71cb49cca4
)

require (
Expand Down
401 changes: 61 additions & 340 deletions go.sum

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions models/db/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,19 @@ func BuildCaseInsensitiveLike(key, value string) builder.Cond {
}
return builder.Like{"UPPER(" + key + ")", strings.ToUpper(value)}
}

// BuilderDialect returns the xorm.Builder dialect of the engine
func BuilderDialect() string {
switch {
case setting.Database.Type.IsMySQL():
return builder.MYSQL
case setting.Database.Type.IsSQLite3():
return builder.SQLITE
case setting.Database.Type.IsPostgreSQL():
return builder.POSTGRES
case setting.Database.Type.IsMSSQL():
return builder.MSSQL
default:
return ""
}
}
78 changes: 78 additions & 0 deletions models/packages/nuget/search.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package nuget

import (
"context"
"strings"

"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"

"xorm.io/builder"
)

// SearchVersions gets all versions of packages matching the search options
func SearchVersions(ctx context.Context, opts *packages_model.PackageSearchOptions) ([]*packages_model.PackageVersion, int64, error) {
var cond builder.Cond = builder.Eq{
"package.is_internal": opts.IsInternal.IsTrue(),
"package.owner_id": opts.OwnerID,
"package.type": packages_model.TypeNuGet,
}
if opts.Name.Value != "" {
if opts.Name.ExactMatch {
cond = cond.And(builder.Eq{"package.lower_name": strings.ToLower(opts.Name.Value)})
} else {
cond = cond.And(builder.Like{"package.lower_name", strings.ToLower(opts.Name.Value)})
}
}
KN4CK3R marked this conversation as resolved.
Show resolved Hide resolved

e := db.GetEngine(ctx)

total, err := e.
Where(cond).
Count(&packages_model.Package{})
if err != nil {
return nil, 0, err
}

inner := builder.
Dialect(db.BuilderDialect()).
KN4CK3R marked this conversation as resolved.
Show resolved Hide resolved
Select("*").
From("package").
Where(cond).
OrderBy("package.name ASC")
if opts.Paginator != nil {
skip, take := opts.GetSkipTake()
inner = inner.Limit(take, skip)
}

sess := e.
Where(opts.ToConds()).
Table("package_version").
Join("INNER", inner, "package.id = package_version.package_id")

pvs := make([]*packages_model.PackageVersion, 0, 10)
return pvs, total, sess.Find(&pvs)
}

// CountPackages counts all packages matching the search options
func CountPackages(ctx context.Context, opts *packages_model.PackageSearchOptions) (int64, error) {
var cond builder.Cond = builder.Eq{
"package.is_internal": opts.IsInternal.IsTrue(),
"package.owner_id": opts.OwnerID,
"package.type": packages_model.TypeNuGet,
}
if opts.Name.Value != "" {
if opts.Name.ExactMatch {
cond = cond.And(builder.Eq{"package.lower_name": strings.ToLower(opts.Name.Value)})
} else {
cond = cond.And(builder.Like{"package.lower_name", strings.ToLower(opts.Name.Value)})
}
}

return db.GetEngine(ctx).
Where(cond).
Count(&packages_model.Package{})
}
10 changes: 5 additions & 5 deletions models/packages/package_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ type PackageSearchOptions struct {
db.Paginator
}

func (opts *PackageSearchOptions) toConds() builder.Cond {
func (opts *PackageSearchOptions) ToConds() builder.Cond {
cond := builder.NewCond()
if !opts.IsInternal.IsNone() {
cond = builder.Eq{
Expand Down Expand Up @@ -283,7 +283,7 @@ func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
// SearchVersions gets all versions of packages matching the search options
func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
sess := db.GetEngine(ctx).
Where(opts.toConds()).
Where(opts.ToConds()).
Table("package_version").
Join("INNER", "package", "package.id = package_version.package_id")

Expand All @@ -300,7 +300,7 @@ func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*Package

// 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().
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))")
Expand Down Expand Up @@ -328,7 +328,7 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P
// ExistVersion checks if a version matching the search options exist
func ExistVersion(ctx context.Context, opts *PackageSearchOptions) (bool, error) {
return db.GetEngine(ctx).
Where(opts.toConds()).
Where(opts.ToConds()).
Table("package_version").
Join("INNER", "package", "package.id = package_version.package_id").
Exist(new(PackageVersion))
Expand All @@ -337,7 +337,7 @@ func ExistVersion(ctx context.Context, opts *PackageSearchOptions) (bool, error)
// CountVersions counts all versions of packages matching the search options
func CountVersions(ctx context.Context, opts *PackageSearchOptions) (int64, error) {
return db.GetEngine(ctx).
Where(opts.toConds()).
Where(opts.ToConds()).
Table("package_version").
Join("INNER", "package", "package.id = package_version.package_id").
Count(new(PackageVersion))
Expand Down
13 changes: 11 additions & 2 deletions routers/api/packages/nuget/api_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import (

packages_model "code.gitea.io/gitea/models/packages"
nuget_module "code.gitea.io/gitea/modules/packages/nuget"

"golang.org/x/text/collate"
"golang.org/x/text/language"
)

// https://docs.microsoft.com/en-us/nuget/api/service-index#resources
Expand Down Expand Up @@ -207,9 +210,15 @@ func createSearchResultResponse(l *linkBuilder, totalHits int64, pds []*packages
grouped[pd.Package.Name] = append(grouped[pd.Package.Name], pd)
}

keys := make([]string, 0, len(grouped))
for key := range grouped {
keys = append(keys, key)
}
collate.New(language.English, collate.IgnoreCase).SortStrings(keys)

data := make([]*SearchResult, 0, len(pds))
for _, group := range grouped {
data = append(data, createSearchResult(l, group))
for _, key := range keys {
data = append(data, createSearchResult(l, grouped[key]))
}

return &SearchResultResponse{
Expand Down
9 changes: 4 additions & 5 deletions routers/api/packages/nuget/nuget.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
nuget_model "code.gitea.io/gitea/models/packages/nuget"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
Expand Down Expand Up @@ -119,7 +120,7 @@ func SearchServiceV2(ctx *context.Context) {
take = ctx.FormInt("$top")
}

pvs, total, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet,
Name: packages_model.SearchValue{
Expand Down Expand Up @@ -153,9 +154,8 @@ func SearchServiceV2(ctx *context.Context) {

// http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351
func SearchServiceV2Count(ctx *context.Context) {
count, err := packages_model.CountVersions(ctx, &packages_model.PackageSearchOptions{
count, err := nuget_model.CountPackages(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet,
Name: packages_model.SearchValue{
Value: getSearchTerm(ctx),
},
Expand All @@ -171,9 +171,8 @@ func SearchServiceV2Count(ctx *context.Context) {

// https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages
func SearchServiceV3(ctx *context.Context) {
pvs, count, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
pvs, count, err := nuget_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet,
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
IsInternal: util.OptionalBoolFalse,
Paginator: db.NewAbsoluteListOptions(
Expand Down
18 changes: 9 additions & 9 deletions tests/integration/api_packages_nuget_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
{"test", 1, 10, 1, 0},
}

req := NewRequestWithBody(t, "PUT", url, createPackage(packageName, "1.0.99"))
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusCreated)

t.Run("v2", func(t *testing.T) {
t.Run("Search()", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
Expand Down Expand Up @@ -456,18 +460,14 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusCreated)

req = NewRequestWithBody(t, "PUT", url, createPackage(packageName, "1.0.99"))
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusCreated)

req = NewRequest(t, "GET", fmt.Sprintf("%s/query?q=%s", url, packageName))
req = AddBasicAuthHeader(req, user.Name)
resp := MakeRequest(t, req, http.StatusOK)

var result nuget.SearchResultResponse
DecodeJSON(t, resp, &result)

assert.EqualValues(t, 3, result.TotalHits)
assert.EqualValues(t, 2, result.TotalHits)
assert.Len(t, result.Data, 2)
for _, sr := range result.Data {
if sr.ID == packageName {
Expand All @@ -480,12 +480,12 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName+".dummy", "1.0.0"))
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusNoContent)

req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, "1.0.99"))
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusNoContent)
})
})

req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, "1.0.99"))
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusNoContent)
})

t.Run("RegistrationService", func(t *testing.T) {
Expand Down