Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: post topic/create #69

Merged
merged 3 commits into from
Mar 15, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 26 additions & 41 deletions app/controller/topic.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

const Controller = require('egg').Controller;
const _ = require('lodash');
// const validator = require('validator');
const path = require('path');
const fs = require('fs');
const awaitWriteStream = require('await-stream-ready').write;
Expand Down Expand Up @@ -102,60 +101,48 @@ class TopicController extends Controller {
async put() {
const { ctx, service } = this;
const { tabs } = this.config;
const title = ctx.request.body.title.trim();
const tab = ctx.request.body.tab.trim();
const content = ctx.request.body.t_content.trim();
const { body } = ctx.request;

// 得到所有的 tab, e.g. ['ask', 'share', ..]
const allTabs = tabs.map(function(tPair) {
return tPair[0];
});

// 验证
let editError;
if (title === '') {
editError = '标题不能是空的。';
} else if (title.length < 5 || title.length > 100) {
editError = '标题字数太多或太少。';
} else if (!tab || allTabs.indexOf(tab) === -1) {
editError = '必须选择一个版块。';
} else if (content === '') {
editError = '内容不可为空。';
}
// END 验证

if (editError) {
ctx.status = 422;
await ctx.render('topic/edit', {
edit_error: editError,
title,
content,
tabs,
});
return;
}
const allTabs = tabs.map(tPair => tPair[0]);

// 使用 egg_validate 验证
// TODO: 此处可以优化,将所有使用egg_validate的rules集中管理,避免即时新建对象
const RULE_CREATE = {
title: {
type: 'string',
max: 100,
min: 5,
},
content: {
type: 'string',
},
tab: {
type: 'enum',
values: allTabs,
},
};
ctx.validate(RULE_CREATE, ctx.request.body);

// 储存新主题帖
const topic = await service.topic.newAndSave(
title,
content,
tab,
body.title,
body.content,
body.tab,
ctx.user._id
);

// 发帖用户增加积分,增加发表主题数量
await service.user.incrementScoreAndReplyCount(topic.author_id, 5, 1);

ctx.redirect('/topic/' + topic._id);

// 通知被@的用户
await service.at.sendMessageToMentionUsers(
content,
body.content,
topic._id,
ctx.user._id
);

await ctx.redirect('/topic/' + topic._id);
ctx.redirect('/topic/' + topic._id);
}

/**
Expand Down Expand Up @@ -198,9 +185,7 @@ class TopicController extends Controller {
const { ctx, service, config } = this;

const topic_id = ctx.params.tid;
let title = ctx.request.body.title;
let tab = ctx.request.body.tab;
let content = ctx.request.body.t_content;
let { title, tab, content } = ctx.request.body;

const { topic } = await service.topic.getTopicById(topic_id);
if (!topic) {
Expand Down
64 changes: 0 additions & 64 deletions app/middleware/limit.js

This file was deleted.

29 changes: 29 additions & 0 deletions app/middleware/topic_per_day_limit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';
const moment = require('moment');

module.exports = ({ perDayPerUserLimitCount = 10 }) => {

return async function(ctx, next) {
const { user, service } = ctx;
const YYYYMMDD = moment().format('YYYYMMDD');
const key = `topics_count_${user._id}_${YYYYMMDD}`;

let todayTopicsCount = (await service.cache.get(key)) || 0;
if (todayTopicsCount >= perDayPerUserLimitCount) {
ctx.status = 403;
await ctx.render('notify/notify',
{ error: `今天的话题发布数量已达到限制(${perDayPerUserLimitCount})` });
return;
}

await next();

if (ctx.status === 302) {
// 新建话题成功
todayTopicsCount += 1;
await service.cache.incr(key, 60 * 60 * 24);
ctx.set('X-RateLimit-Limit', perDayPerUserLimitCount);
ctx.set('X-RateLimit-Remaining', perDayPerUserLimitCount - todayTopicsCount);
}
};
};
5 changes: 2 additions & 3 deletions app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = app => {

const userRequired = middleware.userRequired();
const adminRequired = middleware.adminRequired();
const topicPerDayLimit = middleware.topicPerDayLimit(config.topic);

// home page
router.get('/', site.index);
Expand Down Expand Up @@ -78,9 +79,7 @@ module.exports = app => {
router.post('/topic/:tid/delete', userRequired, topic.delete);

// // 保存新建的文章
// router.post('/topic/create', userRequired, limit.peruserperday('create_topic', config.create_post_per_day, { showJson: false }), topic.put);

router.post('/topic/create', userRequired, topic.put);
router.post('/topic/create', userRequired, topicPerDayLimit, topic.put);

router.post('/topic/:tid/edit', userRequired, topic.update);
router.post('/topic/collect', userRequired, topic.collect); // 关注某话题
Expand Down
2 changes: 1 addition & 1 deletion app/view/topic/edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@

<div class='markdown_editor in_editor'>
<div class='markdown_in_editor'>
<textarea class='editor' name='t_content' rows='20'
<textarea class='editor' name='content' rows='20'
placeholder='文章支持 Markdown 语法, 请注意标记代码'
><%= typeof content !== 'undefined' && content || '' %></textarea>

Expand Down
4 changes: 4 additions & 0 deletions config/config.default.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,9 @@ module.exports = appInfo => {
secret: process.env.EGG_ALINODE_SECRET || '',
};

config.topic = {
perDayPerUserLimitCount: 10,
};

return config;
};
5 changes: 5 additions & 0 deletions config/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,8 @@ exports.alinode = {
package: 'egg-alinode',
env: [ 'prod' ],
};

exports.validate = {
enable: true,
package: 'egg-validate',
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"egg-passport-local": "^1.2.1",
"egg-redis": "^2.0.0",
"egg-scripts": "^2.5.0",
"egg-validate": "^1.0.0",
"egg-view-ejs": "^2.0.0",
"loader": "^2.1.1",
"loader-koa": "^2.0.1",
Expand Down
85 changes: 44 additions & 41 deletions test/app/controller/topic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,51 +92,54 @@ describe('test/app/controller/topic.test.js', () => {
await app.httpRequest().get(`/topic/${topicId}/edit`).expect(200);
});

it('should POST /topic/create ok', async () => {
const body = {
title: '',
tab: '',
t_content: '',
};
it('should POST /topic/create forbidden', async () => {
app.mockCsrf();
await app.httpRequest().post('/topic/create').expect(403);
});

it('should POST /topic/create forbidden', async () => {
mockUser();
app.mockCsrf();
await app.httpRequest().post('/topic/create')
.send({
invalid_field: 'not make sense',
})
.expect(422);
});

const r1 = await app
.httpRequest()
.post('/topic/create')
.send(body);
assert(r1.text.includes('标题不能是空的。'));

body.title = 'hi';
const r2 = await app
.httpRequest()
.post('/topic/create')
.send(body);
assert(r2.text.includes('标题字数太多或太少。'));

body.title = '这是一个大标题';
const r4 = await app
.httpRequest()
.post('/topic/create')
.send(body);
assert(r4.text.includes('必须选择一个版块。'));

body.tab = 'share';
const r3 = await app
.httpRequest()
.post('/topic/create')
.send(body);
assert(r3.text.includes('内容不可为空。'));

body.t_content = 'hi';

await app
.httpRequest()
.post('/topic/create')
.send(body)
it('should POST /topic/create ok', async () => {
mockUser();
app.mockCsrf();
await app.httpRequest().post('/topic/create')
.send({
tab: 'share',
title: 'topic测试标题',
content: 'topic test topic content',
})
.expect(302);
});

it('should POST /topic/create per day limit works', async () => {
mockUser();
app.mockCsrf();
for (let i = 0; i < 9; i++) {
await app.httpRequest().post('/topic/create')
.send({
tab: 'share',
title: `topic测试标题${i + 1}`,
content: 'topic test topic content',
})
.expect(302);
}
await app.httpRequest().post('/topic/create')
.send({
tab: 'share',
title: 'topic测试标题11',
content: 'topic test topic content',
})
.expect(403);
});

it('should POST /topic/:tid/top ok', async () => {
mockUser();
const res = await app.httpRequest().post(`/topic/${topicId}/top`);
Expand Down Expand Up @@ -168,7 +171,7 @@ describe('test/app/controller/topic.test.js', () => {
const body = {
title: '',
tab: '',
t_content: '',
content: '',
};

fakeUser();
Expand Down Expand Up @@ -212,7 +215,7 @@ describe('test/app/controller/topic.test.js', () => {
.send(body);
assert(r3.text.includes('内容不可为空。'));

body.t_content = 'hi';
body.content = 'hi';
await app
.httpRequest()
.post(`/topic/${topicId}/edit`)
Expand Down