cnpmjs.org
Version:
Private npm registry and web for Enterprise, base on MySQL and Simple Store Service
713 lines (622 loc) • 16.6 kB
JavaScript
/**!
* cnpmjs.org - services/package.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var models = require('../models');
var common = require('./common');
var Tag = models.Tag;
var User = models.User;
var Module = models.Module;
var ModuleStar = models.ModuleStar;
var ModuleKeyword = models.ModuleKeyword;
var PrivateModuleMaintainer = models.ModuleMaintainer;
var ModuleDependency = models.ModuleDependency;
var ModuleUnpublished = models.ModuleUnpublished;
var NpmModuleMaintainer = models.NpmModuleMaintainer;
// module
// module:read
function parseRow(row) {
if (row && row.package) {
try {
if (row.package.indexOf('%7B%22') === 0) {
// now store package will encodeURIComponent() after JSON.stringify
row.package = decodeURIComponent(row.package);
}
row.package = JSON.parse(row.package);
} catch (e) {
console.warn('parse package error: %s, id: %s version: %s, error: %s', row.name, row.id, row.version, e);
}
}
}
exports.parseRow = parseRow;
function stringifyPackage(pkg) {
return encodeURIComponent(JSON.stringify(pkg));
}
exports.getModuleById = function* (id) {
var row = yield Module.find(Number(id));
parseRow(row);
return row;
};
exports.getModule = function* (name, version) {
var row = yield* Module.findByNameAndVersion(name, version);
parseRow(row);
return row;
};
exports.getModuleByTag = function* (name, tag) {
var tag = yield* Tag.findByNameAndTag(name, tag);
if (!tag) {
return null;
}
return yield* exports.getModule(tag.name, tag.version);
};
exports.getLatestModule = function* (name) {
return yield* exports.getModuleByTag(name, 'latest');
};
// module:list
exports.listPrivateModulesByScope = function* (scope) {
var tags = yield Tag.findAll({
where: {
tag: 'latest',
name: {
like: scope + '/%'
}
}
});
if (tags.length === 0) {
return [];
}
var ids = tags.map(function (tag) {
return tag.module_id;
});
return yield Module.findAll({
where: {
id: ids
}
});
};
exports.listModules = function* (names) {
if (names.length === 0) {
return [];
}
// fetch latest module tags
var tags = yield Tag.findAll({
where: {
name: names,
tag: 'latest'
}
});
if (tags.length === 0) {
return [];
}
var ids = tags.map(function (tag) {
return tag.module_id;
});
var rows = yield Module.findAll({
where: {
id: ids
},
attributes: [
'name', 'description'
]
});
return rows;
};
exports.listModulesByUser = function* (username) {
var names = yield* exports.listModuleNamesByUser(username);
return yield* exports.listModules(names);
};
exports.listModuleNamesByUser = function* (username) {
var sql = 'SELECT distinct(name) AS name FROM module WHERE author=?;';
var rows = yield* models.query(sql, [username]);
var map = {};
var names = rows.map(function (r) {
return r.name;
});
// find from npm module maintainer table
var moduleNames = yield* NpmModuleMaintainer.listModuleNamesByUser(username);
moduleNames.forEach(function (name) {
if (!map[name]) {
names.push(name);
}
});
// find from private module maintainer table
moduleNames = yield* PrivateModuleMaintainer.listModuleNamesByUser(username);
moduleNames.forEach(function (name) {
if (!map[name]) {
names.push(name);
}
});
return names;
};
exports.listPublicModulesByUser = function* (username) {
var names = yield* exports.listPublicModuleNamesByUser(username);
return yield* exports.listModules(names);
};
// return user all public package names
exports.listPublicModuleNamesByUser = function* (username) {
var sql = 'SELECT distinct(name) AS name FROM module WHERE author=?;';
var rows = yield* models.query(sql, [username]);
var map = {};
var names = rows.map(function (r) {
return r.name;
}).filter(function (name) {
var matched = name[0] !== '@';
if (matched) {
map[name] = 1;
}
return matched;
});
// find from npm module maintainer table
var moduleNames = yield* NpmModuleMaintainer.listModuleNamesByUser(username);
moduleNames.forEach(function (name) {
if (!map[name]) {
names.push(name);
}
});
return names;
};
// start must be a date or timestamp
exports.listPublicModuleNamesSince = function* (start) {
if (!(start instanceof Date)) {
start = new Date(Number(start));
}
var rows = yield Tag.findAll({
attributes: ['name'],
where: {
gmt_modified: {
gt: start
}
},
});
var names = {};
for (var i = 0; i < rows.length; i++) {
names[rows[i].name] = 1;
}
return Object.keys(names);
};
exports.listAllPublicModuleNames = function* () {
var sql = 'SELECT DISTINCT(name) AS name FROM tag ORDER BY name';
var rows = yield models.query(sql);
return rows.filter(function (row) {
return row.name[0] !== '@';
}).map(function (row) {
return row.name;
});
};
exports.listModulesByName = function* (moduleName) {
var mods = yield Module.findAll({
where: {
name: moduleName
},
order: [ ['id', 'DESC'] ]
});
return mods.map(function (mod) {
parseRow(mod);
return mod;
});
};
exports.getModuleLastModified = function* (name) {
var mod = yield Module.find({
where: {
name: name,
},
order: [
['gmt_modified', 'DESC']
],
attributes: [ 'gmt_modified' ]
});
return mod && mod.gmt_modified || null;
};
// module:update
exports.saveModule = function* (mod) {
var keywords = mod.package.keywords;
if (typeof keywords === 'string') {
keywords = [keywords];
}
var pkg = stringifyPackage(mod.package);
var description = mod.package && mod.package.description || '';
var dist = mod.package.dist || {};
// dist.tarball = '';
// dist.shasum = '';
// dist.size = 0;
var publish_time = mod.publish_time || Date.now();
var item = yield* Module.findByNameAndVersion(mod.name, mod.version);
if (!item) {
item = Module.build({
name: mod.name,
version: mod.version
});
}
item.publish_time = publish_time;
// meaning first maintainer, more maintainers please check module_maintainer table
item.author = mod.author;
item.package = pkg;
item.dist_tarball = dist.tarball;
item.dist_shasum = dist.shasum;
item.dist_size = dist.size;
item.description = description;
if (item.isDirty) {
item = yield item.save();
}
var result = {
id: item.id,
gmt_modified: item.gmt_modified
};
if (!Array.isArray(keywords)) {
return result;
}
var words = [];
for (var i = 0; i < keywords.length; i++) {
var w = keywords[i];
if (typeof w === 'string') {
w = w.trim();
if (w) {
words.push(w);
}
}
}
if (words.length > 0) {
// add keywords
yield* exports.addKeywords(mod.name, description, words);
}
return result;
};
exports.updateModulePackage = function* (id, pkg) {
var mod = yield Module.find(Number(id));
if (!mod) {
// not exists
return null;
}
mod.package = stringifyPackage(pkg);
return yield mod.save(['package']);
};
exports.updateModulePackageFields = function* (id, fields) {
var mod = yield* exports.getModuleById(id);
if (!mod) {
return null;
}
var pkg = mod.package || {};
for (var k in fields) {
pkg[k] = fields[k];
}
return yield* exports.updateModulePackage(id, pkg);
};
exports.updateModuleReadme = function* (id, readme) {
var mod = yield* exports.getModuleById(id);
if (!mod) {
return null;
}
var pkg = mod.package || {};
pkg.readme = readme;
return yield* exports.updateModulePackage(id, pkg);
};
exports.updateModuleDescription = function* (id, description) {
var mod = yield* exports.getModuleById(id);
if (!mod) {
return null;
}
mod.description = description;
// also need to update package.description
var pkg = mod.package || {};
pkg.description = description;
mod.package = stringifyPackage(pkg);
return yield mod.save(['description', 'package']);
};
exports.updateModuleLastModified = function* (name) {
var row = yield Module.find({
where: { name: name },
order: [ [ 'gmt_modified', 'DESC' ] ],
});
if (!row) {
return null;
}
row.gmt_modified = new Date();
return yield row.save(['gmt_modified']);
};
exports.removeModulesByName = function* (name) {
yield Module.destroy({
where: {
name: name
}
});
};
exports.removeModulesByNameAndVersions = function* (name, versions) {
yield Module.destroy({
where: {
name: name,
version: versions
}
});
};
// tags
exports.addModuleTag = function* (name, tag, version) {
var mod = yield* exports.getModule(name, version);
if (!mod) {
return null;
}
var row = yield* Tag.findByNameAndTag(name, tag);
if (!row) {
row = Tag.build({
name: name,
tag: tag
});
}
row.module_id = mod.id;
row.version = version;
if (row.isDirty) {
return yield row.save();
}
return row;
};
exports.getModuleTag = function* (name, tag) {
return yield Tag.findByNameAndTag(name, tag);
};
exports.removeModuleTags = function* (name) {
return yield Tag.destroy({where: {name: name}});
};
exports.removeModuleTagsByIds = function* (ids) {
return yield Tag.destroy({where: {id: ids}});
};
exports.removeModuleTagsByNames = function* (moduleName, tagNames) {
return yield Tag.destroy({
where: {
name: moduleName,
tag: tagNames
}
});
};
exports.listModuleTags = function* (name) {
return yield Tag.findAll({ where: { name: name } });
};
// dependencies
// name => dependency
exports.addDependency = function* (name, dependency) {
var row = yield ModuleDependency.find({
where: {
name: dependency,
dependent: name
}
});
if (row) {
return row;
}
return yield ModuleDependency.build({
name: dependency,
dependent: name
}).save();
};
exports.addDependencies = function* (name, dependencies) {
var tasks = [];
for (var i = 0; i < dependencies.length; i++) {
tasks.push(exports.addDependency(name, dependencies[i]));
}
return yield tasks;
};
exports.listDependents = function* (dependency) {
var items = yield ModuleDependency.findAll({
where: {
name: dependency
}
});
return items.map(function (item) {
return item.dependent;
});
};
// maintainers
exports.listPublicModuleMaintainers = function* (name) {
return yield* NpmModuleMaintainer.listMaintainers(name);
};
exports.addPublicModuleMaintainer = function* (name, user) {
return yield* NpmModuleMaintainer.addMaintainer(name, user);
};
exports.removePublicModuleMaintainer = function* (name, user) {
return yield* NpmModuleMaintainer.removeMaintainers(name, user);
};
// only can add to cnpm maintainer table
exports.addPrivateModuleMaintainers = function* (name, usernames) {
return yield* PrivateModuleMaintainer.addMaintainers(name, usernames);
};
exports.updatePrivateModuleMaintainers = function* (name, usernames) {
var result = yield* PrivateModuleMaintainer.updateMaintainers(name, usernames);
if (result.add.length > 0 || result.remove.length > 0) {
yield* exports.updateModuleLastModified(name);
}
return result;
};
function* getMaintainerModel(name) {
var isPrivatePackage = yield* common.isPrivatePackage(name);
return isPrivatePackage ? PrivateModuleMaintainer : NpmModuleMaintainer;
}
exports.listMaintainers = function* (name) {
var mod = yield* getMaintainerModel(name);
var usernames = yield* mod.listMaintainers(name);
if (usernames.length === 0) {
return usernames;
}
var users = yield* User.listByNames(usernames);
return users.map(function (user) {
return {
name: user.name,
email: user.email
};
});
};
exports.listMaintainerNamesOnly = function* (name) {
var mod = yield* getMaintainerModel(name);
return yield* mod.listMaintainers(name);
};
exports.removeAllMaintainers = function* (name) {
return yield [
PrivateModuleMaintainer.removeAllMaintainers(name),
NpmModuleMaintainer.removeAllMaintainers(name),
];
};
exports.authMaintainer = function* (packageName, username) {
var mod = yield* getMaintainerModel(packageName);
var rs = yield [
mod.listMaintainers(packageName),
exports.getLatestModule(packageName)
];
var maintainers = rs[0];
var latestMod = rs[1];
if (maintainers.length === 0) {
// if not found maintainers, try to get from latest module package info
var ms = latestMod && latestMod.package && latestMod.package.maintainers;
if (ms && ms.length > 0) {
maintainers = ms.map(function (user) {
return user.name;
});
}
}
var isMaintainer = false;
if (latestMod && !latestMod.package._publish_on_cnpm) {
// no one can update public package maintainers
// public package only sync from source npm registry
isMaintainer = false;
} else if (maintainers.length === 0) {
// no maintainers, meaning this module is free for everyone
isMaintainer = true;
} else if (maintainers.indexOf(username) >= 0) {
isMaintainer = true;
}
return {
isMaintainer: isMaintainer,
maintainers: maintainers
};
};
exports.isMaintainer = function* (name, username) {
var result = yield* exports.authMaintainer(name, username);
return result.isMaintainer;
};
// module keywords
exports.addKeyword = function* (data) {
var item = yield ModuleKeyword.findByKeywordAndName(data.keyword, data.name);
if (!item) {
item = ModuleKeyword.build(data);
}
item.description = data.description;
if (item.isDirty) {
// make sure object will change, otherwise will cause empty sql error
// @see https://github.com/cnpm/cnpmjs.org/issues/533
return yield item.save();
}
return item;
};
exports.addKeywords = function* (name, description, keywords) {
var tasks = [];
keywords.forEach(function (keyword) {
tasks.push(exports.addKeyword({
name: name,
keyword: keyword,
description: description
}));
});
return yield tasks;
};
// search
exports.search = function* (word, options) {
options = options || {};
var limit = options.limit || 100;
word = word.replace(/^%/, ''); //ignore prefix %
// search flows:
// 1. prefix search by name
// 2. like search by name
// 3. keyword equal search
var ids = {};
var sql = 'SELECT module_id FROM tag WHERE LOWER(name) LIKE LOWER(?) AND tag="latest" \
ORDER BY name LIMIT ?;';
var rows = yield* models.query(sql, [word + '%', limit ]);
for (var i = 0; i < rows.length; i++) {
ids[rows[i].module_id] = 1;
}
if (rows.length < 20) {
rows = yield* models.query(sql, [ '%' + word + '%', limit ]);
for (var i = 0; i < rows.length; i++) {
ids[rows[i].module_id] = 1;
}
}
var keywordRows = yield ModuleKeyword.findAll({
attributes: [ 'name', 'description' ],
where: {
keyword: word
},
limit: limit,
order: [ [ 'id', 'DESC' ] ]
});
var data = {
keywordMatchs: keywordRows,
searchMatchs: []
};
ids = Object.keys(ids);
if (ids.length > 0) {
data.searchMatchs = yield Module.findAll({
attributes: [ 'name', 'description' ],
where: {
id: ids
},
order: 'name'
});
}
return data;
};
// module star
exports.addStar = function* add(name, user) {
var row = yield ModuleStar.find({
where: {
name: name,
user: user
}
});
if (row) {
return row;
}
row = ModuleStar.build({
name: name,
user: user
});
return yield row.save();
};
exports.removeStar = function* (name, user) {
return yield ModuleStar.destroy({
where: {
name: name,
user: user
}
});
};
exports.listStarUserNames = function* (moduleName) {
var rows = yield ModuleStar.findAll({
where: {
name: moduleName
}
});
return rows.map(function (row) {
return row.user;
});
};
exports.listUserStarModuleNames = function* (user) {
var rows = yield ModuleStar.findAll({
where: {
user: user
}
});
return rows.map(function (row) {
return row.name;
});
};
// unpublish info
exports.saveUnpublishedModule = function* (name, pkg) {
return yield* ModuleUnpublished.save(name, pkg);
};
exports.getUnpublishedModule = function* (name) {
return yield* ModuleUnpublished.findByName(name);
};