@verdaccio/api 6.0.0-6-next.12 → 6.0.0-6-next.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/build/dist-tags.d.ts +2 -2
  3. package/build/dist-tags.js +8 -8
  4. package/build/dist-tags.js.map +1 -1
  5. package/build/index.d.ts +2 -2
  6. package/build/index.js +12 -13
  7. package/build/index.js.map +1 -1
  8. package/build/package.d.ts +2 -2
  9. package/build/package.js +6 -6
  10. package/build/package.js.map +1 -1
  11. package/build/publish.d.ts +7 -7
  12. package/build/publish.js +24 -22
  13. package/build/publish.js.map +1 -1
  14. package/build/search.d.ts +1 -1
  15. package/build/search.js +9 -102
  16. package/build/search.js.map +1 -1
  17. package/build/star.d.ts +2 -2
  18. package/build/star.js +6 -5
  19. package/build/star.js.map +1 -1
  20. package/build/stars.d.ts +2 -2
  21. package/build/stars.js +3 -3
  22. package/build/stars.js.map +1 -1
  23. package/build/user.js +12 -12
  24. package/build/user.js.map +1 -1
  25. package/build/v1/profile.js +10 -10
  26. package/build/v1/profile.js.map +1 -1
  27. package/build/v1/search.d.ts +9 -1
  28. package/build/v1/search.js +71 -88
  29. package/build/v1/search.js.map +1 -1
  30. package/build/v1/token.d.ts +2 -2
  31. package/build/v1/token.js +12 -12
  32. package/build/v1/token.js.map +1 -1
  33. package/build/whoami.js +0 -1
  34. package/build/whoami.js.map +1 -1
  35. package/package.json +16 -13
  36. package/src/dist-tags.ts +10 -10
  37. package/src/index.ts +12 -11
  38. package/src/package.ts +5 -10
  39. package/src/publish.ts +28 -19
  40. package/src/search.ts +9 -100
  41. package/src/star.ts +4 -3
  42. package/src/stars.ts +3 -3
  43. package/src/user.ts +7 -7
  44. package/src/v1/profile.ts +7 -7
  45. package/src/v1/search.ts +64 -89
  46. package/src/v1/token.ts +13 -18
  47. package/src/whoami.ts +0 -1
  48. package/test/integration/_helper.ts +3 -2
  49. package/test/integration/package.spec.ts +3 -3
  50. package/test/integration/ping.spec.ts +1 -1
  51. package/test/integration/publish.spec.ts +3 -8
  52. package/test/integration/user.spec.ts +6 -8
  53. package/test/integration/whoami.spec.ts +1 -1
  54. package/test/unit/publish.spec.ts +11 -15
  55. package/tsconfig.json +1 -1
package/src/package.ts CHANGED
@@ -3,11 +3,11 @@ import { Router } from 'express';
3
3
  import buildDebug from 'debug';
4
4
 
5
5
  import { allow } from '@verdaccio/middleware';
6
- import { getVersion, ErrorCode } from '@verdaccio/utils';
7
- import { HEADERS, DIST_TAGS, API_ERROR } from '@verdaccio/commons-api';
6
+ import { getVersion } from '@verdaccio/utils';
7
+ import { HEADERS, DIST_TAGS, API_ERROR, errorUtils } from '@verdaccio/core';
8
8
  import { Config, Package } from '@verdaccio/types';
9
9
  import { IAuth } from '@verdaccio/auth';
10
- import { IStorageHandler } from '@verdaccio/store';
10
+ import { Storage } from '@verdaccio/store';
11
11
  import { convertDistRemoteToLocalTarballUrls } from '@verdaccio/tarball';
12
12
  import { $RequestExtend, $ResponseExtend, $NextFunctionVer } from '../types/custom';
13
13
 
@@ -34,12 +34,7 @@ const downloadStream = (
34
34
  stream.pipe(res);
35
35
  };
36
36
 
