@prosopo/database 2.1.4 → 2.1.6

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 (64) hide show
  1. package/dist/base/index.d.ts +3 -0
  2. package/dist/base/index.d.ts.map +1 -0
  3. package/dist/base/index.js +3 -0
  4. package/dist/base/index.js.map +1 -0
  5. package/dist/base/mongo.d.ts +16 -0
  6. package/dist/base/mongo.d.ts.map +1 -0
  7. package/dist/base/mongo.js +89 -0
  8. package/dist/base/mongo.js.map +1 -0
  9. package/dist/base/mongoMemory.d.ts +11 -0
  10. package/dist/base/mongoMemory.d.ts.map +1 -0
  11. package/dist/base/mongoMemory.js +27 -0
  12. package/dist/base/mongoMemory.js.map +1 -0
  13. package/dist/captchaDatabase/captchaDatabse.d.ts +3 -0
  14. package/dist/captchaDatabase/captchaDatabse.d.ts.map +1 -0
  15. package/dist/captchaDatabase/captchaDatabse.js +22 -0
  16. package/dist/captchaDatabase/captchaDatabse.js.map +1 -0
  17. package/dist/captchaDatabase/index.d.ts +2 -0
  18. package/dist/captchaDatabase/index.d.ts.map +1 -0
  19. package/dist/captchaDatabase/index.js +2 -0
  20. package/dist/captchaDatabase/index.js.map +1 -0
  21. package/dist/cjs/base/index.cjs +6 -0
  22. package/dist/cjs/base/mongo.cjs +97 -0
  23. package/dist/cjs/base/mongoMemory.cjs +28 -0
  24. package/dist/cjs/databases/captcha.cjs +82 -0
  25. package/dist/cjs/databases/client.cjs +52 -0
  26. package/dist/cjs/databases/index.cjs +19 -0
  27. package/dist/cjs/databases/provider.cjs +983 -0
  28. package/dist/cjs/index.cjs +15 -0
  29. package/dist/databases/captcha.d.ts +16 -0
  30. package/dist/databases/captcha.d.ts.map +1 -0
  31. package/dist/databases/captcha.js +75 -0
  32. package/dist/databases/captcha.js.map +1 -0
  33. package/dist/databases/client.d.ts +12 -0
  34. package/dist/databases/client.d.ts.map +1 -0
  35. package/dist/databases/client.js +48 -0
  36. package/dist/databases/client.js.map +1 -0
  37. package/dist/databases/index.d.ts +16 -0
  38. package/dist/databases/index.d.ts.map +1 -0
  39. package/dist/databases/index.js +17 -0
  40. package/dist/databases/index.js.map +1 -0
  41. package/dist/databases/mongo.d.ts +58 -0
  42. package/dist/databases/mongo.d.ts.map +1 -0
  43. package/dist/databases/mongo.js +682 -0
  44. package/dist/databases/mongo.js.map +1 -0
  45. package/dist/databases/mongoMemory.d.ts +10 -0
  46. package/dist/databases/mongoMemory.d.ts.map +1 -0
  47. package/dist/databases/mongoMemory.js +30 -0
  48. package/dist/databases/mongoMemory.js.map +1 -0
  49. package/dist/databases/provider.d.ts +73 -0
  50. package/dist/databases/provider.d.ts.map +1 -0
  51. package/dist/databases/provider.js +823 -0
  52. package/dist/databases/provider.js.map +1 -0
  53. package/dist/eventsDatabase/eventsDatabase.d.ts +3 -0
  54. package/dist/eventsDatabase/eventsDatabase.d.ts.map +1 -0
  55. package/dist/eventsDatabase/eventsDatabase.js +46 -0
  56. package/dist/eventsDatabase/eventsDatabase.js.map +1 -0
  57. package/dist/eventsDatabase/index.d.ts +2 -0
  58. package/dist/eventsDatabase/index.d.ts.map +1 -0
  59. package/dist/eventsDatabase/index.js +2 -0
  60. package/dist/eventsDatabase/index.js.map +1 -0
  61. package/dist/index.d.ts +3 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js.map +1 -0
  64. package/package.json +6 -6
