Compare commits

...

375 Commits

Author SHA1 Message Date
fengmk2
b582f73e7d Release 2.0.0-beta5 2014-12-05 19:22:56 +08:00
fengmk2
9b52820a8d hotfix package.html typo. Closes #521 2014-12-05 19:20:00 +08:00
dead_horse
f08c9dd147 update editorconfig 2014-11-25 11:44:18 +08:00
Yiyu He
990a980af1 Merge pull request #517 from CatTail/master
Add editorconfig
2014-11-25 11:41:48 +08:00
zhongchiyu
948d1b652b Add editorconfig 2014-11-25 11:27:47 +08:00
Yiyu He
7bb782179b Merge pull request #514 from cnpm/style-fix
Style fix
2014-11-23 16:01:34 +08:00
fengmk2
1bf7eb7324 fix(web/package): package name to long cause style problem fix
Closes #511
2014-11-23 15:01:39 +08:00
fengmk2
da69df9bb0 fix(css): use github-markdown-css for markdown body
https://github.com/sindresorhus/github-markdown-css
2014-11-23 14:32:19 +08:00
Yiyu He
b475b231df Merge pull request #512 from cnpm/koa-mock
feat(mock): use koa-mock for front end dev
2014-11-22 10:36:09 +08:00
fengmk2
15cb3e66da feat(mock): use koa-mock for front end dev
demo: ![cnpm-pagemock](https://cloud.githubusercontent.com/assets/156269/5144334/808ddb62-71d4-11e4-9af7-506b17b96d18.gif)

Closes #509
2014-11-21 23:19:25 +08:00
fengmk2
9ee511b96c Release 2.0.0-beta4 2014-11-21 22:13:47 +08:00
fengmk2
cad590deb4 Merge pull request #510 from cnpm/hotfix-short
fix(registry): add missing /-/short api
2014-11-21 22:05:35 +08:00
fengmk2
c253994edc fix(registry): add missing /-/short api
Closes #508
2014-11-21 22:03:45 +08:00
fengmk2
2d042af2c6 Merge pull request #507 from cnpm/new-views
New views
2014-11-17 20:00:48 +08:00
dead_horse
ad0dd602e0 sort the array in test 2014-11-17 16:31:23 +08:00
dead_horse
d95221dd7f update test cases 2014-11-17 16:12:47 +08:00
dead_horse
acbf75514d zoom sync link 2014-11-17 15:53:59 +08:00
dead_horse
87ad2002a7 new design for package page 2014-11-17 15:47:16 +08:00
dead_horse
1183d24d26 image max width, fixed #505 2014-11-17 00:22:23 +08:00
Yiyu He
207707feff Merge pull request #503 from cnpm/block-ruby
feat(middleware): block Ruby user-agent
2014-11-12 19:22:23 +08:00
fengmk2
445db415c9 feat(middleware): block Ruby user-agent
Closes #502
2014-11-12 18:37:45 +08:00
fengmk2
b11693c6bc Release 2.0.0-beta3 2014-11-12 17:01:24 +08:00
fengmk2
a28ede9e32 Merge pull request #501 from cnpm/sync-hotfix
fix(sync): should not sync package when maintainers sort change
2014-11-12 16:56:24 +08:00
fengmk2
45fba36a7d fix(sync): should not sync package when maintainers sort change
Closes #500
2014-11-12 16:44:25 +08:00
Yiyu He
4e02b2d211 Merge pull request #498 from cnpm/fix-maintainers
fix(maintainer): fix missing maintainers
2014-11-11 09:22:06 +08:00
fengmk2
e11679f933 update private maintainer only 2014-11-10 23:48:03 +08:00
fengmk2
49d43999e4 fix(package): detect package is private or not
Use `services/common.js` isPrivatePackage(name)
2014-11-10 19:36:24 +08:00
fengmk2
3b71d81e4a fix(maintainer): fix missing maintainers
Closes #497
2014-11-10 18:44:11 +08:00
dead_horse
b92c239325 remove path.exists compat 2014-11-09 20:29:39 +08:00
fengmk2
25c998db33 Release 2.0.0-beta2 2014-11-09 17:44:17 +08:00
fengmk2
fc3ad15c53 fix(sync): add missing syncUpstreamFirst argument 2014-11-09 17:43:44 +08:00
fengmk2
4fda3d0375 Release 2.0.0-beta1 2014-11-07 17:16:00 +08:00
Yiyu He
dc5c3e24e2 Merge pull request #495 from cnpm/refactor-sync
refactor(sync_worker): only sync request need to sync upstream first
2014-11-07 01:37:25 +08:00
fengmk2
140f5b302e refactor(sync_worker): only sync request need to sync upstream first 2014-11-07 00:56:34 +08:00
fengmk2
aa9d6479de fix(sync_worker): make sure end event will emit 2014-11-07 00:42:01 +08:00
fengmk2
651d4b207e fix: mv readme.md script to public/js/readme.js 2014-11-06 23:44:24 +08:00
fengmk2
c8d66f6d78 fix(sync): hotfix co uncaughtException
Closes #494
2014-11-06 23:25:14 +08:00
Yiyu He
c02e66fea5 Merge pull request #493 from cnpm/sync-python-dist
feat(sync): sync python dist
2014-11-06 18:01:14 +08:00
fengmk2
ff51d7febf feat(sync): sync python dist
https://www.python.org/ftp/python/

Closes #492
2014-11-06 17:40:38 +08:00
dead_horse
2dc92530f5 hot fix remarkable 2014-11-06 16:46:54 +08:00
fengmk2
d835526eda Merge pull request #491 from cnpm/refactor-maintainers
refactor models/_module_maintainer_class_methods.js
2014-11-03 00:45:11 +08:00
dead_horse
6f29f4cc3b pin autod@1 2014-11-03 00:24:57 +08:00
dead_horse
a5baac4f7f remove useless comment 2014-11-03 00:19:29 +08:00
dead_horse
8f1f919d2e refactor models/_module_maintainer_class_methods.js 2014-11-03 00:06:49 +08:00
fengmk2
8e2f0052dc Release 2.0.0-beta0 2014-11-02 23:50:41 +08:00
Yiyu He
6e77a171fd Merge pull request #488 from cnpm/limit-log-size
fix(module_log): limit module sync log size to 1MB
2014-11-02 11:48:03 +08:00
fengmk2
3b08a7fd21 Merge pull request #490 from cnpm/issue482-remarkable
ungrade koa-markdown to use remarkable, close #482
2014-11-02 11:42:01 +08:00
dead_horse
e3f4d8d158 ungrade koa-markdown to use remarkable, close #482 2014-11-02 02:55:14 +08:00
fengmk2
5b3d92ee43 fix(module_log): limit module sync log size to 1MB
If log size bigger than 1MB, it will be auto slice to 0.5MB
2014-11-01 22:32:46 +08:00
fengmk2
c1e35cabe3 refactor(config): remove adaptScope config key 2014-11-01 22:03:39 +08:00
fengmk2
63d604d9bb chore(Makefile): $ make install-production 2014-10-31 16:09:00 +08:00
fengmk2
b402f04fc9 fix(sequelize): show warnning message when using old config.js 2014-10-31 16:05:07 +08:00
fengmk2
a4920fdcee docs(readme): Migrating from 1.x to 2.x
Add new wiki: https://github.com/cnpm/cnpmjs.org/wiki/Migrating-from-1.x-to-2.x
2014-10-31 15:22:22 +08:00
Yiyu He
798f357132 Merge pull request #484 from cnpm/sequelize-merge
Sequelize instead of raw SQL
2014-10-31 14:31:55 +08:00
fengmk2
9ce8482a13 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:16:16 +08:00
fengmk2
23067fa1a2 refactor(dispatch): remove unused codes 2014-10-31 14:11:44 +08:00
fengmk2
abdbf69f25 remove duplicate db_engine 2014-10-30 16:01:59 +08:00
fengmk2
a4d7331f52 fix blank line 2014-10-30 15:42:42 +08:00
fengmk2
fac45324b9 fix(sync): make sure save log in sequeue 2014-10-30 15:36:58 +08:00
fengmk2
98c750df1a show db engine on registry:/ 2014-10-30 14:30:57 +08:00
fengmk2
22335db1de docs(readme): change dev on local database deps 2014-10-30 14:15:35 +08:00
fengmk2
47ad267df5 fix tests 2014-10-30 13:42:51 +08:00
fengmk2
49b27d562d merge sequelize 2014-10-30 13:38:01 +08:00
fengmk2
f49ce17457 refactor(sync): fix sync/*.js tests 2014-10-30 12:11:15 +08:00
fengmk2
128383d032 refactor(controllers): controllers all change to ORM 2014-10-29 23:43:00 +08:00
fengmk2
f47615501c web/package/*.js done 2014-10-29 18:11:40 +08:00
fengmk2
adae52cef3 /package/:name refactor done 2014-10-29 17:35:56 +08:00
fengmk2
30407dee07 merge download.js to total.js 2014-10-29 16:35:10 +08:00
fengmk2
1ba6f08f4b rename download filename 2014-10-29 11:25:58 +08:00
fengmk2
083104e2e1 registry controller done 2014-10-29 11:19:58 +08:00
fengmk2
12b09ab559 fix use_package.js 2014-10-29 02:04:39 +08:00
fengmk2
cf910e1d8c add list package names since startkey 2014-10-29 00:14:41 +08:00
fengmk2
eda52b0a3e list all package names 2014-10-28 19:25:36 +08:00
fengmk2
cd2fd4a0b5 fix test 2014-10-28 19:15:03 +08:00
fengmk2
8cfa7a4d8b improve coverage 2014-10-28 18:56:36 +08:00
fengmk2
716efab4ea add remove package test cases 2014-10-28 18:44:50 +08:00
fengmk2
a0ec1c8521 refactor remove package 2014-10-28 14:59:34 +08:00
fengmk2
be1a966ce1 add package publish and add tag test cases 2014-10-28 14:14:20 +08:00
fengmk2
c5b5064e40 module controllers 拆分到 controllers/package/*.js 2014-10-28 00:14:27 +08:00
Yiyu He
de7bef05c2 fix badge 2014-10-25 10:18:51 +08:00
dead_horse
c03be9c69d add gitter 2014-10-25 10:13:45 +08:00
fengmk2
dd092b356d user data all from services/user.js 2014-10-20 01:56:47 +08:00
fengmk2
d3e93f3990 remove all proxy/*.js 2014-10-20 00:54:33 +08:00
fengmk2
07accd62e4 mv proxy/module_unpublished to models/module_unpublished 2014-10-20 00:31:46 +08:00
fengmk2
8c280bd789 refactor registry download request 2014-10-19 23:49:41 +08:00
fengmk2
3d0674fea3 mv proxy/module_maintainer to models/module_maintainer 2014-10-19 23:29:53 +08:00
fengmk2
393bd42938 mv proxy/dist.js to services/dist.js 2014-10-19 01:46:25 +08:00
fengmk2
1ac1cf82d8 mv proxy/module_log to services/module_log 2014-10-19 00:58:17 +08:00
fengmk2
206d595f02 refator module dependency 2014-10-19 00:36:29 +08:00
fengmk2
aa135d2a30 mv proxy/module.js into services/package.js 2014-10-16 00:01:55 +08:00
dead_horse
d2949c9450 Release 1.7.1 2014-10-15 02:43:01 +08:00
dead_horse
7e583b2edf fix typo in sync popular, fix #477 2014-10-15 02:42:24 +08:00
dead_horse
9b13c184fe Release 1.7.0 2014-10-15 02:26:35 +08:00
Yiyu He
1830301ae6 Merge pull request #475 from KidkArolis/configurable-short-registry-url
If sourceNpmRegistry is cnpm - use it in getShort
2014-10-15 02:22:56 +08:00
Karolis Narkevicius
7774a5c6d9 If sourceNpmRegistry is cnpm - use it in getShort
Only fallback to r.cnpmjs.org in case sourceNpmRegistry is not cnpm
2014-10-14 19:21:11 +01:00
fengmk2
05c6d7b4f6 Merge pull request #472 from cnpm/issue468-upon
add sync popular modules, close #468
2014-10-13 11:54:39 +08:00
fengmk2
d30260f301 --build-from-source 2014-10-13 03:29:49 +08:00
fengmk2
c8990083a7 mv proxy/total.js to services/total.js 2014-10-13 03:17:44 +08:00
fengmk2
ef246c2ec4 controllers/total.js done 2014-10-13 02:10:38 +08:00
dead_horse
2a8bd139d0 fix test label 2014-10-13 00:48:37 +08:00
dead_horse
ea322c3310 add sync popular modules, close #468 2014-10-13 00:42:06 +08:00
fengmk2
48d8cb81fc init database before run test 2014-10-12 22:09:19 +08:00
fengmk2
dd9f4a7f2e change proxy/user.js => models/user.js 2014-10-12 21:29:32 +08:00
fengmk2
56eb1b1640 use sequelize to connect database 2014-10-12 14:07:22 +08:00
fengmk2
4b7a191b73 Change version to 2.0.0 2014-10-12 13:03:06 +08:00
fengmk2
d48cf497cf add sendmail test script 2014-10-09 10:58:24 +08:00
fengmk2
abaac6089a Release 1.6.1 2014-10-09 01:15:09 +08:00
fengmk2
e8ba8eadb3 Merge pull request #466 from cnpm/sync-npm-package-maintainers
save npm original package maintainers to npm_module_maintainer table.
2014-10-09 00:46:29 +08:00
fengmk2
20093741eb make test on travis faster 2014-10-09 00:33:50 +08:00
fengmk2
51ddf2cf02 ensure not sync user also has his own package names 2014-10-09 00:24:14 +08:00
fengmk2
26efecd2fc add v1.6.x-upgrade sql 2014-10-09 00:01:50 +08:00
fengmk2
678c85102e save npm original package maintainers to npm_module_maintainer table. fixed #464 2014-10-09 00:01:14 +08:00
fengmk2
439b6663f3 use simple 404 2014-10-08 18:40:23 +08:00
fengmk2
3069179718 fix possible xss on 404 page 2014-10-08 18:29:13 +08:00
fengmk2
f2613edcd5 Release 1.6.0 2014-10-08 11:15:44 +08:00
dead_horse
6e9ceaf844 Merge pull request #463 from cnpm/by-user-api
list user all packages api. fixed #462
2014-10-08 01:06:14 +08:00
fengmk2
188dab4ed8 list user all packages api. fixed #462 2014-10-08 00:53:20 +08:00
dead_horse
5d0d742e85 Merge pull request #461 from cnpm/node-dev
add node-dev: $ make dev
2014-10-04 01:56:15 +08:00
fengmk2
92a828c04d update readme 2014-10-04 00:33:27 +08:00
fengmk2
3c804758e7 add node-dev: $ make dev 2014-10-04 00:26:08 +08:00
fengmk2
683b2e3640 Merge pull request #459 from cnpm/sync
always start sync worker
2014-10-03 21:11:39 +08:00
dead_horse
35d445cd11 always start sync worker 2014-10-03 02:06:44 +08:00
fengmk2
45a9e0ff97 Merge pull request #458 from cnpm/nodemailer
update node mailer
2014-10-02 09:14:51 +08:00
dead_horse
dda2f0785c update node mailer 2014-10-02 01:58:43 +08:00
dead_horse
ce02d2f48e update autod 2014-10-02 01:10:03 +08:00
fengmk2
c36523da17 Release 1.5.5 2014-09-25 00:00:46 +08:00
fengmk2
93f33a98cf fix sync in web 2014-09-24 23:44:03 +08:00
dead_horse
c8372f06a8 Merge pull request #454 from cnpm/sync-upstream-on-frist-package
sync upstream only the first package. ...
2014-09-24 23:38:25 +08:00
fengmk2
234e8bf27b sync upstream only the first package. make sync devDependencies optionsal, default is false 2014-09-24 23:34:35 +08:00
fengmk2
dfe56b6222 Merge pull request #453 from cnpm/config
add some comment, default sourceNpmRegistryIsCNpm to true
2014-09-24 23:14:13 +08:00
dead_horse
8548104ee6 add some comment, default sourceNpmRegistryIsCNpm to true 2014-09-24 23:10:13 +08:00
fengmk2
ceeb617f0d Release 1.5.4 2014-09-24 23:04:31 +08:00
fengmk2
91ee61c997 format sync log 2014-09-24 23:04:15 +08:00
fengmk2
1d94ba4e11 Release 1.5.3 2014-09-24 22:23:32 +08:00
dead_horse
5b09d974fb Merge pull request #452 from cnpm/sync-upstream-package
support sync upstream first. fixed #451
2014-09-24 19:54:36 +08:00
fengmk2
763224bbbb support sync upstream first. fixed #451 2014-09-24 19:43:51 +08:00
fengmk2
7b6c757b83 Release 1.5.2 2014-09-24 18:22:36 +08:00
fengmk2
fd915e0c5d support im url on user profile page 2014-09-24 18:21:31 +08:00
dead_horse
fb39eb8c2a Release 1.5.1 2014-09-23 17:15:17 +08:00
dead_horse
f139cccad0 search support case insensitive, close #450 2014-09-23 17:14:03 +08:00
fengmk2
4bb2e6a5d8 Merge pull request #449 from cnpm/sync-in-web
add config._syncInWeb, close #448
2014-09-23 09:20:06 +08:00
dead_horse
4d3c257474 add config._syncInWeb, close #448 2014-09-23 02:02:56 +08:00
dead_horse
b990bd9cf1 Merge pull request #445 from cnpm/show-maintainers-when-publish-fail
show maintainers when publish 403. fixed #430
2014-09-16 01:13:21 +08:00
fengmk2
917b407d1e show maintainers when publish 403. fixed #430 2014-09-16 01:05:40 +08:00
dead_horse
774fd44b12 Merge pull request #444 from cnpm/no-attachment-for-html
no attachment for html
2014-09-15 17:12:45 +08:00
fengmk2
a6b67f5453 no attachment for html 2014-09-15 17:10:49 +08:00
fengmk2
1e56c77deb Release 1.5.0 2014-09-15 16:50:20 +08:00
dead_horse
e1e6b6b674 Merge pull request #441 from cnpm/dist-sync-document
dist sync document too. fixed #420
2014-09-14 18:30:27 +08:00
fengmk2
615d16165c dist sync document too. fixed #420 2014-09-13 00:33:20 +08:00
fengmk2
8986503005 Release 1.4.4 2014-09-12 15:37:57 +08:00
fengmk2
4b5a49b626 badge version support 1.0.0-beta1. fixed #440 2014-09-12 11:54:58 +08:00
fengmk2
fce0876295 Release 1.4.3 2014-09-09 12:06:53 +08:00
fengmk2
9df81764a9 alias /:name/-/:file to /:name/download/:file. fixed #439 2014-09-09 12:06:05 +08:00
dead_horse
4801f15768 Release 1.4.2 2014-09-03 13:12:53 +08:00
dead_horse
d24422fdba change default source registry to taobao's registry 2014-09-03 13:11:55 +08:00
dead_horse
835f898eb3 Merge pull request #435 from cnpm/bluebird
add bluebird
2014-09-03 10:17:02 +08:00
fengmk2
2a3a0436e7 add bluebird 2014-09-03 10:12:05 +08:00
dead_horse
0a782ea014 bump fs-cnpm 2014-09-03 09:38:54 +08:00
dead_horse
3813827d77 Merge pull request #434 from cnpm/agent-stat
show agent sockets stat. fixed #433
2014-09-03 09:35:51 +08:00
fengmk2
264544c780 show agent sockets stat. fixed #433 2014-09-03 00:41:04 +08:00
dead_horse
416afb2e9a update readme 2014-09-02 20:51:25 +08:00
dead_horse
fe15eb78e9 remove pic in readme 2014-09-02 20:50:47 +08:00
dead_horse
64d2facef3 Release 1.4.1 2014-08-20 23:19:52 +08:00
dead_horse
d677e804b5 Merge branch 'master' of github.com:cnpm/cnpmjs.org 2014-08-20 23:18:35 +08:00
dead_horse
f4dae42589 fix login error status 2014-08-20 23:18:24 +08:00
fengmk2
71452f692e Release 1.4.0 2014-08-20 10:01:48 +08:00
dead_horse
c24526d7ff Merge pull request #429 from cnpm/badge
Badge version image
2014-08-20 09:56:48 +08:00
fengmk2
43dcd4b4f1 update readme for badge url desc 2014-08-20 09:53:37 +08:00
fengmk2
ff90c0f238 different version, different color badge 2014-08-20 09:47:06 +08:00
fengmk2
f85752e4bc add badge on package page 2014-08-20 00:25:26 +08:00
fengmk2
4b20c13a4f add version badge. fixed #427 2014-08-20 00:07:53 +08:00
dead_horse
ef61605e0c add download and node version badge 2014-08-18 21:32:52 +08:00
dead_horse
a5291d95dd Release 1.3.2 2014-08-18 11:45:25 +08:00
dead_horse
9de10e791c remove unused eventproxy 2014-08-18 11:27:25 +08:00
dead_horse
1162ed95b0 add custom config in tools/sync_not_exist.js 2014-08-18 11:25:09 +08:00
dead_horse
6cdbc0b058 Release 1.3.1 2014-08-18 10:15:49 +08:00
dead_horse
dc7239637d add sync not exist tools, close #424 2014-08-18 10:14:38 +08:00
dead_horse
b735c9c15c use gittip instand of alipay. close #425 2014-08-17 22:21:59 +08:00
dead_horse
417e19a330 update registry api doc 2014-08-16 16:01:06 +08:00
dead_horse
40ab0929e1 Release 1.3.0 2014-08-11 11:46:58 +08:00
dead_horse
6dabe60839 ignore config/config.js 2014-08-11 11:46:29 +08:00
fengmk2
2680512c71 Merge pull request #421 from cnpm/qn-cnpm
use simple fs storage in develop env
2014-08-10 08:59:40 +08:00
dead_horse
f444566a23 fix test case 2014-08-10 01:57:58 +08:00
dead_horse
99cb110ee2 use fs-cnpm 2014-08-10 01:27:08 +08:00
dead_horse
536704705c fix test 2014-08-09 21:32:36 +08:00
dead_horse
d919a81dae use qn-cnpm 2014-08-09 21:17:59 +08:00
dead_horse
fe8100ea30 bump cfork 2014-08-09 20:16:56 +08:00
dead_horse
764c32646e Release 1.2.2 2014-08-08 10:47:50 +08:00
dead_horse
dea16e72de bump koa 2014-08-08 10:45:50 +08:00
fengmk2
7f951a6c5d Release 1.2.1 2014-08-07 18:19:08 +08:00
fengmk2
a1f2bc87b7 Merge pull request #418 from cnpm/undeprecate
deprecated bug fix and support undeprecate
2014-08-07 18:18:31 +08:00
fengmk2
706a5e053b deprecated bug fix and support undeprecate 2014-08-07 18:06:32 +08:00
fengmk2
eddc1a1bb9 Release 1.2.0 2014-08-07 17:27:39 +08:00
dead_horse
6e5abe44b9 Merge pull request #417 from cnpm/deprecate-api
Deprecate API
2014-08-07 17:20:30 +08:00
fengmk2
e6484e7e7b show deprecated message 2014-08-07 17:18:44 +08:00
fengmk2
7a88fa49d2 Sync deprecated field if it missing 2014-08-07 16:48:41 +08:00
fengmk2
82189bd393 Support $ cnpm deprecate [pkgname]@[version] "message". fixed #415 2014-08-07 16:24:35 +08:00
fengmk2
7834f44901 Release 1.1.0 2014-08-07 10:18:39 +08:00
dead_horse
463cf44133 Merge pull request #414 from cnpm/add-maintainers-bugfix
Add user to maintainers when publish. fixed #395
2014-08-07 09:21:17 +08:00
fengmk2
e6bb4b76c2 Add user to maintainers when publish. fixed #395
Also remove the maintainers when package unpublish.
2014-08-07 00:32:10 +08:00
fengmk2
b86d8eb110 List all npm registry api. close #413 2014-08-06 00:49:28 +08:00
dead_horse
070e72b571 Merge pull request #412 from cnpm/limit-list-since
limit list since
2014-08-05 09:24:16 +08:00
fengmk2
6e6f87c147 limit list since 2014-08-05 01:51:19 +08:00
dead_horse
bc9306eda7 Merge pull request #411 from cnpm/deps-change
change deps by "~"
2014-08-04 23:30:02 +08:00
fengmk2
66ea71756c change deps by "~" 2014-08-04 23:25:12 +08:00
dead_horse
0a1fff70cc Merge pull request #409 from cnpm/cfork
use cfork to make sure worker fork and restart
2014-08-04 21:08:05 +08:00
fengmk2
71da60c16f use cfork to make sure worker fork and restart 2014-08-04 18:53:55 +08:00
dead_horse
b5d585988f Merge pull request #405 from cnpm/master-uncaughtException
handle master uncaughtException. fixed #403
2014-08-04 12:27:41 +08:00
fengmk2
758d1d4320 handle master uncaughtException. fixed #403 2014-08-04 12:26:26 +08:00
fengmk2
7b77f09264 Release 1.0.6 2014-08-02 02:13:44 +08:00
fengmk2
7731b44ab4 WTF moment@2.8.0 missing 2014-08-02 02:13:24 +08:00
fengmk2
4bc6998040 Release 1.0.5 2014-08-02 02:04:36 +08:00
fengmk2
0d28fbde54 fix remove tar test cases 2014-08-02 02:03:59 +08:00
fengmk2
660035c1d5 Merge pull request #401 from cnpm/hotfix-unpublish-version
unpublish pkg@version bug hotfix. fixed #400
2014-08-02 01:57:19 +08:00
fengmk2
f21e5a1c07 unpublish pkg@version bug hotfix. fixed #400 2014-08-02 01:56:32 +08:00
fengmk2
4d20d91965 Release 1.0.4 2014-08-01 18:40:45 +08:00
fengmk2
a3e33850fc hotfix #399 use not exists 2014-08-01 18:39:53 +08:00
fengmk2
d888ef3297 Release 1.0.3 2014-08-01 13:31:54 +08:00
fengmk2
ce4ec7e6e9 Merge pull request #397 from cnpm/list-users-packages
add maintaining packages in user page
2014-08-01 13:19:30 +08:00
dead_horse
fe319b06ba Release 1.0.2 2014-08-01 12:05:54 +08:00
dead_horse
d829600ed0 ~_~ fix response message 2014-08-01 12:05:25 +08:00
dead_horse
4dd59cb300 Release 1.0.1 2014-08-01 12:00:33 +08:00
dead_horse
5ff25474c0 Merge pull request #398 from cnpm/fix-auth
hot fix auth error
2014-08-01 11:58:54 +08:00
dead_horse
6d49a859c6 hot fix auth error 2014-08-01 11:58:23 +08:00
dead_horse
094178c3ca add maintaining packages in user page 2014-08-01 10:47:47 +08:00
fengmk2
63a57b906e Release 1.0.0 2014-08-01 09:26:04 +08:00
fengmk2
a09e6b142d Merge pull request #394 from cnpm/list-pravites
add private package list
2014-08-01 08:55:11 +08:00
dead_horse
bca9341e5d add private package list 2014-08-01 01:43:13 +08:00
fengmk2
71b0662d49 Release 0.9.2 2014-07-30 23:15:11 +08:00
fengmk2
b894c0fa2b hotfix save custom user bug 2014-07-30 23:14:53 +08:00
fengmk2
4127fa1f07 Release 0.9.1 2014-07-30 17:20:17 +08:00
dead_horse
5ee2a6e8fd Merge pull request #390 from cnpm/handle-user-service-auth-throw-error
Handle user service auth throw custom error message
2014-07-30 10:16:24 +08:00
fengmk2
8baa34145b Handle user service auth throw custom error message 2014-07-30 10:05:55 +08:00
fengmk2
4acb819356 Merge pull request #389 from cnpm/private-white-list
Private white list
2014-07-30 08:21:43 +08:00
dead_horse
2a84e39aee add test for config private packages 2014-07-30 00:52:44 +08:00
dead_horse
2019d72e36 add config.privatePackages 2014-07-29 23:55:42 +08:00
dead_horse
b9fa7e7f98 add more comments in config/index.js 2014-07-29 23:31:22 +08:00
fengmk2
694795e451 Release 0.9.0 2014-07-29 10:24:56 +08:00
dead_horse
fae67880a5 Merge pull request #388 from cnpm/user-service
Custom user service. fixed #385
2014-07-29 10:17:08 +08:00
fengmk2
0929748cbd listMaintainers only return name and email 2014-07-29 10:06:28 +08:00
fengmk2
a87945c6a0 remove sessions 2014-07-29 01:55:18 +08:00
fengmk2
d7345dcc76 scopes init mv to services/user.js 2014-07-29 01:51:32 +08:00
fengmk2
1a9ea8c6cf show user more profile 2014-07-29 01:42:02 +08:00
fengmk2
8f0ada7bf8 registry show user support custom user service 2014-07-29 00:54:18 +08:00
fengmk2
ddd9c9557d support custom user service for user auth 2014-07-29 00:11:34 +08:00
fengmk2
9b2a846865 remove session middleware 2014-07-28 09:17:42 +08:00
fengmk2
b1b238e20c add DefaultUserService 2014-07-28 09:17:42 +08:00
fengmk2
26031a49d2 Merge pull request #383 from cnpm/issue382-limit-scope
Issue382 limit scope
2014-07-28 09:15:39 +08:00
dead_horse
8bfc8ce71d check scopes in module.getAdapt 2014-07-27 23:32:58 +08:00
dead_horse
dffae785ca test public mode, fix some logic, close #382 2014-07-27 23:28:38 +08:00
dead_horse
078d7ddf3d fix sync scope test 2014-07-27 23:28:38 +08:00
dead_horse
8ec082db89 move scope.js into publishable.js, add forcePublishWithScope 2014-07-27 23:28:38 +08:00
dead_horse
040b727878 config.scopes not exist, means do not support scope 2014-07-27 23:28:38 +08:00
dead_horse
02e1ba63d6 fix scope test cases 2014-07-25 09:39:33 +08:00
dead_horse
a16883618e add test, fix middleware 2014-07-25 00:23:26 +08:00
dead_horse
7bd1c85d4e fix adapt scope 2014-07-25 00:12:01 +08:00
dead_horse
336e6f0da8 fix debug info 2014-07-24 23:55:07 +08:00
dead_horse
d43266670e add assert scope middleware 2014-07-24 23:54:53 +08:00
fengmk2
8fdbbf0b04 Release 0.8.7 2014-07-24 00:05:47 +08:00
fengmk2
b62a3b78fb fix unpublished info missing maintainers cause TypeError 2014-07-24 00:05:28 +08:00
fengmk2
d6cf093844 Release 0.8.6 2014-07-23 13:07:33 +08:00
fengmk2
081f0cb4f8 show unpublished info on web package page. fixes #381 2014-07-23 13:06:20 +08:00
fengmk2
0bf18639b6 Release 0.8.5 2014-07-22 23:38:58 +08:00
dead_horse
cbe7a165b0 Merge pull request #380 from cnpm/private-package-default-scoped
Only private package support default scoped. fixed #378
2014-07-22 23:14:31 +08:00
fengmk2
7cadfb4007 Only private package support default scoped. fixed #378 2014-07-22 21:55:45 +08:00
fengmk2
ec51fcf70c Release 0.8.4 2014-07-22 13:32:47 +08:00
fengmk2
565a7d3cfe Merge pull request #379 from cnpm/get-adapt
adapt default scpoe in /@:scope/:name/:version
2014-07-22 12:51:53 +08:00
dead_horse
4ac857fba2 adapt default scpoe in /@:scope/:name/:version 2014-07-22 11:42:18 +08:00
fengmk2
af7c952d2f Release 0.8.3 2014-07-22 10:14:12 +08:00
fengmk2
6705172d4b hot fix download 2014-07-22 10:13:55 +08:00
fengmk2
c0295ecc77 Release 0.8.2 2014-07-22 01:00:38 +08:00
fengmk2
aaf7f04d08 fix default scope detect 2014-07-22 01:00:22 +08:00
fengmk2
beceac10dc Release 0.8.1 2014-07-21 23:54:45 +08:00
dead_horse
fad58273b6 Merge pull request #377 from cnpm/default-scope
support default @org. close #376
2014-07-21 17:16:21 +08:00
fengmk2
e0361dc1f2 add more test cases 2014-07-21 16:18:28 +08:00
fengmk2
d8c337acc5 support default @org. close #376 2014-07-21 16:13:43 +08:00
dead_horse
9d7683bd61 hotfix redis init error 2014-07-21 14:14:52 +08:00
fengmk2
994b2e47aa Release 0.8.0 2014-07-21 13:58:24 +08:00
dead_horse
b14faad3fb Merge pull request #375 from cnpm/scope-package
support scoped package
2014-07-21 13:56:13 +08:00
fengmk2
9163f793a3 update readme show scoped packages 2014-07-21 13:53:33 +08:00
fengmk2
1c0d4fdcdc support "scoped" packages. close #352 2014-07-21 13:51:43 +08:00
dead_horse
20fce9f55f Merge pull request #373 from cnpm/stop-old-publish-flow
Stop old publish flow
2014-07-19 01:21:22 +08:00
fengmk2
6b73a7762f use safe jsonp 2014-07-19 01:16:30 +08:00
fengmk2
fd34e0512c Stop support old publish flow. fix #368 2014-07-19 01:11:33 +08:00
fengmk2
a84715167e Merge pull request #372 from cnpm/sql
update SQLs
2014-07-13 07:07:59 +08:00
dead_horse
db1cb0d0f8 update SQLs 2014-07-12 23:20:55 +08:00
dead_horse
dc137f0821 bump redis 2014-07-12 14:57:23 +08:00
fengmk2
76d270fc77 Merge pull request #371 from cnpm/update-logger
use sync_info and sync_error categories
2014-07-11 12:06:28 +08:00
dead_horse
b5190b56e8 use sync_info and sync_error categories 2014-07-11 10:03:41 +08:00
fengmk2
d2d238db59 add categories to loggers. fix #370 2014-07-11 09:07:48 +08:00
fengmk2
4c02d84fec fix get latest tag always not exists bug 2014-07-11 01:04:52 +08:00
dead_horse
74585f0b8a Merge pull request #369 from cnpm/publish-with-tag
support `npm publish --tag beta`. fix #366
2014-07-10 23:47:48 +08:00
fengmk2
a6ac632e73 support npm publish --tag beta. fix #366 2014-07-10 23:34:25 +08:00
fengmk2
0b59547ac1 Merge pull request #365 from cnpm/logger
use mini-logger and error-formater
2014-07-09 11:30:48 +08:00
dead_horse
b5929c94e0 use mini-logger and error-formater 2014-07-09 11:23:20 +08:00
fengmk2
4c96682603 Release 0.7.0 2014-07-07 12:11:12 +08:00
dead_horse
78af0fc15b Merge pull request #364 from cnpm/maintainer
Maintainer logic improve
2014-07-07 11:42:53 +08:00
fengmk2
ce381618a0 use module_maintainers on GET /pakcage/:name page 2014-07-07 10:25:06 +08:00
fengmk2
87486c484f use new module_maintainers on GET /:name 2014-07-07 10:23:53 +08:00
fengmk2
4608712e05 admin user should never publish to other user's packages. fix #363
but admin user need have the permission to delete other's packages.
2014-07-07 01:41:03 +08:00
fengmk2
3c6576bcab Add a new table for module-maintainers.
* fix #362 update maintianers change the lastmodified. remove version and change last modified.
* #363 change isMaintainer detect logic.
2014-07-07 00:56:44 +08:00
fengmk2
ba1986b931 gravatar use https 2014-07-02 00:09:56 +08:00
fengmk2
5efa508376 support https 2014-07-01 23:26:48 +08:00
dead_horse
38617d4572 bump dependencies 2014-06-30 17:31:04 +08:00
fengmk2
a9a04b370d Release 0.6.1 2014-06-18 12:27:47 +08:00
fengmk2
ec1d924dc1 hot fix removeTagsByNames() 2014-06-17 10:41:41 +08:00
fengmk2
234d1ec4d6 fix _rev not exists 2014-06-16 15:52:56 +08:00
dead_horse
b8f4958d8f Merge pull request #357 from cnpm/fix-sync-request
sync unpublished on GET /sync/:name
2014-06-16 15:23:22 +08:00
fengmk2
e56a21e890 sync unpublished on GET /sync/:name 2014-06-16 15:21:53 +08:00
fengmk2
e3434a58c5 Release 0.6.0 2014-06-16 15:06:17 +08:00
dead_horse
cd9d38aadb Merge pull request #356 from cnpm/unpublished
sync unpublished info. close #353
2014-06-16 13:50:05 +08:00
fengmk2
0ff22ccc60 sync unpublished info. close #353 2014-06-16 10:54:02 +08:00
dead_horse
e4cfb13383 Merge pull request #354 from cnpm/delete-not-exists-versions
Delete not exists versions on sync worker. #353
2014-06-16 09:39:58 +08:00
fengmk2
23ad3a29b4 Delete not exists versions on sync worker. #353 2014-06-15 00:57:18 +08:00
dead_horse
f11b4e9954 Release 0.5.3 2014-06-13 18:08:12 +08:00
dead_horse
6e775eed47 fix sync response 204 2014-06-13 18:05:09 +08:00
dead_horse
ca6841c042 add links in History.md 2014-06-08 00:59:54 +08:00
dead_horse
652b900fc4 bump koa 2014-06-08 00:59:22 +08:00
dead_horse
43379cc66d fix test-cov 2014-06-04 17:37:19 +08:00
dead_horse
8f9b3db029 bump koa and should 2014-06-04 17:29:52 +08:00
fengmk2
884b75f6a3 Release 0.5.2 2014-06-04 15:41:01 +08:00
dead_horse
a958ee40c4 Merge pull request #350 from cnpm/hotfix
sync hotfix
2014-06-04 15:39:05 +08:00
fengmk2
1a01909750 sync hotfix 2014-06-04 15:35:50 +08:00
dead_horse
6901a5c994 Merge pull request #349 from cnpm/dist-sync-phantomjs
sync phantomjs downloads pkg. close #348
2014-06-04 15:12:31 +08:00
fengmk2
33b13dc30e sync phantomjs downloads pkg. close #348 2014-06-04 14:35:20 +08:00
fengmk2
73d9d2c3d0 Merge pull request #347 from cnpm/issue346-restart-syncer
add restart, fixed #346
2014-06-04 12:39:34 +08:00
dead_horse
8b955adb42 add restart, fixed #346 2014-06-04 10:57:50 +08:00
fengmk2
1f219cbd1b Release 0.5.1 2014-05-28 09:18:15 +08:00
dead_horse
17025d42b4 Merge pull request #344 from cnpm/list-since-from-tag
fix attack on /-/all/since?stale=update_after&startkey=2 close #336
2014-05-28 09:09:43 +08:00
fengmk2
922f26b052 fix attack on /-/all/since?stale=update_after&startkey=2 close #336
Also add modified_time key to tag
2014-05-28 00:19:26 +08:00
dead_horse
9371685188 bump thunkify-wrap 2014-05-26 21:44:51 +08:00
dead_horse
ad0b66c7e5 bump koa-middlewares 2014-05-25 01:03:19 +08:00
dead_horse
5588880ec0 remove outputError 2014-05-25 00:44:26 +08:00
dead_horse
9ad72cbdcb bump dependencies 2014-05-25 00:43:16 +08:00
dead_horse
4d033471fc use svg badge 2014-05-25 00:36:58 +08:00
fengmk2
590b7f7eee Merge pull request #340 from cnpm/404-page
add package/notfound page
2014-05-19 19:26:34 +08:00
dead_horse
0ce817f9f3 add package/notfound page 2014-05-19 19:16:15 +08:00
fengmk2
59af2bfd56 add dist mirror link to home page 2014-05-13 09:37:46 +08:00
fengmk2
a83f6bb183 fix sync listdiff and add more test cases 2014-05-13 09:28:54 +08:00
fengmk2
b6f1531743 Release 0.5.0 2014-05-13 08:32:51 +08:00
dead_horse
42d0b538ca Merge pull request #337 from cnpm/dist-sync
Dist sync
2014-05-12 23:48:52 +08:00
fengmk2
a240eb9922 filter /nightlies/* 2014-05-12 17:32:06 +08:00
fengmk2
6853b73fb2 use koa setter instead of set() 2014-05-12 17:09:51 +08:00
fengmk2
a887acec05 update co-urllib 2014-05-12 10:19:19 +08:00
fengmk2
1d3f0f0ad3 add more info on error email 2014-05-12 10:03:01 +08:00
fengmk2
20e04065df add sync dist to sync/index.js 2014-05-12 07:38:48 +08:00
fengmk2
1cee298bc3 show dist page 2014-05-11 20:56:40 +08:00
fengmk2
854bc6c9d8 sync dist file and save it to database 2014-05-10 18:59:13 +08:00
fengmk2
b70c1c421a disable gzip before #335 has fix 2014-05-04 20:05:55 +08:00
dead_horse
b1b6172892 Release 0.4.3 2014-04-18 16:15:50 +08:00
fengmk2
e5a77a4368 Merge pull request #334 from cnpm/fix-permission
add permission check to /:name/:tag
2014-04-18 14:34:27 +08:00
dead_horse
b74cccd342 add permission check to /:name/:tag 2014-04-18 14:29:42 +08:00
fengmk2
6aae538f49 Merge pull request #333 from cnpm/issue332-tag
add put /:name/:tag, close #332
2014-04-18 14:12:56 +08:00
dead_horse
74c8b25374 fix space 2014-04-18 14:07:49 +08:00
dead_horse
0cec2edea6 add put /:name/:tag, close #332 2014-04-18 12:18:07 +08:00
fengmk2
b59a0a99cf Release 0.4.2 2014-04-17 17:33:17 +08:00
dead_horse
c7e82809e3 Merge pull request #330 from cnpm/package-size-show
fix fav ico and show pkg size on pkg info page. fix #318
2014-04-17 17:31:06 +08:00
fengmk2
206ee505a8 sync interval config 2014-04-17 17:28:27 +08:00
fengmk2
d39838f930 fix fav ico and show pkg size on pkg info page. fix #318 2014-04-17 14:59:38 +08:00
dead_horse
512e21aaf5 Merge pull request #329 from cnpm/fix-stack-over
sync work sync one done must wait for a defer.setImmediate. fix #328
2014-04-17 14:12:22 +08:00
fengmk2
f7904ee699 sync work sync one done must wait for a defer.setImmediate. fix #328 2014-04-17 09:11:46 +08:00
dead_horse
7007be6ef4 bump dep versions 2014-04-16 15:17:14 +08:00
dead_horse
70aa3299e5 Merge pull request #326 from cnpm/noattachment
if download tarball 404, throw err better than ignore it. fixed #325
2014-04-16 12:00:29 +08:00
fengmk2
a04f5d68eb if download tarball 404, throw err better than ignore it. fixed #325 2014-04-16 11:44:28 +08:00
fengmk2
7cfe3b58ce refator sync 2014-04-16 11:19:32 +08:00
fengmk2
afdf0bc653 Merge pull request #323 from cnpm/hot-fix-status
hotfix, close #319
2014-04-16 09:39:15 +08:00
fengmk2
42055ac91e Merge pull request #324 from cnpm/hotfix-dist
hotfix, close #321
2014-04-16 09:31:48 +08:00
dead_horse
b93e0dab41 hotfix, close #321 2014-04-16 09:28:22 +08:00
dead_horse
01f2187830 hotfix, close #319 2014-04-16 09:23:59 +08:00
fengmk2
c2f49fcdd9 support custom web home page 2014-04-14 15:14:28 +08:00
dead_horse
3925ef6044 Merge pull request #316 from cnpm/first-full-sync
npm get short only can read from cnpm now
2014-04-14 14:04:59 +08:00
fengmk2
85494d2ba0 npm get short only can read from cnpm now 2014-04-14 13:57:04 +08:00
dead_horse
660ca394d6 Merge pull request #315 from cnpm/local-bindding
Local bindding and redis fix
2014-04-14 10:47:39 +08:00
fengmk2
ad7eeac00c if using reverted proxy like nginx, only binding on local host 2014-04-14 10:43:49 +08:00
fengmk2
f63b72891b fix redis detect logic 2014-04-14 10:27:46 +08:00
224 changed files with 18408 additions and 6859 deletions

17
.editorconfig Normal file
View File

@@ -0,0 +1,17 @@
# 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

5
.gitignore vendored
View File

@@ -16,11 +16,16 @@ results
node_modules
npm-debug.log
public/dist/
.dist
config/config.js
backup/*.json
backup/*.gz
docs/web/history.md
docs/web/_readme.md
view/web/_layout.html
bin/mysql.js
bin/test.sql
coverage/
config/web_readme.md
.tmp/
*.sqlite

View File

@@ -24,7 +24,7 @@
// "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
"unused" : true, // 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
@@ -58,7 +58,7 @@
"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" : false, // true: Tolerate using this in a non-constructor function
"validthis" : true, // true: Tolerate using this in a non-constructor function
// Environments
"browser" : true, // Web Browser (window, document, etc)

3
.node-dev.json Normal file
View File

@@ -0,0 +1,3 @@
{
"notify": false
}

View File

@@ -8,6 +8,8 @@ logo.png
public/dist/
backup/*.json
backup/*.gz
docs/web/history.md
docs/web/_readme.md
view/web/_layout.html
bin/mysql.js
bin/test.sql
@@ -15,3 +17,8 @@ coverage/
.jshintrc
.jshintignore
.DS_Store
config/web_readme.md
.dist/
config/config.js
*.sqlite
.node-dev.json

View File

@@ -1,3 +1,6 @@
language: node_js
node_js:
- '0.11'
- '0.11.14'
before_install: "npm install --build-from-source"
script: "make test-travis-all"
after_script: "npm install coveralls@2 && cat ./coverage/lcov.info | coveralls"

View File

@@ -1,4 +1,413 @@
2.0.0-beta5 / 2014-12-05
==================
* hotfix package.html typo. Closes #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
* 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
* 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
==================
* fix typo in sync popular, fix [#477](https://github.com/cnpm/cnpmjs.org/issues/477)
1.7.0 / 2014-10-15
==================
* Merge pull request [#475](https://github.com/cnpm/cnpmjs.org/issues/475) from KidkArolis/configurable-short-registry-url
* If sourceNpmRegistry is cnpm - use it in getShort
* Merge pull request [#472](https://github.com/cnpm/cnpmjs.org/issues/472) from cnpm/issue468-upon
* fix test label
* add sync popular modules, close [#468](https://github.com/cnpm/cnpmjs.org/issues/468)
* add sendmail test script
1.6.1 / 2014-10-09
==================
* make test on travis faster
* ensure not sync user also has his own package names
* add [v1.6.x-upgrade.sql](https://github.com/cnpm/cnpmjs.org/blob/master/docs/update_sqls/v1.6.x-upgrade.sql)
* save npm original package maintainers to npm_module_maintainer table. fixed [#464](https://github.com/cnpm/cnpmjs.org/issues/464)
* use simple 404
1.6.0 / 2014-10-08
==================
* list user all packages api. fixed [#462](https://github.com/cnpm/cnpmjs.org/issues/462)
* add node-dev: $ make dev
* always start sync worker
* update node mailer
* update autod
1.5.5 / 2014-09-25
==================
* fix sync in web
* sync upstream only the first package. make sync devDependencies optionsal, default is false
* add some comment, default sourceNpmRegistryIsCNpm to true
1.5.4 / 2014-09-24
==================
* format sync log
1.5.3 / 2014-09-24
==================
* support sync upstream first. fixed [#451](https://github.com/cnpm/cnpmjs.org/issues/451)
1.5.2 / 2014-09-24
==================
* support im url on user profile page; update bootstrap to 3.2.0
1.5.1 / 2014-09-23
==================
* search support case insensitive, close [#450](https://github.com/cnpm/cnpmjs.org/issues/450)
* add config._syncInWeb, close [#448](https://github.com/cnpm/cnpmjs.org/issues/448)
* show maintainers when publish 403. fixed [#430](https://github.com/cnpm/cnpmjs.org/issues/430)
* no attachment for html
1.5.0 / 2014-09-15
==================
* dist sync document too. fixed [#420](https://github.com/cnpm/cnpmjs.org/issues/420)
1.4.4 / 2014-09-12
==================
* badge version support 1.0.0-beta1. fixed [#440](https://github.com/cnpm/cnpmjs.org/issues/440)
1.4.3 / 2014-09-09
==================
* alias /:name/-/:file to /:name/download/:file. fixed [#439](https://github.com/cnpm/cnpmjs.org/issues/439)
1.4.2 / 2014-09-03
==================
* change default source registry to taobao's registry
* Merge pull request [#435](https://github.com/cnpm/cnpmjs.org/issues/435) from cnpm/bluebird
* add bluebird
* bump fs-cnpm
* Merge pull request [#434](https://github.com/cnpm/cnpmjs.org/issues/434) from cnpm/agent-stat
* show agent sockets stat. fixed [#433](https://github.com/cnpm/cnpmjs.org/issues/433)
* update readme
* remove pic in readme
1.4.1 / 2014-08-20
==================
* fix login error status
1.4.0 / 2014-08-20
==================
* different version, different color badge, add version badge. fixed [#427](https://github.com/cnpm/cnpmjs.org/issues/427)
* add download and node version badge
1.3.2 / 2014-08-18
==================
* remove unused eventproxy
* add custom config in tools/sync_not_exist.js
1.3.1 / 2014-08-18
==================
* add sync not exist tools, close [#424](https://github.com/cnpm/cnpmjs.org/issues/424)
* use gittip instand of alipay. close [#425](https://github.com/cnpm/cnpmjs.org/issues/425)
* update registry api doc
1.3.0 / 2014-08-11
==================
* ignore config/config.js
* Merge pull request [#421](https://github.com/cnpm/cnpmjs.org/issues/421) from cnpm/qn-cnpm
* fix test case
* use fs-cnpm
* fix test
* use qn-cnpm
* bump cfork
1.2.2 / 2014-08-08
==================
* bump koa
1.2.1 / 2014-08-07
==================
* deprecated bug fix and support undeprecate
1.2.0 / 2014-08-07
==================
* show deprecated message
* Sync deprecated field if it missing
* Support $ cnpm deprecate [pkgname]@[version] "message". fixed [#415](https://github.com/cnpm/cnpmjs.org/issues/415)
1.1.0 / 2014-08-07
==================
* Add user to maintainers when publish. fixed [#395](https://github.com/cnpm/cnpmjs.org/issues/395)
* List all npm registry api. close [#413](https://github.com/cnpm/cnpmjs.org/issues/413)
* limit list since
* change deps by "~"
* use cfork to make sure worker fork and restart
* handle master uncaughtException. fixed [#403](https://github.com/cnpm/cnpmjs.org/issues/403)
1.0.6 / 2014-08-02
==================
* WTF moment@2.8.0 missing
1.0.5 / 2014-08-02
==================
* unpublish pkg@version bug hotfix. fixed [#400](https://github.com/cnpm/cnpmjs.org/issues/400)
1.0.4 / 2014-08-01
==================
* hotfix [#399](https://github.com/cnpm/cnpmjs.org/issues/399) use not exists
1.0.3 / 2014-08-01
==================
* add maintaining packages in user page
1.0.2 / 2014-08-01
==================
* ~_~ fix auth error response message
1.0.1 / 2014-08-01
==================
* Merge pull request [#398](https://github.com/cnpm/cnpmjs.org/issues/398) from cnpm/fix-auth
* hot fix auth error
1.0.0 / 2014-08-01
==================
* add private package list
0.9.2 / 2014-07-30
==================
* hotfix save custom user bug
0.9.1 / 2014-07-30
==================
* Handle user service auth throw custom error message
* add test for config private packages
* add config.privatePackages
* add more comments in config/index.js
0.9.0 / 2014-07-29
==================
* scopes init mv to services/user.js
* show user more profile
* registry show user support custom user service
* support custom user service for user auth
* remove session middleware
* add DefaultUserService
* check scopes in module.getAdapt
* test public mode, fix some logic, close [#382](https://github.com/cnpm/cnpmjs.org/issues/382)
* move scope.js into publishable.js, add forcePublishWithScope
* config.scopes not exist, means do not support scope
* add assert scope middleware
0.8.7 / 2014-07-24
==================
* fix unpublished info missing maintainers cause TypeError
0.8.6 / 2014-07-23
==================
* show unpublished info on web package page. fixes [#381](https://github.com/cnpm/cnpmjs.org/issues/381)
0.8.5 / 2014-07-22
==================
* Only private package support default scoped. fixed [#378](https://github.com/cnpm/cnpmjs.org/issues/378)
0.8.4 / 2014-07-22
==================
* adapt default scpoe in /@:scope/:name/:version
0.8.3 / 2014-07-22
==================
* hot fix download
0.8.2 / 2014-07-22
==================
* fix default scope detect
0.8.1 / 2014-07-21
==================
* add more test cases
* support default @org. close [#376](https://github.com/cnpm/cnpmjs.org/issues/376)
* hotfix redis init error
0.8.0 / 2014-07-21
==================
* support "scoped" packages. close [#352](https://github.com/cnpm/cnpmjs.org/issues/352)
* use safe jsonp
* Stop support old publish flow. fix [#368](https://github.com/cnpm/cnpmjs.org/issues/368)
* update SQLs
* use sync_info and sync_error categories
* add categories to loggers. fix [#370](https://github.com/cnpm/cnpmjs.org/issues/370)
* fix get latest tag always not exists bug
* support `npm publish --tag beta`. fix [#366](https://github.com/cnpm/cnpmjs.org/issues/366)
* use mini-logger and error-formater
0.7.0 / 2014-07-07
==================
* use module_maintainers on GET /pakcage/:name page
* use new module_maintainers on GET /:name
* admin user should never publish to other user's packages. fix [#363](https://github.com/cnpm/cnpmjs.org/issues/363)
* Add a new table for module-maintainers.
* gravatar use https
* support https
0.6.1 / 2014-06-18
==================
* hot fix removeTagsByNames()
* fix _rev not exists
* sync unpublished on GET /sync/:name
0.6.0 / 2014-06-16
==================
* sync unpublished info. close [#353](https://github.com/cnpm/cnpmjs.org/issues/353)
* Delete not exists versions on sync worker. [#353](https://github.com/cnpm/cnpmjs.org/issues/353)
0.5.3 / 2014-06-13
==================
* fix sync response 204
* add links in History.md
* bump koa
* fix test-cov
* bump koa and should
0.5.2 / 2014-06-04
==================
* sync hotfix
* sync phantomjs downloads pkg. close [#348](https://github.com/cnpm/cnpmjs.org/issues/348)
* add restart, fixed [#346](https://github.com/cnpm/cnpmjs.org/issues/346)
0.5.1 / 2014-05-28
==================
* fix attack on /-/all/since?stale=update_after&startkey=2 close [#336](https://github.com/cnpm/cnpmjs.org/issues/336)
* bump thunkify-wrap
* bump koa-middlewares
* remove outputError
* bump dependencies
* use svg badge
* add package/notfound page
* add dist mirror link to home page
* fix sync listdiff and add more test cases
0.5.0 / 2014-05-13
==================
* filter /nightlies/*
* use koa setter instead of set()
* add more info on error email
* add sync dist to sync/index.js
* show dist page
* sync dist file and save it to database
* disable gzip before [#335](https://github.com/cnpm/cnpmjs.org/issues/335) has fix
0.4.3 / 2014-04-18
==================
* Merge pull request [#334](https://github.com/cnpm/cnpmjs.org/issues/334) from cnpm/fix-permission
* add permission check to /:name/:tag
* Merge pull request [#333](https://github.com/cnpm/cnpmjs.org/issues/333) from cnpm/issue332-tag
* fix space
* add put /:name/:tag, close [#332](https://github.com/cnpm/cnpmjs.org/issues/332)
0.4.2 / 2014-04-17
==================
* sync interval config
* fix fav ico and show pkg size on pkg info page. fix [#318](https://github.com/cnpm/cnpmjs.org/issues/318)
* sync work sync one done must wait for a defer.setImmediate. fix [#328](https://github.com/cnpm/cnpmjs.org/issues/328)
* bump dep versions
* if download tarball 404, throw err better than ignore it. fixed [#325](https://github.com/cnpm/cnpmjs.org/issues/325)
* refator sync
* hotfix, close [#321](https://github.com/cnpm/cnpmjs.org/issues/321)
* hotfix, close [#319](https://github.com/cnpm/cnpmjs.org/issues/319)
* support custom web home page
* npm get short only can read from cnpm now
* if using reverted proxy like nginx, only binding on local host
* fix redis detect logic
0.4.1 / 2014-04-10
==================
@@ -9,18 +418,18 @@
* fix test cases to run on local machine
* add contribute guidelines
* use local mysql for dev env. fix #308
* use local mysql for dev env. fix [#308](https://github.com/cnpm/cnpmjs.org/issues/308)
* use copy to
* use koa-compress and koa-conditional-get
* maintainers is string, fix #301
* maintainers is string, fix [#301](https://github.com/cnpm/cnpmjs.org/issues/301)
0.3.13 / 2014-03-27
==================
* fix npm adduser update 409 bug
* fix multiline coverage
* show package engines. fixed #280
* dont sync local package field. fix #295
* show package engines. fixed [#280](https://github.com/cnpm/cnpmjs.org/issues/280)
* dont sync local package field. fix [#295](https://github.com/cnpm/cnpmjs.org/issues/295)
0.3.12 / 2014-03-26
==================
@@ -28,53 +437,53 @@
* fix result.successes not exist error
* fix search list
* add simple request for listall
* only return package name in /-/all and /-/all/since, fixed #291
* only return package name in /-/all and /-/all/since, fixed [#291](https://github.com/cnpm/cnpmjs.org/issues/291)
* refine docs foloder
* use module gmt_modified as etag. fix #288
* use module gmt_modified as etag. fix [#288](https://github.com/cnpm/cnpmjs.org/issues/288)
* fix typo, remove unused config in package.json
* web page only list cnpm registry related info
* use generator in qnfs
0.3.11 / 2014-03-20
0.3.11 / 2014-03-20
==================
* use common.isMaintainer, fixed #283
* use common.isMaintainer, fixed [#283](https://github.com/cnpm/cnpmjs.org/issues/283)
* update dependencies
* use co-mocha for test, fixed #279
* use co-mocha for test, fixed [#279](https://github.com/cnpm/cnpmjs.org/issues/279)
* update thunkify-wrap, breaking change in thunkify-wrap
* refactor SQLs by using multiline
* use multiline to refactor sqls
* ignore contributors
0.3.10 / 2014-03-16
0.3.10 / 2014-03-16
==================
* Only /_session request send the authSession. fixed #223
* sync npm user info when maintainers and contributors not exists. fixed #82
* Only /_session request send the authSession. fixed [#223](https://github.com/cnpm/cnpmjs.org/issues/223)
* sync npm user info when maintainers and contributors not exists. fixed [#82](https://github.com/cnpm/cnpmjs.org/issues/82)
* save npm user to mysql
* password salt always be randoms
* remove session access in /name and /name/version, fixed #274
* remove session access in /name and /name/version, fixed [#274](https://github.com/cnpm/cnpmjs.org/issues/274)
* fix update maintainer session error
* update koa-middlewares
* fix test, fix sync_by_install
* use defer session
* Support npm owner|author add [name] [pkg]. fixed #271
* Support npm owner|author add [name] [pkg]. fixed [#271](https://github.com/cnpm/cnpmjs.org/issues/271)
0.3.9 / 2014-03-14
0.3.9 / 2014-03-14
==================
* custom user-agent
* use co-urllib instead of thunkify urllib; fix mock http.request test cases
* request limit custom message
* add config.redis check
* add koa-limit, fixed #267
* add koa-limit, fixed [#267](https://github.com/cnpm/cnpmjs.org/issues/267)
0.3.8 / 2014-03-11
0.3.8 / 2014-03-11
==================
* update middlewares, fixed missing charset bug #264
* update middlewares, fixed missing charset bug [#264](https://github.com/cnpm/cnpmjs.org/issues/264)
0.3.7 / 2014-03-11
0.3.7 / 2014-03-11
==================
* show worker die date time
@@ -84,63 +493,63 @@
* fix return versions
* fix makefile, remove eventproxy
* refactor sync_module_worker
* add make test-dev, fixed #259
* add make test-dev, fixed [#259](https://github.com/cnpm/cnpmjs.org/issues/259)
* change npm.js to generator
* update urllib, proxy/npm.js use generator
* sync_all and sync_exist to generator
* change function to generator
* need node >= v0.11.9
0.3.6 / 2014-03-06
0.3.6 / 2014-03-06
==================
* install missing package should sync it from source npm. fixed #252
* install missing package should sync it from source npm. fixed [#252](https://github.com/cnpm/cnpmjs.org/issues/252)
* npm publish dont contains .jshint*
* npm test run jshint
* Add jshint check: $ make jshint
* use `yield* next` instead of `yield next`
* replace dist.u.qiniudn.com with cnpmjs.org/dist
0.3.5 / 2014-03-05
0.3.5 / 2014-03-05
==================
* redirect /dist/xxx.tgz => http://dist.u.qiniudn.com/xxx.tgz fixed #249
* redirect /name to /package/name when /name is 404. fixed #245
* Add missing properies and sync missing star users. fixed #235
* redirect /dist/xxx.tgz => http://dist.u.qiniudn.com/xxx.tgz fixed [#249](https://github.com/cnpm/cnpmjs.org/issues/249)
* redirect /name to /package/name when /name is 404. fixed [#245](https://github.com/cnpm/cnpmjs.org/issues/245)
* Add missing properies and sync missing star users. fixed [#235](https://github.com/cnpm/cnpmjs.org/issues/235)
0.3.4 / 2014-03-04
0.3.4 / 2014-03-04
==================
* add cov
* use istanbul run test coverage
* gzip support. fix #241
* gzip support. fix [#241](https://github.com/cnpm/cnpmjs.org/issues/241)
* readme spelling patch (@stanzheng)
* default readme to null, fixed #233
* default readme to null, fixed [#233](https://github.com/cnpm/cnpmjs.org/issues/233)
* remove readme in versions
0.3.3 / 2014-02-28
0.3.3 / 2014-02-28
==================
* Merge pull request #232 from cnpm/host-hotfix
* Merge pull request [#232](https://github.com/cnpm/cnpmjs.org/issues/232) from cnpm/host-hotfix
* get request host from request.headers
* Merge pull request #231 from cnpm/bug-fix
* fix deps display bug#230 and nsf.url TypeError#229
* Merge pull request [#231](https://github.com/cnpm/cnpmjs.org/issues/231) from cnpm/bug-fix
* fix deps display bug[#230](https://github.com/cnpm/cnpmjs.org/issues/230) and nsf.url TypeError[#229](https://github.com/cnpm/cnpmjs.org/issues/229)
0.3.2 / 2014-02-28
0.3.2 / 2014-02-28
==================
* update koa-sess and koa-redis
* fix sync all test
* remove nfs.downloadStream first, fix tmppath error
* fix fengmk2/giturl#1 bug
* fix fengmk2/giturl[#1](https://github.com/cnpm/cnpmjs.org/issues/1) bug
0.3.1 / 2014-02-27
0.3.1 / 2014-02-27
==================
* add etag fixed #224
* add etag fixed [#224](https://github.com/cnpm/cnpmjs.org/issues/224)
* travis ci install on source npm
0.3.0 / 2014-02-27
0.3.0 / 2014-02-27
==================
* fix typo and dont sync not exists pkgs
@@ -161,162 +570,162 @@
* fix all the test of registry module.test.js
* convert registry/module.js to koa type
* fix auth middleware
* finish registry user controller koa and update mm to support thunkify. fixed #196
* finish registry user controller koa and update mm to support thunkify. fixed [#196](https://github.com/cnpm/cnpmjs.org/issues/196)
* change controllers/user.js to koa
* thunkify all proxy
* convert all middlewares to koa type
* change regsitry sync to koa
* addd koa-jsonp, koa-bodyparser, fix / controller
* first koa run registry home page /
* Merge pull request #212 from cnpm/fix-sync-404
* Merge pull request [#212](https://github.com/cnpm/cnpmjs.org/issues/212) from cnpm/fix-sync-404
* return friendly 404 reason
* Merge pull request #211 from cnpm/bug-fix
* override json limit to default 10mb. fixed #209
* fix #210 addPackageAndDist package version detect bug
* Merge pull request [#211](https://github.com/cnpm/cnpmjs.org/issues/211) from cnpm/bug-fix
* override json limit to default 10mb. fixed [#209](https://github.com/cnpm/cnpmjs.org/issues/209)
* fix [#210](https://github.com/cnpm/cnpmjs.org/issues/210) addPackageAndDist package version detect bug
0.2.27 / 2014-02-19
0.2.27 / 2014-02-19
==================
* support json result in search, fixed #189
* support json result in search, fixed [#189](https://github.com/cnpm/cnpmjs.org/issues/189)
0.2.26 / 2014-02-19
0.2.26 / 2014-02-19
==================
* npm publish also need to add deps
0.2.25 / 2014-02-19
0.2.25 / 2014-02-19
==================
* max handle number of package.json `dependencies` property
* Dependents support. fixed #190
* Dependents support. fixed [#190](https://github.com/cnpm/cnpmjs.org/issues/190)
0.2.24 / 2014-02-13
0.2.24 / 2014-02-13
==================
* fix if delete all the versions
* refactor remove module, fixed #186
* refactor remove module, fixed [#186](https://github.com/cnpm/cnpmjs.org/issues/186)
0.2.23 / 2014-01-26
0.2.23 / 2014-01-26
==================
* system admin can add, publish, remove the packages. fixed #176
* system admin can add, publish, remove the packages. fixed [#176](https://github.com/cnpm/cnpmjs.org/issues/176)
0.2.22 / 2014-01-26
0.2.22 / 2014-01-26
==================
* add keyword and search support keyword. #181
* add keyword and search support keyword. [#181](https://github.com/cnpm/cnpmjs.org/issues/181)
0.2.21 / 2014-01-24
0.2.21 / 2014-01-24
==================
* refactor code styles on package.html
* nav-tabs e.preventDefault
* Show registry server error response. fixed #178
* Show registry server error response. fixed [#178](https://github.com/cnpm/cnpmjs.org/issues/178)
* nav-tabs for package.html (@4simple)
0.2.20 / 2014-01-23
0.2.20 / 2014-01-23
==================
* hotfix sync missing dependencies and readmes
* fix sync readme error, fixed #174
* fix sync readme error, fixed [#174](https://github.com/cnpm/cnpmjs.org/issues/174)
* add updateReadme in module
0.2.19 / 2014-01-22
0.2.19 / 2014-01-22
==================
* npm install no need to check authorization header. fixed #171
* npm install no need to check authorization header. fixed [#171](https://github.com/cnpm/cnpmjs.org/issues/171)
0.2.18 / 2014-01-20
0.2.18 / 2014-01-20
==================
* Support gitlab git url to display and click. fixed #160
* Support gitlab git url to display and click. fixed [#160](https://github.com/cnpm/cnpmjs.org/issues/160)
* fix redis crash
0.2.17 / 2014-01-17
0.2.17 / 2014-01-17
==================
* custom logo url
* hotfix layout bug
0.2.16 / 2014-01-16
0.2.16 / 2014-01-16
==================
* fix publish-time bug
0.2.15 / 2014-01-16
0.2.15 / 2014-01-16
==================
* add publish_time to debug
0.2.14 / 2014-01-16
0.2.14 / 2014-01-16
==================
* add make autod
* update publish_time, fixed #163
* update publish_time, fixed [#163](https://github.com/cnpm/cnpmjs.org/issues/163)
0.2.13 / 2014-01-15
0.2.13 / 2014-01-15
==================
* markdown tmpl not support footer, need to wrap on app start
0.2.12 / 2014-01-15
0.2.12 / 2014-01-15
==================
* add footer and npm client name customable
0.2.11 / 2014-01-15
0.2.11 / 2014-01-15
==================
* package page contributor link to search, default is true
0.2.10 / 2014-01-14
0.2.10 / 2014-01-14
==================
* fix #155 Content-Disposition wrong.
* fix [#155](https://github.com/cnpm/cnpmjs.org/issues/155) Content-Disposition wrong.
0.2.9 / 2014-01-14
0.2.9 / 2014-01-14
==================
* support startkey=c and startkey="c"
* support couch db search api. fixed #153
* support couch db search api. fixed [#153](https://github.com/cnpm/cnpmjs.org/issues/153)
* fix fork me image link
* support sync by query.name
0.2.8 / 2014-01-14
0.2.8 / 2014-01-14
==================
* dont show err stack on test env
* add download link for package page
0.2.7 / 2014-01-13
0.2.7 / 2014-01-13
==================
* add shasum when nfs.upload and hfs.uploadBuffer, fixed #148
* add shasum when nfs.upload and hfs.uploadBuffer, fixed [#148](https://github.com/cnpm/cnpmjs.org/issues/148)
0.2.6 / 2014-01-13
0.2.6 / 2014-01-13
==================
* support custom session store, fixed #146
* support custom session store, fixed [#146](https://github.com/cnpm/cnpmjs.org/issues/146)
0.2.5 / 2014-01-13
0.2.5 / 2014-01-13
==================
* add download timeout and unit test
* use downloadStream() first
* nfs download to a writeable stream.
0.2.4 / 2014-01-10
0.2.4 / 2014-01-10
==================
* set main script to index.js, fixed #142
* set main script to index.js, fixed [#142](https://github.com/cnpm/cnpmjs.org/issues/142)
0.2.3 / 2014-01-10
0.2.3 / 2014-01-10
==================
* Dont show sync button on private package
* Sync package as publish with no deps. fixed #138
* Sync package as publish with no deps. fixed [#138](https://github.com/cnpm/cnpmjs.org/issues/138)
0.2.2 / 2014-01-10
0.2.2 / 2014-01-10
==================
* keep compatibility
@@ -326,23 +735,23 @@
* new npm publish in one request, add _publish_in_cnpm
* support unsure name ufs
* contributors maybe a object
* Object #<Object> has no method 'forEach' fixed #134
* support custom config as a module, fixed issue #132
* support npm new publish flow. fixed #129
* Object #<Object> has no method 'forEach' fixed [#134](https://github.com/cnpm/cnpmjs.org/issues/134)
* support custom config as a module, fixed issue [#132](https://github.com/cnpm/cnpmjs.org/issues/132)
* support npm new publish flow. fixed [#129](https://github.com/cnpm/cnpmjs.org/issues/129)
* add toString and constructor to test admin
* fix #119 hasOwnProperty check admin bug.
* fix [#119](https://github.com/cnpm/cnpmjs.org/issues/119) hasOwnProperty check admin bug.
0.2.0 / 2013-12-27
0.2.0 / 2013-12-27
==================
* remove to lower case
* fix #127 execSync and execsync.
* fix [#127](https://github.com/cnpm/cnpmjs.org/issues/127) execSync and execsync.
* add contributors list on package page
* mv blanket to config
* sync typeerror fix #statusCode
* add disturl
* fix #122 admin security bug
* fixed #121, let pkg 404 as success
* fix [#122](https://github.com/cnpm/cnpmjs.org/issues/122) admin security bug
* fixed [#121](https://github.com/cnpm/cnpmjs.org/issues/121), let pkg 404 as success
* fix sql insert error
* fix typos
@@ -354,78 +763,78 @@
* make sure all packages name are lower case
* select ids from tag
* fix nodejsctl
* fix #112 missing versions and time no sync
* fix [#112](https://github.com/cnpm/cnpmjs.org/issues/112) missing versions and time no sync
* remove restart command
* fix sync missing packages error
* fix web/readme.md, add install
* fix #109 pkg no times and no versions bug.
* fix [#109](https://github.com/cnpm/cnpmjs.org/issues/109) pkg no times and no versions bug.
0.1.2 / 2013-12-19
==================
* fix times not exists canot sync bug. fixed #101
* fix times not exists canot sync bug. fixed [#101](https://github.com/cnpm/cnpmjs.org/issues/101)
* support npm run command
* remove before_install and install in travis, fixed #102
* split all sub queries, fixed #104
* fix doc, fixed #103
* remove before_install and install in travis, fixed [#102](https://github.com/cnpm/cnpmjs.org/issues/102)
* split all sub queries, fixed [#104](https://github.com/cnpm/cnpmjs.org/issues/104)
* fix doc, fixed [#103](https://github.com/cnpm/cnpmjs.org/issues/103)
* fix search too slow.
* dont email sync log level info
* only sync missing packages at first time
* update dependencies
* sync all will sync all the missing packages, fixed #97
* sync all will sync all the missing packages, fixed [#97](https://github.com/cnpm/cnpmjs.org/issues/97)
0.1.0 / 2013-12-12
==================
* add sync title
* add favicon. fixed #69
* refine sync page, fiexd #70
* add favicon. fixed [#69](https://github.com/cnpm/cnpmjs.org/issues/69)
* refine sync page, fiexd [#70](https://github.com/cnpm/cnpmjs.org/issues/70)
* add app version
* add test for sync
* refine sync page
* registry and web all use controllers/sync.js
* sync from web, fixed #58
* sync from web, fixed [#58](https://github.com/cnpm/cnpmjs.org/issues/58)
* saving missing descriptions
* add package download info. fixed #63
* add package download info. fixed [#63](https://github.com/cnpm/cnpmjs.org/issues/63)
* add avatar
* use dependecies, fixed #issue62
* support open search, fixed #60
* make sure publish_time and author is same to source npm registry. fixed #56
* support open search, fixed [#60](https://github.com/cnpm/cnpmjs.org/issues/60)
* make sure publish_time and author is same to source npm registry. fixed [#56](https://github.com/cnpm/cnpmjs.org/issues/56)
* add test for search
* add a simple search by mysql like
* fix This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery. fixed #54
* fix This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery. fixed [#54](https://github.com/cnpm/cnpmjs.org/issues/54)
* update install doc, use nodejsctl to start
* must add limit on list by author sql
* fix sql, change test to fit my local database, fixed #46
* fix sql, change test to fit my local database, fixed [#46](https://github.com/cnpm/cnpmjs.org/issues/46)
* use registry.cnpmjs.org
* add install document and total package info on home page. fix #42
* add module_id to tag table. #46
* skip error version. fixed #43
* add install document and total package info on home page. fix [#42](https://github.com/cnpm/cnpmjs.org/issues/42)
* add module_id to tag table. [#46](https://github.com/cnpm/cnpmjs.org/issues/46)
* skip error version. fixed [#43](https://github.com/cnpm/cnpmjs.org/issues/43)
* sync may make a user do not exist in database, but have modules in registry
* add user page
* fix set license
* ignore 404 on sync. fixed #39
* ignore 404 on sync. fixed [#39](https://github.com/cnpm/cnpmjs.org/issues/39)
* fix module page, add test
* update urllib to 0.5.5
* version and tag
* add module page
* fix download url
* first get tag, then try version
* support sync triggle by install, finish #31
* support sync triggle by install, finish [#31](https://github.com/cnpm/cnpmjs.org/issues/31)
* addTag error return 500
* just one download field
* add download total info on home page
* add download count
* versions empty and also check missing tags
* remove tags on unpublish
* add module tag. fix #6
* add module tag. fix [#6](https://github.com/cnpm/cnpmjs.org/issues/6)
* add [done] flag to check sync done on client
* get sync log #29
* get sync log [#29](https://github.com/cnpm/cnpmjs.org/issues/29)
* fix test in module
* rm tmp file on down request error
* add time for debug str
* fix pkg not exists null bug
* use sync module woker to handle sync process. fixed #19
* use sync module woker to handle sync process. fixed [#19](https://github.com/cnpm/cnpmjs.org/issues/19)
* if private mode enable, only admin can publish module
* add alias in readme
* fix sql, add sort by name
@@ -434,15 +843,15 @@
* add npm and cnpm image
* add registry total info on home page
* fix mods bug in module.removeAll, change module.update => module.removeWithVersions
* add test, fix bug. fixed #18
* add test, fix bug. fixed [#18](https://github.com/cnpm/cnpmjs.org/issues/18)
* spoort unpublish
* add web page index readme
* switchable nfs #21
* switchable nfs [#21](https://github.com/cnpm/cnpmjs.org/issues/21)
* change file path to match npm file path
* use qn cdn to store tarball file fixed #16
* add GET /:name/:version, fixed #3
* use qn cdn to store tarball file fixed [#16](https://github.com/cnpm/cnpmjs.org/issues/16)
* add GET /:name/:version, fixed [#3](https://github.com/cnpm/cnpmjs.org/issues/3)
* add module controller test cases; fix next module not exists logic bug.
* publish module flow finish #11
* publish module flow finish [#11](https://github.com/cnpm/cnpmjs.org/issues/11)
* add test for controllers/registry/user.js
* add test for middleware/auth
* add test for proxy/user
@@ -453,9 +862,9 @@
* add start time
* add home page
* remove session controller
* adduser() finish fixed #5
* adduser() finish fixed [#5](https://github.com/cnpm/cnpmjs.org/issues/5)
* rm app.js and routes.js
* Mock npm adduser server response, fixing #5
* Mock npm adduser server response, fixing [#5](https://github.com/cnpm/cnpmjs.org/issues/5)
* adjust project dir, separate registry and web server
* Init rest frame for cnpmjs.org
* init

View File

@@ -1,52 +1,103 @@
TESTS = $(shell ls -S `find test -type f -name "*.test.js" -print`)
REPORTER = tap
REPORTER = spec
TIMEOUT = 30000
MOCHA_OPTS =
REGISTRY = --registry=http://r.cnpmjs.org
REGISTRY = --registry=https://registry.npm.taobao.org
DB = sqlite
install:
@npm install $(REGISTRY) \
--disturl=http://dist.cnpmjs.org
@npm install --build-from-source $(REGISTRY) \
--disturl=https://npm.taobao.org/dist
install-production production:
@NODE_ENV=production $(MAKE) install
jshint: install
@-./node_modules/.bin/jshint ./
@-node_modules/.bin/jshint ./
pretest:
init-database:
@node --harmony test/init_db.js
init-mysql:
@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;'
test: install pretest
@NODE_ENV=test ./node_modules/.bin/mocha \
--harmony-generators \
test: install init-database
@NODE_ENV=test DB=${DB} 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
@NODE_ENV=test node --harmony \
test-sqlite:
@$(MAKE) test DB=sqlite
test-mysql: init-mysql
@$(MAKE) test DB=mysql
test-all: test-sqlite test-mysql
test-cov cov: install init-database
@NODE_ENV=test DB=${DB} node --harmony \
node_modules/.bin/istanbul cover --preserve-comments \
./node_modules/.bin/_mocha \
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)
@./node_modules/.bin/cov coverage
test-cov-sqlite:
@$(MAKE) test-cov DB=sqlite
test-cov-mysql: init-mysql
@$(MAKE) test-cov DB=mysql
test-travis: install init-database
@NODE_ENV=test DB=${DB} 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)
test-travis-sqlite:
@$(MAKE) test-travis DB=sqlite
test-travis-mysql: init-mysql
@$(MAKE) test-travis DB=mysql
test-travis-all: test-travis-sqlite test-travis-mysql
dev:
@NODE_ENV=development node_modules/.bin/node-dev --harmony dispatch.js
contributors: install
@./node_modules/.bin/contributors -f plain -o AUTHORS
@node_modules/.bin/contributors -f plain -o AUTHORS
autod: install
@./node_modules/.bin/autod -w -e public,view,docs,backup,coverage
@node_modules/.bin/autod -w \
--prefix "~" \
--exclude public,view,docs,backup,coverage \
--dep bluebird,mysql \
--keep should,supertest,should-http,chunkstream,mm,pedding
@$(MAKE) install
.PHONY: test

View File

@@ -1,9 +1,31 @@
cnpmjs.org
=======
[![Build Status](https://secure.travis-ci.org/cnpm/cnpmjs.org.png)](http://travis-ci.org/cnpm/cnpmjs.org) [![Dependency Status](https://gemnasium.com/cnpm/cnpmjs.org.png)](https://gemnasium.com/cnpm/cnpmjs.org)
[![NPM version][npm-image]][npm-url]
[![build status][travis-image]][travis-url]
[![Test coverage][coveralls-image]][coveralls-url]
[![Gittip][gittip-image]][gittip-url]
[![David deps][david-image]][david-url]
[![node version][node-image]][node-url]
[![npm download][download-image]][download-url]
[![gitter][gitter-image]][gitter-url]
[![NPM](https://nodei.co/npm/cnpmjs.org.png?downloads=true&stars=true)](https://nodei.co/npm/cnpmjs.org/)
[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
[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
[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)
@@ -22,9 +44,11 @@ Our goal is to provide a low cost maintenance and easy to use solution for priva
### Features
* **Support "scoped" packages**: [npm/npm#5239](https://github.com/npm/npm/issues/5239)
* **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, tarball(tgz file) store in CDN or other store systems.
* **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.
* **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.
@@ -34,6 +58,10 @@ to extend `npm` with more features(`sync` command, [gzip](https://github.com/npm
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)
**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
@@ -42,22 +70,18 @@ only need to change the registry in config. Even include manual synchronization
* 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)
* [NFS guide](https://github.com/cnpm/cnpmjs.org/wiki/NFS-Guide)
![cnpm](https://docs.google.com/drawings/d/12QeQfGalqjsB77mRnf5Iq5oSXHCIUTvZTwECMonqCmw/pub?w=480&h=360)
* [wiki](https://github.com/cnpm/cnpmjs.org/wiki)
## Develop on your local machine
### Dependencies
* [node](http://nodejs.org) >=0.11.9
* [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 &
```
* [node](http://nodejs.org) >=0.11.12, use `--harmony`
* 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
### Clone codes and run test
@@ -77,8 +101,8 @@ $ make test-cov
# udpate dependencies
$ make autod
# start server
$ node --harmony dispatch.js
# start server with development mode
$ make dev
```
## How to contribute
@@ -90,24 +114,6 @@ $ node --harmony dispatch.js
Tips: make sure your code is following the [node-style-guide](https://github.com/felixge/node-style-guide).
## Authors
```bash
$ git summary
project : cnpmjs.org
repo age : 4 months ago
commits : 472
active : 167 days
files : 104
authors :
272 fengmk2 57.6%
195 dead_horse 41.3%
2 4simple 0.4%
2 Stanley Zheng 0.4%
1 Alsotang 0.2%
```
## License
(The MIT License)

View File

@@ -7,7 +7,7 @@ export NODE_ENV='production'
ulimit -c unlimited
cd `dirname $0`/..
NODEJS='node --harmony-generators'
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'));\

View File

@@ -15,73 +15,48 @@
* Module dependencies.
*/
var formater = require('error-formater');
var Logger = require('mini-logger');
var utility = require('utility');
var util = require('util');
var moment = require('moment');
var logstream = require('logfilestream');
var ms = require('ms');
var config = require('../config');
var mail = require('./mail');
var isTEST = process.env.NODE_ENV === 'test';
var ONE_DAY = ms('1d');
var levels = ['info', 'warn', 'error'];
var categories = ['sync_info', 'sync_error'];
levels.forEach(function (catetory) {
var options = {
logdir: config.logdir,
duration: ONE_DAY,
nameformat: '[' + catetory + '.]YYYY-MM-DD[.log]'
};
var stream = logstream(options);
function write(msg) {
var time = moment().format('YYYY-MM-DD HH:mm:ss.SSS');
var subject = null;
if (msg instanceof Error) {
subject = msg.name;
var err = {
name: msg.name,
code: msg.code,
message: msg.message,
stack: msg.stack,
host: msg.host,
url: msg.url,
data: msg.data
};
if (err.name === 'Error' && typeof err.code === 'string') {
err.name = err.code + err.name;
}
err.name += 'Exception';
if (err.host) {
err.message += ' (' + err.host + ')';
}
msg = util.format('%s nodejs.%s: %s\nURL: %s\nData: %j\n%s\n\n',
time,
err.name,
err.stack,
err.url,
err.data,
time
);
} else {
msg = time + ' ' + util.format.apply(util, arguments) + '\n';
}
if (!isTEST) {
stream.write(msg);
if (config.debug) {
var level = catetory;
console.log('[' + level + '] ' + msg);
} else {
if (catetory === 'error' && subject) {
// send error email
var to = [];
for (var name in config.admins) {
to.push(config.admins[name]);
}
mail.error(to, subject, msg);
}
}
}
}
exports[catetory] = write;
var logger = module.exports = Logger({
categories: categories,
dir: config.logdir,
duration: '1d',
format: '[{category}.]YYYY-MM-DD[.log]',
stdout: config.debug && !isTEST,
errorFormater: errorFormater
});
var to = [];
for (var user in config.admins) {
to.push(config.admins[user]);
}
function errorFormater(err) {
var msg = formater.both(err);
mail.error(to, msg.json.name, msg.text);
return msg.text;
}
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];
}
logger.sync_info.apply(logger, args);
};
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];
}
logger.sync_error.apply(logger, arguments);
};