37
- export default function (
38
- route: Router,
39
- auth: IAuth,
40
- storage: IStorageHandler,
41
- config: Config
42
- ): void {
37
+ export default function (route: Router, auth: IAuth, storage: Storage, config: Config): void {
43
38
  const can = allow(auth);
44
39
  // TODO: anonymous user?
45
40
  route.get(
@@ -85,7 +80,7 @@ export default function (
85
80
  }
86
81
 
87
82
  debug('package version not found %o', queryVersion);
88
- return next(ErrorCode.getNotFound(`${API_ERROR.VERSION_NOT_EXIST}: ${queryVersion}`));
83
+ return next(errorUtils.getNotFound(`${API_ERROR.VERSION_NOT_EXIST}: ${queryVersion}`));
89
84
  };
90
85
 
91
86
  debug('get package name %o', name);
package/src/publish.ts CHANGED
@@ -4,14 +4,21 @@ import mime from 'mime';
4
4
  import { Router } from 'express';
5
5
  import buildDebug from 'debug';
6
6
 
7
- import { API_MESSAGE, HEADERS, DIST_TAGS, API_ERROR, HTTP_STATUS } from '@verdaccio/commons-api';
8
- import { validateMetadata, isObject, ErrorCode, hasDiffOneKey } from '@verdaccio/utils';
7
+ import {
8
+ API_MESSAGE,
9
+ HEADERS,
10
+ DIST_TAGS,
11
+ API_ERROR,
12
+ HTTP_STATUS,
13
+ errorUtils,
14
+ } from '@verdaccio/core';
15
+ import { validateMetadata, isObject, hasDiffOneKey } from '@verdaccio/utils';
9
16
  import { media, expectJson, allow } from '@verdaccio/middleware';
10
17
  import { notify } from '@verdaccio/hooks';
11
- import { Config, Callback, MergeTags, Version, Package } from '@verdaccio/types';
18
+ import { Config, Callback, MergeTags, Version, Package, CallbackAction } from '@verdaccio/types';
12
19
  import { logger } from '@verdaccio/logger';
13
20
  import { IAuth } from '@verdaccio/auth';
14
- import { IStorageHandler } from '@verdaccio/store';
21
+ import { Storage } from '@verdaccio/store';
15
22
  import { $RequestExtend, $ResponseExtend, $NextFunctionVer } from '../types/custom';
16
23
 
17
24
  import star from './star';
@@ -22,7 +29,7 @@ const debug = buildDebug('verdaccio:api:publish');
22
29
  export default function publish(
23
30
  router: Router,
24
31
  auth: IAuth,
25
- storage: IStorageHandler,
32
+ storage: Storage,
26
33
  config: Config
27
34
  ): void {
28
35
  const can = allow(auth);
@@ -138,7 +145,7 @@ export default function publish(
138
145
  /**
139
146
  * Publish a package
140
147
  */
141
- export function publishPackage(storage: IStorageHandler, config: Config, auth: IAuth): any {
148
+ export function publishPackage(storage: Storage, config: Config, auth: IAuth): any {
142
149
  const starApi = star(storage);
143
150
  return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
144
151
  const packageName = req.params.package;
@@ -172,7 +179,7 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
172
179
  /**
173
180
  * Add new package version in storage
174
181
  */
175
- const createVersion = function (version: string, metadata: Version, cb: Callback): void {
182
+ const createVersion = function (version: string, metadata: Version, cb: CallbackAction): void {
176
183
  debug('add a new package version %o to storage %o', version, metadata);
177
184
  storage.addVersion(packageName, version, metadata, null, cb);
178
185
  };
@@ -180,7 +187,7 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
180
187
  /**
181
188
  * Add new tags in storage
182
189
  */
183
- const addTags = function (tags: MergeTags, cb: Callback): void {
190
+ const addTags = function (tags: MergeTags, cb: CallbackAction): void {
184
191
  debug('add new tag %o to storage', packageName);
185
192
  storage.mergeTags(packageName, tags, cb);
186
193
  };
@@ -224,7 +231,7 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
224
231
  // if this happens in normal circumstances, report it as a bug
225
232
  debug('invalid body format');
226
233
  logger.info({ packageName }, `wrong package format on publish a package @{packageName}`);
227
- return next(ErrorCode.getBadRequest(API_ERROR.UNSUPORTED_REGISTRY_CALL));
234
+ return next(errorUtils.getBadRequest(API_ERROR.UNSUPORTED_REGISTRY_CALL));
228
235
  }
229
236
 
230
237
  if (error && error.status !== HTTP_STATUS.CONFLICT) {
@@ -322,7 +329,7 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
322
329
  } catch (error: any) {
323
330
  debug('error on publish, bad package format %o', packageName);
324
331
  logger.error({ packageName }, 'error on publish, bad package data for @{packageName}');
325
- return next(ErrorCode.getBadData(API_ERROR.BAD_PACKAGE_DATA));
332
+ return next(errorUtils.getBadData(API_ERROR.BAD_PACKAGE_DATA));
326
333
  }
327
334
  };
328
335
  }
@@ -330,25 +337,27 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
330
337
  /**
331
338
  * un-publish a package
332
339
  */
333
- export function unPublishPackage(storage: IStorageHandler) {
334
- return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
340
+ export function unPublishPackage(storage: Storage) {
341
+ return async function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
335
342
  const packageName = req.params.package;
336
343
 
337
344
  logger.debug({ packageName }, `unpublishing @{packageName}`);
338
- storage.removePackage(packageName, function (err) {
345
+ try {
346
+ await storage.removePackage(packageName);
347
+ } catch (err) {
339
348
  if (err) {
340
349
  return next(err);
341
350
  }
342
- res.status(HTTP_STATUS.CREATED);
343
- return next({ ok: API_MESSAGE.PKG_REMOVED });
344
- });
351
+ }
352
+ res.status(HTTP_STATUS.CREATED);
353
+ return next({ ok: API_MESSAGE.PKG_REMOVED });
345
354
  };
346
355
  }
347
356
 
348
357
  /**
349
358
  * Delete tarball
350
359
  */
351
- export function removeTarball(storage: IStorageHandler) {
360
+ export function removeTarball(storage: Storage) {
352
361
  return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
353
362
  const packageName = req.params.package;
354
363
  const { filename, revision } = req.params;
@@ -374,7 +383,7 @@ export function removeTarball(storage: IStorageHandler) {
374
383
  /**
375
384
  * Adds a new version
376
385
  */
377
- export function addVersion(storage: IStorageHandler) {
386
+ export function addVersion(storage: Storage) {
378
387
  return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
379
388
  const { version, tag } = req.params;
380
389
  const packageName = req.params.package;
@@ -398,7 +407,7 @@ export function addVersion(storage: IStorageHandler) {
398
407
  /**
399
408
  * uploadPackageTarball
400
409
  */
401
- export function uploadPackageTarball(storage: IStorageHandler) {
410
+ export function uploadPackageTarball(storage: Storage) {
402
411
  return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
403
412
  const packageName = req.params.package;
404
413
  const stream = storage.addTarball(packageName, req.params.filename);
package/src/search.ts CHANGED
@@ -1,102 +1,11 @@
1
- import { HEADERS } from '@verdaccio/commons-api';
2
-
3
- export default function (route, auth, storage): void {
4
- // searching packages
5
- route.get('/-/all(/since)?', function (req, res) {
6
- let received_end = false;
7
- let response_finished = false;
8
- let processing_pkgs = 0;
9
- let firstPackage = true;
10
-
11
- res.status(200);
12
- res.set(HEADERS.CONTENT_TYPE, HEADERS.JSON_CHARSET);
13
-
14
- /*
15
- * Offical NPM registry (registry.npmjs.org) no longer return whole database,
16
- * They only return packages matched with keyword in `referer: search pkg-name`,
17
- * And NPM client will request server in every search.
18
- *
19
- * The magic number 99999 was sent by NPM registry. Modify it may caused strange
20
- * behaviour in the future.
21
- *
22
- * BTW: NPM will not return result if user-agent does not contain string 'npm',
23
- * See: method 'request' in up-storage.js
24
- *
25
- * If there is no cache in local, NPM will request /-/all, then get response with
26
- * _updated: 99999, 'Date' in response header was Mon, 10 Oct 1983 00:12:48 GMT,
27
- * this will make NPM always query from server
28
- *
29
- * Data structure also different, whel request /-/all, response is an object, but
30
- * when request /-/all/since, response is an array
31
- */
32
- const respShouldBeArray = req.path.endsWith('/since');
33
- if (!respShouldBeArray) {
34
- res.set('Date', 'Mon, 10 Oct 1983 00:12:48 GMT');
35
- }
36
- const check_finish = function (): void {
37
- if (!received_end) {
38
- return;
39
- }
40
- if (processing_pkgs) {
41
- return;
42
- }
43
- if (response_finished) {
44
- return;
45
- }
46
- response_finished = true;
47
- if (respShouldBeArray) {
48
- res.end(']\n');
49
- } else {
50
- res.end('}\n');
51
- }
52
- };
53
-
54
- if (respShouldBeArray) {
55
- res.write('[');
56
- } else {
57
- res.write('{"_updated":' + 99999);
58
- }
59
-
60
- const stream = storage.search(req.query.startkey || 0, { req: req });
61
-
62
- stream.on('data', function each(pkg) {
63
- processing_pkgs++;
64
-
65
- auth.allow_access({ packageName: pkg.name }, req.remote_user, function (err, allowed) {
66
- processing_pkgs--;
67
-
68
- if (err) {
69
- if (err.status && String(err.status).match(/^4\d\d$/)) {
70
- // auth plugin returns 4xx user error,
71
- // that's equivalent of !allowed basically
72
- allowed = false;
73
- } else {
74
- stream.abort(err);
75
- }
76
- }
77
-
78
- if (allowed) {
79
- if (respShouldBeArray) {
80
- res.write(`${firstPackage ? '' : ','}${JSON.stringify(pkg)}\n`);
81
- if (firstPackage) {
82
- firstPackage = false;
83
- }
84
- } else {
85
- res.write(',\n' + JSON.stringify(pkg.name) + ':' + JSON.stringify(pkg));
86
- }
87
- }
88
-
89
- check_finish();
90
- });
91
- });
92
-
93
- stream.on('error', function () {
94
- res.socket.destroy();
95
- });
96
-
97
- stream.on('end', function () {
98
- received_end = true;
99
- check_finish();
100
- });
1
+ import { HTTP_STATUS } from '@verdaccio/core';
2
+ import { logger } from '@verdaccio/logger';
3
+
4
+ export default function (route): void {
5
+ // TODO: next major version, remove this
6
+ route.get('/-/all(/since)?', function (_req, res) {
7
+ logger.warn('search endpoint has been removed, please use search v1');
8
+ res.status(HTTP_STATUS.NOT_FOUND);
9
+ res.json({ error: 'not found, endpoint was removed' });
101
10
  });
102
11
  }
package/src/star.ts CHANGED
@@ -1,15 +1,15 @@
1
- import { USERS, HTTP_STATUS } from '@verdaccio/commons-api';
1
+ import { USERS, HTTP_STATUS } from '@verdaccio/core';
2
2
  import { Response } from 'express';
3
3
  import _ from 'lodash';
4
4
  import buildDebug from 'debug';
5
5
 
6
- import { IStorageHandler } from '@verdaccio/store';
6
+ import { Storage } from '@verdaccio/store';
7
7
  import { $RequestExtend, $NextFunctionVer } from '../types/custom';
8
8
 
9
9
  const debug = buildDebug('verdaccio:api:publish:star');
10
10
 
11
11
  export default function (
12
- storage: IStorageHandler
12
+ storage: Storage
13
13
  ): (req: $RequestExtend, res: Response, next: $NextFunctionVer) => void {
14
14
  const validateInputs = (newUsers, localUsers, username, isStar): boolean => {
15
15
  const isExistlocalUsers = _.isNil(localUsers[username]) === false;
@@ -40,6 +40,7 @@ export default function (
40
40
  };
41
41
 
42
42
  debug('get package info package for %o', name);
43
+ // @ts-ignore
43
44
  storage.getPackage({
44
45
  name,
45
46
  req,
package/src/stars.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  import _ from 'lodash';
2
2
  import { Response, Router } from 'express';
3
3
 
4
- import { USERS, HTTP_STATUS } from '@verdaccio/commons-api';
4
+ import { USERS, HTTP_STATUS } from '@verdaccio/core';
5
5
  import { Package } from '@verdaccio/types';
6
6
 
7
- import { IStorageHandler } from '@verdaccio/store';
7
+ import { Storage } from '@verdaccio/store';
8
8
  import { $RequestExtend, $NextFunctionVer } from '../types/custom';
9
9
 
10
10
  type Packages = Package[];
11
11
 
12
- export default function (route: Router, storage: IStorageHandler): void {
12
+ export default function (route: Router, storage: Storage): void {
13
13
  route.get(
14
14
  '/-/_view/starredByUser',
15
15
  (req: $RequestExtend, res: Response, next: $NextFunctionVer): void => {
package/src/user.ts CHANGED
@@ -2,14 +2,14 @@ import _ from 'lodash';
2
2
  import { Response, Router } from 'express';
3
3
  import buildDebug from 'debug';
4
4
 
5
- import { getAuthenticatedMessage, validatePassword, ErrorCode } from '@verdaccio/utils';
5
+ import { getAuthenticatedMessage, validatePassword } from '@verdaccio/utils';
6
6
  import { getApiToken } from '@verdaccio/auth';
7
7
  import { logger } from '@verdaccio/logger';
8
8
  import { createRemoteUser } from '@verdaccio/config';
9
9
 
10
10
  import { Config, RemoteUser } from '@verdaccio/types';
11
11
  import { IAuth } from '@verdaccio/auth';
12
- import { API_ERROR, API_MESSAGE, HTTP_STATUS } from '@verdaccio/commons-api';
12
+ import { API_ERROR, API_MESSAGE, HTTP_STATUS, errorUtils } from '@verdaccio/core';
13
13
  import { $RequestExtend, $NextFunctionVer } from '../types/custom';
14
14
 
15
15
  const debug = buildDebug('verdaccio:api:user');
@@ -47,7 +47,7 @@ export default function (route: Router, auth: IAuth, config: Config): void {
47
47
  'authenticating for user @{username} failed. Error: @{err.message}'
48
48
  );
49
49
  return next(
50
- ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.BAD_USERNAME_PASSWORD)
50
+ errorUtils.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.BAD_USERNAME_PASSWORD)
51
51
  );
52
52
  }
53
53
 
@@ -55,7 +55,7 @@ export default function (route: Router, auth: IAuth, config: Config): void {
55
55
  const token = await getApiToken(auth, config, restoredRemoteUser, password);
56
56
  debug('login: new token');
57
57
  if (!token) {
58
- return next(ErrorCode.getUnauthorized());
58
+ return next(errorUtils.getUnauthorized());
59
59
  }
60
60
 
61
61
  res.status(HTTP_STATUS.CREATED);
@@ -73,7 +73,7 @@ export default function (route: Router, auth: IAuth, config: Config): void {
73
73
  if (validatePassword(password) === false) {
74
74
  debug('adduser: invalid password');
75
75
  // eslint-disable-next-line new-cap
76
- return next(ErrorCode.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.PASSWORD_SHORT()));
76
+ return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.PASSWORD_SHORT()));
77
77
  }
78
78
 
79
79
  auth.add_user(name, password, async function (err, user): Promise<void> {
@@ -84,7 +84,7 @@ export default function (route: Router, auth: IAuth, config: Config): void {
84
84
  // and npm accepts only an 409 error.
85
85
  // So, changing status code here.
86
86
  return next(
87
- ErrorCode.getCode(err.status, err.message) || ErrorCode.getConflict(err.message)
87
+ errorUtils.getCode(err.status, err.message) || errorUtils.getConflict(err.message)
88
88
  );
89
89
  }
90
90
  return next(err);
@@ -94,7 +94,7 @@ export default function (route: Router, auth: IAuth, config: Config): void {
94
94
  name && password ? await getApiToken(auth, config, user, password) : undefined;
95
95
  debug('adduser: new token %o', token);
96
96
  if (!token) {
97
- return next(ErrorCode.getUnauthorized());
97
+ return next(errorUtils.getUnauthorized());
98
98
  }
99
99
 
100
100
  req.remote_user = user;
package/src/v1/profile.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import _ from 'lodash';
2
2
  import { Response, Router } from 'express';
3
3
 
4
- import { API_ERROR, APP_ERROR, HTTP_STATUS, SUPPORT_ERRORS } from '@verdaccio/commons-api';
5
- import { ErrorCode, validatePassword } from '@verdaccio/utils';
4
+ import { API_ERROR, APP_ERROR, HTTP_STATUS, SUPPORT_ERRORS, errorUtils } from '@verdaccio/core';
5
+ import { validatePassword } from '@verdaccio/utils';
6
6
  import { IAuth } from '@verdaccio/auth';
7
7
  import { $RequestExtend, $NextFunctionVer } from '../../types/custom';
8
8
 
@@ -61,7 +61,7 @@ export default function (route: Router, auth: IAuth): void {
61
61
  if (_.isNil(password) === false) {
62
62
  if (validatePassword(password.new) === false) {
63
63
  /* eslint new-cap:off */
64
- return next(ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.PASSWORD_SHORT()));
64
+ return next(errorUtils.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.PASSWORD_SHORT()));
65
65
  /* eslint new-cap:off */
66
66
  }
67
67
 
@@ -72,22 +72,22 @@ export default function (route: Router, auth: IAuth): void {
72
72
  (err, isUpdated): $NextFunctionVer => {
73
73
  if (_.isNull(err) === false) {
74
74
  return next(
75
- ErrorCode.getCode(err.status, err.message) || ErrorCode.getConflict(err.message)
75
+ errorUtils.getCode(err.status, err.message) || errorUtils.getConflict(err.message)
76
76
  );
77
77
  }
78
78
 
79
79
  if (isUpdated) {
80
80
  return next(buildProfile(req.remote_user.name));
81
81
  }
82
- return next(ErrorCode.getInternalError(API_ERROR.INTERNAL_SERVER_ERROR));
82
+ return next(errorUtils.getInternalError(API_ERROR.INTERNAL_SERVER_ERROR));
83
83
  }
84
84
  );
85
85
  } else if (_.isNil(tfa) === false) {
86
86
  return next(
87
- ErrorCode.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.TFA_DISABLED)
87
+ errorUtils.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.TFA_DISABLED)
88
88
  );
89
89
  } else {
90
- return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, APP_ERROR.PROFILE_ERROR));
90
+ return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, APP_ERROR.PROFILE_ERROR));
91
91
  }
92
92
  }
93
93
  );
package/src/v1/search.ts CHANGED
@@ -1,106 +1,81 @@
1
- import semver from 'semver';
1
+ import _ from 'lodash';
2
+ import buildDebug from 'debug';
3
+ import { logger } from '@verdaccio/logger';
4
+ import { IAuth } from '@verdaccio/auth';
5
+ import { HTTP_STATUS, searchUtils } from '@verdaccio/core';
6
+ import { Storage } from '@verdaccio/store';
2
7
  import { Package } from '@verdaccio/types';
3
8
 
4
- function compileTextSearch(textSearch: string): (pkg: Package) => boolean {
5
- const personMatch = (person, search) => {
6
- if (typeof person === 'string') {
7
- return person.includes(search);
8
- }
9
+ const debug = buildDebug('verdaccio:api:search');
9
10
 
10
- if (typeof person === 'object') {
11
- for (const field of Object.values(person)) {
12
- if (typeof field === 'string' && field.includes(search)) {
13
- return true;
11
+ /**
12
+ * Endpoint for npm search v1
13
+ * Empty value
14
+ * - {"objects":[],"total":0,"time":"Sun Jul 25 2021 14:09:11 GMT+0000 (Coordinated Universal Time)"}
15
+ * req: 'GET /-/v1/search?text=react&size=20&frpom=0&quality=0.65&popularity=0.98&maintenance=0.5'
16
+ */
17
+ export default function (route, auth: IAuth, storage: Storage): void {
18
+ function checkAccess(pkg: any, auth: any, remoteUser): Promise<Package | null> {
19
+ return new Promise((resolve, reject) => {
20
+ auth.allow_access({ packageName: pkg?.package?.name }, remoteUser, function (err, allowed) {
21
+ if (err) {
22
+ if (err.status && String(err.status).match(/^4\d\d$/)) {
23
+ // auth plugin returns 4xx user error,
24
+ // that's equivalent of !allowed basically
25
+ allowed = false;
26
+ return resolve(null);
27
+ } else {
28
+ reject(err);
29
+ }
30
+ } else {
31
+ return resolve(allowed ? pkg : null);
14
32
  }
15
- }
16
- }
17
-
18
- return false;
19
- };
20
- const matcher = function (q) {
21
- const match = q.match(/author:(.*)/);
22
- if (match !== null) {
23
- return (pkg) => personMatch(pkg.author, match[1]);
24
- }
25
-
26
- // TODO: maintainer, keywords, not/is unstable insecure, boost-exact
27
- // TODO implement some scoring system for freetext
28
- return (pkg) => {
29
- return ['name', 'displayName', 'description']
30
- .map((k) => pkg[k])
31
- .filter((x) => x !== undefined)
32
- .some((txt) => txt.includes(q));
33
- };
34
- };
35
-
36
- const textMatchers = (textSearch || '').split(' ').map(matcher);
37
- return (pkg) => textMatchers.every((m) => m(pkg));
38
- }
33
+ });
34
+ });
35
+ }
39
36
 
40
- export default function (route, auth, storage): void {
41
- route.get('/-/v1/search', (req, res) => {
42
- // TODO: implement proper result scoring weighted by quality, popularity and
43
- // maintenance query parameters
44
- let [text, size, from] = [
45
- 'text',
46
- 'size',
47
- 'from' /* , 'quality', 'popularity', 'maintenance' */,
48
- ].map((k) => req.query[k]);
37
+ route.get('/-/v1/search', async (req, res, next) => {
38
+ const { query, url } = req;
39
+ let [size, from] = ['size', 'from'].map((k) => query[k]);
40
+ let data;
41
+ const abort = new AbortController();
49
42
 
50
- size = parseInt(size) || 20;
51
- from = parseInt(from) || 0;
43
+ req.on('aborted', () => {
44
+ abort.abort();
45
+ });
52
46
 
53
- const isInteresting = compileTextSearch(text);
47
+ size = parseInt(size, 10) || 20;
48
+ from = parseInt(from, 10) || 0;
54
49
 
55
- const resultStream = storage.search(0, { req: { query: { local: true } } });
56
- const resultBuf = [] as any;
57
- let completed = false;
50
+ try {
51
+ data = await storage.searchManager?.search({
52
+ query,
53
+ url,
54
+ abort,
55
+ });
56
+ debug('stream finish');
57
+ const checkAccessPromises: searchUtils.SearchItemPkg[] = await Promise.all(
58
+ data.map((pkgItem) => {
59
+ return checkAccess(pkgItem, auth, req.remote_user);
60
+ })
61
+ );
58
62
 
59
- const sendResponse = (): void => {
60
- completed = true;
61
- resultStream.destroy();
63
+ const final: searchUtils.SearchItemPkg[] = checkAccessPromises
64
+ .filter((i) => !_.isNull(i))
65
+ .slice(from, size);
66
+ logger.debug(`search results ${final?.length}`);
62
67
 
63
- const final = resultBuf.slice(from, size).map((pkg) => {
64
- return {
65
- package: pkg,
66
- flags: {
67
- unstable: Object.keys(pkg.versions).some((v) => semver.satisfies(v, '^1.0.0'))
68
- ? undefined
69
- : true,
70
- },
71
- score: {
72
- final: 1,
73
- detail: {
74
- quality: 1,
75
- popularity: 1,
76
- maintenance: 0,
77
- },
78
- },
79
- searchScore: 100000,
80
- };
81
- });
82
- const response = {
68
+ const response: searchUtils.SearchResults = {
83
69
  objects: final,
84
70
  total: final.length,
85
71
  time: new Date().toUTCString(),
86
72
  };
87
73
 
88
- res.status(200).json(response);
89
- };
90
-
91
- resultStream.on('data', (pkg) => {
92
- if (!isInteresting(pkg)) {
93
- return;
94
- }
95
- resultBuf.push(pkg);
96
- if (!completed && resultBuf.length >= size + from) {
97
- sendResponse();
98
- }
99
- });
100
- resultStream.on('end', () => {
101
- if (!completed) {
102
- sendResponse();
103
- }
104
- });
74
+ res.status(HTTP_STATUS.OK).json(response);
75
+ } catch (error) {
76
+ logger.error({ error }, 'search endpoint has failed @{error.message}');
77
+ next(next);
78
+ return;
79
+ }
105
80
  });
106
81
  }