@prosopo/database 3.0.8 → 3.0.10
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/CHANGELOG.md +29 -0
- package/dist/base/index.js +6 -3
- package/dist/base/mongo.js +122 -117
- package/dist/base/mongoMemory.js +24 -23
- package/dist/databases/captcha.js +124 -123
- package/dist/databases/client.js +58 -48
- package/dist/databases/index.js +12 -10
- package/dist/databases/provider.js +1218 -1013
- package/dist/index.js +15 -3
- package/package.json +14 -10
- package/vite.cjs.config.ts +4 -1
- package/vite.esm.config.ts +20 -0
- package/dist/base/index.d.ts +0 -3
- package/dist/base/index.d.ts.map +0 -1
- package/dist/base/index.js.map +0 -1
- package/dist/base/mongo.d.ts +0 -18
- package/dist/base/mongo.d.ts.map +0 -1
- package/dist/base/mongo.js.map +0 -1
- package/dist/base/mongoMemory.d.ts +0 -11
- package/dist/base/mongoMemory.d.ts.map +0 -1
- package/dist/base/mongoMemory.js.map +0 -1
- package/dist/databases/captcha.d.ts +0 -23
- package/dist/databases/captcha.d.ts.map +0 -1
- package/dist/databases/captcha.js.map +0 -1
- package/dist/databases/client.d.ts +0 -12
- package/dist/databases/client.d.ts.map +0 -1
- package/dist/databases/client.js.map +0 -1
- package/dist/databases/index.d.ts +0 -16
- package/dist/databases/index.d.ts.map +0 -1
- package/dist/databases/index.js.map +0 -1
- package/dist/databases/provider.d.ts +0 -102
- package/dist/databases/provider.d.ts.map +0 -1
- package/dist/databases/provider.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -1,1073 +1,1278 @@
|
|
|
1
1
|
import { isHex } from "@polkadot/util/is";
|
|
2
2
|
import { ProsopoDBError } from "@prosopo/common";
|
|
3
|
-
import {
|
|
4
|
-
import { CaptchaRecordSchema,
|
|
5
|
-
import { createRedisAccessRulesIndex, createRedisAccessRulesStorage
|
|
3
|
+
import { DatasetWithIdsAndTreeSchema, StoredStatusNames, CaptchaStatus, ApiParams, CaptchaStates } from "@prosopo/types";
|
|
4
|
+
import { CaptchaRecordSchema, PoWCaptchaRecordSchema, DatasetRecordSchema, SolutionRecordSchema, UserCommitmentRecordSchema, UserSolutionRecordSchema, PendingRecordSchema, ScheduledTaskRecordSchema, ClientRecordSchema, FrictionlessTokenRecordSchema, SessionRecordSchema, DetectorRecordSchema, UserCommitmentSchema, ScheduledTaskSchema } from "@prosopo/types-database";
|
|
5
|
+
import { createRedisAccessRulesIndex, createRedisAccessRulesStorage } from "@prosopo/user-access-policy";
|
|
6
6
|
import { createClient } from "redis";
|
|
7
7
|
import { MongoDatabase } from "../base/mongo.js";
|
|
8
|
-
var TableNames
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
})(TableNames ||
|
|
8
|
+
var TableNames = /* @__PURE__ */ ((TableNames2) => {
|
|
9
|
+
TableNames2["captcha"] = "captcha";
|
|
10
|
+
TableNames2["dataset"] = "dataset";
|
|
11
|
+
TableNames2["solution"] = "solution";
|
|
12
|
+
TableNames2["commitment"] = "commitment";
|
|
13
|
+
TableNames2["usersolution"] = "usersolution";
|
|
14
|
+
TableNames2["pending"] = "pending";
|
|
15
|
+
TableNames2["scheduler"] = "scheduler";
|
|
16
|
+
TableNames2["powcaptcha"] = "powcaptcha";
|
|
17
|
+
TableNames2["client"] = "client";
|
|
18
|
+
TableNames2["frictionlessToken"] = "frictionlessToken";
|
|
19
|
+
TableNames2["session"] = "session";
|
|
20
|
+
TableNames2["detector"] = "detector";
|
|
21
|
+
return TableNames2;
|
|
22
|
+
})(TableNames || {});
|
|
23
23
|
const PROVIDER_TABLES = [
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
24
|
+
{
|
|
25
|
+
collectionName: "captcha",
|
|
26
|
+
modelName: "Captcha",
|
|
27
|
+
schema: CaptchaRecordSchema
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
collectionName: "powcaptcha",
|
|
31
|
+
modelName: "PowCaptcha",
|
|
32
|
+
schema: PoWCaptchaRecordSchema
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
collectionName: "dataset",
|
|
36
|
+
modelName: "Dataset",
|
|
37
|
+
schema: DatasetRecordSchema
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
collectionName: "solution",
|
|
41
|
+
modelName: "Solution",
|
|
42
|
+
schema: SolutionRecordSchema
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
collectionName: "commitment",
|
|
46
|
+
modelName: "UserCommitment",
|
|
47
|
+
schema: UserCommitmentRecordSchema
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
collectionName: "usersolution",
|
|
51
|
+
modelName: "UserSolution",
|
|
52
|
+
schema: UserSolutionRecordSchema
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
collectionName: "pending",
|
|
56
|
+
modelName: "Pending",
|
|
57
|
+
schema: PendingRecordSchema
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
collectionName: "scheduler",
|
|
61
|
+
modelName: "Scheduler",
|
|
62
|
+
schema: ScheduledTaskRecordSchema
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
collectionName: "client",
|
|
66
|
+
modelName: "Client",
|
|
67
|
+
schema: ClientRecordSchema
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
collectionName: "frictionlessToken",
|
|
71
|
+
modelName: "FrictionlessToken",
|
|
72
|
+
schema: FrictionlessTokenRecordSchema
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
collectionName: "session",
|
|
76
|
+
modelName: "Session",
|
|
77
|
+
schema: SessionRecordSchema
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
collectionName: "detector",
|
|
81
|
+
modelName: "Detector",
|
|
82
|
+
schema: DetectorRecordSchema
|
|
83
|
+
}
|
|
84
84
|
];
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
class ProviderDatabase extends MongoDatabase {
|
|
86
|
+
constructor(options) {
|
|
87
|
+
super(
|
|
88
|
+
options.mongo.url,
|
|
89
|
+
options.mongo.dbname,
|
|
90
|
+
options.mongo.authSource,
|
|
91
|
+
options.logger
|
|
92
|
+
);
|
|
93
|
+
this.options = options;
|
|
94
|
+
this.tables = {};
|
|
95
|
+
this.tables = {};
|
|
96
|
+
this.userAccessRulesStorage = null;
|
|
97
|
+
}
|
|
98
|
+
async connect() {
|
|
99
|
+
await super.connect();
|
|
100
|
+
this.loadTables();
|
|
101
|
+
await this.setupRedis();
|
|
102
|
+
}
|
|
103
|
+
async setupRedis() {
|
|
104
|
+
const redisClient = await this.createRedisClient();
|
|
105
|
+
await createRedisAccessRulesIndex(redisClient);
|
|
106
|
+
this.userAccessRulesStorage = createRedisAccessRulesStorage(
|
|
107
|
+
redisClient,
|
|
108
|
+
this.logger
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
async createRedisClient() {
|
|
112
|
+
return await createClient({
|
|
113
|
+
url: this.options.redis?.url,
|
|
114
|
+
password: this.options.redis?.password
|
|
115
|
+
}).on("error", (error) => {
|
|
116
|
+
this.logger.error(() => ({
|
|
117
|
+
err: error,
|
|
118
|
+
msg: "Redis client error"
|
|
119
|
+
}));
|
|
120
|
+
}).connect();
|
|
121
|
+
}
|
|
122
|
+
loadTables() {
|
|
123
|
+
const tables = {};
|
|
124
|
+
PROVIDER_TABLES.map(({ collectionName, modelName, schema }) => {
|
|
125
|
+
if (this.connection) {
|
|
126
|
+
tables[collectionName] = this.connection.model(modelName, schema);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
this.tables = tables;
|
|
130
|
+
}
|
|
131
|
+
getTables() {
|
|
132
|
+
if (!this.tables) {
|
|
133
|
+
throw new ProsopoDBError("DATABASE.TABLES_UNDEFINED", {
|
|
134
|
+
context: { failedFuncName: this.getTables.name },
|
|
135
|
+
logger: this.logger
|
|
136
|
+
});
|
|
92
137
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
138
|
+
return this.tables;
|
|
139
|
+
}
|
|
140
|
+
getUserAccessRulesStorage() {
|
|
141
|
+
if (null === this.userAccessRulesStorage) {
|
|
142
|
+
throw new ProsopoDBError("DATABASE.USER_ACCESS_RULES_STORAGE_UNDEFINED");
|
|
97
143
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
144
|
+
return this.userAccessRulesStorage;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* @description Load a dataset to the database
|
|
148
|
+
* @param {Dataset} dataset
|
|
149
|
+
*/
|
|
150
|
+
async storeDataset(dataset) {
|
|
151
|
+
try {
|
|
152
|
+
this.logger.debug(() => ({
|
|
153
|
+
data: { datasetId: dataset.datasetId },
|
|
154
|
+
msg: "Storing dataset in database"
|
|
155
|
+
}));
|
|
156
|
+
const parsedDataset = DatasetWithIdsAndTreeSchema.parse(dataset);
|
|
157
|
+
const datasetDoc = {
|
|
158
|
+
datasetId: parsedDataset.datasetId,
|
|
159
|
+
datasetContentId: parsedDataset.datasetContentId,
|
|
160
|
+
format: parsedDataset.format,
|
|
161
|
+
contentTree: parsedDataset.contentTree,
|
|
162
|
+
solutionTree: parsedDataset.solutionTree
|
|
163
|
+
};
|
|
164
|
+
const filter = {
|
|
165
|
+
datasetId: parsedDataset.datasetId
|
|
166
|
+
};
|
|
167
|
+
await this.tables.dataset?.updateOne(
|
|
168
|
+
filter,
|
|
169
|
+
{ $set: datasetDoc },
|
|
170
|
+
{ upsert: true }
|
|
171
|
+
);
|
|
172
|
+
const captchaDocs = parsedDataset.captchas.map(
|
|
173
|
+
({ solution, ...captcha }, index) => ({
|
|
174
|
+
...captcha,
|
|
175
|
+
datasetId: parsedDataset.datasetId,
|
|
176
|
+
datasetContentId: parsedDataset.datasetContentId,
|
|
177
|
+
index,
|
|
178
|
+
solved: !!solution?.length
|
|
113
179
|
})
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
getTables() {
|
|
126
|
-
if (!this.tables) {
|
|
127
|
-
throw new ProsopoDBError("DATABASE.TABLES_UNDEFINED", {
|
|
128
|
-
context: { failedFuncName: this.getTables.name },
|
|
129
|
-
logger: this.logger,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
return this.tables;
|
|
133
|
-
}
|
|
134
|
-
getUserAccessRulesStorage() {
|
|
135
|
-
if (null === this.userAccessRulesStorage) {
|
|
136
|
-
throw new ProsopoDBError("DATABASE.USER_ACCESS_RULES_STORAGE_UNDEFINED");
|
|
137
|
-
}
|
|
138
|
-
return this.userAccessRulesStorage;
|
|
139
|
-
}
|
|
140
|
-
async storeDataset(dataset) {
|
|
141
|
-
try {
|
|
142
|
-
this.logger.debug(() => ({
|
|
143
|
-
data: { datasetId: dataset.datasetId },
|
|
144
|
-
msg: "Storing dataset in database",
|
|
145
|
-
}));
|
|
146
|
-
const parsedDataset = DatasetWithIdsAndTreeSchema.parse(dataset);
|
|
147
|
-
const datasetDoc = {
|
|
148
|
-
datasetId: parsedDataset.datasetId,
|
|
149
|
-
datasetContentId: parsedDataset.datasetContentId,
|
|
150
|
-
format: parsedDataset.format,
|
|
151
|
-
contentTree: parsedDataset.contentTree,
|
|
152
|
-
solutionTree: parsedDataset.solutionTree,
|
|
153
|
-
};
|
|
154
|
-
const filter = {
|
|
155
|
-
datasetId: parsedDataset.datasetId,
|
|
156
|
-
};
|
|
157
|
-
await this.tables.dataset?.updateOne(filter, { $set: datasetDoc }, { upsert: true });
|
|
158
|
-
const captchaDocs = parsedDataset.captchas.map(({ solution, ...captcha }, index) => ({
|
|
159
|
-
...captcha,
|
|
160
|
-
datasetId: parsedDataset.datasetId,
|
|
161
|
-
datasetContentId: parsedDataset.datasetContentId,
|
|
162
|
-
index,
|
|
163
|
-
solved: !!solution?.length,
|
|
164
|
-
}));
|
|
165
|
-
this.logger.debug(() => ({
|
|
166
|
-
msg: "Inserting captcha records",
|
|
167
|
-
}));
|
|
168
|
-
if (captchaDocs.length) {
|
|
169
|
-
await this.tables?.captcha.bulkWrite(captchaDocs.map((captchaDoc) => ({
|
|
170
|
-
updateOne: {
|
|
171
|
-
filter: { captchaId: captchaDoc.captchaId },
|
|
172
|
-
update: { $set: captchaDoc },
|
|
173
|
-
upsert: true,
|
|
174
|
-
},
|
|
175
|
-
})));
|
|
180
|
+
);
|
|
181
|
+
this.logger.debug(() => ({
|
|
182
|
+
msg: "Inserting captcha records"
|
|
183
|
+
}));
|
|
184
|
+
if (captchaDocs.length) {
|
|
185
|
+
await this.tables?.captcha.bulkWrite(
|
|
186
|
+
captchaDocs.map((captchaDoc) => ({
|
|
187
|
+
updateOne: {
|
|
188
|
+
filter: { captchaId: captchaDoc.captchaId },
|
|
189
|
+
update: { $set: captchaDoc },
|
|
190
|
+
upsert: true
|
|
176
191
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
192
|
+
}))
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const captchaSolutionDocs = parsedDataset.captchas.filter(({ solution }) => solution?.length).map((captcha) => ({
|
|
196
|
+
captchaId: captcha.captchaId,
|
|
197
|
+
captchaContentId: captcha.captchaContentId,
|
|
198
|
+
solution: captcha.solution,
|
|
199
|
+
salt: captcha.salt,
|
|
200
|
+
datasetId: parsedDataset.datasetId,
|
|
201
|
+
datasetContentId: parsedDataset.datasetContentId
|
|
202
|
+
}));
|
|
203
|
+
this.logger.debug(() => ({
|
|
204
|
+
msg: "Inserting solution records"
|
|
205
|
+
}));
|
|
206
|
+
if (captchaSolutionDocs.length) {
|
|
207
|
+
await this.tables?.solution.bulkWrite(
|
|
208
|
+
captchaSolutionDocs.map((captchaSolutionDoc) => ({
|
|
209
|
+
updateOne: {
|
|
210
|
+
filter: { captchaId: captchaSolutionDoc.captchaId },
|
|
211
|
+
update: { $set: captchaSolutionDoc },
|
|
212
|
+
upsert: true
|
|
198
213
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
async getSolutions(datasetId) {
|
|
211
|
-
const filter = { datasetId };
|
|
212
|
-
const docs = await this.tables?.solution
|
|
213
|
-
.find(filter)
|
|
214
|
-
.lean();
|
|
215
|
-
return docs ? docs : [];
|
|
216
|
-
}
|
|
217
|
-
async getSolutionByCaptchaId(captchaId) {
|
|
218
|
-
const filter = { captchaId };
|
|
219
|
-
const doc = await this.tables?.solution
|
|
220
|
-
.findOne(filter)
|
|
221
|
-
.lean();
|
|
222
|
-
return doc || null;
|
|
214
|
+
}))
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
this.logger.debug(() => ({
|
|
218
|
+
msg: "Dataset stored in database"
|
|
219
|
+
}));
|
|
220
|
+
} catch (err) {
|
|
221
|
+
throw new ProsopoDBError("DATABASE.DATASET_LOAD_FAILED", {
|
|
222
|
+
context: { failedFuncName: this.storeDataset.name, error: err },
|
|
223
|
+
logger: this.logger
|
|
224
|
+
});
|
|
223
225
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
datasetContentId: 1,
|
|
276
|
-
captchaId: 1,
|
|
277
|
-
captchaContentId: 1,
|
|
278
|
-
items: 1,
|
|
279
|
-
target: 1,
|
|
280
|
-
},
|
|
281
|
-
},
|
|
282
|
-
]);
|
|
283
|
-
const docs = await cursor;
|
|
284
|
-
if (docs?.length) {
|
|
285
|
-
return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
|
|
286
|
-
}
|
|
287
|
-
throw new ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
|
|
288
|
-
context: {
|
|
289
|
-
failedFuncName: this.getRandomCaptcha.name,
|
|
290
|
-
solved,
|
|
291
|
-
datasetId,
|
|
292
|
-
size,
|
|
293
|
-
},
|
|
294
|
-
});
|
|
226
|
+
}
|
|
227
|
+
/** @description Get solutions for a dataset
|
|
228
|
+
* @param {string} datasetId
|
|
229
|
+
*/
|
|
230
|
+
async getSolutions(datasetId) {
|
|
231
|
+
const filter = { datasetId };
|
|
232
|
+
const docs = await this.tables?.solution.find(filter).lean();
|
|
233
|
+
return docs ? docs : [];
|
|
234
|
+
}
|
|
235
|
+
/** @description Get a solution by captcha id
|
|
236
|
+
* @param {string} captchaId
|
|
237
|
+
*/
|
|
238
|
+
async getSolutionByCaptchaId(captchaId) {
|
|
239
|
+
const filter = { captchaId };
|
|
240
|
+
const doc = await this.tables?.solution.findOne(filter).lean();
|
|
241
|
+
return doc || null;
|
|
242
|
+
}
|
|
243
|
+
/** @description Get a dataset from the database
|
|
244
|
+
* @param {string} datasetId
|
|
245
|
+
*/
|
|
246
|
+
async getDataset(datasetId) {
|
|
247
|
+
const filter = { datasetId };
|
|
248
|
+
const datasetDoc = await this.tables?.dataset.findOne(filter).lean();
|
|
249
|
+
if (datasetDoc) {
|
|
250
|
+
const { datasetContentId, format, contentTree, solutionTree } = datasetDoc;
|
|
251
|
+
const captchas = await this.tables?.captcha.find(filter).lean() || [];
|
|
252
|
+
const solutions = await this.tables?.solution.find(filter).lean() || [];
|
|
253
|
+
const solutionsKeyed = {};
|
|
254
|
+
for (const solution of solutions) {
|
|
255
|
+
solutionsKeyed[solution.captchaId] = solution;
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
datasetId,
|
|
259
|
+
datasetContentId,
|
|
260
|
+
format,
|
|
261
|
+
contentTree: contentTree || [],
|
|
262
|
+
solutionTree: solutionTree || [],
|
|
263
|
+
captchas: captchas.map((captchaDoc) => {
|
|
264
|
+
const { captchaId, captchaContentId, items, target, salt, solved } = captchaDoc;
|
|
265
|
+
const solution = solutionsKeyed[captchaId];
|
|
266
|
+
return {
|
|
267
|
+
captchaId,
|
|
268
|
+
captchaContentId,
|
|
269
|
+
solved: !!solved,
|
|
270
|
+
salt,
|
|
271
|
+
items,
|
|
272
|
+
target,
|
|
273
|
+
solution: solved && solution ? solution.solution : []
|
|
274
|
+
};
|
|
275
|
+
})
|
|
276
|
+
};
|
|
295
277
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
278
|
+
throw new ProsopoDBError("DATABASE.DATASET_GET_FAILED", {
|
|
279
|
+
context: { failedFuncName: this.getDataset.name, datasetId }
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* @description Get random captchas that are solved or not solved
|
|
284
|
+
* @param {boolean} solved `true` when captcha is solved
|
|
285
|
+
* @param {string} datasetId the id of the data set
|
|
286
|
+
* @param {number} size the number of records to be returned
|
|
287
|
+
*/
|
|
288
|
+
async getRandomCaptcha(solved, datasetId, size) {
|
|
289
|
+
if (!isHex(datasetId)) {
|
|
290
|
+
throw new ProsopoDBError("DATABASE.INVALID_HASH", {
|
|
291
|
+
context: { failedFuncName: this.getRandomCaptcha.name, datasetId }
|
|
292
|
+
});
|
|
308
293
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
});
|
|
294
|
+
const sampleSize = size ? Math.abs(Math.trunc(size)) : 1;
|
|
295
|
+
const filter = { datasetId, solved };
|
|
296
|
+
const cursor = this.tables?.captcha.aggregate([
|
|
297
|
+
{ $match: filter },
|
|
298
|
+
{ $sample: { size: sampleSize } },
|
|
299
|
+
{
|
|
300
|
+
$project: {
|
|
301
|
+
datasetId: 1,
|
|
302
|
+
datasetContentId: 1,
|
|
303
|
+
captchaId: 1,
|
|
304
|
+
captchaContentId: 1,
|
|
305
|
+
items: 1,
|
|
306
|
+
target: 1
|
|
323
307
|
}
|
|
308
|
+
}
|
|
309
|
+
]);
|
|
310
|
+
const docs = await cursor;
|
|
311
|
+
if (docs?.length) {
|
|
312
|
+
return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
|
|
324
313
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
314
|
+
throw new ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
|
|
315
|
+
context: {
|
|
316
|
+
failedFuncName: this.getRandomCaptcha.name,
|
|
317
|
+
solved,
|
|
318
|
+
datasetId,
|
|
319
|
+
size
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* @description Get captchas by id
|
|
325
|
+
* @param {string[]} captchaId
|
|
326
|
+
*/
|
|
327
|
+
async getCaptchaById(captchaId) {
|
|
328
|
+
const filter = { captchaId: { $in: captchaId } };
|
|
329
|
+
const cursor = this.tables?.captcha.find(filter).lean();
|
|
330
|
+
const docs = await cursor;
|
|
331
|
+
if (docs?.length) {
|
|
332
|
+
return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
|
|
328
333
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
context: {
|
|
344
|
-
failedFuncName: this.getDatasetDetails.name,
|
|
345
|
-
datasetId,
|
|
346
|
-
},
|
|
347
|
-
});
|
|
334
|
+
throw new ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
|
|
335
|
+
context: { failedFuncName: this.getCaptchaById.name, captchaId }
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* @description Update a captcha
|
|
340
|
+
* @param {Captcha} captcha
|
|
341
|
+
* @param {string} datasetId the id of the data set
|
|
342
|
+
*/
|
|
343
|
+
async updateCaptcha(captcha, datasetId) {
|
|
344
|
+
if (!isHex(datasetId)) {
|
|
345
|
+
throw new ProsopoDBError("DATABASE.INVALID_HASH", {
|
|
346
|
+
context: { failedFuncName: this.updateCaptcha.name, datasetId }
|
|
347
|
+
});
|
|
348
348
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
});
|
|
361
|
-
const ops = captchas.map((captcha) => ({
|
|
362
|
-
updateOne: {
|
|
363
|
-
filter: {
|
|
364
|
-
commitmentId: commit.id,
|
|
365
|
-
captchaId: captcha.captchaId,
|
|
366
|
-
},
|
|
367
|
-
update: {
|
|
368
|
-
$set: {
|
|
369
|
-
captchaId: captcha.captchaId,
|
|
370
|
-
captchaContentId: captcha.captchaContentId,
|
|
371
|
-
salt: captcha.salt,
|
|
372
|
-
solution: captcha.solution,
|
|
373
|
-
commitmentId: commit.id,
|
|
374
|
-
processed: false,
|
|
375
|
-
},
|
|
376
|
-
},
|
|
377
|
-
upsert: true,
|
|
378
|
-
},
|
|
379
|
-
}));
|
|
380
|
-
await this.tables?.usersolution.bulkWrite(ops);
|
|
381
|
-
}
|
|
349
|
+
try {
|
|
350
|
+
const filter = { datasetId };
|
|
351
|
+
await this.tables?.captcha.updateOne(
|
|
352
|
+
filter,
|
|
353
|
+
{ $set: captcha },
|
|
354
|
+
{ upsert: false }
|
|
355
|
+
);
|
|
356
|
+
} catch (err) {
|
|
357
|
+
throw new ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
|
|
358
|
+
context: { failedFuncName: this.getDatasetDetails.name, error: err }
|
|
359
|
+
});
|
|
382
360
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
};
|
|
400
|
-
try {
|
|
401
|
-
await tables.powcaptcha.create(powCaptchaRecord);
|
|
402
|
-
this.logger.info(() => ({
|
|
403
|
-
data: {
|
|
404
|
-
challenge,
|
|
405
|
-
userSubmitted,
|
|
406
|
-
serverChecked,
|
|
407
|
-
storedStatus,
|
|
408
|
-
},
|
|
409
|
-
msg: "PowCaptcha record added successfully",
|
|
410
|
-
}));
|
|
411
|
-
}
|
|
412
|
-
catch (error) {
|
|
413
|
-
const err = new ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
|
|
414
|
-
context: {
|
|
415
|
-
error,
|
|
416
|
-
challenge,
|
|
417
|
-
userSubmitted,
|
|
418
|
-
serverChecked,
|
|
419
|
-
storedStatus,
|
|
420
|
-
},
|
|
421
|
-
logger: this.logger,
|
|
422
|
-
});
|
|
423
|
-
this.logger.error(() => ({
|
|
424
|
-
err: error,
|
|
425
|
-
msg: "Failed to add PowCaptcha record",
|
|
426
|
-
}));
|
|
427
|
-
throw err;
|
|
428
|
-
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* @description Remove captchas
|
|
364
|
+
*/
|
|
365
|
+
async removeCaptchas(captchaIds) {
|
|
366
|
+
const filter = { captchaId: { $in: captchaIds } };
|
|
367
|
+
await this.tables?.captcha.deleteMany(filter);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* @description Get a dataset by Id
|
|
371
|
+
*/
|
|
372
|
+
async getDatasetDetails(datasetId) {
|
|
373
|
+
if (!isHex(datasetId)) {
|
|
374
|
+
throw new ProsopoDBError("DATABASE.INVALID_HASH", {
|
|
375
|
+
context: { failedFuncName: this.getDatasetDetails.name, datasetId }
|
|
376
|
+
});
|
|
429
377
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
logger: this.logger,
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
try {
|
|
438
|
-
const filter = { challenge };
|
|
439
|
-
const record = await this.tables.powcaptcha.findOne(filter).lean();
|
|
440
|
-
if (record) {
|
|
441
|
-
this.logger.info(() => ({
|
|
442
|
-
data: { challenge },
|
|
443
|
-
msg: "PowCaptcha record retrieved successfully",
|
|
444
|
-
}));
|
|
445
|
-
return record;
|
|
446
|
-
}
|
|
447
|
-
this.logger.info(() => ({
|
|
448
|
-
data: { challenge },
|
|
449
|
-
msg: "No PowCaptcha record found",
|
|
450
|
-
}));
|
|
451
|
-
return null;
|
|
452
|
-
}
|
|
453
|
-
catch (error) {
|
|
454
|
-
const err = new ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
|
|
455
|
-
context: { error, challenge },
|
|
456
|
-
logger: this.logger,
|
|
457
|
-
});
|
|
458
|
-
this.logger.error(() => ({
|
|
459
|
-
err: err,
|
|
460
|
-
msg: "Failed to retrieve PowCaptcha record",
|
|
461
|
-
}));
|
|
462
|
-
throw err;
|
|
463
|
-
}
|
|
378
|
+
const filter = { datasetId };
|
|
379
|
+
const doc = await this.tables?.dataset.findOne(filter).lean();
|
|
380
|
+
if (doc) {
|
|
381
|
+
return doc;
|
|
464
382
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
383
|
+
throw new ProsopoDBError("DATABASE.DATASET_GET_FAILED", {
|
|
384
|
+
context: {
|
|
385
|
+
failedFuncName: this.getDatasetDetails.name,
|
|
386
|
+
datasetId
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* @description Store a Dapp User's captcha solution commitment
|
|
392
|
+
*/
|
|
393
|
+
async storeUserImageCaptchaSolution(captchas, commit) {
|
|
394
|
+
const commitmentRecord = UserCommitmentSchema.parse({
|
|
395
|
+
...commit,
|
|
396
|
+
lastUpdatedTimestamp: Date.now()
|
|
397
|
+
});
|
|
398
|
+
if (captchas.length) {
|
|
399
|
+
const filter = {
|
|
400
|
+
id: commit.id
|
|
401
|
+
};
|
|
402
|
+
await this.tables?.commitment.updateOne(filter, commitmentRecord, {
|
|
403
|
+
upsert: true
|
|
404
|
+
});
|
|
405
|
+
const ops = captchas.map((captcha) => ({
|
|
406
|
+
updateOne: {
|
|
407
|
+
filter: {
|
|
408
|
+
commitmentId: commit.id,
|
|
409
|
+
captchaId: captcha.captchaId
|
|
410
|
+
},
|
|
411
|
+
update: {
|
|
412
|
+
$set: {
|
|
413
|
+
captchaId: captcha.captchaId,
|
|
414
|
+
captchaContentId: captcha.captchaContentId,
|
|
415
|
+
salt: captcha.salt,
|
|
416
|
+
solution: captcha.solution,
|
|
417
|
+
commitmentId: commit.id,
|
|
418
|
+
processed: false
|
|
492
419
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
challenge,
|
|
496
|
-
...update,
|
|
497
|
-
},
|
|
498
|
-
msg: "PowCaptcha record updated successfully",
|
|
499
|
-
}));
|
|
420
|
+
},
|
|
421
|
+
upsert: true
|
|
500
422
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
context: {
|
|
504
|
-
error,
|
|
505
|
-
challenge,
|
|
506
|
-
...update,
|
|
507
|
-
},
|
|
508
|
-
logger: this.logger,
|
|
509
|
-
});
|
|
510
|
-
this.logger.error(() => ({
|
|
511
|
-
err: err,
|
|
512
|
-
msg: "Failed to update PowCaptcha record",
|
|
513
|
-
}));
|
|
514
|
-
throw err;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
async getCheckedDappUserCommitments() {
|
|
518
|
-
const filter = { [StoredStatusNames.serverChecked]: true };
|
|
519
|
-
const docs = await this.tables?.commitment
|
|
520
|
-
.find(filter)
|
|
521
|
-
.lean();
|
|
522
|
-
return docs || [];
|
|
523
|
-
}
|
|
524
|
-
async getUnstoredDappUserCommitments(limit = 1000, skip = 0) {
|
|
525
|
-
const filterNoStoredTimestamp = { storedAtTimestamp: { $exists: false } };
|
|
526
|
-
const docs = await this.tables?.commitment.aggregate([
|
|
527
|
-
{
|
|
528
|
-
$match: {
|
|
529
|
-
$or: [
|
|
530
|
-
filterNoStoredTimestamp,
|
|
531
|
-
{
|
|
532
|
-
$expr: {
|
|
533
|
-
$lt: ["$storedAtTimestamp", "$lastUpdatedTimestamp"],
|
|
534
|
-
},
|
|
535
|
-
},
|
|
536
|
-
],
|
|
537
|
-
},
|
|
538
|
-
},
|
|
539
|
-
{
|
|
540
|
-
$sort: { _id: 1 },
|
|
541
|
-
},
|
|
542
|
-
{
|
|
543
|
-
$skip: skip,
|
|
544
|
-
},
|
|
545
|
-
{
|
|
546
|
-
$limit: limit,
|
|
547
|
-
},
|
|
548
|
-
]);
|
|
549
|
-
return docs || [];
|
|
423
|
+
}));
|
|
424
|
+
await this.tables?.usersolution.bulkWrite(ops);
|
|
550
425
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* @description Adds a new PoW Captcha record to the database.
|
|
429
|
+
* @param {string} challenge The challenge string for the captcha.
|
|
430
|
+
* @param components The components of the PoW challenge.
|
|
431
|
+
* @param difficulty
|
|
432
|
+
* @param providerSignature
|
|
433
|
+
* @param ipAddress
|
|
434
|
+
* @param headers
|
|
435
|
+
* @param ja4
|
|
436
|
+
* @param frictionlessTokenId
|
|
437
|
+
* @param serverChecked
|
|
438
|
+
* @param userSubmitted
|
|
439
|
+
* @param storedStatus
|
|
440
|
+
* @param userSignature
|
|
441
|
+
* @returns {Promise<void>} A promise that resolves when the record is added.
|
|
442
|
+
*/
|
|
443
|
+
async storePowCaptchaRecord(challenge, components, difficulty, providerSignature, ipAddress, headers, ja4, frictionlessTokenId, serverChecked = false, userSubmitted = false, storedStatus = StoredStatusNames.notStored, userSignature) {
|
|
444
|
+
const tables = this.getTables();
|
|
445
|
+
const powCaptchaRecord = {
|
|
446
|
+
challenge,
|
|
447
|
+
...components,
|
|
448
|
+
ipAddress,
|
|
449
|
+
headers,
|
|
450
|
+
ja4,
|
|
451
|
+
result: { status: CaptchaStatus.pending },
|
|
452
|
+
userSubmitted,
|
|
453
|
+
serverChecked,
|
|
454
|
+
difficulty,
|
|
455
|
+
providerSignature,
|
|
456
|
+
userSignature,
|
|
457
|
+
lastUpdatedTimestamp: Date.now(),
|
|
458
|
+
frictionlessTokenId
|
|
459
|
+
};
|
|
460
|
+
try {
|
|
461
|
+
await tables.powcaptcha.create(powCaptchaRecord);
|
|
462
|
+
this.logger.info(() => ({
|
|
463
|
+
data: {
|
|
464
|
+
challenge,
|
|
465
|
+
userSubmitted,
|
|
466
|
+
serverChecked,
|
|
467
|
+
storedStatus
|
|
468
|
+
},
|
|
469
|
+
msg: "PowCaptcha record added successfully"
|
|
470
|
+
}));
|
|
471
|
+
} catch (error) {
|
|
472
|
+
const err = new ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
|
|
473
|
+
context: {
|
|
474
|
+
error,
|
|
475
|
+
challenge,
|
|
476
|
+
userSubmitted,
|
|
477
|
+
serverChecked,
|
|
478
|
+
storedStatus
|
|
479
|
+
},
|
|
480
|
+
logger: this.logger
|
|
481
|
+
});
|
|
482
|
+
this.logger.error(() => ({
|
|
483
|
+
err: error,
|
|
484
|
+
msg: "Failed to add PowCaptcha record"
|
|
485
|
+
}));
|
|
486
|
+
throw err;
|
|
603
487
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* @description Retrieves a PoW Captcha record by its challenge string.
|
|
491
|
+
* @param {string} challenge The challenge string to search for.
|
|
492
|
+
* @returns {Promise<PoWCaptchaRecord | null>} A promise that resolves with the found record or null if not found.
|
|
493
|
+
*/
|
|
494
|
+
async getPowCaptchaRecordByChallenge(challenge) {
|
|
495
|
+
if (!this.tables) {
|
|
496
|
+
throw new ProsopoDBError("DATABASE.DATABASE_UNDEFINED", {
|
|
497
|
+
context: { failedFuncName: this.getPowCaptchaRecordByChallenge.name },
|
|
498
|
+
logger: this.logger
|
|
499
|
+
});
|
|
609
500
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
}, { upsert: false });
|
|
618
|
-
}
|
|
619
|
-
async storeFrictionlessTokenRecord(tokenRecord) {
|
|
620
|
-
const doc = await this.tables.frictionlessToken.create(tokenRecord);
|
|
621
|
-
return doc._id;
|
|
622
|
-
}
|
|
623
|
-
async updateFrictionlessTokenRecord(tokenId, updates) {
|
|
624
|
-
const filter = { _id: tokenId };
|
|
625
|
-
await this.tables.frictionlessToken.updateOne(filter, updates);
|
|
626
|
-
}
|
|
627
|
-
async getFrictionlessTokenRecordByTokenId(tokenId) {
|
|
628
|
-
const filter = { _id: tokenId };
|
|
629
|
-
const doc = await this.tables.frictionlessToken.findOne(filter);
|
|
630
|
-
return doc ? doc : undefined;
|
|
631
|
-
}
|
|
632
|
-
async getFrictionlessTokenRecordsByTokenIds(tokenId) {
|
|
633
|
-
const filter = {
|
|
634
|
-
_id: { $in: tokenId },
|
|
635
|
-
};
|
|
636
|
-
return this.tables.frictionlessToken.find(filter);
|
|
637
|
-
}
|
|
638
|
-
async getFrictionlessTokenRecordByToken(token) {
|
|
639
|
-
const filter = { token };
|
|
640
|
-
const record = await this.tables.frictionlessToken.findOne(filter);
|
|
641
|
-
return record || undefined;
|
|
642
|
-
}
|
|
643
|
-
async storeSessionRecord(sessionRecord) {
|
|
644
|
-
try {
|
|
645
|
-
this.logger.debug(() => ({
|
|
646
|
-
data: { action: "storing", sessionRecord },
|
|
647
|
-
}));
|
|
648
|
-
await this.tables.session.create(sessionRecord);
|
|
649
|
-
}
|
|
650
|
-
catch (err) {
|
|
651
|
-
throw new ProsopoDBError("DATABASE.SESSION_STORE_FAILED", {
|
|
652
|
-
context: { error: err, sessionId: sessionRecord.sessionId },
|
|
653
|
-
logger: this.logger,
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
async checkAndRemoveSession(sessionId) {
|
|
658
|
-
this.logger.debug(() => ({
|
|
659
|
-
data: { action: "checking and removing", sessionId },
|
|
501
|
+
try {
|
|
502
|
+
const filter = { challenge };
|
|
503
|
+
const record = await this.tables.powcaptcha.findOne(filter).lean();
|
|
504
|
+
if (record) {
|
|
505
|
+
this.logger.info(() => ({
|
|
506
|
+
data: { challenge },
|
|
507
|
+
msg: "PowCaptcha record retrieved successfully"
|
|
660
508
|
}));
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
});
|
|
679
|
-
}
|
|
509
|
+
return record;
|
|
510
|
+
}
|
|
511
|
+
this.logger.info(() => ({
|
|
512
|
+
data: { challenge },
|
|
513
|
+
msg: "No PowCaptcha record found"
|
|
514
|
+
}));
|
|
515
|
+
return null;
|
|
516
|
+
} catch (error) {
|
|
517
|
+
const err = new ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
|
|
518
|
+
context: { error, challenge },
|
|
519
|
+
logger: this.logger
|
|
520
|
+
});
|
|
521
|
+
this.logger.error(() => ({
|
|
522
|
+
err,
|
|
523
|
+
msg: "Failed to retrieve PowCaptcha record"
|
|
524
|
+
}));
|
|
525
|
+
throw err;
|
|
680
526
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
},
|
|
707
|
-
],
|
|
708
|
-
},
|
|
709
|
-
},
|
|
710
|
-
{
|
|
711
|
-
$sort: { _id: 1 },
|
|
712
|
-
},
|
|
713
|
-
{
|
|
714
|
-
$skip: skip,
|
|
715
|
-
},
|
|
716
|
-
{
|
|
717
|
-
$limit: limit,
|
|
718
|
-
},
|
|
719
|
-
])
|
|
720
|
-
.then((docs) => docs || []);
|
|
721
|
-
}
|
|
722
|
-
async markSessionRecordsStored(sessionIds) {
|
|
723
|
-
const updateDoc = {
|
|
724
|
-
storedAtTimestamp: Date.now(),
|
|
725
|
-
};
|
|
726
|
-
await this.tables?.session.updateMany({ sessionId: { $in: sessionIds } }, { $set: updateDoc }, { upsert: false });
|
|
727
|
-
}
|
|
728
|
-
async storePendingImageCommitment(userAccount, requestHash, salt, deadlineTimestamp, requestedAtTimestamp, ipAddress, threshold, frictionlessTokenId) {
|
|
729
|
-
if (!isHex(requestHash)) {
|
|
730
|
-
throw new ProsopoDBError("DATABASE.INVALID_HASH", {
|
|
731
|
-
context: {
|
|
732
|
-
failedFuncName: this.storePendingImageCommitment.name,
|
|
733
|
-
requestHash,
|
|
734
|
-
},
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
const pendingRecord = {
|
|
738
|
-
accountId: userAccount,
|
|
739
|
-
pending: true,
|
|
740
|
-
salt,
|
|
741
|
-
requestHash,
|
|
742
|
-
deadlineTimestamp,
|
|
743
|
-
requestedAtTimestamp: new Date(requestedAtTimestamp),
|
|
744
|
-
ipAddress,
|
|
745
|
-
frictionlessTokenId,
|
|
746
|
-
threshold,
|
|
747
|
-
};
|
|
748
|
-
await this.tables?.pending.updateOne({ requestHash: requestHash }, { $set: pendingRecord }, { upsert: true });
|
|
749
|
-
}
|
|
750
|
-
async getPendingImageCommitment(requestHash) {
|
|
751
|
-
if (!isHex(requestHash)) {
|
|
752
|
-
throw new ProsopoDBError("DATABASE.INVALID_HASH", {
|
|
753
|
-
context: {
|
|
754
|
-
failedFuncName: this.getPendingImageCommitment.name,
|
|
755
|
-
requestHash,
|
|
756
|
-
},
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
const filter = {
|
|
760
|
-
[ApiParams.requestHash]: requestHash,
|
|
761
|
-
};
|
|
762
|
-
const doc = await this.tables?.pending.findOne(filter).lean();
|
|
763
|
-
if (doc) {
|
|
764
|
-
return doc;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* @description Updates a PoW Captcha record in the database.
|
|
530
|
+
* @param {string} challenge The challenge string of the captcha to be updated.
|
|
531
|
+
* @param result
|
|
532
|
+
* @param serverChecked
|
|
533
|
+
* @param userSubmitted
|
|
534
|
+
* @param userSignature
|
|
535
|
+
* @returns {Promise<void>} A promise that resolves when the record is updated.
|
|
536
|
+
*/
|
|
537
|
+
async updatePowCaptchaRecord(challenge, result, serverChecked = false, userSubmitted = false, userSignature) {
|
|
538
|
+
const tables = this.getTables();
|
|
539
|
+
const timestamp = Date.now();
|
|
540
|
+
const update = {
|
|
541
|
+
result,
|
|
542
|
+
serverChecked,
|
|
543
|
+
userSubmitted,
|
|
544
|
+
userSignature,
|
|
545
|
+
lastUpdatedTimestamp: timestamp
|
|
546
|
+
};
|
|
547
|
+
try {
|
|
548
|
+
const updateResult = await tables.powcaptcha.updateOne(
|
|
549
|
+
{ challenge },
|
|
550
|
+
{
|
|
551
|
+
$set: update
|
|
765
552
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
553
|
+
);
|
|
554
|
+
if (updateResult.matchedCount === 0) {
|
|
555
|
+
const err = new ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
|
|
556
|
+
context: {
|
|
557
|
+
challenge,
|
|
558
|
+
...update
|
|
559
|
+
},
|
|
560
|
+
logger: this.logger
|
|
771
561
|
});
|
|
562
|
+
this.logger.info(() => ({
|
|
563
|
+
err,
|
|
564
|
+
msg: "No PowCaptcha record found to update"
|
|
565
|
+
}));
|
|
566
|
+
throw err;
|
|
567
|
+
}
|
|
568
|
+
this.logger.info(() => ({
|
|
569
|
+
data: {
|
|
570
|
+
challenge,
|
|
571
|
+
...update
|
|
572
|
+
},
|
|
573
|
+
msg: "PowCaptcha record updated successfully"
|
|
574
|
+
}));
|
|
575
|
+
} catch (error) {
|
|
576
|
+
const err = new ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
|
|
577
|
+
context: {
|
|
578
|
+
error,
|
|
579
|
+
challenge,
|
|
580
|
+
...update
|
|
581
|
+
},
|
|
582
|
+
logger: this.logger
|
|
583
|
+
});
|
|
584
|
+
this.logger.error(() => ({
|
|
585
|
+
err,
|
|
586
|
+
msg: "Failed to update PowCaptcha record"
|
|
587
|
+
}));
|
|
588
|
+
throw err;
|
|
772
589
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
590
|
+
}
|
|
591
|
+
/** @description Get serverChecked Dapp User image captcha commitments from the commitments table
|
|
592
|
+
*/
|
|
593
|
+
async getCheckedDappUserCommitments() {
|
|
594
|
+
const filter = { [StoredStatusNames.serverChecked]: true };
|
|
595
|
+
const docs = await this.tables?.commitment.find(filter).lean();
|
|
596
|
+
return docs || [];
|
|
597
|
+
}
|
|
598
|
+
/** @description Get Dapp User captcha commitments from the commitments table that have not been counted towards the
|
|
599
|
+
* client's total
|
|
600
|
+
*/
|
|
601
|
+
async getUnstoredDappUserCommitments(limit = 1e3, skip = 0) {
|
|
602
|
+
const filterNoStoredTimestamp = { storedAtTimestamp: { $exists: false } };
|
|
603
|
+
const docs = await this.tables?.commitment.aggregate([
|
|
604
|
+
{
|
|
605
|
+
$match: {
|
|
606
|
+
$or: [
|
|
607
|
+
filterNoStoredTimestamp,
|
|
608
|
+
{
|
|
609
|
+
$expr: {
|
|
610
|
+
$lt: ["$storedAtTimestamp", "$lastUpdatedTimestamp"]
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
]
|
|
781
614
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
$sort: { _id: 1 }
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
$skip: skip
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
$limit: limit
|
|
624
|
+
}
|
|
625
|
+
]);
|
|
626
|
+
return docs || [];
|
|
627
|
+
}
|
|
628
|
+
/** @description Mark a list of captcha commits as stored
|
|
629
|
+
*/
|
|
630
|
+
async markDappUserCommitmentsStored(commitmentIds) {
|
|
631
|
+
const updateDoc = {
|
|
632
|
+
storedAtTimestamp: Date.now()
|
|
633
|
+
};
|
|
634
|
+
await this.tables?.commitment.updateMany(
|
|
635
|
+
{ id: { $in: commitmentIds } },
|
|
636
|
+
{ $set: updateDoc },
|
|
637
|
+
{ upsert: false }
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
/** @description Mark a list of captcha commits as checked
|
|
641
|
+
*/
|
|
642
|
+
async markDappUserCommitmentsChecked(commitmentIds) {
|
|
643
|
+
const updateDoc = {
|
|
644
|
+
[StoredStatusNames.serverChecked]: true,
|
|
645
|
+
lastUpdatedTimestamp: Date.now()
|
|
646
|
+
};
|
|
647
|
+
await this.tables?.commitment.updateMany(
|
|
648
|
+
{ id: { $in: commitmentIds } },
|
|
649
|
+
{ $set: updateDoc },
|
|
650
|
+
{ upsert: false }
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* @description Get Dapp User PoW captcha commitments that have not been counted towards the client's total
|
|
655
|
+
* @param {number} limit Maximum number of records to return
|
|
656
|
+
* @param {number} skip Number of records to skip (for pagination)
|
|
657
|
+
* @returns {Promise<PoWCaptchaRecord[]>} Array of PoW captcha records
|
|
658
|
+
*/
|
|
659
|
+
async getUnstoredDappUserPoWCommitments(limit = 1e3, skip = 0) {
|
|
660
|
+
const filterNoStoredTimestamp = { storedAtTimestamp: { $exists: false } };
|
|
661
|
+
const docs = await this.tables?.powcaptcha.aggregate([
|
|
662
|
+
{
|
|
663
|
+
$match: {
|
|
664
|
+
$or: [
|
|
665
|
+
filterNoStoredTimestamp,
|
|
666
|
+
{
|
|
667
|
+
$expr: {
|
|
668
|
+
$lt: [
|
|
669
|
+
{
|
|
670
|
+
$convert: {
|
|
671
|
+
input: "$storedAtTimestamp",
|
|
672
|
+
to: "date"
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
$convert: {
|
|
677
|
+
input: "$lastUpdatedTimestamp",
|
|
678
|
+
to: "date"
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
]
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
]
|
|
802
685
|
}
|
|
803
|
-
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
$sort: { _id: 1 }
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
$skip: skip
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
$limit: limit
|
|
695
|
+
}
|
|
696
|
+
]);
|
|
697
|
+
return docs || [];
|
|
698
|
+
}
|
|
699
|
+
/** @description Mark a list of PoW captcha commits as stored
|
|
700
|
+
*/
|
|
701
|
+
async markDappUserPoWCommitmentsStored(challenges) {
|
|
702
|
+
const updateDoc = {
|
|
703
|
+
storedAtTimestamp: Date.now()
|
|
704
|
+
};
|
|
705
|
+
await this.tables?.powcaptcha.updateMany(
|
|
706
|
+
{ challenge: { $in: challenges } },
|
|
707
|
+
{ $set: updateDoc },
|
|
708
|
+
{ upsert: false }
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
/** @description Mark a list of PoW captcha commits as checked by the server
|
|
712
|
+
*/
|
|
713
|
+
async markDappUserPoWCommitmentsChecked(challenges) {
|
|
714
|
+
const updateDoc = {
|
|
715
|
+
[StoredStatusNames.serverChecked]: true,
|
|
716
|
+
lastUpdatedTimestamp: Date.now()
|
|
717
|
+
};
|
|
718
|
+
await this.tables?.powcaptcha.updateMany(
|
|
719
|
+
{ challenge: { $in: challenges } },
|
|
720
|
+
{
|
|
721
|
+
$set: updateDoc
|
|
722
|
+
},
|
|
723
|
+
{ upsert: false }
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Store a new frictionless token record
|
|
728
|
+
*/
|
|
729
|
+
async storeFrictionlessTokenRecord(tokenRecord) {
|
|
730
|
+
const doc = await this.tables.frictionlessToken.create(
|
|
731
|
+
tokenRecord
|
|
732
|
+
);
|
|
733
|
+
return doc._id;
|
|
734
|
+
}
|
|
735
|
+
/** Update a frictionless token record */
|
|
736
|
+
async updateFrictionlessTokenRecord(tokenId, updates) {
|
|
737
|
+
const filter = { _id: tokenId };
|
|
738
|
+
await this.tables.frictionlessToken.updateOne(filter, updates);
|
|
739
|
+
}
|
|
740
|
+
/** Get a frictionless token record */
|
|
741
|
+
async getFrictionlessTokenRecordByTokenId(tokenId) {
|
|
742
|
+
const filter = { _id: tokenId };
|
|
743
|
+
const doc = await this.tables.frictionlessToken.findOne(
|
|
744
|
+
filter
|
|
745
|
+
);
|
|
746
|
+
return doc ? doc : void 0;
|
|
747
|
+
}
|
|
748
|
+
/** Get many frictionless token records */
|
|
749
|
+
async getFrictionlessTokenRecordsByTokenIds(tokenId) {
|
|
750
|
+
const filter = {
|
|
751
|
+
_id: { $in: tokenId }
|
|
752
|
+
};
|
|
753
|
+
return this.tables.frictionlessToken.find(filter);
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Check if a frictionless token record exists.
|
|
757
|
+
* Used to ensure that a token is not used more than once.
|
|
758
|
+
*/
|
|
759
|
+
async getFrictionlessTokenRecordByToken(token) {
|
|
760
|
+
const filter = { token };
|
|
761
|
+
const record = await this.tables.frictionlessToken.findOne(
|
|
762
|
+
filter
|
|
763
|
+
);
|
|
764
|
+
return record || void 0;
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Store a new session record
|
|
768
|
+
*/
|
|
769
|
+
async storeSessionRecord(sessionRecord) {
|
|
770
|
+
try {
|
|
771
|
+
this.logger.debug(() => ({
|
|
772
|
+
data: { action: "storing", sessionRecord }
|
|
773
|
+
}));
|
|
774
|
+
await this.tables.session.create(sessionRecord);
|
|
775
|
+
} catch (err) {
|
|
776
|
+
throw new ProsopoDBError("DATABASE.SESSION_STORE_FAILED", {
|
|
777
|
+
context: { error: err, sessionId: sessionRecord.sessionId },
|
|
778
|
+
logger: this.logger
|
|
779
|
+
});
|
|
804
780
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Check if a session exists and mark it as removed
|
|
784
|
+
* @returns The session record if it existed, undefined otherwise
|
|
785
|
+
*/
|
|
786
|
+
async checkAndRemoveSession(sessionId) {
|
|
787
|
+
this.logger.debug(() => ({
|
|
788
|
+
data: { action: "checking and removing", sessionId }
|
|
789
|
+
}));
|
|
790
|
+
const filter = {
|
|
791
|
+
sessionId,
|
|
792
|
+
deleted: { $exists: false }
|
|
793
|
+
};
|
|
794
|
+
try {
|
|
795
|
+
const session = await this.tables.session.findOneAndUpdate(filter, {
|
|
796
|
+
deleted: true,
|
|
797
|
+
lastUpdatedTimestamp: Date.now()
|
|
798
|
+
}).lean();
|
|
799
|
+
return session || void 0;
|
|
800
|
+
} catch (err) {
|
|
801
|
+
throw new ProsopoDBError("DATABASE.SESSION_CHECK_REMOVE_FAILED", {
|
|
802
|
+
context: { error: err, sessionId },
|
|
803
|
+
logger: this.logger
|
|
804
|
+
});
|
|
817
805
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
806
|
+
}
|
|
807
|
+
/** Get unstored session records
|
|
808
|
+
* @description Get session records that have not been stored yet
|
|
809
|
+
* @param limit
|
|
810
|
+
* @param skip
|
|
811
|
+
*/
|
|
812
|
+
getUnstoredSessionRecords(limit = 1e3, skip = 0) {
|
|
813
|
+
const filterNoStoredTimestamp = { storedAtTimestamp: { $exists: false } };
|
|
814
|
+
return this.tables?.session.aggregate([
|
|
815
|
+
{
|
|
816
|
+
$match: {
|
|
817
|
+
$or: [
|
|
818
|
+
filterNoStoredTimestamp,
|
|
823
819
|
{
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
throw new ProsopoDBError("DATABASE.DATASET_WITH_SOLUTIONS_GET_FAILED");
|
|
843
|
-
}
|
|
844
|
-
async getRandomSolvedCaptchasFromSingleDataset(datasetId, size) {
|
|
845
|
-
if (!isHex(datasetId)) {
|
|
846
|
-
throw new ProsopoDBError("DATABASE.INVALID_HASH", {
|
|
847
|
-
context: {
|
|
848
|
-
failedFuncName: this.getRandomSolvedCaptchasFromSingleDataset.name,
|
|
849
|
-
datasetId,
|
|
850
|
-
},
|
|
851
|
-
});
|
|
820
|
+
$expr: {
|
|
821
|
+
$lt: [
|
|
822
|
+
{
|
|
823
|
+
$convert: {
|
|
824
|
+
input: "$storedAtTimestamp",
|
|
825
|
+
to: "date"
|
|
826
|
+
}
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
$convert: {
|
|
830
|
+
input: "$lastUpdatedTimestamp",
|
|
831
|
+
to: "date"
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
]
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
]
|
|
852
838
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
839
|
+
},
|
|
840
|
+
{
|
|
841
|
+
$sort: { _id: 1 }
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
$skip: skip
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
$limit: limit
|
|
848
|
+
}
|
|
849
|
+
]).then((docs) => docs || []);
|
|
850
|
+
}
|
|
851
|
+
/** Mark a list of session records as stored */
|
|
852
|
+
async markSessionRecordsStored(sessionIds) {
|
|
853
|
+
const updateDoc = {
|
|
854
|
+
storedAtTimestamp: Date.now()
|
|
855
|
+
};
|
|
856
|
+
await this.tables?.session.updateMany(
|
|
857
|
+
{ sessionId: { $in: sessionIds } },
|
|
858
|
+
{ $set: updateDoc },
|
|
859
|
+
{ upsert: false }
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* @description Store a Dapp User's pending record
|
|
864
|
+
*/
|
|
865
|
+
async storePendingImageCommitment(userAccount, requestHash, salt, deadlineTimestamp, requestedAtTimestamp, ipAddress, threshold, frictionlessTokenId) {
|
|
866
|
+
if (!isHex(requestHash)) {
|
|
867
|
+
throw new ProsopoDBError("DATABASE.INVALID_HASH", {
|
|
868
|
+
context: {
|
|
869
|
+
failedFuncName: this.storePendingImageCommitment.name,
|
|
870
|
+
requestHash
|
|
868
871
|
}
|
|
869
|
-
|
|
870
|
-
context: {
|
|
871
|
-
failedFuncName: this.getRandomSolvedCaptchasFromSingleDataset.name,
|
|
872
|
-
datasetId,
|
|
873
|
-
size,
|
|
874
|
-
},
|
|
875
|
-
});
|
|
872
|
+
});
|
|
876
873
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
874
|
+
const pendingRecord = {
|
|
875
|
+
accountId: userAccount,
|
|
876
|
+
pending: true,
|
|
877
|
+
salt,
|
|
878
|
+
requestHash,
|
|
879
|
+
deadlineTimestamp,
|
|
880
|
+
requestedAtTimestamp: new Date(requestedAtTimestamp),
|
|
881
|
+
ipAddress,
|
|
882
|
+
frictionlessTokenId,
|
|
883
|
+
threshold
|
|
884
|
+
};
|
|
885
|
+
await this.tables?.pending.updateOne(
|
|
886
|
+
{ requestHash },
|
|
887
|
+
{ $set: pendingRecord },
|
|
888
|
+
{ upsert: true }
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* @description Get a Dapp user's pending record
|
|
893
|
+
*/
|
|
894
|
+
async getPendingImageCommitment(requestHash) {
|
|
895
|
+
if (!isHex(requestHash)) {
|
|
896
|
+
throw new ProsopoDBError("DATABASE.INVALID_HASH", {
|
|
897
|
+
context: {
|
|
898
|
+
failedFuncName: this.getPendingImageCommitment.name,
|
|
899
|
+
requestHash
|
|
886
900
|
}
|
|
887
|
-
|
|
888
|
-
context: { failedFuncName: this.getCaptchaById.name, commitmentId },
|
|
889
|
-
});
|
|
890
|
-
}
|
|
891
|
-
async getDappUserCommitmentById(commitmentId) {
|
|
892
|
-
const filter = { id: commitmentId };
|
|
893
|
-
const commitmentCursor = this.tables?.commitment
|
|
894
|
-
?.findOne(filter)
|
|
895
|
-
.lean();
|
|
896
|
-
const doc = await commitmentCursor;
|
|
897
|
-
return doc ? doc : undefined;
|
|
901
|
+
});
|
|
898
902
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
const sort = { sort: { _id: -1 } };
|
|
906
|
-
const docs = await this.tables?.commitment
|
|
907
|
-
?.find(filter, project, sort)
|
|
908
|
-
.lean();
|
|
909
|
-
return docs ? docs : [];
|
|
903
|
+
const filter = {
|
|
904
|
+
[ApiParams.requestHash]: requestHash
|
|
905
|
+
};
|
|
906
|
+
const doc = await this.tables?.pending.findOne(filter).lean();
|
|
907
|
+
if (doc) {
|
|
908
|
+
return doc;
|
|
910
909
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
910
|
+
throw new ProsopoDBError("DATABASE.PENDING_RECORD_NOT_FOUND", {
|
|
911
|
+
context: {
|
|
912
|
+
failedFuncName: this.getPendingImageCommitment.name,
|
|
913
|
+
requestHash
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* @description Mark a pending request as used
|
|
919
|
+
*/
|
|
920
|
+
async updatePendingImageCommitmentStatus(requestHash) {
|
|
921
|
+
if (!isHex(requestHash)) {
|
|
922
|
+
throw new ProsopoDBError("DATABASE.INVALID_HASH", {
|
|
923
|
+
context: {
|
|
924
|
+
failedFuncName: this.updatePendingImageCommitmentStatus.name,
|
|
925
|
+
requestHash
|
|
927
926
|
}
|
|
927
|
+
});
|
|
928
928
|
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
?.findOneAndUpdate(filter, { $set: updateDoc }, { upsert: false })
|
|
938
|
-
.lean();
|
|
939
|
-
}
|
|
940
|
-
catch (err) {
|
|
941
|
-
throw new ProsopoDBError("DATABASE.SOLUTION_APPROVE_FAILED", {
|
|
942
|
-
context: { error: err, commitmentId },
|
|
943
|
-
});
|
|
929
|
+
const filter = {
|
|
930
|
+
[ApiParams.requestHash]: requestHash
|
|
931
|
+
};
|
|
932
|
+
await this.tables?.pending.updateOne(
|
|
933
|
+
filter,
|
|
934
|
+
{
|
|
935
|
+
$set: {
|
|
936
|
+
[CaptchaStatus.pending]: false
|
|
944
937
|
}
|
|
938
|
+
},
|
|
939
|
+
{ upsert: true }
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* @description Get all unsolved captchas
|
|
944
|
+
*/
|
|
945
|
+
async getAllCaptchasByDatasetId(datasetId, state) {
|
|
946
|
+
const filter = {
|
|
947
|
+
datasetId,
|
|
948
|
+
solved: state === CaptchaStates.Solved
|
|
949
|
+
};
|
|
950
|
+
const cursor = this.tables?.captcha.find(filter).lean();
|
|
951
|
+
const docs = await cursor;
|
|
952
|
+
if (docs) {
|
|
953
|
+
return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
|
|
945
954
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
955
|
+
throw new ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED");
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* @description Get all dapp user solutions by captchaIds
|
|
959
|
+
*/
|
|
960
|
+
async getAllDappUserSolutions(captchaId) {
|
|
961
|
+
const filter = {
|
|
962
|
+
captchaId: { $in: captchaId }
|
|
963
|
+
};
|
|
964
|
+
const cursor = this.tables?.usersolution?.find(filter).lean();
|
|
965
|
+
const docs = await cursor;
|
|
966
|
+
if (docs) {
|
|
967
|
+
return docs.map(
|
|
968
|
+
({ _id, ...keepAttrs }) => keepAttrs
|
|
969
|
+
);
|
|
957
970
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
971
|
+
throw new ProsopoDBError("DATABASE.SOLUTION_GET_FAILED");
|
|
972
|
+
}
|
|
973
|
+
async getDatasetIdWithSolvedCaptchasOfSizeN(solvedCaptchaCount) {
|
|
974
|
+
const cursor = this.tables?.solution.aggregate([
|
|
975
|
+
{
|
|
976
|
+
$match: {}
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
$group: {
|
|
980
|
+
_id: "$datasetId",
|
|
981
|
+
count: { $sum: 1 }
|
|
964
982
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
$match: {
|
|
986
|
+
count: { $gte: solvedCaptchaCount }
|
|
969
987
|
}
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
$sample: { size: 1 }
|
|
991
|
+
}
|
|
992
|
+
]);
|
|
993
|
+
const docs = await cursor;
|
|
994
|
+
if (docs?.length) {
|
|
995
|
+
return docs[0]._id;
|
|
970
996
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
return cursor ? cursor : undefined;
|
|
980
|
-
}
|
|
981
|
-
async getLastScheduledTaskStatus(task, status) {
|
|
982
|
-
const filter = { processName: task };
|
|
983
|
-
if (status) {
|
|
984
|
-
filter.status = status;
|
|
997
|
+
throw new ProsopoDBError("DATABASE.DATASET_WITH_SOLUTIONS_GET_FAILED");
|
|
998
|
+
}
|
|
999
|
+
async getRandomSolvedCaptchasFromSingleDataset(datasetId, size) {
|
|
1000
|
+
if (!isHex(datasetId)) {
|
|
1001
|
+
throw new ProsopoDBError("DATABASE.INVALID_HASH", {
|
|
1002
|
+
context: {
|
|
1003
|
+
failedFuncName: this.getRandomSolvedCaptchasFromSingleDataset.name,
|
|
1004
|
+
datasetId
|
|
985
1005
|
}
|
|
986
|
-
|
|
987
|
-
datetime: -1,
|
|
988
|
-
};
|
|
989
|
-
const cursor = await this.tables?.scheduler
|
|
990
|
-
?.findOne(filter)
|
|
991
|
-
.sort(sort)
|
|
992
|
-
.limit(1)
|
|
993
|
-
.lean();
|
|
994
|
-
return cursor ? cursor : undefined;
|
|
995
|
-
}
|
|
996
|
-
async createScheduledTaskStatus(taskName, status) {
|
|
997
|
-
const now = new Date().getTime();
|
|
998
|
-
const doc = ScheduledTaskSchema.parse({
|
|
999
|
-
processName: taskName,
|
|
1000
|
-
datetime: now,
|
|
1001
|
-
status,
|
|
1002
|
-
});
|
|
1003
|
-
const taskRecord = await this.tables?.scheduler.create(doc);
|
|
1004
|
-
return taskRecord._id;
|
|
1006
|
+
});
|
|
1005
1007
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
}
|
|
1008
|
+
const sampleSize = size ? Math.abs(Math.trunc(size)) : 1;
|
|
1009
|
+
const cursor = this.tables?.solution.aggregate([
|
|
1010
|
+
{ $match: { datasetId } },
|
|
1011
|
+
{ $sample: { size: sampleSize } },
|
|
1012
|
+
{
|
|
1013
|
+
$project: {
|
|
1014
|
+
captchaId: 1,
|
|
1015
|
+
captchaContentId: 1,
|
|
1016
|
+
solution: 1
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
]);
|
|
1020
|
+
const docs = await cursor;
|
|
1021
|
+
if (docs?.length) {
|
|
1022
|
+
return docs;
|
|
1016
1023
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1024
|
+
throw new ProsopoDBError("DATABASE.SOLUTION_GET_FAILED", {
|
|
1025
|
+
context: {
|
|
1026
|
+
failedFuncName: this.getRandomSolvedCaptchasFromSingleDataset.name,
|
|
1027
|
+
datasetId,
|
|
1028
|
+
size
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* @description Get dapp user solution by ID
|
|
1034
|
+
* @param {string[]} commitmentId
|
|
1035
|
+
*/
|
|
1036
|
+
async getDappUserSolutionById(commitmentId) {
|
|
1037
|
+
const filter = {
|
|
1038
|
+
commitmentId
|
|
1039
|
+
};
|
|
1040
|
+
const project = { projection: { _id: 0 } };
|
|
1041
|
+
const cursor = this.tables?.usersolution?.findOne(filter, project).lean();
|
|
1042
|
+
const doc = await cursor;
|
|
1043
|
+
if (doc) {
|
|
1044
|
+
return doc;
|
|
1022
1045
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1046
|
+
throw new ProsopoDBError("DATABASE.SOLUTION_GET_FAILED", {
|
|
1047
|
+
context: { failedFuncName: this.getCaptchaById.name, commitmentId }
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* @description Get dapp user commitment by user account
|
|
1052
|
+
* @param commitmentId
|
|
1053
|
+
*/
|
|
1054
|
+
async getDappUserCommitmentById(commitmentId) {
|
|
1055
|
+
const filter = { id: commitmentId };
|
|
1056
|
+
const commitmentCursor = this.tables?.commitment?.findOne(filter).lean();
|
|
1057
|
+
const doc = await commitmentCursor;
|
|
1058
|
+
return doc ? doc : void 0;
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* @description Get dapp user commitment by user account
|
|
1062
|
+
* @param {string} userAccount
|
|
1063
|
+
* @param {string} dappAccount
|
|
1064
|
+
*/
|
|
1065
|
+
async getDappUserCommitmentByAccount(userAccount, dappAccount) {
|
|
1066
|
+
const filter = {
|
|
1067
|
+
userAccount,
|
|
1068
|
+
dappAccount
|
|
1069
|
+
};
|
|
1070
|
+
const project = { _id: 0 };
|
|
1071
|
+
const sort = { sort: { _id: -1 } };
|
|
1072
|
+
const docs = await this.tables?.commitment?.find(filter, project, sort).lean();
|
|
1073
|
+
return docs ? docs : [];
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* @description Approve a dapp user's solution
|
|
1077
|
+
* @param {string[]} commitmentId
|
|
1078
|
+
*/
|
|
1079
|
+
async approveDappUserCommitment(commitmentId) {
|
|
1080
|
+
try {
|
|
1081
|
+
const result = { status: CaptchaStatus.approved };
|
|
1082
|
+
const updateDoc = {
|
|
1083
|
+
result,
|
|
1084
|
+
lastUpdatedTimestamp: Date.now()
|
|
1085
|
+
};
|
|
1086
|
+
const filter = { id: commitmentId };
|
|
1087
|
+
await this.tables?.commitment?.findOneAndUpdate(filter, { $set: updateDoc }, { upsert: false }).lean();
|
|
1088
|
+
} catch (err) {
|
|
1089
|
+
throw new ProsopoDBError("DATABASE.SOLUTION_APPROVE_FAILED", {
|
|
1090
|
+
context: { error: err, commitmentId }
|
|
1091
|
+
});
|
|
1044
1092
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* @description Disapprove a dapp user's solution
|
|
1096
|
+
* @param {string} commitmentId
|
|
1097
|
+
* @param reason
|
|
1098
|
+
*/
|
|
1099
|
+
async disapproveDappUserCommitment(commitmentId, reason) {
|
|
1100
|
+
try {
|
|
1101
|
+
const updateDoc = {
|
|
1102
|
+
result: { status: CaptchaStatus.disapproved, reason },
|
|
1103
|
+
lastUpdatedTimestamp: Date.now()
|
|
1104
|
+
};
|
|
1105
|
+
const filter = { id: commitmentId };
|
|
1106
|
+
await this.tables?.commitment?.findOneAndUpdate(filter, { $set: updateDoc }, { upsert: false }).lean();
|
|
1107
|
+
} catch (err) {
|
|
1108
|
+
throw new ProsopoDBError("DATABASE.SOLUTION_APPROVE_FAILED", {
|
|
1109
|
+
context: { error: err, commitmentId }
|
|
1110
|
+
});
|
|
1049
1111
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* @description Flag a dapp user's solutions as used by calculated solution
|
|
1115
|
+
* @param {string[]} captchaIds
|
|
1116
|
+
*/
|
|
1117
|
+
async flagProcessedDappUserSolutions(captchaIds) {
|
|
1118
|
+
try {
|
|
1119
|
+
await this.tables?.usersolution?.updateMany(
|
|
1120
|
+
{ captchaId: { $in: captchaIds } },
|
|
1121
|
+
{ $set: { processed: true } },
|
|
1122
|
+
{ upsert: false }
|
|
1123
|
+
).lean();
|
|
1124
|
+
} catch (err) {
|
|
1125
|
+
throw new ProsopoDBError("DATABASE.SOLUTION_FLAG_FAILED", {
|
|
1126
|
+
context: { error: err, captchaIds }
|
|
1127
|
+
});
|
|
1055
1128
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* @description Flag dapp users' commitments as used by calculated solution
|
|
1132
|
+
* @param {string[]} commitmentIds
|
|
1133
|
+
*/
|
|
1134
|
+
async flagProcessedDappUserCommitments(commitmentIds) {
|
|
1135
|
+
try {
|
|
1136
|
+
const distinctCommitmentIds = [...new Set(commitmentIds)];
|
|
1137
|
+
await this.tables?.commitment?.updateMany(
|
|
1138
|
+
{ id: { $in: distinctCommitmentIds } },
|
|
1139
|
+
{ $set: { processed: true } },
|
|
1140
|
+
{ upsert: false }
|
|
1141
|
+
).lean();
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
throw new ProsopoDBError("DATABASE.COMMITMENT_FLAG_FAILED", {
|
|
1144
|
+
context: { error: err, commitmentIds }
|
|
1145
|
+
});
|
|
1062
1146
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* @description Get a scheduled task status record by task ID and status
|
|
1150
|
+
*/
|
|
1151
|
+
async getScheduledTaskStatus(taskId, status) {
|
|
1152
|
+
const filter = {
|
|
1153
|
+
_id: taskId,
|
|
1154
|
+
status
|
|
1155
|
+
};
|
|
1156
|
+
const cursor = await this.tables?.scheduler?.findOne(filter).lean();
|
|
1157
|
+
return cursor ? cursor : void 0;
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* @description Get the most recent scheduled task status record for a given task
|
|
1161
|
+
*/
|
|
1162
|
+
async getLastScheduledTaskStatus(task, status) {
|
|
1163
|
+
const filter = { processName: task };
|
|
1164
|
+
if (status) {
|
|
1165
|
+
filter.status = status;
|
|
1071
1166
|
}
|
|
1167
|
+
const sort = {
|
|
1168
|
+
datetime: -1
|
|
1169
|
+
};
|
|
1170
|
+
const cursor = await this.tables?.scheduler?.findOne(filter).sort(sort).limit(1).lean();
|
|
1171
|
+
return cursor ? cursor : void 0;
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* @description Create the status of a scheduled task
|
|
1175
|
+
*/
|
|
1176
|
+
async createScheduledTaskStatus(taskName, status) {
|
|
1177
|
+
const now = (/* @__PURE__ */ new Date()).getTime();
|
|
1178
|
+
const doc = ScheduledTaskSchema.parse({
|
|
1179
|
+
processName: taskName,
|
|
1180
|
+
datetime: now,
|
|
1181
|
+
status
|
|
1182
|
+
});
|
|
1183
|
+
const taskRecord = await this.tables?.scheduler.create(doc);
|
|
1184
|
+
return taskRecord._id;
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* @description Update the status of a scheduled task and an optional result
|
|
1188
|
+
*/
|
|
1189
|
+
async updateScheduledTaskStatus(taskId, status, result) {
|
|
1190
|
+
const update = {
|
|
1191
|
+
status,
|
|
1192
|
+
updated: (/* @__PURE__ */ new Date()).getTime(),
|
|
1193
|
+
...result && { result }
|
|
1194
|
+
};
|
|
1195
|
+
const filter = { _id: taskId };
|
|
1196
|
+
await this.tables?.scheduler.updateOne(
|
|
1197
|
+
filter,
|
|
1198
|
+
{ $set: update },
|
|
1199
|
+
{
|
|
1200
|
+
upsert: false
|
|
1201
|
+
}
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* @description Clean up the scheduled task status records
|
|
1206
|
+
*/
|
|
1207
|
+
async cleanupScheduledTaskStatus(status) {
|
|
1208
|
+
const filter = {
|
|
1209
|
+
status
|
|
1210
|
+
};
|
|
1211
|
+
await this.tables?.scheduler.deleteMany(filter);
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* @description Update the client records
|
|
1215
|
+
*/
|
|
1216
|
+
async updateClientRecords(clientRecords) {
|
|
1217
|
+
const ops = clientRecords.map((record) => {
|
|
1218
|
+
const clientRecord = {
|
|
1219
|
+
account: record.account,
|
|
1220
|
+
settings: record.settings,
|
|
1221
|
+
tier: record.tier
|
|
1222
|
+
};
|
|
1223
|
+
const filter = {
|
|
1224
|
+
account: record.account
|
|
1225
|
+
};
|
|
1226
|
+
return {
|
|
1227
|
+
updateOne: {
|
|
1228
|
+
filter,
|
|
1229
|
+
update: {
|
|
1230
|
+
$set: clientRecord
|
|
1231
|
+
},
|
|
1232
|
+
upsert: true
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1235
|
+
});
|
|
1236
|
+
await this.tables?.client.bulkWrite(ops);
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* @description Get a client record
|
|
1240
|
+
*/
|
|
1241
|
+
async getClientRecord(account) {
|
|
1242
|
+
const filter = { account };
|
|
1243
|
+
const doc = await this.tables?.client.findOne(filter).lean();
|
|
1244
|
+
return doc ? doc : void 0;
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* @description Store a detector key
|
|
1248
|
+
*/
|
|
1249
|
+
async storeDetectorKey(detectorKey) {
|
|
1250
|
+
return this.tables?.detector.create({
|
|
1251
|
+
detectorKey,
|
|
1252
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
/** @description Remove a detector key */
|
|
1256
|
+
async removeDetectorKey(detectorKey) {
|
|
1257
|
+
const filter = { detectorKey };
|
|
1258
|
+
const expiresAt = new Date(Date.now() + 10 * 60 * 1e3);
|
|
1259
|
+
await this.tables?.detector.updateOne(filter, {
|
|
1260
|
+
$set: { expiresAt }
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* @description Get valid detector keys
|
|
1265
|
+
*/
|
|
1266
|
+
async getDetectorKeys() {
|
|
1267
|
+
const keyRecords = await this.tables?.detector.find(
|
|
1268
|
+
{
|
|
1269
|
+
$or: [{ expiresAt: { $exists: false } }, { expiresAt: null }]
|
|
1270
|
+
},
|
|
1271
|
+
{ detectorKey: 1 }
|
|
1272
|
+
).sort({ createdAt: -1 }).lean();
|
|
1273
|
+
return (keyRecords || []).map((record) => record.detectorKey);
|
|
1274
|
+
}
|
|
1072
1275
|
}
|
|
1073
|
-
|
|
1276
|
+
export {
|
|
1277
|
+
ProviderDatabase
|
|
1278
|
+
};
|