View File

@@ -14,10 +14,30 @@
* Module dependencies.
*/
var utility = require('utility');
var nodemailer = require('nodemailer');
var os = require('os');
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 = {
// backward compat
host: mailConfig.host,
port: mailConfig.port,
secure: mailConfig.secure || mailConfig.ssl,
debug: mailConfig.debug,
auth: {
user: mailConfig.user,
pass: mailConfig.pass
}
};
}
var transport = nodemailer.createTransport(smtpConfig);
/**
* Send notice email with mail level and appname.
@@ -51,26 +71,14 @@ LEVELS.forEach(function (level) {
exports.send = function (to, subject, html, callback) {
callback = callback || utility.noop;
var transport = nodemailer.createTransport("SMTP", {
host: mailConfig.host,
port: mailConfig.port,
secureConnection: mailConfig.ssl,
debug: mailConfig.debug,
auth: {
user: mailConfig.user,
pass: mailConfig.pass,
}
});
var message = {
sender: mailConfig.sender,
from: mailConfig.from || mailConfig.sender,
to: to,
subject: subject,
html: html,
};
transport.sendMail(message, function (err, result) {
transport.close();
callback(err, result);
});
};

View File

@@ -1,80 +0,0 @@
/*!
* 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

@@ -15,11 +15,5 @@
*/
var config = require('../config');
var nfs = config.nfs;
if (!nfs) {
// use qnfs by default
nfs = require('./qnfs');
}
module.exports = nfs;
module.exports = config.nfs;

