diff --git a/package.json b/package.json index 340113a2a..573b0aa2d 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "@material-ui/core": "3.9.0", "@material-ui/icons": "3.0.2", "@verdaccio/babel-preset": "0.0.4", - "@verdaccio/types": "4.1.4", + "@verdaccio/types": "5.0.0-beta.3", "autosuggest-highlight": "3.1.1", "bundlesize": "0.17.1", "codecov": "3.2.0", @@ -178,7 +178,8 @@ "preferGlobal": true, "husky": { "hooks": { - "pre-commit": "lint-staged && commitlint -e $GIT_PARAMS" + "pre-commit": "lint-staged", + "commit-msg": "commitlint -e $GIT_PARAMS" } }, "lint-staged": { 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 { expect(next).toHaveBeenCalledWith(new Error(API_ERROR.BAD_PACKAGE_DATA)); }); - test('should throw an error for un-implemented star calls', () => { - const storage = {}; - req.body._rev = REVISION_MOCK; - req.body.users = {}; + test('should star a package', () => { + const storage = { + changePackage: jest.fn(), + getPackage({ name, req, callback }) { + callback(null, { + users: {}, + }); + }, + }; + req = { + params: { + package: 'verdaccio', + }, + body: { + _rev: REVISION_MOCK, + users: { + verdaccio: true, + }, + }, + remote_user: { + name: 'verdaccio', + }, + }; publishPackage(storage)(req, res, next); - expect(next).toHaveBeenCalledWith(new Error('npm star| un-star calls are not implemented')); + expect(storage.changePackage).toMatchSnapshot(); }); }); diff --git a/test/unit/api/api.spec.js b/test/unit/api/api.spec.js index a047a5aa3..f71394f67 100644 --- a/test/unit/api/api.spec.js +++ b/test/unit/api/api.spec.js @@ -5,6 +5,7 @@ import rimraf from 'rimraf'; import configDefault from '../partials/config/index'; import publishMetadata from '../partials/publish-api'; +import starMetadata from '../partials/star-api'; import endPointAPI from '../../../src/api/index'; import {HEADERS, API_ERROR, HTTP_STATUS, HEADER_TYPE, API_MESSAGE, TOKEN_BEARER} from '../../../src/lib/constants'; @@ -561,6 +562,46 @@ describe('endpoint unit test', () => { }); }); + 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 diff --git a/yarn.lock b/yarn.lock index d99de06c3..deb62ba02 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1372,10 +1372,10 @@ resolved "https://registry.verdaccio.org/@verdaccio%2fstreams/-/streams-2.0.0-beta.0.tgz#af8c7e673a3c368deacc8024c6f5671aa2ec32ac" integrity sha512-EdVF6RP0abRNT0RfgLCsqLNv7FOpm+BpzMZoaQuQGHSBQRj7OTM8ft5mpbJ40rYVXKv6D8xyU0vUnoRl09ah6g== -"@verdaccio/types@4.1.4": - version "4.1.4" - resolved "https://registry.verdaccio.org/@verdaccio%2ftypes/-/types-4.1.4.tgz#6144410b9fd63d916aa279378a4946c701a82586" - integrity sha512-0kNIQvMakoHIk1dpgnXVgQ5qwxJGTtMpJkLZKiN5WpLi5yuQtjc+kD/0EWDV4164pKg1KUU6YFLpbcXWxR8zvQ== +"@verdaccio/types@5.0.0-beta.3": + version "5.0.0-beta.3" + resolved "https://registry.npmjs.org/@verdaccio/types/-/types-5.0.0-beta.3.tgz#f6c9fd31a40b2be96fe080c49d7f4a1239413318" + integrity sha512-qPwE0bjDhXXVK7Gwg0O6RargAE7/AseVcZhcZEnf25RAra5ZL3MiM0dUUlierMc9XcPVjVHwTasAnF54Qmz9bg== "@webassemblyjs/ast@1.7.8": version "1.7.8"