├── .dockerignore ├── .gitignore ├── .sailsrc ├── Dockerfile ├── README.md ├── README_CN.md ├── api ├── controllers │ ├── .gitkeep │ ├── ArticleController.js │ ├── AuthController.js │ ├── CommentController.js │ ├── ImageController.js │ ├── OptionController.js │ ├── ReviewController.js │ ├── TagController.js │ └── UserController.js ├── models │ ├── .gitkeep │ ├── Article.js │ ├── Comment.js │ ├── Option.js │ ├── Session.js │ ├── Tags.js │ └── User.js ├── policies │ ├── isActive.js │ ├── isAdmin.js │ ├── isAuthenticated.js │ ├── notActive.js │ ├── notCreated.js │ ├── rateLimiting.js │ └── selectApiVersion.js ├── responses │ ├── badRequest.js │ ├── created.js │ ├── forbidden.js │ ├── notFound.js │ ├── ok.js │ └── serverError.js └── services │ ├── .gitkeep │ ├── ArticleService.js │ ├── AuthService.js │ ├── CommentService.js │ ├── OptionService.js │ ├── TagService.js │ └── UserService.js ├── app.js ├── config ├── blueprints.js ├── bootstrap.js ├── connections.js ├── cors.js ├── csrf.js ├── email.js ├── env │ ├── development.js │ └── production.js ├── globals.js ├── http.js ├── i18n.js ├── local.js ├── locales │ ├── _README.md │ ├── de.json │ ├── en.json │ ├── es.json │ └── fr.json ├── log.js ├── models.js ├── policies.js ├── routes.js ├── sockets.js └── views.js ├── docker-compose-dev.yml ├── docker-compose.yml ├── package.json └── portal └── index.html /.dockerignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules 3 | .idea 4 | .temp 5 | temp 6 | *.log 7 | docker-compose.yml 8 | docker-compose-dev.yml 9 | .dockerignore 10 | Dockerfile 11 | ecosystem.config.js 12 | variables.env 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | variables.env 2 | *.log 3 | .cache 4 | .DS_Store 5 | .idea 6 | dist 7 | node_modules 8 | dov 9 | variables 10 | 11 | -------------------------------------------------------------------------------- /.sailsrc: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "grunt": false, 4 | "session": false 5 | } 6 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # base images 2 | FROM node 3 | 4 | # copy dir 5 | RUN mkdir -p tcome/resources/ 6 | 7 | COPY . tcome/resources/ 8 | 9 | # set workdir 10 | WORKDIR /tcome/resources/ 11 | RUN buildDeps= npm config set registry https://registry.npm.taobao.org --global \ 12 | && npm config set disturl https://npm.taobao.org/dist --global \ 13 | && npm config set registry https://registry.npm.taobao.org \ 14 | && npm config set disturl https://npm.taobao.org/dist \ 15 | && npm i apidoc -g 16 | 17 | # set port 18 | EXPOSE 1337 19 | 20 | 21 | # sails lift 22 | CMD ["npm", "run", "docker-start"] 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### T-COME, [中文](https://github.com/WittBulter/tcome/blob/master/README_CN.md) 2 | #### next blog, present for you. 3 | 4 | I'm not perfect, i'm sorry. 5 | 6 | [PREVIEW](http://wittsay.cc/)   [API](http://wittsay.cc/doc)   7 | 8 | This is a blog, based on nodejs and mongodb. 9 | If you need a frontend, goto [tcome-frontend](https://github.com/WittBulter/tcome-frontend) 10 | 11 | ## FEATURES 12 | * Support login/register/manage 13 | * Any users can publish articles 14 | * Support comments 15 | * Canonical interface(RESTfulAPI),highly scalable 16 | * Search and more configuration 17 | * Elegant code 18 | * More features are under development... 19 | 20 | ## INSTALL 21 | Use [tcome-cli](https://github.com/WittBulter/tcome-cli) to install blogs, contains only server code 22 | ```sh 23 | $ npm i tcome-cli -g 24 | $ tcome init [blog-name] 25 | ``` 26 | 27 | ## DEVELOP 28 | tips: require node-gyp,see [node-gyp](https://github.com/nodejs/node-gyp) 29 | ```sh 30 | * install package 31 | $ npm install 32 | 33 | * install sails.js 34 | $ sudo npm install sails -g 35 | 36 | * install grunt (optional, if you need) 37 | $ sudo npm install grunt -g 38 | ``` 39 | 40 | 41 | **run:** 42 | ```sh 43 | * if node version < 7.6.0 (npm start = node --harmony-async-await app.js) 44 | $ npm start 45 | * if node version >= 7.6.0 46 | $ sails lift 47 | ``` 48 | 49 | **make api doc:** 50 | ```sh 51 | $ npm install apidoc -g 52 | $ npm run api 53 | ``` 54 | 55 | **create file:** 56 | ```sh 57 | 58 | $ sails generate api 59 | 60 | $ sails generate model [attribute1:type1, attribute2:type2 ... ] 61 | 62 | $ sails generate controller [action1, action2, ...] 63 | ``` 64 | 65 | ## DEMO 66 | [PREVIEW](http://wittsay.cc/) 67 | ![demo1](http://static.wittsay.cc/tcome-demo-1.png) 68 | ![demo2](http://static.wittsay.cc/tcome-demo-2.png) 69 | 70 | 71 | ## TEAM 72 | 73 | 74 | [![Witt Bulter](http://obqqxnnm4.bkt.clouddn.com/11304944.gif?imageView2/1/w/100)](https://github.com/WittBulter) | 75 | :---:| 76 | [Witt Bulter](https://github.com/WittBulter) | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | ### T-COME, [English](https://github.com/WittBulter/tcome/blob/master/README.md) 2 | #### 你最好的博客 3 | 4 | [预览](http://wittsay.cc/)   [接口文档](http://wittsay.cc/doc)   5 | 6 | 这是基于NodeJs,MongoDB的博客系统服务端,负责基础API服务。 7 | 8 | 如需要与之契合的前端项目请前往[前端项目](https://github.com/WittBulter/tcome-frontend) 9 | 10 | ## 特性 11 | * 支持登录注册,管理 12 | * 任何用户均可发表文章,文章审核机制 13 | * 文章支持评论 14 | * 规范的RESTfulAPI,高度可扩展性 15 | * 搜索与各类配置 16 | * 优雅,可维护性高的代码 17 | * 更多特性正在开发中... 18 | 19 | 20 | ## 安装 21 | 使用 [tcome-cli](https://github.com/WittBulter/tcome-cli) 安装博客(目前只包含服务端,前端在开发中) 22 | ```sh 23 | $ npm i tcome-cli -g 24 | $ tcome init [blog-name] 25 | ``` 26 | 27 | ## 开发 28 | 需要编译环境,具体配置请参阅[node-gyp](https://github.com/nodejs/node-gyp) 29 | ```sh 30 | * 安装依赖 31 | $ npm install 32 | 33 | * 安装全局sails 34 | $ sudo npm install sails -g 35 | 36 | * 安装全局grunt (可选,如果你真的需要) 37 | $ sudo npm install grunt -g 38 | ``` 39 | 40 | 41 | **运行:** 42 | ```sh 43 | * node版本 < 7.6.0 (npm start = node --harmony-async-await app.js) 44 | $ npm start 45 | * node版本 >= 7.6.0 46 | $ sails lift 47 | ``` 48 | 49 | **生成文档:** 50 | ```sh 51 | $ npm install apidoc -g 52 | $ npm run api 53 | ``` 54 | 55 | **创建sails:** 56 | ```sh 57 | 58 | $ sails generate api 59 | 60 | $ sails generate model [attribute1:type1, attribute2:type2 ... ] 61 | 62 | $ sails generate controller [action1, action2, ...] 63 | ``` 64 | 65 | ## 展示 66 | [预览](http://wittsay.cc/) 67 | ![demo1](http://static.wittsay.cc/tcome-demo-1.png) 68 | ![demo2](http://static.wittsay.cc/tcome-demo-2.png) 69 | 70 | ## 团队 71 | TCOME由WittBulter开发,如果你需要加入开发团队,请联系我: 72 | 73 | [![Witt Bulter](http://obqqxnnm4.bkt.clouddn.com/11304944.gif?imageView2/1/w/100)](https://github.com/WittBulter) | 74 | :---:| 75 | [Witt Bulter](https://github.com/WittBulter) | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /api/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unix/tcome/45c6e1c07ea16f94c0400ad9feb8fb07434e299a/api/controllers/.gitkeep -------------------------------------------------------------------------------- /api/controllers/ArticleController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2016/10/12. 3 | * @description :: 文章相关逻辑 4 | */ 5 | const trimHtml = require('trim-html') 6 | 7 | module.exports = { 8 | 9 | /** 10 | * 11 | * @api {GET} http://wittsay.cc/v1/articles/:id [show] 12 | * @apiGroup Article 13 | * @apiDescription 获取指定文章详细信息 任何权限 14 | * @apiParam (path) {string} [id] 文章id 15 | * @apiUse PAGE 16 | * @apiUse CODE_200 17 | * @apiUse CODE_500 18 | * @apiSuccessExample {json} Response 400 Example 19 | * HTTP/1.1 400 Interface Error 20 | * { 21 | * "code": 400, 22 | * "message": "xxx" 23 | * } 24 | */ 25 | show: async(req, res) => { 26 | const { id } = req.params 27 | try { 28 | if (!id) { 29 | const { page, per_page } = req.allParams() 30 | const [count, articles] = await Promise.all([ 31 | ArticleService.findArticleCount(), 32 | ArticleService.findArticleAll(page, per_page), 33 | ]) 34 | res.setHeader('total', count) 35 | return res.ok(articles) 36 | } 37 | 38 | const article = await ArticleService.findArticleForID(id) 39 | if (!article) return res.notFound({ message: '未找到文章' }) 40 | const [user, updated] = await Promise.all([ 41 | UserService.findUserForId(article.authorId), 42 | ArticleService.updateArticle(id, { readTotal: article.readTotal ? article.readTotal + 1 : 2 }), 43 | ]) 44 | res.ok(Object.assign({ avatar: user.avatar ? user.avatar : '' }, updated[0])) 45 | } catch (err) { 46 | return res.serverError(err) 47 | } 48 | }, 49 | 50 | /** 51 | * 52 | * @api {PUT} http://wittsay.cc/v1/articles/:id [update] 53 | * @apiGroup Article 54 | * @apiDescription 修改指定文章 需要登录 已删除或不存在文章无法修改 55 | * @apiParam (path) {string} id 文章id 56 | * @apiParam (body) {string} [title] 文章标题 57 | * @apiParam (body) {string} [content] 文章内容 58 | * @apiParam (body) {string} [thumbnail] 标题图 59 | * @apiParam (body) {string[]} [tags] 标签tags 60 | * @apiUse CODE_200 61 | * @apiUse CODE_500 62 | */ 63 | update: async(req, res) => { 64 | const { id } = req.params 65 | const { title, content, thumbnail, tags } = req.allParams() 66 | const includesTags = tags && Object.prototype.toString.call(tags) === '[object Array]' && tags.length > 0 67 | 68 | if (!id) return res.badRequest({ message: '至少需要指定文章id' }) 69 | if (!title && !content && !thumbnail) return res.badRequest({ message: '至少需要修改一项' }) 70 | if (title.length < 5 || content.length < 5) return res.badRequest({ message: '文章内容过少' }) 71 | 72 | // 减少更新数量有助于提高更新速度 73 | let article = { articleType: 'isReview' } 74 | if (title) article.title = title 75 | if (content) article.content = content 76 | if (thumbnail) article.thumbnail = thumbnail 77 | if (includesTags) { 78 | article.tags = tags 79 | } 80 | 81 | try { 82 | const art = await ArticleService.findArticleForID(id) 83 | if (!art || art.articleType === 'isDestroy') return res.badRequest({ message: '文章已被删除' }) 84 | if (art.authorId !== req.headers.userID) return res.forbidden({ message: '仅只能修改自己发表的文章' }) 85 | const updated = await ArticleService.updateArticle(id, article) 86 | if (includesTags) TagService.saveTagsAsync(tags) 87 | .then(res => { 88 | }) 89 | 90 | res.ok(updated[0]) 91 | } catch (err) { 92 | return res.serverError(err) 93 | } 94 | }, 95 | 96 | 97 | /** 98 | * 99 | * @api {POST} http://wittsay.cc/v1/article [create] 100 | * @apiGroup Article 101 | * @apiDescription 创建一篇文章 需要登录 102 | * @apiParam (body) {string} title 文章标题 103 | * @apiParam (body) {string} content 文章内容 104 | * @apiParam (body) {string} [thumbnail] 文章缩略图 105 | * @apiParam (body) {string[]} [tags] 标签tags 106 | * @apiUse CODE_200 107 | * @apiUse CODE_500 108 | */ 109 | create: async(req, res) => { 110 | const { title, content, tags, thumbnail } = req.allParams() 111 | if (!title || !content) return res.badRequest({ message: '缺少必要的文章参数' }) 112 | if (content.length < 100) { 113 | return res.badRequest({ message: '文章过短' }) 114 | } 115 | const abstract = trimHtml(content, { limit: 50 }).html 116 | try { 117 | const created = await ArticleService.createArticle({ 118 | title: title, 119 | content: content, 120 | thumbnail: thumbnail ? thumbnail : '', 121 | tags: tags ? tags : [], 122 | authorId: req.headers.userID, 123 | authorName: req.headers.username, 124 | abstract: abstract, 125 | articleType: 'isReview', 126 | }) 127 | if (tags) TagService.saveTagsAsync(tags) 128 | .then(res => { 129 | }) 130 | 131 | res.ok(created) 132 | } catch (err) { 133 | return res.serverError(err) 134 | } 135 | }, 136 | 137 | /** 138 | * 139 | * @api {DELETE} http://wittsay.cc/v1/articles/:id [destroy] 140 | * @apiGroup Article 141 | * @apiParam (path) {string} id 文章id 142 | * @apiDescription 删除指定文章 需要管理员或更高权限 143 | * @apiUse PAGE 144 | * @apiUse CODE_200 145 | * @apiUse CODE_500 146 | */ 147 | destroy: async() => { 148 | const { id } = req.params 149 | if (!id) return res.badRequest({ message: '需要文章id' }) 150 | try { 151 | const updated = await ArticleService.updateArticle(id, { articleType: 'isDestroy' }) 152 | res.ok(updated[0]) 153 | } catch (err) { 154 | return res.serverError() 155 | } 156 | }, 157 | 158 | 159 | /** 160 | * 161 | * @api {GET} http://wittsay.cc/v1/articles/:keyword/search [search] 162 | * @apiGroup Article 163 | * @apiDescription 按关键字搜索文章 任何权限 164 | * @apiParam (path) {string} keyword 关键字 (=allArticles返回全部) 165 | * @apiUse PAGE 166 | * @apiUse CODE_200 167 | * @apiUse CODE_500 168 | */ 169 | search: async(req, res) => { 170 | const { keyword } = req.params 171 | const { page, per_page } = req.allParams() 172 | try { 173 | if (keyword === 'allArticles') { 174 | const allArticles = await ArticleService.findArticleAll(page, per_page) 175 | return res.ok(allArticles) 176 | } 177 | const searchArticles = await ArticleService.findArticleForKeyword(keyword, page, per_page) 178 | res.ok(searchArticles) 179 | } catch (err) { 180 | res.serverError() 181 | } 182 | }, 183 | 184 | } 185 | -------------------------------------------------------------------------------- /api/controllers/AuthController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AuthController 3 | * 4 | * @description :: 用户会话相关逻辑 5 | */ 6 | 7 | module.exports = { 8 | /** 9 | * @apiDefine CODE_500 10 | * @apiSuccessExample {json} Response 500 Example 11 | * HTTP/1.1 500 Internal Server Error 12 | * { 13 | * "message": "服务器错误" 14 | * } 15 | */ 16 | 17 | /** 18 | * @apiDefine CODE_400 19 | * @apiSuccessExample {json} Response 400 Example 20 | * HTTP/1.1 400 Internal Server Error 21 | * { 22 | * "message": "语法错误提示(如果有)" 23 | * } 24 | */ 25 | 26 | /** 27 | * @apiDefine CODE_403 28 | * @apiSuccessExample {json} Response 400 Example 29 | * HTTP/1.1 403 Internal Server Error 30 | * { 31 | * "message": "错误内容" 32 | * } 33 | */ 34 | 35 | /** js 36 | * @apiDefine CODE_200 37 | * @apiSuccessExample {json} Response 200 Example 38 | * HTTP/1.1 200 OK 39 | * { 40 | * "code": 200 41 | * } 42 | */ 43 | 44 | /** js 45 | * @apiDefine CODE_204 46 | * @apiSuccessExample {json} Response 204 Example 47 | * HTTP/1.1 204 OK 48 | */ 49 | 50 | /** js 51 | * @apiDefine PAGE 52 | * @apiParam (param) {number} [page] 页码数(默认1) 53 | * @apiParam (param) {number} [per_page] 每页显示数量(默认14) 54 | */ 55 | 56 | /** 57 | * 58 | * @api {ANY} http://wittsay.cc/v1/session [Authorization] 59 | * @apiGroup Authorization 60 | * @apiDescription 接口权限验证 61 | * @apiParam (header) {string} Authorization 用户验证token 62 | */ 63 | 64 | /** 65 | * 66 | * @api {POST} http://wittsay.cc/v1/session [login] 67 | * @apiGroup Session 68 | * @apiDescription 用户登录,获取用户session 69 | * @apiParam (body) {string} email 用户名 70 | * @apiParam (body) {string} password 用户密码 71 | * @apiUse CODE_200 72 | * @apiUse CODE_500 73 | */ 74 | login: async(req, res) => { 75 | const { email, password } = req.allParams() 76 | if (!email || !password) return res.badRequest({ message: '需要邮件地址与密码' }) 77 | AuthService.authUser(email, password) 78 | .then(response => { 79 | let { status, user, msg } = response 80 | if (!status) return res.forbidden({ message: msg }) 81 | 82 | if (user.password) delete user.password 83 | return res.ok({ message: msg, user: user }) 84 | }) 85 | .catch(err => { 86 | return res.serverError(err) 87 | }) 88 | }, 89 | 90 | /** 91 | * 92 | * @api {DELETE} http://wittsay.cc/v1/session [logout] 93 | * @apiGroup Session 94 | * @apiDescription 用户登出,注销用户session 95 | * @apiUse CODE_200 96 | * @apiUse CODE_500 97 | * @apiSuccessExample {json} Response 400 Example 98 | * HTTP/1.1 400 Interface Error 99 | * { 100 | * "code": 400, 101 | * "message": "xxx" 102 | * } 103 | */ 104 | logout: async(req, res) => { 105 | const email = req.headers.email 106 | try { 107 | await AuthService.deleteSession(email) 108 | res.status(204) 109 | return res.json({}) 110 | } catch (err) { 111 | return res.serverError() 112 | } 113 | }, 114 | } 115 | 116 | -------------------------------------------------------------------------------- /api/controllers/CommentController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2016/10/20. 3 | * @description :: 评论相关逻辑 4 | */ 5 | 6 | module.exports = { 7 | 8 | /** 9 | * 10 | * @api {GET} http://wittsay.cc/v1/articles/:id/comment [show] 11 | * @apiGroup Comment 12 | * @apiDescription 获取文章评论 无需权限 13 | * @apiParam (path) {string} id 文章id 14 | * @apiUse PAGE 15 | * @apiUse CODE_200 16 | * @apiUse CODE_500 17 | */ 18 | show: async(req, res) => { 19 | const { id } = req.params 20 | if (!id) return res.badRequest({ message: '缺少文章id' }) 21 | try { 22 | const comments = await CommentService.findCommentForArticle(id) 23 | res.ok(comments) 24 | } catch (err) { 25 | return res.serverError() 26 | } 27 | }, 28 | 29 | 30 | /** 31 | * 32 | * @api {POST} http://wittsay.cc/v1/articles/:id/comment [create] 33 | * @apiGroup Comment 34 | * @apiDescription 对文章创建一个评论 需要登录 35 | * @apiParam (path) {string} id 需要评论的文章id 36 | * @apiParam (body) {string} content 评论内容 5 { 43 | const { id } = req.params 44 | const { content, targetId } = req.allParams() 45 | if (!id) return res.badRequest({ message: '缺少文章id' }) 46 | if (!content) return res.badRequest({ message: '缺少评论内容' }) 47 | if (content.length < 5 || content.length > 500) return res.badRequest({ message: '评论内容不符合规范' }) 48 | 49 | try { 50 | const article = await ArticleService.findArticleForID(id) 51 | if (!article) return res.badRequest({ message: '无效的文章id' }) 52 | const [created, updated] = await Promise.all([ 53 | CommentService.createComment({ 54 | authorId: req.headers.userID, 55 | authorName: req.headers.username, 56 | articleId: article.id, 57 | articleName: article.title, 58 | targetId: targetId ? targetId : null, 59 | content: content, 60 | }), 61 | ArticleService.updateArticle(id, { commentTotal: article.commentTotal + 1 }), 62 | ]) 63 | res.ok(created) 64 | 65 | } catch (err) { 66 | return res.serverError() 67 | } 68 | }, 69 | 70 | 71 | destroy: (req, res) => { 72 | 73 | }, 74 | 75 | 76 | } 77 | 78 | -------------------------------------------------------------------------------- /api/controllers/ImageController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2017/1/31. 3 | */ 4 | const request = require('request') 5 | const QiNiuCloud = require('qiniu') 6 | 7 | // 个人qiniu密钥配置 8 | QiNiuCloud.conf.ACCESS_KEY = process.env.QINIU_ACCESS_KEY 9 | QiNiuCloud.conf.SECRET_KEY = process.env.QINIU_SECRET_KEY 10 | 11 | module.exports = { 12 | 13 | /** 14 | * 15 | * @api {POST} http://wittsay.cc/v1/image [create] 16 | * @apiGroup Image 17 | * @apiDescription 上传一张图片 需要登录 18 | * @apiParam (body) {string} image base64编码图片 转码后长度需小于2400000 19 | * @apiParam (body) {string} size 图片原大小 20 | * @apiUse CODE_200 21 | * @apiUse CODE_500 22 | */ 23 | upload: (req, res) => { 24 | let { image, size } = req.allParams() 25 | if (!image || !size) return res.badRequest({ message: '参数错误' }) 26 | if (image.length > 2400000) return res.badRequest({ message: '图片过大,请压缩后再尝试' }) 27 | const token = new QiNiuCloud.rs.PutPolicy('static').token() 28 | if (image.includes('base64,')) image = image.split('base64,')[1] 29 | 30 | request({ 31 | url: `http://up.qiniu.com/putb64/${size}`, 32 | port: 8080, 33 | method: 'POST', 34 | body: image, 35 | headers: { 36 | 'User-Agent': 'nodejs', 37 | 'Content-Type': 'application/octet-stream', 38 | 'Authorization': `UpToken ${token}`, 39 | }, 40 | }, (err, response, body) => { 41 | if (err) return res.serverError(err) 42 | 43 | res.ok(body) 44 | }) 45 | }, 46 | } 47 | -------------------------------------------------------------------------------- /api/controllers/OptionController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2017/1/29. 3 | */ 4 | 5 | module.exports = { 6 | 7 | /** 8 | * 9 | * @api {GET} http://wittsay.cc/v1/option [show] 10 | * @apiGroup Option 11 | * @apiDescription 查看博客基础信息 12 | * @apiUse CODE_200 13 | * @apiUse CODE_500 14 | */ 15 | show: async(req, res) => { 16 | try { 17 | const options = await OptionService.findOptionAll() 18 | if (!options || !options[0]) return res.ok({}) 19 | const promises = options[0].recommended.map(id => ArticleService.findArticleForID(id)) 20 | const recommended = await Promise.all(promises) 21 | 22 | return res.ok(Object.assign(options[0], { recommended: recommended })) 23 | } catch (err) { 24 | return res.serverError() 25 | } 26 | }, 27 | 28 | /** 29 | * 30 | * @api {PUT} http://wittsay.cc/v1/option [update] 31 | * @apiGroup Option 32 | * @apiDescription 修改博客基础信息(如果没有则自动创建) 需要管理员权限或更高 33 | * @apiParam (path) {string} [blogName] 博客名称 34 | * @apiParam (body) {string} [blogSubhead] 博客副标题 35 | * @apiParam (body) {string[]} [recommended] 博客推荐文章 每一项为文章id 36 | * @apiUse CODE_200 37 | * @apiUse CODE_500 38 | */ 39 | update: async(req, res) => { 40 | const { blogName, blogSubhead, recommended } = req.allParams() 41 | if (!blogName && !blogSubhead && !recommended) { 42 | return res.badRequest({ message: '至少需要修改一项' }) 43 | } 44 | let option = {} 45 | if (blogName) option.blogName = blogName 46 | if (blogSubhead) option.blogSubhead = blogSubhead 47 | if (recommended && recommended[0]) option.recommended = recommended.filter(v => typeof v === 'string') 48 | 49 | try { 50 | const allOptions = await OptionService.findOptionAll() 51 | if (!allOptions || allOptions.length == 0) { 52 | const created = await OptionService.createOption(option) 53 | return res.ok(created) 54 | } 55 | const [updated] = await OptionService.updateOptionForID(allOptions[0].id, option) 56 | const promises = updated.recommended.map(id => ArticleService.findArticleForID(id)) 57 | const recommended = await Promise.all(promises) 58 | 59 | res.ok(Object.assign(updated, { recommended: recommended })) 60 | } catch (err) { 61 | return res.serverError() 62 | } 63 | }, 64 | } 65 | -------------------------------------------------------------------------------- /api/controllers/ReviewController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2017/1/29. 3 | */ 4 | 5 | module.exports = { 6 | 7 | /** 8 | * 9 | * @api {GET} http://wittsay.cc/v1/reviews/:id [showReviewArticles] 10 | * @apiGroup Review 11 | * @apiDescription 获取需要审核的文章 需要Admin或更高权限 12 | * @apiParam (path) {string} [id] 文章id (查询id会自动抛弃query条件) 13 | * @apiParam (query) {string} [status] 文章状态 包括: isReview:审核中, isActive:正常, isDestroy:已删除, all: 所有(默认) 14 | * @apiUse PAGE 15 | * @apiUse CODE_200 16 | * @apiUse CODE_500 17 | */ 18 | show: async(req, res) => { 19 | const { id } = req.params 20 | 21 | try { 22 | if (!id) { 23 | let { page, per_page, status } = req.allParams() 24 | if (status != 'isReview' && status != 'isActive' && status != 'isDestroy') { 25 | status = 'all' 26 | } 27 | const articles = await ArticleService.findReviewForType(status, page, per_page) 28 | return res.ok(articles) 29 | } 30 | const article = await ArticleService.findArticleForID(id) 31 | if (!article) return res.notFound({ message: '未找到文章' }) 32 | const [user, updated] = await Promise.all([ 33 | UserService.findUserForId(article.authorId), 34 | ArticleService.updateArticle(id, { readTotal: article.readTotal ? article.readTotal + 1 : 2 }), 35 | ]) 36 | 37 | res.ok(Object.assign({ avatar: user.avatar ? user.avatar : '' }, updated[0])) 38 | } catch (err) { 39 | return res.serverError() 40 | } 41 | }, 42 | 43 | /** 44 | * 45 | * @api {PUT} http://wittsay.cc/v1/reviews/:id/:status [reviewArticle] 46 | * @apiGroup Review 47 | * @apiDescription 审核指定文章 需要管理员权限或更高 48 | * @apiParam (path) {string} id 文章id 49 | * @apiParam (path) {string} status 文章状态 包括: isReview:审核中, isActive:正常, isDestroy:已删除 50 | * @apiUse CODE_200 51 | * @apiUse CODE_500 52 | */ 53 | update: async(req, res) => { 54 | const { id, status } = req.params 55 | if (!id || !status) return res.badRequest({ message: '参数错误' }) 56 | if (status != 'isReview' && status != 'isActive' && status != 'isDestroy') { 57 | return res.badRequest({ message: '状态错误' }) 58 | } 59 | try { 60 | const updated = await ArticleService.updateArticle(id, { articleType: status }) 61 | res.ok(updated[0]) 62 | } catch (err) { 63 | return res.serverError() 64 | } 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /api/controllers/TagController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2017/2/18. 3 | * @description :: article tag controller 4 | */ 5 | 6 | module.exports = { 7 | /** 8 | * 9 | * @api {GET} http://wittsay.cc/v1/articles/:tag/tag[show] 10 | * @apiGroup Tag 11 | * @apiDescription 获取指定tag下的文章列表 12 | * @apiParam (path) {string} [tag] 标签名 13 | * @apiUse PAGE 14 | * @apiUse CODE_200 15 | * @apiUse CODE_500 16 | * @apiSuccessExample {json} Response 400 Example 17 | * HTTP/1.1 400 Interface Error 18 | * { 19 | * "code": 400, 20 | * "message": "xxx" 21 | * } 22 | */ 23 | showArticles: async(req, res) => { 24 | const { tag } = req.params 25 | if (!tag || tag.length > 30) return res.badRequest({ message: '需要正确的tag名' }) 26 | try { 27 | const articles = await TagService.findArticlesForTag(tag) 28 | res.ok(articles) 29 | } catch (err) { 30 | return res.serverError(err) 31 | } 32 | }, 33 | 34 | /** 35 | * 36 | * @api {GET} http://wittsay.cc/v1/tags [showTags] 37 | * @apiGroup Tag 38 | * @apiDescription 获取所有tag 无需权限 39 | * @apiParam (path) {string} [tag] 标签名 40 | * @apiUse PAGE 41 | * @apiUse CODE_200 42 | * @apiUse CODE_500 43 | * @apiSuccessExample {json} Response 400 Example 44 | * HTTP/1.1 400 Interface Error 45 | * { 46 | * "code": 400, 47 | * "message": "xxx" 48 | * } 49 | */ 50 | showTags: async(req, res) => { 51 | const { page, per_page } = req.allParams() 52 | try { 53 | const tags = await TagService.findTagsAll(page, per_page) 54 | res.ok(tags) 55 | } catch (err) { 56 | return res.serverError(err) 57 | } 58 | }, 59 | } 60 | -------------------------------------------------------------------------------- /api/controllers/UserController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2016/10/12. 3 | * @description :: 管理用户及相关逻辑 4 | */ 5 | const uuid = require('node-uuid') 6 | 7 | module.exports = { 8 | /** 9 | * 10 | * @api {GET} http://wittsay.cc/v1/users/:id [show] 11 | * @apiGroup User 12 | * @apiDescription 获取指定用户的信息 13 | * @apiParam (path) {string} id 用户id 14 | * @apiUse CODE_200 15 | * @apiUse CODE_500 16 | */ 17 | show: async(req, res) => { 18 | const { id } = req.params 19 | if (!id) return res.badRequest({ message: '至少需要用户id' }) 20 | try { 21 | let user = await UserService.findUserForId(id) 22 | if (!user || !user.id) return res.notFound({ message: '未找到此用户' }) 23 | delete user.password 24 | res.ok(user) 25 | } catch (err) { 26 | return res.serverError() 27 | } 28 | }, 29 | 30 | /** 31 | * 32 | * @api {GET} http://wittsay.cc/v1/user [show] 33 | * @apiGroup User 34 | * @apiDescription 获取当前登录用户的信息 35 | * @apiUse CODE_200 36 | * @apiUse CODE_500 37 | */ 38 | self: async(req, res) => { 39 | const userID = req.headers.userID 40 | try { 41 | let user = await UserService.findUserForId(userID) 42 | delete user.password 43 | res.ok(user) 44 | } catch (err) { 45 | return res.serverError() 46 | } 47 | }, 48 | 49 | /** 50 | * 51 | * @api {GET} http://wittsay.cc/v1/users/:id/resource [getResource] 52 | * @apiGroup User 53 | * @apiDescription 获取指定用户的信息 54 | * @apiParam (path) {string} id 用户id 55 | * @apiParam (path) {string} resource 资源名 支持[article, comment] 56 | * @apiUse CODE_200 57 | * @apiUse CODE_500 58 | */ 59 | resource: async(req, res) => { 60 | const { id, resource } = req.params 61 | if (!id) return res.badRequest({ message: '至少需要用户id' }) 62 | try { 63 | if (resource == 'article') { 64 | return res.ok(await UserService.findArticle(id)) 65 | } 66 | if (resource == 'comment') { 67 | return res.ok(await UserService.findComment(id)) 68 | } 69 | return res.badRequest({ message: '需要指定合法资源' }) 70 | } catch (err) { 71 | return res.serverError() 72 | } 73 | }, 74 | 75 | /** 76 | * 77 | * @api {GET} http://wittsay.cc/v1/user/type [userType] 78 | * @apiGroup User 79 | * @apiDescription 获取默认的用户类型 80 | * @apiUse CODE_200 81 | * @apiUse CODE_500 82 | */ 83 | userType: async(req, res) => { 84 | try { 85 | const types = await UserService.findUserType() 86 | res.ok(types) 87 | } catch (err) { 88 | return res.serverError() 89 | } 90 | }, 91 | 92 | /** 93 | * 94 | * @api {POST} http://wittsay.cc/v1/user [create] 95 | * @apiGroup User 96 | * @apiDescription 创建一个用户 无需权限 97 | * @apiParam (body) {string} email 用户邮件 用作登录 98 | * @apiParam (body) {string} password 用户密码 6-20位之间 99 | * @apiParam (body) {string} username 用户名 100 | * @apiParam (body) {string} [phone] 手机号码 101 | * @apiUse CODE_200 102 | * @apiUse CODE_500 103 | */ 104 | create: async(req, res) => { 105 | const { username, password, email, phone } = req.allParams() 106 | if (!/^[0-9a-zA-Z]+@(([0-9a-zA-Z]+)[.])+[a-z]{2,4}$/.test(email)) { 107 | return res.badRequest({ message: '邮件地址不符合规范' }) 108 | } 109 | if (!password || password.length < 6 || password.length > 20) { 110 | return res.badRequest({ message: '密码不符合规则' }) 111 | } 112 | const token = uuid.v4() 113 | 114 | try { 115 | const created = await UserService.createUser({ 116 | email: email, 117 | password: password, 118 | username: username ? username : '新用户', 119 | phone: phone ? phone : '0', 120 | userType: 'notActive', 121 | userTitle: '未激活会员', 122 | activeTarget: token, 123 | }) 124 | const info = await UserService.sendMail({ 125 | id: created.id, 126 | email: email, 127 | subject: '维特博客-帐号激活', 128 | token: token, 129 | }) 130 | res.ok({ message: '注册邮件已发送' }) 131 | } catch (err) { 132 | return res.serverError() 133 | } 134 | }, 135 | 136 | /** 137 | * 138 | * @api {PUT} http://wittsay.cc/v1/user [update] 139 | * @apiGroup User 140 | * @apiDescription 修改一个用户信息 141 | * @apiParam (body) {string} [username] 用户名 142 | * @apiParam (body) {string} [phone] 手机号码 143 | * @apiParam (body) {string} [avatar] 头像图片地址 144 | * @apiUse CODE_200 145 | * @apiUse CODE_500 146 | */ 147 | update: async(req, res) => { 148 | const { username, phone, avatar } = req.allParams() 149 | const userID = req.headers.userID 150 | if (!username && !phone && !avatar) { 151 | return res.badRequest({ message: '至少需要修改一个参数' }) 152 | } 153 | let user = {} 154 | if (username) user.username = username 155 | if (phone) user.phone = phone 156 | if (avatar) user.avatar = avatar 157 | try { 158 | let updated = await UserService.updateUserForID(userID) 159 | delete updated[0].password 160 | return res.ok(updated[0]) 161 | } catch (err) { 162 | return res.serverError() 163 | } 164 | }, 165 | 166 | /** 167 | * 168 | * @api {POST} http://wittsay.cc/v1/users/:id/validate [validate] 169 | * @apiGroup User 170 | * @apiDescription 修改一个用户信息 171 | * @apiParam (body) {string} token 验证token 172 | * @apiUse CODE_200 173 | * @apiUse CODE_500 174 | */ 175 | validate: async(req, res) => { 176 | const { id } = req.params 177 | const { token } = req.allParams() 178 | if (!id || !token) return res.badRequest({ message: '缺少参数' }) 179 | try { 180 | const user = await UserService.findUserForId(id) 181 | if (!user || !user.id) return res.notFound({ message: '未找到此用户' }) 182 | if (user.activeTarget != token) return res.forbidden({ message: '验证失败' }) 183 | const updated = await UserService.updateUserForID(id, { 184 | userType: 'member', 185 | userTitle: '会员', 186 | activeTarget: '', 187 | }) 188 | res.ok(updated[0]) 189 | } catch (err) { 190 | return res.serverError() 191 | } 192 | }, 193 | 194 | 195 | } 196 | -------------------------------------------------------------------------------- /api/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unix/tcome/45c6e1c07ea16f94c0400ad9feb8fb07434e299a/api/models/.gitkeep -------------------------------------------------------------------------------- /api/models/Article.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2016/10/12. 3 | * @description :: 文章模型 4 | */ 5 | 6 | 7 | const map = function () { 8 | this.tags.forEach(tag => emit(tag, 1)) 9 | } 10 | const reduce = function (k, values) { 11 | var total = 0 12 | for (var i = 0; i < values.length; i++) { 13 | total += values[i] 14 | } 15 | return total 16 | 17 | } 18 | 19 | module.exports = { 20 | attributes: { 21 | title: { 22 | type: 'string', 23 | required: true, 24 | minLength: 1, 25 | maxLength: 50, 26 | }, 27 | content: { 28 | type: 'string', 29 | required: true, 30 | minLength: 5, 31 | }, 32 | abstract: { 33 | type: 'string', 34 | required: true, 35 | }, 36 | thumbnail: { 37 | type: 'string', 38 | }, 39 | tags: { 40 | type: 'array', 41 | }, 42 | authorName: { 43 | type: 'string', 44 | }, 45 | authorId: { 46 | type: 'string', 47 | required: true, 48 | }, 49 | readTotal: { 50 | type: 'integer', 51 | defaultsTo: 1, 52 | }, 53 | commentTotal: { 54 | type: 'integer', 55 | defaultsTo: 0, 56 | }, 57 | articleType: { 58 | type: 'string', 59 | enum: ['isReview', 'isActive', 'isDestroy'], 60 | required: true, 61 | }, 62 | 63 | }, 64 | // 65 | // afterCreate: (article, done) =>{ 66 | // Article.native((err, collection) =>{ 67 | // if (err) return res.serverError(err) 68 | // collection.mapReduce(map, reduce, {out: 'tags'}) 69 | // }) 70 | // done() 71 | // } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /api/models/Comment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2016/10/20. 3 | * @description :: 评论模型 4 | */ 5 | 6 | module.exports = { 7 | attributes: { 8 | // 分别记录发起人, 发起文章, 目标id 9 | authorId: { 10 | type: 'string', 11 | required: true, 12 | }, 13 | authorName: { 14 | type: 'string', 15 | }, 16 | articleId: { 17 | type: 'string', 18 | required: true, 19 | }, 20 | articleName: { 21 | type: 'string', 22 | required: true, 23 | }, 24 | targetId: { 25 | type: 'string', 26 | }, 27 | 28 | content: { 29 | type: 'string', 30 | required: true, 31 | minLength: 5, 32 | }, 33 | avatar: { 34 | type: 'string', 35 | }, 36 | }, 37 | 38 | beforeValidate: (values, cb) => { 39 | if (!values.authorId) return cb() 40 | UserService.findUserForId(values.authorId, (err, user) => { 41 | if (err) return cb(err) 42 | values.avatar = user.avatar 43 | cb() 44 | }) 45 | }, 46 | } 47 | -------------------------------------------------------------------------------- /api/models/Option.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2017/1/29. 3 | * @description :: 博客基本配置信息 4 | */ 5 | 6 | module.exports = { 7 | attributes: { 8 | blogName: { 9 | type: 'string', 10 | required: true, 11 | defaultsTo: '维特博客', 12 | }, 13 | blogSubhead: { 14 | type: 'string', 15 | }, 16 | recommended: { 17 | type: 'array', 18 | }, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /api/models/Session.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2016/10/12. 3 | * @description :: 储存用户会话资料 4 | */ 5 | 6 | module.exports = { 7 | attributes: { 8 | email: { 9 | type: 'email', 10 | required: true, 11 | }, 12 | clientToken: { 13 | type: 'string', 14 | }, 15 | userID: { 16 | type: 'string', 17 | }, 18 | userName: { 19 | type: 'string', 20 | }, 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /api/models/Tags.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2016/10/13. 3 | * @description :: 文章标签模型 4 | */ 5 | 6 | module.exports = { 7 | attributes: { 8 | name: { 9 | type: 'string', 10 | required: true, 11 | unique: true, 12 | }, 13 | value: { 14 | type: 'integer', 15 | required: true, 16 | defaultsTo: 1, 17 | }, 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /api/models/User.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User.js 3 | * 4 | * @description :: 用户模型 5 | */ 6 | 7 | const bcrypt = require('bcrypt') 8 | DEFAULT_TYPE = [{ 9 | type: 'admin', 10 | title: '管理员', 11 | }, { 12 | type: 'member', 13 | title: '会员', 14 | }, { 15 | type: 'prisoner', 16 | title: '禁言', 17 | }] 18 | 19 | module.exports = { 20 | 21 | attributes: { 22 | username: { 23 | type: 'string', 24 | required: true, 25 | }, 26 | password: { 27 | type: 'string', 28 | required: true, 29 | }, 30 | /** 31 | * 用户类型 32 | * 管理员-admin 33 | * 普通会员用户-member 34 | * 被禁止的-prisoner 35 | * 未激活的-notActive 36 | * */ 37 | userType: { 38 | type: 'string', 39 | enum: ['admin', 'member', 'prisoner', 'notActive'], 40 | required: true, 41 | }, 42 | userTitle: { 43 | type: 'string', 44 | required: true, 45 | }, 46 | email: { 47 | type: 'email', 48 | required: true, 49 | }, 50 | phone: { 51 | type: 'string', 52 | }, 53 | 54 | activeTarget: { 55 | type: 'string', 56 | }, 57 | avatar: { 58 | type: 'string', 59 | }, 60 | 61 | }, 62 | beforeCreate: (values, cb) => { 63 | bcrypt.genSalt(10, (err, salt) => { 64 | bcrypt.hash(values.password, salt, (err, hash) => { 65 | if (err) return cb(err) 66 | values.password = hash 67 | cb() 68 | }) 69 | }) 70 | }, 71 | beforeUpdate: (values, cb) => { 72 | if (!values.password) return cb() 73 | bcrypt.genSalt(10, (err, salt) => { 74 | bcrypt.hash(values.password, salt, (err, hash) => { 75 | if (err) return cb(err) 76 | values.password = hash 77 | cb() 78 | }) 79 | }) 80 | }, 81 | 82 | getDefault: () => { 83 | return DEFAULT_TYPE 84 | }, 85 | 86 | } 87 | 88 | -------------------------------------------------------------------------------- /api/policies/isActive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2016/12/27. 3 | * description :: 已激活权限判断 4 | */ 5 | module.exports = async(req, res, next) => { 6 | const email = req.allParams().email 7 | try { 8 | const user = await UserService.findUserForMail(email) 9 | if (!user || !user.email) return res.forbidden({ message: '该邮箱未注册' }) 10 | if (user.userType == 'notActive') return res.forbidden({ message: '该用户需要先进行验证' }) 11 | if (user.userType == 'prisoner') return res.forbidden({ message: '该用户已被禁用' }) 12 | return next() 13 | } catch (err) { 14 | return res.serverError(err) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /api/policies/isAdmin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2016/10/12. 3 | * @description :: 管理员[admin]权限判断 4 | */ 5 | 6 | module.exports = async(req, res, next) => { 7 | const email = req.headers.email 8 | try { 9 | const user = await UserService.findUserForMail(email) 10 | 11 | if (user && user.userType && user.userType == 'admin') return next() 12 | return res.forbidden({ message: '需要admin或更高权限' }) 13 | } catch (err) { 14 | return res.serverError() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /api/policies/isAuthenticated.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param req 4 | * @param res 5 | * @param next {} 6 | * @returns {} 7 | * @description :: 接口登录认证逻辑 8 | */ 9 | 10 | module.exports = async(req, res, next) => { 11 | const clientToken = req.headers.authorization 12 | if (!clientToken) return res.forbidden({ message: '未登录或token已过期' }) 13 | 14 | try { 15 | const session = await AuthService.findSessionForToken(clientToken) 16 | if (!session) return res.forbidden({ message: '未登录或token已过期' }) 17 | 18 | req.headers.email = session.email 19 | req.headers.userID = session.userID 20 | req.headers.username = session.username 21 | return next() 22 | } catch (err) { 23 | return res.serverError(err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /api/policies/notActive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param req 4 | * @param res 5 | * @param next 6 | * @description :: 用户激活,需要等待激活用户类型 7 | */ 8 | module.exports = async(req, res, next) => { 9 | const { id } = req.params 10 | if (!id) return res.badRequest({ message: '需要一个用户id' }) 11 | try { 12 | const user = await UserService.findUserForId(id) 13 | 14 | if (!user || !user.id) return res.forbidden({ message: '该用户不存在' }) 15 | if (user.userType != 'notActive') return res.forbidden({ message: '该用户无需再次验证' }) 16 | if (!user.activeTarget) return res.forbidden({ message: '请重新发送验证邮件' }) 17 | 18 | return next() 19 | } catch (err) { 20 | return res.serverError() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /api/policies/notCreated.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param req 4 | * @param res 5 | * @param next 6 | * @description :: 用户创建权限验证 检查数据库中是否已经有用户 7 | */ 8 | module.exports = async(req, res, next) => { 9 | const email = req.allParams().email 10 | try { 11 | const user = await UserService.findUserForMail(email) 12 | if (user) return res.forbidden({ message: '该邮箱已被注册' }) 13 | return next() 14 | } catch (err) { 15 | return res.serverError(err) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /api/policies/rateLimiting.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2016/10/20. 3 | * @param req 4 | * @param res 5 | * @param next 6 | * @description :: ratelimting 流量限制 7 | */ 8 | module.exports = async(req, res, next) => { 9 | const intervalTimeInMillisecond = 300000 10 | const userID = req.headers.userID 11 | try { 12 | const archives = await CommentService.findCommentForUser(userID) 13 | if (!archives || archives.length == 0) return next() 14 | const time = new Date().getTime() - new Date(archives[0].createdAt).getTime() 15 | 16 | if (time < intervalTimeInMillisecond) return res.forbidden({ message: '距离上次调用接口不足30秒' }) 17 | return next() 18 | } catch (err) { 19 | return res.serverError(err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /api/policies/selectApiVersion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2016/12/24. 3 | * @description :: Api version 4 | */ 5 | 6 | module.exports = function (req, res, next) { 7 | if (req.url.startsWith('/v1')) { 8 | req.url = req.url.split('/v1')[1] 9 | return next() 10 | } 11 | return res.notFound() 12 | } 13 | -------------------------------------------------------------------------------- /api/responses/badRequest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 400 (Bad Request) Handler 3 | * 4 | * Usage: 5 | * return res.badRequest(); 6 | * return res.badRequest(data); 7 | * return res.badRequest(data, 'some/specific/badRequest/view'); 8 | * 9 | * e.g.: 10 | * ``` 11 | * return res.badRequest( 12 | * 'Please choose a valid `password` (6-12 characters)', 13 | * 'trial/signup' 14 | * ); 15 | * ``` 16 | */ 17 | 18 | module.exports = function badRequest(data, options) { 19 | 20 | // Get access to `req`, `res`, & `sails` 21 | var req = this.req; 22 | var res = this.res; 23 | var sails = req._sails; 24 | 25 | // Set status code 26 | res.status(400); 27 | 28 | // Log error to console 29 | if (data !== undefined) { 30 | sails.log.verbose('Sending 400 ("Bad Request") response: \n',data); 31 | } 32 | else sails.log.verbose('Sending 400 ("Bad Request") response'); 33 | 34 | // Only include errors in response if application environment 35 | // is not set to 'production'. In production, we shouldn't 36 | // send back any identifying information about errors. 37 | // if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) { 38 | // data = undefined; 39 | // } 40 | 41 | // If the user-agent wants JSON, always respond with JSON 42 | // If views are disabled, revert to json 43 | if (req.wantsJSON || sails.config.hooks.views === false) { 44 | return res.jsonx(data); 45 | } 46 | 47 | // If second argument is a string, we take that to mean it refers to a view. 48 | // If it was omitted, use an empty object (`{}`) 49 | options = (typeof options === 'string') ? { view: options } : options || {}; 50 | 51 | // Attempt to prettify data for views, if it's a non-error object 52 | var viewData = data; 53 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 54 | try { 55 | viewData = require('util').inspect(data, {depth: null}); 56 | } 57 | catch(e) { 58 | viewData = undefined; 59 | } 60 | } 61 | 62 | // If a view was provided in options, serve it. 63 | // Otherwise try to guess an appropriate view, or if that doesn't 64 | // work, just send JSON. 65 | if (options.view) { 66 | return res.view(options.view, { data: viewData, title: 'Bad Request' }); 67 | } 68 | 69 | // If no second argument provided, try to serve the implied view, 70 | // but fall back to sending JSON(P) if no view can be inferred. 71 | else return res.guessView({ data: viewData, title: 'Bad Request' }, function couldNotGuessView () { 72 | return res.jsonx(data); 73 | }); 74 | 75 | }; 76 | 77 | -------------------------------------------------------------------------------- /api/responses/created.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 201 (CREATED) Response 3 | * 4 | * Usage: 5 | * return res.created(); 6 | * return res.created(data); 7 | * return res.created(data, 'auth/login'); 8 | * 9 | * @param {Object} data 10 | * @param {String|Object} options 11 | * - pass string to render specified view 12 | */ 13 | 14 | module.exports = function created (data, options) { 15 | 16 | // Get access to `req`, `res`, & `sails` 17 | var req = this.req; 18 | var res = this.res; 19 | var sails = req._sails; 20 | 21 | sails.log.silly('res.created() :: Sending 201 ("CREATED") response'); 22 | 23 | // Set status code 24 | res.status(201); 25 | 26 | // If appropriate, serve data as JSON(P) 27 | // If views are disabled, revert to json 28 | if (req.wantsJSON || sails.config.hooks.views === false) { 29 | return res.jsonx(data); 30 | } 31 | 32 | // If second argument is a string, we take that to mean it refers to a view. 33 | // If it was omitted, use an empty object (`{}`) 34 | options = (typeof options === 'string') ? { view: options } : options || {}; 35 | 36 | // Attempt to prettify data for views, if it's a non-error object 37 | var viewData = data; 38 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 39 | try { 40 | viewData = require('util').inspect(data, {depth: null}); 41 | } 42 | catch(e) { 43 | viewData = undefined; 44 | } 45 | } 46 | 47 | // If a view was provided in options, serve it. 48 | // Otherwise try to guess an appropriate view, or if that doesn't 49 | // work, just send JSON. 50 | if (options.view) { 51 | return res.view(options.view, { data: viewData, title: 'Created' }); 52 | } 53 | 54 | // If no second argument provided, try to serve the implied view, 55 | // but fall back to sending JSON(P) if no view can be inferred. 56 | else return res.guessView({ data: viewData, title: 'Created' }, function couldNotGuessView () { 57 | return res.jsonx(data); 58 | }); 59 | 60 | }; 61 | -------------------------------------------------------------------------------- /api/responses/forbidden.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 403 (Forbidden) Handler 3 | * 4 | * Usage: 5 | * return res.forbidden(); 6 | * return res.forbidden(err); 7 | * return res.forbidden(err, 'some/specific/forbidden/view'); 8 | * 9 | * e.g.: 10 | * ``` 11 | * return res.forbidden('Access denied.'); 12 | * ``` 13 | */ 14 | 15 | module.exports = function forbidden (data, options) { 16 | 17 | // Get access to `req`, `res`, & `sails` 18 | var req = this.req; 19 | var res = this.res; 20 | var sails = req._sails; 21 | 22 | // Set status code 23 | res.status(403); 24 | 25 | // Log error to console 26 | if (data !== undefined) { 27 | sails.log.verbose('Sending 403 ("Forbidden") response: \n',data); 28 | } 29 | else sails.log.verbose('Sending 403 ("Forbidden") response'); 30 | 31 | // Only include errors in response if application environment 32 | // is not set to 'production'. In production, we shouldn't 33 | // send back any identifying information about errors. 34 | // if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) { 35 | // data = undefined; 36 | // } 37 | 38 | // If the user-agent wants JSON, always respond with JSON 39 | // If views are disabled, revert to json 40 | if (req.wantsJSON || sails.config.hooks.views === false) { 41 | return res.jsonx(data); 42 | } 43 | 44 | // If second argument is a string, we take that to mean it refers to a view. 45 | // If it was omitted, use an empty object (`{}`) 46 | options = (typeof options === 'string') ? { view: options } : options || {}; 47 | 48 | // Attempt to prettify data for views, if it's a non-error object 49 | var viewData = data; 50 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 51 | try { 52 | viewData = require('util').inspect(data, {depth: null}); 53 | } 54 | catch(e) { 55 | viewData = undefined; 56 | } 57 | } 58 | 59 | // If a view was provided in options, serve it. 60 | // Otherwise try to guess an appropriate view, or if that doesn't 61 | // work, just send JSON. 62 | if (options.view) { 63 | return res.view(options.view, { data: viewData, title: 'Forbidden' }); 64 | } 65 | 66 | // If no second argument provided, try to serve the default view, 67 | // but fall back to sending JSON(P) if any errors occur. 68 | else return res.view('403', { data: viewData, title: 'Forbidden' }, function (err, html) { 69 | 70 | // If a view error occured, fall back to JSON(P). 71 | if (err) { 72 | // 73 | // Additionally: 74 | // • If the view was missing, ignore the error but provide a verbose log. 75 | if (err.code === 'E_VIEW_FAILED') { 76 | sails.log.verbose('res.forbidden() :: Could not locate view for error page (sending JSON instead). Details: ',err); 77 | } 78 | // Otherwise, if this was a more serious error, log to the console with the details. 79 | else { 80 | sails.log.warn('res.forbidden() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err); 81 | } 82 | return res.jsonx(data); 83 | } 84 | 85 | return res.send(html); 86 | }); 87 | 88 | }; 89 | 90 | -------------------------------------------------------------------------------- /api/responses/notFound.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 404 (Not Found) Handler 3 | * 4 | * Usage: 5 | * return res.notFound(); 6 | * return res.notFound(err); 7 | * return res.notFound(err, 'some/specific/notfound/view'); 8 | * 9 | * e.g.: 10 | * ``` 11 | * return res.notFound(); 12 | * ``` 13 | * 14 | * NOTE: 15 | * If a request doesn't match any explicit routes (i.e. `config/routes.js`) 16 | * or route blueprints (i.e. "shadow routes", Sails will call `res.notFound()` 17 | * automatically. 18 | */ 19 | 20 | module.exports = function notFound (data, options) { 21 | 22 | // Get access to `req`, `res`, & `sails` 23 | var req = this.req; 24 | var res = this.res; 25 | var sails = req._sails; 26 | 27 | // Set status code 28 | res.status(404); 29 | 30 | // Log error to console 31 | if (data !== undefined) { 32 | sails.log.verbose('Sending 404 ("Not Found") response: \n',data); 33 | } 34 | else sails.log.verbose('Sending 404 ("Not Found") response'); 35 | 36 | // Only include errors in response if application environment 37 | // is not set to 'production'. In production, we shouldn't 38 | // send back any identifying information about errors. 39 | // if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) { 40 | // data = undefined; 41 | // } 42 | 43 | // If the user-agent wants JSON, always respond with JSON 44 | // If views are disabled, revert to json 45 | if (req.wantsJSON || sails.config.hooks.views === false) { 46 | return res.jsonx(data); 47 | } 48 | 49 | // If second argument is a string, we take that to mean it refers to a view. 50 | // If it was omitted, use an empty object (`{}`) 51 | options = (typeof options === 'string') ? { view: options } : options || {}; 52 | 53 | // Attempt to prettify data for views, if it's a non-error object 54 | var viewData = data; 55 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 56 | try { 57 | viewData = require('util').inspect(data, {depth: null}); 58 | } 59 | catch(e) { 60 | viewData = undefined; 61 | } 62 | } 63 | 64 | // If a view was provided in options, serve it. 65 | // Otherwise try to guess an appropriate view, or if that doesn't 66 | // work, just send JSON. 67 | if (options.view) { 68 | return res.view(options.view, { data: viewData, title: 'Not Found' }); 69 | } 70 | 71 | // If no second argument provided, try to serve the default view, 72 | // but fall back to sending JSON(P) if any errors occur. 73 | else return res.view('404', { data: viewData, title: 'Not Found' }, function (err, html) { 74 | 75 | // If a view error occured, fall back to JSON(P). 76 | if (err) { 77 | // 78 | // Additionally: 79 | // • If the view was missing, ignore the error but provide a verbose log. 80 | if (err.code === 'E_VIEW_FAILED') { 81 | sails.log.verbose('res.notFound() :: Could not locate view for error page (sending JSON instead). Details: ',err); 82 | } 83 | // Otherwise, if this was a more serious error, log to the console with the details. 84 | else { 85 | sails.log.warn('res.notFound() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err); 86 | } 87 | return res.jsonx(data); 88 | } 89 | 90 | return res.send(html); 91 | }); 92 | 93 | }; 94 | 95 | -------------------------------------------------------------------------------- /api/responses/ok.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 200 (OK) Response 3 | * 4 | * Usage: 5 | * return res.ok(); 6 | * return res.ok(data); 7 | * return res.ok(data, 'auth/login'); 8 | * 9 | * @param {Object} data 10 | * @param {String|Object} options 11 | * - pass string to render specified view 12 | */ 13 | 14 | module.exports = function sendOK (data, options){ 15 | 16 | // Get access to `req`, `res`, & `sails` 17 | var req = this.req; 18 | var res = this.res; 19 | var sails = req._sails; 20 | 21 | sails.log.silly('res.ok() :: Sending 200 ("OK") response'); 22 | 23 | // Set status code 24 | res.status(200); 25 | 26 | // If appropriate, serve data as JSON(P) 27 | // If views are disabled, revert to json 28 | if (req.wantsJSON || sails.config.hooks.views === false){ 29 | return res.jsonx(data); 30 | } 31 | 32 | // If second argument is a string, we take that to mean it refers to a view. 33 | // If it was omitted, use an empty object (`{}`) 34 | options = (typeof options === 'string')? {view: options}: options || {}; 35 | 36 | // Attempt to prettify data for views, if it's a non-error object 37 | var viewData = data; 38 | if (!(viewData instanceof Error) && 'object' == typeof viewData){ 39 | try{ 40 | viewData = require('util') 41 | .inspect(data, {depth: null}); 42 | } 43 | catch (e){ 44 | viewData = undefined; 45 | } 46 | } 47 | 48 | // If a view was provided in options, serve it. 49 | // Otherwise try to guess an appropriate view, or if that doesn't 50 | // work, just send JSON. 51 | if (options.view){ 52 | return res.view(options.view, {data: viewData, title: 'OK'}); 53 | } 54 | 55 | // If no second argument provided, try to serve the implied view, 56 | // but fall back to sending JSON(P) if no view can be inferred. 57 | else return res.guessView({data: viewData, title: 'OK'}, function couldNotGuessView (){ 58 | return res.jsonx(data); 59 | }); 60 | 61 | }; 62 | -------------------------------------------------------------------------------- /api/responses/serverError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 500 (Server Error) Response 3 | * 4 | * Usage: 5 | * return res.serverError(); 6 | * return res.serverError(err); 7 | * return res.serverError(err, 'some/specific/error/view'); 8 | * 9 | * NOTE: 10 | * If something throws in a policy or controller, or an internal 11 | * error is encountered, Sails will call `res.serverError()` 12 | * automatically. 13 | */ 14 | 15 | module.exports = function serverError (data, options) { 16 | 17 | // Get access to `req`, `res`, & `sails` 18 | var req = this.req; 19 | var res = this.res; 20 | var sails = req._sails; 21 | 22 | // Set status code 23 | res.status(500); 24 | 25 | // Log error to console 26 | if (data !== undefined) { 27 | sails.log.error('Sending 500 ("Server Error") response: \n',data); 28 | } 29 | else sails.log.error('Sending empty 500 ("Server Error") response'); 30 | 31 | // Only include errors in response if application environment 32 | // is not set to 'production'. In production, we shouldn't 33 | // send back any identifying information about errors. 34 | // if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) { 35 | // data = undefined; 36 | // } 37 | 38 | // If the user-agent wants JSON, always respond with JSON 39 | // If views are disabled, revert to json 40 | if (req.wantsJSON || sails.config.hooks.views === false) { 41 | return res.jsonx(data); 42 | } 43 | 44 | // If second argument is a string, we take that to mean it refers to a view. 45 | // If it was omitted, use an empty object (`{}`) 46 | options = (typeof options === 'string') ? { view: options } : options || {}; 47 | 48 | // Attempt to prettify data for views, if it's a non-error object 49 | var viewData = data; 50 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 51 | try { 52 | viewData = require('util').inspect(data, {depth: null}); 53 | } 54 | catch(e) { 55 | viewData = undefined; 56 | } 57 | } 58 | 59 | // If a view was provided in options, serve it. 60 | // Otherwise try to guess an appropriate view, or if that doesn't 61 | // work, just send JSON. 62 | if (options.view) { 63 | return res.view(options.view, { data: viewData, title: 'Server Error' }); 64 | } 65 | 66 | // If no second argument provided, try to serve the default view, 67 | // but fall back to sending JSON(P) if any errors occur. 68 | else return res.view('500', { data: viewData, title: 'Server Error' }, function (err, html) { 69 | 70 | // If a view error occured, fall back to JSON(P). 71 | if (err) { 72 | // 73 | // Additionally: 74 | // • If the view was missing, ignore the error but provide a verbose log. 75 | if (err.code === 'E_VIEW_FAILED') { 76 | sails.log.verbose('res.serverError() :: Could not locate view for error page (sending JSON instead). Details: ',err); 77 | } 78 | // Otherwise, if this was a more serious error, log to the console with the details. 79 | else { 80 | sails.log.warn('res.serverError() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err); 81 | } 82 | return res.jsonx(data); 83 | } 84 | 85 | return res.send(html); 86 | }); 87 | 88 | }; 89 | 90 | -------------------------------------------------------------------------------- /api/services/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unix/tcome/45c6e1c07ea16f94c0400ad9feb8fb07434e299a/api/services/.gitkeep -------------------------------------------------------------------------------- /api/services/ArticleService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2016/10/12. 3 | * @description :: 文章相关服务 4 | * 5 | */ 6 | 7 | 8 | module.exports = { 9 | findArticleForID: id =>{ 10 | return Article.findOne({id: id}) 11 | }, 12 | findArticleCount: _ =>{ 13 | return Article.count({}) 14 | }, 15 | findArticleAll: (page, per_page) =>{ 16 | return Article 17 | .find({ 18 | where: {articleType: 'isActive'}, 19 | sort: {'createdAt': -1}, 20 | }, { 21 | fields: ['id', 'title', 'createdAt', 'readTotal', 'commentTotal', 'authorName', 'thumbnail', 'abstract'] 22 | }) 23 | .paginate({limit: per_page? per_page: 14, page: page? page: 1,}) 24 | }, 25 | findReviewForType: (type, page, per_page) =>{ 26 | const where = !type || type == 'all'? {articleType: {'!': ['isDestroy']}}: {articleType: type} 27 | return Article 28 | .find({ 29 | where: where, 30 | sort: {'createdAt': -1}, 31 | }, { 32 | fields: ['id', 'title', 'createdAt', 'readTotal', 'commentTotal', 'authorName', 'thumbnail', 'articleType', 'abstract'] 33 | }) 34 | .paginate({limit: per_page? per_page: 14, page: page? page: 1,}) 35 | }, 36 | 37 | 38 | updateArticle: (id, newArticle) =>{ 39 | return Article.update({id: id}, newArticle) 40 | }, 41 | 42 | createArticle: article =>{ 43 | return Article.create(article) 44 | }, 45 | 46 | destroyArticleForID: id =>{ 47 | return Article.destroy({id: id}) 48 | }, 49 | 50 | findArticleForKeyword: (keyword, page, per_page) =>{ 51 | return Article 52 | .find({ 53 | where: { 54 | title: {'contains': keyword}, 55 | articleType: 'isActive' 56 | }, 57 | sort: {'createdAt': -1}, 58 | }, { 59 | fields: ['id', 'title', 'createdAt', 'readTotal', 'commentTotal', 'authorName', 'thumbnail'] 60 | }) 61 | .paginate({limit: per_page? per_page: 14, page: page? page: 1,}) 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /api/services/AuthService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2016/10/12. 3 | * @description :: 管理用户session等相关服务 4 | */ 5 | const bcrypt = require('bcrypt') 6 | const uuid = require('node-uuid') 7 | 8 | module.exports = { 9 | findSessionForToken: clientToken =>{ 10 | return Session.findOne({clientToken: clientToken}) 11 | }, 12 | 13 | findSessionForMail: email =>{ 14 | return Session.findOne({email: email}) 15 | }, 16 | 17 | updateSessionForMail: (email, session) =>{ 18 | return Session.update({email: email}, session) 19 | }, 20 | 21 | createSession: session =>{ 22 | return Session.create(session) 23 | }, 24 | 25 | 26 | authUser: async (email, password) =>{ 27 | try { 28 | const user = await UserService.findUserForMail(email) 29 | if (!user) return {status: false, user: null, msg: '未找到用户'} 30 | 31 | const isApproved = await bcrypt.compareSync(password, user.password) 32 | if (!isApproved) return {status: false, user: null, msg: '密码有误'} 33 | 34 | const newSession = { 35 | email: user.email, 36 | username: user.username, 37 | userID: user.id, 38 | clientToken: uuid.v4() 39 | } 40 | const session = await AuthService.findSessionForMail(user.email) 41 | if (!session){ 42 | await AuthService.createSession(newSession) 43 | return {status: true, user: Object.assign(newSession, user), msg: '创建登录状态成功'} 44 | } 45 | await AuthService.updateSessionForMail(user.email, newSession) 46 | return {status: true, user: Object.assign(newSession, user), msg: '更新登录状态成功'} 47 | } catch (err){ 48 | return Promise.reject(err) 49 | } 50 | }, 51 | 52 | deleteSession: email =>{ 53 | return Session.destroy({email: email}) 54 | } 55 | } -------------------------------------------------------------------------------- /api/services/CommentService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2016/10/20. 3 | * @description :: 评论相关服务 4 | */ 5 | 6 | 7 | module.exports = { 8 | findCommentForArticle: id =>{ 9 | return Comment 10 | .find({ 11 | articleId: id, 12 | sort: 'createdAt' 13 | }) 14 | }, 15 | 16 | findCommentForUser: id =>{ 17 | return Comment 18 | .find({authorId: id, sort: 'createdAt DESC'}) 19 | .paginate({limit: 5}) 20 | }, 21 | 22 | findCommentLength: id =>{ 23 | return Comment.count({articleId: id}) 24 | }, 25 | 26 | createComment: comment =>{ 27 | return Comment.create(comment) 28 | } 29 | } -------------------------------------------------------------------------------- /api/services/OptionService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2017/1/29. 3 | */ 4 | 5 | module.exports = { 6 | findOptionAll: _ =>{ 7 | return Option.find() 8 | }, 9 | 10 | createOption: option =>{ 11 | return Option.create(option) 12 | }, 13 | 14 | updateOptionForID: (id, option) =>{ 15 | return Option.update({id: id}, option) 16 | } 17 | } -------------------------------------------------------------------------------- /api/services/TagService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2017/2/18. 3 | */ 4 | 5 | module.exports = { 6 | findArticlesForTag: tagString =>{ 7 | return Article 8 | .find({ 9 | where: {tags: {contains: tagString}}, 10 | sort: {'createdAt': -1}, 11 | }, { 12 | fields: ['id', 'title', 'createdAt', 'readTotal', 'commentTotal', 'authorName', 'thumbnail', 'articleType', 'abstract'] 13 | }) 14 | .paginate({limit: per_page? per_page: 14, page: page? page: 1,}) 15 | }, 16 | 17 | findTagsAll: (page, per_page) =>{ 18 | return Tags 19 | .find({ 20 | where: {}, 21 | sort: {'createdAt': -1}, 22 | }) 23 | .paginate({limit: per_page? per_page: 14, page: page? page: 1,}) 24 | }, 25 | findTagsForString: tagName =>{ 26 | return Tags.findOne({name: tagName}) 27 | }, 28 | createTag: tag =>{ 29 | return Tags.create(tag) 30 | }, 31 | updateTagForID: (id, newTag) =>{ 32 | return Tags.update({id: id}, newTag) 33 | }, 34 | destroyTagForID: id =>{ 35 | return Tags.destroy({id: id}) 36 | }, 37 | 38 | // 非重要数据,不再回调与排除错误,仅作展示 39 | saveTagsAsync: async (tags) =>{ 40 | try { 41 | for (let tag of tags){ 42 | const tagObject = await TagService.findTagsForString(tag) 43 | if (!tagObject|| !tagObject.id){ 44 | return await TagService.createTag({name: tag, value: 1}) 45 | } 46 | await TagService.updateTagForID(tagObject.id, Object.assign(tagObject, {value: tagObject.value + 1})) 47 | } 48 | } catch (err){ 49 | return Promise.reject(err) 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /api/services/UserService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 16/8/25. 3 | * @description :: 用户相关服务 4 | */ 5 | 6 | const bcrypt = require('bcrypt') 7 | const request = require('request') 8 | const email = require('../../config/email') 9 | const nodemailer = require('nodemailer') 10 | 11 | const makeMailHtml = (user) =>{ 12 | return ` 13 |
14 |
15 |
16 |
17 | 18 | 维特博客 · WittSay.cc 19 | 专注 沉淀 讨论 20 |
21 |