View File

@@ -1,74 +0,0 @@
/*!
* cnpmjs.org - common/qnfs.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 qn = require('qn');
var config = require('../config');
var client = qn.create(config.qn);
thunkify(client, ['delete', 'uploadFile', 'upload']);
exports._client = client;
/**
* Upload file
*
* @param {String} filepath
* @param {Object} options
* - {String} key
* - {Number} size
*/
exports.upload = function *(filepath, options) {
try {
yield client.delete(options.key);
} catch (err) {
// ignore error here
}
var res = yield client.uploadFile(filepath, {
key: options.key,
size: options.size
});
var url = res && res[0] ? res[0].url : '';
return { url: url };
};
exports.uploadBuffer = function *(buf, options) {
try {
yield client.delete(options.key);
} catch (err) {
// ignore error here
}
var res = yield client.upload(buf, {key: options.key});
var url = res && res[0] ? res[0].url : '';
return { url: url };
};
exports.url = function (key) {
return client.resourceURL(key);
};
exports.remove = function *(key) {
try {
return yield client.delete(key);
} catch (err) {
if (err.name === 'QiniuFileNotExistsError') {
return;
}
throw err;
}
};

View File

@@ -17,13 +17,12 @@
var config = require('../config');
// close redis by set config.redis to `null` or `{}`
if (!config.redis || !config.redis.host || !config.redis.port) {
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);
var _client = redis.createClient(config.redis.port, config.redis.host);
_client.on('error', function (err) {
logger.error(err);

75
common/sequelize.js Normal file
View File

@@ -0,0 +1,75 @@
/**!
* 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.
*/
var Sequelize = require('sequelize');
var DataTypes = require('sequelize/lib/data-types');
var 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');
var 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,
};
}
var 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',
};
var sequelize = new Sequelize(database.db, database.username, database.password, database);
module.exports = sequelize;

View File

@@ -1,34 +0,0 @@
/*!
* cnpmjs.org - common/session.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';
/**
* Module dependencies.
*/
var middlewares = require('koa-middlewares');
var config = require('../config');
var key = 'AuthSession';
var cookie = { path: '/', httpOnly: true, maxAge: 3600000 * 24 * 365, signed: false };
var options = {
key: key,
cookie: cookie,
defer: true,
rolling: false
};
if (!config.debug) {
options.store = config.sessionStore || middlewares.RedisStore(config.redis);
}
module.exports = middlewares.session(options);

35
common/urllib.js Normal file
View File

@@ -0,0 +1,35 @@
/**!
* cnpmjs.org - common/urllib.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var urllib = require('urllib');
var HttpAgent = require('agentkeepalive');
var HttpsAgent = require('agentkeepalive').HttpsAgent;
var httpAgent = new HttpAgent({
timeout: 0,
keepAliveTimeout: 15000
});
var httpsAgent = new HttpsAgent({
timeout: 0,
keepAliveTimeout: 15000
});
var client = urllib.create({
agent: httpAgent,
httpsAgent: httpsAgent
});
module.exports = client;
module.exports.USER_AGENT = urllib.USER_AGENT;

View File

@@ -15,86 +15,49 @@
* Module dependencies.
*/
var mkdirp = require('mkdirp');
var copy = require('copy-to');
var path = require('path');
var fs = require('fs');
var os = require('os');
var mkdirp = require('mkdirp');
var copy = require('copy-to');
fs.existsSync = fs.existsSync || path.existsSync;
var version = require('../package.json').version;
var root = path.dirname(__dirname);
var config = {
version: version,
registryPort: 7001,
webPort: 7002,
/**
* Cluster mode
*/
enableCluster: false,
numCPUs: os.cpus().length,
debug: true, // if debug
logdir: path.join(root, '.tmp', 'logs'),
viewCache: false,
// mysql config
mysqlServers: [
{
host: '127.0.0.1',
port: 3306,
user: 'root',
password: ''
}
],
mysqlDatabase: 'cnpmjs_test',
mysqlMaxConnections: 4,
mysqlQueryTimeout: 5000,
/*
* server configure
*/
registryPort: 7001,
webPort: 7002,
bindingHost: '127.0.0.1', // only binding on 127.0.0.1 for local access
// debug mode
// if in debug mode, some middleware like limit wont load
// logger module will print to stdout
debug: true,
// session secret
sessionSecret: 'cnpmjs.org test session secret',
redis: {
host: 'pub-redis-19533.us-east-1-4.3.ec2.garantiadata.com',
port: 19533,
pass: 'cnpmjs_dev'
},
jsonLimit: '10mb', // max request json body size
uploadDir: path.join(root, 'public', 'dist'),
// qiniu cdn: http://www.qiniu.com/, it free for dev.
qn: {
accessKey: "iN7NgwM31j4-BZacMjPrOQBs34UG1maYCAQmhdCV",
secretKey: "6QTOr2Jg1gcZEWDQXKOGZh5PziC2MCV5KsntT70j",
bucket: "qtestbucket",
domain: "http://qtestbucket.qiniudn.com"
},
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
},
disturl: 'http://dist.u.qiniudn.com',
logoURL: 'http://ww4.sinaimg.cn/large/69c1d4acgw1ebfly5kjlij208202oglr.jpg',
registryHost: 'r.cnpmjs.org',
customFooter: '', // you can add copyright and site total script html here
npmClientName: 'cnpm', // use `${name} install package`
packagePageContributorSearch: true, // package page contributor link to search, default is true
sourceNpmRegistry: 'http://registry.npmjs.org',
enablePrivate: true, // enable private mode, only admin can publish, other use just can sync package from source npm
admins: {
fengmk2: 'fengmk2@gmail.com',
admin: 'admin@cnpmjs.org',
dead_horse: 'dead_horse@qq.com',
cnpmjstest10: 'cnpmjstest10@cnpmjs.org',
},
syncByInstall: true,
backupFilePrefix: '/cnpm/backup/', // backup filepath prefix
syncModel: 'none', // 'none', 'all', 'exist'
syncConcurrency: 1,
maxDependencies: 200, // max handle number of package.json `dependencies` property
// max request json body size
jsonLimit: '10mb',
// log dir name
logdir: path.join(root, '.tmp/logs'),
// update file template dir
uploadDir: path.join(root, '.dist'),
// web page viewCache
viewCache: false,
// config for koa-limit middleware
// for limit download rates
limit: {
enable: false,
token: 'koa-limit:download',
@@ -103,7 +66,175 @@ var config = {
whiteList: [],
blackList: [],
message: 'request frequency limited, any question, please contact fengmk2@gmail.com',
}
},
enableCompress: false, // enable gzip response or not
// default system admins
admins: {
// name: email
fengmk2: 'fengmk2@gmail.com',
admin: 'admin@cnpmjs.org',
dead_horse: 'dead_horse@qq.com',
},
// email notification for errors
// check https://github.com/andris9/Nodemailer for more informations
mail: {
appname: 'cnpmjs.org',
from: 'cnpmjs.org mail sender <adderss@gmail.com>',
service: 'gmail',
auth: {
user: 'address@gmail.com',
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: '//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`
packagePageContributorSearch: true, // package page contributor link to search, default is true
// max handle number of package.json `dependencies` property
maxDependencies: 200,
// backup filepath prefix
backupFilePrefix: '/cnpm/backup/',
/**
* 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,
},
// 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(root, '.tmp', 'dist')
}),
// registry url name
registryHost: 'r.cnpmjs.org',
/**
* registry mode config
*/
// 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'
],
// 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: [],
/**
* sync configs
*/
// sync dist config
// sync node.js dist from nodejs.org
noticeSyncDistError: true,
disturl: 'http://nodejs.org/dist',
syncDist: false,
pythonDisturl: 'https://www.python.org/ftp',
syncPythonDist: 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.org',
// sync source, upstream registry
// If you want to directly sync from official npm's registry
// please drop them an email first
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
sourceNpmRegistryIsCNpm: true,
// if install return 404, try to sync from source registry
syncByInstall: true,
// sync mode select
// none: do not sync any module
// exist: only sync exist modules
// all: sync all modules
syncModel: 'none', // 'none', 'all', 'exist'
syncConcurrency: 1,
// sync interval, default is 10 minutes
syncInterval: '10m',
// sync polular modules, default to false
// because cnpm can't auto sync tag change for now
// so we want to sync popular modules to ensure their tags
syncPopular: false,
syncPopularInterval: '1h',
// top 100
topPopular: 100,
// sync devDependencies or not, default is false
syncDevDependencies: false,
// badge subject on http://shields.io/
badgeSubject: 'cnpm',
};
// load config/config.js, everything in config.js will cover the same key in index.js

View File

