├── api
├── models
│ ├── .gitkeep
│ ├── Tags.js
│ ├── Option.js
│ ├── Session.js
│ ├── Comment.js
│ ├── Article.js
│ └── User.js
├── services
│ ├── .gitkeep
│ ├── OptionService.js
│ ├── CommentService.js
│ ├── TagService.js
│ ├── AuthService.js
│ ├── ArticleService.js
│ └── UserService.js
├── controllers
│ ├── .gitkeep
│ ├── ImageController.js
│ ├── TagController.js
│ ├── OptionController.js
│ ├── CommentController.js
│ ├── ReviewController.js
│ ├── AuthController.js
│ ├── UserController.js
│ └── ArticleController.js
├── policies
│ ├── selectApiVersion.js
│ ├── notCreated.js
│ ├── isAdmin.js
│ ├── isActive.js
│ ├── notActive.js
│ ├── isAuthenticated.js
│ └── rateLimiting.js
└── responses
│ ├── ok.js
│ ├── created.js
│ ├── badRequest.js
│ ├── forbidden.js
│ ├── serverError.js
│ └── notFound.js
├── config
├── locales
│ ├── de.json
│ ├── en.json
│ ├── es.json
│ ├── fr.json
│ └── _README.md
├── email.js
├── bootstrap.js
├── env
│ ├── development.js
│ └── production.js
├── log.js
├── models.js
├── i18n.js
├── csrf.js
├── globals.js
├── policies.js
├── cors.js
├── routes.js
├── connections.js
├── local.js
├── http.js
├── views.js
├── sockets.js
└── blueprints.js
├── .sailsrc
├── .gitignore
├── .dockerignore
├── docker-compose-dev.yml
├── docker-compose.yml
├── Dockerfile
├── portal
└── index.html
├── package.json
├── README_CN.md
├── app.js
└── README.md
/api/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/api/services/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/api/controllers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.sailsrc:
--------------------------------------------------------------------------------
1 | {
2 | "hooks": {
3 | "grunt": false,
4 | "session": false
5 | }
6 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | variables.env
2 | *.log
3 | .cache
4 | .DS_Store
5 | .idea
6 | dist
7 | node_modules
8 | dov
9 | variables
10 |
11 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 | 
68 | 
69 |
70 | ## 团队
71 | TCOME由WittBulter开发,如果你需要加入开发团队,请联系我:
72 |
73 | [](https://github.com/WittBulter) |
74 | :---:|
75 | [Witt Bulter](https://github.com/WittBulter) |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | 
68 | 
69 |
70 |
71 | ## TEAM
72 |
73 |
74 | [](https://github.com/WittBulter) |
75 | :---:|
76 | [Witt Bulter](https://github.com/WittBulter) |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 | *
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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------