diff --git a/app/api_router.js b/app/api_router.js new file mode 100644 index 00000000..c19a55dc --- /dev/null +++ b/app/api_router.js @@ -0,0 +1,59 @@ +'use strict'; + +module.exports = function(app) { + const { router, controller, config } = app; + const { topic, topicCollect, user, toolsController, + reply, message } = controller.v1; + + // Open API + var topic = require('./api/v1/topic'); + var topicCollectController = require('./api/v1/topic_collect'); + var userController = require('./api/v1/user'); + var toolsController = require('./api/v1/tools'); + var replyController = require('./api/v1/reply'); + var messageController = require('./api/v1/message'); + var middleware = require('./api/v1/middleware'); + + var limit = require('./middlewares/limit'); + + // 主题 + router.get('/api/v1/topics', topic.index); + router.get('/api/v1/topic/:id', middleware.tryAuth, topic.show); + router.post('/api/v1/topics', middleware.auth, + limit.peruserperday('create_topic', config.create_post_per_day, + {showJson: true}), topic.create); + router.post('/api/v1/topics/update', middleware.auth, + topic.update); + + // 主题收藏 + router.post('/api/v1/topic_collect/collect', middleware.auth, + topicCollectController.collect); // 关注某话题 + router.post('/api/v1/topic_collect/de_collect', middleware.auth, + topicCollectController.de_collect); // 取消关注某话题 + router.get('/api/v1/topic_collect/:loginname', + topicCollectController.list); + + // 用户 + router.get('/api/v1/user/:loginname', userController.show); + + // accessToken 测试 + router.post('/api/v1/accesstoken', middleware.auth, + toolsController.accesstoken); + + // 评论 + router.post('/api/v1/topic/:topic_id/replies', middleware.auth, + limit.peruserperday('create_reply', config.create_reply_per_day, + {showJson: true}), replyController.create); + router.post('/api/v1/reply/:reply_id/ups', middleware.auth, + replyController.ups); + + // 通知 + router.get('/api/v1/messages', middleware.auth, + messageController.index); + router.get('/api/v1/message/count', middleware.auth, + messageController.count); + router.post('/api/v1/message/mark_all', middleware.auth, + messageController.markAll); + router.post('/api/v1/message/mark_one/:msg_id', middleware.auth, + messageController.markOne); +}; diff --git a/app/controller/v1/topic.js b/app/controller/v1/topic.js new file mode 100644 index 00000000..dca37456 --- /dev/null +++ b/app/controller/v1/topic.js @@ -0,0 +1,235 @@ +'use strict'; + +var eventproxy = require('eventproxy'); +var _ = require('lodash'); +var at = require('../../common/at'); +var renderHelper = require('../../common/render_helper'); +var validator = require('validator'); + +class TopicController extends Controller { + async index() { + const { ctx, service, config } = this; + var page = parseInt(ctx.query.page, 10) || 1; + page = page > 0 ? page : 1; + var tab = ctx.query.tab || 'all'; + var limit = Number(ctx.query.limit) || config.list_topic_count; + var mdrender = ctx.query.mdrender === 'false' ? false : true; + + var query = {}; + if (!tab || tab === 'all') { + query.tab = {$nin: ['job', 'dev']} + } else { + if (tab === 'good') { + query.good = true; + } else { + query.tab = tab; + } + } + query.deleted = false; + var options = { + skip: (page - 1) * limit, + limit: limit, + sort: '-top -last_reply_at', + }; + + const topics = await service.topic.find(query, '', options); + service.user.findById + ep.all('topics', function (topics) { + topics.forEach(function (topic) { + UserModel.findById(topic.author_id, ep.done(function (author) { + if (mdrender) { + topic.content = renderHelper.markdown(at.linkUsers(topic.content)); + } + topic.author = _.pick(author, ['loginname', 'avatar_url']); + ep.emit('author'); + })); + }); + + ep.after('author', topics.length, function () { + topics = topics.map(function (topic) { + return _.pick(topic, ['id', 'author_id', 'tab', 'content', 'title', 'last_reply_at', + 'good', 'top', 'reply_count', 'visit_count', 'create_at', 'author']); + }); + + res.send({success: true, data: topics}); + }); + }); + } + +var show = function (req, res, next) { + var topicId = String(req.params.id); + + var mdrender = req.query.mdrender === 'false' ? false : true; + var ep = new eventproxy(); + + if (!validator.isMongoId(topicId)) { + res.status(400); + return res.send({success: false, error_msg: '不是有效的话题id'}); + } + + ep.fail(next); + + TopicProxy.getFullTopic(topicId, ep.done(function (msg, topic, author, replies) { + if (!topic) { + res.status(404); + return res.send({success: false, error_msg: '话题不存在'}); + } + topic = _.pick(topic, ['id', 'author_id', 'tab', 'content', 'title', 'last_reply_at', + 'good', 'top', 'reply_count', 'visit_count', 'create_at', 'author']); + + if (mdrender) { + topic.content = renderHelper.markdown(at.linkUsers(topic.content)); + } + topic.author = _.pick(author, ['loginname', 'avatar_url']); + + topic.replies = replies.map(function (reply) { + if (mdrender) { + reply.content = renderHelper.markdown(at.linkUsers(reply.content)); + } + reply.author = _.pick(reply.author, ['loginname', 'avatar_url']); + reply = _.pick(reply, ['id', 'author', 'content', 'ups', 'create_at', 'reply_id']); + reply.reply_id = reply.reply_id || null; + + if (reply.ups && req.user && reply.ups.indexOf(req.user._id) != -1) { + reply.is_uped = true; + } else { + reply.is_uped = false; + } + + return reply; + }); + + ep.emit('full_topic', topic) + })); + + + if (!req.user) { + ep.emitLater('is_collect', null) + } else { + TopicCollect.getTopicCollect(req.user._id, topicId, ep.done('is_collect')) + } + + ep.all('full_topic', 'is_collect', function (full_topic, is_collect) { + full_topic.is_collect = !!is_collect; + + res.send({success: true, data: full_topic}); + }) + +}; + +exports.show = show; + +var create = function (req, res, next) { + var title = validator.trim(req.body.title || ''); + var tab = validator.trim(req.body.tab || ''); + var content = validator.trim(req.body.content || ''); + + // 得到所有的 tab, e.g. ['ask', 'share', ..] + var allTabs = config.tabs.map(function (tPair) { + return tPair[0]; + }); + + // 验证 + var editError; + if (title === '') { + editError = '标题不能为空'; + } else if (title.length < 5 || title.length > 100) { + editError = '标题字数太多或太少'; + } else if (!tab || !_.includes(allTabs, tab)) { + editError = '必须选择一个版块'; + } else if (content === '') { + editError = '内容不可为空'; + } + // END 验证 + + if (editError) { + res.status(400); + return res.send({success: false, error_msg: editError}); + } + + TopicProxy.newAndSave(title, content, tab, req.user.id, function (err, topic) { + if (err) { + return next(err); + } + + var proxy = new eventproxy(); + proxy.fail(next); + + proxy.all('score_saved', function () { + res.send({ + success: true, + topic_id: topic.id + }); + }); + UserProxy.getUserById(req.user.id, proxy.done(function (user) { + user.score += 5; + user.topic_count += 1; + user.save(); + req.user = user; + proxy.emit('score_saved'); + })); + + //发送at消息 + at.sendMessageToMentionUsers(content, topic.id, req.user.id); + }); +}; + +exports.update = function (req, res, next) { + var topic_id = _.trim(req.body.topic_id); + var title = _.trim(req.body.title); + var tab = _.trim(req.body.tab); + var content = _.trim(req.body.content); + + // 得到所有的 tab, e.g. ['ask', 'share', ..] + var allTabs = config.tabs.map(function (tPair) { + return tPair[0]; + }); + + TopicProxy.getTopicById(topic_id, function (err, topic, tags) { + if (!topic) { + res.status(400); + return res.send({success: false, error_msg: '此话题不存在或已被删除。'}); + } + + if (topic.author_id.equals(req.user._id) || req.user.is_admin) { + // 验证 + var editError; + if (title === '') { + editError = '标题不能是空的。'; + } else if (title.length < 5 || title.length > 100) { + editError = '标题字数太多或太少。'; + } else if (!tab || !_.includes(allTabs, tab)) { + editError = '必须选择一个版块。'; + } + // END 验证 + + if (editError) { + return res.send({success: false, error_msg: editError}); + } + + //保存话题 + topic.title = title; + topic.content = content; + topic.tab = tab; + topic.update_at = new Date(); + + topic.save(function (err) { + if (err) { + return next(err); + } + // 发送at消息 + at.sendMessageToMentionUsers(content, topic._id, req.user._id); + + res.send({ + success: true, + topic_id: topic.id + }); + }); + } else { + res.status(403) + return res.send({success: false, error_msg: '对不起,你不能编辑此话题。'}); + } + }); +} + +module.exports = TopicController; diff --git a/app/router.js b/app/router.js index 54f5be6e..cf957b2c 100644 --- a/app/router.js +++ b/app/router.js @@ -1,110 +1,12 @@ 'use strict'; +const web = require('./web_router'); +const api = require('./api_router'); + /** * @param {Egg.Application} app - egg application */ module.exports = app => { - const { router, controller, config, middleware } = app; - - const { site, sign, user, topic, rss, - search, page, reply, message } = controller; - - const userRequired = middleware.userRequired(); - const adminRequired = middleware.adminRequired(); - const createTopicLimit = middleware.createTopicLimit(config.topic); - const createUserLimit = middleware.createUserLimit(config.create_user_per_ip); - - // home page - router.get('/', site.index); - // sitemap - router.get('/sitemap.xml', site.sitemap); - // mobile app download - router.get('/app/download', site.appDownload); - - // sign controller - if (config.allow_sign_up) { - // 跳转到注册页面 - router.get('/signup', sign.showSignup); - // 提交注册信息 - router.post('/signup', createUserLimit, sign.signup); - } else { - // 进行github验证 - router.redirect('/singup', '/auth/github'); - } - - const localStrategy = app.passport.authenticate('local', { - successRedirect: '/', - failureRedirect: '/signin', - }); - - router.get('/signin', sign.showLogin); // 进入登录页面 - router.post('/passport/local', localStrategy); - router.all('/signout', sign.signout); // 登出 - router.get('/active_account', sign.activeAccount); // 帐号激活 - - // github oauth - app.passport.mount('github'); - - router.get('/search_pass', sign.showSearchPass); // 找回密码页面 - router.post('/search_pass', sign.updateSearchPass); // 更新密码 - router.get('/reset_pass', sign.resetPass); // 进入重置密码页面 - router.post('/reset_pass', sign.updatePass); // 更新密码 - - // user controller - router.get('/user/:name', user.index); // 用户个人主页 - router.get('/setting', userRequired, user.showSetting); // 用户个人设置页 - router.post('/setting', userRequired, user.setting); // 提交个人信息设置 - router.get('/stars', user.listStars); // 显示所有达人列表页 - router.get('/users/top100', user.top100); // 显示积分前一百用户页 - router.get('/user/:name/collections', user.listCollectedTopics); // 用户收藏的所有话题页 - router.get('/user/:name/topics', user.listTopics); // 用户发布的所有话题页 - router.get('/user/:name/replies', user.listReplies); // 用户参与的所有回复页 - router.post('/user/set_star', adminRequired, user.toggleStar); // 把某用户设为达人 - router.post('/user/cancel_star', adminRequired, user.toggleStar); // 取消某用户的达人身份 - router.post('/user/:name/block', adminRequired, user.block); // 禁言某用户 - router.post('/user/:name/delete_all', adminRequired, user.deleteAll); // 删除某用户所有发言 - - // message controler - router.get('/my/messages', userRequired, message.index); // 用户个人的所有消息页 - - // topic - - // 新建文章界面 - router.get('/topic/create', userRequired, topic.create); - - router.get('/topic/:tid', topic.index); // 显示某个话题 - router.post('/topic/:tid/top', adminRequired, topic.top); // 将某话题置顶 - router.post('/topic/:tid/good', adminRequired, topic.good); // 将某话题加精 - router.get('/topic/:tid/edit', userRequired, topic.showEdit); // 编辑某话题 - router.post('/topic/:tid/lock', adminRequired, topic.lock); // 锁定主题,不能再回复 - - router.post('/topic/:tid/delete', userRequired, topic.delete); - - // 保存新建的文章 - router.post('/topic/create', userRequired, createTopicLimit, topic.put); - - router.post('/topic/:tid/edit', userRequired, topic.update); - router.post('/topic/collect', userRequired, topic.collect); // 关注某话题 - router.post('/topic/de_collect', userRequired, topic.de_collect); // 取消关注某话题 - - // reply controller - router.post('/:topic_id/reply', userRequired, - // limit.peruserperday('create_reply', config.create_reply_per_day, { showJson: false }), - reply.add); // 提交一级回复 - router.get('/reply/:reply_id/edit', userRequired, reply.showEdit); // 修改自己的评论页 - router.post('/reply/:reply_id/edit', userRequired, reply.update); // 修改某评论 - router.post('/reply/:reply_id/delete', userRequired, reply.delete); // 删除某评论 - router.post('/reply/:reply_id/up', userRequired, reply.up); // 为评论点赞 - router.post('/upload', userRequired, topic.upload); // 上传图片 - // static page - router.get('/about', page.about); - router.get('/faq', page.faq); - router.get('/getstart', page.getstart); - router.get('/robots.txt', page.robots); - router.get('/api', page.api); - - // rss - router.get('/rss', rss.index); - - router.get('/search', search.index); + web(app); + api(app); }; diff --git a/app/web_router.js b/app/web_router.js new file mode 100644 index 00000000..61ed272c --- /dev/null +++ b/app/web_router.js @@ -0,0 +1,107 @@ +'use strict'; + +module.exports = function(app) { + const { router, controller, config, middleware } = app; + + const { site, sign, user, topic, rss, + search, page, reply, message } = controller; + + const userRequired = middleware.userRequired(); + const adminRequired = middleware.adminRequired(); + const createTopicLimit = middleware.createTopicLimit(config.topic); + const createUserLimit = middleware.createUserLimit(config.create_user_per_ip); + + // home page + router.get('/', site.index); + // sitemap + router.get('/sitemap.xml', site.sitemap); + // mobile app download + router.get('/app/download', site.appDownload); + + // sign controller + if (config.allow_sign_up) { + // 跳转到注册页面 + router.get('/signup', sign.showSignup); + // 提交注册信息 + router.post('/signup', createUserLimit, sign.signup); + } else { + // 进行github验证 + router.redirect('/singup', '/auth/github'); + } + + const localStrategy = app.passport.authenticate('local', { + successRedirect: '/', + failureRedirect: '/signin', + }); + + router.get('/signin', sign.showLogin); // 进入登录页面 + router.post('/passport/local', localStrategy); + router.all('/signout', sign.signout); // 登出 + router.get('/active_account', sign.activeAccount); // 帐号激活 + + // github oauth + app.passport.mount('github'); + + router.get('/search_pass', sign.showSearchPass); // 找回密码页面 + router.post('/search_pass', sign.updateSearchPass); // 更新密码 + router.get('/reset_pass', sign.resetPass); // 进入重置密码页面 + router.post('/reset_pass', sign.updatePass); // 更新密码 + + // user controller + router.get('/user/:name', user.index); // 用户个人主页 + router.get('/setting', userRequired, user.showSetting); // 用户个人设置页 + router.post('/setting', userRequired, user.setting); // 提交个人信息设置 + router.get('/stars', user.listStars); // 显示所有达人列表页 + router.get('/users/top100', user.top100); // 显示积分前一百用户页 + router.get('/user/:name/collections', user.listCollectedTopics); // 用户收藏的所有话题页 + router.get('/user/:name/topics', user.listTopics); // 用户发布的所有话题页 + router.get('/user/:name/replies', user.listReplies); // 用户参与的所有回复页 + router.post('/user/set_star', adminRequired, user.toggleStar); // 把某用户设为达人 + router.post('/user/cancel_star', adminRequired, user.toggleStar); // 取消某用户的达人身份 + router.post('/user/:name/block', adminRequired, user.block); // 禁言某用户 + router.post('/user/:name/delete_all', adminRequired, user.deleteAll); // 删除某用户所有发言 + + // message controler + router.get('/my/messages', userRequired, message.index); // 用户个人的所有消息页 + + // topic + + // 新建文章界面 + router.get('/topic/create', userRequired, topic.create); + + router.get('/topic/:tid', topic.index); // 显示某个话题 + router.post('/topic/:tid/top', adminRequired, topic.top); // 将某话题置顶 + router.post('/topic/:tid/good', adminRequired, topic.good); // 将某话题加精 + router.get('/topic/:tid/edit', userRequired, topic.showEdit); // 编辑某话题 + router.post('/topic/:tid/lock', adminRequired, topic.lock); // 锁定主题,不能再回复 + + router.post('/topic/:tid/delete', userRequired, topic.delete); + + // 保存新建的文章 + router.post('/topic/create', userRequired, createTopicLimit, topic.put); + + router.post('/topic/:tid/edit', userRequired, topic.update); + router.post('/topic/collect', userRequired, topic.collect); // 关注某话题 + router.post('/topic/de_collect', userRequired, topic.de_collect); // 取消关注某话题 + + // reply controller + router.post('/:topic_id/reply', userRequired, + // limit.peruserperday('create_reply', config.create_reply_per_day, { showJson: false }), + reply.add); // 提交一级回复 + router.get('/reply/:reply_id/edit', userRequired, reply.showEdit); // 修改自己的评论页 + router.post('/reply/:reply_id/edit', userRequired, reply.update); // 修改某评论 + router.post('/reply/:reply_id/delete', userRequired, reply.delete); // 删除某评论 + router.post('/reply/:reply_id/up', userRequired, reply.up); // 为评论点赞 + router.post('/upload', userRequired, topic.upload); // 上传图片 + // static page + router.get('/about', page.about); + router.get('/faq', page.faq); + router.get('/getstart', page.getstart); + router.get('/robots.txt', page.robots); + router.get('/api', page.api); + + // rss + router.get('/rss', rss.index); + + router.get('/search', search.index); +}; diff --git a/config/config.default.js b/config/config.default.js index 35d571a5..14a9cf07 100644 --- a/config/config.default.js +++ b/config/config.default.js @@ -23,7 +23,18 @@ module.exports = appInfo => { config.session_secret = 'node_club_secret'; // 务必修改 // add your config here - config.middleware = [ 'locals', 'authUser', 'blockUser', 'errorPage' ]; + config.middleware = [ + 'locals', + 'authUser', + 'blockUser', + 'errorPage', + 'cors', + ]; + + exports.cors = { + enable: true, + match: '/api/v1/', + }; config.authUser = { enable: true, diff --git a/config/plugin.js b/config/plugin.js index f6e8c2fb..435e6ee5 100644 --- a/config/plugin.js +++ b/config/plugin.js @@ -43,3 +43,8 @@ exports.validate = { enable: true, package: 'egg-validate', }; + +exports.cors = { + enable: true, + package: 'egg-cors', +}; diff --git a/package.json b/package.json index 4d922be8..3d9f5f8f 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "data2xml": "^1.2.5", "egg": "^2.2.1", "egg-alinode": "^2.0.1", + "egg-cors": "^2.0.0", "egg-mongoose": "^2.1.1", "egg-passport": "^2.0.1", "egg-passport-github": "^1.0.0",