@@ -1,83 +0,0 @@
/**!
* 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('months', 1).startOf('month');
var lastday = end.clone().subtract('days', 1).format('YYYY-MM-DD');
var lastweekStart = end.clone().subtract('weeks', 1).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,940 +0,0 @@
/**!
* cnpmjs.org - controllers/registry/module.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:module');
var path = require('path');
var fs = require('fs');
var crypto = require('crypto');
var utility = require('utility');
var coRead = require('co-read');
var coWrite = require('co-write');
var urlparse = require('url').parse;
var mime = require('mime');
var semver = require('semver');
var ms = require('ms');
var config = require('../../config');
var Module = require('../../proxy/module');
var Total = require('../../proxy/total');
var nfs = require('../../common/nfs');
var common = require('../../lib/common');
var DownloadTotal = require('../../proxy/download');
var SyncModuleWorker = require('../../proxy/sync_module_worker');
var logger = require('../../common/logger');
var ModuleDeps = require('../../proxy/module_deps');
var ModuleStar = require('../../proxy/module_star');
/**
* show all version of a module
*/
exports.show = function *(next) {
var name = this.params.name;
var modifiedTime = yield *Module.getLastModified(name);
debug('show %s, last modified: %s', name, modifiedTime);
if (modifiedTime) {
// use modifiedTime as etag
this.set('ETag', '"' + modifiedTime.getTime() + '"');
// must set status first
this.status = 200;
if (this.fresh) {
debug('%s not change at %s, 304 return', name, modifiedTime);
this.status = 304;
return;
}
}
var r = yield [
Module.listTags(name),
Module.listByName(name),
ModuleStar.listUsers(name)
];
var tags = r[0];
var rows = r[1];
var users = r[2];
var userMap = {};
for (var i = 0; i < users.length; i++) {
userMap[users[i]] = true;
}
users = userMap;
// 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;
}
var result = yield SyncModuleWorker.sync(name, 'sync-by-install');
this.status = result.ok ? 200 : result.statusCode;
this.body = result.pkg;
return;
}
var nextMod = null;
var latestMod = null;
var readme = null;
// set tags
var distTags = {};
for (var i = 0; i < tags.length; i++) {
var t = tags[i];
distTags[t.tag] = t.version;
}
// set versions and times
var versions = {};
var times = {};
var attachments = {};
var createdTime = null;
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
if (row.version === 'next') {
nextMod = row;
continue;
}
var pkg = row.package;
common.setDownloadURL(pkg, this);
pkg._cnpm_publish_time = row.publish_time;
versions[pkg.version] = pkg;
var t = times[pkg.version] = row.publish_time ? new Date(row.publish_time) : row.gmt_modified;
if ((!distTags.latest && !latestMod) || distTags.latest === row.version) {
latestMod = row;
readme = pkg.readme;
}
delete pkg.readme;
if (!createdTime || t < createdTime) {
createdTime = t;
}
}
if (modifiedTime && createdTime) {
var ts = {
modified: modifiedTime,
created: createdTime,
};
for (var t in times) {
ts[t] = times[t];
}
times = ts;
}
if (!latestMod) {
latestMod = nextMod || rows[0];
}
if (!nextMod) {
nextMod = latestMod;
}
var rev = '';
if (nextMod) {
rev = String(nextMod.id);
}
var pkg = latestMod.package;
var info = {
_id: name,
_rev: rev,
name: name,
description: pkg.description,
"dist-tags": distTags,
maintainers: pkg.maintainers,
time: times,
users: users,
author: pkg.author,
repository: pkg.repository,
versions: versions,
readme: 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', name, rev, latestMod.version);
this.body = info;
};
/**
* get the special version or tag of a module
*/
exports.get = function *(next) {
var name = this.params.name;
var tag = this.params.version;
var version = semver.valid(tag);
var method = version ? 'get' : 'getByTag';
var queryLabel = version ? version : tag;
var mod = yield Module[method](name, queryLabel);
if (mod) {
common.setDownloadURL(mod.package, this);
mod.package._cnpm_publish_time = mod.publish_time;
this.body = mod.package;
return;
}
// if not fond, sync from source registry
if (!this.allowSync) {
this.status = 404;
this.body = {
error: 'not exist',
reason: 'version not found: ' + version
};
return;
}
var result = yield SyncModuleWorker.sync(name, 'sync-by-install');
var pkg = result.pkg && result.pkg.versions[version];
if (!pkg) {
this.status = 404;
this.body = {
error: 'not exist',
reason: 'version not found: ' + version
};
return;
}
this.body = pkg;
};
var _downloads = {};
var DOWNLOAD_TIMEOUT = ms('10m');
exports.download = function *(next) {
var name = this.params.name;
var filename = this.params.filename;
var version = filename.slice(name.length + 1, -4);
var row = yield Module.get(name, version);
// can not get dist
var url = null;
if (typeof nfs.url === 'function') {
url = nfs.url(common.getCDNKey(name, filename));
}
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;
}
var dist = row.package.dist;
if (!dist.key) {
debug('get tarball by 302');
this.status = 302;
this.set('Location', dist.tarball || url);
_downloads[name] = (_downloads[name] || 0) + 1;
return;
}
// else use `dist.key` to get tarball from nfs
if (!nfs.download) {
return yield* next;
}
_downloads[name] = (_downloads[name] || 0) + 1;
if (typeof dist.size === 'number') {
this.set('Content-Length', dist.size);
}
this.set('Content-Type', mime.lookup(dist.key));
this.set('Content-Disposition', 'attachment; filename="' + filename + '"');
this.set('ETag', dist.shasum);
// use download file api
var tmpPath = path.join(config.uploadDir,
utility.randomString() + dist.key.replace(/\//g, '-'));
function cleanup() {
fs.unlink(tmpPath, utility.noop);
}
try {
yield nfs.download(dist.key, tmpPath, {timeout: DOWNLOAD_TIMEOUT});
} catch (err) {
cleanup();
this.throw(err);
}
var tarball = fs.createReadStream(tmpPath);
tarball.once('error', cleanup);
tarball.once('end', cleanup);
this.body = tarball;
};
setInterval(function () {
// save download count
var totals = [];
for (var name in _downloads) {
var count = _downloads[name];
totals.push([name, count]);
}
_downloads = {};
if (totals.length === 0) {
return;
}
debug('save download total: %j', totals);
var date = utility.YYYYMMDD();
var next = function () {
var item = totals.shift();
if (!item) {
// done
return;
}
DownloadTotal.plusTotal({name: item[0], date: date, count: item[1]}, function (err) {
if (!err) {
return next();
}
logger.error(err);
debug('save download %j error: %s', item, err);
totals.push(item);
// save to _downloads
for (var i = 0; i < totals.length; i++) {
var v = totals[i];
var name = v[0];
_downloads[name] = (_downloads[name] || 0) + v[1];
}
// end
});
};
next();
}, 5000);
exports.upload = function *(next) {
var length = Number(this.get('content-length')) || 0;
if (!length || !this.is('application/octet-stream')) {
debug('request length or type error');
return yield *next;
}
var username = this.user.name;
var name = this.params.name;
var id = Number(this.params.rev);
var filename = this.params.filename;
var version = filename.substring(name.length + 1);
version = version.replace(/\.tgz$/, '');
// save version on pkg upload
debug('%s: upload %s, file size: %d', username, this.url, length);
var mod = yield Module.getById(id);
if (!mod) {
debug('can not get this module');
return yield* next;
}
if (!common.isMaintainer(this.user, mod.package.maintainers) || mod.name !== name) {
this.status = 403;
this.body = {
error: 'no_perms',
reason: 'Current user can not publish this module'
};
return;
}
if (mod.version !== 'next') {
// rev wrong
this.status = 403;
this.body = {
error: 'rev_wrong',
reason: 'rev not match next module'
};
return;
}
var filepath = common.getTarballFilepath(filename);
var ws = fs.createWriteStream(filepath);
var shasum = crypto.createHash('sha1');
var dataSize = 0;
var buf;
while (buf = yield coRead(this.req)) {
shasum.update(buf);
dataSize += buf.length;
yield coWrite(ws, buf);
}
ws.end();
if (dataSize !== length) {
this.status = 403;
this.body = {
error: 'size_wrong',
reason: 'Header size ' + length + ' not match download size ' + dataSize,
};
return;
}
shasum = shasum.digest('hex');
var options = {
key: common.getCDNKey(name, filename),
size: length,
shasum: shasum
};
var result;
try {
result = yield nfs.upload(filepath, options);
} catch (err) {
fs.unlink(filepath, utility.noop);
this.throw(err);
}
fs.unlink(filepath, utility.noop);
var dist = {
shasum: shasum,
size: length
};
// if nfs upload return a key, record it
if (result.url) {
dist.tarball = result.url;
} else if (result.key) {
dist.key = result.key;
dist.tarball = result.key;
}
mod.package.dist = dist;
mod.package.version = version;
debug('%s module: save file to %s, size: %d, sha1: %s, dist: %j, version: %s',
id, filepath, length, shasum, dist, version);
var updateResult = yield Module.update(mod);
this.status = 201;
this.body = {
ok: true,
rev: String(updateResult.id)
};
};
function _addDepsRelations(pkg) {
var dependencies = Object.keys(pkg.dependencies || {});
if (dependencies.length > config.maxDependencies) {
dependencies = dependencies.slice(0, config.maxDependencies);
}
// add deps relations
dependencies.forEach(function (depName) {
ModuleDeps.add(depName, pkg.name, utility.noop);
});
}
exports.updateLatest = function *(next) {
var username = this.user.name;
var name = this.params.name;
var version = semver.valid(this.params.version);
if (!version) {
this.status = 400;
this.body = {
error: 'Params Invalid',
reason: 'Invalid version: ' + this.params.version,
};
return;
}
var nextMod = yield Module.get(name, 'next');
if (!nextMod) {
debug('can not get nextMod');
return yield* next;
}
if (!common.isMaintainer(this.user, nextMod.package.maintainers)) {
this.status = 401;
this.body = {
error: 'noperms',
reason: 'Current user can not publish this module'
};
return;
}
// check version if not match pkg upload
if (nextMod.package.version !== version) {
this.status = 403;
this.body = {
error: 'version_wrong',
reason: 'version not match'
};
return;
}
var body = this.request.body;
nextMod.version = version;
nextMod.author = username;
body.dist = nextMod.package.dist;
body.maintainers = nextMod.package.maintainers;
if (!body.author) {
body.author = {
name: username,
};
}
body._publish_on_cnpm = true;
nextMod.package = body;
_addDepsRelations(body);
// reset publish time
nextMod.publish_time = Date.now();
debug('update %s:%s %j', nextMod.package.name, nextMod.package.version, nextMod.package.dist);
// change latest to version
try {
yield Module.update(nextMod);
} catch (err) {
debug('update nextMod %s error: %s', name, err);
return this.throw(err);
}
yield Module.addTag(name, 'latest', version);
nextMod.version = 'next';
var addResult = yield Module.add(nextMod);
this.status = 201;
this.body = {
ok: true,
rev: String(addResult.id)
};
};
exports.addPackageAndDist = function *(next) {
// 'dist-tags': { latest: '0.0.2' },
// _attachments:
// { 'nae-sandbox-0.0.2.tgz':
// { content_type: 'application/octet-stream',
// data: 'H4sIAAAAA
// length: 9883
var pkg = this.request.body;
var username = this.user.name;
var name = this.params.name;
var filename = Object.keys(pkg._attachments || {})[0];
var version = Object.keys(pkg.versions || {})[0];
if (!version || !filename) {
this.status = 400;
this.body = {
error: 'version_error',
reason: 'filename or version not found, filename: ' + filename + ', version: ' + version
};
return;
}
var attachment = pkg._attachments[filename];
var versionPackage = pkg.versions[version];
versionPackage._publish_on_cnpm = true;
var distTags = pkg['dist-tags'] || {};
var tags = []; // tag, version
for (var t in distTags) {
tags.push([t, distTags[t]]);
}
debug('addPackageAndDist %s:%s, attachment size: %s', name, version, attachment.length);
var exists = yield Module.get(name, version);
var shasum;
if (exists) {
this.status = 409;
this.body = {
error: 'conflict',
reason: 'Document update conflict.'
};
return;
}
// upload attachment
var tarballBuffer;
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;
}
shasum = crypto.createHash('sha1');
shasum.update(tarballBuffer);
shasum = shasum.digest('hex');
var options = {
key: common.getCDNKey(name, filename),
shasum: shasum
};
var uploadResult = yield nfs.uploadBuffer(tarballBuffer, options);
debug('upload %j', uploadResult);
var dist = {
shasum: 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;
}
var mod = {
name: name,
version: version,
author: username,
package: versionPackage
};
mod.package.dist = dist;
_addDepsRelations(mod.package);
var addResult = yield Module.add(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) {
return Module.addTag(name, tag[0], tag[1]);
});
}
this.status = 201;
this.body = {
ok: true,
rev: String(addResult.id)
};
};
exports.add = function *(next) {
var username = this.user.name;
var name = this.params.name;
var pkg = this.request.body || {};
if (!common.isMaintainer(this.user, pkg.maintainers)) {
this.status = 403;
this.body = {
error: 'no_perms',
reason: 'Current user can not publish this module'
};
return;
}
if (pkg._attachments && Object.keys(pkg._attachments).length > 0) {
return yield exports.addPackageAndDist.call(this, next);
}
var r = yield [Module.getLatest(name), Module.get(name, 'next')];
var latestMod = r[0];
var nextMod = r[1];
if (nextMod) {
nextMod.exists = true;
} else {
nextMod = {
name: name,
version: 'next',
author: username,
package: {
name: name,
version: 'next',
description: pkg.description,
readme: pkg.readme,
maintainers: pkg.maintainers,
}
};
debug('add next module: %s', name);
var result = yield Module.add(nextMod);
nextMod.id = result.id;
}
var maintainers = latestMod && latestMod.package.maintainers.length > 0 ?
latestMod.package.maintainers : nextMod.package.maintainers;
if (!common.isMaintainer(this.user, maintainers)) {
this.status = 403;
this.body = {
error: 'no_perms',
reason: 'Current user can not publish this module'
};
return;
}
debug('add %s rev: %s, version: %s', name, nextMod.id, nextMod.version);
if (latestMod || nextMod.version !== 'next') {
this.status = 409;
this.body = {
error: 'conflict',
reason: 'Document update conflict.'
};
return;
}
this.status = 201;
this.body = {
ok: true,
id: name,
rev: String(nextMod.id),
};
};
exports.updateOrRemove = function *(next) {
debug('updateOrRemove module %s, %j', this.params.name, this.request.body);
var body = this.request.body;
if (body.versions) {
yield *exports.removeWithVersions.call(this, next);
} else if (body.maintainers && body.maintainers.length > 0) {
yield *exports.updateMaintainers.call(this, next);
} else {
yield *next;
}
};
exports.updateMaintainers = function *(next) {
var name = this.params.name;
var body = this.request.body;
debug('updateMaintainers module %s, %j', name, body);
var latestMod = yield Module.getLatest(name);
if (!latestMod || !latestMod.package) {
return yield *next;
}
if (!common.isMaintainer(this.user, latestMod.package.maintainers)) {
this.status = 403;
this.body = {
error: 'no_perms',
reason: 'Current user can not publish this module'
};
return;
}
var r = yield *Module.updateMaintainers(latestMod.id, body.maintainers);
debug('result: %j', r);
this.status = 201;
this.body = {
ok: true,
id: name,
rev: String(latestMod.id),
};
};
exports.removeWithVersions = function *(next) {
debug('removeWithVersions module %s, with info %j', this.params.name, this.request.body);
var username = this.user.name;
var name = this.params.name;
var versions = this.request.body.versions || {};
debug('removeWithVersions module %s, with versions %j', name, Object.keys(versions));
// step1: list all the versions
var mods = yield Module.listByName(name);
if (!mods || !mods.length) {
return yield *next;
}
// step2: check permission
var firstMod = mods[0];
if (!common.isMaintainer(this.user, firstMod.package.maintainers) || firstMod.name !== name) {
this.status = 403;
this.body = {
error: 'no_perms',
reason: 'Current user can not update this module'
};
return;
}
// step3: calculate which versions need to remove and
// which versions need to remain
var removeVersions = [];
var removeVersionMaps = {};
var remainVersions = [];
for (var i = 0; i < mods.length; i++) {
var v = mods[i].version;
if (v === 'next') {
continue;
}
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
yield Module.removeByNameAndVersions(name, removeVersions);
var tags = yield Module.listTags(name);
var removeTags = [];
var 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;
}
}
});
if (removeTags.length) {
debug('remove tags: %j', removeTags);
// step 5: remove all the tags
yield Module.removeTagsByIds(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 Module.addTag(name, 'latest', remainVersions[0]);
}
} else {
debug('no tag need to be remove');
}
this.status = 201;
this.body = { ok: true };
};
exports.removeTar = function *(next) {
debug('remove tarball with filename: %s, id: %s', this.params.filename, this.params.rev);
var id = Number(this.params.rev);
var filename = this.params.filename;
var name = this.params.name;
var username = this.user.name;
var mod = yield Module.getById(id);
if (!mod) {
return yield* next;
}
if (!common.isMaintainer(this.user, mod.package.maintainers) || mod.name !== name) {
this.status = 403;
this.body = {
error: 'no_perms',
reason: 'Current user can not delete this tarball'
};
return;
}
var key = mod.package.dist && mod.package.dist.key;
key = key || common.getCDNKey(mod.name, filename);
yield nfs.remove(key);
this.body = { ok: true };
};
exports.removeAll = function *(next) {
debug('remove all the module with name: %s, id: %s', this.params.name, this.params.rev);
// var id = Number(this.params.rev);
var name = this.params.name;
var mods = yield Module.listByName(name);
debug('removeAll module %s: %d', name, mods.length);
var mod = mods[0];
if (!mod) {
return yield* next;
}
if (!common.isMaintainer(this.user, mod.package.maintainers) || mod.name !== name) {
this.status = 403;
this.body = {
error: 'no_perms',
reason: 'Current user can not delete this tarball'
};
return;
}
Total.plusDeleteModule(utility.noop);
yield [Module.removeByName(name), Module.removeTags(name)];
var keys = [];
for (var i = 0; i < mods.length; i++) {
var key = urlparse(mods[i].dist_tarball).path;
key && keys.push(key);
}
try {
yield keys.map(function (key) {
return nfs.remove(key);
});
} catch (err) {
// ignore error here
}
this.body = { ok: true };
};
function parseModsForList(updated, mods, ctx) {
var results = {
_updated: updated
};
for (var i = 0; i < mods.length; i++) {
var mod = mods[i];
var pkg = {};
try {
pkg = JSON.parse(mod.package);
} catch (e) {
//ignore this pkg
continue;
}
pkg['dist-tags'] = {
latest: pkg.version
};
common.setDownloadURL(pkg, ctx);
results[mod.name] = pkg;
}
return results;
}
exports.listAllModules = function *() {
var updated = Date.now();
var mods = yield Module.listAllNames();
var result = { _updated: updated };
mods.forEach(function (mod) {
result[mod.name] = true;
});
this.body = result;
};
exports.listAllModulesSince = function *() {
var query = this.query || {};
if (query.stale !== 'update_after') {
this.status = 400;
this.body = {
error: 'query_parse_error',
reason: 'Invalid value for `stale`.'
};
return;
}
debug('list all modules from %s', query.startkey);
var startkey = Number(query.startkey) || 0;
var updated = Date.now();
var mods = yield Module.listSince(startkey);
var result = { _updated: updated };
mods.forEach(function (mod) {
result[mod.name] = true;
});
this.body = result;
};
exports.listAllModuleNames = function *() {
this.body = (yield Module.listShort()).map(function (m) {
return m.name;
});
};

View File

@@ -0,0 +1,60 @@
/**!
* cnpmjs.org - controllers/registry/package/deprecate.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var packageService = require('../../../services/package');
module.exports = deprecateVersions;
/**
* @see https://github.com/cnpm/cnpmjs.org/issues/415
*/
function* deprecateVersions() {
var body = this.request.body;
var name = this.params.name || this.params[0];
var tasks = [];
for (var version in body.versions) {
tasks.push(packageService.getModule(name, version));
}
var rs = yield tasks;
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'
};
return;
}
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));
}
}
yield updateTasks;
// update last modified
yield* packageService.updateModuleLastModified(name);
this.status = 201;
this.body = {
ok: true
};
}

View File

@@ -0,0 +1,109 @@
/**!
* cnpmjs.org - controllers/registry/package/download.js
*
* Copyright(c) fengmk2 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:registry:download');
var mime = require('mime');
var utility = require('utility');
var defer = require('co-defer');
var nfs = require('../../../common/nfs');
var logger = require('../../../common/logger');
var common = require('../../../lib/common');
var downloadAsReadStream = require('../../utils').downloadAsReadStream;
var packageService = require('../../../services/package');
var downloadTotalService = require('../../../services/download_total');
var _downloads = {};
module.exports = function* download(next) {
var name = this.params.name || this.params[0];
var filename = this.params.filename || this.params[1];
var version = filename.slice(name.length + 1, -4);
var row = yield* packageService.getModule(name, version);
// can not get dist
var url = null;
if (typeof nfs.url === 'function') {
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;
}
var dist = row.package.dist;
if (!dist.key) {
debug('get tarball by 302, url: %s', dist.tarball || url);
this.status = 302;
this.set('Location', dist.tarball || url);
_downloads[name] = (_downloads[name] || 0) + 1;
return;
}
// else use `dist.key` to get tarball from nfs
if (!nfs.download) {
return yield* next;
}
_downloads[name] = (_downloads[name] || 0) + 1;
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
var totals = [];
for (var name in _downloads) {
var count = _downloads[name];
totals.push([name, count]);
}
_downloads = {};
if (totals.length === 0) {
return;
}
debug('save download total: %j', totals);
var date = utility.YYYYMMDD();
for (var i = 0; i < totals.length; i++) {
var item = totals[i];
var name = item[0];
var count = item[1];
try {
yield* downloadTotalService.plusModuleTotal({ name: name, date: date, count: count });
} catch (err) {
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);

View File

@@ -0,0 +1,217 @@
/**!
* cnpmjs.org - controllers/registry/package/list.js
*
* Copyright(c) fengmk2 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:registry:package:list');
var packageService = require('../../../services/package');
var npmService = require('../../../services/npm');
var config = require('../../../config');
var setDownloadURL = require('../../../lib/common').setDownloadURL;
var SyncModuleWorker = require('../../sync_module_worker');
/**
* list all version of a module
* GET /:name
*/
module.exports = function* list() {
var orginalName = this.params.name || this.params[0];
var name = orginalName;
var rs = yield [
packageService.getModuleLastModified(name),
packageService.listModuleTags(name)
];
var modifiedTime = rs[0];
var 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 (var i = 0; i < tags.length; i++) {
var tag = tags[i];
if (tag.gmt_modified > modifiedTime) {
modifiedTime = tag.gmt_modified;
}
}
// use modifiedTime as etag
this.set('ETag', '"' + modifiedTime.getTime() + '"');
// must set status first
this.status = 200;
if (this.fresh) {
debug('%s not change at %s, 304 return', name, modifiedTime);
this.status = 304;
return;
}
}
var r = yield [
packageService.listModulesByName(name),
packageService.listStarUserNames(name),
packageService.listMaintainers(name),
];
var rows = r[0];
var starUsers = r[1];
var maintainers = r[2];
debug('show %s got %d rows, %d tags, %d star users, maintainers: %j',
name, rows.length, tags.length, starUsers.length, maintainers);
var starUserMap = {};
for (var i = 0; i < starUsers.length; i++) {
starUserMap[starUsers[i]] = true;
}
starUsers = starUserMap;
if (rows.length === 0) {
// check if unpublished
var 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
var logId = yield* SyncModuleWorker.sync(name, 'sync-by-install');
debug('start sync %s, get log id %s', name, logId);
// try to get package from official registry
var r = yield* npmService.request('/' + name, {
registry: config.officialNpmRegistry
});
debug('requet from officialNpmRegistry response %s', r.status);
if (r.status !== 200) {
this.status = 404;
this.body = {
error: 'not_found',
reason: 'document not found'
};
return;
}
this.body = r.data;
return;
}
var latestMod = null;
var readme = null;
// set tags
var distTags = {};
for (var i = 0; i < tags.length; i++) {
var t = tags[i];
distTags[t.tag] = t.version;
}
// set versions and times
var versions = {};
var times = {};
var attachments = {};
var createdTime = null;
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
var pkg = row.package;
setDownloadURL(pkg, this);
pkg._cnpm_publish_time = row.publish_time;
versions[pkg.version] = pkg;
var 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) {
var ts = {
modified: modifiedTime,
created: createdTime,
};
for (var t in times) {
ts[t] = times[t];
}
times = ts;
}
if (!latestMod) {
latestMod = rows[0];
}
var rev = String(latestMod.id);
var pkg = latestMod.package;
if (tags.length === 0) {
// some sync error reason, will cause tags missing
// set latest tag at least
distTags.latest = pkg.version;
}
var 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: versions,
readme: 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.body = info;
};

View File

@@ -0,0 +1,30 @@
/**!
* cnpmjs.org - controllers/registry/package/list_all.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var 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* () {
var updated = Date.now();
var names = yield* packageService.listAllPublicModuleNames();
var result = { _updated: updated };
names.forEach(function (name) {
result[name] = true;
});
this.body = result;
};

View File

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

View File

@@ -0,0 +1,59 @@
/**!
* cnpmjs.org - controllers/registry/package/list_since.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var packageService = require('../../../services/package');
var 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* () {
var query = this.query;
if (query.stale !== 'update_after') {
this.status = 400;
this.body = {
error: 'query_parse_error',
reason: 'Invalid value for `stale`.'
};
return;
}
var startkey = Number(query.startkey);
if (!startkey) {
this.status = 400;
this.body = {
error: 'query_parse_error',
reason: 'Invalid value for `startkey`.'
};
return;
}
var 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);
}
var names = yield* packageService.listPublicModuleNamesSince(startkey);
var result = { _updated: updated };
names.forEach(function (name) {
result[name] = true;
});
this.body = result;
};

View File

@@ -0,0 +1,67 @@
/**!
* 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.
*/
var debug = require('debug')('cnpmjs.org:controllers:registry:package:remove');
var urlparse = require('url').parse;
var packageService = require('../../../services/package');
var totalService = require('../../../services/total');
var nfs = require('../../../common/nfs');
var 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) {
var name = this.params.name || this.params[0];
var rev = this.params.rev || this.params[1];
debug('remove all the module with name: %s, id: %s', name, rev);
var mods = yield* packageService.listModulesByName(name);
debug('removeAll module %s: %d', name, mods.length);
var mod = mods[0];
if (!mod) {
return yield* next;
}
yield [
packageService.removeModulesByName(name),
packageService.removeModuleTags(name),
totalService.plusDeleteModule(),
];
var keys = [];
for (var i = 0; i < mods.length; i++) {
var row = mods[i];
var dist = row.package.dist;
var 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

@@ -0,0 +1,74 @@
/**!
* 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.
*/
var debug = require('debug')('cnpmjs.org:controllers:registry:package:remove_version');
var packageService = require('../../../services/package');
var nfs = require('../../../common/nfs');
var logger = require('../../../common/logger');
var 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) {
var name = this.params.name || this.params[0];
var filename = this.params.filename || this.params[1];
var id = Number(this.params.rev || this.params[2]);
// cnpmjs.org-2.0.0.tgz
var 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;
}
var rs = yield [
packageService.getModuleById(id),
packageService.getModule(name, version),
];
var revertTo = rs[0];
var mod = rs[1]; // module need to delete
if (!mod || mod.name !== name) {
return yield* next;
}
var 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

@@ -0,0 +1,237 @@
/**!
* cnpmjs.org - controllers/registry/package/save.js
*
* Copyright(c) fengmk2 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:registry:package:save');
var crypto = require('crypto');
var deprecateVersions = require('./deprecate');
var packageService = require('../../../services/package');
var common = require('../../../lib/common');
var nfs = require('../../../common/nfs');
var 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
var pkg = this.request.body;
var username = this.user.name;
var name = this.params.name || this.params[0];
var filename = Object.keys(pkg._attachments || {})[0];
var version = Object.keys(pkg.versions || {})[0];
if (!version) {
this.status = 400;
this.body = {
error: 'version_error',
reason: 'package.versions is empty'
};
return;
}
// check maintainers
var 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) {
var hasDeprecated = false;
for (var v in pkg.versions) {
var 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;
}
var attachment = pkg._attachments[filename];
var versionPackage = pkg.versions[version];
var 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
var 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;
var distTags = pkg['dist-tags'] || {};
var tags = []; // tag, version
for (var 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);
var exists = yield* packageService.getModule(name, version);
var shasum;
if (exists) {
this.status = 403;
this.body = {
error: 'forbidden',
reason: 'cannot modify pre-existing version: ' + version
};
return;
}
// upload attachment
var tarballBuffer;
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
var 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');
var options = {
key: common.getCDNKey(name, filename),
shasum: shasum
};
var uploadResult = yield nfs.uploadBuffer(tarballBuffer, options);
debug('upload %j', uploadResult);
var dist = {
shasum: 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;
}
var mod = {
name: name,
version: version,
author: username,
package: versionPackage
};
mod.package.dist = dist;
yield* addDepsRelations(mod.package);
var 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
var 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) {
var dependencies = Object.keys(pkg.dependencies || {});
if (dependencies.length > config.maxDependencies) {
dependencies = dependencies.slice(0, config.maxDependencies);
}
yield packageService.addDependencies(pkg.name, dependencies);
}

View File

@@ -0,0 +1,84 @@
/**!
* cnpmjs.js - controllers/registry/package/show.js
*
* Copyright(c) fengmk2 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:registry:package:show');
var semver = require('semver');
var packageService = require('../../../services/package');
var npmService = require('../../../services/npm');
var setDownloadURL = require('../../../lib/common').setDownloadURL;
var SyncModuleWorker = require('../../sync_module_worker');
var config = require('../../../config');
/**
* [deprecate] api
*
* get the special version or tag of a module
*
* GET /:name/:version
* GET /:name/:tag
*/
module.exports = function* show() {
var name = this.params.name || this.params[0];
var tag = this.params.version || this.params[1];
var version = semver.valid(tag);
var mod;
if (version) {
mod = yield* packageService.getModule(name, version);
} else {
mod = yield* packageService.getModuleByTag(name, tag);
}
if (mod) {
setDownloadURL(mod.package, this);
mod.package._cnpm_publish_time = mod.publish_time;
var maintainers = yield* packageService.listMaintainers(name);
if (maintainers.length > 0) {
mod.package.maintainers = maintainers;
}
this.body = mod.package;
return;
}
// if not fond, sync from source registry
if (!this.allowSync) {
this.status = 404;
this.body = {
error: 'not exist',
reason: 'version not found: ' + version
};
return;
}
// start sync
var logId = yield* SyncModuleWorker.sync(name, 'sync-by-install');
debug('start sync %s, get log id %s', name, logId);
// rty to get package from official registry
var r = yield npmService.request('/' + name + '/' + tag, {
registry: config.officialNpmRegistry
});
if (r.status !== 200) {
debug('requet from officialNpmRegistry response %s', r.status);
this.status = 404;
this.body = {
error: 'not exist',
reason: 'tag or version not found: ' + tag
};
return;
}
this.body = r.data;
};

View File

@@ -0,0 +1,69 @@
/**!
* cnpmjs.org - controllers/registry/package/tag.js
*
* Copyright(c) fengmk2 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:registry:package:tag');
var semver = require('semver');
var util = require('util');
var 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() {
var version = this.request.body;
var name = this.params.name || this.params[0];
var 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;
var reason = util.format('setting tag %s to invalid version: %s: %s/%s',
tag, version, name, tag);
this.body = {
error: 'forbidden',
reason: reason
};
return;
}
var mod = yield* packageService.getModule(name, version);
if (!mod) {
this.status = 403;
var reason = util.format('setting tag %s to unknown version: %s: %s/%s',
tag, version, name, tag);
this.body = {
error: 'forbidden',
reason: reason
};
return;
}
var row = yield* packageService.addModuleTag(name, tag, version);
this.status = 201;
this.body = {
ok: true,
modified: row.gmt_modified
};
};

View File

@@ -0,0 +1,180 @@
/**!
* cnpmjs.org - controllers/registry/package/update.js
*
* Copyright(c) fengmk2 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:registry:package:update');
var packageService = require('../../../services/package');
var userService = require('../../../services/user');
var config = require('../../../config');
// PUT /:name/-rev/:rev
//
// * remove with versions, then will `DELETE /:name/download/:filename/-rev/:rev`
// * ...
module.exports = function* update(next) {
var name = this.params.name || this.params[0];
debug('update module %s, %s, %j', this.url, name, this.request.body);
var 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) {
var name = this.params.name || this.params[0];
// left versions
var versions = this.request.body.versions;
// step1: list all the versions
var 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
var removeVersions = [];
var removeVersionMaps = {};
var remainVersions = [];
for (var i = 0; i < mods.length; i++) {
var 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
var tags = yield* packageService.listModuleTags(name);
var removeTags = [];
var 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() {
var name = this.params.name || this.params[0];
var body = this.request.body;
debug('updateMaintainers module %s, %j', name, body);
var 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
var maintainers = yield* packageService.listMaintainerNamesOnly(name);
var map = {};
var newNames = [];
for (var i = 0; i < maintainers.length; i++) {
map[maintainers[i]] = 1;
}
for (var i = 0; i < usernames.length; i++) {
var username = usernames[i];
if (map[username] !== 1) {
newNames.push(username);
}
}
if (newNames.length > 0) {
var users = yield* userService.list(newNames);
var map = {};
for (var i = 0; i < users.length; i++) {
var user = users[i];
map[user.login] = 1;
}
var invailds = [];
for (var i = 0; i < newNames.length; i++) {
var username = newNames[i];
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;
}
}
}
var 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

@@ -1,213 +0,0 @@
/**!
* 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 User = require('../../proxy/user');
var config = require('../../config');
exports.show = function *(next) {
var name = this.params.name;
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,
};
}
data._cnpm_meta = {
id: user.id,
npm_user: user.npm_user,
gmt_create: user.gmt_create,
gmt_modified: user.gmt_modified,
admin: !!config.admins[user.name],
};
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: '18d8d51936478446a5466d4fb1633b80f3838b4caaa03649a885ac722cd6',
// password_sha: '8f4408912a6db1d96b132a90856d99db029cef3d',
// 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 (!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', user);
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
};
};
exports.authSession = function *() {
// body: {"name":"foo","password":"****"}
var body = this.request.body || {};
var name = body.name;
var password = body.password;
var user = yield User.auth(name, password);
debug('authSession %s: %j', name, user);
if (!user) {
this.status = 401;
this.body = {ok: false, name: null, roles: []};
return;
}
var session = yield *this.session;
session.name = user.name;
this.body = {ok: true, name: user.name, roles: []};
};
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 || [],
};
ensurePasswordSalt(user, body);
if (!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

@@ -0,0 +1,128 @@
/**!
* 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.
*/
var ensurePasswordSalt = require('./common').ensurePasswordSalt;
var userService = require('../../../services/user');
var 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() {
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;
}
var 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;
}
var existUser = yield* userService.get(name);
if (existUser) {
this.status = 409;
this.body = {
error: 'conflict',
reason: 'User ' + name + ' already exists.'
};
return;
}
// add new user
var 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

@@ -0,0 +1,26 @@
/**!
* 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.
*/
var crypto = require('crypto');
var 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

