@waline/vercel 1.36.4 → 1.37.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.
Files changed (39) hide show
  1. package/__tests__/xss.spec.js +8 -19
  2. package/development.js +1 -0
  3. package/index.js +4 -3
  4. package/package.json +17 -18
  5. package/src/config/adapter.js +9 -11
  6. package/src/config/config.js +4 -12
  7. package/src/config/extend.js +0 -6
  8. package/src/config/middleware.js +2 -5
  9. package/src/controller/article.js +5 -12
  10. package/src/controller/comment.js +33 -53
  11. package/src/controller/db.js +3 -9
  12. package/src/controller/oauth.js +8 -8
  13. package/src/controller/rest.js +1 -1
  14. package/src/controller/user/password.js +3 -12
  15. package/src/controller/user.js +18 -45
  16. package/src/controller/verification.js +3 -2
  17. package/src/extend/think.js +33 -23
  18. package/src/logic/base.js +31 -47
  19. package/src/logic/comment.js +7 -4
  20. package/src/logic/db.js +1 -1
  21. package/src/logic/token.js +2 -2
  22. package/src/middleware/dashboard.js +1 -0
  23. package/src/middleware/plugin.js +1 -1
  24. package/src/service/akismet.js +10 -3
  25. package/src/service/avatar.js +2 -4
  26. package/src/service/markdown/highlight.js +1 -1
  27. package/src/service/markdown/mathCommon.js +2 -8
  28. package/src/service/markdown/mathjax.js +1 -2
  29. package/src/service/markdown/utils.js +5 -5
  30. package/src/service/markdown/xss.js +5 -10
  31. package/src/service/notify.js +111 -135
  32. package/src/service/storage/base.js +2 -7
  33. package/src/service/storage/cloudbase.js +9 -7
  34. package/src/service/storage/github.js +39 -42
  35. package/src/service/storage/leancloud.js +41 -54
  36. package/src/service/storage/mongodb.js +11 -8
  37. package/src/service/storage/mysql.js +7 -13
  38. package/src/service/storage/postgresql.js +7 -11
  39. package/src/service/storage/deta.js +0 -310
