Compare commits

..

3 Commits
egg ... 1.7.2

Author SHA1 Message Date
fengmk2
6ca71e1d8b Release 1.7.2 2014-10-31 14:28:30 +08:00
Yiyu He
ff811ab15e Merge pull request #487 from cnpm/min-sync-interval
feat(sync): add min sync interval time detect
2014-10-31 14:21:39 +08:00
fengmk2
ae1ca1bc32 feat(sync): add min sync interval time detect
Forbidden some guy write `config.syncInterval = 1` to let sync too fast.

Closes #486
2014-10-31 14:18:58 +08:00
258 changed files with 12586 additions and 17969 deletions

View File

@@ -1,35 +0,0 @@
'ues strict';
module.exports = {
write: true,
prefix: '^',
devprefix: '^',
registry: 'https://r.cnpmjs.org',
exclude: [
'test/fixtures',
'examples',
'docs',
'public',
],
dep: [
'mysql',
'egg',
'koa-router',
],
devdep: [
'autod',
'autod-egg',
'eslint',
'eslint-config-egg',
'egg-bin',
'egg-mock',
],
keep: [
],
semver: [
'changes-stream@1',
'nodemailer@1',
'koa-router@3',
'supertest@2',
],
};

View File

@@ -1,17 +0,0 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

View File

@@ -1,6 +0,0 @@
test/fixtures
test/mocks
examples/**/app/public
logs
run
public/

View File

@@ -1,3 +0,0 @@
{
"extends": "eslint-config-egg"
}

1
.gitignore vendored
View File

@@ -28,4 +28,3 @@ bin/test.sql
coverage/
config/web_readme.md
.tmp/
*.sqlite

4
.jshintignore Normal file
View File

@@ -0,0 +1,4 @@
node_modules/
coverage/
.tmp/
.git/

95
.jshintrc Normal file
View File

@@ -0,0 +1,95 @@
{
// JSHint Default Configuration File (as on JSHint website)
// See http://jshint.com/docs/ for more details
"maxerr" : 50, // {int} Maximum error before stopping
// Enforcing
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
"camelcase" : false, // true: Identifiers must be in camelCase
"curly" : true, // true: Require {} for every new block or scope
"eqeqeq" : true, // true: Require triple equals (===) for comparison
"forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty()
"immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
"indent" : false, // {int} Number of spaces to use for indentation
"latedef" : false, // true: Require variables/functions to be defined before being used
"newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()`
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
"noempty" : true, // true: Prohibit use of empty blocks
"nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
"plusplus" : false, // true: Prohibit use of `++` & `--`
"quotmark" : false, // Quotation mark consistency:
// false : do nothing (default)
// true : ensure whatever is used is consistent
// "single" : require single quotes
// "double" : require double quotes
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
"unused" : false, // true: Require all defined variables be used
"strict" : true, // true: Requires all functions run in ES5 Strict Mode
"trailing" : false, // true: Prohibit trailing whitespaces
"maxparams" : false, // {int} Max number of formal params allowed per function
"maxdepth" : false, // {int} Max depth of nested blocks (within functions)
"maxstatements" : false, // {int} Max number statements per function
"maxcomplexity" : false, // {int} Max cyclomatic complexity per function
"maxlen" : false, // {int} Max number of characters per line
// Relaxing
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
"boss" : true, // true: Tolerate assignments where comparisons would be expected
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
"eqnull" : false, // true: Tolerate use of `== null`
"es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
"esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`)
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
// (ex: `for each`, multiple try/catch, function expression…)
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
"expr" : true, // true: Tolerate `ExpressionStatement` as Programs
"funcscope" : false, // true: Tolerate defining variables inside control statements"
"globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
"iterator" : false, // true: Tolerate using the `__iterator__` property
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
"laxbreak" : true, // true: Tolerate possibly unsafe line breakings
"laxcomma" : false, // true: Tolerate comma-first style coding
"loopfunc" : false, // true: Tolerate functions being defined in loops
"multistr" : true, // true: Tolerate multi-line strings
"proto" : false, // true: Tolerate using the `__proto__` property
"scripturl" : false, // true: Tolerate script-targeted URLs
"smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment
"shadow" : true, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
"validthis" : true, // true: Tolerate using this in a non-constructor function
// Environments
"browser" : true, // Web Browser (window, document, etc)
"couch" : false, // CouchDB
"devel" : true, // Development/debugging (alert, confirm, etc)
"dojo" : false, // Dojo Toolkit
"jquery" : false, // jQuery
"mootools" : false, // MooTools
"node" : true, // Node.js
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
"prototypejs" : false, // Prototype and Scriptaculous
"rhino" : false, // Rhino
"worker" : false, // Web Workers
"wsh" : false, // Windows Scripting Host
"yui" : false, // Yahoo User Interface
"noyield" : true, // allow generators without a yield
// Legacy
"nomen" : false, // true: Prohibit dangling `_` in variables
"onevar" : false, // true: Allow only one `var` statement per function
"passfail" : false, // true: Stop on first error
"white" : false, // true: Check against strict whitespace and indentation rules
// Custom Globals
"globals" : { // additional predefined global variables
// mocha
"describe": true,
"it": true,
"before": true,
"afterEach": true,
"beforeEach": true,
"after": true
}
}

View File

@@ -20,6 +20,3 @@ coverage/
config/web_readme.md
.dist/
config/config.js
*.sqlite
.node-dev.json
nohup.out

View File

@@ -1,11 +1,5 @@
sudo: false
language: node_js
node_js:
- '6'
- '7'
addons:
- postgresql: '9.3'
script:
- TEST_TIMEOUT=60000 CNPM_SOURCE_NPM=https://registry.npmjs.org CNPM_SOURCE_NPM_ISCNPM=false npm run test-all
after_script:
- npm i codecov && codecov
- '0.11'
script: "make test-travis"
after_script: "npm install coveralls@2 && cat ./coverage/lcov.info | coveralls"

View File