@@ -0,0 +1,69 @@
/**!
* 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.
*/
var userService = require('../../../services/user');
// GET /-/user/org.couchdb.user::name
module.exports = function* show(next) {
var name = this.params.name;
var user = yield* userService.getAndSave(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,
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

@@ -0,0 +1,97 @@
/**!
* 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.
*/
var debug = require('debug')('cnpmjs.org:controllers:registry:user:update');
var ensurePasswordSalt = require('./common').ensurePasswordSalt;
var 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) {
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* 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

@@ -0,0 +1,51 @@
/**!
* cnpmjs.org - controllers/registry/user_package.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var packageService = require('../../services/package');
// GET /-/by-user/:user
exports.list = function* () {
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'
};
return;
}
var firstUser = users[0];
if (!firstUser) {
// params.user = '|'
this.body = {};
return;
}
var tasks = {};
for (var i = 0; i < users.length; i++) {
var username = users[i];
tasks[username] = packageService.listPublicModuleNamesByUser(username);
}
var data = yield tasks;
for (var k in data) {
if (data[k].length === 0) {
data[k] = undefined;
}
}
this.body = data;
};

View File

@@ -1,5 +1,5 @@
/**!
* cnpmjs.org - controllers/download.js
* cnpmjs.org - controllers/sync.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
@@ -14,14 +14,17 @@
* Module dependencies.
*/
var Log = require('../proxy/module_log');
var SyncModuleWorker = require('../proxy/sync_module_worker');
var debug = require('debug')('cnpmjs.org:controllers:sync');
var Log = require('../services/module_log');
var SyncModuleWorker = require('./sync_module_worker');
var config = require('../config');
exports.sync = function *() {
exports.sync = function* () {
var username = this.user.name || 'anonymous';
var name = this.params.name;
var publish = this.query.publish === 'true';
var noDep = this.query.nodeps === 'true';
debug('sync %s with query: %j', name, this.query);
if (publish && !this.user.isAdmin) {
this.status = 403;
this.body = {
@@ -34,35 +37,23 @@ exports.sync = function *() {
var options = {
publish: publish,
noDep: noDep,
syncUpstreamFirst: config.sourceNpmRegistryIsCNpm,
};
var result = yield SyncModuleWorker.sync(name, username, options);
var logId = yield* SyncModuleWorker.sync(name, username, options);
debug('sync %s got log id %j', name, logId);
// friendly 404 reason info
if (result.statusCode === 404) {
this.status = 404;
this.body = {
ok: false,
reason: 'can not found ' + name + ' in the source registry'
};
return;
}
if (!result.ok) {
this.status = result.statusCode || 500;
this.body = result.pkg;
return;
}
this.status = 201;
this.body = {
ok: true,
logId: result.logId
logId: logId
};
};
exports.getSyncLog = function *(next) {
exports.getSyncLog = function* (next) {
var logId = this.params.id;
var offset = Number(this.query.offset) || 0;
var row = yield Log.get(logId);
var row = yield* Log.get(logId);
if (!row) {
return yield* next;
}

File diff suppressed because it is too large Load Diff

View File

@@ -15,16 +15,15 @@
* Module dependencies.
*/
var microtime = require('microtime');
var Total = require('../proxy/total');
var Download = require('./download');
var Total = require('../services/total');
var version = require('../package.json').version;
var config = require('../config');
var getDownloadTotal = require('./utils').getDownloadTotal;
var startTime = '' + microtime.now();
var startTime = '' + Date.now();
exports.show = function *() {
var r = yield [Total.get(), Download.total()];
module.exports = function* showTotal() {
var r = yield [Total.get(), getDownloadTotal()];
var total = r[0];
var download = r[1];
@@ -33,7 +32,7 @@ exports.show = function *() {
total.instance_start_time = startTime;
total.node_version = process.version;
total.app_version = version;
total.donate = 'https://me.alipay.com/imk2';
total.donate = 'https://www.gittip.com/fengmk2';
total.sync_model = config.syncModel;
this.body = total;

161
controllers/utils.js Normal file
View File

@@ -0,0 +1,161 @@
/**!
* cnpmjs.org - controllers/utils.js
*
* Copyright(c) fengmk2 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:utils');
var path = require('path');
var fs = require('fs');
var utility = require('utility');
var ms = require('humanize-ms');
var moment = require('moment');
var downloadTotalService = require('../services/download_total');
var nfs = require('../common/nfs');
var config = require('../config');
var DOWNLOAD_TIMEOUT = ms('10m');
exports.downloadAsReadStream = function* (key) {
var tmpPath = path.join(config.uploadDir,
utility.randomString() + key.replace(/\//g, '-'));
function cleanup() {
debug('cleanup %s', tmpPath);
fs.unlink(tmpPath, utility.noop);
}
debug('downloadAsReadStream() %s to %s', key, tmpPath);
try {
yield nfs.download(key, tmpPath, {timeout: DOWNLOAD_TIMEOUT});
} catch (err) {
debug('downloadAsReadStream() %s to %s error: %s', key, tmpPath, err.stack);
cleanup();
throw err;
}
var tarball = fs.createReadStream(tmpPath);
tarball.once('error', cleanup);
tarball.once('end', cleanup);
return tarball;
};
exports.getDownloadTotal = function* (name) {
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);
}
var rows = yield* downloadTotalService[method].apply(downloadTotalService, args);
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;
}
}
return download;
};
exports.setLicense = function (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: exports.getOssLicenseUrlFromName(license),
name: license
};
}
}
};
exports.getOssLicenseUrlFromName = function (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;
};

49
controllers/web/badge.js Normal file
View File

@@ -0,0 +1,49 @@
/**!
* cnpmjs.org - controllers/web/badge.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var config = require('../../config');
var packageService = require('../../services/package');
exports.version = function* () {
var color = 'lightgrey';
var version = 'invalid';
var name = this.params[0];
var latestTag = yield* packageService.getModuleByTag(name, 'latest');
if (latestTag) {
version = latestTag.version;
if (/^0\.0\./.test(version)) {
// <0.1.0 & >=0.0.0
color = 'red';
} else if (/^0\./.test(version)) {
// <1.0.0 & >=0.1.0
color = 'green';
} else {
// >=1.0.0
color = 'blue';
}
}
var subject = config.badgeSubject.replace(/\-/g, '--');
version = version.replace(/\-/g, '--');
var url = 'https://img.shields.io/badge/' + subject + '-' + version + '-' + color + '.svg';
if (this.querystring) {
url += '?' + this.querystring;
} else {
url += '?style=flat-square';
}
this.redirect(url);
};

View File

@@ -14,10 +14,87 @@
* Module dependencies.
*/
var debug = require('debug')('cnpmjs.org:controllers:web:dist');
var mime = require('mime');
var urlparse = require('url').parse;
var distService = require('../../services/dist');
var config = require('../../config');
var downloadAsReadStream = require('../utils').downloadAsReadStream;
exports.redirect = function *(next) {
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 = config.disturl + (params[0] || '/');
this.redirect(url);
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* distService.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* distService.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);
}

View File

@@ -1,250 +0,0 @@
/*!
* 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 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');
exports.display = function *(next) {
var params = this.params;
var name = params.name;
var tag = params.version;
var getPackageMethod;
var getPackageArgs;
var version = semver.valid(tag || '');
if (version) {
getPackageMethod = 'get';
getPackageArgs = [name, version];
} else {
getPackageMethod = 'getByTag';
getPackageArgs = [name, tag || 'latest'];
}
var r = yield [
Module[getPackageMethod].apply(Module, getPackageArgs),
down.total(name),
ModuleDeps.list(name),
ModuleStar.listUsers(name),
];
var pkg = r[0];
var download = r[1];
var dependents = (r[2] || []).map(function (item) {
return item.deps;
});
var users = r[3];
if (!pkg || !pkg.package) {
return yield* next;
}
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 (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'}, false);
}
}
}
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'}, false);
}
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;
yield this.render('package', {
title: 'Package - ' + pkg.name,
package: pkg,
download: download
});
};
exports.search = function *(next) {
var params = this.params;
var word = params.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.query.name;
yield this.render('sync', {
name: name,
title: 'Sync - ' + name
});
};
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

@@ -0,0 +1,37 @@
/**!
* 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.
*/
var packageService = require('../../../services/package');
var config = require('../../../config');
module.exports = function* listPrivates() {
var tasks = {};
for (var i = 0; i < config.scopes.length; i++) {
var scope = config.scopes[i];
tasks[scope] = packageService.listPrivateModulesByScope(scope);
}
if (config.privatePackages && config.privatePackages.length > 0) {
tasks['no scoped'] = packageService.listModules(config.privatePackages);
}
var scopes = yield tasks;
yield this.render('private', {
title: 'private packages',
scopes: scopes
});
};

View File

@@ -0,0 +1,54 @@
/**!
* 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 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var debug = require('debug')('cnpmjs.org:controllers:web:package:search');
var packageService = require('../../../services/package');
module.exports = function* search() {
var params = this.params;
var word = params.word || params[0];
debug('search %j', word);
var result = yield* packageService.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,
});
};

View File

@@ -0,0 +1,49 @@
/**!
* 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.
*/
var packageService = require('../../../services/package');
module.exports = function* searchRange() {
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* packageService.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
};
};

View File

@@ -0,0 +1,203 @@
/**!
* cnpmjs.org - controllers/web/package/show.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:web:package:show');
var bytes = require('bytes');
var giturl = require('giturl');
var moment = require('moment');
var semver = require('semver');
var marked = require('marked');
var gravatar = require('gravatar');
var humanize = require('humanize-number');
var config = require('../../../config');
var utils = require('../../utils');
var setDownloadURL = require('../../../lib/common').setDownloadURL;
var packageService = require('../../../services/package');
module.exports = function* show(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 = 'getModule';
getPackageArgs = [name, version];
} else {
getPackageMethod = 'getModuleByTag';
getPackageArgs = [name, tag || 'latest'];
}
var pkg = yield packageService[getPackageMethod].apply(packageService, getPackageArgs);
if (!pkg || !pkg.package) {
// check if unpublished
var unpublishedInfo = yield* packageService.getUnpublishedModule(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 [
utils.getDownloadTotal(name),
packageService.listDependents(name),
packageService.listStarUserNames(name),
packageService.listMaintainers(name)
];
var download = r[0];
var dependents = r[1];
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._npmUser) {
pkg.lastPublishedUser = pkg._npmUser;
if (pkg.lastPublishedUser.email) {
pkg.lastPublishedUser.gravatar = gravatar.url(pkg.lastPublishedUser.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 === 'undefined') {
pkg.repository = null;
}
if (pkg.repository && pkg.repository.url) {
pkg.repository.weburl = giturl.parse(pkg.repository.url) || pkg.repository.url;
}
if (!pkg.bugs) {
pkg.bugs = {};
}
utils.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.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 (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
});
};

View File

@@ -0,0 +1,27 @@
/**!
* cnpmjs.org - controllers/web/package/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() {
var name = this.params.name || this.params[0] || this.query.name;
if (!name) {
return this.redirect('/');
}
yield this.render('sync', {
name: name,
title: 'Sync - ' + name,
});
};

View File

@@ -1,38 +0,0 @@
/*!
* 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 Module = require('../../proxy/module');
var User = require('../../proxy/user');
exports.display = function *(next) {
var name = this.params.name;
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 = {
name: name,
email: user && user.email
};
yield this.render('profile', {
title: 'User - ' + name,
packages: packages || [],
user: user
});
};

View File

@@ -0,0 +1,70 @@
/**!
* cnpmjs.org - controllers/web/user/show.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 packageService = require('../../../services/package');
var userService = require('../../../services/user');
var common = require('../../../lib/common');
module.exports = function* showUser(next) {
var name = this.params.name;
var isAdmin = common.isAdmin(name);
var scopes = config.scopes || [];
var user;
var r = yield [packageService.listModulesByUser(name), userService.getAndSave(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.gmt_modified,
isAdmin: isAdmin,
scopes: scopes
});
};

View File

@@ -15,44 +15,52 @@
* Module dependencies.
*/
var childProcess = require('child_process');
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) {
cluster.setupMaster({
exec: workerPath
});
forkWorker();
if (config.syncModel !== 'none') {
forkSyncer();
}
} else {
require(workerPath);
if (config.syncModel !== 'none') {
require(syncPath);
}
}
cluster.on('fork', function (worker) {
function forkWorker() {
cfork({
exec: workerPath,
count: config.numCPUs,
}).on('fork', function (worker) {
console.log('[%s] [worker:%d] new worker start', Date(), worker.process.pid);
});
cluster.on('disconnect', function (worker) {
var w = cluster.fork();
console.error('[%s] [master:%s] wroker:%s disconnect, suicide: %s, state: %s. New worker:%s fork',
Date(), process.pid, worker.process.pid, worker.suicide, worker.state, w.process.pid);
});
cluster.on('exit', function (worker, code, signal) {
}).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', 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);
});
// Fork workers.
for (var i = 0; i < config.numCPUs; i++) {
cluster.fork();
}
childProcess.fork(syncPath);
} else {
require(workerPath);
require(syncPath);
}
function forkSyncer() {
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',
Date(), process.pid, err.name, err.message);
setTimeout(forkSyncer, 1000);
});
}

View File

@@ -1,4 +1,4 @@
CREATE TABLE `user` (
CREATE TABLE IF NOT EXISTS `user` (
`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',
@@ -19,7 +19,7 @@ CREATE TABLE `user` (
-- ADD `json` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'json details',
-- ADD `npm_user` tinyint(1) DEFAULT '0' COMMENT 'user sync from npm or not, 1: true, other: false';
CREATE TABLE `module_keyword` (
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',
@@ -30,7 +30,7 @@ CREATE TABLE `module_keyword` (
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module keyword';
CREATE TABLE `module_star` (
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',
@@ -40,7 +40,27 @@ CREATE TABLE `module_star` (
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module star';
CREATE TABLE `module` (
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 `user_module_name` (`user`,`name`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='private module maintainers';
CREATE TABLE IF NOT EXISTS `npm_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 `user_module_name` (`user`,`name`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='npm original module maintainers';
CREATE TABLE IF NOT EXISTS `module` (
`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',
@@ -66,7 +86,7 @@ CREATE TABLE `module` (
-- show create table module\G
-- ALTER TABLE `module` CHANGE `name` `name` VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name';
CREATE TABLE `module_log` (
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',
@@ -78,7 +98,7 @@ CREATE TABLE `module_log` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module sync log';
-- ALTER TABLE `module_log` CHANGE `name` `name` VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name';
CREATE TABLE `tag` (
CREATE TABLE IF NOT EXISTS `tag` (
`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',
@@ -87,12 +107,25 @@ CREATE TABLE `tag` (
`version` varchar(30) NOT NULL COMMENT 'module version',
`module_id` bigint(20) unsigned NOT NULL COMMENT 'module id',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`, `tag`)
UNIQUE KEY `name` (`name`, `tag`),
KEY `gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module tag';
-- ALTER TABLE `tag` ADD `module_id` BIGINT( 20 ) UNSIGNED NOT NULL;
-- ALTER TABLE `tag` CHANGE `name` `name` VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name';
-- ALTER TABLE `tag` ADD KEY `gmt_modified` (`gmt_modified`);
CREATE TABLE `total` (
CREATE TABLE IF NOT EXISTS `module_unpublished` (
`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',
`package` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'base info: tags, time, maintainers, description, versions',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`),
KEY `gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module unpublished info';
CREATE TABLE IF NOT EXISTS `total` (
`name` varchar(100) NOT NULL COMMENT 'total name',
`gmt_modified` datetime NOT NULL COMMENT 'modified time',
`module_delete` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'module delete count',
@@ -106,7 +139,9 @@ CREATE TABLE `total` (
`last_sync_module` varchar(100) COMMENT 'last sync success module name',
PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='total info';
INSERT INTO total(name, gmt_modified) VALUES('total', now());
-- init `total` count
INSERT INTO total(name, gmt_modified) VALUES('total', now())
ON DUPLICATE KEY UPDATE gmt_modified=now();
-- ALTER TABLE `total` ADD `last_sync_time` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'last timestamp sync from official registry'
-- ALTER TABLE `total` ADD `last_exist_sync_time` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'last timestamp sync exist packages from official registry'
-- ALTER TABLE `total` ADD `sync_status` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'system sync from official registry status'
@@ -116,7 +151,7 @@ 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 `download_total` (
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',
@@ -128,7 +163,7 @@ CREATE TABLE `download_total` (
) 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 `module_deps` (
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',
@@ -137,3 +172,30 @@ CREATE TABLE `module_deps` (
UNIQUE KEY `name_deps` (`name`,`deps`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module deps';
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',
`parent` varchar(200) NOT NULL COMMENT 'parent dir' DEFAULT '/',
`date` varchar(20) COMMENT '02-May-2014 01:06',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`parent`, `name`),
KEY `gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='dist dir info';
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',
`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',
`sha1` varchar(40) COMMENT 'sha1 hex value',
`url` varchar(2048),
PRIMARY KEY (`id`),
UNIQUE KEY `fullname` (`parent`, `name`),
KEY `gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='dist file info';

895
docs/registry-api.md Normal file
View File

@@ -0,0 +1,895 @@
# NPM Registry API
## Overview
* [Schema](/docs/registry-api.md#schema)
* [Client Errors](/docs/registry-api.md#client-errors)
* [Authentication](/docs/registry-api.md#authentication)
* [Package](/docs/registry-api.md#package)
* [User](/docs/registry-api.md#user)
* [Search](/docs/registry-api.md#search)
## Schema
All API access is over HTTPS or HTTP,
and accessed from the `registry.npmjs.org` domain.
All data is sent and received as JSON.
```bash
$ 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":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
```json
Status: 4xx
{
"error": "error_name",
"reason": "error reason string"
}
```
## Authentication
There is only one way to authenticate through the API.
## Basic Authentication
```bash
$ curl -u "username:password" https://registry.npmjs.org
```
## Failed login limit
```bash
$ 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."}
```
## Package
* Read
* [Get a single package](/docs/registry-api.md#get-a-single-package)
* [Get a special version or tag package](/docs/registry-api.md#get-a-special-version-or-tag-package)
* [List packages since from a update time](/docs/registry-api.md#list-packages-since-from-a-update-time)
* [List package names by users](/docs/registry-api.md#list-package-names-by-users)
* Write
* [Publish a new package](/docs/registry-api.md#publish-a-new-package)
* [Update a package's tag](/docs/registry-api.md#update-a-packages-tag)
* [Update a package's maintainers](/docs/registry-api.md#update-a-packages-maintainers)
* [Remove one version from package](/docs/registry-api.md#remove-one-version-from-package)
* [Remove a tgz file from package](/docs/registry-api.md#remove-a-tgz-file-from-package)
### Get a single package
```
GET /:package
```
#### Response
```json
HTTP/1.1 200 OK
Etag: "8UDCP753LFXOG42NMX88JAN40"
Content-Type: application/json
Cache-Control: max-age=60
Content-Length: 2243
{
"_id": "pedding",
"_rev": "11-e6d1e6e96eaf72433fef6aaabe843af8",
"name": "pedding",
"description": "Just pedding for callback.",
"dist-tags": {
"latest": "1.0.0"
},
"versions": {
"1.0.0": {
"name": "pedding",
"version": "1.0.0",
"description": "Just pedding for callback.",
"main": "index.js",
"scripts": {
"test": "make test-all"
},
"repository": {
"type": "git",
"url": "git://github.com/fengmk2/pedding.git"
},
"keywords": [
"pedding",
"callback"
],
"devDependencies": {
"contributors": "*",
"mocha": "*",
"mocha-phantomjs": "*",
"component": "*",
"chai": "*"
},
"author": {
"name": "fengmk2",
"email": "fengmk2@gmail.com"
},
"license": "MIT",
"contributors": [
{
"name": "fengmk2",
"email": "fengmk2@gmail.com",
"url": "https://github.com/fengmk2"
},
{
"name": "dead-horse",
"email": "dead_horse@qq.com",
"url": "https://github.com/dead-horse"
}
],
"gitHead": "b42a708414a704336e9dee570a963e2dbe43e529",
"bugs": {
"url": "https://github.com/fengmk2/pedding/issues"
},
"homepage": "https://github.com/fengmk2/pedding",
"_id": "pedding@1.0.0",
"_shasum": "7f5098d60307b4ef7240c3d693cb20a9473c6074",
"_from": ".",
"_npmVersion": "1.4.13",
"_npmUser": {
"name": "fengmk2",
"email": "fengmk2@gmail.com"
},
"maintainers": [
{
"name": "fengmk2",
"email": "fengmk2@gmail.com"
},
{
"name": "dead-horse",
"email": "dead_horse@qq.com"
}
],
"dist": {
"shasum": "7f5098d60307b4ef7240c3d693cb20a9473c6074",
"tarball": "http://registry.npmjs.org/pedding/-/pedding-1.0.0.tgz"
},
"directories": {}
}
},
"readme": "# pedding\n readme...",
"maintainers": [
{
"name": "fengmk2",
"email": "fengmk2@gmail.com"
},
{
"name": "dead-horse",
"email": "dead_horse@qq.com"
},
{
"name": "dead_horse",
"email": "dead_horse@qq.com"
}
],
"time": {
"modified": "2014-07-05T14:22:53.849Z",
"created": "2012-09-18T14:46:08.346Z",
"0.0.1": "2012-09-18T14:46:21.321Z",
"0.0.2": "2013-06-22T08:26:45.125Z",
"0.0.3": "2013-07-02T15:20:34.707Z",
"1.0.0": "2014-07-05T11:08:51.614Z"
},
"author": {
"name": "fengmk2",
"email": "fengmk2@gmail.com"
},
"repository": {
"type": "git",
"url": "git://github.com/fengmk2/pedding.git"
},
"keywords": [
"pedding",
"callback"
],
"bugs": {
"url": "https://github.com/fengmk2/pedding/issues"
},
"license": "MIT",
"readmeFilename": "README.md",
"homepage": "https://github.com/fengmk2/pedding",
"contributors": [
{
"name": "fengmk2",
"email": "fengmk2@gmail.com",
"url": "https://github.com/fengmk2"
},
{
"name": "dead-horse",
"email": "dead_horse@qq.com",
"url": "https://github.com/dead-horse"
}
],
"_attachments": {}
}
```
### ~~Get a special version or tag package~~
__deprecated__
```
GET /:package/:tag_or_version
```
#### Reponse
```json
HTTP/1.1 200 OK
Etag: "1WJ4JF535RO3BDZR2BARXSGLY"
Content-Type: application/json
Cache-Control: max-age=60
Content-Length: 1183
{
"name": "pedding",
"version": "1.0.0",
"description": "Just pedding for callback.",
"main": "index.js",
"scripts": {
"test": "make test-all"
},
"repository": {
"type": "git",
"url": "git://github.com/fengmk2/pedding.git"
},
"keywords": [
"pedding",
"callback"
],
"devDependencies": {
"contributors": "*",
"mocha": "*",
"mocha-phantomjs": "*",
"component": "*",
"chai": "*"
},
"author": {
"name": "fengmk2",
"email": "fengmk2@gmail.com"
},
"license": "MIT",
"contributors": [
{
"name": "fengmk2",
"email": "fengmk2@gmail.com",
"url": "https://github.com/fengmk2"
},
{
"name": "dead-horse",
"email": "dead_horse@qq.com",
"url": "https://github.com/dead-horse"
}
],
"gitHead": "b42a708414a704336e9dee570a963e2dbe43e529",
"bugs": {
"url": "https://github.com/fengmk2/pedding/issues"
},
"homepage": "https://github.com/fengmk2/pedding",
"_id": "pedding@1.0.0",
"_shasum": "7f5098d60307b4ef7240c3d693cb20a9473c6074",
"_from": ".",
"_npmVersion": "1.4.13",
"_npmUser": {
"name": "fengmk2",
"email": "fengmk2@gmail.com"
},
"maintainers": [
{
"name": "fengmk2",
"email": "fengmk2@gmail.com"
},
{
"name": "dead-horse",
"email": "dead_horse@qq.com"
}
],
"dist": {
"shasum": "7f5098d60307b4ef7240c3d693cb20a9473c6074",
"tarball": "http://registry.npmjs.org/pedding/-/pedding-1.0.0.tgz"
},
"directories": {}
}
```
### Publish a new package
* Authentication required.
```
PUT /:package
```
#### Input
```json
{
"_id": "pedding",
"name": "pedding",
"description": "Just pedding for callback.",
"dist-tags": {
"latest": "1.0.0"
},
"versions": {
"1.0.0": {
"name": "pedding",
"version": "1.0.0",
"description": "Just pedding for callback.",
"main": "index.js",
"scripts": {
"test": "make test-all"
},
"repository": {
"type": "git",
"url": "git://github.com/fengmk2/pedding.git"
},
"keywords": [ "pedding","callback" ],
"devDependencies": {
"contributors": "*",
"mocha": "*",
"mocha-phantomjs": "*",
"component": "*",
"chai": "*"
},
"dependencies": {},
"author": {
"name": "fengmk2",
"email": "fengmk2@gmail.com"
},
"license": "MIT",
"contributors": [
{
"name": "fengmk2",
"email": "fengmk2@gmail.com",
"url": "https://github.com/fengmk2"
},
{
"name": "dead-horse",
"email": "dead_horse@qq.com",
"url": "https://github.com/dead-horse"
}
],
"readme": "# pedding ...",
"readmeFilename": "README.md",
"gitHead": "b42a708414a704336e9dee570a963e2dbe43e529",
"bugs": {
"url": "https://github.com/fengmk2/pedding/issues"
},
"homepage": "https://github.com/fengmk2/pedding",
"_id": "pedding@1.0.0",
"_shasum": "7f5098d60307b4ef7240c3d693cb20a9473c6074",
"_from": ".",
"_npmVersion": "1.5.0-alpha-4",
"_npmUser": {
"name": "admin",
"email": "fengmk2@gmail.com"
},
"maintainers": [
{
"name": "admin",
"email": "fengmk2@gmail.com"
}
],
"dist": {
"shasum": "7f5098d60307b4ef7240c3d693cb20a9473c6074",
"tarball": "https://registry.npmjs.org/pedding/-/pedding-1.0.0.tgz"
}
}
},
"readme": "# pedding ...",
"maintainers": [
{
"name": "admin",
"email": "fengmk2@gmail.com"
}
],
"_attachments": {
"pedding-1.0.0.tgz":{
"content_type": "application/octet-stream",
"data": "H4sIAAAAAAAAA+0aa3PbNjKf8Su...",
"length": 2107
}
}
}
```
#### Response
```json
Status: 201 Created
{
"ok": true,
"rev": "11-e6d1e6e96eaf72433fef6aaabe843af8"
}
```
### Update a package's tag
* Authentication required.
```
PUT /:package/:tag
```
#### Input
The total input body is the `version` string which's setting to the tag.
```json
"1.0.0"
```
#### Response
```json
Status: 201 Created
{
"ok": true
}
```
### Update a package's maintainers
* Authentication required.
```
PUT /:package/-rev/:rev
```
#### Input
```json
{
"_id": "pedding",
"_rev": "11-e6d1e6e96eaf72433fef6aaabe843af8",
"maintainers":[
{ "name": "fengmk2", "email": "fengmk2@gmail.com" },
{ "name": "dead-horse", "email": "dead_horse@qq.com" }
]
}
```
#### Response
```json
Status: 201 Created
{
"ok": true,
"id": "pedding",
"rev": "12-bb300a90c9aeb779748b83ec1b744039"
}
```
### Remove one version from package
* Authentication required.
* In any delete, note that __the version number still cannot be reused__.
```
PUT /:package/-rev/:rev
```
#### Input
Remove that specific version from the versions hash in the `PUT` body.
Example for removing `0.0.1` version:
```json
{
"_id": "pedding",
"_rev": "12-bb300a90c9aeb779748b83ec1b744039",
"name": "pedding",
"description": "desc",
"dist-tags": { "latest": "1.0.0" },
"maintainers":
[ ... ],
"time":
{ ... },
"users": {},
"author": { ... },
"repository": { ... },
"versions":
{ "1.0.0":
{ ... },
"0.0.3":
{ ... },
"0.0.2":
{ ... } },
"readme": "...",
"homepage": "https://github.com/fengmk2/pedding",
"bugs": { ... },
"license": "MIT" }
```
#### Response
```json
Status: 201 Created
{
"ok": true
}
```
### Remove all versions of a package
* Authentication required.
* In any delete, note that __the version number still cannot be reused__.
```
DELETE /:package/-rev/:rev
```
#### Response
```json
Status: 201 Created
{
"ok": true
}
```
### Remove a tgz file from package
* Authentication required.
```
DELETE /:tgzfilepath/-rev/:rev
```
Exmaple for removing `https://registry.npmjs.org/pedding/-/pedding-0.0.1.tgz` file:
```
DELETE /pedding/-/pedding-0.0.1.tgz/-rev/12-bb300a90c9aeb779748b83ec1b744039
```
#### Response
```json
Status: 200 OK
{
"ok": true
}
```
### List packages since from a update time
```
GET /-/all/since?stale=update_after&startkey=:startkey
```
* `startkey` is a ms timestamp
#### Response
```bash
$ curl -i "https://registry.npmjs.org/-/all/since?stale=update_after&startkey=1407255748643"
```
```json
HTTP/1.1 200 OK
{
"_updated": 1407255883282,
"bacon-and-eggs": {
"name": "bacon-and-eggs",
"description": "A functional reactive Twitter API client in node",
"dist-tags": {
"latest": "0.0.4"
},
"maintainers": [
{
"name": "mikegroseclose",
"email": "mike.groseclose@gmail.com"
}
],
"homepage": "http://github.com/mikegroseclose/bacon-and-eggs",
"keywords": [
"twitter",
"api",
"frp",
"functional",
"reactive",
"bacon",
"eggs",
"oauth",
"stream",
"streams"
],
"repository": {
"type": "git",
"url": "git://github.com/mikegroseclose/gulp-regex-replace.git"
},
"author": {
"name": "Mike Groseclose",
"email": "mike.groseclose@gmail.com",
"url": "http://mikegroseclose.com"
},
"bugs": {
"url": "https://github.com/mikegroseclose/gulp-regex-replace/issues"
},
"readmeFilename": "README.md",
"time": {
"modified": "2014-08-05T16:21:17.041Z"
},
"versions": {
"0.0.4": "latest"
}
},
"git-perm-rm": {
"name": "git-perm-rm",
"description": "Permanently remove a file or directory from a git repo including all related commit records.",
"dist-tags": {
"latest": "1.0.1"
},
"maintainers": [
{
"name": "kael",
"email": "i@kael.me"
}
],
"homepage": "https://github.com/kaelzhang/git-perm-rm",
"keywords": [
"git",
"rm",
"git-perm-rm",
"remove",
"permanently"
],
"repository": {
"type": "git",
"url": "git://github.com/kaelzhang/git-perm-rm.git"
},
"author": {
"name": "Kael"
},
"bugs": {
"url": "https://github.com/kaelzhang/git-perm-rm/issues"
},
"license": "MIT",
"readmeFilename": "README.md",
"time": {
"modified": "2014-08-05T16:22:41.253Z"
},
"versions": {
"1.0.1": "latest"
}
}
}
```
### List package names by users
```bash
GET /-/by-user/$username[|$another1[|$another2...]]
```
* `username` user name like `fengmk2`
* also support multi users by `name1|name2|name3`
#### Response
```bash
$ curl -i "https://registry.npmjs.org/-/by-user/czy88840616"
$ curl -i "https://registry.npmjs.org/-/by-user/czy88840616|fengmk2|dead-horse"
```
```json
HTTP/1.1 200 OK
{
"czy88840616": [
"easyconf",
"egg",
"flag",
"gdp",
"generator-webx-vm",
"magic-cube",
"rim",
"tbuild",
"test-publish",
"velocity-parser",
"vmarket",
"wi"
]
}
```
## 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)
### Get a single user
```
GET /-/user/org.couchdb.user::username
```
#### Response
```json
HTTP/1.1 200 OK
ETag: "32-984ee97e01aea166dcab6d1517c730e3"
{
"_id": "org.couchdb.user:fengmk2",
"_rev": "32-984ee97e01aea166dcab6d1517c730e3",
"name": "fengmk2",
"email": "fengmk2@gmail.com",
"type": "user",
"roles": [],
"date": "2014-08-04T10:43:07.063Z",
"fullname": "fengmk2",
"avatar": "https://secure.gravatar.com/avatar/95b9d41231617a05ced5604d242c9670?s=50&d=retro",
"freenode": "",
"github": "fengmk2",
"homepage": "http://fengmk2.github.com",
"twitter": "fengmk2",
"avatarMedium": "https://secure.gravatar.com/avatar/95b9d41231617a05ced5604d242c9670?s=100&d=retro",
"avatarLarge": "https://secure.gravatar.com/avatar/95b9d41231617a05ced5604d242c9670?s=496&d=retro",
"fields": [
{
"name": "fullname",
"value": "fengmk2",
"title": "Full Name",
"show": "fengmk2"
},
{
"name": "email",
"value": "fengmk2@gmail.com",
"title": "Email",
"show": "<a href=\"mailto:fengmk2@gmail.com\">fengmk2@gmail.com</a>"
},
{
"name": "github",
"value": "fengmk2",
"title": "Github",
"show": "<a rel=\"me\" href=\"https://github.com/fengmk2\">fengmk2</a>"
},
{
"name": "twitter",
"value": "fengmk2",
"title": "Twitter",
"show": "<a rel=\"me\" href=\"https://twitter.com/fengmk2\">@fengmk2</a>"
},
{
"name": "appdotnet",
"value": "",
"title": "App.net",
"show": ""
},
{
"name": "homepage",
"value": "http://fengmk2.github.com",
"title": "Homepage",
"show": "<a rel=\"me\" href=\"http://fengmk2.github.com/\">http://fengmk2.github.com</a>"
},
{
"name": "freenode",
"value": "",
"title": "IRC Handle",
"show": ""
}
],
"appdotnet": "fengmk2"
}
```
### Add a new user
```
PUT /-/user/org.couchdb.user::username
```
#### Input
```json
{
"name": "admin",
"password": "123",
"email": "fengmk2@gmail.com",
"_id": "org.couchdb.user:admin",
"type": "user",
"roles": [],
"date": "2014-08-05T16:05:17.792Z"
}
```
#### Response
```json
Status: 201 Created
{
"ok": true,
"id": "org.couchdb.user:fengmk2",
"rev": "32-984ee97e01aea166dcab6d1517c730e3"
}
```
### Update a exists user
* Authentication required.
```
PUT /-/user/org.couchdb.user::username/-rev/:rev
```
#### Input
```json
{
"name": "admin",
"password": "123",
"email": "fengmk2@gmail.com",
"_id": "org.couchdb.user:admin",
"type": "user",
"roles": [],
"date": "2014-08-05T16:05:17.792Z",
"_rev": "2-1a18c3d73ba42e863523a399ff3304d8"
}
```
#### Response
```json
Status: 201 Created
{
"ok": true,
"id": "org.couchdb.user:fengmk2",
"rev": "3-bb300a90c9aeb779748b83ec1b744039"
}
```
## Search

View File

@@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS `npm_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 `user_module_name` (`user`,`name`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='npm original module maintainers';

View File

@@ -1,9 +1,14 @@
# cnpmjs.org: Private npm registry and web for Enterprise
# cnpmjs.org: Private npm registry and web for Company
So `cnpm` is meaning: **Company npm**.
## Registry
* 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/)
<table class="downloads">
<tbody>
@@ -48,57 +53,17 @@
Running on [Node.js](http://nodejs.org), version <span id="node-version"></span>.
<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);
}
<script src="/js/readme.js"></script>
$.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));
## Version Badge
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));
Default style is `flat-square`.
$('#node-version').html(data.node_version || 'v0.10.22');
$('#app-version').html(data.app_version || '0.0.0');
Badge URL: `http://cnpmjs.org/badge/v/cnpmjs.org.svg` ![cnpmjs.org-badge](http://cnpmjs.org/badge/v/cnpmjs.org.svg)
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>
* `<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)
## Usage
@@ -166,34 +131,10 @@ $ cnpm info cnpm
@see Github [Issues](https://github.com/cnpm/cnpmjs.org/issues)
## Authors
## Histories
Release [History](/history).
```bash
$ git summary
project : cnpmjs.org
repo age : 4 months ago
commits : 472
active : 167 days
files : 104
authors :
272 fengmk2 57.6%
195 dead_horse 41.3%
2 4simple 0.4%
2 Stanley Zheng 0.4%
1 Alsotang 0.2%
```
## npm and cnpm relation
![npm&cnpm](https://docs.google.com/drawings/d/12QeQfGalqjsB77mRnf5Iq5oSXHCIUTvZTwECMonqCmw/pub?w=383&h=284)
## 捐赠 Donate
如果您觉得 [cnpmjs.org] 对您有帮助,欢迎请作者一杯咖啡.
[![Donate](https://img.alipay.com/sys/personalprod/style/mc/btn-index.png)](https://me.alipay.com/imk2)
[cnpmjs.org]: http://cnpmjs.org/
[registry.cnpmjs.org]: http://registry.cnpmjs.org/

View File

@@ -21,6 +21,7 @@ var util = require('util');
exports.getTarballFilepath = function (filename) {
// ensure download file path unique
// TODO: not only .tgz, and also other extname
var name = filename.replace(/\.tgz$/, '.' + crypto.randomBytes(16).toString('hex') + '.tgz');
return path.join(config.uploadDir, name);
};

View File

@@ -15,46 +15,50 @@
*/
var debug = require('debug')('cnpmjs.org:middleware:auth');
var User = require('../proxy/user');
var config = require('../config');
var common = require('../lib/common');
var UserService = require('../services/user');
module.exports = function (options) {
return function *auth(next) {
var session = yield *this.session;
debug('%s, %s, %j', this.url, this.sessionId, session);
/**
* Parse the request authorization
* get the real user
*/
module.exports = function () {
return function* auth(next) {
this.user = {};
if (session.name) {
this.user.name = session.name;
this.user.isAdmin = common.isAdmin(session.name);
debug('auth exists user: %j, headers: %j', this.user, this.header);
return yield *next;
}
var authorization = (this.get('authorization') || '').split(' ')[1] || '';
authorization = authorization.trim();
debug('%s %s with %j', this.method, this.url, authorization);
if (!authorization) {
return yield *next;
return yield* next;
}
authorization = new Buffer(authorization, 'base64').toString().split(':');
if (authorization.length !== 2) {
return yield *next;
return yield* next;
}
var username = authorization[0];
var password = authorization[1];
var row = yield User.auth(username, password);
if (!row) {
debug('auth fail user: %j, headers: %j', row, this.header);
return yield *next;
var row;
try {
row = yield* UserService.auth(username, password);
} catch (err) {
// do not response error here
// many request do not need login
this.user.error = err;
}
this.user.name = row.name;
this.user.isAdmin = common.isAdmin(row.name);
if (!row) {
debug('auth fail user: %j, headers: %j', row, this.header);
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;
};
};

28
middleware/block.js Normal file
View File

@@ -0,0 +1,28 @@
/**!
* cnpmjs.org - middleware/block.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
module.exports = function () {
return function* block(next) {
var ua = String(this.get('user-agent')).toLowerCase();
if (ua === 'ruby') {
this.status = 403;
return this.body = {
message: 'forbidden Ruby user-agent, ip: ' + this.ip
};
}
yield* next;
};
};

42
middleware/editable.js Normal file
View File

@@ -0,0 +1,42 @@
/**!
* cnpmjs.org - middleware/editable.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var packageService = require('../services/package');
// admin or module's maintainer can modified the module
module.exports = function* editable(next) {
var username = this.user && this.user.name;
var moduleName = this.params.name || this.params[0];
if (username && moduleName) {
if (this.user.isAdmin) {
return yield* next;
}
var isMaintainer = yield* packageService.isMaintainer(moduleName, username);
if (isMaintainer) {
return yield* next;
}
}
this.status = 403;
var message = 'not authorized to modify ' + moduleName;
if (username) {
message = username + ' ' + message;
}
this.body = {
error: 'forbidden user',
reason: message
};
};

View File

@@ -14,7 +14,22 @@
* Module dependencies.
*/
var http = require('http');
module.exports = function *login(next) {
if (this.user.error) {
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
};
return;
}
if (!this.user.name) {
this.status = 401;
this.body = {

View File

@@ -22,8 +22,6 @@ var template = '<?xml version="1.0" encoding="UTF-8"?>\
<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';

View File

@@ -14,11 +14,13 @@
* Module dependencies.
*/
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, only admin user can publish
this.status = 403;
this.body = {
error: 'no_perms',
@@ -26,5 +28,75 @@ module.exports = function *publishable(next) {
};
return;
}
yield *next;
// 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
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;
}
// scope
if (name[0] === '@') {
if (checkScope(name, this)) {
return yield* next;
}
return;
}
// none-scope
if (checkNoneScope(this)) {
return yield* next;
}
};
/**
* check module's scope legal
*/
function checkScope(name, ctx) {
if (!ctx.user.scopes || !ctx.user.scopes.length) {
ctx.status = 404;
return false;
}
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(', '))
};
return false;
}
return true;
}
/**
* check if user have permission to publish without scope
*/
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)'
};
}

View File

@@ -14,12 +14,16 @@
* Module dependencies.
*/
module.exports = function *notFound(next) {
yield *next;
module.exports = function* notFound(next) {
yield* next;
if (this.status) {
if (this.status && this.status !== 404) {
return;
}
if (this.body && this.body.name) {
return;
}
this.status = 404;
this.body = {
error: 'not_found',

29
middleware/static.js Normal file
View File

@@ -0,0 +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';
/**
* Module dependencies.
*/
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 ? false : true,
maxAge: config.debug ? 0 : 60 * 60 * 24 * 7,
alas: {
'/favicon.ico': '/favicon.png'
}
});

View File

@@ -20,21 +20,30 @@ var config = require('../config');
* this.allowSync - allow sync triggle by cnpm install
*/
module.exports = function *syncByInstall(next) {
module.exports = function* syncByInstall(next) {
this.allowSync = false;
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
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
if (this.query.write) {
return yield *next;
return yield* next;
}
var name = this.params.name || this.params[0];
// scoped package dont sync
if (name && name[0] === '@') {
return yield* next;
}
this.allowSync = true;
yield *next;
yield* next;
};

View File

@@ -14,20 +14,40 @@
* Module dependencies.
*/
module.exports = function *notFound(next) {
yield *next;
var debug = require('debug')('cnpmjs.org:middleware:web_not_found');
if (this.status) {
module.exports = function* notFound(next) {
yield* next;
if (this.status && this.status !== 404) {
return;
}
if (this.body) {
return;
}
var m = /^\/([\w\-\_\.]+)\/?$/.exec(this.url);
var m = /^\/([\w\-\.]+)\/?$/.exec(this.path);
if (!m) {
// scoped packages
m = /^\/(@[\w\-\.]+\/[\w\-\.]+)$/.exec(this.path);
}
debug('%s match %j', this.url, m);
if (m) {
return this.redirect('/package/' + m[1]);
}
// package not found
m = /\/package\/([\w\-\_\.]+)\/?$/.exec(this.url);
var name = null;
var title = '404: Page Not Found';
if (m) {
name = m[1];
title = name + ' Not Found';
}
this.status = 404;
this.type = 'text/html';
this.charset = 'utf-8';
this.body = 'Cannot GET ' + this.path;
yield* this.render('404', {
title: title,
name: name
});
};

View File

@@ -0,0 +1,159 @@
/**!
* cnpmjs.org - models/_module_maintainer_class_methods.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
/**
* list all module names by user
* @param {String} user
*/
exports.listModuleNamesByUser = function* (user) {
var rows = yield this.findAll({
attributrs: ['name'],
where: {
user: user
}
});
return rows.map(function (row) {
return row.name;
});
};
/**
* list all maintainers of module `name`
* @param {String} name
*/
exports.listMaintainers = function* (name) {
var rows = yield this.findAll({
attributrs: ['user'],
where: {
name: 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) {
var row = yield this.find({
where: {
user: user,
name: name
}
});
if (!row) {
row = yield this.build({
user: user,
name: 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: name,
user: users,
}
});
};
/**
* remove all maintainers for module `name`
* @param {String} name
*/
exports.removeAllMaintainers = function* (name) {
yield this.destroy({
where: {
name: 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: []
};
}
var exists = yield* this.listMaintainers(name);
var addUsers = users.filter(function (username) {
// add user which in `users` but do not in `exists`
return exists.indexOf(username) === -1;
});
var 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
};
};

64
models/dist_dir.js Normal file
View File

@@ -0,0 +1,64 @@
/**!
* cnpmjs.org - models/dist_dir.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 `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',
`parent` varchar(200) NOT NULL COMMENT 'parent dir' DEFAULT '/',
`date` varchar(20) COMMENT '02-May-2014 01:06',
PRIMARY KEY (`id`),
UNIQUE KEY `dist_dir_parent_name` (`parent`, `name`),
KEY `dist_dir_gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='dist dir info';
*/
module.exports = function (sequelize, DataTypes) {
return sequelize.define('DistDir', {
name: {
type: DataTypes.STRING(200),
allowNull: false,
comment: 'dir name',
},
parent: {
type: DataTypes.STRING(200),
allowNull: false,
defaultValue: '/',
comment: 'parent dir',
},
date: {
type: DataTypes.STRING(20),
allowNull: false,
comment: '02-May-2014 01:06'
}
}, {
tableName: 'dist_dir',
comment: 'dist dir info',
indexes: [
{
unique: true,
fields: ['parent', 'name']
},
{
fields: ['gmt_modified']
}
],
classMethods: {
}
});
};

82
models/dist_file.js Normal file
View File

@@ -0,0 +1,82 @@
/**!
* cnpmjs.org - models/dist_file.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 `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',
`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',
`sha1` varchar(40) COMMENT 'sha1 hex value',
`url` varchar(2048),
PRIMARY KEY (`id`),
UNIQUE KEY `dist_file_parent_name` (`parent`, `name`),
KEY `dist_file_gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='dist file info';
*/
module.exports = function (sequelize, DataTypes) {
return sequelize.define('DistFile', {
name: {
type: DataTypes.STRING(200),
allowNull: false,
comment: 'dir name',
},
parent: {
type: DataTypes.STRING(200),
allowNull: false,
defaultValue: '/',
comment: 'parent dir',
},
date: {
type: DataTypes.STRING(20),
allowNull: false,
comment: '02-May-2014 01:06'
},
size: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: 'file size'
},
sha1: {
type: DataTypes.STRING(40),
allowNull: false,
comment: 'sha1 hex value'
},
url: {
type: DataTypes.STRING(2048),
allowNull: false
}
}, {
tableName: 'dist_file',
comment: 'dist file info',
indexes: [
{
unique: true,
fields: ['parent', 'name']
},
{
fields: ['gmt_modified']
}
],
classMethods: {
}
});
};

59
models/download_total.js Normal file
View File

@@ -0,0 +1,59 @@
/**!
* cnpmjs.org - models/download_total.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 `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 `download_total_date_name` (`date`, `name`)
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module download total info';
module.exports = function (sequelize, DataTypes) {
return sequelize.define('DownloadTotal', {
date: {
type: DataTypes.STRING(10),
allowNull: false,
comment: 'YYYY-MM-DD format',
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'module name',
},
count: {
type: DataTypes.BIGINT(20).UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: 'download count',
}
}, {
tableName: 'download_total',
comment: 'module download total info',
indexes: [
{
unique: true,
fields: ['date', 'name']
}
],
classMethods: {
}
});
};

49
models/index.js Normal file
View File

@@ -0,0 +1,49 @@
/**!
* cnpmjs.org - models/index.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var path = require('path');
var sequelize = require('../common/sequelize');
function load(name) {
return sequelize.import(path.join(__dirname, name));
}
module.exports = {
sequelize: 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'),
DistFile: load('dist_file'),
DistDir: load('dist_dir'),
query: function* (sql, args) {
return yield this.sequelize.query(sql, null, {raw: true}, args);
},
queryOne: function* (sql, args) {
var rows = yield* this.query(sql, args);
return rows && rows[0];
}
};

48
models/init_script.js Normal file
View File

@@ -0,0 +1,48 @@
/**!
* 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.
*/
var config = require('../config');
config.database.logging = console.log;
// $ node --harmony models/init_script.js <force> <dialect>
var force = process.argv[2] === 'true';
var dialect = process.argv[3];
if (dialect) {
config.database.dialect = dialect;
}
var models = require('./');
models.sequelize.sync({ force: force })
.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);
throw err;
});

110
models/module.js Normal file
View File

@@ -0,0 +1,110 @@
/**!
* cnpmjs.org - models/module.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var utils = require('./utils');
/*
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.UNSIGNED,
allowNull: false,
defaultValue: 0,
},
publish_time: {
type: DataTypes.BIGINT(20).UNSIGNED,
allowNull: true,
}
}, {
tableName: 'module',
comment: 'module info',
indexes: [
{
unique: true,
fields: ['name', 'version']
},
{
fields: ['gmt_modified']
},
{
fields: ['publish_time']
},
{
fields: ['author']
}
],
classMethods: {
findByNameAndVersion: function* (name, version) {
return yield this.find({
where: { name: name, version: version }
});
}
}
});
};

58
models/module_deps.js Normal file
View File

@@ -0,0 +1,58 @@
/**!
* 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: {
}
});
};

69
models/module_keyword.js Normal file
View File

@@ -0,0 +1,69 @@
/**!
* 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: function* (keyword, name) {
return yield this.find({
where: {
keyword: keyword,
name: name
}
});
}
}
});
};

56
models/module_log.js Normal file
View File

@@ -0,0 +1,56 @@
/**!
* 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

@@ -0,0 +1,56 @@
/**!
* 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'),
});
};

57
models/module_star.js Normal file
View File

@@ -0,0 +1,57 @@
/**!
* 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: {
}
});
};

View File

@@ -0,0 +1,84 @@
/**!
* cnpmjs.org - models/module_unpublished.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var utils = require('./utils');
/*
CREATE TABLE IF NOT EXISTS `module_unpublished` (
`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',
`package` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'base info: tags, time, maintainers, description, versions',
PRIMARY KEY (`id`),
UNIQUE KEY `module_unpublished_name` (`name`),
KEY `module_unpublished_gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module unpublished info';
*/
module.exports = function (sequelize, DataTypes) {
return sequelize.define('ModuleUnpublished', {
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'module name',
},
package: {
type: DataTypes.LONGTEXT,
comment: 'base info: tags, time, maintainers, description, versions',
get: utils.JSONGetter('package'),
set: utils.JSONSetter('package'),
}
}, {
tableName: 'module_unpublished',
comment: 'module unpublished info',
indexes: [
{
unique: true,
fields: ['name']
},
{
fields: ['gmt_modified']
}
],
classMethods: {
findByName: function* (name) {
return yield this.find({
where: {
name: name
}
});
},
save: function* (name, pkg) {
var row = yield this.find({
where: {
name: name
}
});
if (row) {
row.package = pkg;
return yield row.save(['package']);
}
row = this.build({
name: name,
package: pkg,
});
return yield row.save();
}
}
});
};

View File

@@ -0,0 +1,56 @@
/**!
* cnpmjs.org - models/npm_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 `npm_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 `npm_module_maintainer_user_name` (`user`,`name`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='npm original module maintainers';
*/
module.exports = function (sequelize, DataTypes) {
return sequelize.define('NpmModuleMaintainer', {
user: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'user name'
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'module name',
}
}, {
tableName: 'npm_module_maintainer',
comment: 'npm original module maintainers',
updatedAt: false,
indexes: [
{
unique: true,
fields: ['user', 'name']
},
{
fields: ['name']
}
],
classMethods: require('./_module_maintainer_class_methods'),
});
};

72
models/tag.js Normal file
View File

@@ -0,0 +1,72 @@
/**!
* cnpmjs.org - models/tag.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 `tag` (
`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',
`tag` varchar(30) NOT NULL COMMENT 'tag name',
`version` varchar(30) NOT NULL COMMENT 'module version',
`module_id` bigint(20) unsigned NOT NULL COMMENT 'module id',
PRIMARY KEY (`id`),
UNIQUE KEY `tag_name_tag` (`name`, `tag`),
KEY `tag_gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module tag';
*/
module.exports = function (sequelize, DataTypes) {
return sequelize.define('Tag', {
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'module name',
},
tag: {
type: DataTypes.STRING(30),
allowNull: false,
comment: 'tag name',
},
version: {
type: DataTypes.STRING(30),
allowNull: false,
comment: 'module version',
},
module_id: {
type: DataTypes.BIGINT(20).UNSIGNED,
allowNull: false,
comment: 'module id'
}
}, {
tableName: 'tag',
comment: 'module tag',
indexes: [
{
unique: true,
fields: ['name', 'tag']
},
{
fields: ['gmt_modified']
}
],
classMethods: {
findByNameAndTag: function* (name, tag) {
return yield this.find({ where: { name: name, tag: tag } });
}
}
});
};

118
models/total.js Normal file
View File

@@ -0,0 +1,118 @@
/**!
* cnpmjs.org - models/total.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 `total` (
// `name` varchar(100) NOT NULL COMMENT 'total name',
// `gmt_modified` datetime NOT NULL COMMENT 'modified time',
// `module_delete` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'module delete count',
// `last_sync_time` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'last timestamp sync from official registry',
// `last_exist_sync_time` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'last timestamp sync exist packages from official registry',
// `sync_status` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'system sync from official registry status',
// `need_sync_num` int unsigned NOT NULL DEFAULT '0' COMMENT 'how many packages need to be sync',
// `success_sync_num` int unsigned NOT NULL DEFAULT '0' COMMENT 'how many packages sync success at this time',
// `fail_sync_num` int unsigned NOT NULL DEFAULT '0' COMMENT 'how many packages sync fail at this time',
// `left_sync_num` int unsigned NOT NULL DEFAULT '0' COMMENT 'how many packages left to be sync',
// `last_sync_module` varchar(100) COMMENT 'last sync success module name',
// PRIMARY KEY (`name`)
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='total info';
// -- init `total` count
// INSERT INTO total(name, gmt_modified) VALUES('total', now())
// ON DUPLICATE KEY UPDATE gmt_modified=now();
module.exports = function (sequelize, DataTypes) {
return sequelize.define('Total', {
name: {
type: DataTypes.STRING(100),
primaryKey: true,
comment: 'total name'
},
module_delete: {
type: DataTypes.BIGINT(20).UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: 'module delete count',
},
last_sync_time: {
type: DataTypes.BIGINT(20).UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: 'last timestamp sync from official registry',
},
last_exist_sync_time: {
type: DataTypes.BIGINT(20).UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: 'last timestamp sync exist packages from official registry',
},
sync_status: {
type: 'TINYINT',
allowNull: false,
defaultValue: 0,
comment: 'system sync from official registry status',
},
need_sync_num: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: 'how many packages need to be sync',
},
success_sync_num: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: 'how many packages sync success at this time',
},
fail_sync_num: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: 'how many packages sync fail at this time',
},
left_sync_num: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: 'how many packages left to be sync',
},
last_sync_module: {
type: DataTypes.STRING(100),
allowNull: true,
comment: 'last sync success module name',
},
}, {
tableName: 'total',
comment: 'total info',
createdAt: false,
classMethods: {
init: function (callback) {
var that = this;
that.find({
where: { name: 'total' }
}).then(function (row) {
if (!row) {
that.build({name: 'total'}).save()
.then(function () {
callback();
})
.catch(callback);
return;
}
callback();
}).catch(callback);
}
}
});
};

241
models/user.js Normal file
View File

@@ -0,0 +1,241 @@
/**!
* cnpmjs.org - models/user.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var utility = require('utility');
var utils = require('./utils');
/*
CREATE TABLE IF NOT EXISTS `user` (
`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 'user name',
`salt` varchar(100) NOT NULL,
`password_sha` varchar(100) NOT NULL COMMENT 'user password hash',
`ip` varchar(64) NOT NULL COMMENT 'user last request ip',
`roles` varchar(200) NOT NULL DEFAULT '[]',
`rev` varchar(40) NOT NULL,
`email` varchar(400) NOT NULL,
`json` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'json details',
`npm_user` tinyint(1) DEFAULT '0' COMMENT 'user sync from npm or not, 1: true, other: false',
PRIMARY KEY (`id`),
UNIQUE KEY `user_name` (`name`),
KEY `user_gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='user base info';
*/
module.exports = function (sequelize, DataTypes) {
return sequelize.define('User', {
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'user name',
},
salt: {
type: DataTypes.STRING(100),
allowNull: false,
},
password_sha: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'user password hash',
},
ip: {
type: DataTypes.STRING(64),
allowNull: false,
comment: 'user last request ip',
},
roles: {
type: DataTypes.STRING(200),
allowNull: false,
defaultValue: '[]',
},
rev: {
type: DataTypes.STRING(40),
allowNull: false,
},
email: {
type: DataTypes.STRING(400),
allowNull: false,
},
json: {
type: DataTypes.LONGTEXT,
allowNull: true,
get: utils.JSONGetter('json'),
set: utils.JSONSetter('json'),
},
isNpmUser: {
field: 'npm_user',
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
comment: 'user sync from npm or not, 1: true, other: false',
}
}, {
tableName: 'user',
comment: 'user base info',
indexes: [
{
unique: true,
fields: ['name']
},
{
fields: ['gmt_modified']
}
],
classMethods: {
// utils
createPasswordSha: function (password, salt) {
return utility.sha1(password + salt);
},
// read
auth: function* (name, password) {
var user = yield* this.findByName(name);
if (user) {
var sha = this.createPasswordSha(password, user.salt);
if (user.password_sha !== sha) {
user = null;
}
}
return user;
},
findByName: function* (name) {
return yield this.find({ where: { name: name } });
},
listByNames: function* (names) {
return yield this.findAll({
where: {
name: {
in: names
}
}
});
},
search: function* (query, options) {
return yield this.findAll({
where: {
name: {
like: query + '%'
}
},
limit: options.limit
});
},
// write
saveNpmUser: function* (data) {
var user = yield* this.findByName(data.name);
if (!user) {
user = this.build({
isNpmUser: true,
name: data.name,
salt: '0',
password_sha: '0',
ip: '0',
});
}
user.isNpmUser = true;
user.json = data;
user.email = data.email || '';
user.rev = data._rev || '';
return yield user.save();
},
saveCustomUser: function* (data) {
var name = data.user.login;
var user = yield* this.findByName(name);
if (!user) {
user = this.build({
isNpmUser: false,
name: name,
});
}
var rev = '1-' + data.user.login;
var salt = data.salt || '0';
var passwordSha = data.password_sha || '0';
var ip = data.ip || '0';
user.isNpmUser = false;
user.email = data.user.email;
user.ip = ip;
user.json = data.user;
user.rev = rev;
user.salt = salt;
user.password_sha = passwordSha;
return yield user.save();
},
// add cnpm user
add: function* (user) {
var roles = user.roles || [];
try {
roles = JSON.stringify(roles);
} catch (e) {
roles = '[]';
}
var rev = '1-' + utility.md5(JSON.stringify(user));
var row = this.build({
rev: rev,
name: user.name,
email: user.email,
salt: user.salt,
password_sha: user.password_sha,
ip: user.ip,
roles: roles,
isNpmUser: false,
});
return yield row.save();
},
update: function* (user) {
var rev = user.rev || user._rev;
var revNo = Number(rev.split('-', 1));
if (!revNo) {
var err = new Error(rev + ' format error');
err.name = 'RevFormatError';
err.data = {user: user};
throw err;
}
revNo++;
var newRev = revNo + '-' + utility.md5(JSON.stringify(user));
var roles = user.roles || [];
try {
roles = JSON.stringify(roles);
} catch (e) {
roles = '[]';
}
var row = yield* this.findByName(user.name);
if (!row) {
return null;
}
row.rev = newRev;
row.email = user.email;
row.salt = user.salt;
row.password_sha = user.password_sha;
row.ip = user.ip;
row.roles = roles;
row.isNpmUser = false;
return yield row.save(['rev', 'email', 'salt', 'password_sha', 'ip', 'roles', 'isNpmUser']);
}
}
});
};

34
models/utils.js Normal file
View File

@@ -0,0 +1,34 @@
/**!
* cnpmjs.org - models/utils.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
exports.JSONGetter = function (propertyName) {
return function JSONGetter() {
var value = this.getDataValue(propertyName);
if (value && typeof value === 'string') {
value = JSON.parse(value);
}
return value;
};
};
exports.JSONSetter = function (propertyName) {
return function JSONSetter(value) {
if (typeof value !== 'string') {
value = JSON.stringify(value);
}
this.setDataValue(propertyName, value);
};
};

View File

@@ -1,6 +1,6 @@
{
"name": "cnpmjs.org",
"version": "0.4.1",
"version": "2.0.0-beta5",
"description": "Private npm registry and web for Enterprise, base on MySQL and Simple Store Service",
"main": "index.js",
"scripts": {
@@ -10,53 +10,59 @@
"stop": "./bin/nodejsctl stop"
},
"dependencies": {
"co": "3.0.5",
"co-gather": "0.0.1",
"co-read": "0.0.2",
"co-redis": "1.1.0",
"co-urllib": "0.2.0",
"co-write": "0.3.0",
"copy-to": "0.0.3",
"debug": "0.8.0",
"eventproxy": "0.3.1",
"giturl": "0.0.2",
"graceful": "0.0.6",
"gravatar": "1.0.6",
"humanize-number": "0.0.2",
"koa": "0.5.2",
"koa-limit": "1.0.0",
"koa-markdown": "0.0.3",
"koa-middlewares": "0.1.3",
"logfilestream": "0.1.0",
"marked": "0.3.2",
"microtime": "0.5.1",
"mime": "1.2.11",
"mkdirp": "0.3.5",
"moment": "2.5.1",
"ms": "0.6.2",
"multiline": "0.3.2",
"mysql": "2.1.1",
"nodemailer": "0.6.1",
"qn": "0.2.1",
"ready": "0.1.1",
"redis": "0.10.1",
"semver": "2.2.1",
"thunkify-wrap": "0.1.1",
"utility": "0.1.12"
"agentkeepalive": "~1.2.0",
"bluebird": "~2.3.10",
"bytes": "~1.0.0",
"cfork": "~1.2.1",
"cheerio": "~0.17.0",
"co": "~3.1.0",
"co-defer": "~0.1.1",
"co-gather": "~0.0.1",
"co-redis": "~1.1.0",
"co-sleep": "~0.0.1",
"copy-to": "~2.0.1",
"debug": "~2.1.0",
"error-formater": "~1.0.3",
"fs-cnpm": "~1.1.0",
"giturl": "~0.0.3",
"graceful": "~0.1.0",
"gravatar": "~1.1.0",
"humanize-ms": "~1.0.0",
"humanize-number": "~0.0.2",
"koa": "~0.13.0",
"koa-limit": "~1.0.2",
"koa-markdown": "~1.0.0",
"koa-middlewares": "~1.4.1",
"marked": "~0.3.2",
"mime": "~1.2.11",
"mini-logger": "~1.0.0",
"mkdirp": "~0.5.0",
"moment": "~2.8.3",
"mysql": "~2.5.2",
"nodemailer": "~1.3.0",
"redis": "~0.12.1",
"semver": "~4.1.0",
"sequelize": "~2.0.0-rc2",
"thunkify-wrap": "~1.0.3",
"urllib": "~2.0.2",
"utility": "~1.2.0"
},
"devDependencies": {
"autod": ">=0.0.13",
"chunkstream": "0.0.1",
"co-mocha": "0.0.2",
"autod": "*",
"chunkstream": "*",
"co-mocha": "*",
"contributors": "*",
"cov": "*",
"istanbul-harmony": "*",
"jshint": "*",
"mm": "0.2.1",
"koa-mock": "^1.0.2",
"mm": "*",
"mocha": "*",
"pedding": "0.0.3",
"should": "3.3.0",
"supertest": "0.10.0"
"node-dev": "*",
"pedding": "*",
"should": "~4.0.4",
"should-http": "*",
"sqlite3": "*",
"supertest": "*"
},
"homepage": "https://github.com/cnpm/cnpmjs.org",
"repository": {
@@ -76,7 +82,7 @@
"registry"
],
"engines": {
"node": ">= 0.11.9"
"node": ">= 0.11.14"
},
"author": [
"fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)",

View File

@@ -1,62 +0,0 @@
/**!
* cnpmjs.org - proxy/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 config = require('../config');
var mysql = require('../common/mysql');
var multiline = require('multiline');
var PLUS_SQL = multiline(function () {;/*
INSERT INTO
download_total(gmt_create, gmt_modified, date, name, count)
VALUES
(now(), now(), ?, ?, ?)
ON DUPLICATE KEY UPDATE
count=count + VALUES(count),
name=VALUES(name),
date=VALUES(date);
*/});
exports.plusTotal = function (data, callback) {
mysql.query(PLUS_SQL, [data.date, data.name, data.count], callback);
};
var SELECT_ONE_TOTAL_SQL = multiline(function () {;/*
SELECT
date, count
FROM
download_total
WHERE
date>=? AND date<=? AND name=?;
*/});
exports.getModuleTotal = function (name, start, end, callback) {
mysql.query(SELECT_ONE_TOTAL_SQL, [start, end, name], callback);
};
var SELECT_ALL_TOTAL_SQL = multiline(function () {;/*
SELECT
date, sum(count) AS count
FROM
download_total
WHERE
date>=? AND date<=?
GROUP BY
date;
*/});
exports.getTotal = function (start, end, callback) {
mysql.query(SELECT_ALL_TOTAL_SQL, [start, end], callback);
};
thunkify(exports);

View File

@@ -1,698 +0,0 @@
/**!
* cnpmjs.org - proxy/module.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 utility = require('utility');
var eventproxy = require('eventproxy');
var config = require('../config');
var mysql = require('../common/mysql');
var multiline = require('multiline');
var MODULE_COLUMNS = 'id, publish_time, gmt_create, gmt_modified, author, name, \
version, description, package, dist_tarball, dist_shasum, dist_size';
var INSERT_MODULE_SQL = multiline(function () {;/*
INSERT INTO
module(gmt_create, gmt_modified, publish_time, author, name, version,
package, dist_tarball, dist_shasum, dist_size, description)
VALUES
(now(), now(), ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
gmt_modified=now(),
publish_time=VALUES(publish_time),
description=VALUES(description),
author=VALUES(author),
name=VALUES(name),
version=VALUES(version),
package=VALUES(package),
dist_tarball=VALUES(dist_tarball),
dist_shasum=VALUES(dist_shasum),
dist_size=VALUES(dist_size);
*/});
exports.add = function (mod, callback) {
var keywords = mod.package.keywords;
var pkg;
try {
pkg = stringifyPackage(mod.package);
} catch (e) {
return callback(e);
}
var description = mod.package && mod.package.description || '';
var dist = mod.package.dist || {};
dist.tarball = '';
dist.shasum = '';
dist.size = 0;
var publish_time = mod.publish_time || Date.now();
var values = [
publish_time, mod.author, mod.name, mod.version, pkg,
dist.tarball, dist.shasum, dist.size, description
];
mysql.query(INSERT_MODULE_SQL, values, function (err, result) {
if (err) {
return callback(err);
}
callback(null, {id: result.insertId, gmt_modified: new Date()});
if (typeof keywords === 'string') {
keywords = [keywords];
}
if (!Array.isArray(keywords)) {
return;
}
var words = [];
for (var i = 0; i < keywords.length; i++) {
var w = keywords[i];
if (typeof w === 'string') {
w = w.trim();
if (w) {
words.push(w);
}
}
}
if (words.length === 0) {
return;
}
// add keywords
exports.addKeywords(mod, description, words, utility.noop);
});
};
var GET_KEYWORD_SQL = multiline(function () {;/*
SELECT
keyword
FROM
module_keyword
WHERE
name = ?
ORDER BY
keyword;
*/});
exports.getKeywords = function (name, callback) {
mysql.query(GET_KEYWORD_SQL, [name], function (err, rows) {
var keywords = [];
if (rows && rows.length) {
keywords = rows.map(function (r) {
return r.keyword;
});
}
callback(err, keywords);
});
};
var ADD_KEYWORD_SQL = multiline(function () {;/*
INSERT INTO
module_keyword(gmt_create, keyword, name, description)
VALUES
(now(), ?, ?, ?)
ON DUPLICATE KEY UPDATE
description=VALUES(description);
*/});
exports.addKeywords = function (name, description, keywords, callback) {
var sql = '';
var values = [];
for (var i = 0; i < keywords.length; i++) {
sql += ADD_KEYWORD_SQL;
values.push(keywords[i]);
values.push(name);
values.push(description);
}
mysql.query(sql, values, function (err, results) {
if (err) {
return callback(err);
}
var ids = [];
for (var i = 0; i < results.length; i++) {
var r = results[i];
if (r.insertId) {
ids.push(r.insertId);
}
}
callback(null, ids);
});
};
var UPDATE_DESC_SQL = multiline(function () {;/*
UPDATE
module
SET
description=?
WHERE
id=?;
*/});
exports.updateDescription = function (id, description, callback) {
mysql.query(UPDATE_DESC_SQL, [description, id], callback);
};
var UPDATE_PACKAGE_SQL = multiline(function () {;/*
UPDATE
module
SET
package=?
WHERE
id=?;
*/});
exports.updateReadme = function (id, readme, callback) {
exports.getById(id, function (err, data) {
if (err) {
return callback(err);
}
data.package = data.package || {};
data.package.readme = readme;
var pkg = stringifyPackage(data.package);
mysql.query(UPDATE_PACKAGE_SQL, [pkg, id], callback);
});
};
var UPDATE_DIST_SQL = multiline(function () {;/*
UPDATE
module
SET
publish_time=?,
version=?,
package=?,
dist_tarball=?,
dist_shasum=?,
dist_size=?
WHERE
id=?;
*/});
exports.update = function (mod, callback) {
var pkg;
try {
pkg = stringifyPackage(mod.package);
} catch (e) {
return callback(e);
}
var dist = mod.package.dist;
mysql.query(UPDATE_DIST_SQL,
[mod.publish_time, mod.version, pkg, dist.tarball, dist.shasum, dist.size, mod.id],
function (err, result) {
if (err) {
return callback(err);
}
callback(null, {id: mod.id, gmt_modified: new Date()});
});
};
function parseRow(row) {
if (row && row.package) {
try {
if (row.package.indexOf('%7B%22') === 0) {
// now store package will encodeURIComponent() after JSON.stringify
row.package = decodeURIComponent(row.package);
}
row.package = JSON.parse(row.package);
} catch (e) {
console.warn('parse package error: %s, id: %s version: %s, error: %s', row.name, row.id, row.version, e);
}
}
}
exports.parseRow = parseRow;
function stringifyPackage(pkg) {
return encodeURIComponent(JSON.stringify(pkg));
}
var SELECT_MODULE_BY_ID_SQL = multiline(function () {;/*
SELECT
id, publish_time, gmt_create, gmt_modified, author, name,
version, description, package, dist_tarball, dist_shasum, dist_size
FROM
module
WHERE
id=?;
*/});
exports.getById = function (id, callback) {
id = Number(id);
mysql.queryOne(SELECT_MODULE_BY_ID_SQL, [id], function (err, row) {
if (err || !row) {
return callback(err, row);
}
try {
parseRow(row);
} catch (e) {
e.data = row;
return callback(e);
}
callback(null, row);
});
};
var SELECT_MODULE_SQL = multiline(function () {;/*
SELECT
id, publish_time, gmt_create, gmt_modified, author, name,
version, description, package, dist_tarball, dist_shasum, dist_size
FROM
module
WHERE
name=? AND version=?;
*/});
exports.get = function (name, version, callback) {
mysql.queryOne(SELECT_MODULE_SQL, [name, version], function (err, row) {
if (err || !row) {
return callback(err, row);
}
try {
parseRow(row);
} catch (e) {
e.data = row;
return callback(e);
}
callback(null, row);
});
};
var SELECT_MODULE_ID_SQL = multiline(function () {;/*
SELECT
id
FROM
module
WHERE
name=? AND version=?;
*/});
var INSERT_TAG_SQL = multiline(function () {;/*
INSERT INTO
tag(gmt_create, gmt_modified, name, tag, version, module_id)
VALUES
(now(), now(), ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
gmt_modified=now(),
module_id=VALUES(module_id),
name=VALUES(name),
tag=VALUES(tag),
version=VALUES(version);
*/});
exports.addTag = function (name, tag, version, callback) {
mysql.queryOne(SELECT_MODULE_ID_SQL, [name, version], function (err, row) {
if (err) {
return callback(err);
}
var module_id = row && row.id || 0;
mysql.query(INSERT_TAG_SQL, [name, tag, version, module_id], function (err, result) {
if (err) {
return callback(err);
}
callback(null, {id: result.insertId, gmt_modified: new Date(), module_id: module_id});
});
});
};
var SELECT_TAG_SQL = multiline(function () {;/*
SELECT
tag, version, gmt_modified, module_id
FROM
tag
WHERE
name=? AND tag=?;
*/});
exports.getByTag = function (name, tag, callback) {
mysql.queryOne(SELECT_TAG_SQL, [name, tag], function (err, row) {
if (err || !row) {
return callback(err, row);
}
exports.get(name, row.version, callback);
});
};
var DELETE_TAGS_SQL = multiline(function () {;/*
DELETE FROM
tag
WHERE
name=?;
*/});
exports.removeTags = function (name, callback) {
mysql.query(DELETE_TAGS_SQL, [name], callback);
};
var DELETE_TAGS_BY_IDS_SQL = multiline(function () {;/*
DELETE FROM
tag
WHERE
id in (?);
*/});
exports.removeTagsByIds = function (ids, callback) {
mysql.query(DELETE_TAGS_BY_IDS_SQL, [ids], callback);
};
var SELECT_ALL_TAGS_SQL = multiline(function () {;/*
SELECT
id, tag, version, gmt_modified, module_id
FROM
tag
WHERE
name=?;
*/});
exports.listTags = function (name, callback) {
mysql.query(SELECT_ALL_TAGS_SQL, [name], callback);
};
var SELECT_LATEST_MODULE_SQL = multiline(function () {;/*
SELECT
id, publish_time, gmt_create, gmt_modified, author, name,
version, description, package, dist_tarball, dist_shasum, dist_size
FROM
module
WHERE
name=? AND version <> "next"
ORDER BY
publish_time DESC
LIMIT
1;
*/});
exports.getLatest = function (name, callback) {
exports.getByTag(name, 'latest', function (err, row) {
if (err || row) {
return callback(err, row);
}
// get latest order by id
mysql.queryOne(SELECT_LATEST_MODULE_SQL, [name], function (err, row) {
if (err || !row) {
return callback(err, row);
}
try {
parseRow(row);
} catch (e) {
e.data = row;
return callback(e);
}
callback(null, row);
});
});
};
var LIST_MODULE_SQL = multiline(function () {;/*
SELECT
id, publish_time, gmt_create, gmt_modified, author, name,
version, description, package, dist_tarball, dist_shasum, dist_size
FROM
module
WHERE
name=?
ORDER BY
id DESC;
*/});
exports.listByName = function (name, callback) {
mysql.query(LIST_MODULE_SQL, [name], function (err, rows) {
if (err) {
return callback(err);
}
rows = rows || [];
try {
for (var i = 0; i < rows.length; i++) {
parseRow(rows[i]);
}
} catch (e) {
err = e;
err.data = rows;
}
callback(err, rows);
});
};
var LIST_SINCE_SQLS = [];
LIST_SINCE_SQLS.push(multiline(function () {;/*
SELECT
module_id
FROM
tag
WHERE
tag="latest" AND gmt_modified>?;
*/}));
LIST_SINCE_SQLS.push(multiline(function () {;/*
SELECT
distinct(name)
FROM
module
WHERE
id IN (?);
*/}));
exports.listSince = function (start, callback) {
var ep = eventproxy.create();
ep.fail(callback);
mysql.query(LIST_SINCE_SQLS[0], [new Date(start)], ep.done(function (rows) {
if (!rows || rows.length === 0) {
return callback(null, []);
}
ep.emit('ids', rows.map(function (r) {
return r.module_id;
}));
}));
ep.once('ids', function (ids) {
mysql.query(LIST_SINCE_SQLS[1], [ids], callback);
});
};
var LIST_ALL_NAME_SQL = multiline(function () {;/*
SELECT
distinct(name)
FROM
module;
*/});
exports.listAllNames = function (callback) {
mysql.query(LIST_ALL_NAME_SQL, [], callback);
};
var LIST_SHORT_SQL = multiline(function () {;/*
SELECT
distinct(name)
FROM
tag
ORDER BY
name;
*/});
exports.listShort = function (callback) {
mysql.query(LIST_SHORT_SQL, callback);
};
var LIST_ALL_MODULE_NAMES_SQL = multiline(function () {;/*
SELECT
distinct(name)
FROM
module
ORDER BY
name;
*/});
exports.listAllModuleNames = function (callback) {
mysql.query(LIST_ALL_MODULE_NAMES_SQL, callback);
};
var DELETE_MODULE_BY_NAME_SQL = multiline(function () {;/*
DELETE FROM
module
WHERE
name=?;
*/});
exports.removeByName = function (name, callback) {
mysql.query(DELETE_MODULE_BY_NAME_SQL, [name], callback);
};
var DELETE_MODULE_BY_NAME_AND_VERSIONS_SQL = multiline(function () {;/*
DELETE FROM
module
WHERE
name=? AND version in(?);
*/});
exports.removeByNameAndVersions = function (name, versions, callback) {
mysql.query(DELETE_MODULE_BY_NAME_AND_VERSIONS_SQL, [name, versions], callback);
};
var LIST_BY_AUTH_SQLS = [];
LIST_BY_AUTH_SQLS.push(multiline(function () {;/*
SELECT
distinct(name) AS name
FROM
module
WHERE
author=?
ORDER BY
publish_time DESC
LIMIT
100;
*/}));
LIST_BY_AUTH_SQLS.push(multiline(function () {;/*
SELECT
module_id
FROM
tag
WHERE
tag="latest" AND name IN (?);
*/}));
LIST_BY_AUTH_SQLS.push(multiline(function () {;/*
SELECT
name, description
FROM
module
WHERE
id IN (?)
ORDER BY
publish_time DESC;
*/}));
exports.listByAuthor = function (author, callback) {
var ep = eventproxy.create();
ep.fail(callback);
mysql.query(LIST_BY_AUTH_SQLS[0], [author], ep.done(function (rows) {
if (!rows || rows.length === 0) {
return callback(null, []);
}
ep.emit('names', rows.map(function (r) {
return r.name;
}));
}));
ep.on('names', function (names) {
mysql.query(LIST_BY_AUTH_SQLS[1], [names], ep.done(function (rows) {
if (!rows || rows.length === 0) {
return callback(null, []);
}
ep.emit('ids', rows.map(function (r) {
return r.module_id;
}));
}));
});
ep.on('ids', function (ids) {
mysql.query(LIST_BY_AUTH_SQLS[2], [ids], callback);
});
};
var SEARCH_MODULES_SQL = multiline(function () {;/*
SELECT
module_id
FROM
tag
WHERE
name LIKE ? AND tag="latest"
ORDER BY
name
LIMIT
?;
*/});
var SEARCH_MODULES_BY_KEYWORD_SQL = multiline(function () {;/*
SELECT
name, description
FROM
module_keyword
WHERE
keyword=?
ORDER BY
id DESC
LIMIT
?;
*/});
var QUERY_MODULES_BY_ID_SQL = multiline(function () {;/*
SELECT
name, description
FROM
module
WHERE
id IN (?)
ORDER BY
name;
*/});
exports.search = function (word, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
var limit = options.limit || 100;
word = word.replace(/^%/, ''); //ignore prefix %
var ep = eventproxy.create();
ep.fail(callback);
// search flows:
// 1. prefix search by name
// 2. like search by name
// 3. keyword equal search
var ids = {};
mysql.query(SEARCH_MODULES_SQL, [word + '%', limit ], ep.done(function (rows) {
rows = rows || [];
if (rows.length > 0) {
for (var i = 0; i < rows.length; i++) {
ids[rows[i].module_id] = 1;
}
}
if (rows.length >= 20) {
return ep.emit('ids', Object.keys(ids));
}
mysql.query(SEARCH_MODULES_SQL, [ '%' + word + '%', limit ], ep.done('likeSearch'));
}));
mysql.query(SEARCH_MODULES_BY_KEYWORD_SQL, [ word, limit ], ep.done('keywordRows'));
ep.on('likeSearch', function (rows) {
rows = rows || [];
if (rows.length > 0) {
for (var i = 0; i < rows.length; i++) {
ids[rows[i].module_id] = 1;
}
}
ep.emit('ids', Object.keys(ids));
});
ep.all('ids', 'keywordRows', function (ids, keywordRows) {
keywordRows = keywordRows || [];
var data = {
keywordMatchs: keywordRows,
searchMatchs: []
};
if (ids.length === 0) {
return callback(null, data);
}
mysql.query(QUERY_MODULES_BY_ID_SQL, [ids], ep.done(function (modules) {
data.searchMatchs = modules;
callback(null, data);
}));
});
};
thunkify(exports);
exports.updateMaintainers = function *(id, maintainers) {
var mod = yield exports.getById(id);
mod.package.maintainers = maintainers;
var pkg = stringifyPackage(mod.package);
return yield mysql.query(UPDATE_PACKAGE_SQL, [pkg, id]);
};
var GET_LAST_MODIFIED_MODULE_SQL = multiline(function () {;/*
SELECT
id, gmt_modified
FROM
module
WHERE
name=?
ORDER BY
gmt_modified DESC;
*/});
exports.getLastModified = function *(name) {
var row = yield mysql.queryOne(GET_LAST_MODIFIED_MODULE_SQL, [name]);
return row && row.gmt_modified;
};

View File

@@ -1,58 +0,0 @@
/**!
* cnpmjs.org - proxy/module_deps.js
*
* Copyright(c) 2014
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
"use strict";
/**
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var mysql = require('../common/mysql');
var multiline = require('multiline');
var LIST_DEPS_SQL = multiline(function () {;/*
SELECT
deps
FROM
module_deps
WHERE
name=?;
*/});
exports.list = function (name, callback) {
mysql.query(LIST_DEPS_SQL, [name], callback);
};
var INSERT_DEPS_SQL = multiline(function () {;/*
INSERT INTO
module_deps(gmt_create, name, deps)
VALUES
(now(), ?, ?);
*/});
exports.add = function (name, deps, callback) {
mysql.query(INSERT_DEPS_SQL, [name, deps], function (err, result) {
if (err && err.code === 'ER_DUP_ENTRY') {
err = null;
}
callback(err);
});
};
var DELETE_DEPS_SQL = multiline(function () {;/*
DELETE FROM
module_deps
WHERE
name=? AND deps=?;
*/});
exports.remove = function (name, deps, callback) {
mysql.query(DELETE_DEPS_SQL, [name, deps], callback);
};
thunkify(exports);

View File

@@ -1,64 +0,0 @@
/**!
* cnpmjs.org - proxy/module_log.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 mysql = require('../common/mysql');
var multiline = require('multiline');
var INSERT_LOG_SQL = multiline(function () {;/*
INSERT INTO
module_log(gmt_create, gmt_modified, name, username, log)
VALUES
(now(), now(), ?, ?, "");
*/});
exports.create = function (data, callback) {
mysql.query(INSERT_LOG_SQL, [data.name, data.username], function (err, result) {
if (err) {
return callback(err);
}
callback(null, {id: result.insertId, gmt_modified: new Date()});
});
};
var APPEND_SQL = multiline(function () {;/*
UPDATE
module_log
SET
log=CONCAT(log, ?),
gmt_modified=now()
WHERE
id=?;
*/});
exports.append = function (id, log, callback) {
log = '\n' + log;
mysql.query(APPEND_SQL, [log, id], function (err) {
callback(null, {id: id, gmt_modified: new Date()});
});
};
var SELECT_SQL = multiline(function () {;/*
SELECT
*
FROM
module_log
WHERE
id=?;
*/});
exports.get = function (id, callback) {
mysql.queryOne(SELECT_SQL, [id], callback);
};
thunkify(exports);

