From 1ab7c504ec57fde660ef33f7dfc8feb0264da510 Mon Sep 17 00:00:00 2001 From: vip30 Date: Sat, 9 Mar 2019 11:16:44 +0800 Subject: [PATCH] feat: add star and unstar api for 4.x --- src/api/endpoint/api/publish.js | 4 ++- src/api/endpoint/api/star.js | 64 +++++++++++++++++++++++++++++++++ src/lib/constants.js | 1 + src/lib/local-storage.js | 3 +- src/lib/storage-utils.js | 5 +-- test/unit/api/api.spec.js | 41 +++++++++++++++++++++ test/unit/partials/star-api.js | 7 ++++ 7 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 src/api/endpoint/api/star.js create mode 100644 test/unit/partials/star-api.js diff --git a/src/api/endpoint/api/publish.js b/src/api/endpoint/api/publish.js index 92a49fb23..0348a8cf7 100644 --- a/src/api/endpoint/api/publish.js +++ b/src/api/endpoint/api/publish.js @@ -11,6 +11,7 @@ import { API_MESSAGE, HEADERS, DIST_TAGS, API_ERROR, HTTP_STATUS } from '../../. import { validateMetadata, isObject, ErrorCode } from '../../../lib/utils'; import { media, expectJson, allow } from '../../middleware'; import { notify } from '../../../lib/notify'; +import star from './star'; import type { Router } from 'express'; import type { Config, Callback, MergeTags, Version } from '@verdaccio/types'; @@ -40,6 +41,7 @@ export default function publish(router: Router, auth: IAuth, storage: IStorageHa * Publish a package */ export function publishPackage(storage: IStorageHandler, config: Config) { + const starApi = star(storage); return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) { const packageName = req.params.package; /** @@ -138,7 +140,7 @@ export function publishPackage(storage: IStorageHandler, config: Config) { }; if (Object.prototype.hasOwnProperty.call(req.body, '_rev') && isObject(req.body.users)) { - return next(ErrorCode.getNotFound('npm star| un-star calls are not implemented')); + return starApi(req, res, next); } try { diff --git a/src/api/endpoint/api/star.js b/src/api/endpoint/api/star.js new file mode 100644 index 000000000..5cc638d4b --- /dev/null +++ b/src/api/endpoint/api/star.js @@ -0,0 +1,64 @@ +// @flow + +import { USERS, HTTP_STATUS } from '../../../lib/constants'; +import type {$Response} from 'express'; +import type {$RequestExtend, $NextFunctionVer, IStorageHandler} from '../../../../types'; +import _ from 'lodash'; + +export default function(storage: IStorageHandler) { + const validateInputs = (newUsers, localUsers, username, isStar) => { + const isExistlocalUsers = _.isNil(localUsers[username]) === false; + if (isStar && isExistlocalUsers && localUsers[username]) { + return true; + } else if (!isStar && isExistlocalUsers) { + return false; + } else if (!isStar && !isExistlocalUsers) { + return true; + } else { + return false; + } + }; + + return (req: $RequestExtend, res: $Response, next: $NextFunctionVer): void => { + const name = req.params.package; + const afterChangePackage = function(err) { + if (err) { + return next(err); + } + res.status(HTTP_STATUS.OK); + next({ + success: true, + }); + }; + + storage.getPackage({ + name, + req, + callback: function(err, info) { + if (err) { + return next(err); + } + const newStarUser = req.body[USERS]; + const remoteUsername = req.remote_user.name; + const localStarUsers = info[USERS]; + // Check is star or unstar + const isStar = Object.keys(newStarUser).includes(remoteUsername); + if (_.isNil(localStarUsers) === false && validateInputs(newStarUser, localStarUsers, remoteUsername, isStar)) { + return afterChangePackage(); + } + const users = isStar ? { + ...localStarUsers, + [remoteUsername]: true, + } : _.reduce(localStarUsers, (users, value, key) => { + if (key !== remoteUsername) { + users[key] = value; + } + return users; + }, {}); + storage.changePackage(name, { ...info, users}, req.body._rev, function(err) { + afterChangePackage(err); + }); + }, + }); + }; +} diff --git a/src/lib/constants.js b/src/lib/constants.js index 21a5ea670..6bb3ed904 100644 --- a/src/lib/constants.js +++ b/src/lib/constants.js @@ -10,6 +10,7 @@ export const DEFAULT_DOMAIN: string = 'localhost'; export const TIME_EXPIRATION_24H: string = '24h'; export const TIME_EXPIRATION_7D: string = '7d'; export const DIST_TAGS = 'dist-tags'; +export const USERS = 'users'; export const DEFAULT_MIN_LIMIT_PASSWORD: number = 3; export const DEFAULT_USER = 'Anonymous'; diff --git a/src/lib/local-storage.js b/src/lib/local-storage.js index e50baa900..16fcceed2 100644 --- a/src/lib/local-storage.js +++ b/src/lib/local-storage.js @@ -8,7 +8,7 @@ import UrlNode from 'url'; import _ from 'lodash'; import { ErrorCode, isObject, getLatestVersion, tagVersion, validateName } from './utils'; import { generatePackageTemplate, normalizePackage, generateRevision, getLatestReadme, cleanUpReadme, normalizeContributors } from './storage-utils'; -import { API_ERROR, DIST_TAGS, STORAGE } from './constants'; +import { API_ERROR, DIST_TAGS, STORAGE, USERS } from './constants'; import { createTarballHash } from './crypto-utils'; import { prepareSearchPackage } from './storage-utils'; import loadPlugin from '../lib/plugin-loader'; @@ -334,6 +334,7 @@ class LocalStorage implements IStorage { } } + localData[USERS] = incomingPkg[USERS]; localData[DIST_TAGS] = incomingPkg[DIST_TAGS]; cb(); }, diff --git a/src/lib/storage-utils.js b/src/lib/storage-utils.js index 3676078e1..e137ac3ca 100644 --- a/src/lib/storage-utils.js +++ b/src/lib/storage-utils.js @@ -10,7 +10,7 @@ import { generateRandomHexString } from '../lib/crypto-utils'; import type { Package, Version, Author } from '@verdaccio/types'; import type { IStorage } from '../../types'; -import { API_ERROR, HTTP_STATUS, DIST_TAGS, STORAGE } from './constants'; +import { API_ERROR, HTTP_STATUS, DIST_TAGS, USERS, STORAGE } from './constants'; export function generatePackageTemplate(name: string): Package { return { @@ -18,6 +18,7 @@ export function generatePackageTemplate(name: string): Package { name, versions: {}, time: {}, + [USERS]: {}, [DIST_TAGS]: {}, _uplinks: {}, _distfiles: {}, @@ -97,7 +98,7 @@ export function normalizeContributors(contributors: Array): Array { }); }); + describe('should test star api', () => { + test('should star a package', (done) => { + request(app) + .put('/@scope%2fpk1-test') + .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); + } + expect(res.body.success).toBeDefined(); + expect(res.body.success).toBeTruthy(); + done(); + }); + }); + + test('should unstar a package', (done) => { + request(app) + .put('/@scope%2fpk1-test') + .set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON) + .send(JSON.stringify(starMetadata)) + .expect(HTTP_STATUS.OK) + .end(function(err, res) { + if (err) { + expect(err).toBeNull(); + return done(err); + } + expect(res.body.success).toBeDefined(); + expect(res.body.success).toBeTruthy(); + done(); + }); + }); + }); test('should unpublish a new package with credentials', async (done) => { const credentials = { name: 'jota_unpublish', password: 'secretPass' }; diff --git a/test/unit/partials/star-api.js b/test/unit/partials/star-api.js new file mode 100644 index 000000000..ee206b9ef --- /dev/null +++ b/test/unit/partials/star-api.js @@ -0,0 +1,7 @@ +const json = { + "_id": "@scope\/pk1-test", + "_rev": "4-6abcdb4efd41a576", + "users": {} +} + + module.exports = json; \ No newline at end of file