@waline/vercel 1.30.4 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waline/vercel",
3
- "version": "1.30.4",
3
+ "version": "1.30.5",
4
4
  "description": "vercel server for waline comment system",
5
5
  "keywords": [
6
6
  "waline",
@@ -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
- if (user_ids.length) {
396
- countWhere._complex.user_id = ['IN', user_ids];
397
- }
398
- const mails = Array.from(
399
- new Set(comments.map(({ mail }) => mail).filter((v) => v))
400
- );
84
+ const fnMap = {
85
+ recent: this.getRecentCommentList,
86
+ count: this.getCommentCount,
87
+ list: this.getAdminCommentList,
88
+ };
401
89
 
402
- if (mails.length) {
403
- countWhere._complex.mail = ['IN', mails];
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
- return this[this.ctx.state.deprecated ? 'json' : 'success']({
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() {
@@ -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
  };
@@ -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
  };