View File

@@ -1,73 +0,0 @@
/**!
* cnpmjs.org - proxy/module_star.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var mysql = require('../common/mysql');
var multiline = require('multiline');
var ADD_SQL = multiline(function () {;/*
INSERT iNTO
module_star(name, user)
VALUES
(?, ?);
*/});
exports.add = function *add(name, user) {
try {
yield mysql.query(ADD_SQL, [name, user]);
} catch (err) {
if (err.code !== 'ER_DUP_ENTRY') {
throw err;
}
}
};
var REMOVE_SQL = multiline(function () {;/*
DELETE FROM
module_star
WHERE
name = ? AND user = ?;
*/});
exports.remove = function *(name, user) {
return yield mysql.query(REMOVE_SQL, [name, user]);
};
var LIST_USERS_SQL = multiline(function () {;/*
SELECT
user
FROM
module_star
WHERE
name = ?;
*/});
exports.listUsers = function *(name) {
var rows = yield mysql.query(LIST_USERS_SQL, [name]);
return rows.map(function (r) {
return r.user;
});
};
var LIST_USER_MODULES_SQL = multiline(function () {;/*
SELECT
name
FROM
module_star
WHERE
user = ?;
*/});
exports.listUserModules = function *(user) {
return (yield mysql.query(LIST_USER_MODULES_SQL, [user])).map(function (r) {
return r.name;
});
};

