@waline/vercel 1.30.3 → 1.30.5
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 +1 -1
- package/src/config/extend.js +6 -0
- package/src/controller/comment.js +358 -391
- package/src/extend/think.js +12 -0
package/package.json
CHANGED
package/src/config/extend.js
CHANGED
|
@@ -4,6 +4,8 @@ const Mongo = require('think-mongo');
|
|
|
4
4
|
|
|
5
5
|
const { isNetlify, netlifyFunctionPrefix } = require('./netlify');
|
|
6
6
|
|
|
7
|
+
const isDeta = think.env === 'deta' || process.env.DETA_RUNTIME === 'true';
|
|
8
|
+
|
|
7
9
|
module.exports = [
|
|
8
10
|
Model(think.app),
|
|
9
11
|
Mongo(think.app),
|
|
@@ -22,6 +24,10 @@ module.exports = [
|
|
|
22
24
|
return `${protocol}://${host}${netlifyFunctionPrefix}`;
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
if (isDeta) {
|
|
28
|
+
return `https://${host}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
return `${protocol}://${host}`;
|
|
26
32
|
},
|
|
27
33
|
async webhook(type, data) {
|
|
@@ -80,399 +80,17 @@ module.exports = class extends BaseRest {
|
|
|
80
80
|
|
|
81
81
|
async getAction() {
|
|
82
82
|
const { type } = this.get();
|
|
83
|
-
const { userInfo } = this.ctx.state;
|
|
84
|
-
|
|
85
|
-
switch (type) {
|
|
86
|
-
case 'recent': {
|
|
87
|
-
const { count } = this.get();
|
|
88
|
-
const where = {};
|
|
89
|
-
|
|
90
|
-
if (think.isEmpty(userInfo) || this.config('storage') === 'deta') {
|
|
91
|
-
where.status = ['NOT IN', ['waiting', 'spam']];
|
|
92
|
-
} else {
|
|
93
|
-
where._complex = {
|
|
94
|
-
_logic: 'or',
|
|
95
|
-
status: ['NOT IN', ['waiting', 'spam']],
|
|
96
|
-
user_id: userInfo.objectId,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const comments = await this.modelInstance.select(where, {
|
|
101
|
-
desc: 'insertedAt',
|
|
102
|
-
limit: count,
|
|
103
|
-
field: [
|
|
104
|
-
'status',
|
|
105
|
-
'comment',
|
|
106
|
-
'insertedAt',
|
|
107
|
-
'link',
|
|
108
|
-
'mail',
|
|
109
|
-
'nick',
|
|
110
|
-
'url',
|
|
111
|
-
'pid',
|
|
112
|
-
'rid',
|
|
113
|
-
'ua',
|
|
114
|
-
'ip',
|
|
115
|
-
'user_id',
|
|
116
|
-
'sticky',
|
|
117
|
-
'like',
|
|
118
|
-
],
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
const userModel = this.getModel('Users');
|
|
122
|
-
const user_ids = Array.from(
|
|
123
|
-
new Set(comments.map(({ user_id }) => user_id).filter((v) => v))
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
let users = [];
|
|
127
|
-
|
|
128
|
-
if (user_ids.length) {
|
|
129
|
-
users = await userModel.select(
|
|
130
|
-
{ objectId: ['IN', user_ids] },
|
|
131
|
-
{
|
|
132
|
-
field: [
|
|
133
|
-
'display_name',
|
|
134
|
-
'email',
|
|
135
|
-
'url',
|
|
136
|
-
'type',
|
|
137
|
-
'avatar',
|
|
138
|
-
'label',
|
|
139
|
-
],
|
|
140
|
-
}
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return this.jsonOrSuccess(
|
|
145
|
-
await Promise.all(
|
|
146
|
-
comments.map((cmt) =>
|
|
147
|
-
formatCmt(
|
|
148
|
-
cmt,
|
|
149
|
-
users,
|
|
150
|
-
{ ...this.config(), deprecated: this.ctx.state.deprecated },
|
|
151
|
-
userInfo
|
|
152
|
-
)
|
|
153
|
-
)
|
|
154
|
-
)
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
case 'count': {
|
|
159
|
-
const { url } = this.get();
|
|
160
|
-
const where =
|
|
161
|
-
Array.isArray(url) && url.length ? { url: ['IN', url] } : {};
|
|
162
|
-
|
|
163
|
-
if (think.isEmpty(userInfo) || this.config('storage') === 'deta') {
|
|
164
|
-
where.status = ['NOT IN', ['waiting', 'spam']];
|
|
165
|
-
} else {
|
|
166
|
-
where._complex = {
|
|
167
|
-
_logic: 'or',
|
|
168
|
-
status: ['NOT IN', ['waiting', 'spam']],
|
|
169
|
-
user_id: userInfo.objectId,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
Array.isArray(url) &&
|
|
175
|
-
(url.length > 1 || !this.ctx.state.deprecated)
|
|
176
|
-
) {
|
|
177
|
-
const data = await this.modelInstance.select(where, {
|
|
178
|
-
field: ['url'],
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
return this.jsonOrSuccess(
|
|
182
|
-
url.map((u) => data.filter(({ url }) => url === u).length)
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
const data = await this.modelInstance.count(where);
|
|
186
|
-
|
|
187
|
-
return this.jsonOrSuccess(data);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
case 'list': {
|
|
191
|
-
const { page, pageSize, owner, status, keyword } = this.get();
|
|
192
|
-
const where = {};
|
|
193
|
-
|
|
194
|
-
if (owner === 'mine') {
|
|
195
|
-
const { userInfo } = this.ctx.state;
|
|
196
|
-
|
|
197
|
-
where.mail = userInfo.email;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (status) {
|
|
201
|
-
where.status = status;
|
|
202
|
-
|
|
203
|
-
// compat with valine old data without status property
|
|
204
|
-
if (status === 'approved') {
|
|
205
|
-
where.status = ['NOT IN', ['waiting', 'spam']];
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (keyword) {
|
|
210
|
-
where.comment = ['LIKE', `%${keyword}%`];
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const count = await this.modelInstance.count(where);
|
|
214
|
-
const spamCount = await this.modelInstance.count({ status: 'spam' });
|
|
215
|
-
const waitingCount = await this.modelInstance.count({
|
|
216
|
-
status: 'waiting',
|
|
217
|
-
});
|
|
218
|
-
const comments = await this.modelInstance.select(where, {
|
|
219
|
-
desc: 'insertedAt',
|
|
220
|
-
limit: pageSize,
|
|
221
|
-
offset: Math.max((page - 1) * pageSize, 0),
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
const userModel = this.getModel('Users');
|
|
225
|
-
const user_ids = Array.from(
|
|
226
|
-
new Set(comments.map(({ user_id }) => user_id).filter((v) => v))
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
let users = [];
|
|
230
|
-
|
|
231
|
-
if (user_ids.length) {
|
|
232
|
-
users = await userModel.select(
|
|
233
|
-
{ objectId: ['IN', user_ids] },
|
|
234
|
-
{
|
|
235
|
-
field: [
|
|
236
|
-
'display_name',
|
|
237
|
-
'email',
|
|
238
|
-
'url',
|
|
239
|
-
'type',
|
|
240
|
-
'avatar',
|
|
241
|
-
'label',
|
|
242
|
-
],
|
|
243
|
-
}
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return this.success({
|
|
248
|
-
page,
|
|
249
|
-
totalPages: Math.ceil(count / pageSize),
|
|
250
|
-
pageSize,
|
|
251
|
-
spamCount,
|
|
252
|
-
waitingCount,
|
|
253
|
-
data: await Promise.all(
|
|
254
|
-
comments.map((cmt) =>
|
|
255
|
-
formatCmt(
|
|
256
|
-
cmt,
|
|
257
|
-
users,
|
|
258
|
-
{ ...this.config(), deprecated: this.ctx.state.deprecated },
|
|
259
|
-
userInfo
|
|
260
|
-
)
|
|
261
|
-
)
|
|
262
|
-
),
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
default: {
|
|
267
|
-
const { path: url, page, pageSize, sortBy } = this.get();
|
|
268
|
-
const where = { url };
|
|
269
|
-
|
|
270
|
-
if (think.isEmpty(userInfo) || this.config('storage') === 'deta') {
|
|
271
|
-
where.status = ['NOT IN', ['waiting', 'spam']];
|
|
272
|
-
} else if (userInfo.type !== 'administrator') {
|
|
273
|
-
where._complex = {
|
|
274
|
-
_logic: 'or',
|
|
275
|
-
status: ['NOT IN', ['waiting', 'spam']],
|
|
276
|
-
user_id: userInfo.objectId,
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const totalCount = await this.modelInstance.count(where);
|
|
281
|
-
const pageOffset = Math.max((page - 1) * pageSize, 0);
|
|
282
|
-
let comments = [];
|
|
283
|
-
let rootComments = [];
|
|
284
|
-
let rootCount = 0;
|
|
285
|
-
const selectOptions = {
|
|
286
|
-
field: [
|
|
287
|
-
'status',
|
|
288
|
-
'comment',
|
|
289
|
-
'insertedAt',
|
|
290
|
-
'link',
|
|
291
|
-
'mail',
|
|
292
|
-
'nick',
|
|
293
|
-
'pid',
|
|
294
|
-
'rid',
|
|
295
|
-
'ua',
|
|
296
|
-
'ip',
|
|
297
|
-
'user_id',
|
|
298
|
-
'sticky',
|
|
299
|
-
'like',
|
|
300
|
-
],
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
if (sortBy) {
|
|
304
|
-
const [field, order] = sortBy.split('_');
|
|
305
|
-
|
|
306
|
-
if (order === 'desc') {
|
|
307
|
-
selectOptions.desc = field;
|
|
308
|
-
} else if (order === 'asc') {
|
|
309
|
-
// do nothing because of ascending order is default behavior
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* most of case we have just little comments
|
|
315
|
-
* while if we want get rootComments, rootCount, childComments with pagination
|
|
316
|
-
* we have to query three times from storage service
|
|
317
|
-
* That's so expensive for user, especially in the serverless.
|
|
318
|
-
* so we have a comments length check
|
|
319
|
-
* If you have less than 1000 comments, then we'll get all comments one time
|
|
320
|
-
* then we'll compute rootComment, rootCount, childComments in program to reduce http request query
|
|
321
|
-
*
|
|
322
|
-
* Why we have limit and the limit is 1000?
|
|
323
|
-
* Many serverless storages have fetch data limit, for example LeanCloud is 100, and CloudBase is 1000
|
|
324
|
-
* If we have much comments, We should use more request to fetch all comments
|
|
325
|
-
* If we have 3000 comments, We have to use 30 http request to fetch comments, things go athwart.
|
|
326
|
-
* And Serverless Service like vercel have execute time limit
|
|
327
|
-
* if we have more http requests in a serverless function, it may timeout easily.
|
|
328
|
-
* so we use limit to avoid it.
|
|
329
|
-
*/
|
|
330
|
-
if (totalCount < 1000) {
|
|
331
|
-
comments = await this.modelInstance.select(where, selectOptions);
|
|
332
|
-
rootCount = comments.filter(({ rid }) => !rid).length;
|
|
333
|
-
rootComments = [
|
|
334
|
-
...comments.filter(({ rid, sticky }) => !rid && sticky),
|
|
335
|
-
...comments.filter(({ rid, sticky }) => !rid && !sticky),
|
|
336
|
-
].slice(pageOffset, pageOffset + pageSize);
|
|
337
|
-
const rootIds = {};
|
|
338
|
-
|
|
339
|
-
rootComments.forEach(({ objectId }) => {
|
|
340
|
-
rootIds[objectId] = true;
|
|
341
|
-
});
|
|
342
|
-
comments = comments.filter(
|
|
343
|
-
(cmt) => rootIds[cmt.objectId] || rootIds[cmt.rid]
|
|
344
|
-
);
|
|
345
|
-
} else {
|
|
346
|
-
comments = await this.modelInstance.select(
|
|
347
|
-
{ ...where, rid: undefined },
|
|
348
|
-
{ ...selectOptions }
|
|
349
|
-
);
|
|
350
|
-
rootCount = comments.length;
|
|
351
|
-
rootComments = [
|
|
352
|
-
...comments.filter(({ rid, sticky }) => !rid && sticky),
|
|
353
|
-
...comments.filter(({ rid, sticky }) => !rid && !sticky),
|
|
354
|
-
].slice(pageOffset, pageOffset + pageSize);
|
|
355
|
-
|
|
356
|
-
const children = await this.modelInstance.select(
|
|
357
|
-
{
|
|
358
|
-
...where,
|
|
359
|
-
rid: ['IN', rootComments.map(({ objectId }) => objectId)],
|
|
360
|
-
},
|
|
361
|
-
selectOptions
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
comments = [...rootComments, ...children];
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
const userModel = this.getModel('Users');
|
|
368
|
-
const user_ids = Array.from(
|
|
369
|
-
new Set(comments.map(({ user_id }) => user_id).filter((v) => v))
|
|
370
|
-
);
|
|
371
|
-
let users = [];
|
|
372
|
-
|
|
373
|
-
if (user_ids.length) {
|
|
374
|
-
users = await userModel.select(
|
|
375
|
-
{ objectId: ['IN', user_ids] },
|
|
376
|
-
{
|
|
377
|
-
field: [
|
|
378
|
-
'display_name',
|
|
379
|
-
'email',
|
|
380
|
-
'url',
|
|
381
|
-
'type',
|
|
382
|
-
'avatar',
|
|
383
|
-
'label',
|
|
384
|
-
],
|
|
385
|
-
}
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (think.isArray(this.config('levels'))) {
|
|
390
|
-
const countWhere = {
|
|
391
|
-
status: ['NOT IN', ['waiting', 'spam']],
|
|
392
|
-
_complex: {},
|
|
393
|
-
};
|
|
394
83
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
);
|
|
84
|
+
const fnMap = {
|
|
85
|
+
recent: this.getRecentCommentList,
|
|
86
|
+
count: this.getCommentCount,
|
|
87
|
+
list: this.getAdminCommentList,
|
|
88
|
+
};
|
|
401
89
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
405
|
-
if (!think.isEmpty(countWhere._complex)) {
|
|
406
|
-
countWhere._complex._logic = 'or';
|
|
407
|
-
} else {
|
|
408
|
-
delete countWhere._complex;
|
|
409
|
-
}
|
|
410
|
-
const counts = await this.modelInstance.count(countWhere, {
|
|
411
|
-
group: ['user_id', 'mail'],
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
comments.forEach((cmt) => {
|
|
415
|
-
const countItem = (counts || []).find(({ mail, user_id }) => {
|
|
416
|
-
if (cmt.user_id) {
|
|
417
|
-
return user_id === cmt.user_id;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return mail === cmt.mail;
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
let level = 0;
|
|
424
|
-
|
|
425
|
-
if (countItem) {
|
|
426
|
-
const _level = think.findLastIndex(
|
|
427
|
-
this.config('levels'),
|
|
428
|
-
(l) => l <= countItem.count
|
|
429
|
-
);
|
|
430
|
-
|
|
431
|
-
if (_level !== -1) {
|
|
432
|
-
level = _level;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
cmt.level = level;
|
|
436
|
-
});
|
|
437
|
-
}
|
|
90
|
+
const fn = fnMap[type] || this.getCommentList;
|
|
91
|
+
const data = await fn.call(this);
|
|
438
92
|
|
|
439
|
-
|
|
440
|
-
page,
|
|
441
|
-
totalPages: Math.ceil(rootCount / pageSize),
|
|
442
|
-
pageSize,
|
|
443
|
-
count: totalCount,
|
|
444
|
-
data: await Promise.all(
|
|
445
|
-
rootComments.map(async (comment) => {
|
|
446
|
-
const cmt = await formatCmt(
|
|
447
|
-
comment,
|
|
448
|
-
users,
|
|
449
|
-
{ ...this.config(), deprecated: this.ctx.state.deprecated },
|
|
450
|
-
userInfo
|
|
451
|
-
);
|
|
452
|
-
|
|
453
|
-
cmt.children = await Promise.all(
|
|
454
|
-
comments
|
|
455
|
-
.filter(({ rid }) => rid === cmt.objectId)
|
|
456
|
-
.map((cmt) =>
|
|
457
|
-
formatCmt(
|
|
458
|
-
cmt,
|
|
459
|
-
users,
|
|
460
|
-
{
|
|
461
|
-
...this.config(),
|
|
462
|
-
deprecated: this.ctx.state.deprecated,
|
|
463
|
-
},
|
|
464
|
-
userInfo
|
|
465
|
-
)
|
|
466
|
-
)
|
|
467
|
-
.reverse()
|
|
468
|
-
);
|
|
469
|
-
|
|
470
|
-
return cmt;
|
|
471
|
-
})
|
|
472
|
-
),
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
}
|
|
93
|
+
return this.jsonOrSuccess(data);
|
|
476
94
|
}
|
|
477
95
|
|
|
478
96
|
async postAction() {
|
|
@@ -493,7 +111,7 @@ module.exports = class extends BaseRest {
|
|
|
493
111
|
user_id: this.ctx.state.userInfo.objectId,
|
|
494
112
|
};
|
|
495
113
|
|
|
496
|
-
if (pid && this.ctx.deprecated) {
|
|
114
|
+
if (pid && this.ctx.state.deprecated) {
|
|
497
115
|
data.comment = `[@${at}](#${pid}): ` + data.comment;
|
|
498
116
|
}
|
|
499
117
|
|
|
@@ -766,4 +384,353 @@ module.exports = class extends BaseRest {
|
|
|
766
384
|
|
|
767
385
|
return this.success();
|
|
768
386
|
}
|
|
387
|
+
|
|
388
|
+
async getCommentList() {
|
|
389
|
+
const { userInfo } = this.ctx.state;
|
|
390
|
+
const { path: url, page, pageSize, sortBy } = this.get();
|
|
391
|
+
const where = { url };
|
|
392
|
+
|
|
393
|
+
if (think.isEmpty(userInfo) || this.config('storage') === 'deta') {
|
|
394
|
+
where.status = ['NOT IN', ['waiting', 'spam']];
|
|
395
|
+
} else if (userInfo.type !== 'administrator') {
|
|
396
|
+
where._complex = {
|
|
397
|
+
_logic: 'or',
|
|
398
|
+
status: ['NOT IN', ['waiting', 'spam']],
|
|
399
|
+
user_id: userInfo.objectId,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const totalCount = await this.modelInstance.count(where);
|
|
404
|
+
const pageOffset = Math.max((page - 1) * pageSize, 0);
|
|
405
|
+
let comments = [];
|
|
406
|
+
let rootComments = [];
|
|
407
|
+
let rootCount = 0;
|
|
408
|
+
const selectOptions = {
|
|
409
|
+
field: [
|
|
410
|
+
'status',
|
|
411
|
+
'comment',
|
|
412
|
+
'insertedAt',
|
|
413
|
+
'link',
|
|
414
|
+
'mail',
|
|
415
|
+
'nick',
|
|
416
|
+
'pid',
|
|
417
|
+
'rid',
|
|
418
|
+
'ua',
|
|
419
|
+
'ip',
|
|
420
|
+
'user_id',
|
|
421
|
+
'sticky',
|
|
422
|
+
'like',
|
|
423
|
+
],
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
if (sortBy) {
|
|
427
|
+
const [field, order] = sortBy.split('_');
|
|
428
|
+
|
|
429
|
+
if (order === 'desc') {
|
|
430
|
+
selectOptions.desc = field;
|
|
431
|
+
} else if (order === 'asc') {
|
|
432
|
+
// do nothing because of ascending order is default behavior
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* most of case we have just little comments
|
|
438
|
+
* while if we want get rootComments, rootCount, childComments with pagination
|
|
439
|
+
* we have to query three times from storage service
|
|
440
|
+
* That's so expensive for user, especially in the serverless.
|
|
441
|
+
* so we have a comments length check
|
|
442
|
+
* If you have less than 1000 comments, then we'll get all comments one time
|
|
443
|
+
* then we'll compute rootComment, rootCount, childComments in program to reduce http request query
|
|
444
|
+
*
|
|
445
|
+
* Why we have limit and the limit is 1000?
|
|
446
|
+
* Many serverless storages have fetch data limit, for example LeanCloud is 100, and CloudBase is 1000
|
|
447
|
+
* If we have much comments, We should use more request to fetch all comments
|
|
448
|
+
* If we have 3000 comments, We have to use 30 http request to fetch comments, things go athwart.
|
|
449
|
+
* And Serverless Service like vercel have execute time limit
|
|
450
|
+
* if we have more http requests in a serverless function, it may timeout easily.
|
|
451
|
+
* so we use limit to avoid it.
|
|
452
|
+
*/
|
|
453
|
+
if (totalCount < 1000) {
|
|
454
|
+
comments = await this.modelInstance.select(where, selectOptions);
|
|
455
|
+
rootCount = comments.filter(({ rid }) => !rid).length;
|
|
456
|
+
rootComments = [
|
|
457
|
+
...comments.filter(({ rid, sticky }) => !rid && sticky),
|
|
458
|
+
...comments.filter(({ rid, sticky }) => !rid && !sticky),
|
|
459
|
+
].slice(pageOffset, pageOffset + pageSize);
|
|
460
|
+
const rootIds = {};
|
|
461
|
+
|
|
462
|
+
rootComments.forEach(({ objectId }) => {
|
|
463
|
+
rootIds[objectId] = true;
|
|
464
|
+
});
|
|
465
|
+
comments = comments.filter(
|
|
466
|
+
(cmt) => rootIds[cmt.objectId] || rootIds[cmt.rid]
|
|
467
|
+
);
|
|
468
|
+
} else {
|
|
469
|
+
comments = await this.modelInstance.select(
|
|
470
|
+
{ ...where, rid: undefined },
|
|
471
|
+
{ ...selectOptions }
|
|
472
|
+
);
|
|
473
|
+
rootCount = comments.length;
|
|
474
|
+
rootComments = [
|
|
475
|
+
...comments.filter(({ rid, sticky }) => !rid && sticky),
|
|
476
|
+
...comments.filter(({ rid, sticky }) => !rid && !sticky),
|
|
477
|
+
].slice(pageOffset, pageOffset + pageSize);
|
|
478
|
+
|
|
479
|
+
const children = await this.modelInstance.select(
|
|
480
|
+
{
|
|
481
|
+
...where,
|
|
482
|
+
rid: ['IN', rootComments.map(({ objectId }) => objectId)],
|
|
483
|
+
},
|
|
484
|
+
selectOptions
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
comments = [...rootComments, ...children];
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const userModel = this.getModel('Users');
|
|
491
|
+
const user_ids = Array.from(
|
|
492
|
+
new Set(comments.map(({ user_id }) => user_id).filter((v) => v))
|
|
493
|
+
);
|
|
494
|
+
let users = [];
|
|
495
|
+
|
|
496
|
+
if (user_ids.length) {
|
|
497
|
+
users = await userModel.select(
|
|
498
|
+
{ objectId: ['IN', user_ids] },
|
|
499
|
+
{
|
|
500
|
+
field: ['display_name', 'email', 'url', 'type', 'avatar', 'label'],
|
|
501
|
+
}
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (think.isArray(this.config('levels'))) {
|
|
506
|
+
const countWhere = {
|
|
507
|
+
status: ['NOT IN', ['waiting', 'spam']],
|
|
508
|
+
_complex: {},
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
if (user_ids.length) {
|
|
512
|
+
countWhere._complex.user_id = ['IN', user_ids];
|
|
513
|
+
}
|
|
514
|
+
const mails = Array.from(
|
|
515
|
+
new Set(comments.map(({ mail }) => mail).filter((v) => v))
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
if (mails.length) {
|
|
519
|
+
countWhere._complex.mail = ['IN', mails];
|
|
520
|
+
}
|
|
521
|
+
if (!think.isEmpty(countWhere._complex)) {
|
|
522
|
+
countWhere._complex._logic = 'or';
|
|
523
|
+
} else {
|
|
524
|
+
delete countWhere._complex;
|
|
525
|
+
}
|
|
526
|
+
const counts = await this.modelInstance.count(countWhere, {
|
|
527
|
+
group: ['user_id', 'mail'],
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
comments.forEach((cmt) => {
|
|
531
|
+
const countItem = (counts || []).find(({ mail, user_id }) =>
|
|
532
|
+
cmt.user_id ? user_id === cmt.user_id : mail === cmt.mail
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
cmt.level = think.getLevel(countItem?.count);
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return {
|
|
540
|
+
page,
|
|
541
|
+
totalPages: Math.ceil(rootCount / pageSize),
|
|
542
|
+
pageSize,
|
|
543
|
+
count: totalCount,
|
|
544
|
+
data: await Promise.all(
|
|
545
|
+
rootComments.map(async (comment) => {
|
|
546
|
+
const cmt = await formatCmt(
|
|
547
|
+
comment,
|
|
548
|
+
users,
|
|
549
|
+
{ ...this.config(), deprecated: this.ctx.state.deprecated },
|
|
550
|
+
userInfo
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
cmt.children = await Promise.all(
|
|
554
|
+
comments
|
|
555
|
+
.filter(({ rid }) => rid === cmt.objectId)
|
|
556
|
+
.map((cmt) =>
|
|
557
|
+
formatCmt(
|
|
558
|
+
cmt,
|
|
559
|
+
users,
|
|
560
|
+
{
|
|
561
|
+
...this.config(),
|
|
562
|
+
deprecated: this.ctx.state.deprecated,
|
|
563
|
+
},
|
|
564
|
+
userInfo
|
|
565
|
+
)
|
|
566
|
+
)
|
|
567
|
+
.reverse()
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
return cmt;
|
|
571
|
+
})
|
|
572
|
+
),
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
async getAdminCommentList() {
|
|
577
|
+
const { userInfo } = this.ctx.state;
|
|
578
|
+
const { page, pageSize, owner, status, keyword } = this.get();
|
|
579
|
+
const where = {};
|
|
580
|
+
|
|
581
|
+
if (owner === 'mine') {
|
|
582
|
+
const { userInfo } = this.ctx.state;
|
|
583
|
+
|
|
584
|
+
where.mail = userInfo.email;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (status) {
|
|
588
|
+
where.status = status;
|
|
589
|
+
|
|
590
|
+
// compat with valine old data without status property
|
|
591
|
+
if (status === 'approved') {
|
|
592
|
+
where.status = ['NOT IN', ['waiting', 'spam']];
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (keyword) {
|
|
597
|
+
where.comment = ['LIKE', `%${keyword}%`];
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const count = await this.modelInstance.count(where);
|
|
601
|
+
const spamCount = await this.modelInstance.count({ status: 'spam' });
|
|
602
|
+
const waitingCount = await this.modelInstance.count({
|
|
603
|
+
status: 'waiting',
|
|
604
|
+
});
|
|
605
|
+
const comments = await this.modelInstance.select(where, {
|
|
606
|
+
desc: 'insertedAt',
|
|
607
|
+
limit: pageSize,
|
|
608
|
+
offset: Math.max((page - 1) * pageSize, 0),
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
const userModel = this.getModel('Users');
|
|
612
|
+
const user_ids = Array.from(
|
|
613
|
+
new Set(comments.map(({ user_id }) => user_id).filter((v) => v))
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
let users = [];
|
|
617
|
+
|
|
618
|
+
if (user_ids.length) {
|
|
619
|
+
users = await userModel.select(
|
|
620
|
+
{ objectId: ['IN', user_ids] },
|
|
621
|
+
{
|
|
622
|
+
field: ['display_name', 'email', 'url', 'type', 'avatar', 'label'],
|
|
623
|
+
}
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return {
|
|
628
|
+
page,
|
|
629
|
+
totalPages: Math.ceil(count / pageSize),
|
|
630
|
+
pageSize,
|
|
631
|
+
spamCount,
|
|
632
|
+
waitingCount,
|
|
633
|
+
data: await Promise.all(
|
|
634
|
+
comments.map((cmt) =>
|
|
635
|
+
formatCmt(
|
|
636
|
+
cmt,
|
|
637
|
+
users,
|
|
638
|
+
{ ...this.config(), deprecated: this.ctx.state.deprecated },
|
|
639
|
+
userInfo
|
|
640
|
+
)
|
|
641
|
+
)
|
|
642
|
+
),
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
async getRecentCommentList() {
|
|
647
|
+
const { count } = this.get();
|
|
648
|
+
const { userInfo } = this.ctx.state;
|
|
649
|
+
const where = {};
|
|
650
|
+
|
|
651
|
+
if (think.isEmpty(userInfo) || this.config('storage') === 'deta') {
|
|
652
|
+
where.status = ['NOT IN', ['waiting', 'spam']];
|
|
653
|
+
} else {
|
|
654
|
+
where._complex = {
|
|
655
|
+
_logic: 'or',
|
|
656
|
+
status: ['NOT IN', ['waiting', 'spam']],
|
|
657
|
+
user_id: userInfo.objectId,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const comments = await this.modelInstance.select(where, {
|
|
662
|
+
desc: 'insertedAt',
|
|
663
|
+
limit: count,
|
|
664
|
+
field: [
|
|
665
|
+
'status',
|
|
666
|
+
'comment',
|
|
667
|
+
'insertedAt',
|
|
668
|
+
'link',
|
|
669
|
+
'mail',
|
|
670
|
+
'nick',
|
|
671
|
+
'url',
|
|
672
|
+
'pid',
|
|
673
|
+
'rid',
|
|
674
|
+
'ua',
|
|
675
|
+
'ip',
|
|
676
|
+
'user_id',
|
|
677
|
+
'sticky',
|
|
678
|
+
'like',
|
|
679
|
+
],
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
const userModel = this.getModel('Users');
|
|
683
|
+
const user_ids = Array.from(
|
|
684
|
+
new Set(comments.map(({ user_id }) => user_id).filter((v) => v))
|
|
685
|
+
);
|
|
686
|
+
|
|
687
|
+
let users = [];
|
|
688
|
+
|
|
689
|
+
if (user_ids.length) {
|
|
690
|
+
users = await userModel.select(
|
|
691
|
+
{ objectId: ['IN', user_ids] },
|
|
692
|
+
{
|
|
693
|
+
field: ['display_name', 'email', 'url', 'type', 'avatar', 'label'],
|
|
694
|
+
}
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
return Promise.all(
|
|
699
|
+
comments.map((cmt) =>
|
|
700
|
+
formatCmt(
|
|
701
|
+
cmt,
|
|
702
|
+
users,
|
|
703
|
+
{ ...this.config(), deprecated: this.ctx.state.deprecated },
|
|
704
|
+
userInfo
|
|
705
|
+
)
|
|
706
|
+
)
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
async getCommentCount() {
|
|
711
|
+
const { url } = this.get();
|
|
712
|
+
const { userInfo } = this.ctx.state;
|
|
713
|
+
const where = Array.isArray(url) && url.length ? { url: ['IN', url] } : {};
|
|
714
|
+
|
|
715
|
+
if (think.isEmpty(userInfo) || this.config('storage') === 'deta') {
|
|
716
|
+
where.status = ['NOT IN', ['waiting', 'spam']];
|
|
717
|
+
} else {
|
|
718
|
+
where._complex = {
|
|
719
|
+
_logic: 'or',
|
|
720
|
+
status: ['NOT IN', ['waiting', 'spam']],
|
|
721
|
+
user_id: userInfo.objectId,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (Array.isArray(url) && (url.length > 1 || !this.ctx.state.deprecated)) {
|
|
726
|
+
const data = await this.modelInstance.select(where, {
|
|
727
|
+
field: ['url'],
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
return url.map((u) => data.filter(({ url }) => url === u).length);
|
|
731
|
+
}
|
|
732
|
+
const data = await this.modelInstance.count(where);
|
|
733
|
+
|
|
734
|
+
return data;
|
|
735
|
+
}
|
|
769
736
|
};
|
package/src/extend/think.js
CHANGED
|
@@ -101,4 +101,16 @@ module.exports = {
|
|
|
101
101
|
|
|
102
102
|
return ua;
|
|
103
103
|
},
|
|
104
|
+
getLevel(val) {
|
|
105
|
+
const levels = this.config('levels');
|
|
106
|
+
const defaultLevel = 0;
|
|
107
|
+
|
|
108
|
+
if (!val) {
|
|
109
|
+
return defaultLevel;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const level = think.findLastIndex(levels, (l) => l <= val);
|
|
113
|
+
|
|
114
|
+
return level === -1 ? defaultLevel : level;
|
|
115
|
+
},
|
|
104
116
|
};
|