From 75c0e1e4ec976baac8af08cc97d5cc23caf9bfd5 Mon Sep 17 00:00:00 2001 From: vip30 Date: Mon, 11 Mar 2019 23:37:17 +0800 Subject: [PATCH] feat: add stars api --- README.md | 2 +- src/api/endpoint/api/stars.js | 32 ++++++++++++++++++++++++++++ src/api/endpoint/index.js | 2 ++ src/lib/storage.js | 4 +++- test/unit/api/api.spec.js | 39 +++++++++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 src/api/endpoint/api/stars.js diff --git a/README.md b/README.md index c94e0af72..81f660a4d 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ Verdaccio aims to support all features of a standard npm client that make sense - Searching (npm search) - **supported** (cli / browser) - Ping (npm ping) - **supported** -- Starring (npm star, npm unstar) - not supported, *PR-welcome* +- Starring (npm star, npm unstar, npm stars) - **supported** ### Security diff --git a/src/api/endpoint/api/stars.js b/src/api/endpoint/api/stars.js new file mode 100644 index 000000000..899408f84 --- /dev/null +++ b/src/api/endpoint/api/stars.js @@ -0,0 +1,32 @@ +/** + * @prettier + * @flow + */ + +import { USERS, HTTP_STATUS } from '../../../lib/constants'; +import type { $Response, Router } from 'express'; +import type { $RequestExtend, $NextFunctionVer, IStorageHandler } from '../../../../types'; +import type { Package } from '@verdaccio/types'; + +export default function(route: Router, storage: IStorageHandler) { + route.get( + '/-/_view/starredByUser', + (req: $RequestExtend, res: $Response, next: $NextFunctionVer): void => { + const remoteUsername = req.remote_user.name; + storage.getLocalDatabase((err, localPackages: Package[]) => { + if (err) { + return next(err); + } + const filteredPackages = localPackages.filter( + localPackage => (localPackage[USERS] ? Object.keys(localPackage[USERS]).indexOf(remoteUsername) >= 0 : false) + ); + res.status(HTTP_STATUS.OK); + next({ + rows: filteredPackages.map(filteredPackage => ({ + value: filteredPackage.name, + })), + }); + }); + } + ); +} diff --git a/src/api/endpoint/index.js b/src/api/endpoint/index.js index 94e99e287..e236d2234 100644 --- a/src/api/endpoint/index.js +++ b/src/api/endpoint/index.js @@ -15,6 +15,7 @@ import distTags from './api/dist-tags'; import publish from './api/publish'; import search from './api/search'; import pkg from './api/package'; +import stars from './api/stars'; import profile from './api/v1/profile'; const { match, validateName, validatePackage, encodeScopePackage, antiLoop } = require('../middleware'); @@ -55,6 +56,7 @@ export default function(config: Config, auth: IAuth, storage: IStorageHandler) { distTags(app, auth, storage); publish(app, auth, storage, config); ping(app); + stars(app, storage); return app; } diff --git a/src/lib/storage.js b/src/lib/storage.js index 0aa4861bc..fdd7a5b00 100644 --- a/src/lib/storage.js +++ b/src/lib/storage.js @@ -375,12 +375,14 @@ class Storage implements IStorageHandler { self.localStorage.getPackageMetadata(locals[itemPkg], function(err, info) { if (_.isNil(err)) { const latest = info[DIST_TAGS].latest; - if (latest && info.versions[latest]) { const version = info.versions[latest]; const time = info.time[latest]; version.time = time; + // Add for stars api + version.users = info.users; + packages.push(version); } else { self.logger.warn({ package: locals[itemPkg] }, 'package @{package} does not have a "latest" tag?'); diff --git a/test/unit/api/api.spec.js b/test/unit/api/api.spec.js index f71394f67..304fadfe8 100644 --- a/test/unit/api/api.spec.js +++ b/test/unit/api/api.spec.js @@ -602,6 +602,45 @@ describe('endpoint unit test', () => { }); }); }); + + test('should retrieve stars list with credentials', async (done) => { + const credentials = { name: 'star_user', password: 'secretPass' }; + const token = await getNewToken(request(app), credentials); + request(app) + .put('/@scope%2fpk1-test') + .set('authorization', buildToken(TOKEN_BEARER, token)) + .set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON) + .send(JSON.stringify({ + ...starMetadata, + users: { + [credentials.name]: true + } + })) + .expect(HTTP_STATUS.OK).end(function(err, res) { + if (err) { + expect(err).toBeNull(); + return done(err); + } + request(app) + .get('/-/_view/starredByUser') + .set('authorization', buildToken(TOKEN_BEARER, token)) + .set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON) + .send(JSON.stringify({ + key: [credentials.name] + })) + .expect(HTTP_STATUS.OK) + .end(function(err, res) { + if (err) { + expect(err).toBeNull(); + return done(err); + } + expect(res.body.rows).toBeDefined(); + expect(res.body.rows.length).toBeGreaterThan(0); + done(); + }); + }); + }); + test('should unpublish a new package with credentials', async (done) => { const credentials = { name: 'jota_unpublish', password: 'secretPass' };