@waline/vercel 1.6.1 → 1.8.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
- ## @waline/vercel
1
+ # @waline/vercel
2
2
 
3
- ![](https://img.shields.io/npm/v/@waline/vercel?color=blue&logo=npm&style=flat-square)
3
+ ![Version](https://img.shields.io/npm/v/@waline/vercel?color=blue&logo=npm&style=flat-square)
4
4
 
5
5
  This is the backend for Waline comment system.
6
6
 
@@ -20,6 +20,6 @@ We support [Akismet](https://akismet.com/) spam protection service default. If y
20
20
 
21
21
  ## Deploy
22
22
 
23
- [ ![](https://vercel.com/button) ](https://vercel.com/import/project?template=https://github.com/walinejs/waline/tree/main/example)
23
+ [![Deploy button](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/walinejs/waline/tree/main/example)
24
24
 
25
25
  Click it to deploy quickly!
package/package.json CHANGED
@@ -1,41 +1,41 @@
1
1
  {
2
2
  "name": "@waline/vercel",
3
- "version": "1.6.1",
3
+ "version": "1.8.1",
4
4
  "description": "vercel server for waline comment system",
5
5
  "repository": "https://github.com/walinejs/waline",
6
6
  "license": "MIT",
7
7
  "author": "lizheming <i@imnerd.org>",
8
8
  "dependencies": {
9
9
  "@byteinspire/api": "^1.0.12",
10
- "@cloudbase/node-sdk": "^2.7.1",
11
- "@koa/cors": "^3.1.0",
10
+ "@cloudbase/node-sdk": "^2.9.0",
11
+ "@koa/cors": "^3.2.0",
12
12
  "akismet": "^2.0.6",
13
- "deta": "^1.0.1",
14
- "dompurify": "^2.3.3",
13
+ "deta": "^1.1.0",
14
+ "dompurify": "^2.3.6",
15
15
  "fast-csv": "^4.3.6",
16
- "jsdom": "^18.0.0",
16
+ "jsdom": "^19.0.0",
17
17
  "jsonwebtoken": "^8.5.1",
18
- "katex": "^0.13.23",
19
- "leancloud-storage": "^4.12.0",
20
- "markdown-it": "^12.2.0",
18
+ "katex": "^0.15.3",
19
+ "leancloud-storage": "^4.12.2",
20
+ "markdown-it": "^12.3.2",
21
21
  "markdown-it-emoji": "^2.0.0",
22
22
  "markdown-it-sub": "^1.0.0",
23
23
  "markdown-it-sup": "^1.0.0",
24
24
  "mathjax-full": "^3.2.0",
25
- "nodemailer": "^6.7.0",
25
+ "nodemailer": "^6.7.3",
26
26
  "nunjucks": "^3.2.3",
27
27
  "phpass": "^0.1.1",
28
- "prismjs": "^1.25.0",
28
+ "prismjs": "^1.27.0",
29
29
  "request": "^2.88.2",
30
30
  "request-promise-native": "^1.0.9",
31
- "think-logger3": "^1.2.1",
31
+ "think-logger3": "^1.3.1",
32
32
  "think-model": "^1.5.4",
33
- "think-model-mysql": "^1.1.6",
33
+ "think-model-mysql": "^1.1.7",
34
34
  "think-model-postgresql": "^1.1.6",
35
35
  "think-model-sqlite": "^1.2.2",
36
36
  "think-mongo": "^2.1.2",
37
37
  "think-router-rest": "^1.0.5",
38
38
  "thinkjs": "^3.2.14",
39
- "ua-parser-js": "^0.7.31"
39
+ "ua-parser-js": "^1.0.2"
40
40
  }
41
41
  }
@@ -37,10 +37,9 @@ const {
37
37
  } = process.env;
38
38
 
39
39
  let type = 'common';
40
- let mongoOpt = {
41
- replicaSet: MONGO_REPLICASET,
42
- authSource: MONGO_AUTHSOURCE,
43
- };
40
+ const mongoOpt = {};
41
+ if (MONGO_REPLICASET) mongoOpt.replicaSet = MONGO_REPLICASET;
42
+ if (MONGO_AUTHSOURCE) mongoOpt.authSource = MONGO_AUTHSOURCE;
44
43
 
45
44
  if (MONGO_DB) {
46
45
  type = 'mongo';
@@ -1,4 +1,3 @@
1
- const helper = require('think-helper');
2
1
  const parser = require('ua-parser-js');
3
2
  const BaseRest = require('./rest');
4
3
  const akismet = require('../service/akismet');
@@ -8,7 +7,8 @@ const markdownParser = getMarkdownParser();
8
7
  async function formatCmt(
9
8
  { ua, user_id, ...comment },
10
9
  users = [],
11
- { avatarProxy }
10
+ { avatarProxy },
11
+ loginUser
12
12
  ) {
13
13
  ua = parser(ua);
14
14
  if (!think.config('disableUserAgent')) {
@@ -36,9 +36,12 @@ async function formatCmt(
36
36
  ? avatarProxy + '?url=' + encodeURIComponent(avatarUrl)
37
37
  : avatarUrl;
38
38
 
39
- comment.mail = helper.md5(
40
- comment.mail ? comment.mail.toLowerCase() : comment.mail
41
- );
39
+ const isAdmin = loginUser && loginUser.type === 'administrator';
40
+ if (!isAdmin) {
41
+ delete comment.mail;
42
+ } else {
43
+ comment.orig = comment.comment;
44
+ }
42
45
 
43
46
  comment.comment = markdownParser(comment.comment);
44
47
  return comment;
@@ -55,30 +58,40 @@ module.exports = class extends BaseRest {
55
58
 
56
59
  async getAction() {
57
60
  const { type } = this.get();
61
+ const { userInfo } = this.ctx.state;
58
62
 
59
63
  switch (type) {
60
64
  case 'recent': {
61
65
  const { count } = this.get();
62
- const comments = await this.modelInstance.select(
63
- { status: ['NOT IN', ['waiting', 'spam']] },
64
- {
65
- desc: 'insertedAt',
66
- limit: count,
67
- field: [
68
- 'comment',
69
- 'insertedAt',
70
- 'link',
71
- 'mail',
72
- 'nick',
73
- 'url',
74
- 'pid',
75
- 'rid',
76
- 'ua',
77
- 'user_id',
78
- 'sticky',
79
- ],
80
- }
81
- );
66
+ const where = {};
67
+ if (think.isEmpty(userInfo) || this.config('storage') === 'deta') {
68
+ where.status = ['NOT IN', ['waiting', 'spam']];
69
+ } else {
70
+ where._complex = {
71
+ _logic: 'or',
72
+ status: ['NOT IN', ['waiting', 'spam']],
73
+ user_id: userInfo.objectId,
74
+ };
75
+ }
76
+
77
+ const comments = await this.modelInstance.select(where, {
78
+ desc: 'insertedAt',
79
+ limit: count,
80
+ field: [
81
+ 'status',
82
+ 'comment',
83
+ 'insertedAt',
84
+ 'link',
85
+ 'mail',
86
+ 'nick',
87
+ 'url',
88
+ 'pid',
89
+ 'rid',
90
+ 'ua',
91
+ 'user_id',
92
+ 'sticky',
93
+ ],
94
+ });
82
95
 
83
96
  const userModel = this.service(
84
97
  `storage/${this.config('storage')}`,
@@ -100,20 +113,26 @@ module.exports = class extends BaseRest {
100
113
 
101
114
  return this.json(
102
115
  await Promise.all(
103
- comments.map((cmt) => formatCmt(cmt, users, this.config()))
116
+ comments.map((cmt) =>
117
+ formatCmt(cmt, users, this.config(), userInfo)
118
+ )
104
119
  )
105
120
  );
106
121
  }
107
122
 
108
123
  case 'count': {
109
124
  const { url } = this.get();
110
- const data = await this.modelInstance.select(
111
- {
112
- url: ['IN', url],
125
+ const where = { url: ['IN', url] };
126
+ if (think.isEmpty(userInfo) || this.config('storage') === 'deta') {
127
+ where.status = ['NOT IN', ['waiting', 'spam']];
128
+ } else {
129
+ where._complex = {
130
+ _logic: 'or',
113
131
  status: ['NOT IN', ['waiting', 'spam']],
114
- },
115
- { field: ['url'] }
116
- );
132
+ user_id: userInfo.objectId,
133
+ };
134
+ }
135
+ const data = await this.modelInstance.select(where, { field: ['url'] });
117
136
  const counts = url.map(
118
137
  (u) => data.filter(({ url }) => url === u).length
119
138
  );
@@ -179,35 +198,42 @@ module.exports = class extends BaseRest {
179
198
  spamCount,
180
199
  waitingCount,
181
200
  data: await Promise.all(
182
- comments.map((cmt) => formatCmt(cmt, users, this.config()))
201
+ comments.map((cmt) =>
202
+ formatCmt(cmt, users, this.config(), userInfo)
203
+ )
183
204
  ),
184
205
  });
185
206
  }
186
207
 
187
208
  default: {
188
209
  const { path: url, page, pageSize } = this.get();
189
-
190
- const comments = await this.modelInstance.select(
191
- {
192
- url,
210
+ const where = { url };
211
+ if (think.isEmpty(userInfo) || this.config('storage') === 'deta') {
212
+ where.status = ['NOT IN', ['waiting', 'spam']];
213
+ } else {
214
+ where._complex = {
215
+ _logic: 'or',
193
216
  status: ['NOT IN', ['waiting', 'spam']],
194
- },
195
- {
196
- desc: 'insertedAt',
197
- field: [
198
- 'comment',
199
- 'insertedAt',
200
- 'link',
201
- 'mail',
202
- 'nick',
203
- 'pid',
204
- 'rid',
205
- 'ua',
206
- 'user_id',
207
- 'sticky',
208
- ],
209
- }
210
- );
217
+ user_id: userInfo.objectId,
218
+ };
219
+ }
220
+
221
+ const comments = await this.modelInstance.select(where, {
222
+ desc: 'insertedAt',
223
+ field: [
224
+ 'status',
225
+ 'comment',
226
+ 'insertedAt',
227
+ 'link',
228
+ 'mail',
229
+ 'nick',
230
+ 'pid',
231
+ 'rid',
232
+ 'ua',
233
+ 'user_id',
234
+ 'sticky',
235
+ ],
236
+ });
211
237
 
212
238
  const userModel = this.service(
213
239
  `storage/${this.config('storage')}`,
@@ -241,11 +267,16 @@ module.exports = class extends BaseRest {
241
267
  count: comments.length,
242
268
  data: await Promise.all(
243
269
  rootComments.map(async (comment) => {
244
- const cmt = await formatCmt(comment, users, this.config());
270
+ const cmt = await formatCmt(
271
+ comment,
272
+ users,
273
+ this.config(),
274
+ userInfo
275
+ );
245
276
  cmt.children = await Promise.all(
246
277
  comments
247
278
  .filter(({ rid }) => rid === cmt.objectId)
248
- .map((cmt) => formatCmt(cmt, users, this.config()))
279
+ .map((cmt) => formatCmt(cmt, users, this.config(), userInfo))
249
280
  .reverse()
250
281
  );
251
282
  return cmt;
@@ -403,12 +434,19 @@ module.exports = class extends BaseRest {
403
434
 
404
435
  think.logger.debug(`Comment post hooks postSave done!`);
405
436
 
406
- return this.success(await formatCmt(resp, [userInfo], this.config()));
437
+ return this.success(
438
+ await formatCmt(resp, [userInfo], this.config(), userInfo)
439
+ );
407
440
  }
408
441
 
409
442
  async putAction() {
410
443
  const data = this.post();
411
- const oldData = await this.modelInstance.select({ objectId: this.id });
444
+ let oldData = await this.modelInstance.select({ objectId: this.id });
445
+ if (think.isEmpty(oldData)) {
446
+ return this.success();
447
+ }
448
+
449
+ oldData = oldData[0];
412
450
  const preUpdateResp = await this.hook('preUpdate', {
413
451
  ...data,
414
452
  objectId: this.id,
@@ -418,18 +456,22 @@ module.exports = class extends BaseRest {
418
456
  return this.fail(preUpdateResp);
419
457
  }
420
458
 
421
- await this.modelInstance.update(data, { objectId: this.id });
459
+ const newData = await this.modelInstance.update(data, {
460
+ objectId: this.id,
461
+ });
422
462
 
423
463
  if (
424
464
  oldData.status === 'waiting' &&
425
465
  data.status === 'approved' &&
426
466
  oldData.pid
427
467
  ) {
428
- let pComment = await this.modelInstance.select({ objectId: oldData.pid });
468
+ let pComment = await this.modelInstance.select({
469
+ objectId: oldData.pid,
470
+ });
429
471
  pComment = pComment[0];
430
472
 
431
473
  const notify = this.service('notify');
432
- await notify.run(oldData, pComment, true);
474
+ await notify.run(newData, pComment, true);
433
475
  }
434
476
 
435
477
  await this.hook('postUpdate', data);
@@ -19,10 +19,11 @@ module.exports = class extends think.Controller {
19
19
  'color: white; background: #0078E7; padding:5px 0;',
20
20
  'padding:4px;border:1px solid #0078E7;'
21
21
  );
22
+ const params = new URLSearchParams(location.search.slice(1));
22
23
  const waline = new Waline({
23
24
  el: '#waline',
24
- path: '/',
25
- lang: new URLSearchParams(location.search.slice(1)).get('lng'),
25
+ path: params.get('path') || '/',
26
+ lang: params.get('lng'),
26
27
  serverURL: location.protocol + '//' + location.host + location.pathname.replace(/\\/+$/, '')
27
28
  });
28
29
  </script>
package/src/logic/base.js CHANGED
@@ -45,6 +45,7 @@ module.exports = class extends think.Logic {
45
45
  { email: userMail },
46
46
  {
47
47
  field: [
48
+ 'id',
48
49
  'email',
49
50
  'url',
50
51
  'display_name',
@@ -5,6 +5,15 @@ const { GRAVATAR_STR } = process.env;
5
5
  const env = new nunjucks.Environment();
6
6
  env.addFilter('md5', (str) => helper.md5(str));
7
7
 
8
+ const DEFAULT_GRAVATAR_STR = `{%- set numExp = r/^[0-9]+$/g -%}
9
+ {%- set qqMailExp = r/^[0-9]+@qq.com$/ig -%}
10
+ {%- if numExp.test(nick) -%}
11
+ https://q1.qlogo.cn/g?b=qq&nk={{nick}}&s=100
12
+ {%- elif qqMailExp.test(mail) -%}
13
+ https://q1.qlogo.cn/g?b=qq&nk={{mail|replace('@qq.com', '')}}&s=100
14
+ {%- else -%}
15
+ https://seccdn.libravatar.org/avatar/{{mail|md5}}
16
+ {%- endif -%}`;
8
17
  module.exports = class extends think.Service {
9
18
  async stringify(comment) {
10
19
  const fn = think.config('avatarUrl');
@@ -15,8 +24,7 @@ module.exports = class extends think.Service {
15
24
  }
16
25
  }
17
26
 
18
- const gravatarStr =
19
- GRAVATAR_STR || 'https://seccdn.libravatar.org/avatar/{{mail|md5}}';
27
+ const gravatarStr = GRAVATAR_STR || DEFAULT_GRAVATAR_STR;
20
28
  return env.renderString(gravatarStr, comment);
21
29
  }
22
30
  };
@@ -28,10 +28,16 @@ DOMPurify.addHook('afterSanitizeAttributes', function (node) {
28
28
  });
29
29
 
30
30
  const sanitize = (content) =>
31
- DOMPurify.sanitize(content, {
32
- FORBID_TAGS: ['form', 'input', 'style'],
33
- FORBID_ATTR: ['autoplay', 'style'],
34
- });
31
+ DOMPurify.sanitize(
32
+ content,
33
+ Object.assign(
34
+ {
35
+ FORBID_TAGS: ['form', 'input', 'style'],
36
+ FORBID_ATTR: ['autoplay', 'style'],
37
+ },
38
+ think.config('domPurify') || {}
39
+ )
40
+ );
35
41
 
36
42
  module.exports = {
37
43
  sanitize,
@@ -273,6 +273,50 @@ module.exports = class extends think.Service {
273
273
  });
274
274
  }
275
275
 
276
+ async pushplus({ title, content }, self, parent) {
277
+ const {
278
+ PUSH_PLUS_KEY,
279
+ PUSH_PLUS_TOPIC: topic,
280
+ PUSH_PLUS_TEMPLATE: template,
281
+ PUSH_PLUS_CHANNEL: channel,
282
+ PUSH_PLUS_WEBHOOK: webhook,
283
+ PUSH_PLUS_CALLBACKURL: callbackUrl,
284
+ SITE_NAME,
285
+ SITE_URL,
286
+ } = process.env;
287
+
288
+ if (!PUSH_PLUS_KEY) {
289
+ return false;
290
+ }
291
+
292
+ const data = {
293
+ self,
294
+ parent,
295
+ site: {
296
+ name: SITE_NAME,
297
+ url: SITE_URL,
298
+ postUrl: SITE_URL + self.url + '#' + self.objectId,
299
+ },
300
+ };
301
+ title = nunjucks.renderString(title, data);
302
+ content = nunjucks.renderString(content, data);
303
+
304
+ return request({
305
+ uri: `http://www.pushplus.plus/send/${PUSH_PLUS_KEY}`,
306
+ method: 'POST',
307
+ form: {
308
+ title,
309
+ content,
310
+ topic,
311
+ template,
312
+ channel,
313
+ webhook,
314
+ callbackUrl,
315
+ },
316
+ json: true,
317
+ });
318
+ }
319
+
276
320
  async run(comment, parent, disableAuthorNotify = false) {
277
321
  const { AUTHOR_EMAIL, BLOGGER_EMAIL } = process.env;
278
322
  const { mailSubject, mailTemplate, mailSubjectAdmin, mailTemplateAdmin } =
@@ -312,11 +356,10 @@ module.exports = class extends think.Service {
312
356
  );
313
357
  const qq = await this.qq(comment, parent);
314
358
  const telegram = await this.telegram(comment, parent);
359
+ const pushplus = await this.pushplus({ title, content }, comment, parent);
360
+ console.log(pushplus);
315
361
  if (
316
- think.isEmpty(wechat) &&
317
- think.isEmpty(qq) &&
318
- think.isEmpty(telegram) &&
319
- think.isEmpty(qywxAmWechat) &&
362
+ [wechat, qq, telegram, qywxAmWechat, pushplus].every(think.isEmpty) &&
320
363
  !isReplyAuthor
321
364
  ) {
322
365
  mailList.push({ to: AUTHOR, title, content });
@@ -33,14 +33,17 @@ module.exports = class extends Base {
33
33
  }
34
34
  }
35
35
 
36
- where(instance, where) {
36
+ parseWhere(where) {
37
37
  if (think.isEmpty(where)) {
38
- return instance;
38
+ return {};
39
39
  }
40
40
 
41
41
  const filter = {};
42
42
  const parseKey = (k) => (k === 'objectId' ? '_id' : k);
43
43
  for (let k in where) {
44
+ if (k === '_complex') {
45
+ continue;
46
+ }
44
47
  if (think.isString(where[k])) {
45
48
  filter[parseKey(k)] = _.eq(where[k]);
46
49
  continue;
@@ -84,7 +87,26 @@ module.exports = class extends Base {
84
87
  }
85
88
  }
86
89
  }
87
- return instance.where(filter);
90
+ return filter;
91
+ }
92
+
93
+ where(instance, where) {
94
+ const filter = this.parseWhere(where);
95
+ if (!where._complex) {
96
+ return instance.where(filter);
97
+ }
98
+
99
+ const filters = [];
100
+ for (const k in where._complex) {
101
+ if (k === '_logic') {
102
+ continue;
103
+ }
104
+ filters.push({
105
+ ...this.parseWhere({ [k]: where._complex[k] }),
106
+ ...filter,
107
+ });
108
+ }
109
+ return instance.where(_[where._complex._logic](...filters));
88
110
  }
89
111
 
90
112
  async _select(where, { desc, limit, offset, field } = {}) {
@@ -166,13 +166,18 @@ module.exports = class extends Base {
166
166
  return this.git.set(filename, csv, { sha });
167
167
  }
168
168
 
169
- where(data, where) {
169
+ parseWhere(where) {
170
+ const _where = [];
170
171
  if (think.isEmpty(where)) {
171
- return data;
172
+ return _where;
172
173
  }
173
174
 
174
175
  const filters = [];
175
176
  for (let k in where) {
177
+ if (k === '_complex') {
178
+ continue;
179
+ }
180
+
176
181
  if (k === 'objectId') {
177
182
  filters.push((item) => item.id === where[k]);
178
183
  continue;
@@ -219,7 +224,33 @@ module.exports = class extends Base {
219
224
  }
220
225
  }
221
226
 
222
- return filters.reduce((data, fn) => data.filter(fn), data);
227
+ return filters;
228
+ }
229
+
230
+ where(data, where) {
231
+ const filter = this.parseWhere(where);
232
+
233
+ if (!where._complex) {
234
+ return data.filter((item) => filter.every((fn) => fn(item)));
235
+ }
236
+
237
+ const logicMap = {
238
+ and: Array.prototype.every,
239
+ or: Array.prototype.some,
240
+ };
241
+ const filters = [];
242
+ for (const k in where._complex) {
243
+ if (k === '_logic') {
244
+ continue;
245
+ }
246
+
247
+ filters.push([...filter, ...this.parseWhere({ [k]: where._complex[k] })]);
248
+ }
249
+
250
+ const logicFn = logicMap[where._complex._logic];
251
+ return data.filter((item) =>
252
+ logicFn.call(filters, (filter) => filter.every((fn) => fn(item)))
253
+ );
223
254
  }
224
255
 
225
256
  async select(where, { desc, limit, offset, field } = {}) {
@@ -7,14 +7,18 @@ module.exports = class extends Base {
7
7
  this.db = inspirecloud.db;
8
8
  }
9
9
 
10
- where(where) {
10
+ parseWhere(where) {
11
+ const _where = {};
11
12
  if (think.isEmpty(where)) {
12
- return;
13
+ return _where;
13
14
  }
14
15
 
15
- const _where = {};
16
16
  const parseKey = (k) => (k === 'objectId' ? '_id' : k);
17
17
  for (const k in where) {
18
+ if (k === '_complex') {
19
+ continue;
20
+ }
21
+
18
22
  if (think.isString(where[k])) {
19
23
  _where[parseKey(k)] =
20
24
  k === 'objectId' ? this.db.ObjectId(where[k]) : where[k];
@@ -28,18 +32,20 @@ module.exports = class extends Base {
28
32
  const handler = where[k][0].toUpperCase();
29
33
  switch (handler) {
30
34
  case 'IN':
31
- _where[parseKey(k)] = this.db.in(
32
- k === 'objectId'
33
- ? where[k][1].map(this.db.ObjectId)
34
- : where[k][1]
35
- );
35
+ _where[parseKey(k)] = {
36
+ $in:
37
+ k === 'objectId'
38
+ ? where[k][1].map(this.db.ObjectId)
39
+ : where[k][1],
40
+ };
36
41
  break;
37
42
  case 'NOT IN':
38
- _where[parseKey(k)] = this.db.nin(
39
- k === 'objectId'
40
- ? where[k][1].map(this.db.ObjectId)
41
- : where[k][1]
42
- );
43
+ _where[parseKey(k)] = {
44
+ $nin:
45
+ k === 'objectId'
46
+ ? where[k][1].map(this.db.ObjectId)
47
+ : where[k][1],
48
+ };
43
49
  break;
44
50
  case 'LIKE': {
45
51
  const first = where[k][1][0];
@@ -52,14 +58,14 @@ module.exports = class extends Base {
52
58
  } else if (last === '%') {
53
59
  reg = new RegExp('^' + where[k][1].slice(0, -1));
54
60
  }
55
- _where[parseKey(k)] = this.db.regex(reg);
61
+ _where[parseKey(k)] = { $regex: reg };
56
62
  break;
57
63
  }
58
64
  case '!=':
59
- _where[parseKey(k)] = this.db.ne(where[k]);
65
+ _where[parseKey(k)] = { $ne: where[k] };
60
66
  break;
61
67
  case '>':
62
- _where[parseKey(k)] = this.db.gt(where[k]);
68
+ _where[parseKey(k)] = { $gt: where[k] };
63
69
  break;
64
70
  }
65
71
  }
@@ -69,6 +75,27 @@ module.exports = class extends Base {
69
75
  return _where;
70
76
  }
71
77
 
78
+ where(where) {
79
+ const filter = this.parseWhere(where);
80
+ if (!where._complex) {
81
+ return filter;
82
+ }
83
+
84
+ const filters = [];
85
+ for (const k in where._complex) {
86
+ if (k === '_logic') {
87
+ continue;
88
+ }
89
+
90
+ filters.push({
91
+ ...this.parseWhere({ [k]: where._complex[k] }),
92
+ ...filter,
93
+ });
94
+ }
95
+
96
+ return { [`$${where._complex._logic}`]: filters };
97
+ }
98
+
72
99
  async _select(where, { desc, limit, offset, field } = {}) {
73
100
  const instance = this.db.table(this.tableName);
74
101
  const query = instance.where(this.where(where));
@@ -13,19 +13,26 @@ if (LEAN_ID && LEAN_KEY && LEAN_MASTER_KEY) {
13
13
  });
14
14
  }
15
15
  module.exports = class extends Base {
16
- where(instance, where) {
16
+ parseWhere(className, where) {
17
+ const instance = new AV.Query(className);
17
18
  if (think.isEmpty(where)) {
18
- return;
19
+ return instance;
19
20
  }
20
21
 
21
22
  for (const k in where) {
23
+ if (k === '_complex') {
24
+ continue;
25
+ }
26
+
22
27
  if (think.isString(where[k])) {
23
28
  instance.equalTo(k, where[k]);
24
29
  continue;
25
30
  }
31
+
26
32
  if (where[k] === undefined) {
27
33
  instance.doesNotExist(k);
28
34
  }
35
+
29
36
  if (Array.isArray(where[k])) {
30
37
  if (where[k][0]) {
31
38
  const handler = where[k][0].toUpperCase();
@@ -58,11 +65,32 @@ module.exports = class extends Base {
58
65
  }
59
66
  }
60
67
  }
68
+ return instance;
69
+ }
70
+
71
+ where(className, where) {
72
+ if (!where._complex) {
73
+ return this.parseWhere(className, where);
74
+ }
75
+
76
+ const filters = [];
77
+ for (const k in where._complex) {
78
+ if (k === '_logic') {
79
+ continue;
80
+ }
81
+
82
+ const filter = this.parseWhere(className, {
83
+ ...where,
84
+ [k]: where._complex[k],
85
+ });
86
+ filters.push(filter);
87
+ }
88
+
89
+ return AV.Query[where._complex._logic](...filters);
61
90
  }
62
91
 
63
92
  async _select(where, { desc, limit, offset, field } = {}) {
64
- const instance = new AV.Query(this.tableName);
65
- this.where(instance, where);
93
+ const instance = this.where(this.tableName, where);
66
94
  if (desc) {
67
95
  instance.descending(desc);
68
96
  }
@@ -99,8 +127,7 @@ module.exports = class extends Base {
99
127
  }
100
128
 
101
129
  async count(where = {}, options = {}) {
102
- const instance = new AV.Query(this.tableName);
103
- this.where(instance, where);
130
+ const instance = this.where(this.tableName, where);
104
131
  return instance.count(options).catch((e) => {
105
132
  if (e.code === 101) {
106
133
  return 0;
@@ -127,8 +154,7 @@ module.exports = class extends Base {
127
154
  }
128
155
 
129
156
  async update(data, where) {
130
- const instance = new AV.Query(this.tableName);
131
- this.where(instance, where);
157
+ const instance = this.where(this.tableName, where);
132
158
  const ret = await instance.find();
133
159
 
134
160
  return Promise.all(
@@ -146,8 +172,7 @@ module.exports = class extends Base {
146
172
  }
147
173
 
148
174
  async delete(where) {
149
- const instance = new AV.Query(this.tableName);
150
- this.where(instance, where);
175
+ const instance = this.where(this.tableName, where);
151
176
  const data = await instance.find();
152
177
 
153
178
  return AV.Object.destroyAll(data);
@@ -2,25 +2,25 @@ const { ObjectId } = require('mongodb');
2
2
  const Base = require('./base');
3
3
 
4
4
  module.exports = class extends Base {
5
- where(instance, where) {
5
+ parseWhere(where) {
6
6
  if (think.isEmpty(where)) {
7
- return;
7
+ return {};
8
8
  }
9
9
 
10
+ const filter = {};
10
11
  const parseKey = (k) => (k === 'objectId' ? '_id' : k);
11
12
  for (let k in where) {
13
+ if (k === '_complex') {
14
+ continue;
15
+ }
12
16
  if (think.isString(where[k])) {
13
- instance.where({
14
- [parseKey(k)]: {
15
- $eq: k === 'objectId' ? ObjectId(where[k]) : where[k],
16
- },
17
- });
17
+ filter[parseKey(k)] = {
18
+ $eq: k === 'objectId' ? ObjectId(where[k]) : where[k],
19
+ };
18
20
  continue;
19
21
  }
20
22
  if (where[k] === undefined) {
21
- instance.where({
22
- [parseKey(k)]: { $eq: null },
23
- });
23
+ filter[parseKey(k)] = { $eq: null };
24
24
  }
25
25
  if (Array.isArray(where[k])) {
26
26
  if (where[k][0]) {
@@ -28,63 +28,70 @@ module.exports = class extends Base {
28
28
  switch (handler) {
29
29
  case 'IN':
30
30
  if (k === 'objectId') {
31
- instance.where({
32
- [parseKey(k)]: { $in: where[k][1].map(ObjectId) },
33
- });
31
+ filter[parseKey(k)] = { $in: where[k][1].map(ObjectId) };
34
32
  } else {
35
- instance.where({
36
- [parseKey(k)]: {
37
- $regex: new RegExp(`^(${where[k][1].join('|')})$`),
38
- },
39
- });
33
+ filter[parseKey(k)] = {
34
+ $regex: new RegExp(`^(${where[k][1].join('|')})$`),
35
+ };
40
36
  }
41
37
  break;
42
38
  case 'NOT IN':
43
- instance.where({
44
- [parseKey(k)]: {
45
- $nin:
46
- k === 'objectId' ? where[k][1].map(ObjectId) : where[k][1],
47
- },
48
- });
39
+ filter[parseKey(k)] = {
40
+ $nin:
41
+ k === 'objectId' ? where[k][1].map(ObjectId) : where[k][1],
42
+ };
49
43
  break;
50
44
  case 'LIKE': {
51
45
  const first = where[k][1][0];
52
46
  const last = where[k][1].slice(-1);
47
+ let reg;
53
48
  if (first === '%' && last === '%') {
54
- instance.where({
55
- [parseKey(k)]: {
56
- $regex: new RegExp(where[k][1].slice(1, -1)),
57
- },
58
- });
49
+ reg = new RegExp(where[k][1].slice(1, -1));
59
50
  } else if (first === '%') {
60
- instance.where({
61
- [parseKey(k)]: {
62
- $regex: new RegExp(where[k][1].slice(1) + '$'),
63
- },
64
- });
51
+ reg = new RegExp(where[k][1].slice(1) + '$');
65
52
  } else if (last === '%') {
66
- instance.where({
67
- [parseKey(k)]: {
68
- $regex: new RegExp('^' + where[k][1].slice(0, -1)),
69
- },
70
- });
53
+ reg = new RegExp('^' + where[k][1].slice(0, -1));
54
+ }
55
+
56
+ if (reg) {
57
+ filter[parseKey(k)] = { $regex: reg };
71
58
  }
72
59
  break;
73
60
  }
74
61
  case '!=':
75
- instance.where({
76
- [parseKey(k)]: { $ne: where[k][1] },
77
- });
62
+ filter[parseKey(k)] = { $ne: where[k][1] };
78
63
  break;
79
64
  case '>':
80
- instance.where({
81
- [parseKey(k)]: { $gt: where[k][1] },
82
- });
65
+ filter[parseKey(k)] = { $gt: where[k][1] };
83
66
  break;
84
67
  }
85
68
  }
86
69
  }
87
70
  }
71
+ return filter;
72
+ }
73
+
74
+ where(instance, where) {
75
+ const filter = this.parseWhere(where);
76
+ if (!where._complex) {
77
+ return instance.where(filter);
78
+ }
79
+
80
+ const filters = [];
81
+ for (const k in where._complex) {
82
+ if (k === '_logic') {
83
+ continue;
84
+ }
85
+ filters.push({
86
+ ...this.parseWhere({ [k]: where._complex[k] }),
87
+ ...filter,
88
+ });
89
+ }
90
+
91
+ return instance.where({
92
+ // $or, $and, $not, $nor
93
+ [`$${where._complex._logic}`]: filters,
94
+ });
88
95
  }
89
96
 
90
97
  async select(where, { desc, limit, offset, field } = {}) {