Skip to content

Commit

Permalink
NPM Package Registry search API endpoint (#20280)
Browse files Browse the repository at this point in the history
Close #20098, in the NPM registry API, implemented to match what's described by https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#get-v1search

Currently have only implemented the bare minimum to work with the [Unity Package Manager](https://docs.unity3d.com/Manual/upm-ui.html).

Co-authored-by: Jack Vine <jackv@jack-lemur-suse.cat-prometheus.ts.net>
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
  • Loading branch information
5 people authored Sep 24, 2022
1 parent da0a9ec commit 83680c9
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 0 deletions.
5 changes: 5 additions & 0 deletions docs/content/doc/packages/npm.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ npm dist-tag add test_package@1.0.2 release

The tag name must not be a valid version. All tag names which are parsable as a version are rejected.

## Search packages

The registry supports [searching](https://docs.npmjs.com/cli/v7/commands/npm-search/) but does not support special search qualifiers like `author:gitea`.

## Supported commands

```
Expand All @@ -136,4 +140,5 @@ npm publish
npm unpublish
npm dist-tag
npm view
npm search
```
28 changes: 28 additions & 0 deletions modules/packages/npm/creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,34 @@ type PackageDistribution struct {
NpmSignature string `json:"npm-signature,omitempty"`
}

type PackageSearch struct {
Objects []*PackageSearchObject `json:"objects"`
Total int64 `json:"total"`
}

type PackageSearchObject struct {
Package *PackageSearchPackage `json:"package"`
}

type PackageSearchPackage struct {
Scope string `json:"scope"`
Name string `json:"name"`
Version string `json:"version"`
Date time.Time `json:"date"`
Description string `json:"description"`
Author User `json:"author"`
Publisher User `json:"publisher"`
Maintainers []User `json:"maintainers"`
Keywords []string `json:"keywords,omitempty"`
Links *PackageSearchPackageLinks `json:"links"`
}

type PackageSearchPackageLinks struct {
Registry string `json:"npm"`
Homepage string `json:"homepage,omitempty"`
Repository string `json:"repository,omitempty"`
}

// User https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#package
type User struct {
Username string `json:"username,omitempty"`
Expand Down
3 changes: 3 additions & 0 deletions routers/api/packages/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,9 @@ func Routes(ctx gocontext.Context) *web.Route {
r.Delete("", npm.DeletePackageTag)
}, reqPackageAccess(perm.AccessModeWrite))
})
r.Group("/-/v1/search", func() {
r.Get("", npm.PackageSearch)
})
})
r.Group("/pub", func() {
r.Group("/api/packages", func() {
Expand Down
35 changes: 35 additions & 0 deletions routers/api/packages/npm/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,38 @@ func createPackageMetadataVersion(registryURL string, pd *packages_model.Package
},
}
}

func createPackageSearchResponse(pds []*packages_model.PackageDescriptor, total int64) *npm_module.PackageSearch {
objects := make([]*npm_module.PackageSearchObject, 0, len(pds))
for _, pd := range pds {
metadata := pd.Metadata.(*npm_module.Metadata)

scope := metadata.Scope
if scope == "" {
scope = "unscoped"
}

objects = append(objects, &npm_module.PackageSearchObject{
Package: &npm_module.PackageSearchPackage{
Scope: scope,
Name: metadata.Name,
Version: pd.Version.Version,
Date: pd.Version.CreatedUnix.AsLocalTime(),
Description: metadata.Description,
Author: npm_module.User{Name: metadata.Author},
Publisher: npm_module.User{Name: pd.Owner.Name},
Maintainers: []npm_module.User{}, // npm cli needs this field
Keywords: metadata.Keywords,
Links: &npm_module.PackageSearchPackageLinks{
Registry: pd.FullWebLink(),
Homepage: metadata.ProjectURL,
},
},
})
}

return &npm_module.PackageSearch{
Objects: objects,
Total: total,
}
}
32 changes: 32 additions & 0 deletions routers/api/packages/npm/npm.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,35 @@ func setPackageTag(tag string, pv *packages_model.PackageVersion, deleteOnly boo

return committer.Commit()
}

func PackageSearch(ctx *context.Context) {
pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNpm,
Name: packages_model.SearchValue{
ExactMatch: false,
Value: ctx.FormTrim("text"),
},
Paginator: db.NewAbsoluteListOptions(
ctx.FormInt("from"),
ctx.FormInt("size"),
),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}

pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}

resp := createPackageSearchResponse(
pds,
total,
)

ctx.JSON(http.StatusOK, resp)
}
31 changes: 31 additions & 0 deletions tests/integration/api_packages_npm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,37 @@ func TestPackageNpm(t *testing.T) {
test(t, http.StatusOK, packageTag2)
})

t.Run("Search", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()

url := fmt.Sprintf("/api/packages/%s/npm/-/v1/search", user.Name)

cases := []struct {
Query string
Skip int
Take int
ExpectedTotal int64
ExpectedResults int
}{
{"", 0, 0, 1, 1},
{"", 0, 10, 1, 1},
{"gitea", 0, 10, 0, 0},
{"test", 0, 10, 1, 1},
{"test", 1, 10, 1, 0},
}

for i, c := range cases {
req := NewRequest(t, "GET", fmt.Sprintf("%s?text=%s&from=%d&size=%d", url, c.Query, c.Skip, c.Take))
resp := MakeRequest(t, req, http.StatusOK)

var result npm.PackageSearch
DecodeJSON(t, resp, &result)

assert.Equal(t, c.ExpectedTotal, result.Total, "case %d: unexpected total hits", i)
assert.Len(t, result.Objects, c.ExpectedResults, "case %d: unexpected result count", i)
}
})

t.Run("Delete", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()

Expand Down

0 comments on commit 83680c9

Please sign in to comment.