@@ -0,0 +1,823 @@
1
+ import { isHex } from "@polkadot/util/is";
2
+ import { ProsopoDBError, ProsopoEnvError } from "@prosopo/common";
3
+ import { CaptchaStates, CaptchaStatus, DatasetWithIdsAndTreeSchema, } from "@prosopo/types";
4
+ import { CaptchaRecordSchema, ClientRecordSchema, DatasetRecordSchema, PendingRecordSchema, PoWCaptchaRecordSchema, ScheduledTaskRecordSchema, ScheduledTaskSchema, SessionRecordSchema, SolutionRecordSchema, StoredStatusNames, UserCommitmentRecordSchema, UserCommitmentSchema, UserSolutionRecordSchema, } from "@prosopo/types-database";
5
+ import { MongoDatabase } from "../base/mongo.js";
6
+ var TableNames;
7
+ (function (TableNames) {
8
+ TableNames["captcha"] = "captcha";
9
+ TableNames["dataset"] = "dataset";
10
+ TableNames["solution"] = "solution";
11
+ TableNames["commitment"] = "commitment";
12
+ TableNames["usersolution"] = "usersolution";
13
+ TableNames["pending"] = "pending";
14
+ TableNames["scheduler"] = "scheduler";
15
+ TableNames["powcaptcha"] = "powcaptcha";
16
+ TableNames["client"] = "client";
17
+ TableNames["session"] = "session";
18
+ })(TableNames || (TableNames = {}));
19
+ const PROVIDER_TABLES = [
20
+ {
21
+ collectionName: TableNames.captcha,
22
+ modelName: "Captcha",
23
+ schema: CaptchaRecordSchema,
24
+ },
25
+ {
26
+ collectionName: TableNames.powcaptcha,
27
+ modelName: "PowCaptcha",
28
+ schema: PoWCaptchaRecordSchema,
29
+ },
30
+ {
31
+ collectionName: TableNames.dataset,
32
+ modelName: "Dataset",
33
+ schema: DatasetRecordSchema,
34
+ },
35
+ {
36
+ collectionName: TableNames.solution,
37
+ modelName: "Solution",
38
+ schema: SolutionRecordSchema,
39
+ },
40
+ {
41
+ collectionName: TableNames.commitment,
42
+ modelName: "UserCommitment",
43
+ schema: UserCommitmentRecordSchema,
44
+ },
45
+ {
46
+ collectionName: TableNames.usersolution,
47
+ modelName: "UserSolution",
48
+ schema: UserSolutionRecordSchema,
49
+ },
50
+ {
51
+ collectionName: TableNames.pending,
52
+ modelName: "Pending",
53
+ schema: PendingRecordSchema,
54
+ },
55
+ {
56
+ collectionName: TableNames.scheduler,
57
+ modelName: "Scheduler",
58
+ schema: ScheduledTaskRecordSchema,
59
+ },
60
+ {
61
+ collectionName: TableNames.client,
62
+ modelName: "Client",
63
+ schema: ClientRecordSchema,
64
+ },
65
+ {
66
+ collectionName: TableNames.session,
67
+ modelName: "Session",
68
+ schema: SessionRecordSchema,
69
+ },
70
+ ];
71
+ export class ProviderDatabase extends MongoDatabase {
72
+ constructor(url, dbname, authSource, logger) {
73
+ super(url, dbname, authSource, logger);
74
+ this.tables = {};
75
+ this.tables = {};
76
+ }
77
+ async connect() {
78
+ await super.connect();
79
+ this.loadTables();
80
+ }
81
+ loadTables() {
82
+ const tables = {};
83
+ PROVIDER_TABLES.map(({ collectionName, modelName, schema }) => {
84
+ if (this.connection) {
85
+ tables[collectionName] = this.connection.model(modelName, schema);
86
+ }
87
+ });
88
+ this.tables = tables;
89
+ }
90
+ getTables() {
91
+ if (!this.tables) {
92
+ throw new ProsopoDBError("DATABASE.TABLES_UNDEFINED", {
93
+ context: { failedFuncName: this.getTables.name },
94
+ logger: this.logger,
95
+ });
96
+ }
97
+ return this.tables;
98
+ }
99
+ async storeDataset(dataset) {
100
+ try {
101
+ this.logger.debug("Storing dataset in database");
102
+ const parsedDataset = DatasetWithIdsAndTreeSchema.parse(dataset);
103
+ const datasetDoc = {
104
+ datasetId: parsedDataset.datasetId,
105
+ datasetContentId: parsedDataset.datasetContentId,
106
+ format: parsedDataset.format,
107
+ contentTree: parsedDataset.contentTree,
108
+ solutionTree: parsedDataset.solutionTree,
109
+ };
110
+ await this.tables.dataset?.updateOne({ datasetId: parsedDataset.datasetId }, { $set: datasetDoc }, { upsert: true });
111
+ const captchaDocs = parsedDataset.captchas.map(({ solution, ...captcha }, index) => ({
112
+ ...captcha,
113
+ datasetId: parsedDataset.datasetId,
114
+ datasetContentId: parsedDataset.datasetContentId,
115
+ index,
116
+ solved: !!solution?.length,
117
+ }));
118
+ this.logger.debug("Inserting captcha records");
119
+ if (captchaDocs.length) {
120
+ await this.tables?.captcha.bulkWrite(captchaDocs.map((captchaDoc) => ({
121
+ updateOne: {
122
+ filter: { captchaId: captchaDoc.captchaId },
123
+ update: { $set: captchaDoc },
124
+ upsert: true,
125
+ },
126
+ })));
127
+ }
128
+ const captchaSolutionDocs = parsedDataset.captchas
129
+ .filter(({ solution }) => solution?.length)
130
+ .map((captcha) => ({
131
+ captchaId: captcha.captchaId,
132
+ captchaContentId: captcha.captchaContentId,
133
+ solution: captcha.solution,
134
+ salt: captcha.salt,
135
+ datasetId: parsedDataset.datasetId,
136
+ datasetContentId: parsedDataset.datasetContentId,
137
+ }));
138
+ this.logger.debug("Inserting solution records");
139
+ if (captchaSolutionDocs.length) {
140
+ await this.tables?.solution.bulkWrite(captchaSolutionDocs.map((captchaSolutionDoc) => ({
141
+ updateOne: {
142
+ filter: { captchaId: captchaSolutionDoc.captchaId },
143
+ update: { $set: captchaSolutionDoc },
144
+ upsert: true,
145
+ },
146
+ })));
147
+ }
148
+ this.logger.debug("Dataset stored in database");
149
+ }
150
+ catch (err) {
151
+ throw new ProsopoDBError("DATABASE.DATASET_LOAD_FAILED", {
152
+ context: { failedFuncName: this.storeDataset.name, error: err },
153
+ logger: this.logger,
154
+ });
155
+ }
156
+ }
157
+ async getSolutions(datasetId) {
158
+ const docs = await this.tables?.solution
159
+ .find({ datasetId })
160
+ .lean();
161
+ return docs ? docs : [];
162
+ }
163
+ async getDataset(datasetId) {
164
+ const datasetDoc = await this.tables?.dataset
165
+ .findOne({ datasetId: datasetId })
166
+ .lean();
167
+ if (datasetDoc) {
168
+ const { datasetContentId, format, contentTree, solutionTree } = datasetDoc;
169
+ const captchas = (await this.tables?.captcha.find({ datasetId }).lean()) ||
170
+ [];
171
+ const solutions = (await this.tables?.solution
172
+ .find({ datasetId })
173
+ .lean()) || [];
174
+ const solutionsKeyed = {};
175
+ for (const solution of solutions) {
176
+ solutionsKeyed[solution.captchaId] = solution;
177
+ }
178
+ return {
179
+ datasetId,
180
+ datasetContentId,
181
+ format,
182
+ contentTree: contentTree || [],
183
+ solutionTree: solutionTree || [],
184
+ captchas: captchas.map((captchaDoc) => {
185
+ const { captchaId, captchaContentId, items, target, salt, solved } = captchaDoc;
186
+ const solution = solutionsKeyed[captchaId];
187
+ return {
188
+ captchaId,
189
+ captchaContentId,
190
+ solved: !!solved,
191
+ salt,
192
+ items,
193
+ target,
194
+ solution: solved && solution ? solution.solution : [],
195
+ };
196
+ }),
197
+ };
198
+ }
199
+ throw new ProsopoDBError("DATABASE.DATASET_GET_FAILED", {
200
+ context: { failedFuncName: this.getDataset.name, datasetId },
201
+ });
202
+ }
203
+ async getRandomCaptcha(solved, datasetId, size) {
204
+ if (!isHex(datasetId)) {
205
+ throw new ProsopoDBError("DATABASE.INVALID_HASH", {
206
+ context: { failedFuncName: this.getRandomCaptcha.name, datasetId },
207
+ });
208
+ }
209
+ const sampleSize = size ? Math.abs(Math.trunc(size)) : 1;
210
+ const cursor = this.tables?.captcha.aggregate([
211
+ { $match: { datasetId, solved } },
212
+ { $sample: { size: sampleSize } },
213
+ {
214
+ $project: {
215
+ datasetId: 1,
216
+ datasetContentId: 1,
217
+ captchaId: 1,
218
+ captchaContentId: 1,
219
+ items: 1,
220
+ target: 1,
221
+ },
222
+ },
223
+ ]);
224
+ const docs = await cursor;
225
+ if (docs?.length) {
226
+ return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
227
+ }
228
+ throw new ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
229
+ context: {
230
+ failedFuncName: this.getRandomCaptcha.name,
231
+ solved,
232
+ datasetId,
233
+ size,
234
+ },
235
+ });
236
+ }
237
+ async getCaptchaById(captchaId) {
238
+ const cursor = this.tables?.captcha
239
+ .find({ captchaId: { $in: captchaId } })
240
+ .lean();
241
+ const docs = await cursor;
242
+ if (docs?.length) {
243
+ return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
244
+ }
245
+ throw new ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
246
+ context: { failedFuncName: this.getCaptchaById.name, captchaId },
247
+ });
248
+ }
249
+ async updateCaptcha(captcha, datasetId) {
250
+ if (!isHex(datasetId)) {
251
+ throw new ProsopoDBError("DATABASE.INVALID_HASH", {
252
+ context: { failedFuncName: this.updateCaptcha.name, datasetId },
253
+ });
254
+ }
255
+ try {
256
+ await this.tables?.captcha.updateOne({ datasetId }, { $set: captcha }, { upsert: false });
257
+ }
258
+ catch (err) {
259
+ throw new ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
260
+ context: { failedFuncName: this.getDatasetDetails.name, error: err },
261
+ });
262
+ }
263
+ }
264
+ async removeCaptchas(captchaIds) {
265
+ await this.tables?.captcha.deleteMany({ captchaId: { $in: captchaIds } });
266
+ }
267
+ async getDatasetDetails(datasetId) {
268
+ if (!isHex(datasetId)) {
269
+ throw new ProsopoDBError("DATABASE.INVALID_HASH", {
270
+ context: { failedFuncName: this.getDatasetDetails.name, datasetId },
271
+ });
272
+ }
273
+ const doc = await this.tables?.dataset
274
+ .findOne({ datasetId })
275
+ .lean();
276
+ if (doc) {
277
+ return doc;
278
+ }
279
+ throw new ProsopoDBError("DATABASE.DATASET_GET_FAILED", {
280
+ context: {
281
+ failedFuncName: this.getDatasetDetails.name,
282
+ datasetId,
283
+ },
284
+ });
285
+ }
286
+ async storeDappUserSolution(captchas, commit) {
287
+ const commitmentRecord = UserCommitmentSchema.parse({
288
+ ...commit,
289
+ lastUpdatedTimestamp: Date.now(),
290
+ });
291
+ if (captchas.length) {
292
+ await this.tables?.commitment.updateOne({
293
+ id: commit.id,
294
+ }, commitmentRecord, { upsert: true });
295
+ const ops = captchas.map((captcha) => ({
296
+ updateOne: {
297
+ filter: { commitmentId: commit.id, captchaId: captcha.captchaId },
298
+ update: {
299
+ $set: {
300
+ captchaId: captcha.captchaId,
301
+ captchaContentId: captcha.captchaContentId,
302
+ salt: captcha.salt,
303
+ solution: captcha.solution,
304
+ commitmentId: commit.id,
305
+ processed: false,
306
+ },
307
+ },
308
+ upsert: true,
309
+ },
310
+ }));
311
+ await this.tables?.usersolution.bulkWrite(ops);
312
+ }
313
+ }
314
+ async storePowCaptchaRecord(challenge, components, difficulty, providerSignature, ipAddress, headers, serverChecked = false, userSubmitted = false, storedStatus = StoredStatusNames.notStored, userSignature) {
315
+ const tables = this.getTables();
316
+ const powCaptchaRecord = {
317
+ challenge,
318
+ ...components,
319
+ ipAddress,
320
+ headers,
321
+ result: { status: CaptchaStatus.pending },
322
+ userSubmitted,
323
+ serverChecked,
324
+ difficulty,
325
+ providerSignature,
326
+ userSignature,
327
+ lastUpdatedTimestamp: Date.now(),
328
+ };
329
+ try {
330
+ await tables.powcaptcha.create(powCaptchaRecord);
331
+ this.logger.info("PowCaptcha record added successfully", {
332
+ challenge,
333
+ userSubmitted,
334
+ serverChecked,
335
+ storedStatus,
336
+ });
337
+ }
338
+ catch (error) {
339
+ this.logger.error("Failed to add PowCaptcha record", {
340
+ error,
341
+ challenge,
342
+ userSubmitted,
343
+ serverChecked,
344
+ storedStatus,
345
+ });
346
+ throw new ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
347
+ context: {
348
+ error,
349
+ challenge,
350
+ userSubmitted,
351
+ serverChecked,
352
+ storedStatus,
353
+ },
354
+ logger: this.logger,
355
+ });
356
+ }
357
+ }
358
+ async getPowCaptchaRecordByChallenge(challenge) {
359
+ if (!this.tables) {
360
+ throw new ProsopoEnvError("DATABASE.DATABASE_UNDEFINED", {
361
+ context: { failedFuncName: this.getPowCaptchaRecordByChallenge.name },
362
+ logger: this.logger,
363
+ });
364
+ }
365
+ try {
366
+ const record = await this.tables.powcaptcha
367
+ .findOne({ challenge })
368
+ .lean();
369
+ if (record) {
370
+ this.logger.info("PowCaptcha record retrieved successfully", {
371
+ challenge,
372
+ });
373
+ return record;
374
+ }
375
+ this.logger.info("No PowCaptcha record found", { challenge });
376
+ return null;
377
+ }
378
+ catch (error) {
379
+ this.logger.error("Failed to retrieve PowCaptcha record", {
380
+ error,
381
+ challenge,
382
+ });
383
+ throw new ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
384
+ context: { error, challenge },
385
+ logger: this.logger,
386
+ });
387
+ }
388
+ }
389
+ async updatePowCaptchaRecord(challenge, result, serverChecked = false, userSubmitted = false, userSignature) {
390
+ const tables = this.getTables();
391
+ const timestamp = Date.now();
392
+ const update = {
393
+ result,
394
+ serverChecked,
395
+ userSubmitted,
396
+ userSignature,
397
+ lastUpdatedTimestamp: timestamp,
398
+ };
399
+ try {
400
+ const updateResult = await tables.powcaptcha.updateOne({ challenge }, {
401
+ $set: update,
402
+ });
403
+ if (updateResult.matchedCount === 0) {
404
+ this.logger.info("No PowCaptcha record found to update", {
405
+ challenge,
406
+ ...update,
407
+ });
408
+ throw new ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
409
+ context: {
410
+ challenge,
411
+ ...update,
412
+ },
413
+ logger: this.logger,
414
+ });
415
+ }
416
+ this.logger.info("PowCaptcha record updated successfully", {
417
+ challenge,
418
+ ...update,
419
+ });
420
+ }
421
+ catch (error) {
422
+ this.logger.error("Failed to update PowCaptcha record", {
423
+ error,
424
+ challenge,
425
+ ...update,
426
+ });
427
+ throw new ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
428
+ context: {
429
+ error,
430
+ challenge,
431
+ ...update,
432
+ },
433
+ logger: this.logger,
434
+ });
435
+ }
436
+ }
437
+ async getProcessedDappUserSolutions() {
438
+ const docs = await this.tables?.usersolution
439
+ .find({ processed: true })
440
+ .lean();
441
+ return docs || [];
442
+ }
443
+ async getProcessedDappUserCommitments() {
444
+ const docs = await this.tables?.commitment
445
+ .find({ processed: true })
446
+ .lean();
447
+ return docs || [];
448
+ }
449
+ async getCheckedDappUserCommitments() {
450
+ const docs = await this.tables?.commitment
451
+ .find({ [StoredStatusNames.serverChecked]: true })
452
+ .lean();
453
+ return docs || [];
454
+ }
455
+ async getUnstoredDappUserCommitments() {
456
+ const docs = await this.tables?.commitment
457
+ .find({
458
+ $or: [
459
+ { storedStatus: { $ne: StoredStatusNames.stored } },
460
+ { storedStatus: { $exists: false } },
461
+ ],
462
+ })
463
+ .lean();
464
+ return docs || [];
465
+ }
466
+ async markDappUserCommitmentsStored(commitmentIds) {
467
+ const updateDoc = {
468
+ storedAtTimestamp: Date.now(),
469
+ };
470
+ await this.tables?.commitment.updateMany({ id: { $in: commitmentIds } }, { $set: updateDoc }, { upsert: false });
471
+ }
472
+ async markDappUserCommitmentsChecked(commitmentIds) {
473
+ const updateDoc = {
474
+ [StoredStatusNames.serverChecked]: true,
475
+ lastUpdatedTimestamp: Date.now(),
476
+ };
477
+ await this.tables?.commitment.updateMany({ id: { $in: commitmentIds } }, { $set: updateDoc }, { upsert: false });
478
+ }
479
+ async getUnstoredDappUserPoWCommitments() {
480
+ const docs = await this.tables?.powcaptcha
481
+ .find({
482
+ $or: [
483
+ { storedStatus: { $ne: StoredStatusNames.stored } },
484
+ { storedStatus: { $exists: false } },
485
+ ],
486
+ })
487
+ .lean();
488
+ return docs || [];
489
+ }
490
+ async markDappUserPoWCommitmentsStored(challenges) {
491
+ const updateDoc = {
492
+ storedAtTimestamp: Date.now(),
493
+ };
494
+ await this.tables?.powcaptcha.updateMany({ challenge: { $in: challenges } }, { $set: updateDoc }, { upsert: false });
495
+ }
496
+ async markDappUserPoWCommitmentsChecked(challenges) {
497
+ const updateDoc = {
498
+ [StoredStatusNames.serverChecked]: true,
499
+ lastUpdatedTimestamp: Date.now(),
500
+ };
501
+ await this.tables?.powcaptcha.updateMany({ challenge: { $in: challenges } }, {
502
+ $set: updateDoc,
503
+ }, { upsert: false });
504
+ }
505
+ async storeSessionRecord(sessionRecord) {
506
+ try {
507
+ await this.tables.session.create(sessionRecord);
508
+ }
509
+ catch (err) {
510
+ throw new ProsopoDBError("DATABASE.SESSION_STORE_FAILED", {
511
+ context: { error: err, sessionId: sessionRecord.sessionId },
512
+ logger: this.logger,
513
+ });
514
+ }
515
+ }
516
+ async checkAndRemoveSession(sessionId) {
517
+ try {
518
+ const session = await this.tables.session
519
+ .findOneAndDelete({ sessionId })
520
+ .lean();
521
+ return session || undefined;
522
+ }
523
+ catch (err) {
524
+ throw new ProsopoDBError("DATABASE.SESSION_CHECK_REMOVE_FAILED", {
525
+ context: { error: err, sessionId },
526
+ logger: this.logger,
527
+ });
528
+ }
529
+ }
530
+ async removeProcessedDappUserSolutions(commitmentIds) {
531
+ return this.tables?.usersolution.deleteMany({
532
+ processed: true,
533
+ commitmentId: { $in: commitmentIds },
534
+ });
535
+ }
536
+ async removeProcessedDappUserCommitments(commitmentIds) {
537
+ return this.tables?.commitment.deleteMany({
538
+ processed: true,
539
+ id: { $in: commitmentIds },
540
+ });
541
+ }
542
+ async storeDappUserPending(userAccount, requestHash, salt, deadlineTimestamp, requestedAtTimestamp, ipAddress) {
543
+ if (!isHex(requestHash)) {
544
+ throw new ProsopoDBError("DATABASE.INVALID_HASH", {
545
+ context: {
546
+ failedFuncName: this.storeDappUserPending.name,
547
+ requestHash,
548
+ },
549
+ });
550
+ }
551
+ const pendingRecord = {
552
+ accountId: userAccount,
553
+ pending: true,
554
+ salt,
555
+ requestHash,
556
+ deadlineTimestamp,
557
+ requestedAtTimestamp,
558
+ ipAddress,
559
+ };
560
+ await this.tables?.pending.updateOne({ requestHash: requestHash }, { $set: pendingRecord }, { upsert: true });
561
+ }
562
+ async getDappUserPending(requestHash) {
563
+ if (!isHex(requestHash)) {
564
+ throw new ProsopoEnvError("DATABASE.INVALID_HASH", {
565
+ context: { failedFuncName: this.getDappUserPending.name, requestHash },
566
+ });
567
+ }
568
+ const doc = await this.tables?.pending
569
+ .findOne({ requestHash: requestHash })
570
+ .lean();
571
+ if (doc) {
572
+ return doc;
573
+ }
574
+ throw new ProsopoEnvError("DATABASE.PENDING_RECORD_NOT_FOUND", {
575
+ context: { failedFuncName: this.getDappUserPending.name, requestHash },
576
+ });
577
+ }
578
+ async updateDappUserPendingStatus(requestHash) {
579
+ if (!isHex(requestHash)) {
580
+ throw new ProsopoEnvError("DATABASE.INVALID_HASH", {
581
+ context: {
582
+ failedFuncName: this.updateDappUserPendingStatus.name,
583
+ requestHash,
584
+ },
585
+ });
586
+ }
587
+ await this.tables?.pending.updateOne({ requestHash: requestHash }, {
588
+ $set: {
589
+ [CaptchaStatus.pending]: false,
590
+ },
591
+ }, { upsert: true });
592
+ }
593
+ async getAllCaptchasByDatasetId(datasetId, state) {
594
+ const cursor = this.tables?.captcha
595
+ .find({
596
+ datasetId,
597
+ solved: state === CaptchaStates.Solved,
598
+ })
599
+ .lean();
600
+ const docs = await cursor;
601
+ if (docs) {
602
+ return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
603
+ }
604
+ throw new ProsopoEnvError("DATABASE.CAPTCHA_GET_FAILED");
605
+ }
606
+ async getAllDappUserSolutions(captchaId) {
607
+ const cursor = this.tables?.usersolution
608
+ ?.find({ captchaId: { $in: captchaId } })
609
+ .lean();
610
+ const docs = await cursor;
611
+ if (docs) {
612
+ return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
613
+ }
614
+ throw new ProsopoEnvError("DATABASE.SOLUTION_GET_FAILED");
615
+ }
616
+ async getDatasetIdWithSolvedCaptchasOfSizeN(solvedCaptchaCount) {
617
+ const cursor = this.tables?.solution.aggregate([
618
+ {
619
+ $match: {},
620
+ },
621
+ {
622
+ $group: {
623
+ _id: "$datasetId",
624
+ count: { $sum: 1 },
625
+ },
626
+ },
627
+ {
628
+ $match: {
629
+ count: { $gte: solvedCaptchaCount },
630
+ },
631
+ },
632
+ {
633
+ $sample: { size: 1 },
634
+ },
635
+ ]);
636
+ const docs = await cursor;
637
+ if (docs?.length) {
638
+ return docs[0]._id;
639
+ }
640
+ throw new ProsopoDBError("DATABASE.DATASET_WITH_SOLUTIONS_GET_FAILED");
641
+ }
642
+ async getRandomSolvedCaptchasFromSingleDataset(datasetId, size) {
643
+ if (!isHex(datasetId)) {
644
+ throw new ProsopoDBError("DATABASE.INVALID_HASH", {
645
+ context: {
646
+ failedFuncName: this.getRandomSolvedCaptchasFromSingleDataset.name,
647
+ datasetId,
648
+ },
649
+ });
650
+ }
651
+ const sampleSize = size ? Math.abs(Math.trunc(size)) : 1;
652
+ const cursor = this.tables?.solution.aggregate([
653
+ { $match: { datasetId } },
654
+ { $sample: { size: sampleSize } },
655
+ {
656
+ $project: {
657
+ captchaId: 1,
658
+ captchaContentId: 1,
659
+ solution: 1,
660
+ },
661
+ },
662
+ ]);
663
+ const docs = await cursor;
664
+ if (docs?.length) {
665
+ return docs;
666
+ }
667
+ throw new ProsopoDBError("DATABASE.SOLUTION_GET_FAILED", {
668
+ context: {
669
+ failedFuncName: this.getRandomSolvedCaptchasFromSingleDataset.name,
670
+ datasetId,
671
+ size,
672
+ },
673
+ });
674
+ }
675
+ async getDappUserSolutionById(commitmentId) {
676
+ const cursor = this.tables?.usersolution
677
+ ?.findOne({
678
+ commitmentId: commitmentId,
679
+ }, { projection: { _id: 0 } })
680
+ .lean();
681
+ const doc = await cursor;
682
+ if (doc) {
683
+ return doc;
684
+ }
685
+ throw new ProsopoDBError("DATABASE.SOLUTION_GET_FAILED", {
686
+ context: { failedFuncName: this.getCaptchaById.name, commitmentId },
687
+ });
688
+ }
689
+ async getDappUserCommitmentById(commitmentId) {
690
+ const commitmentCursor = this.tables?.commitment
691
+ ?.findOne({ id: commitmentId })
692
+ .lean();
693
+ const doc = await commitmentCursor;
694
+ return doc ? doc : undefined;
695
+ }
696
+ async getDappUserCommitmentByAccount(userAccount, dappAccount) {
697
+ const docs = await this.tables?.commitment
698
+ ?.find({ userAccount, dappAccount }, { _id: 0 }, { sort: { _id: -1 } })
699
+ .lean();
700
+ return docs ? docs : [];
701
+ }
702
+ async approveDappUserCommitment(commitmentId) {
703
+ try {
704
+ const result = { status: CaptchaStatus.approved };
705
+ const updateDoc = {
706
+ result,
707
+ lastUpdatedTimestamp: Date.now(),
708
+ };
709
+ await this.tables?.commitment
710
+ ?.findOneAndUpdate({ id: commitmentId }, { $set: updateDoc }, { upsert: false })
711
+ .lean();
712
+ }
713
+ catch (err) {
714
+ throw new ProsopoDBError("DATABASE.SOLUTION_APPROVE_FAILED", {
715
+ context: { error: err, commitmentId },
716
+ });
717
+ }
718
+ }
719
+ async disapproveDappUserCommitment(commitmentId, reason) {
720
+ try {
721
+ const updateDoc = {
722
+ result: { status: CaptchaStatus.disapproved, reason },
723
+ lastUpdatedTimestamp: Date.now(),
724
+ };
725
+ await this.tables?.commitment
726
+ ?.findOneAndUpdate({ id: commitmentId }, { $set: updateDoc }, { upsert: false })
727
+ .lean();
728
+ }
729
+ catch (err) {
730
+ throw new ProsopoDBError("DATABASE.SOLUTION_APPROVE_FAILED", {
731
+ context: { error: err, commitmentId },
732
+ });
733
+ }
734
+ }
735
+ async flagProcessedDappUserSolutions(captchaIds) {
736
+ try {
737
+ await this.tables?.usersolution
738
+ ?.updateMany({ captchaId: { $in: captchaIds } }, { $set: { processed: true } }, { upsert: false })
739
+ .lean();
740
+ }
741
+ catch (err) {
742
+ throw new ProsopoDBError("DATABASE.SOLUTION_FLAG_FAILED", {
743
+ context: { error: err, captchaIds },
744
+ });
745
+ }
746
+ }
747
+ async flagProcessedDappUserCommitments(commitmentIds) {
748
+ try {
749
+ const distinctCommitmentIds = [...new Set(commitmentIds)];
750
+ await this.tables?.commitment
751
+ ?.updateMany({ id: { $in: distinctCommitmentIds } }, { $set: { processed: true } }, { upsert: false })
752
+ .lean();
753
+ }
754
+ catch (err) {
755
+ throw new ProsopoDBError("DATABASE.COMMITMENT_FLAG_FAILED", {
756
+ context: { error: err, commitmentIds },
757
+ });
758
+ }
759
+ }
760
+ async getScheduledTaskStatus(taskId, status) {
761
+ const cursor = await this.tables?.scheduler
762
+ ?.findOne({ taskId: taskId, status: status })
763
+ .lean();
764
+ return cursor ? cursor : undefined;
765
+ }
766
+ async getLastScheduledTaskStatus(task, status) {
767
+ const lookup = { processName: task };
768
+ if (status) {
769
+ lookup.status = status;
770
+ }
771
+ const cursor = await this.tables?.scheduler
772
+ ?.findOne(lookup)
773
+ .sort({ datetime: -1 })
774
+ .limit(1)
775
+ .lean();
776
+ return cursor ? cursor : undefined;
777
+ }
778
+ async createScheduledTaskStatus(taskName, status) {
779
+ const now = new Date().getTime();
780
+ const doc = ScheduledTaskSchema.parse({
781
+ processName: taskName,
782
+ datetime: now,
783
+ status,
784
+ });
785
+ const taskRecord = await this.tables?.scheduler.create(doc);
786
+ return taskRecord._id;
787
+ }
788
+ async updateScheduledTaskStatus(taskId, status, result) {
789
+ const update = {
790
+ status,
791
+ updated: new Date().getTime(),
792
+ ...(result && { result }),
793
+ };
794
+ await this.tables?.scheduler.updateOne({ _id: taskId }, { $set: update }, {
795
+ upsert: false,
796
+ });
797
+ }
798
+ async updateClientRecords(clientRecords) {
799
+ const ops = clientRecords.map((record) => {
800
+ const clientRecord = {
801
+ account: record.account,
802
+ settings: record.settings,
803
+ };
804
+ return {
805
+ updateOne: {
806
+ filter: { account: record.account },
807
+ update: {
808
+ $set: clientRecord,
809
+ },
810
+ upsert: true,
811
+ },
812
+ };
813
+ });
814
+ await this.tables?.client.bulkWrite(ops);
815
+ }
816
+ async getClientRecord(account) {
817
+ const doc = await this.tables?.client
818
+ .findOne({ account })
819
+ .lean();
820
+ return doc ? doc : undefined;
821
+ }
822
+ }
823
+ //# sourceMappingURL=provider.js.map