├── .gitignore ├── LICENSE ├── README.md ├── app.js ├── app ├── controllers │ ├── todos_controller.js │ └── users_controller.js ├── middlewares │ ├── check_api_token.js │ └── msgpack_json.js ├── models │ ├── todo.js │ └── user.js ├── routes │ ├── api │ │ ├── index.js │ │ ├── todo.js │ │ └── users.js │ ├── index.js │ └── users.js ├── services │ └── todos_service.js └── views │ ├── error.jade │ ├── index.jade │ ├── layouts │ └── layout.jade │ └── users ├── bin └── www ├── config ├── log4js.json └── mongodb.js ├── db.js ├── doc ├── moajs.md ├── routes.png └── wechat.jpg ├── gulpfile.js ├── init.js ├── migrate └── bill_in_account.js ├── package.json ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico └── robots.txt └── test └── controller ├── test.js └── users_controller.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | log 3 | logs 4 | *.log 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | config/redis.js 30 | config/kue.js 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Moajs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Moa-api 2 | 3 | 技术栈 4 | 5 | - [base2(mirco kernel)](https://github.com/base-n/base2-core) 6 | - [mongoose](https://github.com/Automattic/mongoose) 7 | - [bluebird](https://github.com/petkaantonov/bluebird) 8 | - [res.api](https://github.com/moajs/res.api) 9 | 10 | ## 前后端分离实践 11 | 12 | - 前端:[moa-frontend](https://github.com/moajs/moa-frontend) 和 [moa-h5](https://github.com/moajs/moa-h5) 13 | - public下面的采用nginx做反向代理 14 | - 其他的采用express+jade精简代码(ajax与后端交互) 15 | - 后端:[moa-api](https://github.com/moajs/moa-api) 16 | 17 | ## Features 18 | 19 | - 自动加载路由 20 | - 支持mongodb配置 21 | - 集成mongoosedao,快速写crud等dao接口 22 | - 自带用户管理 23 | - 使用jsonwebtoken做用户鉴权 24 | - 支持migrate测试 25 | - 支持mocha测试 26 | - 默认集成res.api,便于写接口 27 | - 集成supervisor,代码变动,自动重载 28 | - gulp自动监控文件变动,跑测试 29 | - gulp routes生成路由说明 30 | - 使用log4js记录日志 31 | - 集成kue队列[需要使用mount-queues插件] 32 | ## Starter 33 | 34 | 前置条件,保证以下服务可以正常访问 35 | 36 | - mongodb 37 | 38 | 具体操作如下 39 | 40 | ``` 41 | git clone --depth=1 https://github.com/moajs/moa-api.git api 42 | 43 | cd api 44 | npm install 45 | export MOA_PORT=3040 && npm start 46 | ``` 47 | 48 | 访问地址即可 49 | 50 | http://127.0.0.1:3040/api 51 | 52 | ## 开发流程 53 | 54 | - 确定models内容,如果是已有库或已有模型,可以直接使用 55 | - 编写接口文档 56 | - 通过migrate来测试model里的方法(如果测试熟悉,可以直接写测试) 57 | - 通过supertest来测试接口(R层)是否合法 58 | - 通过mocha测试其他业务代码(C层、S层、M层) 59 | 60 | ## 扩展 61 | 62 | - 集成zeromq(https://github.com/i5ting/mqpush) 63 | 64 | ## RCSM分层思想 65 | 66 | ### R = routes 67 | 68 | 路由层,和express的一样,唯一不一样的是只要是在app/routes下面的js都会自动挂载到路由上。 69 | 70 | 比如app/routes/user.js,它的访问地址是 71 | 72 | http://127.0.0.1:3000/user 73 | 74 | 比如app/routes/api/user.js,它的访问地址是 75 | 76 | http://127.0.0.1:3000/api/user 77 | 78 | 然后路由里面的子地址,参照express路由写法即可。 79 | 80 | 典型用法是 81 | 82 | ``` 83 | var express = require('express'); 84 | var router = express.Router(); 85 | 86 | var $ = require('mount-controllers')(__dirname).users_controller; 87 | 88 | /* GET users listing. */ 89 | router.get('/login', $.api.login); 90 | 91 | router.get('/register', function(req, res, next) { 92 | return res.api(200,{ 93 | a:'register' 94 | }); 95 | }); 96 | 97 | module.exports = router; 98 | ``` 99 | 100 | 从使用上来说,`router.get('/login', $.api.login);`这个是最合理最常用的的。 101 | 102 | 但如果是逻辑非常简单的路由,随便写写也无妨 103 | 104 | ### C = controllers 105 | 106 | 控制层,主要是负责接口处理结果如何返回,异常如何处理等逻辑控制,不处理具体逻辑 107 | 108 | ``` 109 | var users_service = require('mount-services')(__dirname).users_service; 110 | 111 | // -- custom api 112 | exports.api = { 113 | login: function (req, res, next) { 114 | // var user_id = req.api_user._id; 115 | 116 | var is_valid = users_service.login_valid('sang', '000000'); 117 | 118 | if(is_valid == true){ 119 | return res.api(200,{ 120 | a:'login true' 121 | }); 122 | }else{ 123 | return res.api(200,{ 124 | a:'login false ' 125 | }); 126 | } 127 | } 128 | } 129 | ``` 130 | 131 | 说明:控制层的代码只会在R层里用到 132 | 133 | ### S = services 134 | 135 | 业务逻辑层,通常业务比较负责才会用到业务逻辑层的,如果是单表能处理的,就没有必要使用services层了,所以S层通常是多个models操作的业务逻辑,为了逻辑清晰,以及防止C层膨胀,和耦合,S层很多时候是必要的。 136 | 137 | 说明:S层只会出现在C层代码里,是对多个models操作的封装。 138 | 139 | ### M = models 140 | 141 | 模型层,也就是我们常说的dao层,即data access object,这里采用mongoose + mongoosedao完成model层建模 142 | 143 | 说明:M层可能出现在S层或C层,不允许出现在其他位置 144 | 145 | 146 | ## auth权限 147 | 148 | ### 用户管理 149 | 150 | http://127.0.0.1:3000/users 151 | 152 | ### 鉴权接口 153 | 154 | 155 | 获取token作为以后的api授权凭证 156 | 157 | 获取请求api接口,可以通过header或参数授权 158 | 159 | //检查post的信息或者url查询参数或者头信息 160 | var token = req.body.token || req.query.token || req.headers['x-access-token']; 161 | 162 | 163 | 下面给出参数的测试方式 164 | 165 | ``` 166 | ➜ moa-api git:(master) ✗ curl -d "username=sang&password=000000" http://127.0.0.1:3000/api/auth 167 | {"success":true,"message":"Enjoy your token!","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NWMxOWZkZTNkYWMxZGViMDhjNDM4ODkiLCJ1c2VybmFtZSI6InNhbmciLCJwYXNzd29yZCI6IjAwMDAwMCIsImF2YXRhciI6IiIsInBob25lX251bWJlciI6IiIsImFkZHJlc3MiOiIiLCJfX3YiOjB9.ocfeQ_Kx00edNfbwDtpTrxXxotfOAo2a_zni9Ujsxwg"}% 168 | ``` 169 | 170 | 即 171 | 172 | ``` 173 | token = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NWMxOWZkZTNkYWMxZGViMDhjNDM4ODkiLCJ1c2VybmFtZSI6InNhbmciLCJwYXNzd29yZCI6IjAwMDAwMCIsImF2YXRhciI6IiIsInBob25lX251bWJlciI6IiIsImFkZHJlc3MiOiIiLCJfX3YiOjB9.ocfeQ_Kx00edNfbwDtpTrxXxotfOAo2a_zni9Ujsxwg 174 | ``` 175 | 176 | ### R层鉴权示例 177 | 178 | ``` 179 | var express = require('express'); 180 | var router = express.Router(); 181 | 182 | var $middlewares = require('mount-middlewares')(__dirname); 183 | 184 | var $ = require('mount-controllers')(__dirname).users_controller; 185 | 186 | /* GET users listing. */ 187 | router.get('/login', $middlewares.check_api_token, $.api.login); 188 | 189 | module.exports = router; 190 | ``` 191 | 192 | ### 测试获取用户信息接口 193 | 194 | curl http://127.0.0.1:3000/api/users?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NWMxOWZkZTNkYWMxZGViMDhjNDM4ODkiLCJ1c2VybmFtZSI6InNhbmciLCJwYXNzd29yZCI6IjAwMDAwMCIsImF2YXRhciI6IiIsInBob25lX251bWJlciI6IiIsImFkZHJlc3MiOiIiLCJfX3YiOjB9.ocfeQ_Kx00edNfbwDtpTrxXxotfOAo2a_zni9Ujsxwg 195 | 196 | 返回结果 197 | 198 | ``` 199 | // 20150805133902 200 | // http://127.0.0.1:3000/api/users?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NWMxOWZkZTNkYWMxZGViMDhjNDM4ODkiLCJ1c2VybmFtZSI6InNhbmciLCJwYXNzd29yZCI6IjAwMDAwMCIsImF2YXRhciI6IiIsInBob25lX251bWJlciI6IiIsImFkZHJlc3MiOiIiLCJfX3YiOjB9.ocfeQ_Kx00edNfbwDtpTrxXxotfOAo2a_zni9Ujsxwg 201 | 202 | { 203 | "data": { 204 | "users": [ 205 | { 206 | "_id": "55c19fd43dac1deb08c43888", 207 | "username": "12", 208 | "password": "2", 209 | "avatar": "23", 210 | "phone_number": "23", 211 | "address": "", 212 | "__v": 0 213 | }, 214 | { 215 | "_id": "55c19fde3dac1deb08c43889", 216 | "username": "sang", 217 | "password": "000000", 218 | "avatar": "", 219 | "phone_number": "", 220 | "address": "", 221 | "__v": 0 222 | } 223 | ] 224 | }, 225 | "status": { 226 | "code": 0, 227 | "msg": "request success!" 228 | } 229 | } 230 | ``` 231 | 232 | ## queue用法 233 | 234 | 详见 https://github.com/moa-modules/moa-plugin-kue 235 | 236 | ## migrate 237 | 238 | 239 | 当需要确定model里mongoose接口是否正确的时候,可以考虑创建一个migrate 240 | 241 | node migrate/bill_in_account.js 242 | 243 | 如果是复杂的业务逻辑放到测试里。 244 | 245 | ## 使用msgpack序列化json 246 | 247 | 支持使用msgpack序列化json,客户端也需要对应的进行decode。 248 | 249 | 目前默认是不开启的。 250 | 251 | 若要开启,需将process.env.SERIALIZE设为'msgpack':如SERIALIZE=msgpack node bin/www 252 | 253 | ## task 254 | 255 | 256 | ### list api routes 257 | 258 | gulp routes 259 | 260 | ![](doc/routes.png) 261 | 262 | 263 | 杀死进程 264 | 265 | gulp kp 266 | 267 | ### test 268 | 269 | gulp 270 | 271 | 或者下面这样也行 272 | 273 | ➜ moa-api mocha test/controller 274 | 提醒:debug状态连接数据库: 275 | mount routes_folder_path = /Users/sang/workspace/github/moa-api/app/routes 276 | 277 | 278 | GET /users 279 | [mongoose log] Successfully connected to: NaN 280 | mongoose open success 281 | set api header 282 | GET /api/user/login 200 4.529 ms - 40 283 | ✓ respond with json 284 | 285 | 286 | 1 passing (33ms) 287 | 288 | 289 | ### more 290 | 291 | see http://nodeonly.com/2015/06/14/node-restful-api.html 292 | 293 | ## Todo 294 | 295 | - http://bookshelfjs.org/ 296 | 297 | ## Moajs微信用户组 298 | 299 | ![](doc/wechat.jpg) 300 | 301 | 如果加不上,请加微信`shiren1118`,说明加入原因 302 | 303 | ## Contributing 304 | 305 | 1. Fork it 306 | 2. Create your feature branch (`git checkout -b my-new-feature`) 307 | 3. Commit your changes (`git commit -am 'Add some feature'`) 308 | 4. Push to the branch (`git push origin my-new-feature`) 309 | 5. Create new Pull Request 310 | 311 | ## 版本历史 312 | 313 | - v1.0.0 使用base2作为微内核,移除之前express用的依赖 314 | - v0.1.2 增加log4js作为日志管理 315 | - v0.1.0 初始化版本,以express为基础,做的express最佳事件 316 | 317 | ## 欢迎fork和反馈 318 | 319 | - write by `i5ting` shiren1118@126.com 320 | 321 | 如有建议或意见,请在issue提问或邮件 322 | 323 | ## License 324 | 325 | this repo is released under the [MIT 326 | License](http://www.opensource.org/licenses/MIT). 327 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | require('./init') 2 | require('./db') 3 | 4 | var log4js = require('log4js'); 5 | var res_api = require('res.api'); 6 | var log = log4js.getLogger("moa-api"); 7 | 8 | var app = require('base2')({ 9 | // debug: true, 10 | root:__dirname, 11 | "pre": function(app) { 12 | app.use(res_api); 13 | }, 14 | "views": "app/views", 15 | "routes": "app/routes", 16 | "public": "public" 17 | }) 18 | 19 | // use msgpack to serialize json 20 | if (process.env.SERIALIZE === 'msgpack') { 21 | app.use(require('./app/middlewares/msgpack_json')); 22 | } 23 | 24 | // jsonp callback setup 25 | app.set('jsonp callback name', 'callback'); 26 | 27 | // uncomment after placing your favicon in /public 28 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 29 | 30 | // replace morgan with the log4js connect-logger 31 | log4js.configure('config/log4js.json', { reloadSecs: 300 }); 32 | app.use(log4js.connectLogger(log4js.getLogger('http'), { level: 'auto' })); 33 | 34 | // catch 404 and forward to error handler 35 | app.use(function(req, res, next) { 36 | res.api_error_code = 404; 37 | return res.api_error({ 38 | message: 'Not Found' 39 | }); 40 | }); 41 | 42 | // error handlers 43 | 44 | // development error handler 45 | // will print stacktrace 46 | if (app.get('env') === 'development') { 47 | app.use(function(err, req, res, next) { 48 | res.api_error_code = err.status || 500; 49 | return res.api_error({ 50 | message: err.message, 51 | error: err 52 | }); 53 | }); 54 | } 55 | 56 | // production error handler 57 | // no stacktraces leaked to user 58 | app.use(function(err, req, res, next) { 59 | res.api_error_code = err.status || 500; 60 | return res.api_error({ 61 | message: err.message, 62 | error: {} 63 | }); 64 | }); 65 | 66 | module.exports = app; 67 | -------------------------------------------------------------------------------- /app/controllers/todos_controller.js: -------------------------------------------------------------------------------- 1 | var users_service = require('mount-services')(__dirname).todos_service; 2 | 3 | // -- custom api 4 | exports.api = { 5 | login: function (req, res, next) { 6 | // var user_id = req.api_user._id; 7 | 8 | var is_valid = users_service.login_valid('sang', '000000'); 9 | 10 | if(is_valid == true){ 11 | return res.api(200,{ 12 | a:'login true' 13 | }); 14 | }else{ 15 | return res.api(200,{ 16 | a:'login false ' 17 | }); 18 | } 19 | }, 20 | show: function (req, res, next) { 21 | var user_id = req.api_user._id; 22 | var id = req.params.user_id; 23 | 24 | User.getById(id, function (err, user) { 25 | if (err) { 26 | return res.api_error(err); 27 | } 28 | 29 | res.api({ 30 | user : user 31 | }); 32 | }); 33 | } 34 | } -------------------------------------------------------------------------------- /app/controllers/users_controller.js: -------------------------------------------------------------------------------- 1 | ../../node_modules/moa-plugin-user/app/controllers/users_controller.js -------------------------------------------------------------------------------- /app/middlewares/check_api_token.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Moajs Middle 3 | * Copyright(c) 2015-2019 Alfred Sang 4 | * MIT Licensed 5 | */ 6 | 7 | var jwt = require('jsonwebtoken');//用来创建和确认用户信息摘要 8 | // 检查用户会话 9 | module.exports = function(req, res, next) { 10 | if (process.env.moas) { 11 | req.api_user={ 12 | _id : "55d8702d5472aa887b45f68c" 13 | } 14 | console.log('当前使用moas运行,不使用token即可访问!'); 15 | return next(); 16 | } 17 | 18 | console.log('检查post的信息或者url查询参数或者头信息'); 19 | //检查post的信息或者url查询参数或者头信息 20 | var token = req.body.token || req.query.token || req.headers['x-access-token']; 21 | // 解析 token 22 | if (token) { 23 | // 确认token 24 | jwt.verify(token, 'app.get(superSecret)', function(err, decoded) { 25 | if (err) { 26 | return res.json({ success: false, message: 'token信息错误.' }); 27 | } else { 28 | // 如果没问题就把解码后的信息保存到请求中,供后面的路由使用 29 | req.api_user = decoded; 30 | console.dir(req.api_user); 31 | next(); 32 | } 33 | }); 34 | } else { 35 | // 如果没有token,则返回错误 36 | return res.status(403).send({ 37 | success: false, 38 | message: '没有提供token!' 39 | }); 40 | } 41 | }; -------------------------------------------------------------------------------- /app/middlewares/msgpack_json.js: -------------------------------------------------------------------------------- 1 | var msgpack = require('msgpack5')(); 2 | 3 | module.exports = function (req, res, next) { 4 | res._json = res.json; 5 | 6 | res.json = function (obj) { 7 | if (!this.get('Content-Type')) this.set('Content-Type', 'application/x-msgpack'); 8 | res.send(msgpack.encode(obj)); 9 | }; 10 | 11 | next(); 12 | }; 13 | -------------------------------------------------------------------------------- /app/models/todo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by alfred on July 1st 2015, 11:52:49 pm. 3 | */ 4 | 5 | var mongoose = require('mongoose'); 6 | var Schema = mongoose.Schema; 7 | var MongooseDao = require('mongoosedao'); 8 | 9 | var todoSchema = new Schema( 10 | {"todoname":"String","password":"String","avatar":"String","phone_number":"String","address":"String"} 11 | ); 12 | 13 | todoSchema.methods.is_exist = function(cb) { 14 | var query; 15 | query = { 16 | todoname: this.todoname, 17 | password: this.password 18 | }; 19 | return this.model('todo').findOne(query, cb); 20 | }; 21 | 22 | var todo = mongoose.model('todo', todoSchema); 23 | var todoDao = new MongooseDao(todo); 24 | 25 | module.exports = todoDao; -------------------------------------------------------------------------------- /app/models/user.js: -------------------------------------------------------------------------------- 1 | ../../node_modules/moa-plugin-user/app/models/user.js -------------------------------------------------------------------------------- /app/routes/api/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var log = require('log4js').getLogger("api/index"); 4 | 5 | var User = require('../../models/user'); 6 | 7 | var jwt = require('jsonwebtoken');//用来创建和确认用户信息摘要 8 | 9 | var $middlewares = require('mount-middlewares')(__dirname); 10 | // log.info($middlewares); 11 | 12 | router.get('/', function(req, res, next) { 13 | res.json({ 14 | a:1 15 | }); 16 | }); 17 | 18 | // auth 19 | router.post('/auth', function(req, res, next) { 20 | log.info(req.body); 21 | 22 | User.one({username: req.body.username},function(err, user){ 23 | if (err) throw err; 24 | log.info(user); 25 | 26 | if (!user) { 27 | res.json({ success: false, message: '认证失败,用户名找不到' }); 28 | } else if (user) { 29 | 30 | // 检查密码 31 | if (user.password != req.body.password) { 32 | res.json({ success: false, message: '认证失败,密码错误' }); 33 | } else { 34 | // 创建token 35 | var token = jwt.sign(user, 'app.get(superSecret)', { 36 | 'expiresInMinutes': 1440 // 设置过期时间 37 | }); 38 | 39 | // json格式返回token 40 | res.json({ 41 | success: true, 42 | message: 'Enjoy your token!', 43 | token: token 44 | }); 45 | } 46 | } 47 | }); 48 | }); 49 | 50 | 51 | module.exports = router; 52 | -------------------------------------------------------------------------------- /app/routes/api/todo.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var log = require('log4js').getLogger("api/todo"); 4 | 5 | var $middlewares = require('mount-middlewares')(__dirname); 6 | 7 | var $ = require('mount-controllers')(__dirname).todos_controller; 8 | 9 | /* GET users listing. */ 10 | // router.get('/login', $middlewares.check_api_token, $.api.login); 11 | 12 | router.get('/register', function(req, res, next) { 13 | return res.api(200,{ 14 | a:'register' 15 | }); 16 | }); 17 | 18 | router.get('/:user_id', $middlewares.check_api_token, $.api.show); 19 | 20 | module.exports = router; 21 | -------------------------------------------------------------------------------- /app/routes/api/users.js: -------------------------------------------------------------------------------- 1 | ../../../node_modules/moa-plugin-user/app/routes/api/users.js -------------------------------------------------------------------------------- /app/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | var $middlewares = require('mount-middlewares')(__dirname); 5 | 6 | /* GET home page. */ 7 | router.get('/', function(req, res, next) { 8 | res.render('index', { title: '欢迎使用Moajs-Api' }); 9 | }); 10 | 11 | module.exports = router; 12 | -------------------------------------------------------------------------------- /app/routes/users.js: -------------------------------------------------------------------------------- 1 | ../../node_modules/moa-plugin-user/app/routes/users.js -------------------------------------------------------------------------------- /app/services/todos_service.js: -------------------------------------------------------------------------------- 1 | // multi models operation 2 | 3 | exports.login_valid = function (name, password) { 4 | // var user_id = req.api_user._id; 5 | return true; 6 | } 7 | -------------------------------------------------------------------------------- /app/views/error.jade: -------------------------------------------------------------------------------- 1 | extends layouts/layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /app/views/index.jade: -------------------------------------------------------------------------------- 1 | extends layouts/layout 2 | 3 | block content 4 | h1= title 5 | p Welcome to #{title} -------------------------------------------------------------------------------- /app/views/layouts/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | link(href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet") 7 | body 8 | block content 9 | script(src="http://libs.baidu.com/jquery/1.9.0/jquery.js") 10 | script(src="http://libs.baidu.com/bootstrap/3.0.3/js/bootstrap.min.js") 11 | script(src='/javascripts/form2obj.js') 12 | script(src='/javascripts/app.js') -------------------------------------------------------------------------------- /app/views/users: -------------------------------------------------------------------------------- 1 | ../../node_modules/moa-plugin-user/app/views/users -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('xbm-warehouse-api:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.MOA_PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | console.log('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /config/log4js.json: -------------------------------------------------------------------------------- 1 | { 2 | "appenders": [ 3 | { 4 | "type": "clustered", 5 | "appenders": [ 6 | { 7 | "type": "dateFile", 8 | "filename": "logs/access.log", 9 | "pattern": "-yyyy-MM-dd", 10 | "category": "http" 11 | }, 12 | { 13 | "type": "file", 14 | "filename": "logs/app.log", 15 | "maxLogSize": 10485760, 16 | "numBackups": 3 17 | }, 18 | { 19 | "type": "logLevelFilter", 20 | "level": "ERROR", 21 | "appender": { 22 | "type": "file", 23 | "filename": "logs/errors.log" 24 | } 25 | } 26 | ] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /config/mongodb.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "host": "127.0.0.1", 3 | "port": "27017", 4 | "db": "moa-api", 5 | "is_debug":true 6 | }; 7 | -------------------------------------------------------------------------------- /db.js: -------------------------------------------------------------------------------- 1 | var connectionString, db, mongoose, options; 2 | mongoose = require('mongoose'); 3 | 4 | var config = require('./config/mongodb'); 5 | var port = config.port; 6 | var db = config.db; 7 | var host; 8 | 9 | var is_debug = config.is_debug; 10 | 11 | if(is_debug) { 12 | console.log('\033[32m提醒:debug状态连接数据库:\033[39m'); 13 | host = config.host; 14 | }else{ 15 | console.log('\033[91m警告:非debug状态连接数据库:\033[39m'); 16 | host = config.host; 17 | } 18 | 19 | connectionString = 'mongodb://' + host + ':' + port + '/' + db + ''; 20 | 21 | options = { 22 | db: { 23 | native_parser: true 24 | }, 25 | server: { 26 | auto_reconnect: true, 27 | poolSize: 5 28 | } 29 | }; 30 | 31 | console.log(connectionString); 32 | 33 | mongoose.connect(connectionString, options, function(err, res) { 34 | if (err) { 35 | console.log('[mongoose log] Error connecting to: ', +connectionString + '. ' + err); 36 | return process.exit(1); 37 | } else { 38 | return console.log('[mongoose log] Successfully connected to: ', +connectionString); 39 | } 40 | }); 41 | 42 | db = mongoose.connection; 43 | 44 | db.on('error', console.error.bind(console, 'mongoose connection error:')); 45 | 46 | db.once('open', function() { 47 | return console.log('mongoose open success'); 48 | }); 49 | 50 | 51 | module.exports = db; 52 | -------------------------------------------------------------------------------- /doc/moajs.md: -------------------------------------------------------------------------------- 1 | # Moajs框架演进之路 2 | 3 | 大部分框架的演进之路大体都是一样的 4 | 5 | - 实现(土方法) 6 | - 实践(项目实操) 7 | - 看齐最佳实践 8 | - 不断封装、造轮子 9 | - 走出自己的特色 10 | 11 | 下面简单的介绍一下moajs的演进之路 12 | 13 | ## 什么是Moajs? 14 | 15 | moajs是我司开发的开源nodejs web框架。 16 | 17 | https://github.com/moajs 18 | 19 | 目前主要 20 | 21 | - moa-api 22 | - moa-frontend 23 | - moa-h5 24 | - mongoosedao 25 | - res.api 26 | - mount-routes 27 | 28 | moa是作为名词是恐鸟(一种新西兰无翼大鸟,现已灭绝) https://en.wikipedia.org/wiki/Moa 29 | 30 | ![](https://raw.githubusercontent.com/moajs/moa/dev/doc/moa.jpg) 31 | 32 | ## 阶段0:原始的Expressjs 33 | 34 | 最开始的时候都是使用express-generator生成的项目骨架 35 | 36 | ``` 37 | ➜ test express express-test 38 | 39 | create : express-test 40 | create : express-test/package.json 41 | create : express-test/app.js 42 | create : express-test/public 43 | create : express-test/public/javascripts 44 | create : express-test/views 45 | create : express-test/views/index.jade 46 | create : express-test/views/layout.jade 47 | create : express-test/views/error.jade 48 | create : express-test/public/stylesheets 49 | create : express-test/public/stylesheets/style.css 50 | create : express-test/routes 51 | create : express-test/routes/index.js 52 | create : express-test/routes/users.js 53 | create : express-test/public/images 54 | create : express-test/bin 55 | create : express-test/bin/www 56 | 57 | install dependencies: 58 | $ cd express-test && npm install 59 | 60 | run the app: 61 | $ DEBUG=express-test:* npm start 62 | 63 | ➜ test cd express-test 64 | ➜ express-test tree . -L 1 -d 65 | . 66 | ├── bin 67 | ├── public 68 | ├── routes 69 | └── views 70 | 71 | 4 directories 72 | 73 | ``` 74 | 75 | express是遵循小而美的原则,所以只有routes和views层,不足以在项目里使用的。 76 | 77 | ## 阶段1:抽象config和controller、model、middleware等 78 | 79 | ``` 80 | $ tree . -L 1 -d 81 | . 82 | ├── actions 83 | ├── config 84 | ├── cron_later 85 | ├── doc 86 | ├── middleware 87 | ├── migrate 88 | ├── models 89 | ├── node_modules 90 | ├── public 91 | ├── queues 92 | ├── routes 93 | ├── test 94 | ├── tmp 95 | ├── uploads 96 | └── views 97 | ``` 98 | 说明 99 | 100 | - 继承express-generator的结构(routes和views) 101 | - actions即controller控制器层 102 | - models即模型层 103 | - config是配置项目录 104 | - middleware中间件层 105 | - migrate是我们处理数据的,跟rails的migrate不完全一样 106 | - test是测试目录 107 | - queues是队列 108 | - cron_later是调度 109 | - doc文档 110 | - uploads是上传自动创建的目录 111 | 112 | 这阶段仅仅是用,算是基于express抽象了一点业务、配置相关的东西而已,目录多了依然蛋疼 113 | 114 | 115 | ## 阶段2:向rails看齐 116 | 117 | 118 | ### 目录结构 119 | 120 | ``` 121 | ➜ moa-api git:(master) ✗ tree . -L 2 -d 122 | . 123 | ├── app 124 | │   ├── controllers 125 | │   ├── middlewares 126 | │   ├── models 127 | │   ├── routes 128 | │   ├── services 129 | │   └── views 130 | ├── bin 131 | ├── config 132 | ├── doc 133 | ├── logs 134 | ├── migrate 135 | ├── public 136 | └── test 137 | └── controller 138 | 139 | 42 directories 140 | 141 | ``` 142 | 143 | 说明 144 | 145 | - 使用app作为代码管理目录,归类可变代码都放到app下面 146 | - 其他沿用【阶段1】的设计 147 | 148 | 具体看一下app目录下的分类 149 | 150 | ``` 151 | ├── app 152 | │   ├── controllers 153 | │   ├── middlewares 154 | │   ├── models 155 | │   ├── routes 156 | │   ├── services 157 | │   └── views 158 | ``` 159 | 160 | 说明 161 | 162 | - mvc和rails的都一样 163 | - middlewares是express的概念,放这里比较合适,如果像rails一样设置plugin也可以考虑,不如放到此处更容易理解 164 | - services层实际是java里面喜欢用的概念,多个模型dao的操作,看成是一个业务,所以复杂业务场景下,services层有它的好处 165 | 166 | ### 你看不到的优化 167 | 168 | - 使用mongoosedao把mongoose的crud、分页等封装了dao操作 169 | - 使用mount-routes自动挂载路由,让你只关系路由内容,而不必关系url地址,这样也算coc的一种(约定大于配置) 170 | - 使用res.api约定api返回数据 171 | 172 | 173 | 至此,moajs的0.x版本,特性已经足够了。 174 | 175 | 其实moajs还提供脚手架scaffold和插件机制 176 | 177 | ### 脚手架scaffold 178 | 179 | ``` 180 | moag order product_name:string product_count:string all_price:string status:string delivery_num:string pay_num:string activity_id:string owner_id:string contact_id:string 181 | ``` 182 | 183 | 生成目录如下 184 | 185 | ``` 186 | ➜ moa-scaffold tree . 187 | . 188 | └── app 189 | ├── controllers 190 | │   └── orders_controller.js 191 | ├── models 192 | │   └── order.js 193 | ├── routes 194 | │   ├── api 195 | │   │   └── orders.js 196 | │   └── orders.js 197 | └── views 198 | └── orders 199 | ├── edit.jade 200 | ├── index.jade 201 | ├── new.jade 202 | ├── order.jade 203 | └── show.jade 204 | 205 | 7 directories, 9 files 206 | ``` 207 | 208 | 这个生成器的原理是我们参考rails的脚手架,代码结构,mvc都非常方便 209 | 210 | 需要说明的是 211 | 212 | - 先有结构 213 | - 然后才有脚手架 214 | 215 | ### 插件机制 216 | 217 | 大家都知道express基于connect,有middleware中间件的概念,它本身遵循小而美的设计哲学,导致它非常精简 218 | 219 | 从express@generator来看,它就只能做点小打小闹的东西,如果要设计一个复杂的大系统,就免不了和代码结构,模块,组件等战斗 220 | 221 | 从我的角度讲,这些东西都可以理解成是业务插件,比如对于一个框架来说,用户管理就应该像ruby里的devise一样,以一个gem的形式存在,如果代码里引用,调用就好了。 222 | 223 | gem + rails plugin机制可以做,那么express + npm也是可以的,但是我们缺少的plugin机制,先说利用npm的回调实现它的可能性 224 | 225 | 比如在一个boilerplate项目里,我们安装插件 226 | 227 | npm install --save moa-plugin-user 228 | 229 | 安装完成之后,我们需要对项目里的文件或配置也好做一个插件登记,这些东西是否可以放到postinstall里呢? 230 | 231 | 剩下的就都是nodejs代码了,大家写就好了。 232 | 233 | moajs里0.x版本使用软连接创建插件 234 | 235 | - nmm用于插件管理和初始化 236 | - 使用npm的postinstall hook完成安装工作 237 | - 使用软连接的方式创建插件(简单粗暴法) 238 | 239 | 这其实有很多痛苦,每次升级插件都需要在主库里更新,以后还是要放到目录里,按照php的做法走。 240 | 241 | ### 总结 242 | 243 | 总结一下moajs的特性 244 | 245 | - 配置化 246 | - 目录和rails类似 247 | - 脚手架scaffold 248 | - 插件机制 249 | - 一些我们总结的最佳实践 250 | 251 | 具体 252 | 253 | - 自动加载路由 254 | - 支持mongodb配置 255 | - 集成mongoosedao,快速写crud等dao接口 256 | - 自带用户管理 257 | - 使用jsonwebtoken做用户鉴权 258 | - 支持migrate测试 259 | - 支持mocha测试 260 | - 默认集成res.api,便于写接口 261 | - 集成supervisor,代码变动,自动重载 262 | - gulp自动监控文件变动,跑测试 263 | - gulp routes生成路由说明 264 | - 使用log4js记录日志 265 | - 集成kue队列[需要使用mount-queues插件] 266 | 267 | ## 阶段3:走自己的路 268 | 269 | 上一个阶段【阶段2:向rails看齐】其实还是基于express的最佳实践而已。代码里处处都是express的影子,所以moajs 0.x版本真的只是基于express的最佳实践。 270 | 271 | 但是技术的演进步伐非常快 272 | 273 | - es6和7来了 274 | - async/generator也来了 275 | - koa来了 276 | - typescript来了 277 | - ... 278 | 279 | 这些对我们来说都是要思考的 280 | 281 | ### 前后端分离 282 | 283 | - 前端:[moa-frontend](https://github.com/moajs/moa-frontend) 和 [moa-h5](https://github.com/moajs/moa-h5) 284 | - public下面的采用nginx做反向代理 285 | - 其他的采用express+jade精简代码(ajax与后端交互) 286 | - 后端:[moa-api](https://github.com/moajs/moa-api) 287 | 288 | ### 微内核base2 289 | 290 | 之前使用express,各种配置都在app.js里,恶心的要死,而且没有app的生命周期管控 291 | 292 | 于是写了base2,它是一个高度可配置的带有应用生命周期管控的 nodejs web 微框架(同时支持express和koa) 293 | 294 | https://github.com/base-n/base2-core 295 | 296 | Usages 297 | 298 | ``` 299 | var app = require('base2')({ 300 | // debug: true, 301 | root:__dirname, 302 | "views": "views", 303 | "routes": "routes2", 304 | "public": "public", 305 | }) 306 | 307 | app.start(3019); 308 | ``` 309 | 310 | - 如果有views目录就可以显示视图 311 | - 如果有routes目录,就可以自动挂载路由 312 | - 如果有public就有静态server,目录随便指就好了 313 | 314 | 简单吧,下面讲讲为啥这样设计。 315 | 316 | 我们对项目的认知 317 | 318 | - framework(框架选项) 319 | - express 320 | - koa 321 | - env(环境,参考rails的环境说明) 322 | - production 323 | - development 324 | - test 325 | - type(类型,我们对项目分类,有的是带视图的,有的是api的,有的rpc服务的,all即所有) 326 | - normal 327 | - api 328 | - all 329 | - service 330 | 331 | 这些其实对express和koa的中间件分类的维度。也就是说不同场景,我们加载不通的中间价。 332 | 333 | 那么我们什么时候加载呢?你想可配置,可高度配置,你就一定得留出足够的点,让之前可以完成功能也能很好的集成进来。 334 | 335 | 于是引出app的生命周期,我们可以反思一下app.js里都做了什么? 336 | 337 | - 设置 338 | - 全局中间件 339 | - 路由 340 | 341 | 其他都指出去了。 342 | 343 | 那么我们就可以抽象一下了 344 | 345 | - config.pre 346 | - settings 347 | - config.before_settings 348 | - config.after_settings 349 | - global_middlewares 350 | - config.before_global_middlewares 351 | - config.after_global_middlewares 352 | - routes 353 | - config.before_routes 354 | - config.after_routes 355 | - config.post 356 | 357 | 实际上也是之前app做的事儿,但是要放到配置项里,按需采用。 358 | 359 | 举个例子,moa-api里的app.js 360 | 改写后 361 | 362 | ``` 363 | require('./init') 364 | require('./db') 365 | 366 | var log4js = require('log4js'); 367 | var res_api = require('res.api'); 368 | var log = log4js.getLogger("moa-api"); 369 | 370 | var app = require('base2')({ 371 | // debug: true, 372 | root:__dirname, 373 | "pre": function(app) { 374 | app.use(res_api); 375 | }, 376 | "views": "app/views", 377 | "routes": "app/routes", 378 | "public": "public" 379 | }) 380 | ``` 381 | 382 | res_api是一个中间件,如果想在路由里直接使用res.api方法,就必须先挂载。 383 | 384 | 这里选了pre这个最早执行的生命周期方法。其实也可以routes前面的所有生命周期都可以。 385 | 386 | ### 兼容koa和express 387 | 388 | 噱头而已,其实兼容与否并不重要,base2给我们提供了这种可能,我们用就好了 389 | 390 | 通过`koa-generator`大家可以体验一下koa1和koa2的写法和express的差别。 391 | 392 | 另外我组织了大家《一起学koa》 393 | 394 | http://base-n.github.io/koa-generator-examples 395 | 396 | 欢迎参与 397 | 398 | ## Next 399 | 400 | moajs在设计、实践上都是不错的,不过目前还不够完善,希望大家可以多多参与 401 | 402 | 下一步还在做 403 | 404 | - 模型管控,dao层剥离,防止模型乱create和update 405 | - 面向微服务 406 | - 私有npm库和本地npm库实践 407 | - 容器化、自动化(蔡神的https://github.com/fundon/fundon.github.io/issues/19) 408 | 409 | 未来路还很远。。。但很美好 -------------------------------------------------------------------------------- /doc/routes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moajs/moa-api/a6122da093e99ef533962017638574a53f1bf3ff/doc/routes.png -------------------------------------------------------------------------------- /doc/wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moajs/moa-api/a6122da093e99ef533962017638574a53f1bf3ff/doc/wechat.jpg -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var watch = require('gulp-watch'); 3 | var mocha = require('gulp-mocha'); 4 | var mount = require('mount-routes'); 5 | 6 | var source_path = ['test/**/*.js', 'lib/*.js']; 7 | 8 | gulp.task('watch', function() { 9 | gulp.watch(source_path, ['mocha']); 10 | }); 11 | 12 | gulp.task('mocha', function () { 13 | return gulp.src(source_path , {read: false}) 14 | // gulp-mocha needs filepaths so you can't have any plugins before it 15 | .pipe(mocha({reporter: 'spec'})); 16 | }); 17 | 18 | gulp.task('routes', function() { 19 | var express = require('express'); 20 | var app = express(); 21 | 22 | // mount routes 23 | mount(app, __dirname + '/app/routes', true); 24 | }); 25 | 26 | gulp.task('kp', function() { 27 | var kp = require("kp"); 28 | var is_sudo = false; 29 | var pre = is_sudo == true ? 'sudo' : '' ; 30 | 31 | kp(3000, pre); 32 | }); 33 | 34 | 35 | gulp.task('default',['mocha', 'watch']); -------------------------------------------------------------------------------- /init.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | , log4js = require('log4js') 3 | , log_path = 'logs' 4 | , is_exist = fs.existsSync(log_path) 5 | , log = log4js.getLogger("moa-api"); 6 | 7 | /** 8 | * 创建log目录 9 | */ 10 | function _create_log_dir(log_path){ 11 | var is_exist = fs.existsSync(log_path); 12 | 13 | if (is_exist !== true) { 14 | console.log('log_path is not exist, create folder:' + log_path); 15 | fs.mkdirSync(log_path, 0755); 16 | } else { 17 | console.log('log_path is exist, no operation!'); 18 | } 19 | } 20 | 21 | function main(){ 22 | _create_log_dir(log_path); 23 | } 24 | 25 | // 程序入口 26 | main(); -------------------------------------------------------------------------------- /migrate/bill_in_account.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // 完成发货单,并生成对应账单 4 | require('../db.js'); 5 | 6 | var Test = require('../app/models/user'); 7 | 8 | Test.find({}, function(err,docs){ 9 | console.dir(docs); 10 | process.exit(); 11 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moa-api", 3 | "version": "1.0.1", 4 | "private": false, 5 | "scripts": { 6 | "start": "./node_modules/.bin/supervisor ./bin/www", 7 | "pub": "npm publish ." 8 | }, 9 | "dependencies": { 10 | "base2": "^1.0.16", 11 | "bluebird": "^2.9.34", 12 | "debug": "~2.2.0", 13 | "express": "^4.13.3", 14 | "ioredis": "^1.9.1", 15 | "is": "^3.1.0", 16 | "jade": "^1.11.0", 17 | "jsonwebtoken": "^5.0.4", 18 | "kp": "^1.1.0", 19 | "log4js": "^0.6.26", 20 | "moa-plugin-user": "^1.0.1", 21 | "mongoose": "4.0.5", 22 | "mongoosedao": "^1.0.13", 23 | "morgan": "~1.6.1", 24 | "mount-controllers": "^1.0.7", 25 | "mount-middlewares": "^1.0.5", 26 | "mount-services": "^1.0.7", 27 | "msgpack5": "^3.1.0", 28 | "res.api": "^1.0.11" 29 | }, 30 | "devDependencies": { 31 | "chai": "^3.2.0", 32 | "gulp": "^3.9.0", 33 | "gulp-mocha": "^2.1.3", 34 | "gulp-watch": "^4.3.4", 35 | "mocha": "^2.2.5", 36 | "mount-routes": "^1.0.5", 37 | "supertest": "^1.0.1", 38 | "supervisor": "^0.7.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moajs/moa-api/a6122da093e99ef533962017638574a53f1bf3ff/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /test/controller/test.js: -------------------------------------------------------------------------------- 1 | var request = require('supertest'); 2 | var assert = require('chai').assert; 3 | var expect = require('chai').expect; 4 | require('chai').should(); 5 | 6 | var app = require('../../app'); 7 | 8 | describe('GET /users', function(){ 9 | it('respond with json or msgpack', function(done){ 10 | request(app) 11 | .get('/api/user/login') 12 | .set('Accept', 'application/json') 13 | .expect('Content-Type', /json|msgpack/) 14 | .expect(200, done); 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /test/controller/users_controller.js: -------------------------------------------------------------------------------- 1 | var request = require('supertest'); 2 | var assert = require('chai').assert; 3 | var expect = require('chai').expect; 4 | require('chai').should(); 5 | 6 | var app = require('../../app'); 7 | 8 | describe('GET /users', function(){ 9 | it('respond with json or msgpack', function(done){ 10 | request(app) 11 | .get('/api/user/login') 12 | .set('Accept', 'application/json') 13 | .expect('Content-Type', /json|msgpack/) 14 | .expect(200, done); 15 | }) 16 | }) 17 | --------------------------------------------------------------------------------