@waline/vercel 1.15.3 → 1.17.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 +6 -5
- package/src/config/config.js +2 -0
- package/src/controller/comment.js +33 -4
- package/src/controller/user.js +32 -2
- package/src/extend/think.js +22 -0
- package/src/logic/base.js +17 -0
- package/src/logic/user.js +24 -0
- package/src/service/storage/leancloud.js +119 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waline/vercel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.1",
|
|
4
4
|
"description": "vercel server for waline comment system",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"waline",
|
|
@@ -20,20 +20,21 @@
|
|
|
20
20
|
"akismet": "^2.0.7",
|
|
21
21
|
"deta": "^1.1.0",
|
|
22
22
|
"dompurify": "^2.3.6",
|
|
23
|
+
"dy-node-ip2region": "^1.0.1",
|
|
23
24
|
"fast-csv": "^4.3.6",
|
|
24
25
|
"jsdom": "^19.0.0",
|
|
25
26
|
"jsonwebtoken": "^8.5.1",
|
|
26
27
|
"katex": "^0.15.3",
|
|
27
28
|
"leancloud-storage": "^4.12.2",
|
|
28
|
-
"markdown-it": "^
|
|
29
|
-
"markdown-it-emoji": "^2.0.
|
|
29
|
+
"markdown-it": "^13.0.1",
|
|
30
|
+
"markdown-it-emoji": "^2.0.2",
|
|
30
31
|
"markdown-it-sub": "^1.0.0",
|
|
31
32
|
"markdown-it-sup": "^1.0.0",
|
|
32
33
|
"mathjax-full": "^3.2.0",
|
|
33
|
-
"nodemailer": "^6.7.
|
|
34
|
+
"nodemailer": "^6.7.4",
|
|
34
35
|
"nunjucks": "^3.2.3",
|
|
35
36
|
"phpass": "^0.1.1",
|
|
36
|
-
"prismjs": "^1.
|
|
37
|
+
"prismjs": "^1.28.0",
|
|
37
38
|
"request": "^2.88.2",
|
|
38
39
|
"request-promise-native": "^1.0.9",
|
|
39
40
|
"speakeasy": "^2.0.0",
|
package/src/config/config.js
CHANGED
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
TCB_KEY,
|
|
15
15
|
SECURE_DOMAINS,
|
|
16
16
|
DISABLE_USERAGENT,
|
|
17
|
+
DISABLE_REGION,
|
|
17
18
|
AVATAR_PROXY,
|
|
18
19
|
GITHUB_TOKEN,
|
|
19
20
|
DETA_PROJECT_KEY,
|
|
@@ -106,6 +107,7 @@ module.exports = {
|
|
|
106
107
|
disallowIPList: [],
|
|
107
108
|
secureDomains: SECURE_DOMAINS ? SECURE_DOMAINS.split(/\s*,\s*/) : undefined,
|
|
108
109
|
disableUserAgent: DISABLE_USERAGENT && !isFalse(DISABLE_USERAGENT),
|
|
110
|
+
disableRegion: DISABLE_REGION && !isFalse(DISABLE_REGION),
|
|
109
111
|
levels:
|
|
110
112
|
!LEVELS || isFalse(LEVELS)
|
|
111
113
|
? false
|
|
@@ -5,7 +5,7 @@ const { getMarkdownParser } = require('../service/markdown');
|
|
|
5
5
|
|
|
6
6
|
const markdownParser = getMarkdownParser();
|
|
7
7
|
async function formatCmt(
|
|
8
|
-
{ ua, user_id, ...comment },
|
|
8
|
+
{ ua, user_id, ip, ...comment },
|
|
9
9
|
users = [],
|
|
10
10
|
{ avatarProxy },
|
|
11
11
|
loginUser
|
|
@@ -25,6 +25,7 @@ async function formatCmt(
|
|
|
25
25
|
comment.mail = user.email;
|
|
26
26
|
comment.link = user.url;
|
|
27
27
|
comment.type = user.type;
|
|
28
|
+
comment.label = user.label;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
const avatarUrl =
|
|
@@ -41,8 +42,13 @@ async function formatCmt(
|
|
|
41
42
|
delete comment.mail;
|
|
42
43
|
} else {
|
|
43
44
|
comment.orig = comment.comment;
|
|
45
|
+
comment.ip = ip;
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
// administrator can always show region
|
|
49
|
+
if (isAdmin || !think.config('disableRegion')) {
|
|
50
|
+
comment.addr = await think.ip2region(ip, { depth: isAdmin ? 3 : 1 });
|
|
51
|
+
}
|
|
46
52
|
comment.comment = markdownParser(comment.comment);
|
|
47
53
|
return comment;
|
|
48
54
|
}
|
|
@@ -88,6 +94,7 @@ module.exports = class extends BaseRest {
|
|
|
88
94
|
'pid',
|
|
89
95
|
'rid',
|
|
90
96
|
'ua',
|
|
97
|
+
'ip',
|
|
91
98
|
'user_id',
|
|
92
99
|
'sticky',
|
|
93
100
|
],
|
|
@@ -106,7 +113,14 @@ module.exports = class extends BaseRest {
|
|
|
106
113
|
users = await userModel.select(
|
|
107
114
|
{ objectId: ['IN', user_ids] },
|
|
108
115
|
{
|
|
109
|
-
field: [
|
|
116
|
+
field: [
|
|
117
|
+
'display_name',
|
|
118
|
+
'email',
|
|
119
|
+
'url',
|
|
120
|
+
'type',
|
|
121
|
+
'avatar',
|
|
122
|
+
'label',
|
|
123
|
+
],
|
|
110
124
|
}
|
|
111
125
|
);
|
|
112
126
|
}
|
|
@@ -186,7 +200,14 @@ module.exports = class extends BaseRest {
|
|
|
186
200
|
users = await userModel.select(
|
|
187
201
|
{ objectId: ['IN', user_ids] },
|
|
188
202
|
{
|
|
189
|
-
field: [
|
|
203
|
+
field: [
|
|
204
|
+
'display_name',
|
|
205
|
+
'email',
|
|
206
|
+
'url',
|
|
207
|
+
'type',
|
|
208
|
+
'avatar',
|
|
209
|
+
'label',
|
|
210
|
+
],
|
|
190
211
|
}
|
|
191
212
|
);
|
|
192
213
|
}
|
|
@@ -235,6 +256,7 @@ module.exports = class extends BaseRest {
|
|
|
235
256
|
'pid',
|
|
236
257
|
'rid',
|
|
237
258
|
'ua',
|
|
259
|
+
'ip',
|
|
238
260
|
'user_id',
|
|
239
261
|
'sticky',
|
|
240
262
|
],
|
|
@@ -307,7 +329,14 @@ module.exports = class extends BaseRest {
|
|
|
307
329
|
users = await userModel.select(
|
|
308
330
|
{ objectId: ['IN', user_ids] },
|
|
309
331
|
{
|
|
310
|
-
field: [
|
|
332
|
+
field: [
|
|
333
|
+
'display_name',
|
|
334
|
+
'email',
|
|
335
|
+
'url',
|
|
336
|
+
'type',
|
|
337
|
+
'avatar',
|
|
338
|
+
'label',
|
|
339
|
+
],
|
|
311
340
|
}
|
|
312
341
|
);
|
|
313
342
|
}
|
package/src/controller/user.js
CHANGED
|
@@ -11,6 +11,26 @@ module.exports = class extends BaseRest {
|
|
|
11
11
|
);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
async getAction() {
|
|
15
|
+
const { page, pageSize } = this.get();
|
|
16
|
+
|
|
17
|
+
const count = await this.modelInstance.count({});
|
|
18
|
+
const users = await this.modelInstance.select(
|
|
19
|
+
{},
|
|
20
|
+
{
|
|
21
|
+
desc: 'createdAt',
|
|
22
|
+
limit: pageSize,
|
|
23
|
+
offset: Math.max((page - 1) * pageSize, 0),
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
return this.success({
|
|
27
|
+
page,
|
|
28
|
+
totalPages: Math.ceil(count / pageSize),
|
|
29
|
+
pageSize,
|
|
30
|
+
data: users,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
14
34
|
async postAction() {
|
|
15
35
|
const data = this.post();
|
|
16
36
|
const resp = await this.modelInstance.select({
|
|
@@ -92,12 +112,20 @@ module.exports = class extends BaseRest {
|
|
|
92
112
|
}
|
|
93
113
|
|
|
94
114
|
async putAction() {
|
|
95
|
-
const { display_name, url, avatar, password } = this.post();
|
|
115
|
+
const { display_name, url, avatar, password, type, label } = this.post();
|
|
96
116
|
const { objectId } = this.ctx.state.userInfo;
|
|
97
117
|
const twoFactorAuth = this.post('2fa');
|
|
98
118
|
|
|
99
119
|
const updateData = {};
|
|
100
120
|
|
|
121
|
+
if (this.id && type) {
|
|
122
|
+
updateData.type = type;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (think.isString(label)) {
|
|
126
|
+
updateData.label = label;
|
|
127
|
+
}
|
|
128
|
+
|
|
101
129
|
if (display_name) {
|
|
102
130
|
updateData.display_name = display_name;
|
|
103
131
|
}
|
|
@@ -130,7 +158,9 @@ module.exports = class extends BaseRest {
|
|
|
130
158
|
return this.success();
|
|
131
159
|
}
|
|
132
160
|
|
|
133
|
-
await this.modelInstance.update(updateData, {
|
|
161
|
+
await this.modelInstance.update(updateData, {
|
|
162
|
+
objectId: this.id || objectId,
|
|
163
|
+
});
|
|
134
164
|
|
|
135
165
|
return this.success();
|
|
136
166
|
}
|
package/src/extend/think.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
const ip2region = require('dy-node-ip2region');
|
|
2
|
+
const helper = require('think-helper');
|
|
1
3
|
const preventMessage = 'PREVENT_NEXT_PROCESS';
|
|
2
4
|
|
|
5
|
+
const regionSearch = ip2region.create();
|
|
6
|
+
|
|
3
7
|
module.exports = {
|
|
4
8
|
prevent() {
|
|
5
9
|
throw new Error(preventMessage);
|
|
@@ -20,6 +24,10 @@ module.exports = {
|
|
|
20
24
|
},
|
|
21
25
|
promiseAllQueue(promises, taskNum) {
|
|
22
26
|
return new Promise((resolve, reject) => {
|
|
27
|
+
if (!promises.length) {
|
|
28
|
+
return resolve();
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
const ret = [];
|
|
24
32
|
let index = 0;
|
|
25
33
|
let count = 0;
|
|
@@ -46,4 +54,18 @@ module.exports = {
|
|
|
46
54
|
}
|
|
47
55
|
});
|
|
48
56
|
},
|
|
57
|
+
async ip2region(ip, { depth = 1 }) {
|
|
58
|
+
if (!ip) return '';
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const search = helper.promisify(regionSearch.btreeSearch, regionSearch);
|
|
62
|
+
const { region } = await search(ip);
|
|
63
|
+
const [, , province, city, isp] = region.split('|');
|
|
64
|
+
const address = Array.from(new Set([province, city, isp]));
|
|
65
|
+
return address.slice(0, depth).join(' ');
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.log(e);
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
70
|
+
},
|
|
49
71
|
};
|
package/src/logic/base.js
CHANGED
|
@@ -8,6 +8,7 @@ module.exports = class extends think.Logic {
|
|
|
8
8
|
`storage/${this.config('storage')}`,
|
|
9
9
|
'Users'
|
|
10
10
|
);
|
|
11
|
+
this.id = this.getId();
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
async __before() {
|
|
@@ -89,4 +90,20 @@ module.exports = class extends think.Logic {
|
|
|
89
90
|
this.ctx.state.userInfo = userInfo;
|
|
90
91
|
this.ctx.state.token = token;
|
|
91
92
|
}
|
|
93
|
+
|
|
94
|
+
getId() {
|
|
95
|
+
const id = this.get('id');
|
|
96
|
+
|
|
97
|
+
if (id && (think.isString(id) || think.isNumber(id))) {
|
|
98
|
+
return id;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const last = decodeURIComponent(this.ctx.path.split('/').pop());
|
|
102
|
+
|
|
103
|
+
if (last !== this.resource && /^([a-z0-9]+,?)*$/i.test(last)) {
|
|
104
|
+
return last;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return '';
|
|
108
|
+
}
|
|
92
109
|
};
|
package/src/logic/user.js
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
const Base = require('./base');
|
|
2
2
|
|
|
3
3
|
module.exports = class extends Base {
|
|
4
|
+
getAction() {
|
|
5
|
+
const { userInfo } = this.ctx.state;
|
|
6
|
+
if (think.isEmpty(userInfo) || userInfo.type !== 'administrator') {
|
|
7
|
+
return this.fail();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
this.rules = {
|
|
11
|
+
page: {
|
|
12
|
+
int: true,
|
|
13
|
+
default: 1,
|
|
14
|
+
},
|
|
15
|
+
pageSize: {
|
|
16
|
+
int: { max: 100 },
|
|
17
|
+
default: 10,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
4
22
|
/**
|
|
5
23
|
* @api {POST} /user user register
|
|
6
24
|
* @apiGroup User
|
|
@@ -30,9 +48,15 @@ module.exports = class extends Base {
|
|
|
30
48
|
* @apiSuccess (200) {String} errmsg return error message if error
|
|
31
49
|
*/
|
|
32
50
|
putAction() {
|
|
51
|
+
// you need login to update yourself profile
|
|
33
52
|
const { userInfo } = this.ctx.state;
|
|
34
53
|
if (think.isEmpty(userInfo)) {
|
|
35
54
|
return this.fail();
|
|
36
55
|
}
|
|
56
|
+
|
|
57
|
+
// you should be a administrator to update otherself info
|
|
58
|
+
if (this.id && userInfo.type !== 'administrator') {
|
|
59
|
+
return this.fail();
|
|
60
|
+
}
|
|
37
61
|
}
|
|
38
62
|
};
|
|
@@ -126,6 +126,88 @@ module.exports = class extends Base {
|
|
|
126
126
|
return data;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
async _getCmtGroupByMailUserIdCache(key, where) {
|
|
130
|
+
if (this.tableName !== 'Comment' || key !== 'user_id_mail') {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const cacheTableName = `cache_group_count_${key}`;
|
|
135
|
+
const currentTableName = this.tableName;
|
|
136
|
+
this.tableName = cacheTableName;
|
|
137
|
+
const cacheData = await this.select({ _complex: where._complex });
|
|
138
|
+
this.tableName = currentTableName;
|
|
139
|
+
return cacheData;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async _setCmtGroupByMailUserIdCache(key, data) {
|
|
143
|
+
if (this.tableName !== 'Comment' || key !== 'user_id_mail') {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const cacheTableName = `cache_group_count_${key}`;
|
|
148
|
+
const currentTableName = this.tableName;
|
|
149
|
+
this.tableName = cacheTableName;
|
|
150
|
+
|
|
151
|
+
await think.promiseAllQueue(
|
|
152
|
+
data.map((item) => {
|
|
153
|
+
if (item.user_id && !think.isString(item.user_id)) {
|
|
154
|
+
item.user_id = item.user_id.toString();
|
|
155
|
+
}
|
|
156
|
+
return this.add(item);
|
|
157
|
+
}),
|
|
158
|
+
1
|
|
159
|
+
);
|
|
160
|
+
this.tableName = currentTableName;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async _updateCmtGroupByMailUserIdCache(data, method) {
|
|
164
|
+
if (this.tableName !== 'Comment') {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!data.user_id && !data.mail) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const cacheTableName = `cache_group_count_user_id_mail`;
|
|
173
|
+
const cacheData = await this.select({
|
|
174
|
+
_complex: {
|
|
175
|
+
_logic: 'or',
|
|
176
|
+
user_id: think.isObject(data.user_id)
|
|
177
|
+
? data.user_id.toString()
|
|
178
|
+
: data.user_id,
|
|
179
|
+
mail: data.mail,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
if (think.isEmpty(data)) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let count = cacheData[0].count;
|
|
187
|
+
switch (method) {
|
|
188
|
+
case 'add':
|
|
189
|
+
if (data.status === 'approved') {
|
|
190
|
+
count += 1;
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
case 'udpate_status':
|
|
194
|
+
if (data.status === 'approved') {
|
|
195
|
+
count += 1;
|
|
196
|
+
} else {
|
|
197
|
+
count -= 1;
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
case 'delete':
|
|
201
|
+
count -= 1;
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const currentTableName = this.tableName;
|
|
206
|
+
this.tableName = cacheTableName;
|
|
207
|
+
await this.update({ count }, { objectId: cacheData[0].objectId });
|
|
208
|
+
this.tableName = currentTableName;
|
|
209
|
+
}
|
|
210
|
+
|
|
129
211
|
async count(where = {}, options = {}) {
|
|
130
212
|
const instance = this.where(this.tableName, where);
|
|
131
213
|
if (!options.group) {
|
|
@@ -137,7 +219,19 @@ module.exports = class extends Base {
|
|
|
137
219
|
});
|
|
138
220
|
}
|
|
139
221
|
|
|
140
|
-
//
|
|
222
|
+
// get group count cache by group field where data
|
|
223
|
+
const cacheData = await this._getCmtGroupByMailUserIdCache(
|
|
224
|
+
options.group.join('_'),
|
|
225
|
+
where
|
|
226
|
+
);
|
|
227
|
+
const cacheDataMap = {};
|
|
228
|
+
for (let i = 0; i < cacheData.length; i++) {
|
|
229
|
+
const key = options.group
|
|
230
|
+
.map((item) => cacheData[i][item] || null)
|
|
231
|
+
.join('_');
|
|
232
|
+
cacheDataMap[key] = cacheData[i];
|
|
233
|
+
}
|
|
234
|
+
|
|
141
235
|
const counts = [];
|
|
142
236
|
const countsPromise = [];
|
|
143
237
|
for (let i = 0; i < options.group.length; i++) {
|
|
@@ -152,6 +246,19 @@ module.exports = class extends Base {
|
|
|
152
246
|
});
|
|
153
247
|
|
|
154
248
|
for (let j = 0; j < where._complex[groupName][1].length; j++) {
|
|
249
|
+
const cacheKey = options.group
|
|
250
|
+
.map(
|
|
251
|
+
(item) =>
|
|
252
|
+
({
|
|
253
|
+
...groupFlatValue,
|
|
254
|
+
[groupName]: where._complex[groupName][1][j],
|
|
255
|
+
}[item] || null)
|
|
256
|
+
)
|
|
257
|
+
.join('_');
|
|
258
|
+
if (cacheDataMap[cacheKey]) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
|
|
155
262
|
const groupWhere = {
|
|
156
263
|
...where,
|
|
157
264
|
...groupFlatValue,
|
|
@@ -172,8 +279,10 @@ module.exports = class extends Base {
|
|
|
172
279
|
}
|
|
173
280
|
}
|
|
174
281
|
|
|
175
|
-
await think.promiseAllQueue(countsPromise,
|
|
176
|
-
|
|
282
|
+
await think.promiseAllQueue(countsPromise, 1);
|
|
283
|
+
// cache data
|
|
284
|
+
await this._setCmtGroupByMailUserIdCache(options.group.join('_'), counts);
|
|
285
|
+
return [...cacheData, ...counts];
|
|
177
286
|
}
|
|
178
287
|
|
|
179
288
|
async add(
|
|
@@ -190,6 +299,7 @@ module.exports = class extends Base {
|
|
|
190
299
|
instance.setACL(acl);
|
|
191
300
|
|
|
192
301
|
const resp = await instance.save();
|
|
302
|
+
await this._updateCmtGroupByMailUserIdCache(data, 'add');
|
|
193
303
|
return resp.toJSON();
|
|
194
304
|
}
|
|
195
305
|
|
|
@@ -199,11 +309,16 @@ module.exports = class extends Base {
|
|
|
199
309
|
|
|
200
310
|
return Promise.all(
|
|
201
311
|
ret.map(async (item) => {
|
|
312
|
+
const _oldStatus = item.get('status');
|
|
202
313
|
if (think.isFunction(data)) {
|
|
203
314
|
item.set(data(item.toJSON()));
|
|
204
315
|
} else {
|
|
205
316
|
item.set(data);
|
|
206
317
|
}
|
|
318
|
+
const _newStatus = item.get('status');
|
|
319
|
+
if (_newStatus && _oldStatus !== _newStatus) {
|
|
320
|
+
await this._updateCmtGroupByMailUserIdCache(data, 'update_status');
|
|
321
|
+
}
|
|
207
322
|
|
|
208
323
|
const resp = await item.save();
|
|
209
324
|
return resp.toJSON();
|
|
@@ -214,6 +329,7 @@ module.exports = class extends Base {
|
|
|
214
329
|
async delete(where) {
|
|
215
330
|
const instance = this.where(this.tableName, where);
|
|
216
331
|
const data = await instance.find();
|
|
332
|
+
await this._updateCmtGroupByMailUserIdCache(data, 'delete');
|
|
217
333
|
|
|
218
334
|
return AV.Object.destroyAll(data);
|
|
219
335
|
}
|