Skip to content

Commit d754380

Browse files
sinchangJacksonTian
authored andcommitted
feat: add topic_collect api (#131)
* feat: add topic_collect api closes #125, closes #113 * chore: update status code * chore: fix typo
1 parent 4f47480 commit d754380

File tree

5 files changed

+256
-2
lines changed

5 files changed

+256
-2
lines changed

app/controller/api/collect.js

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
'use strict';
2+
3+
const Controller = require('egg').Controller;
4+
const _ = require('lodash');
5+
6+
const MongoObjectIdSchema = {
7+
type: 'string',
8+
max: 24,
9+
min: 24,
10+
};
11+
12+
class CollectController extends Controller {
13+
async index() {
14+
const { ctx, service } = this;
15+
const name = ctx.params.name;
16+
17+
const user = await service.user.getUserByLoginName(name);
18+
19+
if (!user) {
20+
ctx.status = 404;
21+
ctx.body = {
22+
success: false,
23+
error_msg: '用户不存在',
24+
};
25+
return;
26+
}
27+
28+
const opt = { skip: 0, limit: 100 };
29+
30+
const collects = await service.topicCollect.getTopicCollectsByUserId(user._id, opt);
31+
const ids = collects.map(doc => {
32+
return doc.topic_id.toString();
33+
});
34+
35+
const query = { _id: { $in: ids } };
36+
let topics = await service.topic.getTopicsByQuery(query, {});
37+
38+
topics = _.sortBy(topics, topic => {
39+
return ids.indexOf(topic._id.toString());
40+
});
41+
42+
topics = topics.map(topic => {
43+
topic.author = _.pick(topic.author, [ 'loginname', 'avatar_url' ]);
44+
return _.pick(topic, [ 'id', 'author_id', 'tab', 'content', 'title', 'last_reply_at',
45+
'good', 'top', 'reply_count', 'visit_count', 'create_at', 'author' ]);
46+
});
47+
48+
ctx.body = {
49+
success: true,
50+
data: topics,
51+
};
52+
}
53+
54+
async collect() {
55+
const { ctx, service } = this;
56+
const topic_id = ctx.request.body.topic_id;
57+
const user_id = ctx.request.user.id;
58+
59+
ctx.validate({
60+
topic_id: MongoObjectIdSchema,
61+
}, ctx.request.body);
62+
63+
const topic = await service.topic.getTopic(topic_id);
64+
65+
if (!topic) {
66+
ctx.status = 404;
67+
ctx.body = {
68+
success: false,
69+
error_msg: '话题不存在',
70+
};
71+
return;
72+
}
73+
74+
const doc = await service.topicCollect.getTopicCollect(
75+
user_id,
76+
topic._id
77+
);
78+
79+
if (doc) {
80+
ctx.body = {
81+
success: false,
82+
error_msg: '已经收藏过该主题',
83+
};
84+
return;
85+
}
86+
87+
await service.topicCollect.newAndSave(user_id, topic._id);
88+
await Promise.all([
89+
service.user.incrementCollectTopicCount(user_id),
90+
service.topic.incrementCollectCount(topic_id),
91+
]);
92+
93+
ctx.body = { success: true };
94+
}
95+
96+
async de_collect() {
97+
const { ctx, service } = this;
98+
const topic_id = ctx.request.body.topic_id;
99+
const user_id = ctx.request.user.id;
100+
101+
ctx.validate({
102+
topic_id: MongoObjectIdSchema,
103+
}, ctx.request.body);
104+
105+
const topic = await service.topic.getTopic(topic_id);
106+
107+
if (!topic) {
108+
ctx.status = 404;
109+
ctx.body = {
110+
success: false,
111+
error_msg: '话题不存在',
112+
};
113+
return;
114+
}
115+
116+
const removeResult = await service.topicCollect.remove(
117+
user_id,
118+
topic._id
119+
);
120+
121+
if (removeResult.result.n === 0) {
122+
ctx.status = 500;
123+
ctx.body = {
124+
success: false,
125+
error_msg: '取消收藏失败',
126+
};
127+
return;
128+
}
129+
130+
const user = await service.user.getUserById(user_id);
131+
132+
user.collect_topic_count -= 1;
133+
await user.save();
134+
135+
topic.collect_count -= 1;
136+
await topic.save();
137+
138+
ctx.body = { success: true };
139+
}
140+
}
141+
142+
module.exports = CollectController;

app/middleware/error_handler.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict';
2+
3+
module.exports = (option, app) => {
4+
// 中间件的配置项,框架会将 app.config[${middlewareName}] 传递进来
5+
return async function(ctx, next) {
6+
try {
7+
await next();
8+
} catch (err) {
9+
// 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志
10+
app.emit('error', err, this);
11+
const status = err.status || 500;
12+
// 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
13+
const error_msg = status === 500 && app.config.env === 'prod'
14+
? 'Internal Server Error'
15+
: err.message;
16+
// 从 error 对象上读出各个属性,设置到响应中
17+
ctx.body = { error_msg };
18+
ctx.body.success = false;
19+
if (status === 422) {
20+
ctx.body.detail = err.errors;
21+
}
22+
ctx.status = status;
23+
}
24+
};
25+
};

app/router/api.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module.exports = app => {
77
const apiV1Router = app.router.namespace('/api/v1');
88
const { controller, middleware } = app;
99

10-
const { user, message, topic, reply } = controller.api;
10+
const { user, message, topic, reply, collect } = controller.api;
1111

1212
const tokenRequired = middleware.tokenRequired();
1313
const pagination = middleware.pagination();
@@ -30,6 +30,11 @@ module.exports = app => {
3030
apiV1Router.post('/topics', tokenRequired, topic.create);
3131
apiV1Router.post('/topics/update', tokenRequired, topic.update);
3232

33+
// 主题收藏
34+
apiV1Router.post('/topic_collect/collect', tokenRequired, collect.collect);
35+
apiV1Router.post('/topic_collect/de_collect', tokenRequired, collect.de_collect);
36+
apiV1Router.get('/topic_collect/:name', collect.index);
37+
3338
// 评论
3439
apiV1Router.post('/topic/:topic_id/replies', tokenRequired, reply.create);
3540
apiV1Router.post('/reply/:reply_id/ups', tokenRequired, reply.updateUps);

config/config.default.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ module.exports = appInfo => {
2323
config.session_secret = 'node_club_secret'; // 务必修改
2424

2525
// add your config here
26-
config.middleware = [ 'locals', 'authUser', 'blockUser', 'errorPage' ];
26+
config.middleware = [ 'locals', 'authUser', 'blockUser', 'errorPage', 'errorHandler' ];
2727

2828
config.authUser = {
2929
enable: true,
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
'use strict';
2+
3+
const { app, assert } = require('egg-mock/bootstrap');
4+
5+
describe('test/app/controller/api/collect.test.js', () => {
6+
let user,
7+
topic_id;
8+
9+
before(async function() {
10+
const ctx = app.mockContext();
11+
const loginname = `user_loginname_${Date.now()}`;
12+
const email = `${loginname}@test.com`;
13+
user = await ctx.service.user.newAndSave('name', loginname, ctx.helper.bhash('pass'), email, 'avatar_url', 'active');
14+
const title = 'test topic';
15+
const content = 'test topic';
16+
const tab = 'share';
17+
18+
const topic = await ctx.service.topic.newAndSave(title, content, tab, user.id);
19+
topic_id = topic._id.toString();
20+
});
21+
22+
it('post /topic_collect/collect should ok', async () => {
23+
await app.httpRequest()
24+
.post('/api/v1/topic_collect/collect')
25+
.send({
26+
accesstoken: user.accessToken,
27+
topic_id,
28+
})
29+
.expect(200);
30+
await app.httpRequest()
31+
.post('/api/v1/topic_collect/collect')
32+
.send({
33+
accesstoken: user.accessToken,
34+
topic_id: '5aaa4432f472129d5e4d6773',
35+
})
36+
.expect(404);
37+
await app.httpRequest()
38+
.post('/api/v1/topic_collect/collect')
39+
.send({
40+
accesstoken: user.accessToken,
41+
topic_id: '123',
42+
})
43+
.expect(422);
44+
const result = await app.httpRequest()
45+
.post('/api/v1/topic_collect/collect')
46+
.send({
47+
accesstoken: user.accessToken,
48+
topic_id,
49+
});
50+
assert(result.body.error_msg === '已经收藏过该主题');
51+
});
52+
53+
it('get /topic_collect/:loginname should ok', async () => {
54+
const loginname = user.loginname;
55+
await app.httpRequest().get(`/api/v1/topic_collect/${loginname}`).expect(200);
56+
await app.httpRequest().get(`/api/v1/topic_collect/${loginname}test`).expect(404);
57+
});
58+
59+
it('post /topic_collect/de_collect should ok', async () => {
60+
await app.httpRequest()
61+
.post('/api/v1/topic_collect/de_collect')
62+
.send({
63+
accesstoken: user.accessToken,
64+
topic_id: '123',
65+
})
66+
.expect(422);
67+
await app.httpRequest()
68+
.post('/api/v1/topic_collect/de_collect')
69+
.send({
70+
accesstoken: user.accessToken,
71+
topic_id: '5aaa4432f472129d5e4d6773',
72+
})
73+
.expect(404);
74+
await app.httpRequest()
75+
.post('/api/v1/topic_collect/de_collect')
76+
.send({
77+
accesstoken: user.accessToken,
78+
topic_id,
79+
})
80+
.expect(200);
81+
});
82+
});

0 commit comments

Comments
 (0)