View File

@@ -1,76 +0,0 @@
/**!
* cnpmjs.org - proxy/npm.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var urllib = require('co-urllib');
var config = require('../config');
var USER_AGENT = 'cnpmjs.org/' + config.version + ' ' + urllib.USER_AGENT;
function *request(url, options) {
options = options || {};
options.dataType = options.dataType || 'json';
options.timeout = options.timeout || 120000;
options.headers = {
'user-agent': USER_AGENT
};
url = config.sourceNpmRegistry + url;
var r;
try {
r = yield *urllib.request(url, options);
} catch (err) {
var statusCode = err.status || -1;
var data = err.data || '[empty]';
if (err.name === 'JSONResponseFormatError' && statusCode >= 500) {
err.name = 'NPMServerError';
err.message = 'Status ' + statusCode + ', ' + data.toString();
}
throw err;
}
return r;
}
exports.getUser = function *(name) {
var url = '/-/user/org.couchdb.user:' + name;
var r = yield *request(url);
var data = r.data;
if (data && !data.name) {
data = null;
}
return data;
};
exports.get = function *(name) {
var r = yield *request('/' + name);
var data = r.data;
if (r.status === 404) {
data = null;
}
return data;
};
exports.getAllSince = function *(startkey) {
var r = yield *request('/-/all/since?stale=update_after&startkey=' + startkey, {
timeout: 300000
});
return r.data;
};
exports.getShort = function *() {
var r = yield *request('/-/short', {
timeout: 300000
});
return r.data;
};

View File

@@ -1,716 +0,0 @@
/**!
* cnpmjs.org - proxy/sync_module_worker.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';
/**
* Module dependencies.
*/
var co = require('co');
var gather = require('co-gather');
var thunkify = require('thunkify-wrap');
var debug = require('debug')('cnpmjs.org:proxy:sync_module_worker');
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
var urllib = require('co-urllib');
var utility = require('utility');
var ms = require('ms');
var nfs = require('../common/nfs');
var npm = require('./npm');
var common = require('../lib/common');
var Module = require('./module');
var ModuleDeps = require('./module_deps');
var Log = require('./module_log');
var config = require('../config');
var ModuleStar = require('./module_star');
var User = require('./user');
var USER_AGENT = 'sync.cnpmjs.org/' + config.version + ' ' + urllib.USER_AGENT;
function SyncModuleWorker(options) {
EventEmitter.call(this);
this._logId = options.logId;
this.startName = options.name;
if (!Array.isArray(options.name)) {
options.name = [options.name];
}
this.names = options.name;
// for (var i = 0; i < this.names.length; i++) {
// // ensure package name is lower case
// this.names[i] = this.names[i].toLowerCase();
// }
this.username = options.username;
this.concurrency = options.concurrency || 1;
this._publish = options.publish; // _publish_on_cnpm
this.syncingNames = {};
this.nameMap = {};
this.names.forEach(function (name) {
this.nameMap[name] = true;
}.bind(this));
this.noDep = options.noDep; //do not sync dependences
this.successes = [];
this.fails = [];
}
util.inherits(SyncModuleWorker, EventEmitter);
module.exports = SyncModuleWorker;
SyncModuleWorker.prototype.finish = function () {
debug('syncingNames: %j', this.syncingNames);
if (this._finished || Object.keys(this.syncingNames).length > 0) {
return;
}
this.log('[done] Sync %s module finished, %d success, %d fail\nSuccess: [ %s ]\nFail: [ %s ]',
this.startName,
this.successes.length, this.fails.length,
this.successes.join(', '), this.fails.join(', '));
this.emit('end');
this._finished = true;
};
SyncModuleWorker.prototype.log = function (format, arg1, arg2) {
var str = '[' + utility.YYYYMMDDHHmmss() + '] ' + util.format.apply(util, arguments);
debug(str);
this._logId && Log.append(this._logId, str, utility.noop);
};
SyncModuleWorker.prototype.start = function () {
this.log('user: %s, sync %s worker start, %d concurrency, nodeps: %s, publish: %s',
this.username, this.names[0], this.concurrency, this.noDep, this._publish);
var self = this;
co(function *() {
var arr = [];
for (var i = 0; i < self.concurrency; i++) {
arr.push(self.next(i));
}
yield arr;
})();
};
SyncModuleWorker.prototype.pushSuccess = function (name) {
this.successes.push(name);
this.emit('success', name);
};
SyncModuleWorker.prototype.pushFail = function (name) {
this.fails.push(name);
this.emit('fail', name);
};
SyncModuleWorker.prototype.add = function (name) {
if (this.nameMap[name]) {
return;
}
this.nameMap[name] = true;
this.names.push(name);
this.emit('add', name);
this.log(' add dependencies: %s', name);
};
SyncModuleWorker.prototype.next = function *(concurrencyId) {
var name = this.names.shift();
if (!name) {
return process.nextTick(this.finish.bind(this));
}
var that = this;
that.syncingNames[name] = true;
var pkg;
// get from npm
try {
pkg = yield npm.get(name);
} catch (err) {
// if 404
if (err.res && err.res.statusCode === 404) {
that.pushSuccess(name);
} else {
that.pushFail(name);
}
that.log('[error] [%s] get package error: %s', name, err.stack);
delete that.syncingNames[name];
yield *that.next(concurrencyId);
return;
}
if (!pkg) {
that.log('[error] [%s] get package error: package not exist', name);
delete that.syncingNames[name];
yield that.next(concurrencyId);
return;
}
that.log('[c#%d] [%s] start...', concurrencyId, name);
var versions;
try {
versions = yield that._sync(name, pkg);
} catch (err) {
that.pushFail(name);
that.log('[error] [%s] sync error: %s', name, err.stack);
delete that.syncingNames[name];
yield *that.next(concurrencyId);
return;
}
that.log('[%s] synced success, %d versions: %s',
name, versions.length, versions.join(', '));
that.pushSuccess(name);
delete that.syncingNames[name];
yield that.next(concurrencyId);
};
function *_listStarUsers(modName) {
var users = yield ModuleStar.listUsers(modName);
var userMap = {};
users.forEach(function (user) {
userMap[user] = true;
});
return userMap;
}
function *_addStar(modName, username) {
yield ModuleStar.add(modName, username);
}
function *_saveNpmUser(username) {
var user = yield *npm.getUser(username);
if (!user) {
return;
}
yield User.saveNpmUser(user);
}
SyncModuleWorker.prototype._sync = function *(name, pkg) {
var username = this.username;
var that = this;
var hasModules = false;
var result = yield [
Module.listByName(name),
Module.listTags(name),
_listStarUsers(name)
];
var moduleRows = result[0];
var tagRows = result[1];
var existsStarUsers = result[2];
hasModules = moduleRows.length > 0;
var map = {};
for (var i = 0; i < moduleRows.length; i++) {
var r = moduleRows[i];
if (!r.package || !r.package.dist) {
// package json parse error
continue;
}
if (r.package && r.package._publish_on_cnpm) {
// publish on cnpm, dont sync this version package
that.log(' [%s] publish on local cnpm, don\'t sync', name);
return [];
}
if (r.version === 'next') {
continue;
}
if (!map.latest) {
map.latest = r;
}
map[r.version] = r;
}
var tags = {};
for (var i = 0; i < tagRows.length; i++) {
var r = tagRows[i];
if (!r.module_id) {
// no module_id, need to sync tags
continue;
}
tags[r.tag] = r.version;
}
var missingVersions = [];
var missingTags = [];
var missingDescriptions = [];
var missingReadmes = [];
var missingStarUsers = [];
var npmUsernames = {};
// find out all user names
for (var v in pkg.versions) {
var p = pkg.versions[v];
var maintainers = p.maintainers || [];
if (maintainers && !Array.isArray(maintainers)) {
// http://r.cnpmjs.org/jasmine-node
// TODO: "maintainers": "Martin Häger <martin.haeger@gmail.com>",
maintainers = [maintainers];
}
maintainers.forEach(function (m) {
if (m.name) {
npmUsernames[m.name.toLowerCase()] = 1;
}
});
}
// get the missing star users
var starUsers = pkg.users || {};
for (var k in starUsers) {
if (!existsStarUsers[k]) {
missingStarUsers.push(k);
}
npmUsernames[k.toLowerCase()] = 1;
}
that.log(' [%s] found %d missing star users', name, missingStarUsers.length);
var times = pkg.time || {};
pkg.versions = pkg.versions || {};
var versionNames = Object.keys(times);
if (versionNames.length === 0) {
versionNames = Object.keys(pkg.versions);
}
if (versionNames.length === 0) {
that.log(' [%s] no times and no versions, hasModules: %s', name, hasModules);
if (!hasModules) {
// save a next module
var maintainer = pkg.maintainers && pkg.maintainers[0];
if (maintainer && maintainer.name) {
maintainer = maintainer.name;
}
if (!maintainer) {
maintainer = '-';
}
var nextMod = {
name: name,
version: 'next',
author: maintainer,
package: {
name: name,
version: 'next',
description: pkg.description || '',
readme: pkg.readme || '',
maintainers: pkg.maintainers || {
name: maintainer
},
},
};
try {
var result = yield Module.add(nextMod);
that.log(' [%s] save next module, %j', name, result);
} catch (err) {
that.log(' [%s] save next module error %s', err.message);
}
}
}
var versions = [];
for (var i = 0; i < versionNames.length; i++) {
var v = versionNames[i];
var exists = map[v] || {};
var version = pkg.versions[v];
if (!version || !version.dist || !version.dist.tarball) {
continue;
}
//patch for readme
if (!version.readme) {
version.readme = pkg.readme;
}
var publish_time = times[v];
version.publish_time = publish_time ? Date.parse(publish_time) : null;
if (!version.maintainers || !version.maintainers[0]) {
version.maintainers = pkg.maintainers;
}
var sourceAuthor = version.maintainers && version.maintainers[0] &&
version.maintainers[0].name || exists.author;
if (exists.package && exists.package.dist.shasum === version.dist.shasum &&
exists.author === sourceAuthor) {
// * author make sure equal
// * shasum make sure equal
if ((version.publish_time === exists.publish_time) ||
(!version.publish_time && exists.publish_time)) {
// debug(' [%s] %s publish_time equal: %s, %s',
// name, version.version, version.publish_time, exists.publish_time);
// * publish_time make sure equal
if (exists.description === null && version.description) {
// * make sure description exists
missingDescriptions.push({
id: exists.id,
description: version.description
});
}
if (!exists.package.readme && version.readme) {
// * make sure readme exists
missingReadmes.push({
id: exists.id,
readme: version.readme
});
}
continue;
}
}
versions.push(version);
}
var sourceTags = pkg['dist-tags'] || {};
for (var t in sourceTags) {
var sourceTagVersion = sourceTags[t];
if (sourceTagVersion && tags[t] !== sourceTagVersion) {
missingTags.push([t, sourceTagVersion]);
}
}
if (versions.length === 0) {
that.log(' [%s] all versions are exists', name);
} else {
versions.sort(function (a, b) {
return a.publish_time - b.publish_time;
});
that.log(' [%s] %d versions need to sync', name, versions.length);
}
missingVersions = versions;
var versionNames = [];
var syncIndex = 0;
// sync missing versions
while (missingVersions.length) {
var index = syncIndex++;
var syncModule = missingVersions.shift();
if (!syncModule.dist.tarball) {
continue;
}
try {
var result = yield that._syncOneVersion(index, syncModule);
versionNames.push(syncModule.version);
} catch (err) {
that.log(' [%s:%d] error, version: %s, %s: %s',
syncModule.name, index, syncModule.version, err.name, err.message);
}
}
// sync missing descriptions
function *syncDes() {
if (missingDescriptions.length === 0) {
return;
}
that.log(' [%s] saving %d descriptions', name, missingDescriptions.length);
var res = yield gather(missingDescriptions.map(function (item) {
return Module.updateDescription(item.id, item.description);
}));
for (var i = 0; i < res.length; i++) {
var item = missingDescriptions[i];
var r = res[i];
if (r.error) {
that.log(' save error, id: %s, description: %s, error: %s',
item.id, item.description, r.error.message);
} else {
that.log(' saved, id: %s, description length: %d',
item.id, item.description.length);
}
}
}
// sync missing tags
function *syncTag() {
if (missingTags.length === 0) {
return;
}
that.log(' [%s] adding %d tags', name, missingTags.length);
// sync tags
var res = yield gather(missingTags.map(function (item) {
return Module.addTag(name, item[0], item[1]);
}));
for (var i = 0; i < res.length; i++) {
var item = missingTags[i];
var r = res[i];
if (r.error) {
that.log(' add tag %s:%s error, error: %s',
item.id, item.description, r.error.message);
} else {
that.log(' added tag %s:%s, module_id: %s',
item[0], item[1], r.value && r.value.module_id);
}
}
}
// sycn missing readme
function *syncReadme() {
if (missingReadmes.length === 0) {
return;
}
that.log(' [%s] saving %d readmes', name, missingReadmes.length);
var res = yield gather(missingReadmes.map(function (item) {
return Module.updateReadme(item.id, item.readme);
}));
for (var i = 0; i < res.length; i++) {
var item = missingReadmes[i];
var r = res[i];
if (r.error) {
that.log(' save error, id: %s, error: %s', item.id, r.error.message);
} else {
that.log(' saved, id: %s', item.id);
}
}
}
function *syncMissingUsers() {
var missingUsers = [];
var names = Object.keys(npmUsernames);
if (names.length === 0) {
return;
}
var rows = yield *User.listByNames(names);
var map = {};
rows.forEach(function (r) {
map[r.name] = r;
});
names.forEach(function (username) {
var r = map[username];
if (!r || !r.json) {
missingUsers.push(username);
}
});
if (missingUsers.length === 0) {
that.log(' [%s] all %d npm users exists', name, names.length);
return;
}
that.log(' [%s] saving %d/%d missing npm users: %j',
name, missingUsers.length, names.length, missingUsers);
var res = yield gather(missingUsers.map(function (username) {
return _saveNpmUser(username);
}));
for (var i = 0; i < res.length; i++) {
var r = res[i];
if (r.error) {
that.log(' save npm user error, %s', r.error.message);
}
}
}
// sync missing star users
function *syncMissingStarUsers() {
if (missingStarUsers.length === 0) {
return;
}
that.log(' [%s] saving %d star users', name, missingStarUsers.length);
var res = yield gather(missingStarUsers.map(function (username) {
return _addStar(name, username);
}));
for (var i = 0; i < res.length; i++) {
var r = res[i];
if (r.error) {
that.log(' add star user error, %s', r.error.message);
}
}
}
yield [syncDes(), syncTag(), syncReadme(), syncMissingStarUsers(), syncMissingUsers()];
return versionNames;
};
SyncModuleWorker.prototype._syncOneVersion = function *(versionIndex, sourcePackage) {
var that = this;
var username = this.username;
var downurl = sourcePackage.dist.tarball;
var filename = path.basename(downurl);
var filepath = common.getTarballFilepath(filename);
var ws = fs.createWriteStream(filepath);
var options = {
writeStream: ws,
followRedirect: true,
timeout: 600000, // 10 minutes download
headers: {
'user-agent': USER_AGENT
}
};
var dependencies = Object.keys(sourcePackage.dependencies || {});
var devDependencies = Object.keys(sourcePackage.devDependencies || {});
that.log(' [%s:%d] syncing, version: %s, dist: %j, no deps: %s, ' +
'publish on cnpm: %s, dependencies: %d, devDependencies: %d',
sourcePackage.name, versionIndex, sourcePackage.version,
sourcePackage.dist, that.noDep, that._publish,
dependencies.length, devDependencies.length);
if (dependencies.length > config.maxDependencies) {
dependencies = dependencies.slice(0, config.maxDependencies);
}
if (devDependencies.length > config.maxDependencies) {
devDependencies = devDependencies.slice(0, config.maxDependencies);
}
if (!that.noDep) {
for (var i = 0; i < dependencies.length; i++) {
that.add(dependencies[i]);
}
for (var i = 0; i < devDependencies.length; i++) {
that.add(devDependencies[i]);
}
}
// add module dependence
try {
yield dependencies.map(function (depName) {
return ModuleDeps.add(depName, sourcePackage.name);
});
} catch (err) {
// ignore
}
var shasum = crypto.createHash('sha1');
var dataSize = 0;
try {
// get tarball
var r = yield *urllib.request(downurl, options);
var statusCode = r.status || -1;
if (statusCode === 404) {
shasum = sourcePackage.dist.shasum;
return yield afterUpload({
url: downurl
});
}
if (statusCode !== 200) {
var err = new Error('Download ' + downurl + ' fail, status: ' + statusCode);
err.name = 'DownloadTarballError';
err.data = sourcePackage;
throw err;
}
// read and check
var rs = fs.createReadStream(filepath);
rs.on('data', function (data) {
shasum.update(data);
dataSize += data.length;
});
var end = thunkify.event(rs);
yield end(); // after end event emit
// check shasum
shasum = shasum.digest('hex');
if (shasum !== sourcePackage.dist.shasum) {
var err = new Error('Download ' + downurl + ' shasum:' + shasum +
' not match ' + sourcePackage.dist.shasum);
err.name = 'DownloadTarballShasumError';
err.data = sourcePackage;
throw err;
}
options = {
key: common.getCDNKey(sourcePackage.name, filename),
size: dataSize,
shasum: shasum
};
// upload to NFS
var result = yield nfs.upload(filepath, options);
return yield afterUpload(result);
} finally {
// remove tmp file whatever
fs.unlink(filepath, utility.noop);
}
function *afterUpload(result) {
//make sure sync module have the correct author info
//only if can not get maintainers, use the username
var author = username;
if (Array.isArray(sourcePackage.maintainers)) {
author = sourcePackage.maintainers[0].name || username;
}
var mod = {
version: sourcePackage.version,
name: sourcePackage.name,
package: sourcePackage,
author: author,
publish_time: sourcePackage.publish_time,
};
// delete _publish_on_cnpm, because other cnpm maybe sync from current cnpm
delete mod.package._publish_on_cnpm;
if (that._publish) {
// sync as publish
mod.package._publish_on_cnpm = true;
}
var dist = {
shasum: shasum,
size: dataSize,
noattachment: dataSize === 0,
};
if (result.url) {
dist.tarball = result.url;
} else if (result.key) {
dist.key = result.key;
dist.tarball = result.key;
}
mod.package.dist = dist;
var r = yield Module.add(mod);
that.log(' [%s:%s] done, insertId: %s, author: %s, version: %s, ' +
'size: %d, publish_time: %j, publish on cnpm: %s',
sourcePackage.name, versionIndex,
r.id,
author, mod.version, dataSize,
new Date(mod.publish_time),
that._publish);
return r;
}
};
SyncModuleWorker.sync = function *(name, username, options) {
options = options || {};
var pkg = yield npm.get(name);
if (!pkg || !pkg._rev) {
return {
ok: false,
pkg: pkg
};
}
var result = yield Log.create({name: name, username: username});
var worker = new SyncModuleWorker({
logId: result.id,
name: name,
username: username,
noDep: options.noDep,
publish: options.publish,
});
worker.start();
return {
ok: true,
logId: result.id,
pkg: pkg
};
};