维特博客帐号激活

22 |

您好!您于刚刚注册维特博客账号,请在24小时内点击以下链接,完成操作:

23 | 24 | 26 | http://wittsay.cc/user/register/${user.id}/${user.token} 27 |

(若无法点击链接请复制到地址栏访问;您若未注册维特博客请忽略此邮件,敬请谅解)

28 |
29 |

感谢您的注册

30 |

31 | 32 |

33 | 本邮件由维特博客系统自动发出,请勿直接回复 34 | © support · 维特博客 WittSay.cc 35 |
36 | ` 37 | } 38 | 39 | module.exports = { 40 | /** 41 | * 42 | * @param userMail {string} 用户邮件地址 43 | * @param done {function} 回调参数 44 | * @return done {obj| array} (错误信息| 已创建用户) 45 | * @description :: 按邮箱地址查询用户 46 | */ 47 | findUserForMail: email =>{ 48 | return User.findOne({email: email}) 49 | }, 50 | 51 | /** 52 | * 53 | * @param userMail {string} 用户邮件地址 54 | * @param done {function} 回调参数 55 | * @description :: 按邮箱地址查询用户 56 | */ 57 | findUserForId: id =>{ 58 | return User.findOne({id: id}) 59 | }, 60 | 61 | /** 62 | * 63 | * @param filter {any} 过滤条件 64 | * @param cb {function} 回调参数 65 | * @return cb {obj| array} (错误信息| 已创建用户) 66 | * @description :: 查询所有用户 67 | */ 68 | findUserAll: (filter = null, cb) =>{ 69 | User 70 | .find({}) 71 | .exec((err, dataArray) =>{ 72 | if (err) return cb(err) 73 | cb(null, dataArray) 74 | }) 75 | }, 76 | 77 | findUserType: _ =>{ 78 | return User.getDefault() 79 | }, 80 | 81 | /** 82 | * 83 | * @param user {obj} 用户对象 84 | * @param cb {function} 回调参数 85 | * @return cb {obj| obj} (错误信息| 已创建用户) 86 | * @description :: 按对象创建用户 87 | */ 88 | createUser: user =>{ 89 | return User.create(user) 90 | }, 91 | 92 | updateUserForID: (id, newUser) =>{ 93 | return User.update({id: id}, newUser) 94 | }, 95 | 96 | sendMail: user =>{ 97 | const mailOptions = { 98 | from: '维特博客', // sender address mailfrom must be same with the user 99 | to: user.email, // list of receivers 100 | subject: user.subject, // Subject line 101 | html: makeMailHtml(user), // html body 102 | } 103 | const transporter = nodemailer.createTransport({ 104 | "host": email.mailhost, 105 | "port": 25, 106 | "secureConnection": false, // use SSL 107 | "auth": { 108 | "user": email.user, 109 | "pass": email.pass 110 | } 111 | }) 112 | return transporter.sendMail(mailOptions) 113 | 114 | }, 115 | findArticle: id =>{ 116 | return Article 117 | .find({authorId: id, articleType: {'!': ['isDestroy']}}) 118 | }, 119 | findComment: id =>{ 120 | return Comment 121 | .find({authorId: id, sort: 'createdAt DESC' }) 122 | .paginate({limit: 14}) 123 | }, 124 | 125 | } -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * app.js 3 | * 4 | * Use `app.js` to run your app without `sails lift`. 5 | * To start the server, run: `node app.js`. 6 | * 7 | * This is handy in situations where the sails CLI is not relevant or useful. 8 | * 9 | * For example: 10 | * => `node app.js` 11 | * => `forever start app.js` 12 | * => `node debug app.js` 13 | * => `modulus deploy` 14 | * => `heroku scale` 15 | * 16 | * 17 | * The same command-line arguments are supported, e.g.: 18 | * `node app.js --silent --port=80 --prod` 19 | */ 20 | 21 | // Ensure we're in the project directory, so relative paths work as expected 22 | // no matter where we actually lift from. 23 | process.chdir(__dirname); 24 | 25 | // Ensure a "sails" can be located: 26 | (function () { 27 | var sails 28 | try { 29 | sails = require('sails') 30 | } catch (e) { 31 | console.error('To run an app using `node app.js`, you usually need to have a version of `sails` installed in the same directory as your app.') 32 | console.error('To do that, run `npm install sails`') 33 | console.error('') 34 | console.error('Alternatively, if you have sails installed globally (i.e. you did `npm install -g sails`), you can use `sails lift`.') 35 | console.error('When you run `sails lift`, your app will still use a local `./node_modules/sails` dependency if it exists,') 36 | console.error('but if it doesn\'t, the app will run with the global sails instead!') 37 | return 38 | } 39 | 40 | // Try to get `rc` dependency 41 | var rc 42 | try { 43 | rc = require('rc') 44 | } catch (e0) { 45 | try { 46 | rc = require('sails/node_modules/rc') 47 | } catch (e1) { 48 | console.error('Could not find dependency: `rc`.') 49 | console.error('Your `.sailsrc` file(s) will be ignored.') 50 | console.error('To resolve this, run:') 51 | console.error('npm install rc --save') 52 | rc = function () { 53 | return {} 54 | } 55 | } 56 | } 57 | 58 | // Start server 59 | sails.lift(rc('sails')) 60 | })() 61 | -------------------------------------------------------------------------------- /config/blueprints.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Blueprint API Configuration 3 | * (sails.config.blueprints) 4 | * 5 | * These settings are for the global configuration of blueprint routes and 6 | * request options (which impact the behavior of blueprint actions). 7 | * 8 | * You may also override any of these settings on a per-controller basis 9 | * by defining a '_config' key in your controller definition, and assigning it 10 | * a configuration object with overrides for the settings in this file. 11 | * A lot of the configuration options below affect so-called "CRUD methods", 12 | * or your controllers' `find`, `create`, `update`, and `destroy` actions. 13 | * 14 | * It's important to realize that, even if you haven't defined these yourself, as long as 15 | * a model exists with the same name as the controller, Sails will respond with built-in CRUD 16 | * logic in the form of a JSON API, including support for sort, pagination, and filtering. 17 | * 18 | * For more information on the blueprint API, check out: 19 | * http://sailsjs.org/#!/documentation/reference/blueprint-api 20 | * 21 | * For more information on the settings in this file, see: 22 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.blueprints.html 23 | * 24 | */ 25 | 26 | module.exports.blueprints = { 27 | 28 | /*************************************************************************** 29 | * * 30 | * Action routes speed up the backend development workflow by * 31 | * eliminating the need to manually bind routes. When enabled, GET, POST, * 32 | * PUT, and DELETE routes will be generated for every one of a controller's * 33 | * actions. * 34 | * * 35 | * If an `index` action exists, additional naked routes will be created for * 36 | * it. Finally, all `actions` blueprints support an optional path * 37 | * parameter, `id`, for convenience. * 38 | * * 39 | * `actions` are enabled by default, and can be OK for production-- * 40 | * however, if you'd like to continue to use controller/action autorouting * 41 | * in a production deployment, you must take great care not to * 42 | * inadvertently expose unsafe/unintentional controller logic to GET * 43 | * requests. * 44 | * * 45 | ***************************************************************************/ 46 | 47 | actions: false, 48 | 49 | /*************************************************************************** 50 | * * 51 | * RESTful routes (`sails.config.blueprints.rest`) * 52 | * * 53 | * REST blueprints are the automatically generated routes Sails uses to * 54 | * expose a conventional REST API on top of a controller's `find`, * 55 | * `create`, `update`, and `destroy` actions. * 56 | * * 57 | * For example, a BoatController with `rest` enabled generates the * 58 | * following routes: * 59 | * ::::::::::::::::::::::::::::::::::::::::::::::::::::::: * 60 | * GET /boat -> BoatController.find * 61 | * GET /boat/:id -> BoatController.findOne * 62 | * POST /boat -> BoatController.create * 63 | * PUT /boat/:id -> BoatController.update * 64 | * DELETE /boat/:id -> BoatController.destroy * 65 | * * 66 | * `rest` blueprint routes are enabled by default, and are suitable for use * 67 | * in a production scenario, as long you take standard security precautions * 68 | * (combine w/ policies, etc.) * 69 | * * 70 | ***************************************************************************/ 71 | 72 | rest: false, 73 | 74 | /*************************************************************************** 75 | * * 76 | * Shortcut routes are simple helpers to provide access to a * 77 | * controller's CRUD methods from your browser's URL bar. When enabled, * 78 | * GET, POST, PUT, and DELETE routes will be generated for the * 79 | * controller's`find`, `create`, `update`, and `destroy` actions. * 80 | * * 81 | * `shortcuts` are enabled by default, but should be disabled in * 82 | * production. * 83 | * * 84 | ***************************************************************************/ 85 | 86 | shortcuts: false, 87 | 88 | /*************************************************************************** 89 | * * 90 | * An optional mount path for all blueprint routes on a controller, * 91 | * including `rest`, `actions`, and `shortcuts`. This allows you to take * 92 | * advantage of blueprint routing, even if you need to namespace your API * 93 | * methods. * 94 | * * 95 | * (NOTE: This only applies to blueprint autoroutes, not manual routes from * 96 | * `sails.config.routes`) * 97 | * * 98 | ***************************************************************************/ 99 | 100 | // prefix: '/api', 101 | 102 | /*************************************************************************** 103 | * * 104 | * An optional mount path for all REST blueprint routes on a controller. * 105 | * And it do not include `actions` and `shortcuts` routes. * 106 | * This allows you to take advantage of REST blueprint routing, * 107 | * even if you need to namespace your RESTful API methods * 108 | * * 109 | ***************************************************************************/ 110 | 111 | // restPrefix: '/v1', 112 | 113 | /*************************************************************************** 114 | * * 115 | * Whether to pluralize controller names in blueprint routes. * 116 | * * 117 | * (NOTE: This only applies to blueprint autoroutes, not manual routes from * 118 | * `sails.config.routes`) * 119 | * * 120 | * For example, REST blueprints for `FooController` with `pluralize` * 121 | * enabled: * 122 | * GET /foos/:id? * 123 | * POST /foos * 124 | * PUT /foos/:id? * 125 | * DELETE /foos/:id? * 126 | * * 127 | ***************************************************************************/ 128 | 129 | // pluralize: false, 130 | 131 | /*************************************************************************** 132 | * * 133 | * Whether the blueprint controllers should populate model fetches with * 134 | * data from other models which are linked by associations * 135 | * * 136 | * If you have a lot of data in one-to-many associations, leaving this on * 137 | * may result in very heavy api calls * 138 | * * 139 | ***************************************************************************/ 140 | 141 | // populate: true, 142 | 143 | /**************************************************************************** 144 | * * 145 | * Whether to run Model.watch() in the find and findOne blueprint actions. * 146 | * Can be overridden on a per-model basis. * 147 | * * 148 | ****************************************************************************/ 149 | 150 | // autoWatch: true, 151 | 152 | /**************************************************************************** 153 | * * 154 | * The default number of records to show in the response from a "find" * 155 | * action. Doubles as the default size of populated arrays if populate is * 156 | * true. * 157 | * * 158 | ****************************************************************************/ 159 | 160 | // defaultLimit: 30 161 | 162 | } 163 | -------------------------------------------------------------------------------- /config/bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bootstrap 3 | * (sails.config.bootstrap) 4 | * 5 | * An asynchronous bootstrap function that runs before your Sails app gets lifted. 6 | * This gives you an opportunity to set up your data model, run jobs, or perform some special logic. 7 | * 8 | * For more information on bootstrapping your app, check out: 9 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.bootstrap.html 10 | */ 11 | 12 | module.exports.bootstrap = function (cb) { 13 | // It's very important to trigger this callback method when you are finished 14 | // with the bootstrap! (otherwise your server will never lift, since it's waiting on the bootstrap) 15 | 16 | cb() 17 | } 18 | -------------------------------------------------------------------------------- /config/connections.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Connections 3 | * (sails.config.connections) 4 | * 5 | */ 6 | 7 | const pro = { 8 | adapter: 'sails-mongo', 9 | host: 'pro.mongo', 10 | port: 27017, 11 | user: process.env.MONGODB_USER, 12 | password: process.env.MONGODB_PASS, 13 | database: 'blog', 14 | } 15 | const dev = { 16 | adapter: 'sails-mongo', 17 | host: '127.0.0.1', 18 | port: 27017, 19 | user: 'user', 20 | password: 'abcd123456', 21 | database: 'blog', 22 | } 23 | 24 | module.exports.connections = { 25 | 26 | /*************************************************************************** 27 | * * 28 | * Local disk storage for DEVELOPMENT ONLY * 29 | * * 30 | * Installed by default. * 31 | * * 32 | ***************************************************************************/ 33 | localDiskDb: { 34 | adapter: 'sails-disk', 35 | }, 36 | 37 | /*************************************************************************** 38 | * * 39 | * MySQL is the world's most popular relational database. * 40 | * http://en.wikipedia.org/wiki/MySQL * 41 | * * 42 | * Run: npm install sails-mysql * 43 | * * 44 | ***************************************************************************/ 45 | // someMysqlServer: { 46 | // adapter: 'sails-mysql', 47 | // host: 'YOUR_MYSQL_SERVER_HOSTNAME_OR_IP_ADDRESS', 48 | // user: 'YOUR_MYSQL_USER', //optional 49 | // password: 'YOUR_MYSQL_PASSWORD', //optional 50 | // database: 'YOUR_MYSQL_DB' //optional 51 | // }, 52 | 53 | /*************************************************************************** 54 | * * 55 | * MongoDB is the leading NoSQL database. * 56 | * http://en.wikipedia.org/wiki/MongoDB * 57 | * * 58 | * Run: npm install sails-mongo * 59 | * * 60 | ***************************************************************************/ 61 | mongo: process.env.NODE_ENV === 'production' ? pro : dev, 62 | 63 | /*************************************************************************** 64 | * * 65 | * PostgreSQL is another officially supported relational database. * 66 | * http://en.wikipedia.org/wiki/PostgreSQL * 67 | * * 68 | * Run: npm install sails-postgresql * 69 | * * 70 | * * 71 | ***************************************************************************/ 72 | // somePostgresqlServer: { 73 | // adapter: 'sails-postgresql', 74 | // host: 'YOUR_POSTGRES_SERVER_HOSTNAME_OR_IP_ADDRESS', 75 | // user: 'YOUR_POSTGRES_USER', // optional 76 | // password: 'YOUR_POSTGRES_PASSWORD', // optional 77 | // database: 'YOUR_POSTGRES_DB' //optional 78 | // } 79 | 80 | 81 | /*************************************************************************** 82 | * * 83 | * More adapters: https://github.com/balderdashy/sails * 84 | * * 85 | ***************************************************************************/ 86 | 87 | } 88 | -------------------------------------------------------------------------------- /config/cors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cross-Origin Resource Sharing (CORS) Settings 3 | * (sails.config.cors) 4 | * 5 | * CORS is like a more modern version of JSONP-- it allows your server/API 6 | * to successfully respond to requests from client-side JavaScript code 7 | * running on some other domain (e.g. google.com) 8 | * Unlike JSONP, it works with POST, PUT, and DELETE requests 9 | * 10 | * For more information on CORS, check out: 11 | * http://en.wikipedia.org/wiki/Cross-origin_resource_sharing 12 | * 13 | * Note that any of these settings (besides 'allRoutes') can be changed on a per-route basis 14 | * by adding a "cors" object to the route configuration: 15 | * 16 | * '/get foo': { 17 | * controller: 'foo', 18 | * action: 'bar', 19 | * cors: { 20 | * origin: 'http://foobar.com,https://owlhoot.com' 21 | * } 22 | * } 23 | * 24 | * For more information on this configuration file, see: 25 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.cors.html 26 | * 27 | */ 28 | 29 | module.exports.cors = { 30 | 31 | /*************************************************************************** 32 | * * 33 | * Allow CORS on all routes by default? If not, you must enable CORS on a * 34 | * per-route basis by either adding a "cors" configuration object to the * 35 | * route config, or setting "cors:true" in the route config to use the * 36 | * default settings below. * 37 | * * 38 | ***************************************************************************/ 39 | 40 | allRoutes: true, 41 | 42 | /*************************************************************************** 43 | * * 44 | * Which domains which are allowed CORS access? This can be a * 45 | * comma-delimited list of hosts (beginning with http:// or https://) or * 46 | * "*" to allow all domains CORS access. * 47 | * * 48 | ***************************************************************************/ 49 | 50 | origin: '*', 51 | 52 | /*************************************************************************** 53 | * * 54 | * Allow cookies to be shared for CORS requests? * 55 | * * 56 | ***************************************************************************/ 57 | 58 | credentials: false, 59 | 60 | /*************************************************************************** 61 | * * 62 | * Which methods should be allowed for CORS requests? This is only used in * 63 | * response to preflight requests (see article linked above for more info) * 64 | * * 65 | ***************************************************************************/ 66 | 67 | methods: 'GET, POST, PUT, DELETE, OPTIONS, HEAD', 68 | 69 | /*************************************************************************** 70 | * * 71 | * Which headers should be allowed for CORS requests? This is only used in * 72 | * response to preflight requests. * 73 | * * 74 | ***************************************************************************/ 75 | 76 | headers: 'content-type, authorization, Access-Control-Allow-Headers, Access-Control-Allow-Origin, Authorization, X-Requested-With', 77 | 78 | 79 | exposeHeaders: 'total, Total', 80 | 81 | } 82 | -------------------------------------------------------------------------------- /config/csrf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cross-Site Request Forgery Protection Settings 3 | * (sails.config.csrf) 4 | * 5 | * CSRF tokens are like a tracking chip. While a session tells the server that a user 6 | * "is who they say they are", a csrf token tells the server "you are where you say you are". 7 | * 8 | * When enabled, all non-GET requests to the Sails server must be accompanied by 9 | * a special token, identified as the '_csrf' parameter. 10 | * 11 | * This option protects your Sails app against cross-site request forgery (or CSRF) attacks. 12 | * A would-be attacker needs not only a user's session cookie, but also this timestamped, 13 | * secret CSRF token, which is refreshed/granted when the user visits a URL on your app's domain. 14 | * 15 | * This allows us to have certainty that our users' requests haven't been hijacked, 16 | * and that the requests they're making are intentional and legitimate. 17 | * 18 | * This token has a short-lived expiration timeline, and must be acquired by either: 19 | * 20 | * (a) For traditional view-driven web apps: 21 | * Fetching it from one of your views, where it may be accessed as 22 | * a local variable, e.g.: 23 | *
24 | * 25 | *
26 | * 27 | * or (b) For AJAX/Socket-heavy and/or single-page apps: 28 | * Sending a GET request to the `/csrfToken` route, where it will be returned 29 | * as JSON, e.g.: 30 | * { _csrf: 'ajg4JD(JGdajhLJALHDa' } 31 | * 32 | * 33 | * Enabling this option requires managing the token in your front-end app. 34 | * For traditional web apps, it's as easy as passing the data from a view into a form action. 35 | * In AJAX/Socket-heavy apps, just send a GET request to the /csrfToken route to get a valid token. 36 | * 37 | * For more information on CSRF, check out: 38 | * http://en.wikipedia.org/wiki/Cross-site_request_forgery 39 | * 40 | * For more information on this configuration file, including info on CSRF + CORS, see: 41 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.csrf.html 42 | * 43 | */ 44 | 45 | /**************************************************************************** 46 | * * 47 | * Enabled CSRF protection for your site? * 48 | * * 49 | ****************************************************************************/ 50 | 51 | // module.exports.csrf = false; 52 | 53 | /**************************************************************************** 54 | * * 55 | * You may also specify more fine-grained settings for CSRF, including the * 56 | * domains which are allowed to request the CSRF token via AJAX. These * 57 | * settings override the general CORS settings in your config/cors.js file. * 58 | * * 59 | ****************************************************************************/ 60 | 61 | // module.exports.csrf = { 62 | // grantTokenViaAjax: true, 63 | // origin: '' 64 | // } 65 | -------------------------------------------------------------------------------- /config/email.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WittBulter on 2016/10/25. 3 | * @description :: 邮件相关服务支持 4 | */ 5 | 6 | module.exports = { 7 | 8 | mailhost: process.env.MAIL_HOST, 9 | user: process.env.MAIL_USER, 10 | pass: process.env.MAIL_PASS, 11 | support: process.env.MAIL_SUPPORT, 12 | } 13 | -------------------------------------------------------------------------------- /config/env/development.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Development environment settings 3 | * 4 | * This file can include shared settings for a development team, 5 | * such as API keys or remote database passwords. If you're using 6 | * a version control solution for your Sails app, this file will 7 | * be committed to your repository unless you add it to your .gitignore 8 | * file. If your repository will be publicly viewable, don't add 9 | * any private information to this file! 10 | * 11 | */ 12 | 13 | module.exports = { 14 | 15 | /*************************************************************************** 16 | * Set the default database connection for models in the development * 17 | * environment (see config/connections.js and config/models.js ) * 18 | ***************************************************************************/ 19 | 20 | // models: { 21 | // connection: 'someMongodbServer' 22 | // } 23 | paths: { 24 | public: './portal', 25 | }, 26 | 27 | } 28 | -------------------------------------------------------------------------------- /config/env/production.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Production environment settings 3 | * 4 | * This file can include shared settings for a production environment, 5 | * such as API keys or remote database passwords. If you're using 6 | * a version control solution for your Sails app, this file will 7 | * be committed to your repository unless you add it to your .gitignore 8 | * file. If your repository will be publicly viewable, don't add 9 | * any private information to this file! 10 | * 11 | */ 12 | 13 | module.exports = { 14 | 15 | /*************************************************************************** 16 | * Set the default database connection for models in the production * 17 | * environment (see config/connections.js and config/models.js ) * 18 | ***************************************************************************/ 19 | 20 | // models: { 21 | // connection: 'mongo' 22 | // }, 23 | 24 | /*************************************************************************** 25 | * Set the port in the production environment to 80 * 26 | ***************************************************************************/ 27 | 28 | port: 1337, 29 | 30 | /*************************************************************************** 31 | * Set the log level in production environment to "silent" * 32 | ***************************************************************************/ 33 | 34 | log: { 35 | level: 'silent', 36 | }, 37 | 38 | paths: { 39 | public: './portal', 40 | }, 41 | 42 | } 43 | -------------------------------------------------------------------------------- /config/globals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global Variable Configuration 3 | * (sails.config.globals) 4 | * 5 | * Configure which global variables which will be exposed 6 | * automatically by Sails. 7 | * 8 | * For more information on configuration, check out: 9 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.globals.html 10 | */ 11 | module.exports.globals = { 12 | 13 | /**************************************************************************** 14 | * * 15 | * Expose the lodash installed in Sails core as a global variable. If this * 16 | * is disabled, like any other node module you can always run npm install * 17 | * lodash --save, then var _ = require('lodash') at the top of any file. * 18 | * * 19 | ****************************************************************************/ 20 | 21 | _: false, 22 | 23 | /**************************************************************************** 24 | * * 25 | * Expose the async installed in Sails core as a global variable. If this is * 26 | * disabled, like any other node module you can always run npm install async * 27 | * --save, then var async = require('async') at the top of any file. * 28 | * * 29 | ****************************************************************************/ 30 | 31 | async: false, 32 | 33 | /**************************************************************************** 34 | * * 35 | * Expose the sails instance representing your app. If this is disabled, you * 36 | * can still get access via req._sails. * 37 | * * 38 | ****************************************************************************/ 39 | 40 | sails: true, 41 | 42 | /**************************************************************************** 43 | * * 44 | * Expose each of your app's services as global variables (using their * 45 | * "globalId"). E.g. a service defined in api/models/NaturalLanguage.js * 46 | * would have a globalId of NaturalLanguage by default. If this is disabled, * 47 | * you can still access your services via sails.services.* * 48 | * * 49 | ****************************************************************************/ 50 | 51 | services: true, 52 | 53 | /**************************************************************************** 54 | * * 55 | * Expose each of your app's models as global variables (using their * 56 | * "globalId"). E.g. a model defined in api/models/User.js would have a * 57 | * globalId of User by default. If this is disabled, you can still access * 58 | * your models via sails.models.*. * 59 | * * 60 | ****************************************************************************/ 61 | 62 | models: true, 63 | 64 | } 65 | -------------------------------------------------------------------------------- /config/http.js: -------------------------------------------------------------------------------- 1 | /** 2 | * HTTP Server Settings 3 | * (sails.config.http) 4 | * 5 | * Configuration for the underlying HTTP server in Sails. 6 | * Only applies to HTTP requests (not WebSockets) 7 | * 8 | * For more information on configuration, check out: 9 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.http.html 10 | */ 11 | const express = require('express') 12 | const skipper = require('skipper') 13 | 14 | module.exports.http = { 15 | 16 | /**************************************************************************** 17 | * * 18 | * Express middleware to use for every Sails request. To add custom * 19 | * middleware to the mix, add a function to the middleware config object and * 20 | * add its key to the "order" array. The $custom key is reserved for * 21 | * backwards-compatibility with Sails v0.9.x apps that use the * 22 | * `customMiddleware` config option. * 23 | * * 24 | ****************************************************************************/ 25 | middleware: { 26 | 27 | /*************************************************************************** 28 | * * 29 | * The order in which middleware should be run for HTTP request. (the Sails * 30 | * router is invoked by the "router" middleware below.) * 31 | * * 32 | ***************************************************************************/ 33 | 34 | order: [ 35 | // 'startRequestTimer', 36 | // 'cookieParser', 37 | // 'myRequestLogger', 38 | 'bodyParser', 39 | 'handleBodyParserError', 40 | // 'compress', 41 | // 'methodOverride', 42 | 'poweredBy', 43 | '$custom', 44 | 'router', 45 | 'www', 46 | 'favicon', 47 | '404', 48 | '500', 49 | ], 50 | 51 | compress: require('compression')(), 52 | 53 | /**************************************************************************** 54 | * * 55 | * Example custom middleware; logs each request to the console. * 56 | * * 57 | ****************************************************************************/ 58 | 59 | myRequestLogger: function (req, res, next) { 60 | console.log('Requested :: ', req.method, req.url) 61 | return next() 62 | }, 63 | 64 | 65 | /*************************************************************************** 66 | * * 67 | * The body parser that will handle incoming multipart HTTP requests. By * 68 | * default as of v0.10, Sails uses * 69 | * [skipper](http://github.com/balderdashy/skipper). See * 70 | * http://www.senchalabs.org/connect/multipart.html for other options. * 71 | * * 72 | * Note that Sails uses an internal instance of Skipper by default; to * 73 | * override it and specify more options, make sure to "npm install skipper" * 74 | * in your project first. You can also specify a different body parser or * 75 | * a custom function with req, res and next parameters (just like any other * 76 | * middleware function). * 77 | * * 78 | ***************************************************************************/ 79 | 80 | // bodyParser: require('skipper')({strict: true}) 81 | 82 | 83 | }, 84 | 85 | /*************************************************************************** 86 | * * 87 | * The number of seconds to cache flat files on disk being served by * 88 | * Express static middleware (by default, these files are in `.tmp/public`) * 89 | * * 90 | * The HTTP static cache is only active in a 'production' environment, * 91 | * since that's the only time Express will cache flat-files. * 92 | * * 93 | ***************************************************************************/ 94 | 95 | cache: 31557600000, 96 | 97 | customMiddleware: app => { 98 | app.use('/doc', express.static('doc')) 99 | app.all('/*', (req, res, next) => { 100 | if (req.path.includes('/v1/')) return next() 101 | res.sendfile('index.html', { root: './portal/' }) 102 | }) 103 | }, 104 | 105 | bodyParser: () => skipper({ limit: '50mb' }), 106 | } 107 | -------------------------------------------------------------------------------- /config/i18n.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internationalization / Localization Settings 3 | * (sails.config.i18n) 4 | * 5 | * If your app will touch people from all over the world, i18n (or internationalization) 6 | * may be an important part of your international strategy. 7 | * 8 | * 9 | * For more informationom i18n in Sails, check out: 10 | * http://sailsjs.org/#!/documentation/concepts/Internationalization 11 | * 12 | * For a complete list of i18n options, see: 13 | * https://github.com/mashpie/i18n-node#list-of-configuration-options 14 | * 15 | * 16 | */ 17 | 18 | module.exports.i18n = { 19 | 20 | /*************************************************************************** 21 | * * 22 | * Which locales are supported? * 23 | * * 24 | ***************************************************************************/ 25 | 26 | // locales: ['en', 'es', 'fr', 'de'], 27 | 28 | /**************************************************************************** 29 | * * 30 | * What is the default locale for the site? Note that this setting will be * 31 | * overridden for any request that sends an "Accept-Language" header (i.e. * 32 | * most browsers), but it's still useful if you need to localize the * 33 | * response for requests made by non-browser clients (e.g. cURL). * 34 | * * 35 | ****************************************************************************/ 36 | 37 | // defaultLocale: 'en', 38 | 39 | /**************************************************************************** 40 | * * 41 | * Automatically add new keys to locale (translation) files when they are * 42 | * encountered during a request? * 43 | * * 44 | ****************************************************************************/ 45 | 46 | // updateFiles: false, 47 | 48 | /**************************************************************************** 49 | * * 50 | * Path (relative to app root) of directory to store locale (translation) * 51 | * files in. * 52 | * * 53 | ****************************************************************************/ 54 | 55 | // localesDirectory: '/config/locales' 56 | 57 | } 58 | -------------------------------------------------------------------------------- /config/local.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local environment settings 3 | * 4 | * Use this file to specify configuration settings for use while developing 5 | * the app on your personal system: for example, this would be a good place 6 | * to store database or email passwords that apply only to you, and shouldn't 7 | * be shared with others in your organization. 8 | * 9 | * These settings take precedence over all other config files, including those 10 | * in the env/ subfolder. 11 | * 12 | * PLEASE NOTE: 13 | * local.js is included in your .gitignore, so if you're using git 14 | * as a version control solution for your Sails app, keep in mind that 15 | * this file won't be committed to your repository! 16 | * 17 | * Good news is, that means you can specify configuration for your local 18 | * machine in this file without inadvertently committing personal information 19 | * (like database passwords) to the repo. Plus, this prevents other members 20 | * of your team from commiting their local configuration changes on top of yours. 21 | * 22 | * In a production environment, you probably want to leave this file out 23 | * entirely and leave all your settings in env/production.js 24 | * 25 | * 26 | * For more information, check out: 27 | * http://sailsjs.org/#!/documentation/anatomy/myApp/config/local.js.html 28 | */ 29 | 30 | module.exports = { 31 | 32 | /*************************************************************************** 33 | * Your SSL certificate and key, if you want to be able to serve HTTP * 34 | * responses over https:// and/or use websockets over the wss:// protocol * 35 | * (recommended for HTTP, strongly encouraged for WebSockets) * 36 | * * 37 | * In this example, we'll assume you created a folder in your project, * 38 | * `config/ssl` and dumped your certificate/key files there: * 39 | ***************************************************************************/ 40 | 41 | // ssl: { 42 | // ca: require('fs').readFileSync(__dirname + './ssl/my_apps_ssl_gd_bundle.crt'), 43 | // key: require('fs').readFileSync(__dirname + './ssl/my_apps_ssl.key'), 44 | // cert: require('fs').readFileSync(__dirname + './ssl/my_apps_ssl.crt') 45 | // }, 46 | 47 | /*************************************************************************** 48 | * The `port` setting determines which TCP port your app will be * 49 | * deployed on. * 50 | * * 51 | * Ports are a transport-layer concept designed to allow many different * 52 | * networking applications run at the same time on a single computer. * 53 | * More about ports: * 54 | * http://en.wikipedia.org/wiki/Port_(computer_networking) * 55 | * * 56 | * By default, if it's set, Sails uses the `PORT` environment variable. * 57 | * Otherwise it falls back to port 1337. * 58 | * * 59 | * In env/production.js, you'll probably want to change this setting * 60 | * to 80 (http://) or 443 (https://) if you have an SSL certificate * 61 | ***************************************************************************/ 62 | 63 | port: 1337, 64 | 65 | /*************************************************************************** 66 | * The runtime "environment" of your Sails app is either typically * 67 | * 'development' or 'production'. * 68 | * * 69 | * In development, your Sails app will go out of its way to help you * 70 | * (for instance you will receive more descriptive error and * 71 | * debugging output) * 72 | * * 73 | * In production, Sails configures itself (and its dependencies) to * 74 | * optimize performance. You should always put your app in production mode * 75 | * before you deploy it to a server. This helps ensure that your Sails * 76 | * app remains stable, performant, and scalable. * 77 | * * 78 | * By default, Sails sets its environment using the `NODE_ENV` environment * 79 | * variable. If NODE_ENV is not set, Sails will run in the * 80 | * 'development' environment. * 81 | ***************************************************************************/ 82 | 83 | environment: process.env.NODE_ENV || 'development', 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /config/locales/_README.md: -------------------------------------------------------------------------------- 1 | # Internationalization / Localization Settings 2 | 3 | > Also see the official docs on internationalization/localization: 4 | > http://links.sailsjs.org/docs/config/locales 5 | 6 | ## Locales 7 | All locale files live under `config/locales`. Here is where you can add translations 8 | as JSON key-value pairs. The name of the file should match the language that you are supporting, which allows for automatic language detection based on request headers. 9 | 10 | Here is an example locale stringfile for the Spanish language (`config/locales/es.json`): 11 | ```json 12 | { 13 | "Hello!": "Hola!", 14 | "Hello %s, how are you today?": "¿Hola %s, como estas?", 15 | } 16 | ``` 17 | ## Usage 18 | Locales can be accessed in controllers/policies through `res.i18n()`, or in views through the `__(key)` or `i18n(key)` functions. 19 | Remember that the keys are case sensitive and require exact key matches, e.g. 20 | 21 | ```ejs 22 |