@@ -53,8 +53,7 @@ class Github {
53
53
  // content api can only get file < 1MB
54
54
  async get(filename) {
55
55
  const resp = await fetch(
56
- 'https://api.github.com/repos/' +
57
- path.join(this.repo, 'contents', filename),
56
+ 'https://api.github.com/repos/' + path.join(this.repo, 'contents', filename),
58
57
  {
59
58
  headers: {
60
59
  accept: 'application/vnd.github.v3+json',
@@ -64,11 +63,11 @@ class Github {
64
63
  },
65
64
  )
66
65
  .then((resp) => resp.json())
67
- .catch((e) => {
68
- const isTooLarge = e.message.includes('"too_large"');
66
+ .catch((err) => {
67
+ const isTooLarge = err.message.includes('"too_large"');
69
68
 
70
69
  if (!isTooLarge) {
71
- throw e;
70
+ throw err;
72
71
  }
73
72
 
74
73
  return this.getLargeFile(filename);
@@ -83,9 +82,7 @@ class Github {
83
82
  // blob api can get file larger than 1MB
84
83
  async getLargeFile(filename) {
85
84
  const { tree } = await fetch(
86
- 'https://api.github.com/repos/' +
87
- path.join(this.repo, 'git/trees/HEAD') +
88
- '?recursive=1',
85
+ 'https://api.github.com/repos/' + path.join(this.repo, 'git/trees/HEAD') + '?recursive=1',
89
86
  {
90
87
  headers: {
91
88
  accept: 'application/vnd.github.v3+json',
@@ -114,23 +111,19 @@ class Github {
114
111
  }
115
112
 
116
113
  async set(filename, content, { sha }) {
117
- return fetch(
118
- 'https://api.github.com/repos/' +
119
- path.join(this.repo, 'contents', filename),
120
- {
121
- method: 'PUT',
122
- headers: {
123
- accept: 'application/vnd.github.v3+json',
124
- authorization: 'token ' + this.token,
125
- 'user-agent': 'Waline',
126
- },
127
- body: JSON.stringify({
128
- sha,
129
- message: 'feat(waline): update comment data',
130
- content: Buffer.from(content, 'utf-8').toString('base64'),
131
- }),
114
+ return fetch('https://api.github.com/repos/' + path.join(this.repo, 'contents', filename), {
115
+ method: 'PUT',
116
+ headers: {
117
+ accept: 'application/vnd.github.v3+json',
118
+ authorization: 'token ' + this.token,
119
+ 'user-agent': 'Waline',
132
120
  },
133
- );
121
+ body: JSON.stringify({
122
+ sha,
123
+ message: 'feat(waline): update comment data',
124
+ content: Buffer.from(content, 'utf-8').toString('base64'),
125
+ }),
126
+ });
134
127
  }
135
128
  }
136
129
 
@@ -147,11 +140,11 @@ module.exports = class extends Base {
147
140
 
148
141
  async collection(tableName) {
149
142
  const filename = path.join(this.basePath, tableName + '.csv');
150
- const file = await this.git.get(filename).catch((e) => {
151
- if (e.statusCode === 404) {
143
+ const file = await this.git.get(filename).catch((err) => {
144
+ if (err.statusCode === 404) {
152
145
  return '';
153
146
  }
154
- throw e;
147
+ throw err;
155
148
  });
156
149
 
157
150
  return new Promise((resolve, reject) => {
@@ -164,7 +157,9 @@ module.exports = class extends Base {
164
157
  })
165
158
  .on('error', reject)
166
159
  .on('data', (row) => data.push(row))
167
- .on('end', () => resolve(data));
160
+ .on('end', () => {
161
+ resolve(data);
162
+ });
168
163
  });
169
164
  }
170
165
 
@@ -187,7 +182,7 @@ module.exports = class extends Base {
187
182
 
188
183
  const filters = [];
189
184
 
190
- for (let k in where) {
185
+ for (const k in where) {
191
186
  if (k === '_complex') {
192
187
  continue;
193
188
  }
@@ -210,12 +205,14 @@ module.exports = class extends Base {
210
205
  const handler = where[k][0].toUpperCase();
211
206
 
212
207
  switch (handler) {
213
- case 'IN':
208
+ case 'IN': {
214
209
  filters.push((item) => where[k][1].includes(item[k]));
215
210
  break;
216
- case 'NOT IN':
211
+ }
212
+ case 'NOT IN': {
217
213
  filters.push((item) => !where[k][1].includes(item[k]));
218
214
  break;
215
+ }
219
216
  case 'LIKE': {
220
217
  const first = where[k][1][0];
221
218
  const last = where[k][1].slice(-1);
@@ -231,12 +228,14 @@ module.exports = class extends Base {
231
228
  filters.push((item) => reg.test(item[k]));
232
229
  break;
233
230
  }
234
- case '!=':
231
+ case '!=': {
235
232
  filters.push((item) => item[k] !== where[k][1]);
236
233
  break;
237
- case '>':
234
+ }
235
+ case '>': {
238
236
  filters.push((item) => item[k] >= where[k][1]);
239
237
  break;
238
+ }
240
239
  }
241
240
  }
242
241
 
@@ -266,9 +265,7 @@ module.exports = class extends Base {
266
265
 
267
266
  const logicFn = logicMap[where._complex._logic];
268
267
 
269
- return data.filter((item) =>
270
- logicFn.call(filters, (filter) => filter.every((fn) => fn(item))),
271
- );
268
+ return data.filter((item) => logicFn.call(filters, (filter) => filter.every((fn) => fn(item))));
272
269
  }
273
270
 
274
271
  async select(where, { desc, limit, offset, field } = {}) {
@@ -288,7 +285,7 @@ module.exports = class extends Base {
288
285
  });
289
286
  }
290
287
 
291
- data = data.slice(limit || 0, offset || data.length);
288
+ data = data.slice(limit ?? 0, offset ?? data.length);
292
289
  if (field) {
293
290
  field.push('id');
294
291
  const fieldObj = {};
@@ -321,9 +318,9 @@ module.exports = class extends Base {
321
318
  const counts = {};
322
319
 
323
320
  // FIXME: The loop is weird @lizheming
324
- // eslint-disable-next-line @typescript-eslint/prefer-for-of
321
+ // oxlint-disable-next-line typescript/prefer-for-of
325
322
  for (let i = 0; i < data.length; i++) {
326
- const key = group.map((field) => data[field]).join();
323
+ const key = group.map((field) => data[field]).join(',');
327
324
 
328
325
  if (!counts[key]) {
329
326
  counts[key] = { count: 0 };
@@ -342,7 +339,7 @@ module.exports = class extends Base {
342
339
  // { access: { read = true, write = true } = { read: true, write: true } } = {}
343
340
  ) {
344
341
  const instance = await this.collection(this.tableName);
345
- const id = Math.random().toString(36).substr(2, 15);
342
+ const id = Math.random().toString(36).slice(2, 15);
346
343
 
347
344
  instance.push({ ...data, id });
348
345
  await this.save(this.tableName, instance, instance.sha);
@@ -373,8 +370,8 @@ module.exports = class extends Base {
373
370
  async delete(where) {
374
371
  const instance = await this.collection(this.tableName);
375
372
  const deleteData = this.where(instance, where);
376
- const deleteId = deleteData.map(({ id }) => id);
377
- const data = instance.filter((data) => !deleteId.includes(data.id));
373
+ const deleteId = new Set(deleteData.map(({ id }) => id));
374
+ const data = instance.filter((data) => !deleteId.has(data.id));
378
375
 
379
376
  await this.save(this.tableName, data, instance.sha);
380
377
  }
@@ -41,12 +41,14 @@ module.exports = class extends Base {
41
41
  const handler = where[k][0].toUpperCase();
42
42
 
43
43
  switch (handler) {
44
- case 'IN':
44
+ case 'IN': {
45
45
  instance.containedIn(k, where[k][1]);
46
46
  break;
47
- case 'NOT IN':
47
+ }
48
+ case 'NOT IN': {
48
49
  instance.notContainedIn(k, where[k][1]);
49
50
  break;
51
+ }
50
52
  case 'LIKE': {
51
53
  const first = where[k][1][0];
52
54
  const last = where[k][1].slice(-1);
@@ -60,12 +62,14 @@ module.exports = class extends Base {
60
62
  }
61
63
  break;
62
64
  }
63
- case '!=':
65
+ case '!=': {
64
66
  instance.notEqualTo(k, where[k][1]);
65
67
  break;
66
- case '>':
68
+ }
69
+ case '>': {
67
70
  instance.greaterThan(k, where[k][1]);
68
71
  break;
72
+ }
69
73
  }
70
74
  }
71
75
  }
@@ -113,11 +117,11 @@ module.exports = class extends Base {
113
117
  instance.select(field);
114
118
  }
115
119
 
116
- const data = await instance.find().catch((e) => {
117
- if (e.code === 101) {
120
+ const data = await instance.find().catch((err) => {
121
+ if (err.code === 101) {
118
122
  return [];
119
123
  }
120
- throw e;
124
+ throw err;
121
125
  });
122
126
 
123
127
  return data.map((item) => item.toJSON());
@@ -126,7 +130,7 @@ module.exports = class extends Base {
126
130
  async select(where, options = {}) {
127
131
  let data = [];
128
132
  let ret = [];
129
- let offset = options.offset || 0;
133
+ const offset = options.offset ?? 0;
130
134
 
131
135
  do {
132
136
  options.offset = offset + data.length;
@@ -185,10 +189,7 @@ module.exports = class extends Base {
185
189
  }
186
190
 
187
191
  async _updateCmtGroupByMailUserIdCache(data, method) {
188
- if (
189
- this.tableName !== 'Comment' ||
190
- !think.isArray(think.config('levels'))
191
- ) {
192
+ if (this.tableName !== 'Comment' || !think.isArray(think.config('levels'))) {
192
193
  return;
193
194
  }
194
195
 
@@ -200,9 +201,7 @@ module.exports = class extends Base {
200
201
  const cacheData = await this.select({
201
202
  _complex: {
202
203
  _logic: 'or',
203
- user_id: think.isObject(data.user_id)
204
- ? data.user_id.toString()
205
- : data.user_id,
204
+ user_id: think.isObject(data.user_id) ? data.user_id.toString() : data.user_id,
206
205
  mail: data.mail,
207
206
  },
208
207
  });
@@ -211,37 +210,38 @@ module.exports = class extends Base {
211
210
  return;
212
211
  }
213
212
 
214
- let count = cacheData[0].count;
213
+ let { count } = cacheData[0];
215
214
 
216
215
  switch (method) {
217
- case 'add':
216
+ case 'add': {
218
217
  if (data.status === 'approved') {
219
218
  count += 1;
220
219
  }
221
220
  break;
222
- case 'udpate_status':
221
+ }
222
+ case 'udpate_status': {
223
223
  if (data.status === 'approved') {
224
224
  count += 1;
225
225
  } else {
226
226
  count -= 1;
227
227
  }
228
228
  break;
229
- case 'delete':
229
+ }
230
+ case 'delete': {
230
231
  count -= 1;
231
232
  break;
233
+ }
232
234
  }
233
235
 
234
236
  const currentTableName = this.tableName;
235
237
 
236
238
  this.tableName = cacheTableName;
237
- await this.update({ count }, { objectId: cacheData[0].objectId }).catch(
238
- (e) => {
239
- if (e.code === 101) {
240
- return;
241
- }
242
- throw e;
243
- },
244
- );
239
+ await this.update({ count }, { objectId: cacheData[0].objectId }).catch((err) => {
240
+ if (err.code === 101) {
241
+ return;
242
+ }
243
+ throw err;
244
+ });
245
245
  this.tableName = currentTableName;
246
246
  }
247
247
 
@@ -249,22 +249,19 @@ module.exports = class extends Base {
249
249
  const instance = this.where(this.tableName, where);
250
250
 
251
251
  if (!options.group) {
252
- return instance.count(options).catch((e) => {
253
- if (e.code === 101) {
252
+ return instance.count(options).catch((err) => {
253
+ if (err.code === 101) {
254
254
  return 0;
255
255
  }
256
- throw e;
256
+ throw err;
257
257
  });
258
258
  }
259
259
 
260
260
  // get group count cache by group field where data
261
- const cacheData = await this._getCmtGroupByMailUserIdCache(
262
- options.group.join('_'),
263
- where,
264
- );
261
+ const cacheData = await this._getCmtGroupByMailUserIdCache(options.group.join('_'), where);
265
262
 
266
263
  if (!where._complex) {
267
- if (cacheData.length) {
264
+ if (cacheData.length > 0) {
268
265
  return cacheData;
269
266
  }
270
267
 
@@ -272,9 +269,7 @@ module.exports = class extends Base {
272
269
  const countsMap = {};
273
270
 
274
271
  for (const count of counts) {
275
- const key = options.group
276
- .map((item) => count[item] || undefined)
277
- .join('_');
272
+ const key = options.group.map((item) => count[item] ?? undefined).join('_');
278
273
 
279
274
  if (!countsMap[key]) {
280
275
  countsMap[key] = {};
@@ -298,9 +293,7 @@ module.exports = class extends Base {
298
293
  const cacheDataMap = {};
299
294
 
300
295
  for (const item of cacheData) {
301
- const key = options.group
302
- .map((item) => item[item] || undefined)
303
- .join('_');
296
+ const key = options.group.map((item) => item[item] ?? undefined).join('_');
304
297
 
305
298
  cacheDataMap[key] = item;
306
299
  }
@@ -328,7 +321,7 @@ module.exports = class extends Base {
328
321
  ({
329
322
  ...groupFlatValue,
330
323
  [groupName]: item,
331
- })[item] || undefined,
324
+ })[item] ?? undefined,
332
325
  )
333
326
  .join('_');
334
327
 
@@ -364,19 +357,14 @@ module.exports = class extends Base {
364
357
  return [...cacheData, ...counts];
365
358
  }
366
359
 
367
- async add(
368
- data,
369
- {
370
- access: { read = true, write = true } = { read: true, write: true },
371
- } = {},
372
- ) {
360
+ async add(data, { access: { read = true, write = true } = { read: true, write: true } } = {}) {
373
361
  const Table = AV.Object.extend(this.tableName);
374
362
  const instance = new Table();
375
363
 
376
- const REVERSED_KEYS = ['objectId', 'createdAt', 'updatedAt'];
364
+ const REVERSED_KEYS = new Set(['objectId', 'createdAt', 'updatedAt']);
377
365
 
378
366
  for (const k in data) {
379
- if (REVERSED_KEYS.includes(k)) {
367
+ if (REVERSED_KEYS.has(k)) {
380
368
  continue;
381
369
  }
382
370
  instance.set(k, data[k]);
@@ -403,13 +391,12 @@ module.exports = class extends Base {
403
391
  ret.map(async (item) => {
404
392
  const _oldStatus = item.get('status');
405
393
 
406
- const updateData =
407
- typeof data === 'function' ? data(item.toJSON()) : data;
394
+ const updateData = typeof data === 'function' ? data(item.toJSON()) : data;
408
395
 
409
- const REVERSED_KEYS = ['createdAt', 'updatedAt'];
396
+ const REVERSED_KEYS = new Set(['createdAt', 'updatedAt']);
410
397
 
411
398
  for (const k in updateData) {
412
- if (REVERSED_KEYS.includes(k)) {
399
+ if (REVERSED_KEYS.has(k)) {
413
400
  continue;
414
401
  }
415
402
  item.set(k, updateData[k]);
@@ -11,7 +11,7 @@ module.exports = class extends Base {
11
11
  const filter = {};
12
12
  const parseKey = (k) => (k === 'objectId' ? '_id' : k);
13
13
 
14
- for (let k in where) {
14
+ for (const k in where) {
15
15
  if (k === '_complex') {
16
16
  continue;
17
17
  }
@@ -29,7 +29,7 @@ module.exports = class extends Base {
29
29
  const handler = where[k][0].toUpperCase();
30
30
 
31
31
  switch (handler) {
32
- case 'IN':
32
+ case 'IN': {
33
33
  if (k === 'objectId') {
34
34
  filter[parseKey(k)] = { $in: where[k][1].map(ObjectId) };
35
35
  } else {
@@ -38,12 +38,13 @@ module.exports = class extends Base {
38
38
  };
39
39
  }
40
40
  break;
41
- case 'NOT IN':
41
+ }
42
+ case 'NOT IN': {
42
43
  filter[parseKey(k)] = {
43
- $nin:
44
- k === 'objectId' ? where[k][1].map(ObjectId) : where[k][1],
44
+ $nin: k === 'objectId' ? where[k][1].map(ObjectId) : where[k][1],
45
45
  };
46
46
  break;
47
+ }
47
48
  case 'LIKE': {
48
49
  const first = where[k][1][0];
49
50
  const last = where[k][1].slice(-1);
@@ -62,12 +63,14 @@ module.exports = class extends Base {
62
63
  }
63
64
  break;
64
65
  }
65
- case '!=':
66
+ case '!=': {
66
67
  filter[parseKey(k)] = { $ne: where[k][1] };
67
68
  break;
68
- case '>':
69
+ }
70
+ case '>': {
69
71
  filter[parseKey(k)] = { $gt: where[k][1] };
70
72
  break;
73
+ }
71
74
  }
72
75
  }
73
76
  }
@@ -109,7 +112,7 @@ module.exports = class extends Base {
109
112
  instance.order(`${desc} DESC`);
110
113
  }
111
114
  if (limit || offset) {
112
- instance.limit(offset || 0, limit);
115
+ instance.limit(offset ?? 0, limit);
113
116
  }
114
117
  if (field) {
115
118
  instance.field(field);
@@ -25,7 +25,7 @@ module.exports = class extends Base {
25
25
  }
26
26
 
27
27
  if (Array.isArray(filter[k])) {
28
- if (filter[k][0] === 'IN' && !filter[k][1].length) {
28
+ if (filter[k][0] === 'IN' && filter[k][1].length === 0) {
29
29
  continue;
30
30
  }
31
31
  if (think.isDate(filter[k][1])) {
@@ -47,7 +47,7 @@ module.exports = class extends Base {
47
47
  instance.order({ [desc]: 'DESC' });
48
48
  }
49
49
  if (limit || offset) {
50
- instance.limit(offset || 0, limit);
50
+ instance.limit(offset ?? 0, limit);
51
51
  }
52
52
  if (field) {
53
53
  field.push('id');
@@ -80,8 +80,8 @@ module.exports = class extends Base {
80
80
  }
81
81
  const date = new Date();
82
82
 
83
- if (!data.createdAt) data.createdAt = date;
84
- if (!data.updatedAt) data.updatedAt = date;
83
+ data.createdAt ??= date;
84
+ data.updatedAt ??= date;
85
85
 
86
86
  const instance = this.model(this.tableName);
87
87
  const id = await instance.add(data);
@@ -90,17 +90,13 @@ module.exports = class extends Base {
90
90
  }
91
91
 
92
92
  async update(data, where) {
93
- const list = await this.model(this.tableName)
94
- .where(this.parseWhere(where))
95
- .select();
93
+ const list = await this.model(this.tableName).where(this.parseWhere(where)).select();
96
94
 
97
95
  return Promise.all(
98
96
  list.map(async (item) => {
99
97
  const updateData = typeof data === 'function' ? data(item) : data;
100
98
 
101
- await this.model(this.tableName)
102
- .where({ id: item.id })
103
- .update(updateData);
99
+ await this.model(this.tableName).where({ id: item.id }).update(updateData);
104
100
 
105
101
  return { ...item, ...updateData };
106
102
  }),
@@ -116,8 +112,6 @@ module.exports = class extends Base {
116
112
  async setSeqId(id) {
117
113
  const instance = this.model(this.tableName);
118
114
 
119
- return instance.query(
120
- `ALTER TABLE ${instance.tableName} AUTO_INCREMENT = ${id};`,
121
- );
115
+ return instance.query(`ALTER TABLE ${instance.tableName} AUTO_INCREMENT = ${id};`);
122
116
  }
123
117
  };
@@ -48,10 +48,8 @@ module.exports = class extends MySQL {
48
48
  const val = data[key];
49
49
 
50
50
  data[key.toLowerCase()] =
51
- val instanceof Date
52
- ? think.datetime(val, 'YYYY-MM-DD HH:mm:ss')
53
- : val;
54
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
51
+ val instanceof Date ? think.datetime(val, 'YYYY-MM-DD HH:mm:ss') : val;
52
+ // oxlint-disable-next-line typescript/no-dynamic-delete
55
53
  delete data[key];
56
54
  });
57
55
 
@@ -64,13 +62,13 @@ module.exports = class extends MySQL {
64
62
  try {
65
63
  if (Array.isArray(result)) {
66
64
  result.forEach((r) => {
67
- r.count = parseInt(r.count);
65
+ r.count = Number.parseInt(r.count);
68
66
  });
69
67
  } else {
70
- result = parseInt(result);
68
+ result = Number.parseInt(result);
71
69
  }
72
- } catch (e) {
73
- console.log(e);
70
+ } catch (err) {
71
+ console.log(err);
74
72
  }
75
73
 
76
74
  return result;
@@ -79,8 +77,6 @@ module.exports = class extends MySQL {
79
77
  async setSeqId(id) {
80
78
  const instance = this.model(this.tableName);
81
79
 
82
- return instance.query(
83
- `ALTER SEQUENCE ${instance.tableName}_seq RESTART WITH ${id};`,
84
- );
80
+ return instance.query(`ALTER SEQUENCE ${instance.tableName}_seq RESTART WITH ${id};`);
85
81
  }
86
82
  };