View File

@@ -1,170 +0,0 @@
/**!
* cnpmjs.org - proxy/total.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 eventproxy = require('eventproxy');
var config = require('../config');
var mysql = require('../common/mysql');
var multiline = require('multiline');
var DB_SIZE_SQL = multiline(function () {;/*
SELECT
TABLE_NAME AS name, data_length, index_length
FROM
information_schema.tables
WHERE
TABLE_SCHEMA = ?
GROUP BY
TABLE_NAME
ORDER BY
data_length DESC
LIMIT
0, 200;
*/});
var TOTAL_MODULE_SQL = 'SELECT count(distinct(name)) AS count FROM module;';
var TOTAL_VERSION_SQL = 'SELECT count(name) AS count FROM module;';
var TOTAL_USER_SQL = 'SELECT count(name) AS count FROM user;';
var TOTAL_INFO_SQL = 'SELECT * FROM total WHERE name="total";';
exports.get = function (callback) {
var ep = eventproxy.create();
ep.fail(callback);
mysql.queryOne(TOTAL_MODULE_SQL, ep.done('module'));
mysql.queryOne(TOTAL_USER_SQL, ep.done('user'));
mysql.queryOne(TOTAL_VERSION_SQL, ep.done('version'));
mysql.query(DB_SIZE_SQL, [config.mysqlDatabase], ep.done('db_sizes'));
mysql.queryOne(TOTAL_INFO_SQL, ep.done('info'));
ep.all('db_sizes', 'module', 'version', 'user', 'info',
function (sizes, mc, vc, uc, info) {
info = info || {};
var total = {
data_tables: {},
disk_size: 0,
data_size: 0,
index_size: 0,
disk_format_version: 0,
committed_update_seq: 0,
update_seq: 0,
purge_seq: 0,
compact_running: false,
doc_count: mc.count,
doc_del_count: info.module_delete || 0,
doc_version_count: vc.count,
user_count: uc.count,
store_engine: 'mysql',
sync_status: info.sync_status,
need_sync_num: info.need_sync_num || 0,
success_sync_num: info.success_sync_num || 0,
fail_sync_num: info.fail_sync_num || 0,
left_sync_num: info.left_sync_num || 0,
last_sync_time: info.last_sync_time || 0,
last_exist_sync_time: info.last_exist_sync_time || 0,
last_sync_module: info.last_sync_module || '',
};
for (var i = 0; i < sizes.length; i++) {
var row = sizes[i];
total.data_tables[row.name] = {
data_size: row.data_length,
index_size: row.index_length,
};
total.data_size += row.data_length;
total.index_size += row.index_length;
}
total.disk_size = total.data_size + total.index_size;
callback(null, total);
});
};
var PLUS_DELETE_MODULE_SQL = multiline(function () {;/*
UPDATE
total
SET
module_delete=module_delete+1
WHERE
name="total";
*/});
exports.plusDeleteModule = function (callback) {
mysql.query(PLUS_DELETE_MODULE_SQL, callback);
};
exports.getTotalInfo = function (callback) {
mysql.queryOne(TOTAL_INFO_SQL, callback);
};
var SET_LAST_SYNC_TIME_SQL = multiline(function () {;/*
UPDATE
total
SET
last_sync_time=?
WHERE
name="total";
*/});
exports.setLastSyncTime = function (time, callback) {
mysql.query(SET_LAST_SYNC_TIME_SQL, Number(time), callback);
};
var SET_LAST_EXIST_SYNC_TIME_SQL = multiline(function () {;/*
UPDATE
total
SET
last_exist_sync_time=?
WHERE
name="total";
*/});
exports.setLastExistSyncTime = function (time, callback) {
mysql.query(SET_LAST_EXIST_SYNC_TIME_SQL, Number(time), callback);
};
var UPDATE_SYNC_STATUS_SQL = multiline(function () {;/*
UPDATE
total
SET
sync_status=?
WHERE
name="total";
*/});
exports.updateSyncStatus = function (status, callback) {
mysql.query(UPDATE_SYNC_STATUS_SQL, [status], callback);
};
var UPDATE_SYNC_NUM_SQL = multiline(function () {;/*
UPDATE
total
SET
sync_status = ?,
need_sync_num = ?,
success_sync_num = ?,
fail_sync_num = ?,
left_sync_num = ?,
last_sync_module = ?
WHERE
name="total";
*/});
exports.updateSyncNum = function (params, callback) {
var query = [
params.syncStatus, params.need || 0,
params.success || 0, params.fail || 0, params.left || 0,
params.lastSyncModule,
];
mysql.query(UPDATE_SYNC_NUM_SQL, query, callback);
};
thunkify(exports);

View File

@@ -1,161 +0,0 @@
/**!
* cnpmjs.org - proxy/user.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 utility = require('utility');
var config = require('../config');
var mysql = require('../common/mysql');
var multiline = require('multiline');
var SELECT_USER_SQL = multiline(function () {;/*
SELECT
id, rev, name, email, salt, password_sha, ip,
roles, json, npm_user, gmt_create, gmt_modified
FROM
user
WHERE
name=?;
*/});
exports.get = function (name, callback) {
mysql.queryOne(SELECT_USER_SQL, [name], function (err, row) {
if (row) {
try {
row.roles = row.roles ? JSON.parse(row.roles) : [];
} catch (e) {
row.roles = [];
}
try {
row.json = row.json ? JSON.parse(row.json) : null;
} catch (e) {
row.json = null;
}
}
callback(err, row);
});
};
function passwordSha(password, salt) {
return utility.sha1(password + salt);
}
exports.auth = function (name, password, callback) {
exports.get(name, function (err, row) {
if (err || !row) {
return callback(err, row);
}
var sha = passwordSha(password, row.salt);
if (row.password_sha !== sha) {
row = null;
}
callback(null, row);
});
};
var INSERT_USER_SQL = multiline(function () {;/*
INSERT INTO
user(rev, name, email, salt, password_sha,
ip, roles, gmt_create, gmt_modified)
VALUES
(?, ?, ?, ?, ?, ?, ?, now(), now());
*/});
exports.add = function (user, callback) {
var roles = user.roles || [];
try {
roles = JSON.stringify(roles);
} catch (e) {
roles = '[]';
}
var rev = '1-' + utility.md5(JSON.stringify(user));
var values = [rev, user.name, user.email, user.salt, user.password_sha, user.ip, roles];
mysql.query(INSERT_USER_SQL, values, function (err) {
callback(err, {rev: rev});
});
};
var UPDATE_USER_SQL = multiline(function () {;/*
UPDATE
user
SET
rev=?,
email=?,
salt=?,
password_sha=?,
ip=?,
roles=?,
gmt_modified=now()
WHERE
name=? AND rev=?;
*/});
exports.update = function (user, callback) {
var rev = user.rev || user._rev;
var revNo = Number(rev.split('-', 1));
if (!revNo) {
var err = new Error(rev + ' format error');
err.name = 'RevFormatError';
err.data = {user: user};
return callback(err);
}
revNo++;
var newRev = revNo + '-' + utility.md5(JSON.stringify(user));
var roles = user.roles || [];
try {
roles = JSON.stringify(roles);
} catch (e) {
roles = '[]';
}
var values = [newRev, user.email, user.salt, user.password_sha, user.ip, roles, user.name, rev];
mysql.query(UPDATE_USER_SQL, values, function (err, data) {
if (err) {
return callback(err);
}
callback(null, {rev: newRev, result: data});
});
};
thunkify(exports);
exports.passwordSha = passwordSha;
exports.saveNpmUser = function *(user) {
var sql = 'SELECT id, json FROM user WHERE name=?;';
var row = yield mysql.queryOne(sql, [user.name]);
if (!row) {
sql = 'INSERT INTO user(npm_user, json, rev, name, email, salt, password_sha, ip, gmt_create, gmt_modified) \
VALUES(1, ?, ?, ?, ?, "0", "0", "0", now(), now());';
yield mysql.query(sql, [JSON.stringify(user), user._rev, user.name, user.email]);
} else {
sql = 'UPDATE user SET json=?, rev=? WHERE id=?;';
yield mysql.query(sql, [JSON.stringify(user), user._rev, row.id]);
}
};
var LIST_BY_NAMES_SQL = multiline(function () {;/*
SELECT
id, name, email, json
FROM
user
WHERE
name in (?);
*/});
exports.listByNames = function *(names) {
if (names.length === 0) {
return [];
}
return yield mysql.query(LIST_BY_NAMES_SQL, [names]);
};

View File

@@ -0,0 +1,855 @@
/*
https://github.com/sindresorhus/github-markdown-css/blob/gh-pages/github-markdown.css
*/
@font-face {
font-family: octicons-anchor;
src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAYcAA0AAAAACjQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAABMAAAABwAAAAca8vGTk9TLzIAAAFMAAAARAAAAFZG1VHVY21hcAAAAZAAAAA+AAABQgAP9AdjdnQgAAAB0AAAAAQAAAAEACICiGdhc3AAAAHUAAAACAAAAAj//wADZ2x5ZgAAAdwAAADRAAABEKyikaNoZWFkAAACsAAAAC0AAAA2AtXoA2hoZWEAAALgAAAAHAAAACQHngNFaG10eAAAAvwAAAAQAAAAEAwAACJsb2NhAAADDAAAAAoAAAAKALIAVG1heHAAAAMYAAAAHwAAACABEAB2bmFtZQAAAzgAAALBAAAFu3I9x/Nwb3N0AAAF/AAAAB0AAAAvaoFvbwAAAAEAAAAAzBdyYwAAAADP2IQvAAAAAM/bz7t4nGNgZGFgnMDAysDB1Ml0hoGBoR9CM75mMGLkYGBgYmBlZsAKAtJcUxgcPsR8iGF2+O/AEMPsznAYKMwIkgMA5REMOXicY2BgYGaAYBkGRgYQsAHyGMF8FgYFIM0ChED+h5j//yEk/3KoSgZGNgYYk4GRCUgwMaACRoZhDwCs7QgGAAAAIgKIAAAAAf//AAJ4nHWMMQrCQBBF/0zWrCCIKUQsTDCL2EXMohYGSSmorScInsRGL2DOYJe0Ntp7BK+gJ1BxF1stZvjz/v8DRghQzEc4kIgKwiAppcA9LtzKLSkdNhKFY3HF4lK69ExKslx7Xa+vPRVS43G98vG1DnkDMIBUgFN0MDXflU8tbaZOUkXUH0+U27RoRpOIyCKjbMCVejwypzJJG4jIwb43rfl6wbwanocrJm9XFYfskuVC5K/TPyczNU7b84CXcbxks1Un6H6tLH9vf2LRnn8Ax7A5WQAAAHicY2BkYGAA4teL1+yI57f5ysDNwgAC529f0kOmWRiYVgEpDgYmEA8AUzEKsQAAAHicY2BkYGB2+O/AEMPCAAJAkpEBFbAAADgKAe0EAAAiAAAAAAQAAAAEAAAAAAAAKgAqACoAiAAAeJxjYGRgYGBhsGFgYgABEMkFhAwM/xn0QAIAD6YBhwB4nI1Ty07cMBS9QwKlQapQW3VXySvEqDCZGbGaHULiIQ1FKgjWMxknMfLEke2A+IJu+wntrt/QbVf9gG75jK577Lg8K1qQPCfnnnt8fX1NRC/pmjrk/zprC+8D7tBy9DHgBXoWfQ44Av8t4Bj4Z8CLtBL9CniJluPXASf0Lm4CXqFX8Q84dOLnMB17N4c7tBo1AS/Qi+hTwBH4rwHHwN8DXqQ30XXAS7QaLwSc0Gn8NuAVWou/gFmnjLrEaEh9GmDdDGgL3B4JsrRPDU2hTOiMSuJUIdKQQayiAth69r6akSSFqIJuA19TrzCIaY8sIoxyrNIrL//pw7A2iMygkX5vDj+G+kuoLdX4GlGK/8Lnlz6/h9MpmoO9rafrz7ILXEHHaAx95s9lsI7AHNMBWEZHULnfAXwG9/ZqdzLI08iuwRloXE8kfhXYAvE23+23DU3t626rbs8/8adv+9DWknsHp3E17oCf+Z48rvEQNZ78paYM38qfk3v/u3l3u3GXN2Dmvmvpf1Srwk3pB/VSsp512bA/GG5i2WJ7wu430yQ5K3nFGiOqgtmSB5pJVSizwaacmUZzZhXLlZTq8qGGFY2YcSkqbth6aW1tRmlaCFs2016m5qn36SbJrqosG4uMV4aP2PHBmB3tjtmgN2izkGQyLWprekbIntJFing32a5rKWCN/SdSoga45EJykyQ7asZvHQ8PTm6cslIpwyeyjbVltNikc2HTR7YKh9LBl9DADC0U/jLcBZDKrMhUBfQBvXRzLtFtjU9eNHKin0x5InTqb8lNpfKv1s1xHzTXRqgKzek/mb7nB8RZTCDhGEX3kK/8Q75AmUM/eLkfA+0Hi908Kx4eNsMgudg5GLdRD7a84npi+YxNr5i5KIbW5izXas7cHXIMAau1OueZhfj+cOcP3P8MNIWLyYOBuxL6DRylJ4cAAAB4nGNgYoAALjDJyIAOWMCiTIxMLDmZedkABtIBygAAAA==) format('woff');
}
.markdown-body {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
color: #333;
overflow: hidden;
font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif;
font-size: 16px;
line-height: 1.6;
word-wrap: break-word;
}
.markdown-body a {
background: transparent;
}
.markdown-body a:active,
.markdown-body a:hover {
outline: 0;
}
.markdown-body strong {
font-weight: bold;
}
.markdown-body h1 {
font-size: 2em;
margin: 0.67em 0;
}
.markdown-body img {
border: 0;
}
.markdown-body hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
.markdown-body pre {
overflow: auto;
}
.markdown-body code,
.markdown-body kbd,
.markdown-body pre {
font-family: monospace, monospace;
font-size: 1em;
}
.markdown-body input {
color: inherit;
font: inherit;
margin: 0;
}
.markdown-body html input[disabled] {
cursor: default;
}
.markdown-body input {
line-height: normal;
}
.markdown-body input[type="checkbox"] {
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 0;
}
.markdown-body table {
border-collapse: collapse;
border-spacing: 0;
}
.markdown-body td,
.markdown-body th {
padding: 0;
}
.markdown-body * {
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.markdown-body input {
font: 13px/1.4 Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol";
}
.markdown-body a {
color: #4183c4;
text-decoration: none;
}
.markdown-body a:hover,
.markdown-body a:focus,
.markdown-body a:active {
text-decoration: underline;
}
.markdown-body hr {
height: 0;
margin: 15px 0;
overflow: hidden;
background: transparent;
border: 0;
border-bottom: 1px solid #ddd;
}
.markdown-body hr:before {
display: table;
content: "";
}
.markdown-body hr:after {
display: table;
clear: both;
content: "";
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin-top: 15px;
margin-bottom: 15px;
line-height: 1.1;
}
.markdown-body h1 {
font-size: 30px;
}
.markdown-body h2 {
font-size: 21px;
}
.markdown-body h3 {
font-size: 16px;
}
.markdown-body h4 {
font-size: 14px;
}
.markdown-body h5 {
font-size: 12px;
}
.markdown-body h6 {
font-size: 11px;
}
.markdown-body blockquote {
margin: 0;
}
.markdown-body ul,
.markdown-body ol {
padding: 0;
margin-top: 0;
margin-bottom: 0;
}
.markdown-body ol ol,
.markdown-body ul ol {
list-style-type: lower-roman;
}
.markdown-body ul ul ol,
.markdown-body ul ol ol,
.markdown-body ol ul ol,
.markdown-body ol ol ol {
list-style-type: lower-alpha;
}
.markdown-body dd {
margin-left: 0;
}
.markdown-body code {
font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
.markdown-body pre {
margin-top: 0;
margin-bottom: 0;
font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
.markdown-body .octicon {
font: normal normal 16px octicons-anchor;
line-height: 1;
display: inline-block;
text-decoration: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.markdown-body .octicon-link:before {
content: '\f05c';
}
.markdown-body>*:first-child {
margin-top: 0 !important;
}
.markdown-body>*:last-child {
margin-bottom: 0 !important;
}
.markdown-body .anchor {
position: absolute;
top: 0;
bottom: 0;
left: 0;
display: block;
padding-right: 6px;
padding-left: 30px;
margin-left: -30px;
}
.markdown-body .anchor:focus {
outline: none;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
position: relative;
margin-top: 1em;
margin-bottom: 16px;
font-weight: bold;
line-height: 1.4;
}
.markdown-body h1 .octicon-link,
.markdown-body h2 .octicon-link,
.markdown-body h3 .octicon-link,
.markdown-body h4 .octicon-link,
.markdown-body h5 .octicon-link,
.markdown-body h6 .octicon-link {
display: none;
color: #000;
vertical-align: middle;
}
.markdown-body h1:hover .anchor,
.markdown-body h2:hover .anchor,
.markdown-body h3:hover .anchor,
.markdown-body h4:hover .anchor,
.markdown-body h5:hover .anchor,
.markdown-body h6:hover .anchor {
padding-left: 8px;
margin-left: -30px;
line-height: 1;
text-decoration: none;
}
.markdown-body h1:hover .anchor .octicon-link,
.markdown-body h2:hover .anchor .octicon-link,
.markdown-body h3:hover .anchor .octicon-link,
.markdown-body h4:hover .anchor .octicon-link,
.markdown-body h5:hover .anchor .octicon-link,
.markdown-body h6:hover .anchor .octicon-link {
display: inline-block;
}
.markdown-body h1 {
padding-bottom: 0.3em;
font-size: 2.25em;
line-height: 1.2;
border-bottom: 1px solid #eee;
}
.markdown-body h2 {
padding-bottom: 0.3em;
font-size: 1.75em;
line-height: 1.225;
border-bottom: 1px solid #eee;
}
.markdown-body h3 {
font-size: 1.5em;
line-height: 1.43;
}
.markdown-body h4 {
font-size: 1.25em;
}
.markdown-body h5 {
font-size: 1em;
}
.markdown-body h6 {
font-size: 1em;
color: #777;
}
.markdown-body p,
.markdown-body blockquote,
.markdown-body ul,
.markdown-body ol,
.markdown-body dl,
.markdown-body table,
.markdown-body pre {
margin-top: 0;
margin-bottom: 16px;
}
.markdown-body hr {
height: 4px;
padding: 0;
margin: 16px 0;
background-color: #e7e7e7;
border: 0 none;
}
.markdown-body ul,
.markdown-body ol {
padding-left: 2em;
}
.markdown-body ul ul,
.markdown-body ul ol,
.markdown-body ol ol,
.markdown-body ol ul {
margin-top: 0;
margin-bottom: 0;
}
.markdown-body li>p {
margin-top: 16px;
}
.markdown-body dl {
padding: 0;
}
.markdown-body dl dt {
padding: 0;
margin-top: 16px;
font-size: 1em;
font-style: italic;
font-weight: bold;
}
.markdown-body dl dd {
padding: 0 16px;
margin-bottom: 16px;
}
.markdown-body blockquote {
padding: 0 15px;
color: #777;
border-left: 4px solid #ddd;
}
.markdown-body blockquote>:first-child {
margin-top: 0;
}
.markdown-body blockquote>:last-child {
margin-bottom: 0;
}
.markdown-body table {
display: block;
width: 100%;
overflow: auto;
word-break: normal;
word-break: keep-all;
}
.markdown-body table th {
font-weight: bold;
}
.markdown-body table th,
.markdown-body table td {
padding: 6px 13px;
border: 1px solid #ddd;
}
.markdown-body table tr {
background-color: #fff;
border-top: 1px solid #ccc;
}
.markdown-body table tr:nth-child(2n) {
background-color: #f8f8f8;
}
.markdown-body img {
max-width: 100%;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.markdown-body code {
padding: 0;
padding-top: 0.2em;
padding-bottom: 0.2em;
margin: 0;
font-size: 85%;
background-color: rgba(0,0,0,0.04);
border-radius: 3px;
}
.markdown-body code:before,
.markdown-body code:after {
letter-spacing: -0.2em;
content: "\00a0";
}
.markdown-body pre>code {
padding: 0;
margin: 0;
font-size: 100%;
word-break: normal;
white-space: pre;
background: transparent;
border: 0;
}
.markdown-body .highlight {
margin-bottom: 16px;
}
.markdown-body .highlight pre,
.markdown-body pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f7f7f7;
border-radius: 3px;
}
.markdown-body .highlight pre {
margin-bottom: 0;
word-break: normal;
}
.markdown-body pre {
word-wrap: normal;
}
.markdown-body pre code {
display: inline;
max-width: initial;
padding: 0;
margin: 0;
overflow: initial;
line-height: inherit;
word-wrap: normal;
background-color: transparent;
border: 0;
}
.markdown-body pre code:before,
.markdown-body pre code:after {
content: normal;
}
.markdown-body .highlight {
background: #fff;
}
.markdown-body .highlight .h {
color: #333;
font-style: normal;
font-weight: normal;
}
.markdown-body .highlight .mf,
.markdown-body .highlight .mh,
.markdown-body .highlight .mi,
.markdown-body .highlight .mo,
.markdown-body .highlight .il,
.markdown-body .highlight .m {
color: #945277;
}
.markdown-body .highlight .s,
.markdown-body .highlight .sb,
.markdown-body .highlight .sc,
.markdown-body .highlight .sd,
.markdown-body .highlight .s2,
.markdown-body .highlight .se,
.markdown-body .highlight .sh,
.markdown-body .highlight .si,
.markdown-body .highlight .sx,
.markdown-body .highlight .s1 {
color: #df5000;
}
.markdown-body .highlight .kc,
.markdown-body .highlight .kd,
.markdown-body .highlight .kn,
.markdown-body .highlight .kp,
.markdown-body .highlight .kr,
.markdown-body .highlight .kt,
.markdown-body .highlight .k,
.markdown-body .highlight .o {
font-weight: bold;
}
.markdown-body .highlight .kt {
color: #458;
}
.markdown-body .highlight .c,
.markdown-body .highlight .cm,
.markdown-body .highlight .c1 {
color: #998;
font-style: italic;
}
.markdown-body .highlight .cp,
.markdown-body .highlight .cs,
.markdown-body .highlight .cp .h {
color: #999;
font-weight: bold;
}
.markdown-body .highlight .cs {
font-style: italic;
}
.markdown-body .highlight .n {
color: #333;
}
.markdown-body .highlight .na,
.markdown-body .highlight .nv,
.markdown-body .highlight .vc,
.markdown-body .highlight .vg,
.markdown-body .highlight .vi {
color: #008080;
}
.markdown-body .highlight .nb {
color: #0086B3;
}
.markdown-body .highlight .nc {
color: #458;
font-weight: bold;
}
.markdown-body .highlight .no {
color: #094e99;
}
.markdown-body .highlight .ni {
color: #800080;
}
.markdown-body .highlight .ne {
color: #990000;
font-weight: bold;
}
.markdown-body .highlight .nf {
color: #945277;
font-weight: bold;
}
.markdown-body .highlight .nn {
color: #555;
}
.markdown-body .highlight .nt {
color: #000080;
}
.markdown-body .highlight .err {
color: #a61717;
background-color: #e3d2d2;
}
.markdown-body .highlight .gd {
color: #000;
background-color: #fdd;
}
.markdown-body .highlight .gd .x {
color: #000;
background-color: #faa;
}
.markdown-body .highlight .ge {
font-style: italic;
}
.markdown-body .highlight .gr {
color: #aa0000;
}
.markdown-body .highlight .gh {
color: #999;
}
.markdown-body .highlight .gi {
color: #000;
background-color: #dfd;
}
.markdown-body .highlight .gi .x {
color: #000;
background-color: #afa;
}
.markdown-body .highlight .go {
color: #888;
}
.markdown-body .highlight .gp {
color: #555;
}
.markdown-body .highlight .gs {
font-weight: bold;
}
.markdown-body .highlight .gu {
color: #800080;
font-weight: bold;
}
.markdown-body .highlight .gt {
color: #aa0000;
}
.markdown-body .highlight .ow {
font-weight: bold;
}
.markdown-body .highlight .w {
color: #bbb;
}
.markdown-body .highlight .sr {
color: #017936;
}
.markdown-body .highlight .ss {
color: #8b467f;
}
.markdown-body .highlight .bp {
color: #999;
}
.markdown-body .highlight .gc {
color: #999;
background-color: #EAF2F5;
}
.markdown-body kbd {
background-color: #e7e7e7;
background-image: -webkit-linear-gradient(#fefefe, #e7e7e7);
background-image: linear-gradient(#fefefe, #e7e7e7);
background-repeat: repeat-x;
display: inline-block;
padding: 3px 5px;
font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace;
line-height: 10px;
color: #000;
border: 1px solid #cfcfcf;
border-radius: 2px;
}
.markdown-body .highlight .pl-coc,
.markdown-body .highlight .pl-entm,
.markdown-body .highlight .pl-eoa,
.markdown-body .highlight .pl-mai .pl-sf,
.markdown-body .highlight .pl-pdv,
.markdown-body .highlight .pl-sc,
.markdown-body .highlight .pl-sr,
.markdown-body .highlight .pl-v,
.markdown-body .highlight .pl-vpf {
color: #0086b3;
}
.markdown-body .highlight .pl-eoac,
.markdown-body .highlight .pl-mdht,
.markdown-body .highlight .pl-mi1,
.markdown-body .highlight .pl-mri,
.markdown-body .highlight .pl-va,
.markdown-body .highlight .pl-vpu {
color: #008080;
}
.markdown-body .highlight .pl-c,
.markdown-body .highlight .pl-pdc {
color: #b4b7b4;
font-style: italic;
}
.markdown-body .highlight .pl-k,
.markdown-body .highlight .pl-ko,
.markdown-body .highlight .pl-kolp,
.markdown-body .highlight .pl-mc,
.markdown-body .highlight .pl-mr,
.markdown-body .highlight .pl-ms,
.markdown-body .highlight .pl-s,
.markdown-body .highlight .pl-sok,
.markdown-body .highlight .pl-st {
color: #6e5494;
}
.markdown-body .highlight .pl-ef,
.markdown-body .highlight .pl-enf,
.markdown-body .highlight .pl-enm,
.markdown-body .highlight .pl-entc,
.markdown-body .highlight .pl-eoi,
.markdown-body .highlight .pl-sf,
.markdown-body .highlight .pl-smc {
color: #d12089;
}
.markdown-body .highlight .pl-ens,
.markdown-body .highlight .pl-eoai,
.markdown-body .highlight .pl-kos,
.markdown-body .highlight .pl-mh .pl-pdh,
.markdown-body .highlight .pl-mp,
.markdown-body .highlight .pl-pde,
.markdown-body .highlight .pl-stp {
color: #458;
}
.markdown-body .highlight .pl-enti {
color: #d12089;
font-weight: bold;
}
.markdown-body .highlight .pl-cce,
.markdown-body .highlight .pl-enc,
.markdown-body .highlight .pl-kou,
.markdown-body .highlight .pl-mq {
color: #f93;
}
.markdown-body .highlight .pl-mp1 .pl-sf {
color: #458;
font-weight: bold;
}
.markdown-body .highlight .pl-cos,
.markdown-body .highlight .pl-ent,
.markdown-body .highlight .pl-md,
.markdown-body .highlight .pl-mdhf,
.markdown-body .highlight .pl-ml,
.markdown-body .highlight .pl-pdc1,
.markdown-body .highlight .pl-pds,
.markdown-body .highlight .pl-s1,
.markdown-body .highlight .pl-scp,
.markdown-body .highlight .pl-sol {
color: #df5000;
}
.markdown-body .highlight .pl-c1,
.markdown-body .highlight .pl-cn,
.markdown-body .highlight .pl-pse,
.markdown-body .highlight .pl-pse .pl-s2,
.markdown-body .highlight .pl-vi {
color: #a31515;
}
.markdown-body .highlight .pl-mb,
.markdown-body .highlight .pl-pdb {
color: #df5000;
font-weight: bold;
}
.markdown-body .highlight .pl-mi,
.markdown-body .highlight .pl-pdi {
color: #6e5494;
font-style: italic;
}
.markdown-body .highlight .pl-ms1 {
background-color: #f5f5f5;
}
.markdown-body .highlight .pl-mdh,
.markdown-body .highlight .pl-mdi {
font-weight: bold;
}
.markdown-body .highlight .pl-mdr {
color: #0086b3;
font-weight: bold;
}
.markdown-body .highlight .pl-s2 {
color: #333;
}
.markdown-body .highlight .pl-ii {
background-color: #df5000;
color: #fff;
}
.markdown-body .highlight .pl-ib {
background-color: #f93;
}
.markdown-body .highlight .pl-id {
background-color: #a31515;
color: #fff;
}
.markdown-body .highlight .pl-iu {
background-color: #b4b7b4;
}
.markdown-body .highlight .pl-mo {
color: #969896;
}
.markdown-body .task-list-item {
list-style-type: none;
}
.markdown-body .task-list-item+.task-list-item {
margin-top: 3px;
}
.markdown-body .task-list-item input {
float: left;
margin: 0.3em 0 0.25em -1.6em;
vertical-align: middle;
}

140
public/css/github.css Normal file
View File

@@ -0,0 +1,140 @@
/* GitHub Theme */
.prettyprint {
background: white;
font-family: Menlo, 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Monaco, Consolas, monospace;
font-size: 12px;
line-height: 1.5;
border: 1px solid #ccc;
padding: 10px;
}
.pln {
color: #333333;
}
@media screen {
.str {
color: #dd1144;
}
.kwd {
color: #333333;
}
.com {
color: #999988;
}
.typ {
color: #445588;
}
.lit {
color: #445588;
}
.pun {
color: #333333;
}
.opn {
color: #333333;
}
.clo {
color: #333333;
}
.tag {
color: navy;
}
.atn {
color: teal;
}
.atv {
color: #dd1144;
}
.dec {
color: #333333;
}
.var {
color: teal;
}
.fun {
color: #990000;
}
}
@media print, projection {
.str {
color: #006600;
}
.kwd {
color: #006;
font-weight: bold;
}
.com {
color: #600;
font-style: italic;
}
.typ {
color: #404;
font-weight: bold;
}
.lit {
color: #004444;
}
.pun, .opn, .clo {
color: #444400;
}
.tag {
color: #006;
font-weight: bold;
}
.atn {
color: #440044;
}
.atv {
color: #006600;
}
}
/* Specify class=linenums on a pre to get line numbering */
ol.linenums {
margin-top: 0;
margin-bottom: 0;
}
/* IE indents via margin-left */
li.L0,
li.L1,
li.L2,
li.L3,
li.L4,
li.L5,
li.L6,
li.L7,
li.L8,
li.L9 {
/* */
}
/* Alternate shading for lines */
li.L1,
li.L3,
li.L5,
li.L7,
li.L9 {
/* */
}

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