@waline/vercel 1.38.0 → 1.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/controller/comment/rss.js +158 -0
- package/src/controller/rest.js +1 -2
- package/src/locales/index.js +3 -0
- package/src/locales/ko-KR.json +19 -0
- package/src/logic/base.js +60 -60
- package/src/logic/comment/rss.js +54 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waline/vercel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.39.1",
|
|
4
4
|
"description": "vercel server for waline comment system",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"blog",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"nunjucks": "^3.2.4",
|
|
44
44
|
"phpass": "^0.1.1",
|
|
45
45
|
"prismjs": "^1.30.0",
|
|
46
|
+
"rss": "^1.2.2",
|
|
46
47
|
"speakeasy": "^2.0.0",
|
|
47
48
|
"think-helper": "^1.1.4",
|
|
48
49
|
"think-logger3": "^1.4.0",
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const RSS = require('rss');
|
|
2
|
+
const BaseRest = require('../rest.js');
|
|
3
|
+
const { getMarkdownParser } = require('../../service/markdown/index.js');
|
|
4
|
+
|
|
5
|
+
const markdownParser = getMarkdownParser();
|
|
6
|
+
|
|
7
|
+
const isHttpUrl = (value) => /^(https?:)?\/\//i.test(value);
|
|
8
|
+
|
|
9
|
+
const buildAbsoluteUrl = (baseUrl, path) => {
|
|
10
|
+
if (!path) return baseUrl || '';
|
|
11
|
+
if (isHttpUrl(path)) return path;
|
|
12
|
+
if (!baseUrl) return path;
|
|
13
|
+
const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
14
|
+
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
15
|
+
|
|
16
|
+
return `${normalizedBase}${normalizedPath}`;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const buildRssXml = ({ title, link, description, items }) => {
|
|
20
|
+
const channelLink = link || '';
|
|
21
|
+
const now = new Date().toUTCString();
|
|
22
|
+
|
|
23
|
+
const feed = new RSS({
|
|
24
|
+
title,
|
|
25
|
+
description,
|
|
26
|
+
site_url: channelLink,
|
|
27
|
+
pubDate: now,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
items.forEach((item) => {
|
|
31
|
+
feed.item({
|
|
32
|
+
title: item.title || 'Comment',
|
|
33
|
+
description: item.description,
|
|
34
|
+
url: item.link || '',
|
|
35
|
+
guid: item.guid || item.link || '',
|
|
36
|
+
date: item.pubDate || now,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return feed.xml({ indent: true });
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const setRssResponse = (ctx, xml) => {
|
|
44
|
+
ctx.set('Content-Type', 'application/rss+xml; charset=utf-8');
|
|
45
|
+
ctx.body = xml;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
module.exports = class extends BaseRest {
|
|
49
|
+
constructor(ctx) {
|
|
50
|
+
super(ctx);
|
|
51
|
+
this.modelInstance = this.getModel('Comment');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async getAction() {
|
|
55
|
+
const { path, email, user_id: userId, count } = this.get();
|
|
56
|
+
const limit = Number.isFinite(Number(count)) ? Number(count) : 20;
|
|
57
|
+
const safeLimit = Math.min(Math.max(limit, 1), 50);
|
|
58
|
+
|
|
59
|
+
const siteUrl = process.env.SITE_URL || this.ctx.serverURL;
|
|
60
|
+
const siteName = process.env.SITE_NAME || 'Waline';
|
|
61
|
+
|
|
62
|
+
const where = {
|
|
63
|
+
status: ['NOT IN', ['waiting', 'spam']],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (path) {
|
|
67
|
+
where.url = path;
|
|
68
|
+
} else if (email || userId) {
|
|
69
|
+
const parentWhere = {
|
|
70
|
+
status: ['NOT IN', ['waiting', 'spam']],
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (email && userId) {
|
|
74
|
+
parentWhere._complex = {
|
|
75
|
+
_logic: 'or',
|
|
76
|
+
mail: email,
|
|
77
|
+
user_id: userId,
|
|
78
|
+
};
|
|
79
|
+
} else if (email) {
|
|
80
|
+
parentWhere.mail = email;
|
|
81
|
+
} else if (userId) {
|
|
82
|
+
parentWhere.user_id = userId;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const parents = await this.modelInstance.select(parentWhere, {});
|
|
86
|
+
const parentIds = parents.map(({ objectId }) => objectId).filter(Boolean);
|
|
87
|
+
|
|
88
|
+
if (parentIds.length === 0) {
|
|
89
|
+
setRssResponse(
|
|
90
|
+
this.ctx,
|
|
91
|
+
buildRssXml({
|
|
92
|
+
title: `${siteName} Reply Comments`,
|
|
93
|
+
link: siteUrl,
|
|
94
|
+
description: 'Recent reply comments.',
|
|
95
|
+
items: [],
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
where.pid = ['IN', parentIds];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const comments = await this.modelInstance.select(where, {
|
|
105
|
+
desc: 'insertedAt',
|
|
106
|
+
limit: safeLimit,
|
|
107
|
+
field: ['comment', 'insertedAt', 'link', 'nick', 'url', 'user_id'],
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const userModel = this.getModel('Users');
|
|
111
|
+
const userIds = [...new Set(comments.map(({ user_id }) => user_id).filter(Boolean))];
|
|
112
|
+
let users = [];
|
|
113
|
+
|
|
114
|
+
if (userIds.length > 0) {
|
|
115
|
+
users = await userModel.select(
|
|
116
|
+
{ objectId: ['IN', userIds] },
|
|
117
|
+
{ field: ['display_name', 'url'] },
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const items = comments.map((comment) => {
|
|
122
|
+
const user = users.find(({ objectId }) => objectId === comment.user_id);
|
|
123
|
+
const nick = user?.display_name || comment.nick || 'Anonymous';
|
|
124
|
+
const commentUrl = buildAbsoluteUrl(siteUrl, comment.url);
|
|
125
|
+
const itemLink = commentUrl ? `${commentUrl}#${comment.objectId}` : '';
|
|
126
|
+
const description = markdownParser(comment.comment || '');
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
title: `${nick} commented${comment.url ? ` on ${comment.url}` : ''}`,
|
|
130
|
+
link: itemLink || commentUrl,
|
|
131
|
+
guid: comment.objectId,
|
|
132
|
+
pubDate: new Date(comment.insertedAt).toUTCString(),
|
|
133
|
+
description,
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const title = path
|
|
138
|
+
? `${siteName} Comments for ${path}`
|
|
139
|
+
: email || userId
|
|
140
|
+
? `${siteName} Reply Comments`
|
|
141
|
+
: `${siteName} Recent Comments`;
|
|
142
|
+
const description = path
|
|
143
|
+
? `Recent comments for ${path}.`
|
|
144
|
+
: email || userId
|
|
145
|
+
? 'Recent reply comments.'
|
|
146
|
+
: 'Recent comments.';
|
|
147
|
+
|
|
148
|
+
setRssResponse(
|
|
149
|
+
this.ctx,
|
|
150
|
+
buildRssXml({
|
|
151
|
+
title,
|
|
152
|
+
link: siteUrl,
|
|
153
|
+
description,
|
|
154
|
+
items,
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
};
|
package/src/controller/rest.js
CHANGED
|
@@ -21,7 +21,7 @@ module.exports = class extends think.Controller {
|
|
|
21
21
|
const filename = this.__filename || __filename;
|
|
22
22
|
const last = filename.lastIndexOf(path.sep);
|
|
23
23
|
|
|
24
|
-
return filename.slice(last + 1,
|
|
24
|
+
return filename.slice(last + 1, - 3);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
getId() {
|
|
@@ -32,7 +32,6 @@ module.exports = class extends think.Controller {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const last = decodeURIComponent(this.ctx.path.split('/').pop());
|
|
35
|
-
|
|
36
35
|
if (last !== this.resource && /^([a-z0-9]+,?)*$/i.test(last)) {
|
|
37
36
|
return last;
|
|
38
37
|
}
|
package/src/locales/index.js
CHANGED
|
@@ -3,6 +3,7 @@ const it = require('./it.json');
|
|
|
3
3
|
const zhCN = require('./zh-CN.json');
|
|
4
4
|
const zhTW = require('./zh-TW.json');
|
|
5
5
|
const jp = require('./jp.json');
|
|
6
|
+
const koKR = require('./ko-KR.json');
|
|
6
7
|
const de = require('./de.json');
|
|
7
8
|
const esMX = require('./es.json');
|
|
8
9
|
const fr = require('./fr.json');
|
|
@@ -19,6 +20,8 @@ module.exports = {
|
|
|
19
20
|
'it-it': it,
|
|
20
21
|
jp,
|
|
21
22
|
'jp-jp': jp,
|
|
23
|
+
ko: koKR,
|
|
24
|
+
'ko-kr': koKR,
|
|
22
25
|
de,
|
|
23
26
|
esMX,
|
|
24
27
|
fr,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"import data format not support!": "가져오기 데이터 형식이 지원되지 않습니다!",
|
|
3
|
+
"USER_EXIST": "이미 존재하는 사용자입니다",
|
|
4
|
+
"USER_NOT_EXIST": "사용자가 존재하지 않습니다",
|
|
5
|
+
"USER_REGISTERED": "사용자가 등록되었습니다",
|
|
6
|
+
"TOKEN_EXPIRED": "토큰이 만료되었습니다",
|
|
7
|
+
"TWO_FACTOR_AUTH_ERROR_DETAIL": "2단계 인증 오류 상세",
|
|
8
|
+
"[{{name | safe}}] Registration Confirm Mail": "[{{name | safe}}] 가입 확인 메일",
|
|
9
|
+
"Please click <a href=\"{{url}}\">{{url}}<a/> to confirm registration, the link is valid for 1 hour. If you are not registering, please ignore this email.": "<a href=\"{{url}}\">{{url}}</a>을 클릭하여 가입을 확인해 주세요. 링크는 1시간 동안 유효합니다. 가입하지 않으셨다면 이 이메일을 무시해 주세요.",
|
|
10
|
+
"[{{name | safe}}] Reset Password": "[{{name | safe}}] 비밀번호 재설정",
|
|
11
|
+
"Please click <a href=\"{{url}}\">{{url}}</a> to login and change your password as soon as possible!": "<a href=\"{{url}}\">{{url}}</a>을 클릭하여 로그인하고 가능한 빨리 비밀번호를 변경해 주세요!",
|
|
12
|
+
"Duplicate Content": "중복된 내용",
|
|
13
|
+
"Comment too fast": "댓글 작성이 너무 빠릅니다",
|
|
14
|
+
"Registration confirm mail send failed, please {%- if isAdmin -%}check your mail configuration{%- else -%}check your email address and contact administrator{%- endif -%}.": "가입 확인 메일 발송에 실패했습니다. {%- if isAdmin -%}메일 설정을 확인해 주세요{%- else -%}이메일 주소를 확인하고 관리자에게 문의해 주세요{%- endif -%}.",
|
|
15
|
+
"MAIL_SUBJECT": "{{site.name | safe}}에서 댓글에 답글이 달렸습니다",
|
|
16
|
+
"MAIL_TEMPLATE": "<div style='border-top:2px solid #12ADDB;box-shadow:0 1px 3px #AAAAAA;line-height:180%;padding:0 15px 12px;margin:50px auto;font-size:12px;'> <h2 style='border-bottom:1px solid #DDD;font-size:14px;font-weight:normal;padding:13px 0 10px 8px;'> <a style='text-decoration:none;color: #12ADDB;' href='{{site.url}}' target='_blank'>{{site.name}}</a>에서 댓글에 답글이 달렸습니다 </h2>{{parent.nick}}님이 작성한 댓글: <div style='padding:0 12px 0 12px;margin-top:18px'> <div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{parent.comment | safe}}</div><p><strong>{{self.nick}}</strong>님이 답글을 남겼습니다:</p><div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{self.comment | safe}}</div><p><a style='text-decoration:none; color:#12addb' href='{{site.postUrl}}' target='_blank'>전체 답글 보기</a> 또는 <a style='text-decoration:none; color:#12addb' href='{{site.url}}' target='_blank'>{{site.name}}</a> 방문.</p><br/> </div></div>",
|
|
17
|
+
"MAIL_SUBJECT_ADMIN": "{{site.name | safe}}에 새 댓글이 달렸습니다",
|
|
18
|
+
"MAIL_TEMPLATE_ADMIN": "<div style='border-top:2px solid #12ADDB;box-shadow:0 1px 3px #AAAAAA;line-height:180%;padding:0 15px 12px;margin:50px auto;font-size:12px;'> <h2 style='border-bottom:1px solid #DDD;font-size:14px;font-weight:normal;padding:13px 0 10px 8px;'> <a style='text-decoration:none;color: #12ADDB;' href='{{site.url}}' target='_blank'>{{site.name}}</a>에 새 댓글이 달렸습니다 </h2> <p><strong>{{self.nick}}</strong>님이 작성했습니다:</p><div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{self.comment | safe}}</div><p><a style='text-decoration:none; color:#12addb' href='{{site.postUrl}}' target='_blank'>페이지 보기</a></p><br/></div>"
|
|
19
|
+
}
|
package/src/logic/base.js
CHANGED
|
@@ -13,65 +13,9 @@ module.exports = class BaseLogic extends think.Logic {
|
|
|
13
13
|
|
|
14
14
|
// oxlint-disable-next-line max-statements
|
|
15
15
|
async __before() {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (origin) {
|
|
20
|
-
try {
|
|
21
|
-
const parsedOrigin = new URL(origin);
|
|
22
|
-
|
|
23
|
-
origin = parsedOrigin.hostname;
|
|
24
|
-
} catch (err) {
|
|
25
|
-
console.error('Invalid origin format:', origin, err);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
let { secureDomains } = this.config();
|
|
30
|
-
|
|
31
|
-
if (secureDomains) {
|
|
32
|
-
secureDomains = think.isArray(secureDomains) ? secureDomains : [secureDomains];
|
|
33
|
-
|
|
34
|
-
secureDomains.push(
|
|
35
|
-
'localhost',
|
|
36
|
-
'127.0.0.1',
|
|
37
|
-
// 'github.com',
|
|
38
|
-
// 'api.twitter.com',
|
|
39
|
-
// 'www.facebook.com',
|
|
40
|
-
// 'api.weibo.com',
|
|
41
|
-
// 'graph.qq.com',
|
|
42
|
-
);
|
|
43
|
-
secureDomains = [
|
|
44
|
-
...secureDomains,
|
|
45
|
-
...this.ctx.state.oauthServices.map(({ origin }) => origin),
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
// 转换可能的正则表达式字符串为正则表达式对象
|
|
49
|
-
secureDomains = secureDomains
|
|
50
|
-
.map((domain) => {
|
|
51
|
-
// 如果是正则表达式字符串,创建一个 RegExp 对象
|
|
52
|
-
if (typeof domain === 'string' && domain.startsWith('/') && domain.endsWith('/')) {
|
|
53
|
-
try {
|
|
54
|
-
return new RegExp(domain.slice(1, -1)); // 去掉斜杠并创建 RegExp 对象
|
|
55
|
-
} catch (err) {
|
|
56
|
-
console.error('Invalid regex pattern in secureDomains:', domain, err);
|
|
57
|
-
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return domain;
|
|
63
|
-
})
|
|
64
|
-
.filter(Boolean); // 过滤掉无效的正则表达式
|
|
65
|
-
|
|
66
|
-
// 有 referrer 检查 referrer,没有则检查 origin
|
|
67
|
-
const checking = referrer || origin;
|
|
68
|
-
const isSafe = secureDomains.some((domain) =>
|
|
69
|
-
think.isFunction(domain.test) ? domain.test(checking) : domain === checking,
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
if (!isSafe) {
|
|
73
|
-
return this.ctx.throw(403);
|
|
74
|
-
}
|
|
16
|
+
const referrerCheckResult = this.referrerCheck();
|
|
17
|
+
if (!referrerCheckResult) {
|
|
18
|
+
return this.ctx.throw(403);
|
|
75
19
|
}
|
|
76
20
|
|
|
77
21
|
this.ctx.state.userInfo = {};
|
|
@@ -134,11 +78,67 @@ module.exports = class BaseLogic extends think.Logic {
|
|
|
134
78
|
this.ctx.state.token = token;
|
|
135
79
|
}
|
|
136
80
|
|
|
81
|
+
referrerCheck() {
|
|
82
|
+
let { secureDomains } = this.config();
|
|
83
|
+
if (!secureDomains) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const whitelistPath = ['/api/comment/rss'];
|
|
88
|
+
if (this.ctx.path && whitelistPath.includes(this.ctx.path)) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const referrer = this.ctx.referrer(true);
|
|
93
|
+
let { origin } = this.ctx;
|
|
94
|
+
if (origin) {
|
|
95
|
+
try {
|
|
96
|
+
const parsedOrigin = new URL(origin);
|
|
97
|
+
|
|
98
|
+
origin = parsedOrigin.hostname;
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.error('Invalid origin format:', origin, err);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
secureDomains = think.isArray(secureDomains) ? secureDomains : [secureDomains];
|
|
105
|
+
secureDomains.push('localhost', '127.0.0.1');
|
|
106
|
+
secureDomains = [...secureDomains, ...this.ctx.state.oauthServices.map(({ origin }) => origin)];
|
|
107
|
+
|
|
108
|
+
// 转换可能的正则表达式字符串为正则表达式对象
|
|
109
|
+
secureDomains = secureDomains
|
|
110
|
+
.map((domain) => {
|
|
111
|
+
// 如果是正则表达式字符串,创建一个 RegExp 对象
|
|
112
|
+
if (typeof domain === 'string' && domain.startsWith('/') && domain.endsWith('/')) {
|
|
113
|
+
try {
|
|
114
|
+
return new RegExp(domain.slice(1, -1)); // 去掉斜杠并创建 RegExp 对象
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error('Invalid regex pattern in secureDomains:', domain, err);
|
|
117
|
+
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return domain;
|
|
123
|
+
})
|
|
124
|
+
.filter(Boolean); // 过滤掉无效的正则表达式
|
|
125
|
+
|
|
126
|
+
// 有 referrer 检查 referrer,没有则检查 origin
|
|
127
|
+
const checking = referrer || origin;
|
|
128
|
+
const isSafe = secureDomains.some((domain) =>
|
|
129
|
+
think.isFunction(domain.test) ? domain.test(checking) : domain === checking,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
if (!isSafe) {
|
|
133
|
+
return this.ctx.throw(403);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
137
|
getResource() {
|
|
138
138
|
const filename = this.__filename || __filename;
|
|
139
139
|
const last = filename.lastIndexOf(path.sep);
|
|
140
140
|
|
|
141
|
-
return filename.slice(last + 1,
|
|
141
|
+
return filename.slice(last + 1, - 3);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
getId() {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const Base = require('../base.js');
|
|
2
|
+
|
|
3
|
+
module.exports = class extends Base {
|
|
4
|
+
/**
|
|
5
|
+
* @api {GET} /api/comment/rss Get site recent comments RSS
|
|
6
|
+
* @apiGroup Comment
|
|
7
|
+
* @apiVersion 0.0.1
|
|
8
|
+
*
|
|
9
|
+
* @apiParam {String} [count] return comments number, default value is 20
|
|
10
|
+
*
|
|
11
|
+
* @apiHeader {String} Content-Type application/rss+xml
|
|
12
|
+
* @apiSuccess (200) {String} body RSS 2.0 xml content
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* @api {GET} /api/comment/rss?path= Get comments RSS for a path
|
|
16
|
+
* @apiGroup Comment
|
|
17
|
+
* @apiVersion 0.0.1
|
|
18
|
+
*
|
|
19
|
+
* @apiParam {String} path comment url path
|
|
20
|
+
* @apiParam {String} [count] return comments number, default value is 20
|
|
21
|
+
*
|
|
22
|
+
* @apiHeader {String} Content-Type application/rss+xml
|
|
23
|
+
* @apiSuccess (200) {String} body RSS 2.0 xml content
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* @api {GET} /api/comment/rss?email=&user_id= Get reply comments RSS for user
|
|
27
|
+
* @apiGroup Comment
|
|
28
|
+
* @apiVersion 0.0.1
|
|
29
|
+
*
|
|
30
|
+
* @apiParam {String} [email] comment user email
|
|
31
|
+
* @apiParam {String} [user_id] comment user id
|
|
32
|
+
* @apiParam {String} [count] return comments number, default value is 20
|
|
33
|
+
*
|
|
34
|
+
* @apiHeader {String} Content-Type application/rss+xml
|
|
35
|
+
* @apiSuccess (200) {String} body RSS 2.0 xml content
|
|
36
|
+
*/
|
|
37
|
+
getAction() {
|
|
38
|
+
this.rules = {
|
|
39
|
+
path: {
|
|
40
|
+
string: true,
|
|
41
|
+
},
|
|
42
|
+
email: {
|
|
43
|
+
email: true,
|
|
44
|
+
},
|
|
45
|
+
user_id: {
|
|
46
|
+
string: true,
|
|
47
|
+
},
|
|
48
|
+
count: {
|
|
49
|
+
int: { max: 50 },
|
|
50
|
+
default: 20,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
};
|