@waline/vercel 1.6.0 → 1.8.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/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,6 +1,6 @@
1
1
  {
2
2
  "name": "@waline/vercel",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "vercel server for waline comment system",
5
5
  "repository": "https://github.com/walinejs/waline",
6
6
  "license": "MIT",
@@ -10,25 +10,25 @@
10
10
  "@cloudbase/node-sdk": "^2.7.1",
11
11
  "@koa/cors": "^3.1.0",
12
12
  "akismet": "^2.0.6",
13
- "deta": "^1.0.1",
14
- "dompurify": "^2.3.3",
13
+ "deta": "^1.0.2",
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.2",
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.2",
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
33
  "think-model-mysql": "^1.1.6",
34
34
  "think-model-postgresql": "^1.1.6",
@@ -36,6 +36,6 @@
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
  );
@@ -154,40 +173,67 @@ module.exports = class extends BaseRest {
154
173
  offset: Math.max((page - 1) * pageSize, 0),
155
174
  });
156
175
 
176
+ const userModel = this.service(
177
+ `storage/${this.config('storage')}`,
178
+ 'Users'
179
+ );
180
+ const user_ids = Array.from(
181
+ new Set(comments.map(({ user_id }) => user_id).filter((v) => v))
182
+ );
183
+
184
+ let users = [];
185
+ if (user_ids.length) {
186
+ users = await userModel.select(
187
+ { objectId: ['IN', user_ids] },
188
+ {
189
+ field: ['display_name', 'email', 'url', 'type', 'avatar'],
190
+ }
191
+ );
192
+ }
193
+
157
194
  return this.success({
158
195
  page,
159
196
  totalPages: Math.ceil(count / pageSize),
160
197
  pageSize,
161
198
  spamCount,
162
199
  waitingCount,
163
- data: comments,
200
+ data: await Promise.all(
201
+ comments.map((cmt) =>
202
+ formatCmt(cmt, users, this.config(), userInfo)
203
+ )
204
+ ),
164
205
  });
165
206
  }
166
207
 
167
208
  default: {
168
209
  const { path: url, page, pageSize } = this.get();
169
-
170
- const comments = await this.modelInstance.select(
171
- {
172
- 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',
173
216
  status: ['NOT IN', ['waiting', 'spam']],
174
- },
175
- {
176
- desc: 'insertedAt',
177
- field: [
178
- 'comment',
179
- 'insertedAt',
180
- 'link',
181
- 'mail',
182
- 'nick',
183
- 'pid',
184
- 'rid',
185
- 'ua',
186
- 'user_id',
187
- 'sticky',
188
- ],
189
- }
190
- );
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
+ });
191
237
 
192
238
  const userModel = this.service(
193
239
  `storage/${this.config('storage')}`,
@@ -221,11 +267,16 @@ module.exports = class extends BaseRest {
221
267
  count: comments.length,
222
268
  data: await Promise.all(
223
269
  rootComments.map(async (comment) => {
224
- const cmt = await formatCmt(comment, users, this.config());
270
+ const cmt = await formatCmt(
271
+ comment,
272
+ users,
273
+ this.config(),
274
+ userInfo
275
+ );
225
276
  cmt.children = await Promise.all(
226
277
  comments
227
278
  .filter(({ rid }) => rid === cmt.objectId)
228
- .map((cmt) => formatCmt(cmt, users, this.config()))
279
+ .map((cmt) => formatCmt(cmt, users, this.config(), userInfo))
229
280
  .reverse()
230
281
  );
231
282
  return cmt;
@@ -383,7 +434,9 @@ module.exports = class extends BaseRest {
383
434
 
384
435
  think.logger.debug(`Comment post hooks postSave done!`);
385
436
 
386
- return this.success(await formatCmt(resp, [userInfo], this.config()));
437
+ return this.success(
438
+ await formatCmt(resp, [userInfo], this.config(), userInfo)
439
+ );
387
440
  }
388
441
 
389
442
  async putAction() {
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',
@@ -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 } =
@@ -287,7 +331,7 @@ module.exports = class extends think.Service {
287
331
  ? parent && parent.mail.toLowerCase() === AUTHOR.toLowerCase()
288
332
  : false;
289
333
 
290
- const title = mailSubjectAdmin || '{{site.name}} 上有新评论了';
334
+ const title = mailSubjectAdmin || '{{site.name | safe}} 上有新评论了';
291
335
  const content =
292
336
  mailTemplateAdmin ||
293
337
  `
@@ -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 });
@@ -331,7 +374,8 @@ module.exports = class extends think.Service {
331
374
  mailList.push({
332
375
  to: parent.mail,
333
376
  title:
334
- mailSubject || '{{parent.nick}},『{{site.name}}』上的评论收到了回复',
377
+ mailSubject ||
378
+ '{{parent.nick | safe}},『{{site.name | safe}}』上的评论收到了回复',
335
379
  content:
336
380
  mailTemplate ||
337
381
  `
@@ -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 } = {}) {
@@ -115,8 +137,9 @@ module.exports = class extends Base {
115
137
  async select(where, options = {}) {
116
138
  let data = [];
117
139
  let ret = [];
140
+ let offset = options.offset || 0;
118
141
  do {
119
- options.offset = (options.offset || 0) + data.length;
142
+ options.offset = offset + data.length;
120
143
  ret = await this._select(where, options);
121
144
  data = data.concat(ret);
122
145
  } while (ret.length === 100);
@@ -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));
@@ -101,8 +128,9 @@ module.exports = class extends Base {
101
128
  async select(where, options = {}) {
102
129
  let data = [];
103
130
  let ret = [];
131
+ let offset = options.offset || 0;
104
132
  do {
105
- options.offset = (options.offset || 0) + data.length;
133
+ options.offset = offset + data.length;
106
134
  ret = await this._select(where, options);
107
135
  data = data.concat(ret);
108
136
  } while (ret.length === 1000);
@@ -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
  }
@@ -88,8 +116,9 @@ module.exports = class extends Base {
88
116
  async select(where, options = {}) {
89
117
  let data = [];
90
118
  let ret = [];
119
+ let offset = options.offset || 0;
91
120
  do {
92
- options.offset = (options.offset || 0) + data.length;
121
+ options.offset = offset + data.length;
93
122
  ret = await this._select(where, options);
94
123
  data = data.concat(ret);
95
124
  } while (ret.length === 100);
@@ -98,8 +127,7 @@ module.exports = class extends Base {
98
127
  }
99
128
 
100
129
  async count(where = {}, options = {}) {
101
- const instance = new AV.Query(this.tableName);
102
- this.where(instance, where);
130
+ const instance = this.where(this.tableName, where);
103
131
  return instance.count(options).catch((e) => {
104
132
  if (e.code === 101) {
105
133
  return 0;
@@ -126,8 +154,7 @@ module.exports = class extends Base {
126
154
  }
127
155
 
128
156
  async update(data, where) {
129
- const instance = new AV.Query(this.tableName);
130
- this.where(instance, where);
157
+ const instance = this.where(this.tableName, where);
131
158
  const ret = await instance.find();
132
159
 
133
160
  return Promise.all(
@@ -145,8 +172,7 @@ module.exports = class extends Base {
145
172
  }
146
173
 
147
174
  async delete(where) {
148
- const instance = new AV.Query(this.tableName);
149
- this.where(instance, where);
175
+ const instance = this.where(this.tableName, where);
150
176
  const data = await instance.find();
151
177
 
152
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 } = {}) {