@@ -1,12 +1,8 @@
# Ordered by date of first contribution.
# Auto-generated by 'contributors' on Mon, 03 Mar 2014 13:01:28 GMT.
# https://github.com/xingrz/node-contributors
fengmk2 <fengmk2@gmail.com> (https://github.com/fengmk2)
dead-horse <dead_horse@qq.com> (https://github.com/dead-horse)
alsotang <alsotang@gmail.com> (https://github.com/alsotang)
4simple <wondger@qq.com> (https://github.com/4simple)
afc163 <afc163@gmail.com> (https://github.com/afc163)
Yuwei Ba <i@xiaoba.me> (https://github.com/ibigbug)
dickeylth <dickeylth@gmail.com> (https://github.com/dickeylth)
wenbing <wenbing@users.noreply.github.com> (https://github.com/wenbing)
21paradox <1036339815@qq.com> (https://github.com/21paradox)

View File

@@ -1,536 +1,10 @@
2.19.2 / 2017-01-05
1.7.2 / 2014-10-31
==================
* fix: should auto sync un-deprecate message (#1105)
2.19.1 / 2016-12-29
==================
* fix: try to use the best repository url (#1102)
2.19.0 / 2016-12-21
==================
* feat: keyword search with limit to support keywords > 100 (#1097)
2.18.0 / 2016-12-05
==================
* fix: support downloads total on scope package (#1088)
* fix: try to sync from official replicate (#1076)
* feat: add change password script (#1070)
* test: skip always fail tests
* test: add node v7
* feat: show more sync info
2.17.2 / 2016-11-13
==================
* fix: ignore long package name on unpublished sync (#1067)
2.17.1 / 2016-11-08
==================
* fix: add publish_time for private packages (#1061)
2.17.0 / 2016-11-03
==================
* feat: make snyk.io url configable (#1058)
2.16.2 / 2016-09-27
==================
* fix: try to use config.registryHost first on setDownloadURL (#1044)
2.16.1 / 2016-08-22
==================
* refactor: refine publishable's code (#1022)
2.16.0 / 2016-08-22
==================
* feat: admin can do everything (#1021)
2.15.0 / 2016-08-22
==================
* feat: return dist-tag on package registry ([#1020](https://github.com/cnpm/cnpmjs.org/issues/1020))
* chore(package): update supertest to version 2.0.0 ([#1004](https://github.com/cnpm/cnpmjs.org/issues/1004))
2.14.0 / 2016-08-04
==================
* feat: password may contains ":" ([#999](https://github.com/cnpm/cnpmjs.org/issues/999))
* fix: limit sync fails email notice ([#1006](https://github.com/cnpm/cnpmjs.org/issues/1006))
2.13.0 / 2016-07-26
==================
* feat: enable maxrequests middleware ([#1003](https://github.com/cnpm/cnpmjs.org/issues/1003))
2.12.2 / 2016-07-11
==================
* fix: getModuleByRange don't list all packages ([#990](https://github.com/cnpm/cnpmjs.org/issues/990))
* fix: should show new version package count ([#984](https://github.com/cnpm/cnpmjs.org/issues/984))
2.12.1 / 2016-07-01
==================
* fix: make sure chagnes stream destroy ([#982](https://github.com/cnpm/cnpmjs.org/issues/982))
* chore(package): update semver to version 5.2.0 ([#978](https://github.com/cnpm/cnpmjs.org/issues/978))
* deps: use ^ instead of ~ ([#976](https://github.com/cnpm/cnpmjs.org/issues/976))
* chore(package): update mini-logger to version 1.1.1 ([#973](https://github.com/cnpm/cnpmjs.org/issues/973))
2.12.0 / 2016-06-26
==================
* fix: logger seperator should be one EOL ([#972](https://github.com/cnpm/cnpmjs.org/issues/972))
* feat: add security check badge for public package ([#971](https://github.com/cnpm/cnpmjs.org/issues/971))
2.11.0 / 2016-06-25
==================
* feat: add changes stream syncer ([#970](https://github.com/cnpm/cnpmjs.org/issues/970))
* chore(package): update pg to version 5.1.0 ([#953](https://github.com/cnpm/cnpmjs.org/issues/953))
2.10.1 / 2016-06-05
==================
* fix: should sync missing public scoped package on install ([#946](https://github.com/cnpm/cnpmjs.org/issues/946))
* chore(package): update bytes to version 2.4.0 ([#943](https://github.com/cnpm/cnpmjs.org/issues/943))
* userService ([#926](https://github.com/cnpm/cnpmjs.org/issues/926))
* chore(package): update should to version 8.4.0 ([#928](https://github.com/cnpm/cnpmjs.org/issues/928))
* chore(package): update humanize-ms to version 1.2.0 ([#927](https://github.com/cnpm/cnpmjs.org/issues/927))
* chore(package): update kcors to version 1.2.1 ([#918](https://github.com/cnpm/cnpmjs.org/issues/918))
* chore(package): update urllib to version 2.9.0 ([#898](https://github.com/cnpm/cnpmjs.org/issues/898))
2.10.0 / 2016-04-15
==================
* feat: show tarball url on package page ([#894](https://github.com/cnpm/cnpmjs.org/issues/894))
* chore(package): update koa-mock to version 1.6.1 ([#891](https://github.com/cnpm/cnpmjs.org/issues/891))
2.9.5 / 2016-04-12
==================
* fix: change logo url to a better https source
* fix: http://cnpmjs.org/package/fms pre style ([#739](https://github.com/cnpm/cnpmjs.org/issues/739))
2.9.4 / 2016-04-09
==================
* fix: don't sync constructor package on exists mode ([#883](https://github.com/cnpm/cnpmjs.org/issues/883))
* Update utility to version 1.7.0 🚀
* chore: update sponsor link
2.9.3 / 2016-04-05
==================
* fix: use better diff time to check sync status
* Update sequelize to version 3.21.0 🚀
* chore(package): update agentkeepalive to version 2.1.0
* chore(package): update pg to version 4.5.2
2.9.2 / 2016-03-29
==================
* fix: override antd for ul & ol list number & icon.
2.9.1 / 2016-03-29
==================
* refactor: add more ua info on syncer
2.9.0 / 2016-03-26
==================
* feat: only admin can unpublish
* chore(package): update gravatar to version 1.5.0
* chore(package): update sequelize to version 3.20.0
* fix: fix save download count unqiue constraint error
* chore(package): update moment to version 2.12.0
2.8.1 / 2016-03-07
==================
* fix: only send warning email if no any sync data after 24h
* chore(package): update kcors to version 1.1.0
* chore(package): update koa to version 1.2.0
* chore(package): update urllib to version 2.8.0
2.8.0 / 2016-02-23
==================
* fix: convert `*` to latest tag
* deps: upgrade deps and remove node 2.0.0 support
* doc: update sponsors on readme
* fix: update copyright year
* doc: fix disturl typo
* deps: sequelize@3.19.0
2.7.1 / 2016-02-01
==================
* fix(semver): when have invalid version([#817](https://github.com/cnpm/cnpmjs.org/issues/817))
2.7.0 / 2016-02-01
==================
* test: fix all test cases
* test: fix unpublish
* test: add complex range test case
* feat: support semver([#816](https://github.com/cnpm/cnpmjs.org/issues/816))
2.6.2 / 2016-01-19
==================
* feat: list & show support jsonp
* chore(package): update urllib to version 2.7.0
* Delete install.md
2.6.1 / 2016-01-12
==================
* fix: source registry is not cnpm, ignore check status
2.6.0 / 2016-01-12
==================
* feat(sync): monitor sync status
* chore(package): update agentkeepalive to version 2.0.3
* fix SequelizeDatabaseError: ER_NO_SUCH_TABLE: Table 'qnpm.total' doesn't exist\nreproduce this bug:\nthe first startup of cnpmjs.org
* chore(package): update moment to version 2.11.0
* chore(package): update xss to version 0.2.10
* chore(package): update pg-hstore to version 2.3.2
* chore(package): update mini-logger to version 1.1.0
* chore(package): update urllib to version 2.6.0
* fix: row.package will json parse error
* remove bluebird
* chore(package): update utility to version 1.6.0
2.5.1 / 2015-12-02
==================
* chore(package): update bluebird to version 3.0.6
* fix: SequelizeDatabaseError
* fix(dist_tag): disable delete latest tag
* feat: count total private pkgs
* fix: use isoweek. a week start from monday
* chore(package): update xss to version 0.2.8
* chore(package): update semver to version 5.1.0
2.5.0 / 2015-11-17
==================
* test: add node v5
* feat(sync): sync deleted user
* Update show.js
* chore(package): update bytes to version 2.2.0
* do not sync inner username
* gzip static file
* chore(package): update bytes to version 2.1.0
* chore(package): update is-type-of to version 1.0.0
* Update static.js
* chore(package): update cfork to version 1.4.0
* chore(package): update bluebird to version 3.0.5
2.4.1 / 2015-10-27
==================
* fix: improve registry index page performance with cache
* Configable badge URL prefix.
* chore(package): update koa-mock to version 1.5.0
* chore(package): update urllib to version 2.5.0
* chore(package): update co to version 4.6.0
* chore(package): update semver to version 5.0.3
2.4.0 / 2015-10-21
==================
* feat(registry): add package's dependents api
* fix: show package's dependents
* chore(package): update utility to version 1.5.0
* chore(package): update bluebird to version 2.10.2
2.3.1 / 2015-10-15
==================
* refactor: remove gnode
* deps: upgrade giturl
* Some fixes
2.3.0 / 2015-10-15
==================
* Add dev dependencies.
* Package page fix.
* refactor: add more sync log
* Add mock data.
* refactor: add more sync log
* Fix sidebar overflow.
* Merge pull request [#680](https://github.com/cnpm/cnpmjs.org/issues/680) from ibigbug/ant-design
* Clean code.
* Indent.
* chore(package): update debug to version 2.2.0
* chore(package): update should to version 7.1.0
* chore(package): update koa to version 1.1.0
* Remove default adBanner.
* Package pages.
* Common styles.
* search page.
* Sync page.
* Profile page antd style.
* Unpublished pkg page style.
* Add page title for unpubed pkg.
* Index page style use antd.
* chore(package): update pg to version 4.4.2
* chore(package): update cfork to version 1.3.1
* chore(package): update moment to version 2.10.6
* feat(badge): Use qiniu cdn
2.2.1 / 2015-09-30
==================
* test: use istanbul
* pref: move out try/catch block
* fix: support nfs.url is generator
2.2.0 / 2015-09-29
==================
* feat: list packages by username
* test: use codecov
* feat(badge): support custom subject
* fix(sync): add recover logic
* feat(sync): add sync scripts
2.1.5 / 2015-09-05
==================
* fix: only sync update packages
2.1.4 / 2015-09-05
==================
* fix: support new array and old map format both
* fix: /-/all/since had been redirect to /-/all/static/today.json
* fix(list): let koa-etag to caculate the etag
2.1.3 / 2015-08-18
==================
* fix: sync public scope package download url is wrong
* fix: default registry change to taobao registry
2.1.2 / 2015-08-09
==================
* fix(syncer): sync worker pkg null bug
* feat(web): add downloads badge
2.1.1 / 2015-07-27
==================
* fix: fix private scope package detect
* fix: dont sync if upstream is npm registry
* fix(sync): support sync public scope package
* test: fix fails tests
* fix: ignore 503 server error
* fix: ignore sync 503 server error
2.1.0 / 2015-07-08
==================
* feat(web): search support jsonp
* fix function name
2.0.0 / 2015-05-11
==================
* fix: real download as stream
* add custom ad banner config
* add sponsors: ucloud.cn
* fix small typo
* feat(urllib): support http_proxy
* force using https links
2.0.0-rc.15 / 2015-02-15
==================
* fix(markdown): filter xss after markdown render
* feat(database): support PostgreSQL
2.0.0-rc.14 / 2015-02-14
==================
* feat: support always-auth
* fix mysql select args = [] bug
* fix [#597](https://github.com/cnpm/cnpmjs.org/issues/597) sequelize raw query.
* fix(markdown): hotfix markdown-it cpu problem
* feat: upgrade to co4
* use kcors fixes [#594](https://github.com/cnpm/cnpmjs.org/issues/594)
2.0.0-rc.13 / 2015-02-04
==================
* docs: Deploy a private npm registry in 5 minutes
* refactor(config): move application data to ~/.cnpmjs.org/
* fix(sync): make get popular pakcage faster
* feat(sync): web page also redirect to npm www
* refactor(config): make syncModel to none by default
* test: fix admin can not publish non-scoped package test cases
* docs: add chinese mirror link
* fix: admin can not publish non scoped package on "none" sync model
* feat(sync): enable none syncModel proxy all public packages
* fix: ignore username start with " or '
* fix(bin): fix stop not work on iojs
2.0.0-rc.12 / 2015-02-01
==================
* feat(syncer): add hostname ua
* fix(web): remove pkg.contributors logic
2.0.0-rc.11 / 2015-02-01
==================
* fix xss tests
* fix(markdown): revert marky-markdown
2.0.0-rc.10 / 2015-01-31
==================
* feat(middleware): CORS headers for GET and HEAD requests
* fix(readme): fix index page markdown
* feat(markdown): use npm same markdown parser
* feat(download): support download redirect to nfs
* feat(syncer): request npm registry with gzip
* change(sync): remove dist syncer
* feat(registry): add dist tag api
* refactor(common): remove redis store
2.0.0-rc.9 / 2015-01-22
==================
* hotfix reame render error, pin xss
* fix registry user auth api
2.0.0-rc.8 / 2015-01-10
==================
* fix(markdown): readme.md allow scripts
* fix(style) flexbox compatibility for both chrome and firefox (@afc163)
* feat(sync): default sync exist packages
2.0.0-rc.7 / 2015-01-07
==================
* install sync dont check `enablePrivate`
* fix(markdown): filter xss readme before markdown render
2.0.0-rc.6 / 2015-01-05
==================
* fix(markdown): use markdown-it
* add userService options on config
* add upload to nfs sync info log
2.0.0-rc.5 / 2015-01-03
==================
* fix(markdown): use marked instead of remarkable
* fix(package): pkg.readme is not a string, dont remarkable it
* feat(sync): sync user profile
2.0.0-rc.4 / 2014-12-25
==================
* refactor(download): try to use nsf.url() first
* use __all__ for full downloads
* refactor(download_total): optimize download total
* fix sqlite raw sql return datetime is string format
* fix(download_total): change column date to DateTime
* fix(services/download_total): fix download_total slow sql on `date >= $start and date <= $end`
* fix(markdown): replace marked use remarkable
2.0.0-rc.3 / 2014-12-14
==================
* fix(services): need to detect instance isDirty or not before save()
2.0.0-rc.2 / 2014-12-11
==================
* add download API, closes [#529](https://github.com/cnpm/cnpmjs.org/issues/529)
* fix missing home page title (@rockdai)
* Fix typo in view/web/package.html (@LoicMahieu)
2.0.0-rc.1 / 2014-12-09
==================
* fix xss on title
* feat(badge): support badge image url with tag
2.0.0-beta5 / 2014-12-05
==================
* hotfix package.html typo. Closes [#521](https://github.com/cnpm/cnpmjs.org/issues/521)
* Add editorconfig
* fix(web/package): package name to long cause style problem fix
* fix(css): use github-markdown-css for markdown body
* feat(mock): use koa-mock for front end dev
2.0.0-beta4 / 2014-11-21
==================
* fix(registry): add missing /-/short api
* zoom sync link
* new design for package page
* image max width, fixed [#505](https://github.com/cnpm/cnpmjs.org/issues/505)
* feat(middleware): block Ruby user-agent
2.0.0-beta3 / 2014-11-12
==================
* fix(sync): should not sync package when maintainers sort change
* fix(package): detect package is private or not
* fix(maintainer): fix missing maintainers
2.0.0-beta2 / 2014-11-09
==================
* fix(sync): add missing syncUpstreamFirst argument
2.0.0-beta1 / 2014-11-07
==================
* refactor(sync_worker): only sync request need to sync upstream first
* fix(sync_worker): make sure end event will emit
* fix: mv readme.md script to public/js/readme.js
* fix(sync): hotfix co uncaughtException
* feat(sync): sync python dist
* pin autod@1
* remove useless comment
* refactor models/_module_maintainer_class_methods.js
2.0.0-beta0 / 2014-11-02
==================
* ungrade koa-markdown to use remarkable, close [#482](https://github.com/cnpm/cnpmjs.org/issues/482)
* fix(module_log): limit module sync log size to 1MB
* refactor(config): remove adaptScope config key
* chore(Makefile): $ make install-production
* fix(sequelize): show warnning message when using old config.js
* docs(readme): Migrating from 1.x to 2.x
* feat(sync): add min sync interval time detect
* refactor(dispatch): remove unused codes
* use sequelize to connect database
1.7.1 / 2014-10-15
1.7.1 / 2014-10-15
==================
* fix typo in sync popular, fix [#477](https://github.com/cnpm/cnpmjs.org/issues/477)
@@ -585,7 +59,7 @@
* support im url on user profile page; update bootstrap to 3.2.0
1.5.1 / 2014-09-23
1.5.1 / 2014-09-23
==================
* search support case insensitive, close [#450](https://github.com/cnpm/cnpmjs.org/issues/450)
@@ -983,7 +457,7 @@
* npm publish dont contains .jshint*
* npm test run jshint
* Add jshint check: $ make jshint
* use `yield next` instead of `yield next`
* use `yield* next` instead of `yield next`
* replace dist.u.qiniudn.com with cnpmjs.org/dist
0.3.5 / 2014-03-05

View File

@@ -1,6 +1,6 @@
This software is licensed under the MIT License.
Copyright(c) cnpm and other contributors.
Copyright(c) cnpmjs.org and other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

77
Makefile Normal file
View File

@@ -0,0 +1,77 @@
TESTS = $(shell ls -S `find test -type f -name "*.test.js" -print`)
REPORTER = spec
TIMEOUT = 30000
MOCHA_OPTS =
REGISTRY = --registry=https://registry.npm.taobao.org
install:
@npm install $(REGISTRY) \
--disturl=https://npm.taobao.org/dist
jshint: install
@-./node_modules/.bin/jshint ./
pretest:
@mysql -uroot -e 'DROP DATABASE IF EXISTS cnpmjs_test;'
@mysql -uroot -e 'CREATE DATABASE cnpmjs_test;'
@mysql -uroot 'cnpmjs_test' < ./docs/db.sql
@mysql -uroot 'cnpmjs_test' -e 'show tables;'
@rm -rf .tmp/dist
test: install pretest
@NODE_ENV=test ./node_modules/.bin/mocha \
--harmony \
--reporter $(REPORTER) \
--timeout $(TIMEOUT) \
--require should \
--require should-http \
--require co-mocha \
--require ./test/init.js \
$(MOCHA_OPTS) \
$(TESTS)
test-cov cov: install pretest
@NODE_ENV=test node --harmony \
node_modules/.bin/istanbul cover --preserve-comments \
./node_modules/.bin/_mocha \
-- -u exports \
--reporter $(REPORTER) \
--timeout $(TIMEOUT) \
--require should \
--require should-http \
--require co-mocha \
--require ./test/init.js \
$(MOCHA_OPTS) \
$(TESTS)
test-travis: install pretest
@NODE_ENV=test CNPM_SOURCE_NPM=https://registry.npmjs.org CNPM_SOURCE_NPM_ISCNPM=false \
node --harmony \
node_modules/.bin/istanbul cover --preserve-comments \
./node_modules/.bin/_mocha \
--report lcovonly \
-- \
--reporter dot \
--timeout $(TIMEOUT) \
--require should \
--require should-http \
--require co-mocha \
--require ./test/init.js \
$(MOCHA_OPTS) \
$(TESTS)
dev:
@node_modules/.bin/node-dev --harmony dispatch.js
contributors: install
@./node_modules/.bin/contributors -f plain -o AUTHORS
autod: install
@./node_modules/.bin/autod -w \
--prefix "~"\
--exclude public,view,docs,backup,coverage \
--dep bluebird \
--devdep mocha,should,istanbul-harmony,jshint
@$(MAKE) install
.PHONY: test

View File

@@ -3,23 +3,29 @@ cnpmjs.org
[![NPM version][npm-image]][npm-url]
[![build status][travis-image]][travis-url]
[![Test coverage][codecov-image]][codecov-url]
[![Test coverage][coveralls-image]][coveralls-url]
[![Gittip][gittip-image]][gittip-url]
[![David deps][david-image]][david-url]
[![Known Vulnerabilities][snyk-image]][snyk-url]
[![node version][node-image]][node-url]
[![npm download][download-image]][download-url]
[![gitter][gitter-image]][gitter-url]
[npm-image]: http://cnpmjs.org/badge/v/cnpmjs.org.svg?style=flat-square
[npm-url]: http://cnpmjs.org/package/cnpmjs.org
[travis-image]: https://img.shields.io/travis/cnpm/cnpmjs.org.svg?style=flat-square
[travis-url]: https://travis-ci.org/cnpm/cnpmjs.org
[codecov-image]: https://codecov.io/gh/cnpm/cnpmjs.org/branch/master/graph/badge.svg
[codecov-url]: https://codecov.io/gh/cnpm/cnpmjs.org
[coveralls-image]: https://img.shields.io/coveralls/cnpm/cnpmjs.org.svg?style=flat-square
[coveralls-url]: https://coveralls.io/r/cnpm/cnpmjs.org?branch=master
[gittip-image]: https://img.shields.io/gittip/fengmk2.svg?style=flat-square
[gittip-url]: https://www.gittip.com/fengmk2/
[david-image]: https://img.shields.io/david/cnpm/cnpmjs.org.svg?style=flat-square
[david-url]: https://david-dm.org/cnpm/cnpmjs.org
[snyk-image]: https://snyk.io/test/npm/cnpmjs.org/badge.svg?style=flat-square
[snyk-url]: https://snyk.io/test/npm/cnpmjs.org
[node-image]: https://img.shields.io/badge/node.js-%3E=_0.11-red.svg?style=flat-square
[node-url]: http://nodejs.org/download/
[download-image]: https://img.shields.io/npm/dm/cnpmjs.org.svg?style=flat-square
[download-url]: https://npmjs.org/package/cnpmjs.org
[gitter-image]: https://badges.gitter.im/Join%20Chat.svg
[gitter-url]: https://gitter.im/cnpm/cnpmjs.org?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
![logo](https://raw.github.com/cnpm/cnpmjs.org/master/logo.png)
@@ -36,14 +42,12 @@ Our goal is to provide a low cost maintenance and easy to use solution for priva
* Build a mirror NPM. (we use it to build a mirror in China: [cnpmjs.org](http://cnpmjs.org/))
* Build a completely independent NPM registry to store whatever you like.
## Features
### Features
* **Support "scoped" packages**: [npm/npm#5239](https://github.com/npm/npm/issues/5239)
* **Support [CORS](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing)**
* **Simple to deploy**: only need `mysql` and a [simple store system](https://github.com/cnpm/cnpmjs.org/wiki/NFS-Guide).
You can get the source code through `npm` or `git`.
* **Low cost and easy maintenance**: `package.json` info store in MySQL, MariaDB, SQLite or PostgreSQL databases,
tarball(tgz file) store in CDN or other store systems.
* **Low cost and easy maintenance**: `package.json` info store in MySQL, tarball(tgz file) store in CDN or other store systems.
* **Automatic synchronization**: automatic synchronization from any registry specified, support two sync modes:
- Sync all modules from a specified registry, like [npm registry](http://registry.npmjs.org).
- Only sync the modules that exists in your own registry.
@@ -54,32 +58,28 @@ And it easy to wrap for your own registry which build with `cnpmjs.org`.
* **Compatible with NPM client**: you can use the origin NPM client with `cnpmjs.org`,
only need to change the registry in config. Even include manual synchronization (through `install` command).
* **Version badge**: base on [shields.io](http://shields.io/) ![cnpm-badge](http://cnpmjs.org/badge/v/cnpmjs.org.svg?style=flat-square)
* **Support http_proxy**: if you're behind firewall, need to request through http proxy
**PROTIP** Be sure to read [Migrating from 1.x to 2.x](https://github.com/cnpm/cnpmjs.org/wiki/Migrating-from-1.x-to-2.x)
as well as [New features in 2.x](https://github.com/cnpm/cnpmjs.org/wiki/New-features-in-2.x).
## Getting Start
* [Deploy a private npm registry in 5 minutes](https://github.com/cnpm/cnpmjs.org/wiki/Deploy-a-private-npm-registry-in-5-minutes)
* @[dead-horse](https://github.com/dead-horse): [What is cnpm?](http://deadhorse.me/slides/cnpmjs.html)
* install and deploy cnpmjs.org through npm: [examples](https://github.com/cnpm/custom-cnpm-example)
* Mirror NPM in China: [cnpmjs.org](http://cnpmjs.org)
* cnpm client: [cnpm](https://github.com/cnpm/cnpm), `npm install -g cnpm`
* [How to deploy cnpmjs.org](https://github.com/cnpm/cnpmjs.org/wiki/Deploy)
* [Sync packages through `http_proxy`](https://github.com/cnpm/cnpmjs.org/wiki/Sync-packages-through-http_proxy)
* [wiki](https://github.com/cnpm/cnpmjs.org/wiki)
## Develop on your local machine
### Dependencies
* [node](http://nodejs.org) >= 4.3.1
* Databases: only required one type
* [sqlite3](https://npm.taobao.org/package/sqlite3) >= 3.0.2, we use `sqlite3` by default
* [MySQL](http://dev.mysql.com/downloads/) >= 0.5.0, include `mysqld` and `mysql cli`. I test on `mysql@5.6.16`.
* MariaDB
* PostgreSQL
* [node](http://nodejs.org) >=0.11.12, use `--harmony`
* [mysql](http://dev.mysql.com/downloads/) >= 0.5.0, include `mysqld` and `mysql cli`. I test on `mysql@5.6.16`.
### Start MySQL
```bash
$ nohup mysqld &
```
### Clone codes and run test
@@ -96,7 +96,7 @@ $ make test
# coverage
$ make test-cov
# update dependencies
# udpate dependencies
$ make autod
# start server with development mode
@@ -112,11 +112,27 @@ $ make dev
Tips: make sure your code is following the [node-style-guide](https://github.com/felixge/node-style-guide).
## Sponsors
- [![阿里云](https://static.aliyun.com/images/www-summerwind/logo.gif)](http://click.aliyun.com/m/4288/) (2016.2 - now)
- [![UCloud云计算](https://www.ucloud.cn/static/style/images/about/logo.png)](http://www.ucloud.cn?sem=sdk-CNPMJS) (2015.3 - 2016.3)
## License
[MIT](LICENSE.txt)
(The MIT License)
Copyright(c) cnpmjs.org and other contributors.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

142
backup/dump.js Normal file
View File

@@ -0,0 +1,142 @@
/**!
* cnpmjs.org - backup/dump.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
/**
* 1. dump module
* 2. dump tag
* 3. dump user
* 4. total
* 5. download_total
*/
var path = require('path');
var fs = require('fs');
var moment = require('moment');
var eventproxy = require('eventproxy');
var util = require('util');
var zlib = require('zlib');
var mysql = require('../common/mysql');
var nfs = require('../common/nfs');
var config = require('../config');
function dumpTable(name, lastRow, callback) {
var sql = 'SELECT * from ' + name + ' WHERE gmt_modified >=? ORDER BY gmt_modified ASC LIMIT 10000;';
mysql.query(sql, [lastRow.gmt_modified], function (err, rows) {
if (err || rows.length === 0) {
return callback(err, rows);
}
if (rows[0].id === lastRow.id) {
rows = rows.slice(1);
}
callback(null, rows);
});
}
function log() {
var str = '[' + moment().format('YYYY-MM-DD HH:mm:ss') + '] ' + util.format.apply(util, arguments);
console.log(str);
}
function syncTable(name, callback) {
var datadir = __dirname;
var dataFile = path.join(datadir, moment().format('YYYY-MM-DD-HH') + '_' + name + '.json');
var lastRowFile = path.join(datadir, name + '_lastdate.json');
var lastRow = null;
if (fs.existsSync(lastRowFile)) {
lastRow = require(lastRowFile);
lastRow.gmt_modified = new Date(Date.parse(lastRow.gmt_modified));
} else {
lastRow = {
gmt_modified: new Date('2011-11-11'),
};
}
log('getting "%s" since %j', name, lastRow);
dumpTable(name, lastRow, function (err, rows) {
console.log('[%s] got %d rows', Date(), rows && rows.length || 0);
if (err) {
return callback(err);
}
if (!rows || rows.length === 0) {
return callback();
}
var writeStream = fs.createWriteStream(dataFile, {flags: 'a'});
writeStream.once('error', callback);
for (var i = 0; i < rows.length; i++) {
writeStream.write(JSON.stringify(rows[i]) + '\n');
}
writeStream.end();
writeStream.on('finish', function () {
log('append %d rows to %s', rows.length, dataFile);
var gzfile = dataFile + '.gz';
var gzip = zlib.createGzip();
var inp = fs.createReadStream(dataFile);
var out = fs.createWriteStream(gzfile);
inp.pipe(gzip).pipe(out);
out.once('error', callback);
out.on('finish', function () {
var key = path.join(config.backupFilePrefix, path.basename(gzfile));
log('saving %s to %s ...', gzfile, key);
nfs.upload(gzfile, {key: key}, function (err, result) {
if (err) {
return callback(err);
}
lastRow = rows[rows.length - 1];
lastRow = {gmt_modified: lastRow.gmt_modified, id: lastRow.id};
fs.writeFileSync(lastRowFile, JSON.stringify(lastRow));
log('save %s data file to %j, lastrow: %j', name, result, lastRow);
callback();
});
});
});
});
}
var ep = eventproxy.create();
ep.fail(function (err) {
log('error: %s', err.stack);
process.exit(1);
});
syncTable('module', ep.done('module'));
ep.on('module', function () {
syncTable('tag', ep.done('tag'));
});
ep.on('tag', function () {
syncTable('user', ep.done('user'));
});
ep.on('user', function () {
syncTable('total', ep.done('total'));
});
ep.on('total', function () {
syncTable('download_total', ep.done('download_total'));
});
ep.on('download_total', function () {
ep.emit('finish');
});
ep.on('finish', function () {
log('finish, %d process exit', process.pid);
process.exit(0);
});

View File

@@ -1,24 +0,0 @@
'use strict';
// Only support for ./services/DefaultUserService. If you use custom user service, ignore this file.
// call with:
// $ node ./bin/change_password.js 'username' 'new_password'
const UserModel = require('../models').User;
const co = require('co');
const utility = require('utility');
const username = process.argv[2];
const newPassword = process.argv[3];
co(function* () {
let user = yield UserModel.find({ where: { name: username } });
const salt = user.salt;
console.log(`user original password_sha: ${user.password_sha}`);
const newPasswordSha = utility.sha1(newPassword + salt);
user.password_sha = newPasswordSha;
user = yield user.save();
console.log(`change user password successful!! user new password_sha: ${user.password_sha}`);
process.exit(0);
}).catch(function(e) {
console.log(e);
});

View File

@@ -1,118 +0,0 @@
#!/usr/bin/env node
'use strict';
const debug = require('debug')('cnpmjs.org:cli');
const program = require('commander');
const path = require('path');
const fs = require('fs');
const mkdirp = require('mkdirp');
const treekill = require('treekill');
const version = require('../package.json').version;
function list(val) {
return val.split(',');
}
program
.version(version);
program
.command('start')
.description('start cnpmjs.org server')
.option('--admins <admins>', 'set admins', list)
.option('--scopes <scopes>', 'set scopes', list)
// .option('--cluster', 'enable cluster mode')
.option('--dataDir <dataDir>', 'cnpmjs.org data dir, default is `$HOME/.cnpmjs.org`')
.action(start);
program
.command('stop')
.description('stop cnpmjs.org server')
.option('--dataDir <dataDir>', 'cnpmjs.org data dir, default is `$HOME/.cnpmjs.org`')
.action(stop);
program.parse(process.argv);
function start(options) {
stop(options);
const dataDir = options.dataDir || path.join(process.env.HOME, '.cnpmjs.org');
mkdirp.sync(dataDir);
const configfile = path.join(dataDir, 'config.json');
let config = {};
if (fs.existsSync(configfile)) {
try {
config = require(configfile);
} catch (err) {
console.warn('load old %s error: %s', configfile, err);
}
}
// config.enableCluster = !!options.cluster;
if (options.admins) {
config.admins = {};
for (let i = 0; i < options.admins.length; i++) {
config.admins[options.admins[i]] = options.admins[i] + '@localhost.com';
}
}
if (options.scopes) {
config.scopes = options.scopes.map(function(name) {
if (name[0] !== '@') {
name = '@' + name;
}
return name;
});
}
const configJSON = JSON.stringify(config, null, 2);
fs.writeFileSync(configfile, configJSON);
debug('save config %s to %s', configJSON, configfile);
// if sqlite db file not exists, init first
initDatabase(function() {
require('../dispatch');
});
fs.writeFileSync(path.join(dataDir, 'pid'), process.pid + '');
}
function stop(options) {
const dataDir = options.dataDir || path.join(process.env.HOME, '.cnpmjs.org');
const pidfile = path.join(dataDir, 'pid');
if (fs.existsSync(pidfile)) {
const pid = Number(fs.readFileSync(pidfile, 'utf8'));
treekill(pid, function(err) {
if (err) {
console.log(err);
throw err;
}
console.log('cnpmjs.org server:%d stop', pid);
fs.unlinkSync(pidfile);
});
}
}
function initDatabase(callback) {
const models = require('../models');
models.sequelize.sync({ force: false })
.then(function() {
models.Total.init(function(err) {
if (err) {
console.error('[models/init_script.js] sequelize init fail');
console.error(err);
throw err;
} else {
console.log('[models/init_script.js] `sqlite` sequelize sync and init success');
callback();
}
});
})
.catch(function(err) {
console.error('[models/init_script.js] sequelize sync fail');
console.error(err);
throw err;
});
}

View File

@@ -7,7 +7,7 @@ export NODE_ENV='production'
ulimit -c unlimited
cd `dirname $0`/..
NODEJS='node'
NODEJS='node --harmony'
BASE_HOME=`pwd`
PROJECT_NAME=`basename ${BASE_HOME}`
STDOUT_LOG=`$NODEJS -e "console.log(require('path').join(require('$BASE_HOME/config').logdir, 'nodejs_stdout.log'));\
@@ -52,9 +52,9 @@ stop()
kill -15 $PID
sleep 2
node_num=`ps -ef | grep ${PROJECT_NAME} | grep -v grep | wc -l`
node_num=`ps -ef |grep node|grep ${PROJECT_NAME}|grep -v grep|wc -l`
if [ $node_num != 0 ]; then
ps -ef | grep ${PROJECT_NAME} |grep -v grep|awk '{print $2}'|xargs kill -9
ps -ef |grep node | grep ${PROJECT_NAME} |grep -v grep|awk '{print $2}'|xargs kill -9
ipcs -s | grep 0x | awk '{print $2}' | xargs -n1 ipcrm -s > /dev/null 2>&1
ipcs -m | grep 0x | awk '{print $2}' | xargs -n1 ipcrm -m > /dev/null 2>&1
fi

View File

@@ -0,0 +1,74 @@
/**!
* cnpmjs.org - bin/restore_module_deps.js
*
* Copyright(c) 2014
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
"use strict";
/**
* Module dependencies.
*/
var mysql = require('../common/mysql');
var Module = require('../proxy/module');
var ModuleDeps = require('../proxy/module_deps');
var addCount = 0;
function restore(id, callback) {
var sql = 'SELECT id, name, package FROM module WHERE id > ? ORDER BY id ASC LIMIT 1000';
mysql.query(sql, [id], function (err, rows) {
if (err) {
return callback(err);
}
if (rows.length === 0) {
return callback(null, []);
}
console.log('[%s] got %d rows', id, rows.length);
rows.forEach(function (r) {
Module.parseRow(r);
if (!r.package) {
return;
}
var deps = Object.keys(r.package.dependencies || {});
if (!Array.isArray(deps) || !deps.length) {
return;
}
deps.forEach(function (dep) {
ModuleDeps.add(dep, r.name, function (err) {
// console.log('[%s] add %s <= %s, error: %s', id, dep, r.name, err);
});
});
addCount += deps.length;
});
setTimeout(function () {
console.log('[%s] add %d relations', id, addCount);
callback(null, rows);
}, 1000);
});
}
var id = 0;
function run() {
restore(id, function (err, rows) {
if (err) {
throw err;
}
if (rows.length === 0) {
console.log('finished, last id: %s, exit.', id);
process.exit(0);
}
id = rows[rows.length - 1].id;
run();
});
}
run();

View File

@@ -1,56 +1,62 @@
/**!
* cnpmjs.org - common/logger.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
const debug = require('debug')('cnpmjs.org:logger');
const formater = require('error-formater');
const Logger = require('mini-logger');
const utility = require('utility');
const util = require('util');
const os = require('os');
const config = require('../config');
const mail = require('./mail');
/**
* Module dependencies.
*/
const isTEST = process.env.NODE_ENV === 'test';
const categories = [ 'sync_info', 'sync_error' ];
var formater = require('error-formater');
var Logger = require('mini-logger');
var config = require('../config');
var utility = require('utility');
var mail = require('./mail');
var util = require('util');
const logger = module.exports = Logger({
categories,
var isTEST = process.env.NODE_ENV === 'test';
var categories = ['sync_info', 'sync_error'];
var logger = module.exports = Logger({
categories: categories,
dir: config.logdir,
duration: '1d',
format: '[{category}.]YYYY-MM-DD[.log]',
stdout: config.debug && !isTEST,
errorFormater,
seperator: os.EOL,
errorFormater: errorFormater
});
const to = [];
for (const user in config.admins) {
var to = [];
for (var user in config.admins) {
to.push(config.admins[user]);
}
function errorFormater(err) {
const msg = formater.both(err);
var msg = formater.both(err);
mail.error(to, msg.json.name, msg.text);
return msg.text;
}
logger.syncInfo = function() {
const args = [].slice.call(arguments);
logger.syncInfo = function () {
var args = [].slice.call(arguments);
if (typeof args[0] === 'string') {
args[0] = util.format('[%s][%s] ', utility.logDate(), process.pid) + args[0];
}
if (debug.enabled) {
debug.apply(debug, args);
}
logger.sync_info.apply(logger, args);
};
logger.syncError = function() {
const args = [].slice.call(arguments);
logger.syncError =function () {
var args = [].slice.call(arguments);
if (typeof args[0] === 'string') {
args[0] = util.format('[%s][%s] ', utility.logDate(), process.pid) + args[0];
}
if (debug.enabled) {
debug.apply(debug, args);
}
logger.sync_error.apply(logger, arguments);
};

View File

@@ -1,17 +1,30 @@
/**!
* cnpmjs.org - common/mail.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
*/
'use strict';
const nodemailer = require('nodemailer');
const utility = require('utility');
const os = require('os');
const mailConfig = require('../config').mail;
/**
* Module dependencies.
*/
let smtpConfig;
var mailConfig = require('../config').mail;
var nodemailer = require('nodemailer');
var utility = require('utility');
var os = require('os');
var smtpConfig;
if (mailConfig.auth) {
// new style
smtpConfig = mailConfig;
} else {
smtpConfig = {
enable: mailConfig.enable,
// backward compat
host: mailConfig.host,
port: mailConfig.port,
@@ -19,12 +32,12 @@ if (mailConfig.auth) {
debug: mailConfig.debug,
auth: {
user: mailConfig.user,
pass: mailConfig.pass,
},
pass: mailConfig.pass
}
};
}
let transport;
var transport = nodemailer.createTransport(smtpConfig);
/**
* Send notice email with mail level and appname.
@@ -35,16 +48,15 @@ let transport;
* @param {String} html
* @param {Function(err, result)} callback
*/
exports.notice = function sendLogMail(to, level, subject, html, callback) {
subject = '[' + mailConfig.appname + '] [' + level + '] [' + os.hostname() + '] ' + subject;
html = String(html);
exports.send(to, subject, html.replace(/\n/g, '<br/>'), callback);
};
const LEVELS = [ 'log', 'warn', 'error' ];
LEVELS.forEach(function(level) {
exports[level] = function(to, subject, html, callback) {
var LEVELS = [ 'log', 'warn', 'error' ];
LEVELS.forEach(function (level) {
exports[level] = function (to, subject, html, callback) {
exports.notice(to, level, subject, html, callback);
};
});
@@ -56,27 +68,17 @@ LEVELS.forEach(function(level) {
* @param {String} html
* @param {Function(err, result)} callback
*/
exports.send = function(to, subject, html, callback) {
exports.send = function (to, subject, html, callback) {
callback = callback || utility.noop;
if (mailConfig.enable === false) {
console.log('[send mail debug] [%s] to: %s, subject: %s\n%s', Date(), to, subject, html);
return callback();
}
if (!transport) {
transport = nodemailer.createTransport(smtpConfig);
}
const message = {
var message = {
from: mailConfig.from || mailConfig.sender,
to,
subject,
html,
to: to,
subject: subject,
html: html,
};
transport.sendMail(message, function(err, result) {
transport.sendMail(message, function (err, result) {
callback(err, result);
});
};

View File

@@ -1,34 +0,0 @@
/**!
* cnpmjs.org - common/markdown.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
const xss = require('xss');
const MarkdownIt = require('markdown-it');
// allow class attr on code
xss.whiteList.code = [ 'class' ];
const md = new MarkdownIt({
html: true,
linkify: true,
});
exports.render = function(content, filterXss) {
let html = md.render(content);
if (filterXss !== false) {
html = xss(html);
}
return html;
};

80
common/mysql.js Normal file
View File

@@ -0,0 +1,80 @@
/*!
* cnpmjs.org - common/mysql.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com>
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var ready = require('ready');
var mysql = require('mysql');
var config = require('../config');
var server = config.mysqlServers[0];
// TODO: query timeout
var pool = mysql.createPool({
host: server.host,
port: server.port,
user: server.user,
password: server.password,
database: config.mysqlDatabase,
connectionLimit: config.mysqlMaxConnections,
multipleStatements: true,
});
exports.pool = pool;
exports.query = function (sql, values, cb) {
if (typeof values === 'function') {
cb = values;
values = null;
}
pool.query(sql, values, function (err, rows) {
cb(err, rows);
});
};
exports.queryOne = function (sql, values, cb) {
if (typeof values === 'function') {
cb = values;
values = null;
}
exports.query(sql, values, function (err, rows) {
if (rows) {
rows = rows[0];
}
cb(err, rows);
});
};
exports.escape = function (val) {
return pool.escape(val);
};
ready(exports);
thunkify(exports);
function init() {
exports.query('show tables', function (err, rows) {
if (err) {
console.error('[%s] [worker:%s] mysql init error: %s', Date(), process.pid, err);
setTimeout(init, 1000);
return;
}
console.log('[%s] [worker:%s] mysql ready, got %d tables', Date(), process.pid, rows.length);
exports.ready(true);
});
}
init();

View File

@@ -1,9 +1,11 @@
/* !
/*!
* cnpmjs.org - common/nfs.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.com)
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
@@ -12,6 +14,6 @@
* Module dependencies.
*/
const config = require('../config');
var config = require('../config');
module.exports = config.nfs;

37
common/redis.js Normal file
View File

@@ -0,0 +1,37 @@
/**!
* cnpmjs.org - common/redis.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
*/
'use strict';
/**
* Module dependencies.
*/
var config = require('../config');
// close redis by set config.redis to `null` or `{}`
if (config.redis && config.redis.host && config.redis.port) {
var redis = require('redis');
var wrapper = require('co-redis');
var logger = require('./logger');
var _client = redis.createClient(config.redis.port, config.redis.host);
_client.on('error', function (err) {
logger.error(err);
});
module.exports = wrapper(_client);
} else {
console.warn('[%s] [worker:%s:common/redis.js] Redis config can not found',
Date(), process.pid);
module.exports = null;
}

View File

@@ -1,75 +0,0 @@
/**!
* cnpmjs.org - common/sequelize.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
const Sequelize = require('sequelize');
const DataTypes = require('sequelize/lib/data-types');
const config = require('../config');
if (config.mysqlServers && config.database.dialect === 'sqlite') {
// https://github.com/cnpm/cnpmjs.org/wiki/Migrating-from-1.x-to-2.x
// forward compat with old style on 1.x
// mysqlServers: [
// {
// host: '127.0.0.1',
// port: 3306,
// user: 'root',
// password: ''
// }
// ],
// mysqlDatabase: 'cnpmjs_test',
// mysqlMaxConnections: 4,
// mysqlQueryTimeout: 5000,
console.warn('[WARNNING] your config.js was too old, please @see https://github.com/cnpm/cnpmjs.org/wiki/Migrating-from-1.x-to-2.x');
const server = config.mysqlServers[0];
config.database = {
db: config.mysqlDatabase,
username: server.user,
password: server.password,
dialect: 'mysql',
host: server.host,
port: server.port,
pool: {
maxConnections: config.mysqlMaxConnections || 10,
minConnections: 0,
maxIdleTime: 30000,
},
logging: !!process.env.SQL_DEBUG,
};
}
const database = config.database;
// sync database before app start, defaul is false
database.syncFirst = false;
// add longtext for mysql
Sequelize.LONGTEXT = DataTypes.LONGTEXT = DataTypes.TEXT;
if (config.dialect === 'mysql') {
Sequelize.LONGTEXT = DataTypes.LONGTEXT = 'LONGTEXT';
}
database.define = {
timestamps: true,
createdAt: 'gmt_create',
updatedAt: 'gmt_modified',
charset: 'utf8',
collate: 'utf8_general_ci',
};
const sequelize = new Sequelize(database.db, database.username, database.password, database);
module.exports = sequelize;

View File

@@ -14,62 +14,72 @@
* Module dependencies.
*/
const urlparse = require('url').parse;
const urllib = require('urllib');
const HttpAgent = require('agentkeepalive');
const HttpsAgent = require('agentkeepalive').HttpsAgent;
const config = require('../config');
var urllib = require('urllib');
var HttpAgent = require('agentkeepalive');
var HttpsAgent = require('agentkeepalive').HttpsAgent;
let httpAgent;
let httpsAgent;
if (config.httpProxy) {
const tunnel = require('tunnel-agent');
const urlinfo = urlparse(config.httpProxy);
if (urlinfo.protocol === 'http:') {
httpAgent = tunnel.httpOverHttp({
proxy: {
host: urlinfo.hostname,
port: urlinfo.port,
},
});
httpsAgent = tunnel.httpsOverHttp({
proxy: {
host: urlinfo.hostname,
port: urlinfo.port,
},
});
} else if (urlinfo.protocol === 'https:') {
httpAgent = tunnel.httpOverHttps({
proxy: {
host: urlinfo.hostname,
port: urlinfo.port,
},
});
httpsAgent = tunnel.httpsOverHttps({
proxy: {
host: urlinfo.hostname,
port: urlinfo.port,
},
});
} else {
throw new TypeError('httpProxy format error: ' + config.httpProxy);
}
} else {
httpAgent = new HttpAgent({
timeout: 0,
keepAliveTimeout: 15000,
});
httpsAgent = new HttpsAgent({
timeout: 0,
keepAliveTimeout: 15000,
});
}
const client = urllib.create({
var httpAgent = new HttpAgent({
timeout: 0,
keepAliveTimeout: 15000
});
var httpsAgent = new HttpsAgent({
timeout: 0,
keepAliveTimeout: 15000
});
var client = urllib.create({
agent: httpAgent,
httpsAgent,
httpsAgent: httpsAgent
});
module.exports = client;
module.exports.USER_AGENT = urllib.USER_AGENT;
function startMonitor() {
var statInterval = 60000;
var agents = [
['httpAgent', httpAgent],
['httpsAgent', httpsAgent]
];
function agentStat() {
for (var i = 0; i < agents.length; i++) {
var type = agents[i][0];
var agent = agents[i][1];
var rate = '0';
if (agent.createSocketCount > 0) {
rate = (agent.requestCount / agent.createSocketCount).toFixed(0);
}
console.info('[%s] socket: %d created, %d close, %d timeout, request: %d requests, %s req/socket',
type,
agent.createSocketCount,
agent.closeSocketCount,
agent.timeoutSocketCount,
agent.requestCount,
rate
);
var name;
for (name in agent.sockets) {
console.info('working sockets %s: %d', name, agent.sockets[name].length);
}
for (name in agent.freeSockets) {
console.info('free sockets %s: %d', name, agent.freeSockets[name].length);
}
for (name in agent.requests) {
console.info('pedding requests %s: %d', name, agent.requests[name].length);
}
if (agent.requestCount >= 100000000) {
agent.requestCount = 0;
agent.createSocketCount = 0;
agent.closeSocketCount = 0;
agent.timeoutSocketCount = 0;
}
}
}
agentStat();
return setInterval(agentStat, statInterval);
}
startMonitor();

View File

@@ -1,19 +1,33 @@
/**!
* cnpmjs.org - config/index.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com>
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
const mkdirp = require('mkdirp');
const copy = require('copy-to');
const path = require('path');
const fs = require('fs');
const os = require('os');
/**
* Module dependencies.
*/
const version = require('../package.json').version;
var path = require('path');
var fs = require('fs');
var os = require('os');
var mkdirp = require('mkdirp');
var copy = require('copy-to');
const root = path.dirname(__dirname);
const dataDir = path.join(process.env.HOME || root, '.cnpmjs.org');
fs.existsSync = fs.existsSync || path.existsSync;
var version = require('../package.json').version;
const config = {
version,
dataDir,
var root = path.dirname(__dirname);
var config = {
version: version,
/**
* Cluster mode
@@ -24,7 +38,6 @@ const config = {
/*
* server configure
*/
registryPort: 7001,
webPort: 7002,
bindingHost: '127.0.0.1', // only binding on 127.0.0.1 for local access
@@ -32,17 +45,15 @@ const config = {
// debug mode
// if in debug mode, some middleware like limit wont load
// logger module will print to stdout
debug: process.env.NODE_ENV === 'development',
// page mode, enable on development env
pagemock: process.env.NODE_ENV === 'development',
debug: true,
// session secret
sessionSecret: 'cnpmjs.org test session secret',
// max request json body size
jsonLimit: '10mb',
// log dir name
logdir: path.join(dataDir, 'logs'),
logdir: path.join(root, '.tmp', 'logs'),
// update file template dir
uploadDir: path.join(dataDir, 'downloads'),
uploadDir: path.join(root, '.dist'),
// web page viewCache
viewCache: false,
@@ -71,18 +82,28 @@ const config = {
// email notification for errors
// check https://github.com/andris9/Nodemailer for more informations
mail: {
enable: false,
appname: 'cnpmjs.org',
from: 'cnpmjs.org mail sender <adderss@gmail.com>',
service: 'gmail',
auth: {
user: 'address@gmail.com',
pass: 'your password',
},
pass: 'your password'
}
},
// forward Compat with old style
// mail: {
// appname: 'cnpmjs.org',
// sender: 'cnpmjs.org mail sender <adderss@gmail.com>',
// host: 'smtp.gmail.com',
// port: 465,
// user: 'address@gmail.com',
// pass: 'your password',
// ssl: true,
// debug: false
// },
logoURL: 'https://os.alipayobjects.com/rmsportal/oygxuIUkkrRccUz.jpg', // cnpm logo image url
adBanner: '',
logoURL: '//ww4.sinaimg.cn/large/69c1d4acgw1ebfly5kjlij208202oglr.jpg', // cnpm logo image url
customReadmeFile: '', // you can use your custom readme file instead the cnpm one
customFooter: '', // you can add copyright and site total script html here
npmClientName: 'cnpm', // use `${name} install package`
@@ -94,45 +115,30 @@ const config = {
backupFilePrefix: '/cnpm/backup/',
/**
* database config
* mysql config
*/
database: {
db: 'cnpmjs_test',
username: 'root',
password: '',
mysqlServers: [
{
host: '127.0.0.1',
port: 3306,
user: 'root',
password: ''
}
],
mysqlDatabase: 'cnpmjs_test',
mysqlMaxConnections: 4,
mysqlQueryTimeout: 5000,
// the sql dialect of the database
// - currently supported: 'mysql', 'sqlite', 'postgres', 'mariadb'
dialect: 'sqlite',
// custom host; default: 127.0.0.1
host: '127.0.0.1',
// custom port; default: 3306
port: 3306,
// use pooling in order to reduce db connection overload and to increase speed
// currently only for mysql and postgresql (since v1.5.0)
pool: {
maxConnections: 10,
minConnections: 0,
maxIdleTime: 30000,
},
// the storage engine for 'sqlite'
// default store into ~/.cnpmjs.org/data.sqlite
storage: path.join(dataDir, 'data.sqlite'),
logging: !!process.env.SQL_DEBUG,
},
// redis config
// use for koa-limit module as storage
redis: null,
// package tarball store in local filesystem by default
nfs: require('fs-cnpm')({
dir: path.join(dataDir, 'nfs'),
dir: path.join(root, '.tmp', 'dist')
}),
// if set true, will 302 redirect to `nfs.url(dist.key)`
downloadRedirectToNFS: false,
// registry url name
registryHost: 'r.cnpmjs.org',
@@ -141,34 +147,48 @@ const config = {
* registry mode config
*/
// enable private mode or not
// private mode: only admins can publish, other users just can sync package from source npm
// public mode: all users can publish
enablePrivate: false,
// enable private mode, only admin can publish, other use just can sync package from source npm
enablePrivate: true,
// registry scopes, if don't set, means do not support scopes
scopes: [ '@cnpm', '@cnpmtest', '@cnpm-test' ],
scopes: [
'@cnpm',
'@cnpmtest'
],
// redirect @cnpm/private-package => private-package
// forward compatbility for update from lower version cnpmjs.org
adaptScope: true,
// force user publish with scope
// but admins still can publish without scope
forcePublishWithScope: true,
// some registry already have some private packages in global scope
// but we want to treat them as scoped private packages,
// so you can use this white list.
privatePackages: [],
privatePackages: ['private-package'],
/**
* sync configs
*/
// sync dist config
// sync node.js dist from nodejs.org
noticeSyncDistError: true,
disturl: 'http://nodejs.org/dist',
syncDist: false,
// the official npm registry
// cnpm wont directly sync from this one
// but sometimes will request it for some package infomations
// please don't change it if not necessary
officialNpmRegistry: 'https://registry.npmjs.com',
officialNpmReplicate: 'https://replicate.npmjs.com',
officialNpmRegistry: 'https://registry.npmjs.org',
// sync source, upstream registry
// If you want to directly sync from official npm's registry
// please drop them an email first
sourceNpmRegistry: 'https://registry.npm.taobao.org',
sourceNpmRegistry: 'http://registry.npm.taobao.org',
// upstream registry is base on cnpm/cnpmjs.org or not
// if your upstream is official npm registry, please turn it off
@@ -178,7 +198,7 @@ const config = {
syncByInstall: true,
// sync mode select
// none: do not sync any module, proxy all public modules from sourceNpmRegistry
// none: do not sync any module
// exist: only sync exist modules
// all: sync all modules
syncModel: 'none', // 'none', 'all', 'exist'
@@ -198,46 +218,14 @@ const config = {
// sync devDependencies or not, default is false
syncDevDependencies: false,
// changes streaming sync
syncChangesStream: false,
handleSyncRegistry: 'http://127.0.0.1:7001',
// badge subject on http://shields.io/
badgePrefixURL: 'https://img.shields.io/badge',
badgeSubject: 'cnpm',
// custom user service, @see https://github.com/cnpm/cnpmjs.org/wiki/Use-Your-Own-User-Authorization
// when you not intend to ingegrate with your company's user system, then use null, it would
// use the default cnpm user system
userService: null,
// always-auth https://docs.npmjs.com/misc/config#always-auth
// Force npm to always require authentication when accessing the registry, even for GET requests.
alwaysAuth: false,
// if you're behind firewall, need to request through http proxy, please set this
// e.g.: `httpProxy: 'http://proxy.mycompany.com:8080'`
httpProxy: null,
// snyk.io root url
snykUrl: 'https://snyk.io',
};
if (process.env.NODE_ENV !== 'test') {
let customConfig;
if (process.env.NODE_ENV === 'development') {
customConfig = path.join(root, 'config', 'config.js');
} else {
// 1. try to load `$dataDir/config.json` first, not exists then goto 2.
// 2. load config/config.js, everything in config.js will cover the same key in index.js
customConfig = path.join(dataDir, 'config.json');
if (!fs.existsSync(customConfig)) {
customConfig = path.join(root, 'config', 'config.js');
}
}
if (fs.existsSync(customConfig)) {
copy(require(customConfig)).override(config);
}
// load config/config.js, everything in config.js will cover the same key in index.js
var customConfig = path.join(root, 'config/config.js');
if (fs.existsSync(customConfig)) {
copy(require(customConfig)).override(config);
}
mkdirp.sync(config.logdir);
@@ -245,7 +233,7 @@ mkdirp.sync(config.uploadDir);
module.exports = config;
config.loadConfig = function(customConfig) {
config.loadConfig = function (customConfig) {
if (!customConfig) {
return;
}

83
controllers/download.js Normal file
View File

@@ -0,0 +1,83 @@
/**!
* cnpmjs.org - controllers/download.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var moment = require('moment');
var DownloadTotal = require('../proxy/download');
exports.total = function (name, callback) {
if (typeof name === 'function') {
callback = name;
name = null;
}
var end = moment();
var start = end.clone().subtract(1, 'months').startOf('month');
var lastday = end.clone().subtract(1, 'days').format('YYYY-MM-DD');
var lastweekStart = end.clone().subtract(1, 'weeks').startOf('week');
var lastweekEnd = lastweekStart.clone().endOf('week').format('YYYY-MM-DD');
var lastmonthEnd = start.clone().endOf('month').format('YYYY-MM-DD');
var thismonthStart = end.clone().startOf('month').format('YYYY-MM-DD');
var thisweekStart = end.clone().startOf('week').format('YYYY-MM-DD');
start = start.format('YYYY-MM-DD');
end = end.format('YYYY-MM-DD');
lastweekStart = lastweekStart.format('YYYY-MM-DD');
var method = name ? 'getModuleTotal' : 'getTotal';
var args = [start, end];
if (name) {
args.unshift(name);
}
args.push(function (err, rows) {
if (err) {
return callback(err);
}
var download = {
today: 0,
thisweek: 0,
thismonth: 0,
lastday: 0,
lastweek: 0,
lastmonth: 0,
};
for (var i = 0; i < rows.length; i++) {
var r = rows[i];
if (r.date === end) {
download.today += r.count;
}
if (r.date >= thismonthStart) {
download.thismonth += r.count;
}
if (r.date >= thisweekStart) {
download.thisweek += r.count;
}
if (r.date === lastday) {
download.lastday += r.count;
}
if (r.date >= lastweekStart && r.date <= lastweekEnd) {
download.lastweek += r.count;
}
if (r.date >= start && r.date <= lastmonthEnd) {
download.lastmonth += r.count;
}
}
callback(null, download);
});
DownloadTotal[method].apply(DownloadTotal, args);
};
thunkify(exports);

View File

@@ -1,5 +1,5 @@
/**!
* cnpmjs.org - controllers/registry/package/deprecate.js
* cnpmjs.org - controllers/registry/deprecate.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
@@ -14,47 +14,47 @@
* Module dependencies.
*/
const packageService = require('../../../services/package');
var Module = require('../../proxy/module');
module.exports = deprecateVersions;
/**
* @see https://github.com/cnpm/cnpmjs.org/issues/415
*/
function* deprecateVersions() {
const body = this.request.body;
const name = this.params.name || this.params[0];
function* deprecateVersions(next) {
var body = this.request.body;
var name = this.params.name || this.params[0];
const tasks = [];
for (const version in body.versions) {
tasks.push(packageService.getModule(name, version));
var tasks = [];
for (var version in body.versions) {
tasks.push(Module.get(name, version));
}
const rs = yield tasks;
var rs = yield tasks;
const updateTasks = [];
for (let i = 0; i < rs.length; i++) {
const row = rs[i];
var updateTasks = [];
for (var i = 0; i < rs.length; i++) {
var row = rs[i];
if (!row) {
// some version not exists
this.status = 400;
this.body = {
error: 'version_error',
reason: 'Some versions: ' + JSON.stringify(Object.keys(body.versions)) + ' not found',
reason: 'Some versions: ' + JSON.stringify(Object.keys(body.versions)) + ' not found'
};
return;
}
const data = body.versions[row.package.version];
var data = body.versions[row.package.version];
if (typeof data.deprecated === 'string') {
row.package.deprecated = data.deprecated;
updateTasks.push(packageService.updateModulePackage(row.id, row.package));
updateTasks.push(Module.updatePackage(row.id, row.package));
}
}
yield updateTasks;
// update last modified
yield packageService.updateModuleLastModified(name);
yield* Module.updateLastModified(name);
this.status = 201;
this.body = {
ok: true,
ok: true
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,78 +0,0 @@
'use strict';
const packageService = require('../../../services/package');
function ok() {
return {
ok: 'dist-tags updated',
};
}
// GET /-/package/:pkg/dist-tags -- returns the package's dist-tags
exports.index = function* () {
const name = this.params.name || this.params[0];
const rows = yield packageService.listModuleTags(name);
const tags = {};
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
tags[row.tag] = row.version;
}
this.body = tags;
};
// PUT /-/package/:pkg/dist-tags -- Set package's dist-tags to provided object body (removing missing)
exports.save = function* () {
const name = this.params.name || this.params[0];
yield packageService.removeModuleTags(name);
yield exports.update.call(this);
};
// POST /-/package/:pkg/dist-tags -- Add/modify dist-tags from provided object body (merge)
exports.update = function* () {
const name = this.params.name || this.params[0];
const tags = this.request.body;
for (const tag in tags) {
const version = tags[tag];
yield packageService.addModuleTag(name, tag, version);
}
this.status = 201;
this.body = ok();
};
// PUT /-/package/:pkg/dist-tags/:tag -- Set package's dist-tags[tag] to provided string body
// POST /-/package/:pkg/dist-tags/:tag -- Same as PUT /-/package/:pkg/dist-tags/:tag
exports.set = function* () {
const name = this.params.name || this.params[0];
const tag = this.params.tag || this.params[1];
const version = this.request.body;
// make sure version exists
const pkg = yield packageService.getModule(name, version);
if (!pkg) {
this.status = 400;
this.body = {
error: 'version_error',
reason: name + '@' + version + ' not exists',
};
return;
}
yield packageService.addModuleTag(name, tag, version);
this.status = 201;
this.body = ok();
};
// DELETE /-/package/:pkg/dist-tags/:tag -- Remove tag from dist-tags
exports.destroy = function* () {
const name = this.params.name || this.params[0];
const tag = this.params.tag || this.params[1];
if (tag === 'latest') {
this.status = 400;
this.body = {
error: 'dist_tag_error',
reason: 'Can\'t not delete latest tag',
};
return;
}
yield packageService.removeModuleTagsByNames(name, tag);
this.body = ok();
};

View File

@@ -1,106 +0,0 @@
'use strict';
const debug = require('debug')('cnpmjs.org:controllers:registry:download');
const mime = require('mime');
const utility = require('utility');
const defer = require('co-defer');
const is = require('is-type-of');
const nfs = require('../../../common/nfs');
const logger = require('../../../common/logger');
const common = require('../../../lib/common');
const downloadAsReadStream = require('../../utils').downloadAsReadStream;
const packageService = require('../../../services/package');
const downloadTotalService = require('../../../services/download_total');
const config = require('../../../config');
let _downloads = {};
module.exports = function* download(next) {
const name = this.params.name || this.params[0];
const filename = this.params.filename || this.params[1];
const version = filename.slice(name.length + 1, -4);
const row = yield packageService.getModule(name, version);
// can not get dist
let url = null;
if (typeof nfs.url === 'function') {
if (is.generatorFunction(nfs.url)) {
url = yield nfs.url(common.getCDNKey(name, filename));
} else {
url = nfs.url(common.getCDNKey(name, filename));
}
}
debug('download %s %s %s %s', name, filename, version, url);
if (!row || !row.package || !row.package.dist) {
if (!url) {
return yield next;
}
this.status = 302;
this.set('Location', url);
_downloads[name] = (_downloads[name] || 0) + 1;
return;
}
_downloads[name] = (_downloads[name] || 0) + 1;
if (config.downloadRedirectToNFS && url) {
this.status = 302;
this.set('Location', url);
return;
}
const dist = row.package.dist;
if (!dist.key) {
// try to use nsf.url() first
url = url || dist.tarball;
debug('get tarball by 302, url: %s', url);
this.status = 302;
this.set('Location', url);
return;
}
// else use `dist.key` to get tarball from nfs
if (typeof dist.size === 'number' && dist.size > 0) {
this.length = dist.size;
}
this.type = mime.lookup(dist.key);
this.attachment(filename);
this.etag = dist.shasum;
this.body = yield downloadAsReadStream(dist.key);
};
defer.setInterval(function* () {
// save download count
const totals = [];
for (const name in _downloads) {
const count = _downloads[name];
totals.push([ name, count ]);
}
_downloads = {};
if (totals.length === 0) {
return;
}
debug('save download total: %j', totals);
const date = utility.YYYYMMDD();
for (let i = 0; i < totals.length; i++) {
const item = totals[i];
const name = item[0];
const count = item[1];
try {
yield downloadTotalService.plusModuleTotal({ name, date, count });
} catch (err) {
if (err.name !== 'SequelizeUniqueConstraintError') {
err.message += '; name: ' + name + ', count: ' + count + ', date: ' + date;
logger.error(err);
}
// save back to _downloads, try again next time
_downloads[name] = (_downloads[name] || 0) + count;
}
}
}, 5000 + Math.ceil(Math.random() * 1000));

View File

@@ -1,66 +0,0 @@
'use strict';
const DownloadTotal = require('../../../services/download_total');
const DATE_REG = /^\d{4}-\d{2}-\d{2}$/;
module.exports = function* downloadTotal() {
let range = this.params.range || this.params[0] || '';
const name = this.params.name || this.params[1];
range = range.split(':');
if (range.length !== 2
|| !range[0].match(DATE_REG)
|| !range[1].match(DATE_REG)) {
this.status = 400;
this.body = {
error: 'range_error',
reason: 'range must be YYYY-MM-DD:YYYY-MM-DD style',
};
return;
}
this.body = name
? yield getPackageTotal(name, range[0], range[1])
: yield getTotal(range[0], range[1]);
};
function* getPackageTotal(name, start, end) {
const res = yield DownloadTotal.getModuleTotal(name, start, end);
const downloads = res.map(function(row) {
return {
day: row.date,
downloads: row.count,
};
});
downloads.sort(function(a, b) {
return a.day > b.day ? 1 : -1;
});
return {
downloads,
package: name,
start,
end,
};
}
function* getTotal(start, end) {
const res = yield DownloadTotal.getTotal(start, end);
const downloads = res.map(function(row) {
return {
day: row.date,
downloads: row.count,
};
});
downloads.sort(function(a, b) {
return a.day > b.day ? 1 : -1;
});
return {
downloads,
start,
end,
};
}

View File

@@ -1,189 +0,0 @@
'use strict';
const debug = require('debug')('cnpmjs.org:controllers:registry:package:list');
const packageService = require('../../../services/package');
const common = require('../../../lib/common');
const SyncModuleWorker = require('../../sync_module_worker');
const config = require('../../../config');
/**
* list all version of a module
* GET /:name
*/
module.exports = function* list() {
const orginalName = this.params.name || this.params[0];
const name = orginalName;
const rs = yield [
packageService.getModuleLastModified(name),
packageService.listModuleTags(name),
];
let modifiedTime = rs[0];
const tags = rs[1];
debug('show %s(%s), last modified: %s, tags: %j', name, orginalName, modifiedTime, tags);
if (modifiedTime) {
// find out the latest modfied time
// because update tags only modfied tag, wont change module gmt_modified
for (const tag of tags) {
if (tag.gmt_modified > modifiedTime) {
modifiedTime = tag.gmt_modified;
}
}
// must set status first
this.status = 200;
if (this.fresh) {
debug('%s not change at %s, 304 return', name, modifiedTime);
this.status = 304;
return;
}
}
const r = yield [
packageService.listModulesByName(name),
packageService.listStarUserNames(name),
packageService.listMaintainers(name),
];
const rows = r[0];
let starUsers = r[1];
const maintainers = r[2];
debug('show %s got %d rows, %d tags, %d star users, maintainers: %j',
name, rows.length, tags.length, starUsers.length, maintainers);
const starUserMap = {};
for (const starUser of starUsers) {
if (starUser[0] !== '"' && starUser[0] !== "'") {
starUserMap[starUser] = true;
}
}
starUsers = starUserMap;
if (rows.length === 0) {
// check if unpublished
const unpublishedInfo = yield packageService.getUnpublishedModule(name);
debug('show unpublished %j', unpublishedInfo);
if (unpublishedInfo) {
this.status = 404;
this.body = {
_id: orginalName,
name: orginalName,
time: {
modified: unpublishedInfo.package.time,
unpublished: unpublishedInfo.package,
},
_attachments: {},
};
return;
}
}
// if module not exist in this registry,
// sync the module backend and return package info from official registry
if (rows.length === 0) {
if (!this.allowSync) {
this.status = 404;
this.body = {
error: 'not_found',
reason: 'document not found',
};
return;
}
// start sync
const logId = yield SyncModuleWorker.sync(name, 'sync-by-install');
debug('start sync %s, get log id %s', name, logId);
return this.redirect(config.officialNpmRegistry + this.url);
}
let latestMod = null;
let readme = null;
// set tags
const distTags = {};
for (const t of tags) {
distTags[t.tag] = t.version;
}
// set versions and times
const versions = {};
let times = {};
const attachments = {};
let createdTime = null;
for (const row of rows) {
const pkg = row.package;
// pkg is string ... ignore it
if (typeof pkg === 'string') {
continue;
}
common.setDownloadURL(pkg, this);
pkg._cnpm_publish_time = row.publish_time;
pkg.publish_time = pkg.publish_time || row.publish_time;
versions[pkg.version] = pkg;
const t = times[pkg.version] = row.publish_time ? new Date(row.publish_time) : row.gmt_modified;
if ((!distTags.latest && !latestMod) || distTags.latest === pkg.version) {
latestMod = row;
readme = pkg.readme;
}
delete pkg.readme;
if (maintainers.length > 0) {
pkg.maintainers = maintainers;
}
if (!createdTime || t < createdTime) {
createdTime = t;
}
}
if (modifiedTime && createdTime) {
const ts = {
modified: modifiedTime,
created: createdTime,
};
for (const t in times) {
ts[t] = times[t];
}
times = ts;
}
if (!latestMod) {
latestMod = rows[0];
}
const rev = String(latestMod.id);
const pkg = latestMod.package;
if (tags.length === 0) {
// some sync error reason, will cause tags missing
// set latest tag at least
distTags.latest = pkg.version;
}
const info = {
_id: orginalName,
_rev: rev,
name: orginalName,
description: pkg.description,
'dist-tags': distTags,
maintainers: pkg.maintainers,
time: times,
users: starUsers,
author: pkg.author,
repository: pkg.repository,
versions,
readme,
_attachments: attachments,
};
info.readmeFilename = pkg.readmeFilename;
info.homepage = pkg.homepage;
info.bugs = pkg.bugs;
info.license = pkg.license;
debug('show module %s: %s, latest: %s', orginalName, rev, latestMod.version);
this.jsonp = info;
};

View File

@@ -1,16 +0,0 @@
'use strict';
const packageService = require('../../../services/package');
// GET /-/all
// List all packages names
// https://github.com/npm/npm-registry-client/blob/master/lib/get.js#L86
module.exports = function* () {
const updated = Date.now();
const names = yield packageService.listAllPublicModuleNames();
const result = { _updated: updated };
names.forEach(function(name) {
result[name] = true;
});
this.body = result;
};

View File

@@ -1,29 +0,0 @@
/**!
* list packages by username
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <m@fengmk2.com> (http://fengmk2.com)
*/
'use strict';
/**
* Module dependencies.
*/
const packageService = require('../../../services/package');
module.exports = function* () {
const username = this.params.user;
const packages = yield packageService.listModulesByUser(username);
this.body = {
user: {
name: username,
},
packages,
};
};

View File

@@ -1,26 +0,0 @@
/**!
* list package's dependents
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <m@fengmk2.com> (http://fengmk2.com)
*/
'use strict';
/**
* Module dependencies.
*/
const packageService = require('../../../services/package');
module.exports = function* () {
const name = this.params.name || this.params[0];
const dependents = yield packageService.listDependents(name);
this.body = {
dependents,
};
};

View File

@@ -1,21 +0,0 @@
/**!
* Copyright(c) cnpm and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.com)
*/
'use strict';
/**
* Module dependencies.
*/
const packageService = require('../../../services/package');
// GET /-/short
// List all packages names only
module.exports = function* () {
this.body = yield packageService.listAllPublicModuleNames();
};

View File

@@ -1,45 +0,0 @@
'use strict';
const packageService = require('../../../services/package');
const A_WEEK_MS = 3600000 * 24 * 7;
// GET /-/all/since?stale=update_after&startkey={key}
// List packages names since startkey
// https://github.com/npm/npm-registry-client/blob/master/lib/get.js#L89
module.exports = function* listSince() {
const query = this.query;
if (query.stale !== 'update_after') {
this.status = 400;
this.body = {
error: 'query_parse_error',
reason: 'Invalid value for `stale`.',
};
return;
}
let startkey = Number(query.startkey);
if (!startkey) {
this.status = 400;
this.body = {
error: 'query_parse_error',
reason: 'Invalid value for `startkey`.',
};
return;
}
const updated = Date.now();
if (updated - startkey > A_WEEK_MS) {
startkey = updated - A_WEEK_MS;
console.warn('[%s] list modules since time out of range: query: %j, ip: %s',
Date(), query, this.ip);
}
const names = yield packageService.listPublicModuleNamesSince(startkey);
const result = { _updated: updated };
names.forEach(function(name) {
result[name] = true;
});
this.body = result;
};

View File

@@ -1,67 +0,0 @@
/**!
* cnpmjs.org - controllers/registry/package/remove.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
const debug = require('debug')('cnpmjs.org:controllers:registry:package:remove');
const urlparse = require('url').parse;
const packageService = require('../../../services/package');
const totalService = require('../../../services/total');
const nfs = require('../../../common/nfs');
const logger = require('../../../common/logger');
// DELETE /:name/-rev/:rev
// https://github.com/npm/npm-registry-client/blob/master/lib/unpublish.js#L25
module.exports = function* remove(next) {
const name = this.params.name || this.params[0];
const rev = this.params.rev || this.params[1];
debug('remove all the module with name: %s, id: %s', name, rev);
const mods = yield packageService.listModulesByName(name);
debug('removeAll module %s: %d', name, mods.length);
const mod = mods[0];
if (!mod) {
return yield next;
}
yield [
packageService.removeModulesByName(name),
packageService.removeModuleTags(name),
totalService.plusDeleteModule(),
];
const keys = [];
for (let i = 0; i < mods.length; i++) {
const row = mods[i];
const dist = row.package.dist;
let key = dist.key;
if (!key) {
key = urlparse(dist.tarball).pathname;
}
key && keys.push(key);
}
try {
yield keys.map(function(key) {
return nfs.remove(key);
});
} catch (err) {
logger.error(err);
}
// remove the maintainers
yield packageService.removeAllMaintainers(name);
this.body = { ok: true };
};

View File

@@ -1,74 +0,0 @@
/**!
* cnpmjs.org - controllers/registry/package/remove_version.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
const debug = require('debug')('cnpmjs.org:controllers:registry:package:remove_version');
const packageService = require('../../../services/package');
const nfs = require('../../../common/nfs');
const logger = require('../../../common/logger');
const getCDNKey = require('../../../lib/common').getCDNKey;
// DELETE /:name/download/:filename/-rev/:rev
// https://github.com/npm/npm-registry-client/blob/master/lib/unpublish.js#L97
module.exports = function* removeOneVersion(next) {
const name = this.params.name || this.params[0];
const filename = this.params.filename || this.params[1];
const id = Number(this.params.rev || this.params[2]);
// cnpmjs.org-2.0.0.tgz
let version = filename.split(name + '-')[1];
if (version) {
// 2.0.0.tgz
version = version.substring(0, version.lastIndexOf('.tgz'));
}
if (!version) {
return yield next;
}
debug('remove tarball with filename: %s, version: %s, revert to => rev id: %s', filename, version, id);
if (isNaN(id)) {
return yield next;
}
const rs = yield [
packageService.getModuleById(id),
packageService.getModule(name, version),
];
const revertTo = rs[0];
const mod = rs[1]; // module need to delete
if (!mod || mod.name !== name) {
return yield next;
}
let key = mod.package && mod.package.dist && mod.package.dist.key;
if (!key) {
key = getCDNKey(mod.name, filename);
}
if (revertTo && revertTo.package) {
debug('removing key: %s from nfs, revert to %s@%s', key, revertTo.name, revertTo.package.version);
} else {
debug('removing key: %s from nfs, no revert mod', key);
}
try {
yield nfs.remove(key);
} catch (err) {
logger.error(err);
}
// remove version from table
yield packageService.removeModulesByNameAndVersions(name, [ version ]);
debug('removed %s@%s', name, version);
this.body = { ok: true };
};

View File

@@ -1,222 +0,0 @@
'use strict';
const debug = require('debug')('cnpmjs.org:controllers:registry:package:save');
const crypto = require('crypto');
const deprecateVersions = require('./deprecate');
const packageService = require('../../../services/package');
const common = require('../../../lib/common');
const nfs = require('../../../common/nfs');
const config = require('../../../config');
// old flows:
// 1. add()
// 2. upload()
// 3. updateLatest()
//
// new flows: only one request
// PUT /:name
// https://github.com/npm/npm-registry-client/blob/master/lib/publish.js#L84
module.exports = function* save(next) {
// 'dist-tags': { latest: '0.0.2' },
// _attachments:
// { 'nae-sandbox-0.0.2.tgz':
// { content_type: 'application/octet-stream',
// data: 'H4sIAAAAA
// length: 9883
const pkg = this.request.body;
const username = this.user.name;
const name = this.params.name || this.params[0];
const filename = Object.keys(pkg._attachments || {})[0];
const version = Object.keys(pkg.versions || {})[0];
if (!version) {
this.status = 400;
this.body = {
error: 'version_error',
reason: 'package.versions is empty',
};
return;
}
// check maintainers
const result = yield packageService.authMaintainer(name, username);
if (!result.isMaintainer) {
this.status = 403;
this.body = {
error: 'forbidden user',
reason: username + ' not authorized to modify ' + name +
', please contact maintainers: ' + result.maintainers.join(', '),
};
return;
}
if (!filename) {
let hasDeprecated = false;
for (const v in pkg.versions) {
const row = pkg.versions[v];
if (typeof row.deprecated === 'string') {
hasDeprecated = true;
break;
}
}
if (hasDeprecated) {
return yield deprecateVersions.call(this, next);
}
this.status = 400;
this.body = {
error: 'attachment_error',
reason: 'package._attachments is empty',
};
return;
}
const attachment = pkg._attachments[filename];
const versionPackage = pkg.versions[version];
const maintainers = versionPackage.maintainers;
// should never happened in normal request
if (!maintainers) {
this.status = 400;
this.body = {
error: 'maintainers error',
reason: 'request body need maintainers',
};
return;
}
// notice that admins can not publish to all modules
// (but admins can add self to maintainers first)
// make sure user in auth is in maintainers
// should never happened in normal request
const m = maintainers.filter(function(maintainer) {
return maintainer.name === username;
});
if (m.length === 0) {
this.status = 403;
this.body = {
error: 'maintainers error',
reason: username + ' does not in maintainer list',
};
return;
}
// TODO: add this info into some table
versionPackage._publish_on_cnpm = true;
const distTags = pkg['dist-tags'] || {};
const tags = []; // tag, version
for (const t in distTags) {
tags.push([ t, distTags[t] ]);
}
if (tags.length === 0) {
this.status = 400;
this.body = {
error: 'invalid',
reason: 'dist-tags should not be empty',
};
return;
}
debug('%s publish new %s:%s, attachment size: %s, maintainers: %j, distTags: %j',
username, name, version, attachment.length, versionPackage.maintainers, distTags);
const exists = yield packageService.getModule(name, version);
let shasum;
if (exists) {
this.status = 403;
this.body = {
error: 'forbidden',
reason: 'cannot modify pre-existing version: ' + version,
};
return;
}
// upload attachment
const tarballBuffer = new Buffer(attachment.data, 'base64');
if (tarballBuffer.length !== attachment.length) {
this.status = 403;
this.body = {
error: 'size_wrong',
reason: 'Attachment size ' + attachment.length
+ ' not match download size ' + tarballBuffer.length,
};
return;
}
if (!distTags.latest) {
// need to check if latest tag exists or not
const latest = yield packageService.getModuleByTag(name, 'latest');
if (!latest) {
// auto add latest
tags.push([ 'latest', tags[0][1] ]);
debug('auto add latest tag: %j', tags);
}
}
shasum = crypto.createHash('sha1');
shasum.update(tarballBuffer);
shasum = shasum.digest('hex');
const options = {
key: common.getCDNKey(name, filename),
shasum,
};
const uploadResult = yield nfs.uploadBuffer(tarballBuffer, options);
debug('upload %j', uploadResult);
const dist = {
shasum,
size: attachment.length,
};
// if nfs upload return a key, record it
if (uploadResult.url) {
dist.tarball = uploadResult.url;
} else if (uploadResult.key) {
dist.key = uploadResult.key;
dist.tarball = uploadResult.key;
}
const mod = {
name,
version,
author: username,
package: versionPackage,
};
mod.package.dist = dist;
yield addDepsRelations(mod.package);
const addResult = yield packageService.saveModule(mod);
debug('%s module: save file to %s, size: %d, sha1: %s, dist: %j, version: %s',
addResult.id, dist.tarball, dist.size, shasum, dist, version);
if (tags.length) {
yield tags.map(function(tag) {
// tag: [tagName, version]
return packageService.addModuleTag(name, tag[0], tag[1]);
});
}
// ensure maintainers exists
const maintainerNames = maintainers.map(function(item) {
return item.name;
});
yield packageService.addPrivateModuleMaintainers(name, maintainerNames);
this.status = 201;
this.body = {
ok: true,
rev: String(addResult.id),
};
};
function* addDepsRelations(pkg) {
let dependencies = Object.keys(pkg.dependencies || {});
if (dependencies.length > config.maxDependencies) {
dependencies = dependencies.slice(0, config.maxDependencies);
}
yield packageService.addDependencies(pkg.name, dependencies);
}

View File

@@ -1,74 +0,0 @@
'use strict';
const debug = require('debug')('cnpmjs.org:controllers:registry:package:show');
const semver = require('semver');
const packageService = require('../../../services/package');
const setDownloadURL = require('../../../lib/common').setDownloadURL;
const SyncModuleWorker = require('../../sync_module_worker');
const config = require('../../../config');
/**
* [deprecate] api
*
* get the special version or tag of a module
*
* GET /:name/:version
* GET /:name/:tag
*/
module.exports = function* show() {
const name = this.params.name || this.params[0];
let tag = this.params.version || this.params[1];
if (tag === '*') {
tag = 'latest';
}
const version = semver.valid(tag);
const range = semver.validRange(tag);
let mod;
if (version) {
mod = yield packageService.getModule(name, version);
} else if (range) {
mod = yield packageService.getModuleByRange(name, range);
} else {
mod = yield packageService.getModuleByTag(name, tag);
}
if (mod) {
setDownloadURL(mod.package, this);
mod.package._cnpm_publish_time = mod.publish_time;
mod.package.publish_time = mod.package.publish_time || mod.publish_time;
const rs = yield [
packageService.listMaintainers(name),
packageService.listModuleTags(name),
];
const maintainers = rs[0];
if (maintainers.length > 0) {
mod.package.maintainers = maintainers;
}
const tags = rs[1];
const distTags = {};
for (let i = 0; i < tags.length; i++) {
const t = tags[i];
distTags[t.tag] = t.version;
}
// show tags for npminstall faster download
mod.package['dist-tags'] = distTags;
this.jsonp = mod.package;
return;
}
// if not fond, sync from source registry
if (!this.allowSync) {
this.status = 404;
this.jsonp = {
error: 'not exist',
reason: 'version not found: ' + version,
};
return;
}
// start sync
const logId = yield SyncModuleWorker.sync(name, 'sync-by-install');
debug('start sync %s, get log id %s', name, logId);
this.redirect(config.officialNpmRegistry + this.url);
};

View File

@@ -1,55 +0,0 @@
'use strict';
const debug = require('debug')('cnpmjs.org:controllers:registry:package:tag');
const semver = require('semver');
const util = require('util');
const packageService = require('../../../services/package');
// PUT /:name/:tag
// https://github.com/npm/npm-registry-client/blob/master/lib/tag.js#L4
// this.request("PUT", uri+"/"+tagName, { body : JSON.stringify(version) }, cb)
module.exports = function* tag() {
const version = this.request.body;
const name = this.params.name || this.params[0];
const tag = this.params.tag || this.params[1];
debug('tag %j to %s/%s', version, name, tag);
if (!version) {
this.status = 400;
this.body = {
error: 'version_missed',
reason: 'version not found',
};
return;
}
if (!semver.valid(version)) {
this.status = 403;
const reason = util.format('setting tag %s to invalid version: %s: %s/%s',
tag, version, name, tag);
this.body = {
error: 'forbidden',
reason,
};
return;
}
const mod = yield packageService.getModule(name, version);
if (!mod) {
this.status = 403;
const reason = util.format('setting tag %s to unknown version: %s: %s/%s',
tag, version, name, tag);
this.body = {
error: 'forbidden',
reason,
};
return;
}
const row = yield packageService.addModuleTag(name, tag, version);
this.status = 201;
this.body = {
ok: true,
modified: row.gmt_modified,
};
};

View File

@@ -1,163 +0,0 @@
'use strict';
const debug = require('debug')('cnpmjs.org:controllers:registry:package:update');
const packageService = require('../../../services/package');
const userService = require('../../../services/user');
const config = require('../../../config');
// PUT /:name/-rev/:rev
//
// * remove with versions, then will `DELETE /:name/download/:filename/-rev/:rev`
// * ...
module.exports = function* update(next) {
const name = this.params.name || this.params[0];
debug('update module %s, %s, %j', this.url, name, this.request.body);
const body = this.request.body;
if (body.versions) {
yield updateVersions.call(this, next);
} else if (body.maintainers) {
yield updateMaintainers.call(this, next);
} else {
yield next;
}
};
// update with versions
// https://github.com/npm/npm-registry-client/blob/master/lib/unpublish.js#L63
function* updateVersions(next) {
const name = this.params.name || this.params[0];
// left versions
const versions = this.request.body.versions;
// step1: list all the versions
const mods = yield packageService.listModulesByName(name);
debug('removeWithVersions module %s, left versions %j, %s mods',
name, Object.keys(versions), mods && mods.length);
if (!mods || !mods.length) {
return yield next;
}
// step3: calculate which versions need to remove and
// which versions need to remain
const removeVersions = [];
const removeVersionMaps = {};
const remainVersions = [];
for (let i = 0; i < mods.length; i++) {
const v = mods[i].version;
if (!versions[v]) {
removeVersions.push(v);
removeVersionMaps[v] = true;
} else {
remainVersions.push(v);
}
}
if (!removeVersions.length) {
debug('no versions need to remove');
this.status = 201;
this.body = { ok: true };
return;
}
debug('remove versions: %j, remain versions: %j', removeVersions, remainVersions);
// step 4: remove all the versions which need to remove
// let removeTar do remove versions from module table
const tags = yield packageService.listModuleTags(name);
const removeTags = [];
let latestRemoved = false;
tags.forEach(function(tag) {
// this tag need be removed
if (removeVersionMaps[tag.version]) {
removeTags.push(tag.id);
if (tag.tag === 'latest') {
latestRemoved = true;
}
}
});
debug('remove tags: %j', removeTags);
if (removeTags.length) {
// step 5: remove all the tags
yield packageService.removeModuleTagsByIds(removeTags);
if (latestRemoved && remainVersions[0]) {
debug('latest tags removed, generate a new latest tag with new version: %s',
remainVersions[0]);
// step 6: insert new latest tag
yield packageService.addModuleTag(name, 'latest', remainVersions[0]);
}
}
// step 7: update last modified, make sure etag change
yield packageService.updateModuleLastModified(name);
this.status = 201;
this.body = { ok: true };
}
function* updateMaintainers() {
const name = this.params.name || this.params[0];
const body = this.request.body;
debug('updateMaintainers module %s, %j', name, body);
const usernames = body.maintainers.map(function(user) {
return user.name;
});
if (usernames.length === 0) {
this.status = 403;
this.body = {
error: 'invalid operation',
reason: 'Can not remove all maintainers',
};
return;
}
if (config.customUserService) {
// ensure new authors are vaild
const maintainers = yield packageService.listMaintainerNamesOnly(name);
const map = {};
const newNames = [];
for (const maintainer of maintainers) {
map[maintainer] = 1;
}
for (const username of usernames) {
if (map[username] !== 1) {
newNames.push(username);
}
}
if (newNames.length > 0) {
const users = yield userService.list(newNames);
const map = {};
for (const user of users) {
map[user.login] = 1;
}
const invailds = [];
for (const username of newNames) {
if (map[username] !== 1) {
invailds.push(username);
}
}
if (invailds.length > 0) {
this.status = 403;
this.body = {
error: 'invalid user name',
reason: 'User: `' + invailds.join(', ') + '` not exists',
};
return;
}
}
}
const r = yield packageService.updatePrivateModuleMaintainers(name, usernames);
debug('result: %j', r);
this.status = 201;
this.body = {
ok: true,
id: name,
rev: this.params.rev || this.params[1],
};
}

View File

@@ -0,0 +1,291 @@
/**!
* cnpmjs.org - controllers/registry/user.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var debug = require('debug')('cnpmjs.org:controllers:registry:user');
var utility = require('utility');
var crypto = require('crypto');
var UserService = require('../../services/user');
var User = require('../../proxy/user');
var config = require('../../config');
var common = require('../../lib/common');
exports.show = function* (next) {
var name = this.params.name;
var isAdmin = common.isAdmin(name);
var scopes = config.scopes || [];
if (config.customUserService) {
var customUser = yield* UserService.get(name);
if (customUser) {
isAdmin = !!customUser.site_admin;
scopes = customUser.scopes;
var data = {
user: customUser
};
yield* User.saveCustomUser(data);
}
}
var user = yield* User.get(name);
if (!user) {
return yield* next;
}
var data = user.json;
if (!data) {
data = {
_id: 'org.couchdb.user:' + user.name,
_rev: user.rev,
name: user.name,
email: user.email,
type: 'user',
roles: [],
date: user.gmt_modified,
};
}
if (data.login) {
// custom user format
// convert to npm user format
data = {
_id: 'org.couchdb.user:' + user.name,
_rev: user.rev,
name: user.name,
email: user.email,
type: 'user',
roles: [],
date: user.gmt_modified,
avatar: data.avatar_url,
fullname: data.name || data.login,
homepage: data.html_url,
};
}
data._cnpm_meta = {
id: user.id,
npm_user: user.npm_user === 1,
custom_user: user.npm_user === 2,
gmt_create: user.gmt_create,
gmt_modified: user.gmt_modified,
admin: isAdmin,
scopes: scopes,
};
this.body = data;
};
function ensurePasswordSalt(user, body) {
if (!user.password_sha && body.password) {
// create password_sha on server
user.salt = crypto.randomBytes(30).toString('hex');
user.password_sha = utility.sha1(body.password + user.salt);
}
}
// npm 1.4.4
// add new user first
// @see https://github.com/npm/npm-registry-client/commit/effb4bc88d443f764f2c2e8b4dd583cc72cf6084
// PUT /-/user/org.couchdb.user:mk2 { accept: 'application/json',
// 'accept-encoding': 'gzip',
// 'user-agent': 'node/v0.11.12 darwin x64',
// host: '127.0.0.1:7001',
// 'content-type': 'application/json',
// 'content-length': '150',
// connection: 'close' } { name: 'mk2',
// password: '123456',
// email: 'fengmk2@gmail.com',
// _id: 'org.couchdb.user:mk2',
// type: 'user',
// roles: [],
// date: '2014-03-15T02:33:19.465Z' }
// old npm flow
// json:
// PUT /-/user/org.couchdb.user:mk2 { accept: 'application/json',
// 'user-agent': 'node/v0.8.26 darwin x64',
// host: '127.0.0.1:7001',
// 'content-type': 'application/json',
// 'content-length': '258',
// connection: 'keep-alive' }
// { name: 'mk2',
// salt: '12351936478446a5466d4fb1633b80f3838b4caaa03649a885ac722cd6',
// password_sha: '123408912a6db1d96b132a90856d99db029cef3d',
// email: 'fengmk2@gmail.com',
// _id: 'org.couchdb.user:mk2',
// type: 'user',
// roles: [],
// date: '2014-03-15T02:39:25.696Z' }
exports.add = function* () {
var name = this.params.name;
var body = this.request.body || {};
var user = {
name: body.name,
// salt: body.salt,
// password_sha: body.password_sha,
email: body.email,
ip: this.ip || '0.0.0.0',
// roles: body.roles || [],
};
ensurePasswordSalt(user, body);
if (!body.password || !user.name || !user.salt || !user.password_sha || !user.email) {
this.status = 422;
this.body = {
error: 'paramError',
reason: 'params missing, name, email or password missing.'
};
return;
}
debug('add user: %j', body);
var loginedUser;
try {
loginedUser = yield UserService.auth(body.name, body.password);
} catch (err) {
this.status = err.status || 500;
this.body = {
error: err.name,
reason: err.message
};
return;
}
if (loginedUser) {
var rev = Date.now() + '-' + loginedUser.login;
if (config.customUserService) {
// make sure sync user meta to cnpm database
var data = user;
data.rev = rev;
data.user = loginedUser;
yield* User.saveCustomUser(data);
}
this.status = 201;
this.body = {
ok: true,
id: 'org.couchdb.user:' + loginedUser.login,
rev: rev,
};
return;
}
if (config.customUserService) {
// user login fail, not allow to add new user
this.status = 401;
this.body = {
error: 'unauthorized',
reason: 'Login fail, please check your login name and password'
};
return;
}
var existUser = yield User.get(name);
if (existUser) {
this.status = 409;
this.body = {
error: 'conflict',
reason: 'User ' + name + ' already exists.'
};
return;
}
var result = yield User.add(user);
this.etag = '"' + result.rev + '"';
this.status = 201;
this.body = {
ok: true,
id: 'org.couchdb.user:' + name,
rev: result.rev
};
};
// logined before update, no need to auth user again
// { name: 'admin',
// password: '123123',
// email: 'fengmk2@gmail.com',
// _id: 'org.couchdb.user:admin',
// type: 'user',
// roles: [],
// date: '2014-08-05T16:08:22.645Z',
// _rev: '1-1a18c3d73ba42e863523a399ff3304d8',
// _cnpm_meta:
// { id: 14,
// npm_user: false,
// custom_user: false,
// gmt_create: '2014-08-05T15:46:58.000Z',
// gmt_modified: '2014-08-05T15:46:58.000Z',
// admin: true,
// scopes: [ '@cnpm', '@cnpmtest' ] } }
exports.update = function *(next) {
var name = this.params.name;
var rev = this.params.rev;
if (!name || !rev) {
return yield* next;
}
debug('update: %s, rev: %s, user.name: %s', name, rev, this.user.name);
if (name !== this.user.name) {
// must auth user first
this.status = 401;
this.body = {
error: 'unauthorized',
reason: 'Name is incorrect.'
};
return;
}
var body = this.request.body || {};
var user = {
name: body.name,
// salt: body.salt,
// password_sha: body.password_sha,
email: body.email,
ip: this.ip || '0.0.0.0',
rev: body.rev || body._rev,
// roles: body.roles || [],
};
debug('update user %j', body);
ensurePasswordSalt(user, body);
if (!body.password || !user.name || !user.salt || !user.password_sha || !user.email) {
this.status = 422;
this.body = {
error: 'paramError',
reason: 'params missing, name, email or password missing.'
};
return;
}
var result = yield User.update(user);
if (!result) {
this.status = 409;
this.body = {
error: 'conflict',
reason: 'Document update conflict.'
};
return;
}
this.status = 201;
this.body = {
ok: true,
id: 'org.couchdb.user:' + user.name,
rev: result.rev
};
};

View File

@@ -1,128 +0,0 @@
/**!
* cnpmjs.org - controllers/registry/user/add.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
const ensurePasswordSalt = require('./common').ensurePasswordSalt;
const userService = require('../../../services/user');
const config = require('../../../config');
// npm 1.4.4
// add new user first
// @see https://github.com/npm/npm-registry-client/commit/effb4bc88d443f764f2c2e8b4dd583cc72cf6084
// PUT /-/user/org.couchdb.user:mk2 { accept: 'application/json',
// 'accept-encoding': 'gzip',
// 'user-agent': 'node/v0.11.12 darwin x64',
// host: '127.0.0.1:7001',
// 'content-type': 'application/json',
// 'content-length': '150',
// connection: 'close' } { name: 'mk2',
// password: '123456',
// email: 'fengmk2@gmail.com',
// _id: 'org.couchdb.user:mk2',
// type: 'user',
// roles: [],
// date: '2014-03-15T02:33:19.465Z' }
// old npm flow
// json:
// PUT /-/user/org.couchdb.user:mk2 { accept: 'application/json',
// 'user-agent': 'node/v0.8.26 darwin x64',
// host: '127.0.0.1:7001',
// 'content-type': 'application/json',
// 'content-length': '258',
// connection: 'keep-alive' }
// { name: 'mk2',
// salt: '12351936478446a5466d4fb1633b80f3838b4caaa03649a885ac722cd6',
// password_sha: '123408912a6db1d96b132a90856d99db029cef3d',
// email: 'fengmk2@gmail.com',
// _id: 'org.couchdb.user:mk2',
// type: 'user',
// roles: [],
// date: '2014-03-15T02:39:25.696Z' }
module.exports = function* addUser() {
const name = this.params.name;
const body = this.request.body || {};
const user = {
name: body.name,
// salt: body.salt,
// password_sha: body.password_sha,
email: body.email,
ip: this.ip || '0.0.0.0',
// roles: body.roles || [],
};
ensurePasswordSalt(user, body);
if (!body.password || !user.name || !user.salt || !user.password_sha || !user.email) {
this.status = 422;
this.body = {
error: 'paramError',
reason: 'params missing, name, email or password missing.',
};
return;
}
let loginedUser;
try {
loginedUser = yield userService.authAndSave(body.name, body.password);
} catch (err) {
this.status = err.status || 500;
this.body = {
error: err.name,
reason: err.message,
};
return;
}
if (loginedUser) {
this.status = 201;
this.body = {
ok: true,
id: 'org.couchdb.user:' + loginedUser.login,
rev: Date.now() + '-' + loginedUser.login,
};
return;
}
if (config.customUserService) {
// user login fail, not allow to add new user
this.status = 401;
this.body = {
error: 'unauthorized',
reason: 'Login fail, please check your login name and password',
};
return;
}
const existUser = yield userService.get(name);
if (existUser) {
this.status = 409;
this.body = {
error: 'conflict',
reason: 'User ' + name + ' already exists.',
};
return;
}
// add new user
const result = yield userService.add(user);
this.etag = '"' + result.rev + '"';
this.status = 201;
this.body = {
ok: true,
id: 'org.couchdb.user:' + name,
rev: result.rev,
};
};

View File

@@ -1,26 +0,0 @@
/**!
* cnpmjs.org - controllers/registry/user/common.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
const crypto = require('crypto');
const utility = require('utility');
exports.ensurePasswordSalt = function(user, body) {
if (!user.password_sha && body.password) {
// create password_sha on server
user.salt = crypto.randomBytes(30).toString('hex');
user.password_sha = utility.sha1(body.password + user.salt);
}
};

View File

@@ -1,69 +0,0 @@
/**!
* cnpmjs.org - controllers/registry/user/show.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
const userService = require('../../../services/user');
// GET /-/user/org.couchdb.user::name
module.exports = function* show(next) {
const name = this.params.name;
const user = yield userService.getAndSave(name);
if (!user) {
return yield next;
}
let data = user.json;
if (!data) {
data = {
_id: 'org.couchdb.user:' + user.name,
_rev: user.rev,
name: user.name,
email: user.email,
type: 'user',
roles: [],
date: user.gmt_modified,
};
}
if (data.login) {
// custom user format
// convert to npm user format
data = {
_id: 'org.couchdb.user:' + user.name,
_rev: user.rev,
name: user.name,
email: user.email,
type: 'user',
roles: [],
date: user.gmt_modified,
avatar: data.avatar_url,
fullname: data.name || data.login,
homepage: data.html_url,
scopes: data.scopes,
site_admin: data.site_admin,
};
}
data._cnpm_meta = {
id: user.id,
npm_user: user.isNpmUser,
custom_user: !!data.login,
gmt_create: user.gmt_create,
gmt_modified: user.gmt_modified,
};
this.body = data;
};

View File

@@ -1,97 +0,0 @@
/**!
* cnpmjs.org - controllers/registry/user/update.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
const debug = require('debug')('cnpmjs.org:controllers:registry:user:update');
const ensurePasswordSalt = require('./common').ensurePasswordSalt;
const userService = require('../../../services/user');
// logined before update, no need to auth user again
// { name: 'admin',
// password: '123123',
// email: 'fengmk2@gmail.com',
// _id: 'org.couchdb.user:admin',
// type: 'user',
// roles: [],
// date: '2014-08-05T16:08:22.645Z',
// _rev: '1-1a18c3d73ba42e863523a399ff3304d8',
// _cnpm_meta:
// { id: 14,
// npm_user: false,
// custom_user: false,
// gmt_create: '2014-08-05T15:46:58.000Z',
// gmt_modified: '2014-08-05T15:46:58.000Z',
// admin: true,
// scopes: [ '@cnpm', '@cnpmtest' ] } }
module.exports = function* updateUser(next) {
const name = this.params.name;
const rev = this.params.rev;
if (!name || !rev) {
return yield next;
}
debug('update: %s, rev: %s, user.name: %s', name, rev, this.user.name);
if (name !== this.user.name) {
// must auth user first
this.status = 401;
this.body = {
error: 'unauthorized',
reason: 'Name is incorrect.',
};
return;
}
const body = this.request.body || {};
const user = {
name: body.name,
// salt: body.salt,
// password_sha: body.password_sha,
email: body.email,
ip: this.ip || '0.0.0.0',
rev: body.rev || body._rev,
// roles: body.roles || [],
};
debug('update user %j', body);
ensurePasswordSalt(user, body);
if (!body.password || !user.name || !user.salt || !user.password_sha || !user.email) {
this.status = 422;
this.body = {
error: 'paramError',
reason: 'params missing, name, email or password missing.',
};
return;
}
const result = yield userService.update(user);
if (!result) {
this.status = 409;
this.body = {
error: 'conflict',
reason: 'Document update conflict.',
};
return;
}
this.status = 201;
this.body = {
ok: true,
id: 'org.couchdb.user:' + user.name,
rev: result.rev,
};
};

View File

@@ -14,38 +14,57 @@
* Module dependencies.
*/
const packageService = require('../../services/package');
var Module = require('../../proxy/module');
var NpmModuleMaintainer = require('../../proxy/npm_module_maintainer');
// GET /-/by-user/:user
exports.list = function* () {
const users = this.params.user.split('|');
var users = this.params.user.split('|');
if (users.length > 20) {
this.status = 400;
this.body = {
error: 'bad_request',
reason: 'reach max user names limit, must <= 20 user names',
reason: 'reach max user names limit, must <= 20 user names'
};
return;
}
const firstUser = users[0];
var firstUser = users[0];
if (!firstUser) {
// params.user = '|'
this.body = {};
return;
}
const tasks = {};
for (let i = 0; i < users.length; i++) {
const username = users[i];
tasks[username] = packageService.listPublicModuleNamesByUser(username);
}
const data = yield tasks;
for (const k in data) {
if (data[k].length === 0) {
data[k] = undefined;
var data = {};
var r = yield [
NpmModuleMaintainer.listByUsers(users),
// get the first user module by author field
Module.listNamesByAuthor(firstUser),
];
var rows = r[0];
var firstUserModuleNames = r[1];
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
if (data[row.user]) {
data[row.user].push(row.name);
} else {
data[row.user] = [row.name];
}
}
if (firstUserModuleNames.length > 0) {
if (!data[firstUser]) {
data[firstUser] = firstUserModuleNames;
} else {
var items = data[firstUser];
for (var i = 0; i < firstUserModuleNames.length; i++) {
var name = firstUserModuleNames[i];
if (items.indexOf(name) === -1) {
items.push(name);
}
}
}
}
this.body = data;
};

View File

@@ -14,67 +14,52 @@
* Module dependencies.
*/
const debug = require('debug')('cnpmjs.org:controllers:sync');
const Log = require('../services/module_log');
const SyncModuleWorker = require('./sync_module_worker');
const config = require('../config');
var debug = require('debug')('cnpmjs.org:controllers:sync');
var Log = require('../proxy/module_log');
var SyncModuleWorker = require('../proxy/sync_module_worker');
exports.sync = function* () {
const username = this.user.name || 'anonymous';
let name = this.params.name || this.params[0];
let type = 'package';
if (name.indexOf(':') > 0) {
// user:fengmk2
// package:pedding
const splits = name.split(':');
type = splits[0];
name = splits[1];
}
const publish = this.query.publish === 'true';
const noDep = this.query.nodeps === 'true';
var username = this.user.name || 'anonymous';
var name = this.params.name || this.params[0];
var publish = this.query.publish === 'true';
var noDep = this.query.nodeps === 'true';
debug('sync %s with query: %j', name, this.query);
if (type === 'package' && publish && !this.user.isAdmin) {
if (publish && !this.user.isAdmin) {
this.status = 403;
this.body = {
error: 'no_perms',
reason: 'Only admin can publish',
reason: 'Only admin can publish'
};
return;
}
const options = {
type,
publish,
noDep,
syncUpstreamFirst: config.sourceNpmRegistryIsCNpm,
var options = {
publish: publish,
noDep: noDep,
};
const logId = yield SyncModuleWorker.sync(name, username, options);
var logId = yield SyncModuleWorker.sync(name, username, options);
debug('sync %s got log id %j', name, logId);
this.status = 201;
this.body = {
ok: true,
logId,
logId: logId
};
};
exports.getSyncLog = function* (next) {
const logId = Number(this.params.id || this.params[1]);
const offset = Number(this.query.offset) || 0;
if (!logId) { // NaN
this.status = 404;
return;
}
const row = yield Log.get(logId);
// params: [$name, $id] on scope package
var logId = this.params.id || this.params[1];
var offset = Number(this.query.offset) || 0;
var row = yield Log.get(logId);
if (!row) {
return yield next;
return yield* next;
}
let log = row.log.trim();
var log = row.log.trim();
if (offset > 0) {
log = log.split('\n').slice(offset).join('\n');
}
this.body = { ok: true, log };
this.body = {ok: true, log: log};
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +1,31 @@
/**!
* cnpmjs.org - controllers/total.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
*/
'use strict';
const Total = require('../services/total');
const version = require('../package.json').version;
const config = require('../config');
const getDownloadTotal = require('./utils').getDownloadTotal;
/**
* Module dependencies.
*/
const startTime = '' + Date.now();
let cache = null;
var Total = require('../proxy/total');
var Download = require('./download');
var version = require('../package.json').version;
var config = require('../config');
module.exports = function* showTotal() {
if (cache && Date.now() - cache.cache_time < 10000) {
// cache 10 seconds
this.body = cache;
return;
}
var startTime = '' + Date.now();
const r = yield [ Total.get(), getDownloadTotal() ];
const total = r[0];
const download = r[1];
exports.show = function *() {
var r = yield [Total.get(), Download.total()];
var total = r[0];
var download = r[1];
total.download = download;
total.db_name = 'registry';
@@ -27,8 +35,5 @@ module.exports = function* showTotal() {
total.donate = 'https://www.gittip.com/fengmk2';
total.sync_model = config.syncModel;
cache = total;
total.cache_time = Date.now();
this.body = total;
};

View File

@@ -14,25 +14,18 @@
* Module dependencies.
*/
const debug = require('debug')('cnpmjs.org:controllers:utils');
const path = require('path');
const fs = require('fs');
const utility = require('utility');
const ms = require('humanize-ms');
const moment = require('moment');
const downloadTotalService = require('../services/download_total');
const nfs = require('../common/nfs');
const config = require('../config');
var debug = require('debug')('cnpmjs.org:controllers:utils');
var path = require('path');
var fs = require('fs');
var utility = require('utility');
var ms = require('humanize-ms');
var nfs = require('../common/nfs');
var config = require('../config');
const DOWNLOAD_TIMEOUT = ms('10m');
var DOWNLOAD_TIMEOUT = ms('10m');
exports.downloadAsReadStream = function* (key) {
const options = { timeout: DOWNLOAD_TIMEOUT };
if (nfs.createDownloadStream) {
return yield nfs.createDownloadStream(key, options);
}
const tmpPath = path.join(config.uploadDir,
var tmpPath = path.join(config.uploadDir,
utility.randomString() + key.replace(/\//g, '-'));
function cleanup() {
debug('cleanup %s', tmpPath);
@@ -40,127 +33,14 @@ exports.downloadAsReadStream = function* (key) {
}
debug('downloadAsReadStream() %s to %s', key, tmpPath);
try {
yield nfs.download(key, tmpPath, options);
yield nfs.download(key, tmpPath, {timeout: DOWNLOAD_TIMEOUT});
} catch (err) {
debug('downloadAsReadStream() %s to %s error: %s', key, tmpPath, err.stack);
cleanup();
throw err;
}
const tarball = fs.createReadStream(tmpPath);
var tarball = fs.createReadStream(tmpPath);
tarball.once('error', cleanup);
tarball.once('end', cleanup);
return tarball;
};
exports.getDownloadTotal = function* (name) {
let end = moment();
let start = end.clone().subtract(1, 'months').startOf('month');
const lastday = end.clone().subtract(1, 'days').format('YYYY-MM-DD');
let lastweekStart = end.clone().subtract(1, 'weeks').startOf('isoweek');
const lastweekEnd = lastweekStart.clone().endOf('isoweek').format('YYYY-MM-DD');
const lastmonthEnd = start.clone().endOf('month').format('YYYY-MM-DD');
const thismonthStart = end.clone().startOf('month').format('YYYY-MM-DD');
const thisweekStart = end.clone().startOf('isoweek').format('YYYY-MM-DD');
start = start.format('YYYY-MM-DD');
end = end.format('YYYY-MM-DD');
lastweekStart = lastweekStart.format('YYYY-MM-DD');
const method = name ? 'getModuleTotal' : 'getTotal';
const args = [ start, end ];
if (name) {
args.unshift(name);
}
const rows = yield downloadTotalService[method].apply(downloadTotalService, args);
const download = {
today: 0,
thisweek: 0,
thismonth: 0,
lastday: 0,
lastweek: 0,
lastmonth: 0,
};
for (let i = 0; i < rows.length; i++) {
const r = rows[i];
if (r.date === end) {
download.today += r.count;
}
if (r.date >= thismonthStart) {
download.thismonth += r.count;
}
if (r.date >= thisweekStart) {
download.thisweek += r.count;
}
if (r.date === lastday) {
download.lastday += r.count;
}
if (r.date >= lastweekStart && r.date <= lastweekEnd) {
download.lastweek += r.count;
}
if (r.date >= start && r.date <= lastmonthEnd) {
download.lastmonth += r.count;
}
}
return download;
};
exports.setLicense = function(pkg) {
let license;
license = pkg.license || pkg.licenses || pkg.licence || pkg.licences;
if (!license) {
return;
}
if (Array.isArray(license)) {
license = license[0];
}
if (typeof license === 'object') {
pkg.license = {
name: license.name || license.type,
url: license.url,
};
}
if (typeof license === 'string') {
if (license.match(/(http|https)(:\/\/)/ig)) {
pkg.license = {
name: license,
url: license,
};
} else {
pkg.license = {
url: exports.getOssLicenseUrlFromName(license),
name: license,
};
}
}
};
exports.getOssLicenseUrlFromName = function(name) {
const base = 'http://opensource.org/licenses/';
const licenseMap = {
bsd: 'BSD-2-Clause',
mit: 'MIT',
x11: 'MIT',
'mit/x11': 'MIT',
'apache 2.0': 'Apache-2.0',
apache2: 'Apache-2.0',
'apache 2': 'Apache-2.0',
'apache-2': 'Apache-2.0',
apache: 'Apache-2.0',
gpl: 'GPL-3.0',
gplv3: 'GPL-3.0',
gplv2: 'GPL-2.0',
gpl3: 'GPL-3.0',
gpl2: 'GPL-2.0',
lgpl: 'LGPL-2.1',
'lgplv2.1': 'LGPL-2.1',
lgplv2: 'LGPL-2.1',
};
return licenseMap[name.toLowerCase()] ?
base + licenseMap[name.toLowerCase()] : base + name;
};

View File

@@ -5,7 +5,7 @@
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.com)
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
@@ -14,20 +14,16 @@
* Module dependencies.
*/
const utility = require('utility');
const util = require('util');
const config = require('../../config');
const packageService = require('../../services/package');
const DownloadTotal = require('../../services/download_total');
var config = require('../../config');
var Module = require('../../proxy/module');
exports.version = function* () {
let color = 'lightgrey';
let version = 'invalid';
const name = this.params[0];
const tag = this.query.tag || 'latest';
const info = yield packageService.getModuleByTag(name, tag);
if (info) {
version = info.version;
exports.version = function* (next) {
var color = 'lightgrey';
var version = 'invalid';
var name = this.params[0];
var latestTag = yield* Module.getTag(name, 'latest');
if (latestTag) {
version = latestTag.version;
if (/^0\.0\./.test(version)) {
// <0.1.0 & >=0.0.0
color = 'red';
@@ -40,23 +36,14 @@ exports.version = function* () {
}
}
let subject = config.badgeSubject.replace(/\-/g, '--');
if (this.query.subject) {
subject = this.query.subject.replace(/\-/g, '--');
}
var subject = config.badgeSubject.replace(/\-/g, '--');
version = version.replace(/\-/g, '--');
const style = this.query.style || 'flat-square';
const url = util.format(config.badgePrefixURL + '/%s-%s-%s.svg?style=%s',
utility.encodeURIComponent(subject), version, color, utility.encodeURIComponent(style));
this.redirect(url);
};
var url = 'https://img.shields.io/badge/' + subject + '-' + version + '-' + color + '.svg';
if (this.querystring) {
url += '?' + this.querystring;
} else {
url += '?style=flat-square';
}
exports.downloads = function* () {
// https://dn-img-shields-io.qbox.me/badge/downloads-100k/month-brightgreen.svg?style=flat-square
const name = this.params[0];
const count = yield DownloadTotal.getTotalByName(name);
const style = this.query.style || 'flat-square';
const url = util.format(config.badgePrefixURL + '/downloads-%s-brightgreen.svg?style=%s',
count, utility.encodeURIComponent(style));
this.redirect(url);
};

100
controllers/web/dist.js Normal file
View File

@@ -0,0 +1,100 @@
/**!
* cnpmjs.org - controllers/web/dist.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
"use strict";
/**
* Module dependencies.
*/
var debug = require('debug')('cnpmjs.org:controllers:web:dist');
var mime = require('mime');
var urlparse = require('url').parse;
var Dist = require('../../proxy/dist');
var config = require('../../config');
var downloadAsReadStream = require('../utils').downloadAsReadStream;
function padding(max, current, pad) {
pad = pad || ' ';
var left = max - current;
var str = '';
for (var i = 0; i < left; i++) {
str += pad;
}
return str;
}
exports.list = function* (next) {
var params = this.params;
var url = params[0];
if (!url) {
// GET /dist => /dist/
return this.redirect('/dist/');
}
var isDir = url[url.length - 1] === '/';
if (!isDir) {
return yield* download.call(this, next);
}
var items = yield* Dist.listdir(url);
if (url === '/') {
// phantomjs/
items.push({
name: 'phantomjs/',
date: '',
});
}
yield this.render('dist', {
title: 'Mirror index of ' + config.disturl + url,
disturl: config.disturl,
dirname: url,
items: items,
padding: padding
});
};
function* download(next) {
var fullname = this.params[0];
var info = yield* Dist.getfile(fullname);
debug('download %s got %j', fullname, info);
if (!info || !info.url) {
return yield* next;
}
if (/\.(html|js|css|json|txt)$/.test(fullname)) {
if (info.url.indexOf('http') === 0) {
info.url = urlparse(info.url).path;
}
return yield* pipe.call(this, info, false);
}
if (info.url.indexOf('http') === 0) {
return this.redirect(info.url);
}
yield* pipe.call(this, info, true);
}
function* pipe(info, attachment) {
debug('pipe %j, attachment: %s', info, attachment);
// download it from nfs
if (typeof info.size === 'number' && info.size > 0) {
this.length = info.size;
}
this.type = mime.lookup(info.url);
if (attachment) {
this.attachment(info.name);
}
if (info.sha1) {
this.etag = info.sha1;
}
this.body = yield* downloadAsReadStream(info.url);
}

347
controllers/web/package.js Normal file
View File

@@ -0,0 +1,347 @@
/*!
* cnpmjs.org - controllers/web/package.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
*/
'use strict';
/**
* Module dependencies.
*/
var debug = require('debug')('cnpmjs.org:controllers:web:package');
var bytes = require('bytes');
var giturl = require('giturl');
var moment = require('moment');
var eventproxy = require('eventproxy');
var semver = require('semver');
var marked = require('marked');
var gravatar = require('gravatar');
var humanize = require('humanize-number');
var config = require('../../config');
var Module = require('../../proxy/module');
var down = require('../download');
var sync = require('../sync');
var Log = require('../../proxy/module_log');
var ModuleDeps = require('../../proxy/module_deps');
var setDownloadURL = require('../../lib/common').setDownloadURL;
var ModuleStar = require('../../proxy/module_star');
var packageService = require('../../services/package');
var ModuleUnpublished = require('../../proxy/module_unpublished');
exports.display = function* (next) {
var params = this.params;
// normal: {name: $name, version: $version}
// scope: [$name, $version]
var orginalName = params.name || params[0];
var name = orginalName;
var tag = params.version || params[1];
debug('display %s with %j', name, params);
var getPackageMethod;
var getPackageArgs;
var version = semver.valid(tag || '');
if (version) {
getPackageMethod = 'get';
getPackageArgs = [name, version];
} else {
getPackageMethod = 'getByTag';
getPackageArgs = [name, tag || 'latest'];
}
var pkg = yield Module[getPackageMethod].apply(Module, getPackageArgs);
if (!pkg) {
var adaptName = yield* Module.getAdaptName(name);
if (adaptName) {
name = adaptName;
pkg = yield Module[getPackageMethod].apply(Module, [name, getPackageArgs[1]]);
}
}
if (!pkg || !pkg.package) {
// check if unpublished
var unpublishedInfo = yield* ModuleUnpublished.get(name);
debug('show unpublished %j', unpublishedInfo);
if (unpublishedInfo) {
var data = {
name: name,
unpublished: unpublishedInfo.package
};
data.unpublished.time = new Date(data.unpublished.time);
if (data.unpublished.maintainers) {
for (var i = 0; i < data.unpublished.maintainers.length; i++) {
var maintainer = data.unpublished.maintainers[i];
if (maintainer.email) {
maintainer.gravatar = gravatar.url(maintainer.email, {s: '50', d: 'retro'}, true);
}
}
}
yield this.render('package_unpublished', {
package: data
});
return;
}
return yield* next;
}
var r = yield [
down.total(name),
ModuleDeps.list(name),
ModuleStar.listUsers(name),
packageService.listMaintainers(name)
];
var download = r[0];
var dependents = (r[1] || []).map(function (item) {
return item.deps;
});
var users = r[2];
var maintainers = r[3];
pkg.package.fromNow = moment(pkg.publish_time).fromNow();
pkg = pkg.package;
pkg.users = users;
pkg.readme = marked(pkg.readme || '');
if (!pkg.readme) {
pkg.readme = pkg.description || '';
}
if (maintainers.length > 0) {
pkg.maintainers = maintainers;
}
if (pkg.maintainers) {
for (var i = 0; i < pkg.maintainers.length; i++) {
var maintainer = pkg.maintainers[i];
if (maintainer.email) {
maintainer.gravatar = gravatar.url(maintainer.email, {s: '50', d: 'retro'}, true);
}
}
}
if (pkg.contributors) {
// registry.cnpmjs.org/compressible
if (!Array.isArray(pkg.contributors)) {
pkg.contributors = [pkg.contributors];
}
for (var i = 0; i < pkg.contributors.length; i++) {
var contributor = pkg.contributors[i];
if (contributor.email) {
contributor.gravatar = gravatar.url(contributor.email, {s: '50', d: 'retro'}, true);
}
if (config.packagePageContributorSearch || !contributor.url) {
contributor.url = '/~' + encodeURIComponent(contributor.name);
}
}
}
if (pkg.repository && pkg.repository.url) {
pkg.repository.weburl = giturl.parse(pkg.repository.url) || pkg.repository.url;
}
setLicense(pkg);
for (var k in download) {
download[k] = humanize(download[k]);
}
setDownloadURL(pkg, this, config.registryHost);
pkg.dependents = dependents;
if (pkg.dist) {
pkg.dist.size = bytes(pkg.dist.size || 0);
}
if (pkg.name !== orginalName) {
pkg.name = orginalName;
}
// pkg.engines = {
// "python": ">= 0.11.9",
// "node": ">= 0.11.9",
// "node1": ">= 0.8.9",
// "node2": ">= 0.10.9",
// "node3": ">= 0.6.9",
// };
if (pkg.engines) {
for (var k in pkg.engines) {
var engine = String(pkg.engines[k] || '').trim();
var color = 'blue';
if (k.indexOf('node') === 0) {
color = 'yellowgreen';
var version = /(\d+\.\d+\.\d+)/.exec(engine);
if (version) {
version = version[0];
if (/^0\.11\.\d+/.test(version)) {
color = 'red';
} else if (/^0\.10\./.test(version) ||
/^0\.12\./.test(version) ||
/^0\.14\./.test(version) ||
/^[^0]+\./.test(version)) {
color = 'brightgreen';
}
}
}
pkg.engines[k] = {
version: engine,
title: k + ': ' + engine,
badgeURL: 'https://img.shields.io/badge/' + encodeURIComponent(k) +
'-' + encodeURIComponent(engine) + '-' + color + '.svg?style=flat-square',
};
}
}
yield this.render('package', {
title: 'Package - ' + pkg.name,
package: pkg,
download: download
});
};
exports.search = function *(next) {
var params = this.params;
var word = params.word || params[0];
debug('search %j', word);
var result = yield Module.search(word);
var match = null;
for (var i = 0; i < result.searchMatchs.length; i++) {
var p = result.searchMatchs[i];
if (p.name === word) {
match = p;
break;
}
}
// return a json result
if (this.query && this.query.type === 'json') {
this.body = {
keyword: word,
match: match,
packages: result.searchMatchs,
keywords: result.keywordMatchs,
};
this.type = 'application/json; charset=utf-8';
return;
}
yield this.render('search', {
title: 'Keyword - ' + word,
keyword: word,
match: match,
packages: result.searchMatchs,
keywords: result.keywordMatchs,
});
};
exports.rangeSearch = function *(next) {
var startKey = this.query.startkey || '';
if (startKey[0] === '"') {
startKey = startKey.substring(1);
}
if (startKey[startKey.length - 1] === '"') {
startKey = startKey.substring(0, startKey.length - 1);
}
var limit = Number(this.query.limit) || 20;
var result = yield Module.search(startKey, {limit: limit});
var packages = result.searchMatchs.concat(result.keywordMatchs);
var rows = [];
for (var i = 0; i < packages.length; i++) {
var p = packages[i];
var row = {
key: p.name,
count: 1,
value: {
name: p.name,
description: p.description,
}
};
rows.push(row);
}
this.body = {
rows: rows
};
};
exports.displaySync = function* (next) {
var name = this.params.name || this.params[0] || this.query.name;
yield this.render('sync', {
name: name,
title: 'Sync - ' + name,
});
};
exports.listPrivates = function* () {
var packages = yield Module.listPrivates();
yield this.render('private', {
title: 'private packages',
packages: packages
});
};
function setLicense(pkg) {
var license;
license = pkg.license || pkg.licenses || pkg.licence || pkg.licences;
if (!license) {
return ;
}
if (Array.isArray(license)) {
license = license[0];
}
if (typeof license === 'object') {
pkg.license = {
name: license.name || license.type,
url: license.url
};
}
if (typeof license === 'string') {
if (license.match(/(http|https)(:\/\/)/ig)) {
pkg.license = {
name: license,
url: license
};
} else {
pkg.license = {
url: getOssLicenseUrlFromName(license),
name: license
};
}
}
}
exports.setLicense = setLicense;
function getOssLicenseUrlFromName(name) {
var base = 'http://opensource.org/licenses/';
var licenseMap = {
'bsd': 'BSD-2-Clause',
'mit': 'MIT',
'x11': 'MIT',
'mit/x11': 'MIT',
'apache 2.0': 'Apache-2.0',
'apache2': 'Apache-2.0',
'apache 2': 'Apache-2.0',
'apache-2': 'Apache-2.0',
'apache': 'Apache-2.0',
'gpl': 'GPL-3.0',
'gplv3': 'GPL-3.0',
'gplv2': 'GPL-2.0',
'gpl3': 'GPL-3.0',
'gpl2': 'GPL-2.0',
'lgpl': 'LGPL-2.1',
'lgplv2.1': 'LGPL-2.1',
'lgplv2': 'LGPL-2.1'
};
return licenseMap[name.toLowerCase()] ?
base + licenseMap[name.toLowerCase()] : base + name;
}

View File

@@ -1,37 +0,0 @@
/**!
* cnpmjs.org - controllers/web/package/list_privates.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
const packageService = require('../../../services/package');
const config = require('../../../config');
module.exports = function* listPrivates() {
const tasks = {};
for (let i = 0; i < config.scopes.length; i++) {
const scope = config.scopes[i];
tasks[scope] = packageService.listPrivateModulesByScope(scope);
}
if (config.privatePackages && config.privatePackages.length > 0) {
tasks['no scoped'] = packageService.listModules(config.privatePackages);
}
const scopes = yield tasks;
yield this.render('private', {
title: 'private packages',
scopes,
});
};

View File

@@ -1,61 +0,0 @@
/**!
* cnpmjs.org - controllers/web/package/search.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
* fengmk2 <m@fengmk2.com> (http://fengmk2.com)
*/
'use strict';
/**
* Module dependencies.
*/
const debug = require('debug')('cnpmjs.org:controllers:web:package:search');
const packageService = require('../../../services/package');
module.exports = function* search() {
const params = this.params;
const word = params.word || params[0];
let limit = Number(this.query.limit) || 100;
if (limit > 10000) {
limit = 10000;
}
debug('search %j', word);
const result = yield packageService.search(word, {
limit,
});
let match = null;
for (let i = 0; i < result.searchMatchs.length; i++) {
const p = result.searchMatchs[i];
if (p.name === word) {
match = p;
break;
}
}
// return a json result
if (this.query && this.query.type === 'json') {
this.jsonp = {
keyword: word,
match,
packages: result.searchMatchs,
keywords: result.keywordMatchs,
};
return;
}
yield this.render('search', {
title: 'Keyword - ' + word,
keyword: word,
match,
packages: result.searchMatchs,
keywords: result.keywordMatchs,
});
};

View File

@@ -1,49 +0,0 @@
/**!
* cnpmjs.org - controllers/web/package/search_range.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
const packageService = require('../../../services/package');
module.exports = function* searchRange() {
let startKey = this.query.startkey || '';
if (startKey[0] === '"') {
startKey = startKey.substring(1);
}
if (startKey[startKey.length - 1] === '"') {
startKey = startKey.substring(0, startKey.length - 1);
}
const limit = Number(this.query.limit) || 20;
const result = yield packageService.search(startKey, { limit });
const packages = result.searchMatchs.concat(result.keywordMatchs);
const rows = [];
for (let i = 0; i < packages.length; i++) {
const p = packages[i];
const row = {
key: p.name,
count: 1,
value: {
name: p.name,
description: p.description,
},
};
rows.push(row);
}
this.body = {
rows,
};
};

View File

@@ -1,189 +0,0 @@
'use strict';
const debug = require('debug')('cnpmjs.org:controllers:web:package:show');
const bytes = require('bytes');
const giturl = require('giturl');
const moment = require('moment');
const semver = require('semver');
const gravatar = require('gravatar');
const humanize = require('humanize-number');
const config = require('../../../config');
const utils = require('../../utils');
const setDownloadURL = require('../../../lib/common').setDownloadURL;
const renderMarkdown = require('../../../common/markdown').render;
const packageService = require('../../../services/package');
module.exports = function* show(next) {
const params = this.params;
// normal: {name: $name, version: $version}
// scope: [$name, $version]
const orginalName = params.name || params[0];
const name = orginalName;
const tag = params.version || params[1];
debug('display %s with %j', name, params);
let getPackageMethod;
let getPackageArgs;
const version = semver.valid(tag || '');
if (version) {
getPackageMethod = 'getModule';
getPackageArgs = [ name, version ];
} else {
getPackageMethod = 'getModuleByTag';
getPackageArgs = [ name, tag || 'latest' ];
}
let pkg = yield packageService[getPackageMethod].apply(packageService, getPackageArgs);
if (!pkg || !pkg.package) {
// check if unpublished
const unpublishedInfo = yield packageService.getUnpublishedModule(name);
debug('show unpublished %j', unpublishedInfo);
if (unpublishedInfo) {
const data = {
name,
unpublished: unpublishedInfo.package,
};
data.unpublished.time = new Date(data.unpublished.time);
if (data.unpublished.maintainers) {
for (let i = 0; i < data.unpublished.maintainers.length; i++) {
const maintainer = data.unpublished.maintainers[i];
if (maintainer.email) {
maintainer.gravatar = gravatar.url(maintainer.email, { s: '50', d: 'retro' }, true);
}
}
}
yield this.render('package_unpublished', {
package: data,
title: 'Package - ' + name,
});
return;
}
return yield next;
}
const r = yield [
utils.getDownloadTotal(name),
packageService.listDependents(name),
packageService.listStarUserNames(name),
packageService.listMaintainers(name),
];
const download = r[0];
const dependents = r[1];
const users = r[2];
const maintainers = r[3];
pkg.package.fromNow = moment(pkg.publish_time).fromNow();
pkg = pkg.package;
pkg.users = users;
if (pkg.readme && typeof pkg.readme !== 'string') {
pkg.readme = 'readme is not string: ' + JSON.stringify(pkg.readme);
} else {
pkg.readme = renderMarkdown(pkg.readme || '');
}
if (!pkg.readme) {
pkg.readme = pkg.description || '';
}
if (maintainers.length > 0) {
pkg.maintainers = maintainers;
}
if (pkg.maintainers) {
for (let i = 0; i < pkg.maintainers.length; i++) {
const maintainer = pkg.maintainers[i];
if (maintainer.email) {
maintainer.gravatar = gravatar.url(maintainer.email, { s: '50', d: 'retro' }, true);
}
}
}
if (pkg._npmUser) {
pkg.lastPublishedUser = pkg._npmUser;
if (pkg.lastPublishedUser.email) {
pkg.lastPublishedUser.gravatar = gravatar.url(pkg.lastPublishedUser.email, { s: '50', d: 'retro' }, true);
}
}
if (pkg.repository === 'undefined') {
pkg.repository = null;
}
if (pkg.repository && pkg.repository.url) {
pkg.repository.weburl = /^https?:\/\//.test(pkg.repository.url) ? pkg.repository.url : (giturl.parse(pkg.repository.url) || pkg.repository.url);
}
if (!pkg.bugs) {
pkg.bugs = {};
}
utils.setLicense(pkg);
for (const k in download) {
download[k] = humanize(download[k]);
}
setDownloadURL(pkg, this, config.registryHost);
pkg.dependents = dependents;
if (pkg.dist) {
pkg.dist.size = bytes(pkg.dist.size || 0);
}
if (pkg.name !== orginalName) {
pkg.name = orginalName;
}
pkg.registryUrl = '//' + config.registryHost + '/' + pkg.name;
// pkg.engines = {
// "python": ">= 0.11.9",
// "node": ">= 0.11.9",
// "node1": ">= 0.8.9",
// "node2": ">= 0.10.9",
// "node3": ">= 0.6.9",
// };
if (pkg.engines) {
for (const k in pkg.engines) {
const engine = String(pkg.engines[k] || '').trim();
let color = 'blue';
if (k.indexOf('node') === 0) {
color = 'yellowgreen';
let version = /(\d+\.\d+\.\d+)/.exec(engine);
if (version) {
version = version[0];
if (/^0\.11\.\d+/.test(version)) {
color = 'red';
} else if (/^0\.10\./.test(version) ||
/^0\.12\./.test(version) ||
/^0\.14\./.test(version) ||
/^[^0]+\./.test(version)) {
color = 'brightgreen';
}
}
}
pkg.engines[k] = {
version: engine,
title: k + ': ' + engine,
badgeURL: config.badgePrefixURL + '/' + encodeURIComponent(k) +
'-' + encodeURIComponent(engine) + '-' + color + '.svg?style=flat-square',
};
}
}
if (pkg._publish_on_cnpm) {
pkg.isPrivate = true;
} else {
pkg.isPrivate = false;
// add security check badge
pkg.snyk = {
badge: `${config.snykUrl}/test/npm/${pkg.name}/badge.svg?style=flat-square`,
url: `${config.snykUrl}/test/npm/${pkg.name}`,
};
}
yield this.render('package', {
title: 'Package - ' + pkg.name,
package: pkg,
download,
});
};

View File

@@ -1,34 +0,0 @@
/**!
* cnpmjs.org - controllers/web/show_sync.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
module.exports = function* showSync() {
let name = this.params.name || this.params[0] || this.query.name;
if (!name) {
return this.redirect('/');
}
let type = 'package';
if (name.indexOf(':') > 0) {
const splits = name.split(':');
name = splits[1];
type = splits[0];
}
yield this.render('sync', {
type,
name,
title: 'Sync ' + type + ' - ' + name,
});
};

82
controllers/web/user.js Normal file
View File

@@ -0,0 +1,82 @@
/**!
* cnpmjs.org - controllers/web/package.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var config = require('../../config');
var Module = require('../../proxy/module');
var User = require('../../proxy/user');
var UserService = require('../../services/user');
var common = require('../../lib/common');
exports.display = function* (next) {
var name = this.params.name;
var isAdmin = common.isAdmin(name);
var scopes = config.scopes || [];
if (config.customUserService) {
var customUser = yield* UserService.get(name);
if (customUser) {
isAdmin = !!customUser.site_admin;
scopes = customUser.scopes;
var data = {
user: customUser
};
yield* User.saveCustomUser(data);
}
}
var r = yield [Module.listByAuthor(name), User.get(name)];
var packages = r[0] || [];
var user = r[1];
if (!user && !packages.length) {
return yield* next;
}
user = user || {};
var data = {
name: name,
email: user.email,
json: user.json || {}
};
if (data.json.login) {
// custom user format
// convert to npm user format
var json = data.json;
data.json = {
_id: 'org.couchdb.user:' + user.name,
_rev: user.rev,
name: user.name,
email: user.email,
type: 'user',
roles: [],
date: user.gmt_modified,
avatar: json.avatar_url,
fullname: json.name || json.login,
homepage: json.html_url,
im: json.im_url
};
}
yield this.render('profile', {
title: 'User - ' + name,
packages: packages,
user: data,
lastModified: user && user.gmt_modified,
isAdmin: isAdmin,
scopes: scopes
});
};

View File

@@ -1,55 +0,0 @@
'use strict';
const config = require('../../../config');
const packageService = require('../../../services/package');
const userService = require('../../../services/user');
const common = require('../../../lib/common');
module.exports = function* showUser(next) {
const name = this.params.name;
const isAdmin = common.isAdmin(name);
const scopes = config.scopes || [];
const r = yield [ packageService.listModulesByUser(name), userService.getAndSave(name) ];
const packages = r[0];
let user = r[1];
if (!user && !packages.length) {
return yield next;
}
user = user || {};
const data = {
name,
email: user.email,
json: user.json || {},
isNpmUser: user.isNpmUser,
};
if (data.json.login) {
// custom user format
// convert to npm user format
const json = data.json;
data.json = {
_id: 'org.couchdb.user:' + user.name,
_rev: user.rev,
name: user.name,
email: user.email,
type: 'user',
roles: [],
date: user.gmt_modified,
avatar: json.avatar_url,
fullname: json.name || json.login,
homepage: json.html_url,
im: json.im_url,
};
}
yield this.render('profile', {
title: 'User - ' + name,
packages,
user: data,
lastModified: user.gmt_modified,
isAdmin,
scopes,
});
};

View File

@@ -1,15 +1,28 @@
/**!
* cnpmjs.org - dispatch.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com>
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
const childProcess = require('child_process');
const path = require('path');
const util = require('util');
const cfork = require('cfork');
const config = require('./config');
const workerPath = path.join(__dirname, 'worker.js');
const syncPath = path.join(__dirname, 'sync');
/**
* Module dependencies.
*/
console.log('Starting cnpmjs.org ...\ncluster: %s\nadmins: %j\nscopes: %j\nsourceNpmRegistry: %s\nsyncModel: %s',
config.enableCluster, config.admins, config.scopes, config.sourceNpmRegistry, config.syncModel);
var path = require('path');
var util = require('util');
var cluster = require('cluster');
var cfork = require('cfork');
var config = require('./config');
var workerPath = path.join(__dirname, 'worker.js');
var childProcess = require('child_process');
var syncPath = path.join(__dirname, 'sync');
if (config.enableCluster) {
forkWorker();
@@ -27,17 +40,14 @@ function forkWorker() {
cfork({
exec: workerPath,
count: config.numCPUs,
})
.on('fork', worker => {
}).on('fork', function (worker) {
console.log('[%s] [worker:%d] new worker start', Date(), worker.process.pid);
})
.on('disconnect', worker => {
}).on('disconnect', function (worker) {
console.error('[%s] [master:%s] wroker:%s disconnect, suicide: %s, state: %s.',
Date(), process.pid, worker.process.pid, worker.suicide, worker.state);
})
.on('exit', (worker, code, signal) => {
const exitCode = worker.process.exitCode;
const err = new Error(util.format('worker %s died (code: %s, signal: %s, suicide: %s, state: %s)',
}).on('exit', function (worker, code, signal) {
var exitCode = worker.process.exitCode;
var err = new Error(util.format('worker %s died (code: %s, signal: %s, suicide: %s, state: %s)',
worker.process.pid, exitCode, signal, worker.suicide, worker.state));
err.name = 'WorkerDiedError';
console.error('[%s] [master:%s] wroker exit: %s', Date(), process.pid, err.stack);
@@ -45,9 +55,9 @@ function forkWorker() {
}
function forkSyncer() {
const syncer = childProcess.fork(syncPath);
syncer.on('exit', (code, signal) => {
const err = new Error(util.format('syncer %s died (code: %s, signal: %s, stdout: %s, stderr: %s)',
var syncer = childProcess.fork(syncPath);
syncer.on('exit', function (code, signal) {
var err = new Error(util.format('syncer %s died (code: %s, signal: %s, stdout: %s, stderr: %s)',
syncer.pid, code, signal, syncer.stdout, syncer.stderr));
err.name = 'SyncerWorkerDiedError';
console.error('[%s] [master:%s] syncer exit: %s: %s',

View File

@@ -1,143 +0,0 @@
# Migrating from 1.x to 2.x
2.x using [Sequelize] ORM to supports MySQL, MariaDB, SQLite or PostgreSQL databases.
## New download total table structure
### Create `downloads` table SQL
You should create `downloads` table first:
```sql
CREATE TABLE IF NOT EXISTS `downloads` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
`gmt_modified` datetime NOT NULL COMMENT 'modified time',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
`date` int unsigned NOT NULL COMMENT 'YYYYMM format',
`d01` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '01 download count',
`d02` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '02 download count',
`d03` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '03 download count',
`d04` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '04 download count',
`d05` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '05 download count',
`d06` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '06 download count',
`d07` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '07 download count',
`d08` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '08 download count',
`d09` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '09 download count',
`d10` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '10 download count',
`d11` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '11 download count',
`d12` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '12 download count',
`d13` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '13 download count',
`d14` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '14 download count',
`d15` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '15 download count',
`d16` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '16 download count',
`d17` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '17 download count',
`d18` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '18 download count',
`d19` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '19 download count',
`d20` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '20 download count',
`d21` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '21 download count',
`d22` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '22 download count',
`d23` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '23 download count',
`d24` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '24 download count',
`d25` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '25 download count',
`d26` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '26 download count',
`d27` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '27 download count',
`d28` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '28 download count',
`d29` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '29 download count',
`d30` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '30 download count',
`d31` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '31 download count',
PRIMARY KEY (`id`),
UNIQUE KEY `name_date` (`name`, `date`),
KEY `date` (`date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module download total info';
```
### Sync `download_total` to `downloads`
Then use [sync_download_total.js](../tools/sync_download_total.js) scrpt to sync datas from `download_total`:
```bash
$ node --harmony tools/sync_download_total.js
```
# `config.js` changes in 2.x
## New database config
```js
/**
* database config
*/
database: {
db: 'cnpmjs_test',
username: 'root',
password: '',
// the sql dialect of the database
// - currently supported: 'mysql', 'sqlite', 'postgres', 'mariadb'
dialect: 'sqlite',
// custom host; default: 127.0.0.1
host: '127.0.0.1',
// custom port; default: 3306
port: 3306,
// use pooling in order to reduce db connection overload and to increase speed
// currently only for mysql and postgresql (since v1.5.0)
pool: {
maxConnections: 10,
minConnections: 0,
maxIdleTime: 30000
},
// the storage engine for 'sqlite'
// default store into ~/cnpmjs.org.sqlite
storage: path.join(process.env.HOME || root, 'cnpmjs.org.sqlite'),
logging: !!process.env.SQL_DEBUG,
},
```
If you're still using MySQL and old config.js `mysqlServers: []` from 1.x:
```js
mysqlServers: [
{
host: '127.0.0.1',
port: 3306,
user: 'root',
password: ''
}
],
mysqlDatabase: 'cnpmjs_test',
mysqlMaxConnections: 4,
mysqlQueryTimeout: 5000,
```
We will do forward compat, and auto change old style config.js to:
```js
database: {
db: 'cnpmjs_test',
username: 'root',
password: '',
dialect: 'mysql',
host: '127.0.0.1',
port: 3306,
pool: {
maxConnections: 10,
minConnections: 0,
maxIdleTime: 30000
},
logging: !!process.env.SQL_DEBUG,
},
```
## remove `adaptScope`
`adaptScope: true | false` feature was removed.
[Sequelize]: http://sequelizejs.com/

View File

@@ -151,60 +151,17 @@ INSERT INTO total(name, gmt_modified) VALUES('total', now())
-- ALTER TABLE `total` ADD `left_sync_num` int unsigned NOT NULL DEFAULT '0' COMMENT 'how many packages left to be sync'
-- ALTER TABLE `total` ADD `last_sync_module` varchar(100) NOT NULL COMMENT 'last sync success module name';
-- CREATE TABLE IF NOT EXISTS `download_total` (
-- `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
-- `gmt_create` datetime NOT NULL COMMENT 'create time',
-- `gmt_modified` datetime NOT NULL COMMENT 'modified time',
-- `date` datetime NOT NULL COMMENT 'YYYY-MM-DD format',
-- `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
-- `count` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'download count',
-- PRIMARY KEY (`id`),
-- UNIQUE KEY `date_name` (`date`, `name`)
-- ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module download total info';
-- ALTER TABLE `download_total` CHANGE `name` `name` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name';
-- ALTER TABLE `download_total` CHANGE `date` `date` datetime NOT NULL COMMENT 'YYYY-MM-DD format';
CREATE TABLE IF NOT EXISTS `downloads` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
`gmt_modified` datetime NOT NULL COMMENT 'modified time',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
`date` int unsigned NOT NULL COMMENT 'YYYYMM format',
`d01` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '01 download count',
`d02` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '02 download count',
`d03` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '03 download count',
`d04` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '04 download count',
`d05` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '05 download count',
`d06` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '06 download count',
`d07` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '07 download count',
`d08` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '08 download count',
`d09` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '09 download count',
`d10` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '10 download count',
`d11` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '11 download count',
`d12` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '12 download count',
`d13` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '13 download count',
`d14` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '14 download count',
`d15` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '15 download count',
`d16` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '16 download count',
`d17` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '17 download count',
`d18` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '18 download count',
`d19` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '19 download count',
`d20` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '20 download count',
`d21` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '21 download count',
`d22` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '22 download count',
`d23` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '23 download count',
`d24` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '24 download count',
`d25` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '25 download count',
`d26` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '26 download count',
`d27` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '27 download count',
`d28` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '28 download count',
`d29` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '29 download count',
`d30` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '30 download count',
`d31` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '31 download count',
PRIMARY KEY (`id`),
UNIQUE KEY `name_date` (`name`, `date`),
KEY `date` (`date`)
CREATE TABLE IF NOT EXISTS `download_total` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
`gmt_modified` datetime NOT NULL COMMENT 'modified time',
`date` varchar(10) NOT NULL COMMENT 'YYYY-MM-DD format',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
`count` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'download count',
PRIMARY KEY (`id`),
UNIQUE KEY `date_name` (`date`, `name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module download total info';
-- ALTER TABLE `download_total` CHANGE `name` `name` VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name';
CREATE TABLE IF NOT EXISTS `module_deps` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
@@ -220,7 +177,7 @@ CREATE TABLE IF NOT EXISTS `dist_dir` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
`gmt_modified` datetime NOT NULL COMMENT 'modified time',
`name` varchar(200) NOT NULL COMMENT 'dir name',
`name` varchar(200) NOT NULL COMMENT 'user name',
`parent` varchar(200) NOT NULL COMMENT 'parent dir' DEFAULT '/',
`date` varchar(20) COMMENT '02-May-2014 01:06',
PRIMARY KEY (`id`),
@@ -232,7 +189,7 @@ CREATE TABLE IF NOT EXISTS `dist_file` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
`gmt_modified` datetime NOT NULL COMMENT 'modified time',
`name` varchar(100) NOT NULL COMMENT 'file name',
`name` varchar(100) NOT NULL COMMENT 'user name',
`parent` varchar(200) NOT NULL COMMENT 'parent dir' DEFAULT '/',
`date` varchar(20) COMMENT '02-May-2014 01:06',
`size` int(10) unsigned NOT NULL COMMENT 'file size' DEFAULT '0',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

View File

@@ -1,98 +0,0 @@
@startuml
title cnpmjs.org, npm.taobao.org Network
node "China User" {
[cnpm cli]
}
node "OSS: aliyuncs.com" {
[tnpm-hz.oss-cn-hangzhou]
}
node "SLB: 114.55.80.225 Hangzhou" {
[npm.taobao.org]
[registry.npm.taobao.org]
}
node "npmtaobao3: 121.41.*" {
[nginx 5]
[registry 5]
[web 5]
[syncer 5]
}
node "npmtaobao4: 120.26.*" {
[nginx 6]
[registry 6]
[web 6]
}
node "cnpm6: 47.88.189.193 Singapore" {
[cnpmjs.org]
[r.cnpmjs.org]
[nginx 7]
[registry 7]
[web 7]
[syncer 7]
}
node "Aliyun CDN" {
[cdn.npm.taobao.org]
}
node "qiniu CDN" {
[cnpmjs.up.qiniu.com]
[dn-cnpm.qbox.me]
}
database "taobaonpm DB" {
[rds2860*.mysql.rds.aliyuncs.com]
}
database "cnpm DB" {
[rdsqiz*.mysql.rds.aliyuncs.com]
}
node "npmjs.com" {
[registry.npmjs.com]
[www.npmjs.com]
}
[cnpm cli] -down-> [registry.npm.taobao.org]: Read, Write
[cnpm cli] -down-> [cdn.npm.taobao.org]: Read tgz
[cnpm cli] -down-> [npm.taobao.org]: "Read /mirrors"
[registry.npm.taobao.org] -down-> [nginx 5]
[nginx 5] -down-> [registry 5]
[npm.taobao.org] -down-> [nginx 5]
[nginx 5] -down-> [web 5]
[registry.npm.taobao.org] -down-> [nginx 6]
[nginx 6] -down-> [registry 6]
[npm.taobao.org] -down-> [nginx 6]
[nginx 6] -down-> [web 6]
[registry 5] -down-> [rds2860*.mysql.rds.aliyuncs.com]: Read, Write
[web 5] -down-> [rds2860*.mysql.rds.aliyuncs.com]: Read
[syncer 5] -down-> [rds2860*.mysql.rds.aliyuncs.com]: Read, Write
[syncer 5] -down-> [tnpm-hz.oss-cn-hangzhou]: Write
[syncer 5] -> [r.cnpmjs.org]: Read
[syncer 5] -> [dn-cnpm.qbox.me]: Read tgz
[registry 6] -down-> [rds2860*.mysql.rds.aliyuncs.com]: Read, Write
[web 6] -down-> [rds2860*.mysql.rds.aliyuncs.com]: Read
[cnpmjs.org] -down-> [nginx 7]
[nginx 7] -down-> [registry 7]
[r.cnpmjs.org] -down-> [nginx 7]
[nginx 7] -down-> [web 7]
[registry 7] -down-> [rdsqiz*.mysql.rds.aliyuncs.com]: Read, Write
[web 7] -down-> [rdsqiz*.mysql.rds.aliyuncs.com]: Read
[syncer 7] -down-> [rdsqiz*.mysql.rds.aliyuncs.com]: Read, Write
[syncer 7] -down-> [cnpmjs.up.qiniu.com]: Write
[syncer 7] -down-> [registry.npmjs.com]: Read
[cdn.npm.taobao.org] -down-> [tnpm-hz.oss-cn-hangzhou]: Read
[dn-cnpm.qbox.me] -down-> [cnpmjs.up.qiniu.com]: Read
@enduml

View File

@@ -9,8 +9,6 @@
* [User](/docs/registry-api.md#user)
* [Search](/docs/registry-api.md#search)
[![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/f6c8cb46358039bcd689#?env%5BRegistry%5D=W3sia2V5IjoicmVnaXN0cnkiLCJ0eXBlIjoidGV4dCIsInZhbHVlIjoiaHR0cHM6Ly9yZWdpc3RyeS5ucG0udGFvYmFvLm9yZyIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoicGFja2FnZSIsInZhbHVlIjoiY25wbSIsInR5cGUiOiJ0ZXh0IiwiZW5hYmxlZCI6dHJ1ZX1d)
## Schema
All API access is over HTTPS or HTTP,
@@ -21,20 +19,22 @@ All data is sent and received as JSON.
$ curl -i https://registry.npmjs.org
HTTP/1.1 200 OK
Date: Tue, 05 Aug 2014 10:53:24 GMT
Server: CouchDB/1.5.0 (Erlang OTP/R16B03)
Content-Type: text/plain; charset=utf-8
Cache-Control: max-age=60
Content-Length: 258
Accept-Ranges: bytes
Via: 1.1 varnish
Age: 11
X-Served-By: cache-ty67-TYO
X-Cache: HIT
X-Cache-Hits: 1
X-Timer: S1407236004.867906,VS0,VE0
{
"db_name": "registry",
"doc_count": 123772,
"doc_del_count": 377,
"update_seq": 685591,
"purge_seq": 0,
"compact_running": false,
"disk_size": 634187899,
"data_size": 445454185,
"instance_start_time": "1420670152481614",
"disk_format_version": 6,
"committed_update_seq": 685591
}
{"db_name":"registry","doc_count":90789,"doc_del_count":381,"update_seq":137250,"purge_seq":0,
"compact_running":false,"disk_size":436228219,"data_size":332875061,
"instance_start_time":"1405721973718703","disk_format_version":6,"committed_update_seq":137250}
```
## Client Errors
@@ -61,10 +61,22 @@ $ curl -u "username:password" https://registry.npmjs.org
## Failed login limit
```bash
$ curl -i -X "GET" -u "foo:pwd" \
"https://registry.npmjs.com/-/user/org.couchdb.user:npm-user-service-testuser?write=true"
$ curl -i -X PUT -u foo:pwd \
-d '{"name":"foo","email":"foo@bar.com","type":"user","roles":[]}' \
https://registry.npmjs.org/-/user/org.couchdb.user:foo/-rev/11-d226c6afa9286ab5b9eb858c429bdabf
HTTP/1.1 401 Unauthorized
Date: Tue, 05 Aug 2014 15:33:25 GMT
Server: CouchDB/1.5.0 (Erlang OTP/R14B04)
Content-Type: text/plain; charset=utf-8
Cache-Control: max-age=60
Content-Length: 67
Accept-Ranges: bytes
Via: 1.1 varnish
X-Served-By: cache-ty66-TYO
X-Cache: MISS
X-Cache-Hits: 0
X-Timer: S1407252805.261390,VS0,VE434
{"error":"unauthorized","reason":"Name or password is incorrect."}
```
@@ -89,11 +101,14 @@ HTTP/1.1 401 Unauthorized
GET /:package
```
#### Response 200
#### Response
```json
HTTP/1.1 200 OK
Etag: "8UDCP753LFXOG42NMX88JAN40"
Content-Type: application/json
Cache-Control: max-age=60
Content-Length: 2243
{
"_id": "pedding",
@@ -231,17 +246,6 @@ Etag: "8UDCP753LFXOG42NMX88JAN40"
}
```
#### Response 404
```json
HTTP/1.1 404 Object Not Found
{
"error": "not_found",
"reason": "document not found"
}
```
### ~~Get a special version or tag package~~
__deprecated__
@@ -250,7 +254,7 @@ __deprecated__
GET /:package/:tag_or_version
```
#### Reponse 200
#### Reponse
```json
HTTP/1.1 200 OK
@@ -741,52 +745,9 @@ HTTP/1.1 200 OK
## User
- [Auth user](/docs/registry-api.md#auth-user)
- [Get a single user](/docs/registry-api.md#get-a-single-user)
- [Add a new user](/docs/registry-api.md#add-a-new-user)
- [Update a exists user](/docs/registry-api.md#update-a-exists-user)
### Auth user
* Authentication required.
```
GET /-/user/org.couchdb.user::username?write=true
```
#### Response 200
```json
HTTP/1.1 200 OK
ETag: "5-a31b61ba3c50b50f7fcaf185e079e17a"
{
"_id": "org.couchdb.user:npm-user-service-testuser",
"_rev": "5-a31b61ba3c50b50f7fcaf185e079e17a",
"password_scheme": "pbkdf2",
"iterations": 10,
"name": "npm-user-service-testuser",
"email": "fengmk2@gmail.com",
"type": "user",
"roles": [],
"date": "2015-01-04T08:28:51.378Z",
"password_scheme": "pbkdf2",
"iterations": 10,
"derived_key": "644157c126b93356e6eba2c59fdf1b7ec644ebf2",
"salt": "5d13874c0aa10751e35743bacd6eedd5"
}
```
#### Response 401
```json
HTTP/1.1 401 Unauthorized
{
"error": "unauthorized",
"reason": "Name or password is incorrect."
}
```
* [Get a single user](/docs/registry-api.md#get-a-single-user)
* [Add a new user](/docs/registry-api.md#add-a-new-user)
* [Update a exists user](/docs/registry-api.md#update-a-exists-user)
### Get a single user
@@ -794,7 +755,7 @@ HTTP/1.1 401 Unauthorized
GET /-/user/org.couchdb.user::username
```
#### Response 200
#### Response
```json
HTTP/1.1 200 OK
@@ -864,17 +825,6 @@ ETag: "32-984ee97e01aea166dcab6d1517c730e3"
}
```
#### Response 404
```json
HTTP/1.1 404 Object Not Found
{
"error": "not_found",
"reason": "missing"
}
```
### Add a new user
```
@@ -895,7 +845,7 @@ PUT /-/user/org.couchdb.user::username
}
```
#### Response 201
#### Response
```json
Status: 201 Created
@@ -907,19 +857,6 @@ Status: 201 Created
}
```
#### Response 409
User already exists
```json
HTTP/1.1 409 Conflict
{
"error": "conflict",
"reason": "Document update conflict."
}
```
### Update a exists user
* Authentication required.

115
docs/web/install.md Normal file
View File

@@ -0,0 +1,115 @@
# Install & Get Started
## Deps
* MySQL Server: http://db4free.net/
* qiniu CDN: http://www.qiniu.com/
* redis session: https://garantiadata.com Support 24MB free spaces.
* node: >=0.10.21
## Clone
```bash
$ git clone git://github.com/fengmk2/cnpmjs.org.git $HOME/cnpmjs.org
$ cd $HOME/cnpmjs.org
```
## Create your `config.js`
```bash
$ vim config/config.js
```
`config.js` content sample:
```js
module.exports = {
debug: false,
enableCluster: true, // enable cluster mode
logdir: 'your application log dir',
mysqlServers: [
{
host: 'your mysql host',
port: 3306,
user: 'yourname',
password: 'your password'
}
],
mysqlDatabase: 'cnpmjs',
redis: {
host: 'your redist host',
port: 6379,
},
qn: {
accessKey: "your qiniu appkey",
secretKey: "your secret key",
bucket: "foobucket",
domain: "http://foobucket.u.qiniudn.com"
},
nfs: null, // you can set a nfs to replace qiniu cdn
enablePrivate: true, // enable private mode, only admin can publish, other use just can sync package from source npm
admins: {
admin: 'admin@cnpmjs.org',
},
syncModel: 'exist', //`all` sync all packages, `exist` only update exist packages, `none` do nothing
};
```
## Create MySQL Database and Tables
```bash
$ mysql -u yourname -p
mysql> use cnpmjs;
mysql> source docs/db.sql
```
## Use your own CDN
If you wan to use your own CDN instead of qiniu. Just look at `common/qnfs.js` and implement the interface like it, then pass it by set `config.nfs`.
## npm install
```bash
$ npm install
```
## start
```bash
$ ./bin/nodejsctl start
Starting cnpmjs.org ...
Start nodejs success. PID=27175
```
## open registry and web
```bash
# registry
$ open http://localhost:7001
# web
$ open http://localhost:7002
```
## use cnpm cli with your own registry
You do not need to write another command line tool with your own registry,
just alias [cnpm](http://github.com/fengmk2/cnpm), then you can get a npm client for you own registry.
```
# install cnpm first
npm install -g cnpm
# then alias lnpm to cnpm, but change config to your own registry
alias lnpm='cnpm --registry=http://localhost:7001\
--registryweb=http://localhost:7002\
--cache=$HOME/.npm/.cache/lnpm\
--disturl=http://cnpmjs.org/dist\
--userconfig=$HOME/.lnpmrc'
#or put this in .zshrc or .bashrc
echo "#lnpm alias\nalias lnpm='cnpm --registry=http://localhost:7001\
--registryweb=http://localhost:7002\
--cache=$HOME/.npm/.cache/lnpm\
--disturl=http://cnpmjs.org/dist\
--userconfig=$HOME/.lnpmrc'" >> $HOME/.zshrc && source $HOME/.zshrc
```

View File

@@ -4,12 +4,12 @@ So `cnpm` is meaning: **Company npm**.
## Registry
- Our public registry: [r.cnpmjs.org](https://r.cnpmjs.org), syncing from [registry.npmjs.com](https://registry.npmjs.com)
- [cnpmjs.org](/) version: <span id="app-version"></span>
- [Node.js](https://nodejs.org) version: <span id="node-version"></span>
- For developers in China, please visit [the China mirror](https://npm.taobao.org). 中国用户请访问[国内镜像站点](https://npm.taobao.org)
* Our public registry: [r.cnpmjs.org](http://r.cnpmjs.org), syncing from [registry.npmjs.org](http://registry.npmjs.org)
* Current [cnpmjs.org](/) version: <span id="app-version"></span>
* Mirror of [Node.js Manual & Documentation](/dist/latest/docs/api/index.html)
* Mirror of [nodejs.org/dist](http://nodejs.org/dist): [/dist mirror](/dist)
* Mirror of [phantomjs downloads](https://bitbucket.org/ariya/phantomjs/downloads): [phantomjs mirror](/dist/phantomjs/)
<div class="ant-table">
<table class="downloads">
<tbody>
<tr>
@@ -29,17 +29,12 @@ So `cnpm` is meaning: **Company npm**.
</tr>
</tbody>
</table>
</div>
<div class="sync" style="display:none;">
<h3>Sync Status</h3>
<p id="sync-model"></p>
<p>Last sync time is <span id="last-sync-time"></span>. </p>
<div class="ant-alert ant-alert-info syncing">
<span class="anticon ant-alert-icon anticon-info-circle"></span>
<span class="ant-alert-description">The sync worker is working in the backend now. </span>
</div>
<div class="ant-table">
<p class="syncing alert alert-info">The sync worker is working in the backend now. </p>
<table class="sync-status">
<tbody>
<tr>
@@ -54,55 +49,100 @@ So `cnpm` is meaning: **Company npm**.
</tr>
</tbody>
</table>
</div>
</div>
<script src="/js/readme.js"></script>
Running on [Node.js](http://nodejs.org), version <span id="node-version"></span>.
## Badges
<script>
$(function () {
function humanize(n, options) {
options = options || {};
var d = options.delimiter || ',';
var s = options.separator || '.';
n = n.toString().split('.');
n[0] = n[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1' + d);
return n.join(s);
}
$.getJSON('/total', function (data) {
$('#total-packages').html(humanize(data.doc_count));
$('#total-versions').html(humanize(data.doc_version_count));
$('#total-deletes').html(humanize(data.doc_del_count));
var downloads = $('table.downloads');
downloads.find('td.count:eq(3)').html(humanize(data.download.today));
downloads.find('td.count:eq(4)').html(humanize(data.download.thisweek));
downloads.find('td.count:eq(5)').html(humanize(data.download.thismonth));
downloads.find('td.count:eq(6)').html(humanize(data.download.lastday));
downloads.find('td.count:eq(7)').html(humanize(data.download.lastweek));
downloads.find('td.count:eq(8)').html(humanize(data.download.lastmonth));
$('#node-version').html(data.node_version || 'v0.10.22');
$('#app-version').html(data.app_version || '0.0.0');
if (data.sync_model === 'all') {
$('#sync-model').html('This registry will sync all packages from official registry.');
$('#last-sync-time').html(new Date(data.last_sync_time));
} else if (data.sync_model === 'exist') {
$('#sync-model').html('This registry will only update exist packages from official registry.');
$('#last-sync-time').html(new Date(data.last_exist_sync_time));
}
$('#need-sync').html(data.need_sync_num);
$('#success-sync').html(data.success_sync_num);
$('#fail-sync').html(data.fail_sync_num);
$('#left-sync').html(data.left_sync_num);
$('#percent-sync').html(Math.floor(data.success_sync_num / data.need_sync_num * 100));
$('#last-success-name').html('<a target="_blank" href="/package/' + data.last_sync_module + '">' +
data.last_sync_module + '</a>');
if (!data.sync_status) {
$('.syncing').html('');
}
$('.sync').show();
});
});
</script>
## Version Badge
Default style is `flat-square`.
### Version
Badge URL: `https://cnpmjs.org/badge/v/cnpmjs.org.svg` ![cnpmjs.org-version-badge](//cnpmjs.org/badge/v/cnpmjs.org.svg)
Badge URL: `http://cnpmjs.org/badge/v/cnpmjs.org.svg` ![cnpmjs.org-badge](http://cnpmjs.org/badge/v/cnpmjs.org.svg)
* `<0.1.0 & >=0.0.0`: ![red-badge](https://img.shields.io/badge/cnpm-0.0.1-red.svg?style=flat-square)
* `<1.0.0 & >=0.1.0`: ![red-badge](https://img.shields.io/badge/cnpm-0.1.0-green.svg?style=flat-square)
* `>=1.0.0`: ![red-badge](https://img.shields.io/badge/cnpm-1.0.0-blue.svg?style=flat-square)
### Downloads
Badge URL: `https://cnpmjs.org/badge/d/cnpmjs.org.svg` ![cnpmjs.org-download-badge](//cnpmjs.org/badge/d/cnpmjs.org.svg)
## Usage
use our npm client [cnpm](https://github.com/cnpm/cnpm)(More suitable with cnpmjs.org and gzip support), you can get our client through npm:
```bash
$ npm install -g cnpm --registry=https://registry.npm.taobao.org
```
npm install -g cnpm --registry=http://r.cnpmjs.org
```
Or you can alias NPM to use it:
```bash
alias cnpm="npm --registry=https://registry.npm.taobao.org \
alias cnpm="npm --registry=http://r.cnpmjs.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=https://npm.taobao.org/mirrors/node \
--disturl=http://cnpmjs.org/dist \
--userconfig=$HOME/.cnpmrc"
#Or alias it in .bashrc or .zshrc
$ echo '\n#alias for cnpm\nalias cnpm="npm --registry=https://registry.npm.taobao.org \
$ echo '\n#alias for cnpm\nalias cnpm="npm --registry=http://r.cnpmjs.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=https://npm.taobao.org/mirrors/node \
--disturl=http://cnpmjs.org/dist \
--userconfig=$HOME/.cnpmrc"' >> ~/.zshrc && source ~/.zshrc
```
### install
Install package from [r.cnpmjs.org](//r.cnpmjs.org). When installing a package or version does not exist, it will try to install from the official registry([registry.npmjs.org](https://registry.npmjs.org)), and sync this package to cnpm in the backend.
Install package from [r.cnpmjs.org](http://r.cnpmjs.org). When installing a package or version does not exist, it will try to install from the official registry([registry.npmjs.org](http://registry.npmjs.org)), and sync this package to cnpm in the backend.
```bash
```
$ cnpm install [name]
```
@@ -114,10 +154,10 @@ Only `cnpm` cli has this command. Meaning sync package from source npm.
$ cnpm sync connect
```
sync package on web: [sync/connect](/sync/connect)
sync package on web: [cnpmjs.org/sync/connect](http://cnpmjs.org/sync/connect)
```bash
$ open http://registry.npm.taobao.org/sync/connect
$ open http://cnpmjs.org/sync/connect
```
### publish / unpublish
@@ -145,11 +185,6 @@ $ cnpm info cnpm
Release [History](/history).
## npmjs.org, cnpmjs.org and npm.taobao.org relation
## npm and cnpm relation
![npm&cnpm](https://cloud.githubusercontent.com/assets/543405/21505401/fd0b6220-cca1-11e6-86ed-599cc81bb03b.png)
## Sponsors
- [![阿里云](https://static.aliyun.com/images/www-summerwind/logo.gif)](http://click.aliyun.com/m/4288/) (2016.2 - now)
- [![UCloud云计算](https://www.ucloud.cn/static/style/images/about/logo.png)](http://www.ucloud.cn?sem=sdk-CNPMJS) (2015.3 - 2016.3)
![npm&cnpm](https://docs.google.com/drawings/d/12QeQfGalqjsB77mRnf5Iq5oSXHCIUTvZTwECMonqCmw/pub?w=383&h=284)

View File

@@ -1,16 +1,30 @@
/**!
* cnpmjs.org - index.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
*/
'use strict';
const config = require('./config');
/**
* Module dependencies.
*/
var config = require('./config');
exports.loadConfig = config.loadConfig;
exports.config = config;
exports.startWorker = function(customConfig) {
exports.startWorker = function (customConfig) {
config.loadConfig(customConfig);
require('./worker');
};
exports.startSync = function(customConfig) {
exports.startSync = function (customConfig) {
config.loadConfig(customConfig);
require('./sync');
};

View File

@@ -1,66 +1,58 @@
/**!
* cnpmjs.org - lib/common.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
const crypto = require('crypto');
const path = require('path');
const config = require('../config');
const util = require('util');
/**
* Module dependencies.
*/
exports.getTarballFilepath = function(filename) {
var crypto = require('crypto');
var path = require('path');
var config = require('../config');
var util = require('util');
exports.getTarballFilepath = function (filename) {
// ensure download file path unique
// TODO: not only .tgz, and also other extname
const name = filename.replace(/\.tgz$/, '.' + crypto.randomBytes(16).toString('hex') + '.tgz');
var name = filename.replace(/\.tgz$/, '.' + crypto.randomBytes(16).toString('hex') + '.tgz');
return path.join(config.uploadDir, name);
};
exports.getCDNKey = function(name, filename) {
// if name is scope package name, need to auto fix filename as a scope package file name
// e.g.: @scope/foo, filename: foo-1.0.0.tgz => filename: @scope/foo-1.0.0.tgz
if (name[0] === '@' && filename[0] !== '@') {
filename = name.split('/')[0] + '/' + filename;
}
exports.getCDNKey = function (name, filename) {
return '/' + name + '/-/' + filename;
};
exports.setDownloadURL = function(pkg, ctx, host) {
exports.setDownloadURL = function (pkg, ctx, host) {
if (pkg.dist) {
host = host || config.registryHost || ctx.host;
host = host || ctx.host;
pkg.dist.tarball = util.format('%s://%s/%s/download/%s-%s.tgz',
ctx.protocol,
host, pkg.name, pkg.name, pkg.version);
}
};
exports.isAdmin = function(username) {
exports.isAdmin = function (username) {
return typeof config.admins[username] === 'string';
};
exports.isMaintainer = function(user, maintainers) {
exports.isMaintainer = function (user, maintainers) {
if (user.isAdmin) {
return true;
}
const username = user.name;
var username = user.name;
maintainers = maintainers || [];
const match = maintainers.filter(function(item) {
var match = maintainers.filter(function (item) {
return item.name === username;
});
return match.length > 0;
};
exports.isLocalModule = function(mods) {
for (let i = 0; i < mods.length; i++) {
const r = mods[i];
if (r.package && r.package._publish_on_cnpm) {
return true;
}
}
return false;
};
exports.isPrivateScopedPackage = function(name) {
if (name[0] !== '@') {
return false;
}
return config.scopes.indexOf(name.split('/')[0]) >= 0;
};

View File

@@ -1,37 +1,49 @@
/**!
* cnpmjs.org - middleware/auth.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
const debug = require('debug')('cnpmjs.org:middleware:auth');
const UserService = require('../services/user');
const config = require('../config');
/**
* Module dependencies.
*/
var debug = require('debug')('cnpmjs.org:middleware:auth');
var UserService = require('../services/user');
/**
* Parse the request authorization
* get the real user
*/
module.exports = function() {
module.exports = function (options) {
return function* auth(next) {
this.user = {};
let authorization = (this.get('authorization') || '').split(' ')[1] || '';
var authorization = (this.get('authorization') || '').split(' ')[1] || '';
authorization = authorization.trim();
debug('%s %s with %j', this.method, this.url, authorization);
if (!authorization) {
return yield unauthorized.call(this, next);
return yield* next;
}
authorization = new Buffer(authorization, 'base64').toString();
const pos = authorization.indexOf(':');
if (pos === -1) {
return yield unauthorized.call(this, next);
authorization = new Buffer(authorization, 'base64').toString().split(':');
if (authorization.length !== 2) {
return yield* next;
}
const username = authorization.slice(0, pos);
const password = authorization.slice(pos + 1);
var username = authorization[0];
var password = authorization[1];
let row;
var row;
try {
row = yield UserService.auth(username, password);
row = yield* UserService.auth(username, password);
} catch (err) {
// do not response error here
// many request do not need login
@@ -40,29 +52,13 @@ module.exports = function() {
if (!row) {
debug('auth fail user: %j, headers: %j', row, this.header);
return yield unauthorized.call(this, next);
return yield* next;
}
this.user.name = row.login;
this.user.isAdmin = row.site_admin;
this.user.scopes = row.scopes;
debug('auth pass user: %j, headers: %j', this.user, this.header);
yield next;
yield* next;
};
};
function* unauthorized(next) {
if (!config.alwaysAuth || this.method !== 'GET') {
return yield next;
}
this.status = 401;
this.set('WWW-Authenticate', 'Basic realm="sample"');
if (this.accepts([ 'html', 'json' ]) === 'json') {
this.body = {
error: 'unauthorized',
reason: 'login first',
};
} else {
this.body = 'login first';
}
}

View File

@@ -1,15 +0,0 @@
'use strict';
module.exports = () => {
return function* block(next) {
const ua = String(this.get('user-agent')).toLowerCase();
if (ua === 'ruby') {
this.status = 403;
this.body = {
message: 'forbidden Ruby user-agent, ip: ' + this.ip,
};
return;
}
yield next;
};
};

View File

@@ -1,28 +0,0 @@
'use strict';
const packageService = require('../services/package');
// admin or module's maintainer can modified the module
module.exports = function* editable(next) {
const username = this.user && this.user.name;
const moduleName = this.params.name || this.params[0];
if (username && moduleName) {
if (this.user.isAdmin) {
return yield next;
}
const isMaintainer = yield packageService.isMaintainer(moduleName, username);
if (isMaintainer) {
return yield next;
}
}
this.status = 403;
let message = 'not authorized to modify ' + moduleName;
if (username) {
message = username + ' ' + message;
}
this.body = {
error: 'forbidden user',
reason: message,
};
};

View File

@@ -1,16 +0,0 @@
'use strict';
const packageService = require('../services/package');
module.exports = function* (next) {
const name = this.params.name || this.params[0];
const pkg = yield packageService.getLatestModule(name);
if (pkg) {
return yield next;
}
this.status = 404;
this.body = {
error: 'not_found',
reason: 'document not found',
};
};

View File

@@ -1,14 +1,34 @@
/**!
* cnpmjs.org - middleware/limit.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
*/
'use strict';
const config = require('../config');
const limit = require('koa-limit');
/**
* Module dependencies.
*/
const limitConfig = config.limit;
var config = require('../config');
var limit = require('koa-limit');
var store = require('../common/redis');
var limitConfig = config.limit;
if (!limitConfig.enable) {
module.exports = function* ignoreLimit(next) {
yield next;
module.exports = function *ignoreLimit(next) {
yield *next;
};
} else {
if (!config.debug) {
limitConfig.store = store;
}
module.exports = limit(limitConfig);
}

View File

@@ -1,28 +1,43 @@
/*!
* cnpmjs.org - middleware/login.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
const http = require('http');
/**
* Module dependencies.
*/
module.exports = function* login(next) {
var http = require('http');
module.exports = function *login(next) {
if (this.user.error) {
const status = this.user.error.status;
var status = this.user.error.status;
this.status = http.STATUS_CODES[status]
? status
: 500;
this.body = {
error: this.user.error.name,
reason: this.user.error.message,
reason: this.user.error.message
};
return;
}
if (!this.user.name) {
this.status = 401;
this.body = {
error: 'unauthorized',
reason: 'Login first',
reason: 'Login first.'
};
return;
}
yield next;
yield *next;
};

View File

@@ -1,19 +1,34 @@
/**!
* cnpmjs.org - middleware/opensearch.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
*/
'use strict';
const template = `<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>CNPM</ShortName>
<Description>Search packages in CNPM.</Description>
<Tags>CNPM</Tags>
<Url method="get" type="text/html" template="http://{{host}}/browse/keyword/{searchTerms}"/>
</OpenSearchDescription>`;
/**
* Module dependencies.
*/
module.exports = function* opensearch(next) {
var template = '<?xml version="1.0" encoding="UTF-8"?>\
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">\
<ShortName>CNPM</ShortName>\
<Description>Search packages in CNPM.</Description>\
<Tags>CNPM</Tags>\
<Url method="get" type="text/html" template="http://${host}/browse/keyword/{searchTerms}"/>\
</OpenSearchDescription>';
var lastModifyDate = new Date();
module.exports = function *publishable(next) {
if (this.path === '/opensearch.xml') {
this.type = 'text/xml';
this.charset = 'utf-8';
this.body = template.replace('{{host}}', this.host);
return;
this.body = template.replace('${host}', this.host);
}
yield next;
yield *next;
};

View File

@@ -1,46 +0,0 @@
'use strict';
const debug = require('debug')('cnpmjs.org:middleware:proxy_to_npm');
const config = require('../config');
module.exports = function(options) {
let redirectUrl = config.sourceNpmRegistry;
let proxyUrls = [
// /:pkg, dont contains scoped package
/^\/[\w\-\.]+$/,
// /-/package/:pkg/dist-tags
/^\/\-\/package\/[\w\-\.]+\/dist-tags/,
];
if (options && options.isWeb) {
redirectUrl = redirectUrl.replace('//registry.', '//');
proxyUrls = [
// /package/:pkg
/^\/package\/[\w\-\.]+$/,
];
}
return function* proxyToNpm(next) {
if (config.syncModel !== 'none') {
return yield next;
}
// only proxy read requests
if (this.method !== 'GET' && this.method !== 'HEAD') {
return yield next;
}
const pathname = this.path;
let match;
for (let i = 0; i < proxyUrls.length; i++) {
match = proxyUrls[i].test(pathname);
if (match) {
break;
}
}
if (!match) {
return yield next;
}
const url = redirectUrl + this.url;
debug('proxy to %s', url);
this.redirect(url);
};
};

View File

@@ -1,45 +1,58 @@
/**!
* cnpmjs.org - middleware/publishable.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
const util = require('util');
const config = require('../config');
const debug = require('debug')('cnpmjs.org:middlewares/publishable');
/**
* Module dependencies.
*/
module.exports = function* publishable(next) {
// admins always can publish and unpublish
if (this.user.isAdmin) {
return yield next;
}
var util = require('util');
var config = require('../config');
var debug = require('debug')('cnpmjs.org:middlewares/publishable');
module.exports = function *publishable(next) {
// private mode, only admin user can publish
if (config.enablePrivate && !this.user.isAdmin) {
// private mode, normal user can't publish and unpublish
if (config.enablePrivate) {
this.status = 403;
this.body = {
error: 'no_perms',
reason: 'Private mode enable, only admin can publish this module',
reason: 'Private mode enable, only admin can publish this module'
};
return;
}
// public mode, normal user have permission to publish `scoped package`
// and only can publish with scopes in `ctx.user.scopes`, default is `config.scopes`
// public mode, all user have permission to publish
// but if `config.scopes` exist, only can publish with scopes in `config.scope`
// if `config.forcePublishWithScope` set to true, only admins can publish without scope
const name = this.params.name || this.params[0];
var name = this.params.name || this.params[0];
// check if is private package list in config
if (config.privatePackages && config.privatePackages.indexOf(name) !== -1) {
return yield next;
return yield* next;
}
// scoped module
// scope
if (name[0] === '@') {
if (checkScope(name, this)) {
return yield next;
return yield* next;
}
return;
}
// none-scope
assertNoneScope(name, this);
if (checkNoneScope(this)) {
return yield* next;
}
};
/**
@@ -52,13 +65,13 @@ function checkScope(name, ctx) {
return false;
}
const scope = name.split('/')[0];
var scope = name.split('/')[0];
if (ctx.user.scopes.indexOf(scope) === -1) {
debug('assert scope %s error', name);
ctx.status = 400;
ctx.body = {
error: 'invalid scope',
reason: util.format('scope %s not match legal scopes: %s', scope, ctx.user.scopes.join(', ')),
reason: util.format('scope %s not match legal scopes %j', scope, ctx.user.scopes)
};
return false;
}
@@ -70,18 +83,21 @@ function checkScope(name, ctx) {
* check if user have permission to publish without scope
*/
function assertNoneScope(name, ctx) {
ctx.status = 403;
if (ctx.user.scopes.length === 0) {
ctx.body = {
error: 'no_perms',
reason: 'can\'t publish non-scoped package, please set `config.scopes`',
};
return;
function checkNoneScope(ctx) {
if (!config.scopes
|| !config.scopes.length
|| !config.forcePublishWithScope) {
return true;
}
// only admins can publish or unpublish non-scope modules
if (ctx.user.isAdmin) {
return true;
}
ctx.status = 403;
ctx.body = {
error: 'no_perms',
reason: 'only allow publish with ' + ctx.user.scopes.join(', ') + ' scope(s)',
reason: 'only allow publish with ' + ctx.user.scopes.join(',') + ' scope(s)'
};
}

View File

@@ -14,8 +14,8 @@
* Module dependencies.
*/
module.exports = function* notFound(next) {
yield next;
module.exports = function *notFound(next) {
yield *next;
if (this.status && this.status !== 404) {
return;
@@ -27,6 +27,6 @@ module.exports = function* notFound(next) {
this.status = 404;
this.body = {
error: 'not_found',
reason: 'document not found',
reason: 'document not found'
};
};

View File

@@ -1,16 +1,29 @@
/**!
* cnpmjs.org - middleware/static.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
const path = require('path');
const middlewares = require('koa-middlewares');
const config = require('../config');
/**
* Module dependencies.
*/
const staticDir = path.join(path.dirname(__dirname), 'public');
var path = require('path');
var middlewares = require('koa-middlewares');
var config = require('../config');
var staticDir = path.join(path.dirname(__dirname), 'public');
module.exports = middlewares.staticCache(staticDir, {
buffer: !config.debug,
buffer: config.debug ? false : true,
maxAge: config.debug ? 0 : 60 * 60 * 24 * 7,
alias: {
'/favicon.ico': '/favicon.png',
},
gzip: config.enableCompress,
alas: {
'/favicon.ico': '/favicon.png'
}
});

View File

@@ -1,38 +1,41 @@
/**!
* cnpmjs.org - middleware/sync_by_install.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
*/
'use strict';
const config = require('../config');
/**
* Module dependencies.
*/
var config = require('../config');
/**
* {Boolean} this.allowSync - allow sync triggle by cnpm install
* this.allowSync - allow sync triggle by cnpm install
*/
module.exports = function* syncByInstall(next) {
this.allowSync = false;
if (!config.syncByInstall) {
if (!config.syncByInstall || !config.enablePrivate) {
// only config.enablePrivate should enable sync on install
return yield next;
return yield* next;
}
// request not by node, consider it request from web, don't sync
const ua = this.get('user-agent');
// request not by node, consider it request from web
var ua = this.get('user-agent');
if (!ua || ua.indexOf('node') < 0) {
return yield next;
return yield* next;
}
// if request with `/xxx?write=true`, meaning the read request using for write, don't sync
// if request with `/xxx?write=true`, meaning the read request using for write
if (this.query.write) {
return yield next;
}
const name = this.params.name || this.params[0];
// private scoped package don't sync
if (name && name[0] === '@') {
const scope = name.split('/')[0];
if (config.scopes.indexOf(scope) >= 0) {
return yield next;
}
return yield* next;
}
this.allowSync = true;
yield next;
yield* next;
};

View File

@@ -1,14 +0,0 @@
'use strict';
module.exports = function* unpublishable(next) {
// only admin user can unpublish
if (!this.user.isAdmin) {
this.status = 403;
this.body = {
error: 'no_perms',
reason: 'Only administrators can unpublish module',
};
return;
}
yield next;
};

View File

@@ -1,9 +1,23 @@
/**!
* cnpmjs.org - middleware/web_not_found.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
*/
'use strict';
const debug = require('debug')('cnpmjs.org:middleware:web_not_found');
/**
* Module dependencies.
*/
module.exports = function* notFound(next) {
yield next;
var debug = require('debug')('cnpmjs.org:middleware:web_not_found');
module.exports = function *notFound(next) {
yield *next;
if (this.status && this.status !== 404) {
return;
@@ -12,7 +26,7 @@ module.exports = function* notFound(next) {
return;
}
let m = /^\/([\w\-\.]+)\/?$/.exec(this.path);
var m = /^\/([\w\-\.]+)\/?$/.exec(this.path);
if (!m) {
// scoped packages
m = /^\/(@[\w\-\.]+\/[\w\-\.]+)$/.exec(this.path);
@@ -24,16 +38,16 @@ module.exports = function* notFound(next) {
// package not found
m = /\/package\/([\w\-\_\.]+)\/?$/.exec(this.url);
let name = null;
let title = '404: Page Not Found';
var name = null;
var title = '404: Page Not Found';
if (m) {
name = m[1];
title = name + ' Not Found';
}
this.status = 404;
yield this.render('404', {
title,
name,
yield* this.render('404', {
title: title,
name: name
});
};

View File

@@ -1,144 +0,0 @@
'use strict';
/**
* list all module names by user
* @param {String} user
*/
exports.listModuleNamesByUser = function* (user) {
const rows = yield this.findAll({
attributrs: [ 'name' ],
where: {
user,
},
});
return rows.map(function(row) {
return row.name;
});
};
/**
* list all maintainers of module `name`
* @param {String} name
*/
exports.listMaintainers = function* (name) {
const rows = yield this.findAll({
attributrs: [ 'user' ],
where: {
name,
},
});
return rows.map(function(row) {
return row.user;
});
};
/**
* add a maintainer for module `name`
* @param {String} name
* @param {String} user
*/
exports.addMaintainer = function* (name, user) {
let row = yield this.find({
where: {
user,
name,
},
});
if (!row) {
row = yield this.build({
user,
name,
}).save();
}
return row;
};
/**
* add maintainers for module `name`
* @param {String} name
* @param {Array} users
*/
exports.addMaintainers = function* (name, users) {
return yield users.map(function(user) {
return this.addMaintainer(name, user);
}.bind(this));
};
/**
* remove maintainers for module `name`
* @param {String} name
* @param {Array} users
*/
exports.removeMaintainers = function* (name, users) {
// removeMaintainers(name, oneUserName)
if (typeof users === 'string') {
users = [ users ];
}
if (users.length === 0) {
return;
}
yield this.destroy({
where: {
name,
user: users,
},
});
};
/**
* remove all maintainers for module `name`
* @param {String} name
*/
exports.removeAllMaintainers = function* (name) {
yield this.destroy({
where: {
name,
},
});
};
/**
* add maintainers to module
* @param {String} name
* @param {Array} users
*/
exports.updateMaintainers = function* (name, users) {
// maintainers should be [username1, username2, ...] format
// find out the exists maintainers
// then remove all the users not present and add all the left
if (users.length === 0) {
return {
add: [],
remove: [],
};
}
const exists = yield this.listMaintainers(name);
const addUsers = users.filter(function(username) {
// add user which in `users` but do not in `exists`
return exists.indexOf(username) === -1;
});
const removeUsers = exists.filter(function(username) {
// remove user which in `exists` by not in `users`
return users.indexOf(username) === -1;
});
yield [
this.addMaintainers(name, addUsers),
this.removeMaintainers(name, removeUsers),
];
return {
add: addUsers,
remove: removeUsers,
};
};

View File

@@ -1,263 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
// CREATE TABLE IF NOT EXISTS `downloads` (
// `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
// `gmt_create` datetime NOT NULL COMMENT 'create time',
// `gmt_modified` datetime NOT NULL COMMENT 'modified time',
// `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
// `date` int unsigned NOT NULL COMMENT 'YYYYMM format',
// `d01` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '01 download count',
// `d02` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '02 download count',
// `d03` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '03 download count',
// `d04` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '04 download count',
// `d05` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '05 download count',
// `d06` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '06 download count',
// `d07` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '07 download count',
// `d08` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '08 download count',
// `d09` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '09 download count',
// `d10` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '10 download count',
// `d11` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '11 download count',
// `d12` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '12 download count',
// `d13` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '13 download count',
// `d14` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '14 download count',
// `d15` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '15 download count',
// `d16` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '16 download count',
// `d17` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '17 download count',
// `d18` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '18 download count',
// `d19` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '19 download count',
// `d20` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '20 download count',
// `d21` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '21 download count',
// `d22` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '22 download count',
// `d23` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '23 download count',
// `d24` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '24 download count',
// `d25` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '25 download count',
// `d26` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '26 download count',
// `d27` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '27 download count',
// `d28` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '28 download count',
// `d29` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '29 download count',
// `d30` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '30 download count',
// `d31` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '31 download count',
// PRIMARY KEY (`id`),
// UNIQUE KEY `name_date` (`name`, `date`)
// KEY `date` (`date`)
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module download total info';
module.exports = function(sequelize, DataTypes) {
return sequelize.define('DownloadTotal', {
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'module name',
},
date: {
type: DataTypes.INTEGER,
allowNull: false,
comment: 'YYYYMM format',
},
d01: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '01 download count',
},
d02: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '02 download count',
},
d03: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '03 download count',
},
d04: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '04 download count',
},
d05: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '05 download count',
},
d06: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '06 download count',
},
d07: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '07 download count',
},
d08: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '08 download count',
},
d09: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '09 download count',
},
d10: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '10 download count',
},
d11: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '11 download count',
},
d12: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '12 download count',
},
d13: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '13 download count',
},
d14: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '14 download count',
},
d15: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '15 download count',
},
d16: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '16 download count',
},
d17: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '17 download count',
},
d18: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '18 download count',
},
d19: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '19 download count',
},
d20: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '20 download count',
},
d21: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '21 download count',
},
d22: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '22 download count',
},
d23: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '23 download count',
},
d24: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '24 download count',
},
d25: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '25 download count',
},
d26: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '26 download count',
},
d27: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '27 download count',
},
d28: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '28 download count',
},
d29: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '29 download count',
},
d30: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '30 download count',
},
d31: {
type: DataTypes.BIGINT(20),
allowNull: false,
defaultValue: 0,
comment: '31 download count',
},
}, {
tableName: 'downloads',
comment: 'module download total info',
indexes: [
{
unique: true,
fields: [ 'name', 'date' ],
},
{
fields: [ 'date' ],
},
],
classMethods: {
},
});
};

View File

@@ -1,50 +0,0 @@
/**!
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.com)
*/
'use strict';
/**
* Module dependencies.
*/
const path = require('path');
const sequelize = require('../common/sequelize');
function load(name) {
return sequelize.import(path.join(__dirname, name));
}
module.exports = {
sequelize,
Module: load('module'),
ModuleLog: load('module_log'),
ModuleStar: load('module_star'),
ModuleKeyword: load('module_keyword'),
ModuleDependency: load('module_deps'),
ModuleMaintainer: load('module_maintainer'),
ModuleUnpublished: load('module_unpublished'),
NpmModuleMaintainer: load('npm_module_maintainer'),
Tag: load('tag'),
User: load('user'),
Total: load('total'),
DownloadTotal: load('download_total'),
* query(sql, args) {
const options = { replacements: args };
const data = yield this.sequelize.query(sql, options);
if (/select /i.test(sql)) {
return data[0];
}
return data[1];
},
* queryOne(sql, args) {
const rows = yield this.query(sql, args);
return rows && rows[0];
},
};

View File

@@ -1,59 +0,0 @@
/**!
* cnpmjs.org - models/init_script.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
const config = require('../config');
config.database.logging = console.log;
// $ node --harmony models/init_script.js <force> <dialect> <port> <username>
const force = process.argv[2] === 'true';
const dialect = process.argv[3];
if (dialect) {
config.database.dialect = dialect;
}
const port = process.argv[4];
if (port) {
config.database.port = parseInt(port);
}
const username = process.argv[5];
if (username) {
config.database.username = username;
}
const models = require('./');
models.sequelize.sync({
force,
logging: console.log,
})
.then(function() {
models.Total.init(function(err) {
if (err) {
console.error('[models/init_script.js] sequelize init fail');
console.error(err);
throw err;
} else {
console.log('[models/init_script.js] `%s` sequelize sync and init success',
config.database.dialect);
process.exit(0);
}
});
})
.catch(function(err) {
console.error('[models/init_script.js] sequelize sync fail');
console.error(err);
process.exit(1);
});

View File

@@ -1,94 +0,0 @@
'use strict';
/*
CREATE TABLE IF NOT EXISTS `module` (
`id` INTEGER NOT NULL auto_increment ,
`author` VARCHAR(100) NOT NULL,
`name` VARCHAR(100) NOT NULL,
`version` VARCHAR(30) NOT NULL,
`description` LONGTEXT,
`package` LONGTEXT,
`dist_shasum` VARCHAR(100),
`dist_tarball` VARCHAR(2048),
`dist_size` INTEGER UNSIGNED NOT NULL DEFAULT 0,
`publish_time` BIGINT(20) UNSIGNED,
`gmt_create` DATETIME NOT NULL,
`gmt_modified` DATETIME NOT NULL,
PRIMARY KEY (`id`)
)
COMMENT 'module info' ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci;
CREATE UNIQUE INDEX `module_name_version` ON `module` (`name`, `version`);
CREATE INDEX `module_gmt_modified` ON `module` (`gmt_modified`);
CREATE INDEX `module_publish_time` ON `module` (`publish_time`);
CREATE INDEX `module_author` ON `module` (`author`);
*/
module.exports = function(sequelize, DataTypes) {
return sequelize.define('Module', {
author: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'first maintainer name',
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'module name',
},
version: {
type: DataTypes.STRING(30),
allowNull: false,
comment: 'module version',
},
description: {
type: DataTypes.LONGTEXT,
},
package: {
type: DataTypes.LONGTEXT,
comment: 'package.json',
},
dist_shasum: {
type: DataTypes.STRING(100),
allowNull: true,
},
dist_tarball: {
type: DataTypes.STRING(2048),
allowNull: true,
},
dist_size: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
},
publish_time: {
type: DataTypes.BIGINT(20),
allowNull: true,
},
}, {
tableName: 'module',
comment: 'module info',
indexes: [
{
unique: true,
fields: [ 'name', 'version' ],
},
{
fields: [ 'gmt_modified' ],
},
{
fields: [ 'publish_time' ],
},
{
fields: [ 'author' ],
},
],
classMethods: {
* findByNameAndVersion(name, version) {
return yield this.find({
where: { name, version },
});
},
},
});
};

View File

@@ -1,58 +0,0 @@
/**!
* cnpmjs.org - models/module_deps.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
/*
CREATE TABLE IF NOT EXISTS `module_deps` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
`deps` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '`name` is deped by `deps`',
PRIMARY KEY (`id`),
UNIQUE KEY `module_deps_name_deps` (`name`,`deps`),
KEY `deps` (`module_deps_deps`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module deps';
*/
module.exports = function(sequelize, DataTypes) {
return sequelize.define('ModuleDependency', {
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'module name',
},
dependent: {
field: 'deps',
type: DataTypes.STRING(100),
comment: '`name` is depended by `deps`. `deps` == depend => `name`',
},
}, {
tableName: 'module_deps',
comment: 'module deps',
// no need update timestamp
updatedAt: false,
indexes: [
{
unique: true,
fields: [ 'name', 'deps' ],
},
{
fields: [ 'deps' ],
},
],
classMethods: {
},
});
};

View File

@@ -1,69 +0,0 @@
/**!
* cnpmjs.org - models/module_keyword.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
/*
CREATE TABLE IF NOT EXISTS `module_keyword` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
`keyword` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'keyword',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
`description` longtext,
PRIMARY KEY (`id`),
UNIQUE KEY `keyword_module_name` (`keyword`,`name`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module keyword';
*/
module.exports = function(sequelize, DataTypes) {
return sequelize.define('ModuleKeyword', {
keyword: {
type: DataTypes.STRING(100),
allowNull: false,
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'module name',
},
description: {
type: DataTypes.LONGTEXT,
allowNull: true,
},
}, {
tableName: 'module_keyword',
comment: 'module keyword',
updatedAt: false,
indexes: [
{
unique: true,
fields: [ 'keyword', 'name' ],
},
{
fields: [ 'name' ],
},
],
classMethods: {
* findByKeywordAndName(keyword, name) {
return yield this.find({
where: {
keyword,
name,
},
});
},
},
});
};

View File

@@ -1,56 +0,0 @@
/**!
* cnpmjs.org - models/module_log.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
/*
CREATE TABLE IF NOT EXISTS `module_log` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
`gmt_modified` datetime NOT NULL COMMENT 'modified time',
`username` varchar(100) NOT NULL,
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
`log` longtext,
PRIMARY KEY (`id`),
KEY `module_log_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module sync log';
*/
module.exports = function(sequelize, DataTypes) {
return sequelize.define('ModuleLog', {
username: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'user name',
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'module name',
},
log: {
type: DataTypes.LONGTEXT,
},
}, {
tableName: 'module_log',
comment: 'module sync log',
indexes: [
{
fields: [ 'name' ],
},
],
classMethods: {
},
});
};

View File

@@ -1,56 +0,0 @@
/**!
* cnpmjs.org - models/module_maintainer.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
/*
CREATE TABLE IF NOT EXISTS `module_maintainer` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
`user` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'user name',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
PRIMARY KEY (`id`),
UNIQUE KEY `module_maintainer_user_name` (`user`,`name`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='private module maintainers';
*/
module.exports = function(sequelize, DataTypes) {
return sequelize.define('ModuleMaintainer', {
user: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'user name',
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'module name',
},
}, {
tableName: 'module_maintainer',
comment: 'private module maintainers',
updatedAt: false,
indexes: [
{
unique: true,
fields: [ 'user', 'name' ],
},
{
fields: [ 'name' ],
},
],
classMethods: require('./_module_maintainer_class_methods'),
});
};

View File

@@ -1,57 +0,0 @@
/**!
* cnpmjs.org - models/module_star.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
/*
CREATE TABLE IF NOT EXISTS `module_star` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
`user` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'user name',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
PRIMARY KEY (`id`),
UNIQUE KEY `module_star_user_name` (`user`,`name`),
KEY `module_star_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module star';
*/
module.exports = function(sequelize, DataTypes) {
return sequelize.define('ModuleStar', {
user: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'user name',
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'module name',
},
}, {
tableName: 'module_star',
comment: 'module star',
updatedAt: false,
indexes: [
{
unique: true,
fields: [ 'user', 'name' ],
},
{
fields: [ 'name' ],
},
],
classMethods: {
},
});
};

Some files were not shown because too many files have changed in this diff Show More