Skip to content

Commit 83680c9

Browse files
jackv24Jack VineKN4CK3Rlunnywxiaoguang
authored
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 <[email protected]> Co-authored-by: KN4CK3R <[email protected]> Co-authored-by: Lunny Xiao <[email protected]> Co-authored-by: wxiaoguang <[email protected]>
1 parent da0a9ec commit 83680c9

File tree

6 files changed

+134
-0
lines changed

6 files changed

+134
-0
lines changed

docs/content/doc/packages/npm.en-us.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ npm dist-tag add [email protected] release
127127

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

130+
## Search packages
131+
132+
The registry supports [searching](https://docs.npmjs.com/cli/v7/commands/npm-search/) but does not support special search qualifiers like `author:gitea`.
133+
130134
## Supported commands
131135

132136
```
@@ -136,4 +140,5 @@ npm publish
136140
npm unpublish
137141
npm dist-tag
138142
npm view
143+
npm search
139144
```

modules/packages/npm/creator.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,34 @@ type PackageDistribution struct {
9696
NpmSignature string `json:"npm-signature,omitempty"`
9797
}
9898

99+
type PackageSearch struct {
100+
Objects []*PackageSearchObject `json:"objects"`
101+
Total int64 `json:"total"`
102+
}
103+
104+
type PackageSearchObject struct {
105+
Package *PackageSearchPackage `json:"package"`
106+
}
107+
108+
type PackageSearchPackage struct {
109+
Scope string `json:"scope"`
110+
Name string `json:"name"`
111+
Version string `json:"version"`
112+
Date time.Time `json:"date"`
113+
Description string `json:"description"`
114+
Author User `json:"author"`
115+
Publisher User `json:"publisher"`
116+
Maintainers []User `json:"maintainers"`
117+
Keywords []string `json:"keywords,omitempty"`
118+
Links *PackageSearchPackageLinks `json:"links"`
119+
}
120+
121+
type PackageSearchPackageLinks struct {
122+
Registry string `json:"npm"`
123+
Homepage string `json:"homepage,omitempty"`
124+
Repository string `json:"repository,omitempty"`
125+
}
126+
99127
// User https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#package
100128
type User struct {
101129
Username string `json:"username,omitempty"`

routers/api/packages/api.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,9 @@ func Routes(ctx gocontext.Context) *web.Route {
236236
r.Delete("", npm.DeletePackageTag)
237237
}, reqPackageAccess(perm.AccessModeWrite))
238238
})
239+
r.Group("/-/v1/search", func() {
240+
r.Get("", npm.PackageSearch)
241+
})
239242
})
240243
r.Group("/pub", func() {
241244
r.Group("/api/packages", func() {

routers/api/packages/npm/api.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,38 @@ func createPackageMetadataVersion(registryURL string, pd *packages_model.Package
7474
},
7575
}
7676
}
77+
78+
func createPackageSearchResponse(pds []*packages_model.PackageDescriptor, total int64) *npm_module.PackageSearch {
79+
objects := make([]*npm_module.PackageSearchObject, 0, len(pds))
80+
for _, pd := range pds {
81+
metadata := pd.Metadata.(*npm_module.Metadata)
82+
83+
scope := metadata.Scope
84+
if scope == "" {
85+
scope = "unscoped"
86+
}
87+
88+
objects = append(objects, &npm_module.PackageSearchObject{
89+
Package: &npm_module.PackageSearchPackage{
90+
Scope: scope,
91+
Name: metadata.Name,
92+
Version: pd.Version.Version,
93+
Date: pd.Version.CreatedUnix.AsLocalTime(),
94+
Description: metadata.Description,
95+
Author: npm_module.User{Name: metadata.Author},
96+
Publisher: npm_module.User{Name: pd.Owner.Name},
97+
Maintainers: []npm_module.User{}, // npm cli needs this field
98+
Keywords: metadata.Keywords,
99+
Links: &npm_module.PackageSearchPackageLinks{
100+
Registry: pd.FullWebLink(),
101+
Homepage: metadata.ProjectURL,
102+
},
103+
},
104+
})
105+
}
106+
107+
return &npm_module.PackageSearch{
108+
Objects: objects,
109+
Total: total,
110+
}
111+
}

routers/api/packages/npm/npm.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,3 +350,35 @@ func setPackageTag(tag string, pv *packages_model.PackageVersion, deleteOnly boo
350350

351351
return committer.Commit()
352352
}
353+
354+
func PackageSearch(ctx *context.Context) {
355+
pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
356+
OwnerID: ctx.Package.Owner.ID,
357+
Type: packages_model.TypeNpm,
358+
Name: packages_model.SearchValue{
359+
ExactMatch: false,
360+
Value: ctx.FormTrim("text"),
361+
},
362+
Paginator: db.NewAbsoluteListOptions(
363+
ctx.FormInt("from"),
364+
ctx.FormInt("size"),
365+
),
366+
})
367+
if err != nil {
368+
apiError(ctx, http.StatusInternalServerError, err)
369+
return
370+
}
371+
372+
pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
373+
if err != nil {
374+
apiError(ctx, http.StatusInternalServerError, err)
375+
return
376+
}
377+
378+
resp := createPackageSearchResponse(
379+
pds,
380+
total,
381+
)
382+
383+
ctx.JSON(http.StatusOK, resp)
384+
}

tests/integration/api_packages_npm_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,37 @@ func TestPackageNpm(t *testing.T) {
224224
test(t, http.StatusOK, packageTag2)
225225
})
226226

227+
t.Run("Search", func(t *testing.T) {
228+
defer tests.PrintCurrentTest(t)()
229+
230+
url := fmt.Sprintf("/api/packages/%s/npm/-/v1/search", user.Name)
231+
232+
cases := []struct {
233+
Query string
234+
Skip int
235+
Take int
236+
ExpectedTotal int64
237+
ExpectedResults int
238+
}{
239+
{"", 0, 0, 1, 1},
240+
{"", 0, 10, 1, 1},
241+
{"gitea", 0, 10, 0, 0},
242+
{"test", 0, 10, 1, 1},
243+
{"test", 1, 10, 1, 0},
244+
}
245+
246+
for i, c := range cases {
247+
req := NewRequest(t, "GET", fmt.Sprintf("%s?text=%s&from=%d&size=%d", url, c.Query, c.Skip, c.Take))
248+
resp := MakeRequest(t, req, http.StatusOK)
249+
250+
var result npm.PackageSearch
251+
DecodeJSON(t, resp, &result)
252+
253+
assert.Equal(t, c.ExpectedTotal, result.Total, "case %d: unexpected total hits", i)
254+
assert.Len(t, result.Objects, c.ExpectedResults, "case %d: unexpected result count", i)
255+
}
256+
})
257+
227258
t.Run("Delete", func(t *testing.T) {
228259
defer tests.PrintCurrentTest(t)()
229260

0 commit comments

Comments
 (0)