@waline/vercel 1.21.0 → 1.23.0

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.21.0",
3
+ "version": "1.23.0",
4
4
  "description": "vercel server for waline comment system",
5
5
  "keywords": [
6
6
  "waline",
@@ -1,6 +1,5 @@
1
1
  const BaseRest = require('./rest');
2
2
 
3
- // title, time, url, xid
4
3
  module.exports = class extends BaseRest {
5
4
  constructor(ctx) {
6
5
  super(ctx);
@@ -11,7 +10,7 @@ module.exports = class extends BaseRest {
11
10
  }
12
11
 
13
12
  async getAction() {
14
- const { path } = this.get();
13
+ const { path, type } = this.get();
15
14
 
16
15
  // path is required
17
16
  if (!Array.isArray(path) || !path.length) {
@@ -21,42 +20,69 @@ module.exports = class extends BaseRest {
21
20
  const resp = await this.modelInstance.select({ url: ['IN', path] });
22
21
 
23
22
  if (think.isEmpty(resp)) {
24
- return this.json(0);
25
- }
23
+ const data = type.reduce((o, field) => {
24
+ o[field] = 0;
25
+
26
+ return o;
27
+ }, {});
26
28
 
27
- if (path.length === 1) {
28
- return this.json(resp[0].time);
29
+ return this.json(type.length === 1 ? data[type[0]] : data);
29
30
  }
30
31
 
31
32
  const respObj = resp.reduce((o, n) => {
32
- o[n.url] = n.time;
33
+ o[n.url] = n;
33
34
 
34
35
  return o;
35
36
  }, {});
36
37
 
37
- return this.json(path.map((url) => respObj[url] || 0));
38
+ const data = [];
39
+
40
+ for (let i = 0; i < path.length; i++) {
41
+ const url = path[i];
42
+ let counters = {};
43
+
44
+ for (let j = 0; j < type.length; j++) {
45
+ const field = type[j];
46
+
47
+ counters[field] =
48
+ respObj[url] && respObj[url][field] ? respObj[url][field] : 0;
49
+ }
50
+
51
+ if (type.length === 1) {
52
+ counters = counters[type[0]];
53
+ }
54
+ data.push(counters);
55
+ }
56
+
57
+ return this.json(path.length === 1 ? data[0] : data);
38
58
  }
39
59
 
40
60
  async postAction() {
41
- const { path } = this.post();
61
+ const { path, type, action } = this.post();
42
62
  const resp = await this.modelInstance.select({ url: path });
43
63
 
44
64
  if (think.isEmpty(resp)) {
45
- const time = 1;
65
+ if (action === 'desc') {
66
+ return this.json(0);
67
+ }
68
+
69
+ const count = 1;
46
70
 
47
71
  await this.modelInstance.add(
48
- { url: path, time },
72
+ { url: path, [type]: count },
49
73
  { access: { read: true, write: true } }
50
74
  );
51
75
 
52
- return this.json(time);
76
+ return this.json(count);
53
77
  }
54
78
 
55
79
  const ret = await this.modelInstance.update(
56
- (counter) => ({ time: counter.time + 1 }),
80
+ (counter) => ({
81
+ [type]: action === 'desc' ? counter[type] - 1 : counter[type] + 1,
82
+ }),
57
83
  { objectId: ['IN', resp.map(({ objectId }) => objectId)] }
58
84
  );
59
85
 
60
- return this.json(ret[0].time);
86
+ return this.json(ret[0][type]);
61
87
  }
62
88
  };
@@ -655,8 +655,10 @@ module.exports = class extends BaseRest {
655
655
  oldData = oldData[0];
656
656
  if (think.isBoolean(data.like)) {
657
657
  const likeIncMax = this.config('LIKE_INC_MAX') || 1;
658
- data.like = (Number(oldData.like) || 0) +
659
- (data.like ? Math.ceil(Math.random() * likeIncMax) : -1);
658
+
659
+ data.like =
660
+ (Number(oldData.like) || 0) +
661
+ (data.like ? Math.ceil(Math.random() * likeIncMax) : -1);
660
662
  }
661
663
 
662
664
  const preUpdateResp = await this.hook('preUpdate', {
@@ -673,6 +675,7 @@ module.exports = class extends BaseRest {
673
675
  });
674
676
 
675
677
  let cmtUser;
678
+
676
679
  if (!think.isEmpty(newData) && newData[0].user_id) {
677
680
  cmtUser = await this.service(
678
681
  `storage/${this.config('storage')}`,
@@ -688,13 +691,12 @@ module.exports = class extends BaseRest {
688
691
  this.config(),
689
692
  userInfo
690
693
  );
691
-
694
+
692
695
  if (
693
696
  oldData.status === 'waiting' &&
694
697
  data.status === 'approved' &&
695
698
  oldData.pid
696
699
  ) {
697
-
698
700
  let pComment = await this.modelInstance.select({
699
701
  objectId: oldData.pid,
700
702
  });
@@ -1,29 +1,5 @@
1
- const fs = require('fs');
2
- const util = require('util');
3
1
  const BaseRest = require('./rest');
4
2
 
5
- const readFileAsync = util.promisify(fs.readFile);
6
-
7
- function formatID(data, idGenerator) {
8
- const objectIdMap = {};
9
-
10
- for (let i = 0; i < data.length; i++) {
11
- const { objectId } = data[i];
12
-
13
- objectIdMap[objectId] = idGenerator(data[i], i, data);
14
- }
15
-
16
- for (let i = 0; i < data.length; i++) {
17
- ['objectId', 'pid', 'rid']
18
- .filter((k) => data[i][k])
19
- .forEach((k) => {
20
- data[i][k] = objectIdMap[data[i][k]];
21
- });
22
- }
23
-
24
- return data;
25
- }
26
-
27
3
  module.exports = class extends BaseRest {
28
4
  async getAction() {
29
5
  const exportData = {
@@ -52,95 +28,41 @@ module.exports = class extends BaseRest {
52
28
  }
53
29
 
54
30
  async postAction() {
55
- const file = this.file('file');
56
-
57
- try {
58
- const jsonText = await readFileAsync(file.path, 'utf-8');
59
- const importData = JSON.parse(jsonText);
60
-
61
- if (!importData || importData.type !== 'waline') {
62
- return this.fail(this.locale('import data format not support!'));
63
- }
64
-
65
- const idMaps = {};
66
- const storage = this.config('storage');
67
-
68
- for (let i = 0; i < importData.tables.length; i++) {
69
- const tableName = importData.tables[i];
70
- const model = this.service(`storage/${storage}`, tableName);
71
-
72
- idMaps[tableName] = new Map();
73
- let data = importData.data[tableName];
74
-
75
- if (['postgresql', 'mysql', 'sqlite'].includes(storage)) {
76
- let i = 0;
77
-
78
- data = formatID(data, () => (i = i + 1));
79
- await model.setSeqId(1);
80
- }
81
-
82
- if (storage === 'leancloud' || storage === 'mysql') {
83
- data.forEach((item) => {
84
- item.insertedAt && (item.insertedAt = new Date(item.insertedAt));
85
- item.createdAt && (item.createdAt = new Date(item.createdAt));
86
- item.updatedAt && (item.updatedAt = new Date(item.updatedAt));
87
- });
88
- }
89
-
90
- // delete all data at first
91
- await model.delete({});
92
- // then add data one by one
93
- for (let j = 0; j < data.length; j++) {
94
- const ret = await model.add(data[j]);
31
+ const { table } = this.get();
32
+ const item = this.post();
33
+ const storage = this.config('storage');
34
+ const model = this.service(`storage/${storage}`, table);
35
+
36
+ if (storage === 'leancloud' || storage === 'mysql') {
37
+ item.insertedAt && (item.insertedAt = new Date(item.insertedAt));
38
+ item.createdAt && (item.createdAt = new Date(item.createdAt));
39
+ item.updatedAt && (item.updatedAt = new Date(item.updatedAt));
40
+ }
95
41
 
96
- idMaps[tableName].set(data[j].objectId, ret.objectId);
97
- }
98
- }
42
+ delete item.objectId;
43
+ const resp = await model.add(item);
99
44
 
100
- const cmtModel = this.service(`storage/${storage}`, 'Comment');
101
- const commentData = importData.data.Comment;
102
- const willUpdateData = [];
45
+ return this.success(resp);
46
+ }
103
47
 
104
- for (let i = 0; i < commentData.length; i++) {
105
- const cmt = commentData[i];
106
- const willUpdateItem = {};
48
+ async putAction() {
49
+ const { table, objectId } = this.get();
50
+ const data = this.post();
51
+ const storage = this.config('storage');
52
+ const model = this.service(`storage/${storage}`, table);
107
53
 
108
- [
109
- { tableName: 'Comment', field: 'pid' },
110
- { tableName: 'Comment', field: 'rid' },
111
- { tableName: 'Users', field: 'user_id' },
112
- ].forEach(({ tableName, field }) => {
113
- if (!cmt[field]) {
114
- return;
115
- }
116
- const oldId = cmt[field];
117
- const newId = idMaps[tableName].get(cmt[field]);
54
+ await model.update(data, { objectId });
118
55
 
119
- if (oldId && newId && oldId !== newId) {
120
- willUpdateItem[field] = newId;
121
- }
122
- });
123
- if (!think.isEmpty(willUpdateItem)) {
124
- willUpdateData.push([
125
- willUpdateItem,
126
- { objectId: idMaps.Comment.get(cmt.objectId) },
127
- ]);
128
- }
129
- }
130
- for (let i = 0; i < willUpdateData.length; i++) {
131
- const [data, where] = willUpdateData[i];
56
+ return this.success();
57
+ }
132
58
 
133
- await cmtModel.update(data, where);
134
- }
59
+ async deleteAction() {
60
+ const { table } = this.get();
61
+ const storage = this.config('storage');
62
+ const model = this.service(`storage/${storage}`, table);
135
63
 
136
- return this.success();
137
- } catch (e) {
138
- if (think.isPrevent(e)) {
139
- return this.success();
140
- }
141
- console.log(e);
64
+ await model.delete({});
142
65
 
143
- return this.fail(e.message);
144
- }
66
+ return this.success();
145
67
  }
146
68
  };
@@ -26,7 +26,8 @@ module.exports = class extends think.Controller {
26
26
  el: '#waline',
27
27
  path: params.get('path') || '/',
28
28
  lang: params.get('lng'),
29
- serverURL: location.protocol + '//' + location.host + location.pathname.replace(/\\/+$/, '')
29
+ serverURL: location.protocol + '//' + location.host + location.pathname.replace(/\\/+$/, ''),
30
+ recaptchaV3Key: '${process.env.RECAPTCHA_V3_KEY || ''}',
30
31
  });
31
32
  </script>
32
33
  </body>
@@ -4,6 +4,24 @@ module.exports = class extends Base {
4
4
  getAction() {
5
5
  this.rules = {
6
6
  path: { array: true },
7
+ type: { array: true, default: ['time'] },
8
+ };
9
+ }
10
+
11
+ postAction() {
12
+ this.rules = {
13
+ path: {
14
+ string: true,
15
+ },
16
+ type: {
17
+ string: true,
18
+ default: 'time',
19
+ },
20
+ action: {
21
+ string: true,
22
+ in: ['inc', 'desc'],
23
+ default: 'inc',
24
+ }
7
25
  };
8
26
  }
9
27
  };
package/src/logic/base.js CHANGED
@@ -1,4 +1,6 @@
1
1
  const path = require('path');
2
+ const qs = require('querystring');
3
+ const fetch = require('node-fetch');
2
4
  const jwt = require('jsonwebtoken');
3
5
  const helper = require('think-helper');
4
6
 
@@ -122,4 +124,35 @@ module.exports = class extends think.Logic {
122
124
 
123
125
  return '';
124
126
  }
127
+
128
+ async useCaptchaCheck() {
129
+ const { RECAPTCHA_V3_SECRET } = process.env;
130
+
131
+ if (!RECAPTCHA_V3_SECRET) {
132
+ return;
133
+ }
134
+ const { recaptchaV3 } = this.post();
135
+
136
+ if (!recaptchaV3) {
137
+ return this.ctx.throw(403);
138
+ }
139
+
140
+ const query = qs.stringify({
141
+ secret: RECAPTCHA_V3_SECRET,
142
+ response: recaptchaV3,
143
+ remoteip: this.ctx.ip,
144
+ });
145
+ const recaptchaV3Result = await fetch(
146
+ `https://recaptcha.net/recaptcha/api/siteverify?${query}`
147
+ ).then((resp) => resp.json());
148
+
149
+ if (!recaptchaV3Result.success) {
150
+ think.logger.debug(
151
+ 'RecaptchaV3 Result:',
152
+ JSON.stringify(recaptchaV3Result, null, '\t')
153
+ );
154
+
155
+ return this.ctx.throw(403);
156
+ }
157
+ }
125
158
  };
@@ -107,6 +107,7 @@ module.exports = class extends Base {
107
107
  getAction() {
108
108
  const { type, path } = this.get();
109
109
  const isAllowedGet = type !== 'list' || path;
110
+
110
111
  if (!isAllowedGet) {
111
112
  this.checkAdmin();
112
113
  }
@@ -199,13 +200,19 @@ module.exports = class extends Base {
199
200
  * @apiSuccess (200) {String} data.avatar comment user avatar
200
201
  * @apiSuccess (200) {String} data.type comment login user type
201
202
  */
202
- postAction() {
203
+ async postAction() {
203
204
  const { LOGIN } = process.env;
204
205
  const { userInfo } = this.ctx.state;
205
206
 
206
- if (LOGIN === 'force' && think.isEmpty(userInfo)) {
207
+ if (!think.isEmpty(userInfo)) {
208
+ return;
209
+ }
210
+
211
+ if (LOGIN === 'force') {
207
212
  return this.ctx.throw(401);
208
213
  }
214
+
215
+ return this.useCaptchaCheck();
209
216
  }
210
217
 
211
218
  /**
@@ -235,6 +242,7 @@ module.exports = class extends Base {
235
242
  boolean: true,
236
243
  },
237
244
  };
245
+
238
246
  return;
239
247
  }
240
248
 
@@ -252,11 +260,15 @@ module.exports = class extends Base {
252
260
  `storage/${this.config('storage')}`,
253
261
  'Comment'
254
262
  );
255
- const commentData = await modelInstance.select({ user_id: userInfo.objectId, objectId: this.id });
263
+ const commentData = await modelInstance.select({
264
+ user_id: userInfo.objectId,
265
+ objectId: this.id,
266
+ });
267
+
256
268
  if (!think.isEmpty(commentData)) {
257
269
  return;
258
270
  }
259
-
271
+
260
272
  return this.ctx.throw(403);
261
273
  }
262
274
 
@@ -283,10 +295,15 @@ module.exports = class extends Base {
283
295
  `storage/${this.config('storage')}`,
284
296
  'Comment'
285
297
  );
286
- const commentData = await modelInstance.select({ user_id: userInfo.objectId, objectId: this.id });
298
+ const commentData = await modelInstance.select({
299
+ user_id: userInfo.objectId,
300
+ objectId: this.id,
301
+ });
302
+
287
303
  if (!think.isEmpty(commentData)) {
288
304
  return;
289
305
  }
306
+
290
307
  return this.ctx.throw(403);
291
308
  }
292
309
  };
package/src/logic/db.js CHANGED
@@ -23,9 +23,51 @@ module.exports = class extends Base {
23
23
  async getAction() {}
24
24
 
25
25
  /**
26
- * @api {GET} /db import site data
26
+ * @api {POST} /db import site data
27
27
  * @apiGroup Site
28
28
  * @apiVersion 0.0.1
29
29
  */
30
- async postAction() {}
30
+ async postAction() {
31
+ this.rules = {
32
+ table: {
33
+ string: true,
34
+ required: true,
35
+ method: 'GET',
36
+ },
37
+ };
38
+ }
39
+
40
+ /**
41
+ * @api {PUT} /db update site table data
42
+ * @apiGroup Site
43
+ * @apiVersion 0.0.1
44
+ */
45
+ async putAction() {
46
+ this.rules = {
47
+ table: {
48
+ string: true,
49
+ required: true,
50
+ method: 'GET',
51
+ },
52
+ objectId: {
53
+ required: true,
54
+ method: 'GET',
55
+ },
56
+ };
57
+ }
58
+
59
+ /**
60
+ * @api {DELETE} /db clean site data
61
+ * @apiGroup Site
62
+ * @apiVersion 0.0.1
63
+ */
64
+ async deleteAction() {
65
+ this.rules = {
66
+ table: {
67
+ string: true,
68
+ required: true,
69
+ method: 'GET',
70
+ },
71
+ };
72
+ }
31
73
  };
@@ -32,7 +32,9 @@ module.exports = class extends Base {
32
32
  * @apiSuccess (200) {Number} errno 0
33
33
  * @apiSuccess (200) {String} errmsg return error message if error
34
34
  */
35
- postAction() {}
35
+ postAction() {
36
+ return this.useCaptchaCheck();
37
+ }
36
38
 
37
39
  /**
38
40
  * @api {DELETE} /token user logout
package/src/logic/user.js CHANGED
@@ -33,7 +33,9 @@ module.exports = class extends Base {
33
33
  * @apiSuccess (200) {Number} errno 0
34
34
  * @apiSuccess (200) {String} errmsg return error message if error
35
35
  */
36
- postAction() {}
36
+ postAction() {
37
+ return this.useCaptchaCheck();
38
+ }
37
39
 
38
40
  /**
39
41
  * @api {PUT} /user update user profile
@@ -12,6 +12,7 @@ module.exports = function () {
12
12
  <script>
13
13
  window.SITE_URL = ${JSON.stringify(process.env.SITE_URL)};
14
14
  window.SITE_NAME = ${JSON.stringify(process.env.SITE_NAME)};
15
+ window.recaptchaV3Key = ${JSON.stringify(process.env.RECAPTCHA_V3_KEY)};
15
16
  </script>
16
17
  <script src="${
17
18
  process.env.WALINE_ADMIN_MODULE_ASSET_URL || '//unpkg.com/@waline/admin'
@@ -69,7 +69,7 @@ module.exports = class extends Base {
69
69
 
70
70
  instance.field([...group, 'COUNT(*) as count'].join(','));
71
71
  instance.group(group);
72
-
72
+
73
73
  return instance.select();
74
74
  }
75
75
 
@@ -79,10 +79,9 @@ module.exports = class extends Base {
79
79
  delete data.objectId;
80
80
  }
81
81
  const date = new Date();
82
- if (!data.createdAt)
83
- data.createdAt = date;
84
- if (!data.updatedAt)
85
- data.updatedAt = date;
82
+
83
+ if (!data.createdAt) data.createdAt = date;
84
+ if (!data.updatedAt) data.updatedAt = date;
86
85
 
87
86
  const instance = this.model(this.tableName);
88
87
  const id = await instance.add(data);