mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-25 08:59:31 -05:00
NPM Package Registry search API endpoint (#20280)
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>
This commit is contained in:
parent
da0a9ec811
commit
83680c97a7
6 changed files with 134 additions and 0 deletions
|
@ -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
|
||||
|
||||
```
|
||||
|
@ -136,4 +140,5 @@ npm publish
|
|||
npm unpublish
|
||||
npm dist-tag
|
||||
npm view
|
||||
npm search
|
||||
```
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)()
|
||||
|
||||
|
|
Loading…
Reference in a new issue