<%= __('Welcome to PencilPals!') %>

23 |

<%= i18n('Hello %s, how are you today?', 'Pencil Maven') %>

24 |

<%= i18n('That\'s right-- you can use either i18n() or __()') %>

25 | ``` 26 | 27 | ## Configuration 28 | Localization/internationalization config can be found in `config/i18n.js`, from where you can set your supported locales. 29 | -------------------------------------------------------------------------------- /config/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Willkommen", 3 | "A brand new app.": "Eine neue App." 4 | } 5 | -------------------------------------------------------------------------------- /config/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Welcome", 3 | "A brand new app.": "A brand new app." 4 | } 5 | -------------------------------------------------------------------------------- /config/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Bienvenido", 3 | "A brand new app.": "Una nueva aplicación." 4 | } 5 | -------------------------------------------------------------------------------- /config/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Bienvenue", 3 | "A brand new app.": "Une toute nouvelle application." 4 | } 5 | -------------------------------------------------------------------------------- /config/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Built-in Log Configuration 3 | * (sails.config.log) 4 | * 5 | * Configure the log level for your app, as well as the transport 6 | * (Underneath the covers, Sails uses Winston for logging, which 7 | * allows for some pretty neat custom transports/adapters for log messages) 8 | * 9 | * For more information on the Sails logger, check out: 10 | * http://sailsjs.org/#!/documentation/concepts/Logging 11 | */ 12 | 13 | module.exports.log = { 14 | 15 | /*************************************************************************** 16 | * * 17 | * Valid `level` configs: i.e. the minimum log level to capture with * 18 | * sails.log.*() * 19 | * * 20 | * The order of precedence for log levels from lowest to highest is: * 21 | * silly, verbose, info, debug, warn, error * 22 | * * 23 | * You may also set the level to "silent" to suppress all logs. * 24 | * * 25 | ***************************************************************************/ 26 | 27 | // level: 'info' 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /config/models.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default model configuration 3 | * (sails.config.models) 4 | * 5 | * Unless you override them, the following properties will be included 6 | * in each of your models. 7 | * 8 | * For more info on Sails models, see: 9 | * http://sailsjs.org/#!/documentation/concepts/ORM 10 | */ 11 | 12 | module.exports.models = { 13 | 14 | /*************************************************************************** 15 | * * 16 | * Your app's default connection. i.e. the name of one of your app's * 17 | * connections (see `config/connections.js`) * 18 | * * 19 | ***************************************************************************/ 20 | connection: 'mongo', 21 | 22 | autoCreatedAt: true, 23 | autoUpdatedAt: true, 24 | 25 | /*************************************************************************** 26 | * * 27 | * How and whether Sails will attempt to automatically rebuild the * 28 | * tables/collections/etc. in your schema. * 29 | * * 30 | * See http://sailsjs.org/#!/documentation/concepts/ORM/model-settings.html * 31 | * * 32 | ***************************************************************************/ 33 | migrate: 'safe', 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /config/policies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Policy Mappings 3 | * (sails.config.policies) 4 | * 5 | * Policies are simple functions which run **before** your controllers. 6 | * You can apply one or more policies to a given controller, or protect 7 | * its actions individually. 8 | * 9 | * Any policy file (e.g. `api/policies/authenticated.js`) can be accessed 10 | * below by its filename, minus the extension, (e.g. "authenticated") 11 | * 12 | * For more information on how policies work, see: 13 | * http://sailsjs.org/#!/documentation/concepts/Policies 14 | * 15 | * For more information on configuring policies, check out: 16 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.policies.html 17 | */ 18 | 19 | 20 | module.exports.policies = { 21 | 22 | /*************************************************************************** 23 | * * 24 | * Default policy for all controllers and actions (`true` allows public * 25 | * access) * 26 | * * 27 | ***************************************************************************/ 28 | 29 | // 登录无需验证token 需要激活状态 用户退出需要先登录 30 | AuthController: { 31 | '*': true, 32 | logout: 'isAuthenticated', 33 | login: 'isActive', 34 | }, 35 | 36 | // 修改删除文章需要管理员权限 展示无需权限 37 | ArticleController: { 38 | '*': true, 39 | update: ['isAuthenticated'], 40 | destroy: ['isAuthenticated', 'isAdmin'], 41 | create: ['isAuthenticated'], 42 | validate: ['notActive'], 43 | }, 44 | 45 | // 审核文章 总是需要管理员权限 46 | ReviewController: { 47 | show: ['isAuthenticated', 'isAdmin'], 48 | update: ['isAuthenticated', 'isAdmin'], 49 | }, 50 | 51 | // 用户 52 | UserController: { 53 | '*': true, 54 | create: ['notCreated'], 55 | update: ['isAuthenticated'], 56 | self: ['isAuthenticated'], 57 | show: ['isAuthenticated', 'isAdmin'], 58 | }, 59 | 60 | // 增加评论需要登录 删除需要管理员权限 展示无需权限 61 | CommentController: { 62 | '*': true, 63 | create: ['isAuthenticated'], 64 | delete: ['isAdmin'], 65 | }, 66 | 67 | // 博客基础信息 68 | OptionController: { 69 | '*': true, 70 | update: ['isAuthenticated', 'isAdmin'], 71 | }, 72 | 73 | // 上传图片 74 | ImageController: { 75 | upload: ['isAuthenticated'], 76 | }, 77 | 78 | 79 | 80 | 81 | /*************************************************************************** 82 | * * 83 | * Here's an example of mapping some policies to run before a controller * 84 | * and its actions * 85 | * * 86 | ***************************************************************************/ 87 | // RabbitController: { 88 | 89 | // Apply the `false` policy as the default for all of RabbitController's actions 90 | // (`false` prevents all access, which ensures that nothing bad happens to our rabbits) 91 | // '*': false, 92 | 93 | // For the action `nurture`, apply the 'isRabbitMother' policy 94 | // (this overrides `false` above) 95 | // nurture : 'isRabbitMother', 96 | 97 | // Apply the `isNiceToAnimals` AND `hasRabbitFood` policies 98 | // before letting any users feed our rabbits 99 | // feed : ['isNiceToAnimals', 'hasRabbitFood'] 100 | // } 101 | } 102 | -------------------------------------------------------------------------------- /config/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Route Mappings 3 | * (sails.config.routes) 4 | * 5 | * Your routes map URLs to views and controllers. 6 | * 7 | * If Sails receives a URL that doesn't match any of the routes below, 8 | * it will check for matching files (images, scripts, stylesheets, etc.) 9 | * in your assets directory. e.g. `http://localhost:1337/images/foo.jpg` 10 | * might match an image file: `/assets/images/foo.jpg` 11 | * 12 | * Finally, if those don't match either, the default 404 handler is triggered. 13 | * See `api/responses/notFound.js` to adjust your app's 404 logic. 14 | * 15 | * Note: Sails doesn't ACTUALLY serve stuff from `assets`-- the default Gruntfile in Sails copies 16 | * flat files from `assets` to `.tmp/public`. This allows you to do things like compile LESS or 17 | * CoffeeScript for the front-end. 18 | * 19 | * For more information on configuring custom routes, check out: 20 | * http://sailsjs.org/#!/documentation/concepts/Routes/RouteTargetSyntax.html 21 | */ 22 | 23 | module.exports.routes = { 24 | 25 | /*************************************************************************** 26 | * * 27 | * Make the view located at `views/homepage.ejs` (or `views/homepage.jade`, * 28 | * etc. depending on your default view engine) your home page. * 29 | * * 30 | * (Alternatively, remove this and add an `index.html` file in your * 31 | * `assets` directory) * 32 | * * 33 | ***************************************************************************/ 34 | 35 | // 'GET /doc': (req, res) =>{ 36 | // res.send(__dirname) 37 | // }, 38 | // '/*': {policy: 'selectApiVersion', skipAssets: true}, 39 | 40 | // 用户登录 41 | 'post /v1/session': 'AuthController.login', 42 | // 用户登出 注销session 43 | 'delete /v1/session': 'AuthController.logout', 44 | 45 | // 文章 46 | 'get /v1/articles': 'ArticleController.show', 47 | 'get /v1/articles/:id': 'ArticleController.show', 48 | 'get /v1/articles/:keyword/search': 'ArticleController.search', 49 | 'post /v1/article': 'ArticleController.create', 50 | 'put /v1/articles/:id': 'ArticleController.update', 51 | 'delete /v1/articles/:id': 'ArticleController.destroy', 52 | 53 | // 文章评论 54 | 'get /v1/articles/:id/comment': 'CommentController.show', 55 | 'post /v1/articles/:id/comment': 'CommentController.create', 56 | 'delete /v1/comments/:id': 'CommentController.destroy', 57 | 58 | // 文章标签 59 | 'get /v1/articles/:tag/tag': 'TagController.showArticles', 60 | 'get /v1/tags': 'TagController.showTags', 61 | 62 | // 审核文章 63 | 'get /v1/reviews': 'ReviewController.show', 64 | 'get /v1/reviews/:id': 'ReviewController.show', 65 | 'put /v1/reviews/:id/:status': 'ReviewController.update', 66 | 67 | // 用户 68 | 'get /v1/users/:id': 'UserController.show', 69 | 'get /v1/users/:id/:resource': 'UserController.resource', 70 | 'post /v1/users/:id/validate': 'UserController.validate', 71 | 'get /v1/user/type': 'UserController.userType', 72 | 'get /v1/user': 'UserController.self', 73 | 'put /v1/user': 'UserController.update', 74 | 'post /v1/user': 'UserController.create', 75 | 76 | // 博客基础信息 77 | 'get /v1/option': 'OptionController.show', 78 | 'put /v1/option': 'OptionController.update', 79 | 80 | // 上传图片 81 | 'post /v1/image': 'ImageController.upload', 82 | 83 | 84 | /*************************************************************************** 85 | * * 86 | * Custom routes here... * 87 | * * 88 | * If a request to a URL doesn't match any of the custom routes above, it * 89 | * is matched against Sails route blueprints. See `config/blueprints.js` * 90 | * for configuration options and examples. * 91 | * * 92 | ***************************************************************************/ 93 | 94 | } 95 | -------------------------------------------------------------------------------- /config/sockets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WebSocket Server Settings 3 | * (sails.config.sockets) 4 | * 5 | * These settings provide transparent access to the options for Sails' 6 | * encapsulated WebSocket server, as well as some additional Sails-specific 7 | * configuration layered on top. 8 | * 9 | * For more information on sockets configuration, including advanced config options, see: 10 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.sockets.html 11 | */ 12 | 13 | module.exports.sockets = { 14 | 15 | 16 | /*************************************************************************** 17 | * * 18 | * Node.js (and consequently Sails.js) apps scale horizontally. It's a * 19 | * powerful, efficient approach, but it involves a tiny bit of planning. At * 20 | * scale, you'll want to be able to copy your app onto multiple Sails.js * 21 | * servers and throw them behind a load balancer. * 22 | * * 23 | * One of the big challenges of scaling an application is that these sorts * 24 | * of clustered deployments cannot share memory, since they are on * 25 | * physically different machines. On top of that, there is no guarantee * 26 | * that a user will "stick" with the same server between requests (whether * 27 | * HTTP or sockets), since the load balancer will route each request to the * 28 | * Sails server with the most available resources. However that means that * 29 | * all room/pubsub/socket processing and shared memory has to be offloaded * 30 | * to a shared, remote messaging queue (usually Redis) * 31 | * * 32 | * Luckily, Socket.io (and consequently Sails.js) apps support Redis for * 33 | * sockets by default. To enable a remote redis pubsub server, uncomment * 34 | * the config below. * 35 | * * 36 | * Worth mentioning is that, if `adapter` config is `redis`, but host/port * 37 | * is left unset, Sails will try to connect to redis running on localhost * 38 | * via port 6379 * 39 | * * 40 | ***************************************************************************/ 41 | // adapter: 'memory', 42 | 43 | // 44 | // -OR- 45 | // 46 | 47 | // adapter: 'socket.io-redis', 48 | // host: '127.0.0.1', 49 | // port: 6379, 50 | // db: 0, 51 | // pass: '', 52 | 53 | 54 | /*************************************************************************** 55 | * * 56 | * Whether to expose a 'get /__getcookie' route with CORS support that sets * 57 | * a cookie (this is used by the sails.io.js socket client to get access to * 58 | * a 3rd party cookie and to enable sessions). * 59 | * * 60 | * Warning: Currently in this scenario, CORS settings apply to interpreted * 61 | * requests sent via a socket.io connection that used this cookie to * 62 | * connect, even for non-browser clients! (e.g. iOS apps, toasters, node.js * 63 | * unit tests) * 64 | * * 65 | ***************************************************************************/ 66 | 67 | // grant3rdPartyCookie: true, 68 | 69 | 70 | /*************************************************************************** 71 | * * 72 | * `beforeConnect` * 73 | * * 74 | * This custom beforeConnect function will be run each time BEFORE a new * 75 | * socket is allowed to connect, when the initial socket.io handshake is * 76 | * performed with the server. * 77 | * * 78 | * By default, when a socket tries to connect, Sails allows it, every time. * 79 | * (much in the same way any HTTP request is allowed to reach your routes. * 80 | * If no valid cookie was sent, a temporary session will be created for the * 81 | * connecting socket. * 82 | * * 83 | * If the cookie sent as part of the connection request doesn't match any * 84 | * known user session, a new user session is created for it. * 85 | * * 86 | * In most cases, the user would already have a cookie since they loaded * 87 | * the socket.io client and the initial HTML page you're building. * 88 | * * 89 | * However, in the case of cross-domain requests, it is possible to receive * 90 | * a connection upgrade request WITHOUT A COOKIE (for certain transports) * 91 | * In this case, there is no way to keep track of the requesting user * 92 | * between requests, since there is no identifying information to link * 93 | * him/her with a session. The sails.io.js client solves this by connecting * 94 | * to a CORS/jsonp endpoint first to get a 3rd party cookie(fortunately this* 95 | * works, even in Safari), then opening the connection. * 96 | * * 97 | * You can also pass along a ?cookie query parameter to the upgrade url, * 98 | * which Sails will use in the absence of a proper cookie e.g. (when * 99 | * connecting from the client): * 100 | * io.sails.connect('http://localhost:1337?cookie=smokeybear') * 101 | * * 102 | * Finally note that the user's cookie is NOT (and will never be) accessible* 103 | * from client-side javascript. Using HTTP-only cookies is crucial for your * 104 | * app's security. * 105 | * * 106 | ***************************************************************************/ 107 | // beforeConnect: function(handshake, cb) { 108 | // // `true` allows the connection 109 | // return cb(null, true); 110 | // 111 | // // (`false` would reject the connection) 112 | // }, 113 | 114 | 115 | /*************************************************************************** 116 | * * 117 | * `afterDisconnect` * 118 | * * 119 | * This custom afterDisconnect function will be run each time a socket * 120 | * disconnects * 121 | * * 122 | ***************************************************************************/ 123 | // afterDisconnect: function(session, socket, cb) { 124 | // // By default: do nothing. 125 | // return cb(); 126 | // }, 127 | 128 | /*************************************************************************** 129 | * * 130 | * `transports` * 131 | * * 132 | * A array of allowed transport methods which the clients will try to use. * 133 | * On server environments that don't support sticky sessions, the "polling" * 134 | * transport should be disabled. * 135 | * * 136 | ***************************************************************************/ 137 | // transports: ["polling", "websocket"] 138 | 139 | } 140 | -------------------------------------------------------------------------------- /config/views.js: -------------------------------------------------------------------------------- 1 | /** 2 | * View Engine Configuration 3 | * (sails.config.views) 4 | * 5 | * Server-sent views are a classic and effective way to get your app up 6 | * and running. Views are normally served from controllers. Below, you can 7 | * configure your templating language/framework of choice and configure 8 | * Sails' layout support. 9 | * 10 | * For more information on views and layouts, check out: 11 | * http://sailsjs.org/#!/documentation/concepts/Views 12 | */ 13 | 14 | module.exports.views = { 15 | 16 | /**************************************************************************** 17 | * * 18 | * View engine (aka template language) to use for your app's *server-side* * 19 | * views * 20 | * * 21 | * Sails+Express supports all view engines which implement TJ Holowaychuk's * 22 | * `consolidate.js`, including, but not limited to: * 23 | * * 24 | * ejs, jade, handlebars, mustache underscore, hogan, haml, haml-coffee, * 25 | * dust atpl, eco, ect, jazz, jqtpl, JUST, liquor, QEJS, swig, templayed, * 26 | * toffee, walrus, & whiskers * 27 | * * 28 | * For more options, check out the docs: * 29 | * https://github.com/balderdashy/sails-wiki/blob/0.9/config.views.md#engine * 30 | * * 31 | ****************************************************************************/ 32 | 33 | engine: 'ejs', 34 | 35 | 36 | /**************************************************************************** 37 | * * 38 | * Layouts are simply top-level HTML templates you can use as wrappers for * 39 | * your server-side views. If you're using ejs or jade, you can take * 40 | * advantage of Sails' built-in `layout` support. * 41 | * * 42 | * When using a layout, when one of your views is served, it is injected * 43 | * into the `body` partial defined in the layout. This lets you reuse header * 44 | * and footer logic between views. * 45 | * * 46 | * NOTE: Layout support is only implemented for the `ejs` view engine! * 47 | * For most other engines, it is not necessary, since they implement * 48 | * partials/layouts themselves. In those cases, this config will be * 49 | * silently ignored. * 50 | * * 51 | * The `layout` setting may be set to one of the following: * 52 | * * 53 | * If `false`, layouts will be disabled. Otherwise, if a string is * 54 | * specified, it will be interpreted as the relative path to your layout * 55 | * file from `views/` folder. (the file extension, ".ejs", should be * 56 | * omitted) * 57 | * * 58 | ****************************************************************************/ 59 | 60 | /**************************************************************************** 61 | * * 62 | * Using Multiple Layouts * 63 | * * 64 | * If you're using the default `ejs` or `handlebars` Sails supports the use * 65 | * of multiple `layout` files. To take advantage of this, before rendering a * 66 | * view, override the `layout` local in your controller by setting * 67 | * `res.locals.layout`. (this is handy if you parts of your app's UI look * 68 | * completely different from each other) * 69 | * * 70 | * e.g. your default might be * 71 | * layout: 'layouts/public' * 72 | * * 73 | * But you might override that in some of your controllers with: * 74 | * layout: 'layouts/internal' * 75 | * * 76 | ****************************************************************************/ 77 | 78 | layout: 'layout', 79 | 80 | /**************************************************************************** 81 | * * 82 | * Partials are simply top-level snippets you can leverage to reuse template * 83 | * for your server-side views. If you're using handlebars, you can take * 84 | * advantage of Sails' built-in `partials` support. * 85 | * * 86 | * If `false` or empty partials will be located in the same folder as views. * 87 | * Otherwise, if a string is specified, it will be interpreted as the * 88 | * relative path to your partial files from `views/` folder. * 89 | * * 90 | ****************************************************************************/ 91 | 92 | partials: false, 93 | 94 | 95 | } 96 | -------------------------------------------------------------------------------- /docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | services: 3 | dev.mongo: 4 | image: tutum/mongodb:3.0 5 | ports: 6 | - "27017:27017" 7 | - "28017:28017" 8 | restart: always 9 | volumes: 10 | - blogdb_dev:/data/db 11 | environment: 12 | - MONGODB_DATABASE=blog 13 | - MONGODB_USER=user 14 | - MONGODB_PASS=abcd123456 15 | volumes: 16 | blogdb_dev: 17 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | services: 3 | server: 4 | image: tcome:v1.0.5 5 | build: . 6 | ports: 7 | - "1337:1337" 8 | restart: always 9 | links: 10 | - pro.mongo 11 | env_file: 12 | - ./variables.env 13 | pro.mongo: 14 | image: tutum/mongodb:3.0 15 | ports: 16 | - "27017:27017" 17 | - "28017:28017" 18 | restart: always 19 | volumes: 20 | - blogdb_pro:/data/db 21 | env_file: 22 | - ./variables.env 23 | volumes: 24 | blogdb_pro: 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tcome-blog", 3 | "version": "1.1.0", 4 | "description": "tcome-blog", 5 | "main": "app.js", 6 | "apidoc": { 7 | "title": "TCOME Doc " 8 | }, 9 | "scripts": { 10 | "debug": "node debug app.js", 11 | "start": "export NODE_ENV=development && node app.js", 12 | "api": "apidoc -i api/ -o doc/", 13 | "docker-start": "export NODE_ENV=production && npm install --production && node --harmony app.js --env production", 14 | "mongo": "docker-compose -f docker-compose-dev.yml up -d" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/WittBulter/wittBlog.git" 19 | }, 20 | "author": "WittBulter[nanazuimeng123@gmail.com]", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/WittBulter/wittBlog/issues" 24 | }, 25 | "homepage": "https://github.com/WittBulter/wittBlog#readme", 26 | "dependencies": { 27 | "bcrypt": "^0.8.7", 28 | "compression": "^1.6.2", 29 | "connect-mongo": "^1.3.2", 30 | "cron": "^1.1.0", 31 | "express": "^4.15.3", 32 | "include-all": "~0.1.6", 33 | "node-uuid": "^1.4.7", 34 | "nodemailer": "^2.6.4", 35 | "qiniu": "^6.1.13", 36 | "rc": "1.0.1", 37 | "request": "^2.74.0", 38 | "sails": "~0.12.11", 39 | "sails-disk": "~0.10.9", 40 | "sails-mongo": "^0.12.2", 41 | "trim-html": "^0.1.7" 42 | }, 43 | "devDependencies": { 44 | "@types/node": "^8.0.13", 45 | "nodemon": "^1.11.0", 46 | "typedi": "^0.5.2" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /portal/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 加载中...-维特博客 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

loading...

15 |
16 | 22 | 23 | 24 | 25 | 26 | 27 | --------------------------------------------------------------------------------