@prosopo/database 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const types = require("@prosopo/types");
|
|
4
|
+
const mongo = require("./mongo.cjs");
|
|
5
|
+
const mongoMemory = require("./mongoMemory.cjs");
|
|
6
|
+
const Databases = {
|
|
7
|
+
[types.DatabaseTypes.Values.mongo]: mongo.ProsopoDatabase,
|
|
8
|
+
[types.DatabaseTypes.Values.mongoMemory]: mongoMemory.MongoMemoryDatabase
|
|
9
|
+
};
|
|
10
|
+
exports.Databases = Databases;
|
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const types = require("@prosopo/types");
|
|
4
|
+
const common = require("@prosopo/common");
|
|
5
|
+
const typesDatabase = require("@prosopo/types-database");
|
|
6
|
+
const mongodb = require("mongodb");
|
|
7
|
+
const util = require("@polkadot/util");
|
|
8
|
+
const mongoose = require("mongoose");
|
|
9
|
+
mongoose.set("strictQuery", false);
|
|
10
|
+
const DEFAULT_ENDPOINT = "mongodb://127.0.0.1:27017";
|
|
11
|
+
class ProsopoDatabase extends common.AsyncFactory {
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
this.url = "";
|
|
15
|
+
this.dbname = "";
|
|
16
|
+
this.logger = common.getLoggerDefault();
|
|
17
|
+
}
|
|
18
|
+
async init(url, dbname, logger, authSource) {
|
|
19
|
+
const baseEndpoint = url || DEFAULT_ENDPOINT;
|
|
20
|
+
const parsedUrl = new URL(baseEndpoint);
|
|
21
|
+
parsedUrl.pathname = dbname;
|
|
22
|
+
if (authSource) {
|
|
23
|
+
parsedUrl.searchParams.set("authSource", authSource);
|
|
24
|
+
}
|
|
25
|
+
this.url = parsedUrl.toString();
|
|
26
|
+
this.dbname = dbname;
|
|
27
|
+
this.logger = logger;
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* @description Connect to the database and set the various tables
|
|
32
|
+
*/
|
|
33
|
+
async connect() {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
if (this.connection) {
|
|
36
|
+
resolve();
|
|
37
|
+
}
|
|
38
|
+
this.logger.info(`Mongo url: ${this.url.replace(/\w+:\w+/, "<Credentials>")}`);
|
|
39
|
+
this.connection = mongoose.createConnection(this.url, {
|
|
40
|
+
dbName: this.dbname,
|
|
41
|
+
serverApi: mongodb.ServerApiVersion.v1
|
|
42
|
+
});
|
|
43
|
+
this.tables = {
|
|
44
|
+
captcha: this.connection.models.Captcha || this.connection.model("Captcha", typesDatabase.CaptchaRecordSchema),
|
|
45
|
+
dataset: this.connection.models.Dataset || this.connection.model("Dataset", typesDatabase.DatasetRecordSchema),
|
|
46
|
+
solution: this.connection.models.Solution || this.connection.model("Solution", typesDatabase.SolutionRecordSchema),
|
|
47
|
+
commitment: this.connection.models.UserCommitment || this.connection.model("UserCommitment", typesDatabase.UserCommitmentRecordSchema),
|
|
48
|
+
usersolution: this.connection.models.UserSolution || this.connection.model("UserSolution", typesDatabase.UserSolutionRecordSchema),
|
|
49
|
+
pending: this.connection.models.Pending || this.connection.model("Pending", typesDatabase.PendingRecordSchema),
|
|
50
|
+
scheduler: this.connection.models.Scheduler || this.connection.model("Scheduler", typesDatabase.ScheduledTaskRecordSchema)
|
|
51
|
+
};
|
|
52
|
+
this.connection.once("open", resolve).on("error", (e) => {
|
|
53
|
+
this.logger.warn(`mongoose connection error`);
|
|
54
|
+
if (e.message.code === "ETIMEDOUT") {
|
|
55
|
+
this.logger.error(e);
|
|
56
|
+
mongoose.connect(this.url);
|
|
57
|
+
}
|
|
58
|
+
this.logger.error(e);
|
|
59
|
+
reject(new common.ProsopoEnvError(e, "DATABASE.CONNECT_ERROR", {}, this.url));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/** Close connection to the database */
|
|
64
|
+
async close() {
|
|
65
|
+
this.logger.info(`Closing connection to ${this.url}`);
|
|
66
|
+
await new Promise(async (resolve, reject) => {
|
|
67
|
+
mongoose.connection.close().then(() => {
|
|
68
|
+
this.logger.info(`Connection to ${this.url} closed`);
|
|
69
|
+
resolve();
|
|
70
|
+
}).catch(reject);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* @description Load a dataset to the database
|
|
75
|
+
* @param {Dataset} dataset
|
|
76
|
+
*/
|
|
77
|
+
async storeDataset(dataset) {
|
|
78
|
+
var _a, _b, _c;
|
|
79
|
+
try {
|
|
80
|
+
this.logger.info(`Storing dataset in database`);
|
|
81
|
+
const parsedDataset = types.DatasetWithIdsAndTreeSchema.parse(dataset);
|
|
82
|
+
const datasetDoc = {
|
|
83
|
+
datasetId: parsedDataset.datasetId,
|
|
84
|
+
datasetContentId: parsedDataset.datasetContentId,
|
|
85
|
+
format: parsedDataset.format,
|
|
86
|
+
contentTree: parsedDataset.contentTree,
|
|
87
|
+
solutionTree: parsedDataset.solutionTree
|
|
88
|
+
};
|
|
89
|
+
await ((_a = this.tables) == null ? void 0 : _a.dataset.updateOne(
|
|
90
|
+
{ datasetId: parsedDataset.datasetId },
|
|
91
|
+
{ $set: datasetDoc },
|
|
92
|
+
{ upsert: true }
|
|
93
|
+
));
|
|
94
|
+
this.logger.info("parsedDataset.captchas", parsedDataset.captchas);
|
|
95
|
+
const captchaDocs = parsedDataset.captchas.map(({ solution, ...captcha }, index) => ({
|
|
96
|
+
...captcha,
|
|
97
|
+
datasetId: parsedDataset.datasetId,
|
|
98
|
+
datasetContentId: parsedDataset.datasetContentId,
|
|
99
|
+
index,
|
|
100
|
+
solved: !!(solution == null ? void 0 : solution.length)
|
|
101
|
+
}));
|
|
102
|
+
this.logger.info(`Inserting captcha records`);
|
|
103
|
+
if (captchaDocs.length) {
|
|
104
|
+
await ((_b = this.tables) == null ? void 0 : _b.captcha.bulkWrite(
|
|
105
|
+
captchaDocs.map((captchaDoc) => ({
|
|
106
|
+
updateOne: {
|
|
107
|
+
filter: { captchaId: captchaDoc.captchaId },
|
|
108
|
+
update: { $set: captchaDoc },
|
|
109
|
+
upsert: true
|
|
110
|
+
}
|
|
111
|
+
}))
|
|
112
|
+
));
|
|
113
|
+
}
|
|
114
|
+
const captchaSolutionDocs = parsedDataset.captchas.filter(({ solution }) => solution == null ? void 0 : solution.length).map((captcha) => ({
|
|
115
|
+
captchaId: captcha.captchaId,
|
|
116
|
+
captchaContentId: captcha.captchaContentId,
|
|
117
|
+
solution: captcha.solution,
|
|
118
|
+
salt: captcha.salt,
|
|
119
|
+
datasetId: parsedDataset.datasetId,
|
|
120
|
+
datasetContentId: parsedDataset.datasetContentId
|
|
121
|
+
}));
|
|
122
|
+
this.logger.info(`Inserting solution records`);
|
|
123
|
+
if (captchaSolutionDocs.length) {
|
|
124
|
+
await ((_c = this.tables) == null ? void 0 : _c.solution.bulkWrite(
|
|
125
|
+
captchaSolutionDocs.map((captchaSolutionDoc) => ({
|
|
126
|
+
updateOne: {
|
|
127
|
+
filter: { captchaId: captchaSolutionDoc.captchaId },
|
|
128
|
+
update: { $set: captchaSolutionDoc },
|
|
129
|
+
upsert: true
|
|
130
|
+
}
|
|
131
|
+
}))
|
|
132
|
+
));
|
|
133
|
+
}
|
|
134
|
+
this.logger.info(`Dataset stored in database`);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
throw new common.ProsopoEnvError(err, "DATABASE.DATASET_LOAD_FAILED");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** @description Get a dataset from the database
|
|
140
|
+
* @param {string} datasetId
|
|
141
|
+
*/
|
|
142
|
+
async getDataset(datasetId) {
|
|
143
|
+
var _a, _b, _c;
|
|
144
|
+
const datasetDoc = await ((_a = this.tables) == null ? void 0 : _a.dataset.findOne({ datasetId }).lean());
|
|
145
|
+
if (datasetDoc) {
|
|
146
|
+
const { datasetContentId, format, contentTree, solutionTree } = datasetDoc;
|
|
147
|
+
const captchas = await ((_b = this.tables) == null ? void 0 : _b.captcha.find({ datasetId }).lean()) || [];
|
|
148
|
+
const solutions = await ((_c = this.tables) == null ? void 0 : _c.solution.find({ datasetId }).lean()) || [];
|
|
149
|
+
const solutionsKeyed = {};
|
|
150
|
+
for (const solution of solutions) {
|
|
151
|
+
solutionsKeyed[solution.captchaId] = solution;
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
datasetId,
|
|
155
|
+
datasetContentId,
|
|
156
|
+
format,
|
|
157
|
+
contentTree: contentTree || [],
|
|
158
|
+
solutionTree: solutionTree || [],
|
|
159
|
+
captchas: captchas.map((captchaDoc) => {
|
|
160
|
+
const { captchaId, captchaContentId, items, target, salt, solved } = captchaDoc;
|
|
161
|
+
const solution = solutionsKeyed[captchaId];
|
|
162
|
+
return {
|
|
163
|
+
captchaId,
|
|
164
|
+
captchaContentId,
|
|
165
|
+
solved: !!solved,
|
|
166
|
+
salt,
|
|
167
|
+
items,
|
|
168
|
+
target,
|
|
169
|
+
solution: solved && solution ? solution.solution : []
|
|
170
|
+
};
|
|
171
|
+
})
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
throw new common.ProsopoEnvError("DATABASE.DATASET_GET_FAILED", void 0, {}, datasetId);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* @description Get random captchas that are solved or not solved
|
|
178
|
+
* @param {boolean} solved `true` when captcha is solved
|
|
179
|
+
* @param {string} datasetId the id of the data set
|
|
180
|
+
* @param {number} size the number of records to be returned
|
|
181
|
+
*/
|
|
182
|
+
async getRandomCaptcha(solved, datasetId, size) {
|
|
183
|
+
var _a;
|
|
184
|
+
if (!util.isHex(datasetId)) {
|
|
185
|
+
throw new common.ProsopoEnvError("DATABASE.INVALID_HASH", this.getRandomCaptcha.name, {}, datasetId);
|
|
186
|
+
}
|
|
187
|
+
const sampleSize = size ? Math.abs(Math.trunc(size)) : 1;
|
|
188
|
+
const cursor = (_a = this.tables) == null ? void 0 : _a.captcha.aggregate([
|
|
189
|
+
{ $match: { datasetId, solved } },
|
|
190
|
+
{ $sample: { size: sampleSize } },
|
|
191
|
+
{
|
|
192
|
+
$project: {
|
|
193
|
+
datasetId: 1,
|
|
194
|
+
datasetContentId: 1,
|
|
195
|
+
captchaId: 1,
|
|
196
|
+
captchaContentId: 1,
|
|
197
|
+
items: 1,
|
|
198
|
+
target: 1
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
]);
|
|
202
|
+
const docs = await cursor;
|
|
203
|
+
if (docs && docs.length) {
|
|
204
|
+
return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
|
|
205
|
+
}
|
|
206
|
+
throw new common.ProsopoEnvError(
|
|
207
|
+
"DATABASE.CAPTCHA_GET_FAILED",
|
|
208
|
+
this.getRandomCaptcha.name,
|
|
209
|
+
{},
|
|
210
|
+
{
|
|
211
|
+
solved,
|
|
212
|
+
datasetId,
|
|
213
|
+
size
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* @description Get captchas by id
|
|
219
|
+
* @param {string[]} captchaId
|
|
220
|
+
*/
|
|
221
|
+
async getCaptchaById(captchaId) {
|
|
222
|
+
var _a;
|
|
223
|
+
const cursor = (_a = this.tables) == null ? void 0 : _a.captcha.find({ captchaId: { $in: captchaId } }).lean();
|
|
224
|
+
const docs = await cursor;
|
|
225
|
+
if (docs && docs.length) {
|
|
226
|
+
return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
|
|
227
|
+
}
|
|
228
|
+
throw new common.ProsopoEnvError("DATABASE.CAPTCHA_GET_FAILED", this.getCaptchaById.name, {}, captchaId);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* @description Update a captcha
|
|
232
|
+
* @param {Captcha} captcha
|
|
233
|
+
* @param {string} datasetId the id of the data set
|
|
234
|
+
*/
|
|
235
|
+
async updateCaptcha(captcha, datasetId) {
|
|
236
|
+
var _a;
|
|
237
|
+
if (!util.isHex(datasetId)) {
|
|
238
|
+
throw new common.ProsopoEnvError("DATABASE.INVALID_HASH", this.updateCaptcha.name, {}, datasetId);
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
await ((_a = this.tables) == null ? void 0 : _a.captcha.updateOne({ datasetId }, { $set: captcha }, { upsert: false }));
|
|
242
|
+
} catch (err) {
|
|
243
|
+
throw new common.ProsopoEnvError(err);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* @description Remove captchas
|
|
248
|
+
*/
|
|
249
|
+
async removeCaptchas(captchaIds) {
|
|
250
|
+
var _a;
|
|
251
|
+
await ((_a = this.tables) == null ? void 0 : _a.captcha.deleteMany({ captchaId: { $in: captchaIds } }));
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* @description Get a dataset by Id
|
|
255
|
+
*/
|
|
256
|
+
async getDatasetDetails(datasetId) {
|
|
257
|
+
var _a;
|
|
258
|
+
if (!util.isHex(datasetId)) {
|
|
259
|
+
throw new common.ProsopoEnvError("DATABASE.INVALID_HASH", this.getDatasetDetails.name, {}, datasetId);
|
|
260
|
+
}
|
|
261
|
+
const doc = await ((_a = this.tables) == null ? void 0 : _a.dataset.findOne({ datasetId }).lean());
|
|
262
|
+
if (doc) {
|
|
263
|
+
return doc;
|
|
264
|
+
}
|
|
265
|
+
throw new common.ProsopoEnvError("DATABASE.DATASET_GET_FAILED", this.getDatasetDetails.name, {}, datasetId);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* @description Store a Dapp User's captcha solution commitment
|
|
269
|
+
*/
|
|
270
|
+
async storeDappUserSolution(captchas, commit) {
|
|
271
|
+
var _a, _b;
|
|
272
|
+
const commitmentRecord = typesDatabase.UserCommitmentSchema.parse(commit);
|
|
273
|
+
if (captchas.length) {
|
|
274
|
+
await ((_a = this.tables) == null ? void 0 : _a.commitment.updateOne(
|
|
275
|
+
{
|
|
276
|
+
id: commit.id
|
|
277
|
+
},
|
|
278
|
+
commitmentRecord,
|
|
279
|
+
{ upsert: true }
|
|
280
|
+
));
|
|
281
|
+
const ops = captchas.map((captcha) => ({
|
|
282
|
+
updateOne: {
|
|
283
|
+
filter: { commitmentId: commit.id, captchaId: captcha.captchaId },
|
|
284
|
+
update: {
|
|
285
|
+
$set: {
|
|
286
|
+
captchaId: captcha.captchaId,
|
|
287
|
+
captchaContentId: captcha.captchaContentId,
|
|
288
|
+
salt: captcha.salt,
|
|
289
|
+
solution: captcha.solution,
|
|
290
|
+
commitmentId: commit.id,
|
|
291
|
+
processed: false
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
upsert: true
|
|
295
|
+
}
|
|
296
|
+
}));
|
|
297
|
+
await ((_b = this.tables) == null ? void 0 : _b.usersolution.bulkWrite(ops));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/** @description Get processed Dapp User captcha solutions from the user solution table
|
|
301
|
+
*/
|
|
302
|
+
async getProcessedDappUserSolutions() {
|
|
303
|
+
var _a;
|
|
304
|
+
const docs = await ((_a = this.tables) == null ? void 0 : _a.usersolution.find({ processed: true }).lean());
|
|
305
|
+
return docs ? docs.map((doc) => typesDatabase.UserSolutionSchema.parse(doc)) : [];
|
|
306
|
+
}
|
|
307
|
+
/** @description Get processed Dapp User captcha commitments from the commitments table
|
|
308
|
+
*/
|
|
309
|
+
async getProcessedDappUserCommitments() {
|
|
310
|
+
var _a;
|
|
311
|
+
const docs = await ((_a = this.tables) == null ? void 0 : _a.commitment.find({ processed: true }).lean());
|
|
312
|
+
return docs ? docs.map((doc) => typesDatabase.UserCommitmentSchema.parse(doc)) : [];
|
|
313
|
+
}
|
|
314
|
+
/** @description Get Dapp User captcha commitments from the commitments table that have not been batched on-chain
|
|
315
|
+
*/
|
|
316
|
+
async getUnbatchedDappUserCommitments() {
|
|
317
|
+
var _a;
|
|
318
|
+
const docs = await ((_a = this.tables) == null ? void 0 : _a.commitment.find({ batched: false }).lean());
|
|
319
|
+
return docs ? docs.map((doc) => typesDatabase.UserCommitmentSchema.parse(doc)) : [];
|
|
320
|
+
}
|
|
321
|
+
/** @description Get Dapp User captcha commitments from the commitments table that have been batched on-chain
|
|
322
|
+
*/
|
|
323
|
+
async getBatchedDappUserCommitments() {
|
|
324
|
+
var _a;
|
|
325
|
+
const docs = await ((_a = this.tables) == null ? void 0 : _a.commitment.find({ batched: true }).lean());
|
|
326
|
+
return docs ? docs.map((doc) => typesDatabase.UserCommitmentSchema.parse(doc)) : [];
|
|
327
|
+
}
|
|
328
|
+
/** @description Remove processed Dapp User captcha solutions from the user solution table
|
|
329
|
+
*/
|
|
330
|
+
async removeProcessedDappUserSolutions(commitmentIds) {
|
|
331
|
+
var _a;
|
|
332
|
+
return await ((_a = this.tables) == null ? void 0 : _a.usersolution.deleteMany({ processed: true, commitmentId: { $in: commitmentIds } }));
|
|
333
|
+
}
|
|
334
|
+
/** @description Remove processed Dapp User captcha commitments from the user commitments table
|
|
335
|
+
*/
|
|
336
|
+
async removeProcessedDappUserCommitments(commitmentIds) {
|
|
337
|
+
var _a;
|
|
338
|
+
return await ((_a = this.tables) == null ? void 0 : _a.commitment.deleteMany({ processed: true, id: { $in: commitmentIds } }));
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* @description Store a Dapp User's pending record
|
|
342
|
+
*/
|
|
343
|
+
async storeDappUserPending(userAccount, requestHash, salt, deadlineTimestamp, requestedAtBlock) {
|
|
344
|
+
var _a;
|
|
345
|
+
if (!util.isHex(requestHash)) {
|
|
346
|
+
throw new common.ProsopoEnvError("DATABASE.INVALID_HASH", this.storeDappUserPending.name, {}, requestHash);
|
|
347
|
+
}
|
|
348
|
+
const pendingRecord = {
|
|
349
|
+
accountId: userAccount,
|
|
350
|
+
pending: true,
|
|
351
|
+
salt,
|
|
352
|
+
requestHash,
|
|
353
|
+
deadlineTimestamp,
|
|
354
|
+
requestedAtBlock
|
|
355
|
+
};
|
|
356
|
+
await ((_a = this.tables) == null ? void 0 : _a.pending.updateOne({ requestHash }, { $set: pendingRecord }, { upsert: true }));
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* @description Get a Dapp user's pending record
|
|
360
|
+
*/
|
|
361
|
+
async getDappUserPending(requestHash) {
|
|
362
|
+
var _a;
|
|
363
|
+
if (!util.isHex(requestHash)) {
|
|
364
|
+
throw new common.ProsopoEnvError("DATABASE.INVALID_HASH", this.getDappUserPending.name, {}, requestHash);
|
|
365
|
+
}
|
|
366
|
+
const doc = await ((_a = this.tables) == null ? void 0 : _a.pending.findOne({ requestHash }).lean());
|
|
367
|
+
if (doc) {
|
|
368
|
+
return doc;
|
|
369
|
+
}
|
|
370
|
+
throw new common.ProsopoEnvError("DATABASE.PENDING_RECORD_NOT_FOUND", this.getDappUserPending.name);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* @description Update a Dapp User's pending record
|
|
374
|
+
*/
|
|
375
|
+
async updateDappUserPendingStatus(userAccount, requestHash, approve) {
|
|
376
|
+
var _a;
|
|
377
|
+
if (!util.isHex(requestHash)) {
|
|
378
|
+
throw new common.ProsopoEnvError("DATABASE.INVALID_HASH", this.updateDappUserPendingStatus.name, {}, requestHash);
|
|
379
|
+
}
|
|
380
|
+
await ((_a = this.tables) == null ? void 0 : _a.pending.updateOne(
|
|
381
|
+
{ requestHash },
|
|
382
|
+
{
|
|
383
|
+
$set: {
|
|
384
|
+
accountId: userAccount,
|
|
385
|
+
pending: false,
|
|
386
|
+
approved: approve,
|
|
387
|
+
requestHash
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
{ upsert: true }
|
|
391
|
+
));
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* @description Get all unsolved captchas
|
|
395
|
+
*/
|
|
396
|
+
async getAllCaptchasByDatasetId(datasetId, state) {
|
|
397
|
+
var _a;
|
|
398
|
+
const cursor = (_a = this.tables) == null ? void 0 : _a.captcha.find({
|
|
399
|
+
datasetId,
|
|
400
|
+
solved: state === types.CaptchaStates.Solved
|
|
401
|
+
}).lean();
|
|
402
|
+
const docs = await cursor;
|
|
403
|
+
if (docs) {
|
|
404
|
+
return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
|
|
405
|
+
}
|
|
406
|
+
throw new common.ProsopoEnvError("DATABASE.CAPTCHA_GET_FAILED");
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* @description Get all dapp user solutions by captchaIds
|
|
410
|
+
*/
|
|
411
|
+
async getAllDappUserSolutions(captchaId) {
|
|
412
|
+
var _a, _b;
|
|
413
|
+
const cursor = (_b = (_a = this.tables) == null ? void 0 : _a.usersolution) == null ? void 0 : _b.find({ captchaId: { $in: captchaId } }).lean();
|
|
414
|
+
const docs = await cursor;
|
|
415
|
+
if (docs) {
|
|
416
|
+
return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
|
|
417
|
+
}
|
|
418
|
+
throw new common.ProsopoEnvError("DATABASE.SOLUTION_GET_FAILED");
|
|
419
|
+
}
|
|
420
|
+
async getDatasetIdWithSolvedCaptchasOfSizeN(solvedCaptchaCount) {
|
|
421
|
+
var _a;
|
|
422
|
+
const cursor = (_a = this.tables) == null ? void 0 : _a.solution.aggregate([
|
|
423
|
+
{
|
|
424
|
+
$match: {}
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
$group: {
|
|
428
|
+
_id: "$datasetId",
|
|
429
|
+
count: { $sum: 1 }
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
$match: {
|
|
434
|
+
count: { $gte: solvedCaptchaCount }
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
$sample: { size: 1 }
|
|
439
|
+
}
|
|
440
|
+
]);
|
|
441
|
+
const docs = await cursor;
|
|
442
|
+
if (docs && docs.length) {
|
|
443
|
+
return docs[0]._id;
|
|
444
|
+
}
|
|
445
|
+
throw new common.ProsopoEnvError("DATABASE.DATASET_WITH_SOLUTIONS_GET_FAILED");
|
|
446
|
+
}
|
|
447
|
+
async getRandomSolvedCaptchasFromSingleDataset(datasetId, size) {
|
|
448
|
+
var _a;
|
|
449
|
+
if (!util.isHex(datasetId)) {
|
|
450
|
+
throw new common.ProsopoEnvError(
|
|
451
|
+
"DATABASE.INVALID_HASH",
|
|
452
|
+
this.getRandomSolvedCaptchasFromSingleDataset.name,
|
|
453
|
+
{},
|
|
454
|
+
datasetId
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
const sampleSize = size ? Math.abs(Math.trunc(size)) : 1;
|
|
458
|
+
const cursor = (_a = this.tables) == null ? void 0 : _a.solution.aggregate([
|
|
459
|
+
{ $match: { datasetId } },
|
|
460
|
+
{ $sample: { size: sampleSize } },
|
|
461
|
+
{
|
|
462
|
+
$project: {
|
|
463
|
+
captchaId: 1,
|
|
464
|
+
captchaContentId: 1,
|
|
465
|
+
solution: 1
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
]);
|
|
469
|
+
const docs = await cursor;
|
|
470
|
+
if (docs && docs.length) {
|
|
471
|
+
return docs;
|
|
472
|
+
}
|
|
473
|
+
throw new common.ProsopoEnvError("DATABASE.SOLUTION_GET_FAILED");
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* @description Get dapp user solution by ID
|
|
477
|
+
* @param {string[]} commitmentId
|
|
478
|
+
*/
|
|
479
|
+
async getDappUserSolutionById(commitmentId) {
|
|
480
|
+
var _a, _b;
|
|
481
|
+
const cursor = (_b = (_a = this.tables) == null ? void 0 : _a.usersolution) == null ? void 0 : _b.findOne(
|
|
482
|
+
{
|
|
483
|
+
commitmentId
|
|
484
|
+
},
|
|
485
|
+
{ projection: { _id: 0 } }
|
|
486
|
+
).lean();
|
|
487
|
+
const doc = await cursor;
|
|
488
|
+
if (doc) {
|
|
489
|
+
return doc;
|
|
490
|
+
}
|
|
491
|
+
throw new common.ProsopoEnvError("DATABASE.SOLUTION_GET_FAILED", this.getCaptchaById.name, {}, commitmentId);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* @description Get dapp user commitment by user account
|
|
495
|
+
* @param commitmentId
|
|
496
|
+
*/
|
|
497
|
+
async getDappUserCommitmentById(commitmentId) {
|
|
498
|
+
var _a, _b;
|
|
499
|
+
const commitmentCursor = (_b = (_a = this.tables) == null ? void 0 : _a.commitment) == null ? void 0 : _b.findOne({ id: commitmentId }).lean();
|
|
500
|
+
const doc = await commitmentCursor;
|
|
501
|
+
return doc ? typesDatabase.UserCommitmentSchema.parse(doc) : void 0;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* @description Get dapp user commitment by user account
|
|
505
|
+
* @param {string[]} userAccount
|
|
506
|
+
*/
|
|
507
|
+
async getDappUserCommitmentByAccount(userAccount) {
|
|
508
|
+
var _a, _b;
|
|
509
|
+
const docs = await ((_b = (_a = this.tables) == null ? void 0 : _a.commitment) == null ? void 0 : _b.find({ userAccount }).lean());
|
|
510
|
+
return docs ? docs : [];
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* @description Approve a dapp user's solution
|
|
514
|
+
* @param {string[]} commitmentId
|
|
515
|
+
*/
|
|
516
|
+
async approveDappUserCommitment(commitmentId) {
|
|
517
|
+
var _a, _b;
|
|
518
|
+
try {
|
|
519
|
+
await ((_b = (_a = this.tables) == null ? void 0 : _a.commitment) == null ? void 0 : _b.findOneAndUpdate(
|
|
520
|
+
{ id: commitmentId },
|
|
521
|
+
{ $set: { status: types.CaptchaStatus.approved } },
|
|
522
|
+
{ upsert: false }
|
|
523
|
+
).lean());
|
|
524
|
+
} catch (err) {
|
|
525
|
+
throw new common.ProsopoEnvError(err, "DATABASE.SOLUTION_APPROVE_FAILED", {}, commitmentId);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* @description Flag a dapp user's solutions as used by calculated solution
|
|
530
|
+
* @param {string[]} captchaIds
|
|
531
|
+
*/
|
|
532
|
+
async flagProcessedDappUserSolutions(captchaIds) {
|
|
533
|
+
var _a, _b;
|
|
534
|
+
try {
|
|
535
|
+
await ((_b = (_a = this.tables) == null ? void 0 : _a.usersolution) == null ? void 0 : _b.updateMany({ captchaId: { $in: captchaIds } }, { $set: { processed: true } }, { upsert: false }).lean());
|
|
536
|
+
} catch (err) {
|
|
537
|
+
throw new common.ProsopoEnvError(err, "DATABASE.SOLUTION_FLAG_FAILED", {}, captchaIds);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* @description Flag dapp users' commitments as used by calculated solution
|
|
542
|
+
* @param {string[]} commitmentIds
|
|
543
|
+
*/
|
|
544
|
+
async flagProcessedDappUserCommitments(commitmentIds) {
|
|
545
|
+
var _a, _b;
|
|
546
|
+
try {
|
|
547
|
+
const distinctCommitmentIds = [...new Set(commitmentIds)];
|
|
548
|
+
await ((_b = (_a = this.tables) == null ? void 0 : _a.commitment) == null ? void 0 : _b.updateMany({ id: { $in: distinctCommitmentIds } }, { $set: { processed: true } }, { upsert: false }).lean());
|
|
549
|
+
} catch (err) {
|
|
550
|
+
throw new common.ProsopoEnvError(err, "DATABASE.COMMITMENT_FLAG_FAILED", {}, commitmentIds);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* @description Flag dapp users' commitments as used by calculated solution
|
|
555
|
+
* @param {string[]} commitmentIds
|
|
556
|
+
*/
|
|
557
|
+
async flagBatchedDappUserCommitments(commitmentIds) {
|
|
558
|
+
var _a, _b;
|
|
559
|
+
try {
|
|
560
|
+
const distinctCommitmentIds = [...new Set(commitmentIds)];
|
|
561
|
+
await ((_b = (_a = this.tables) == null ? void 0 : _a.commitment) == null ? void 0 : _b.updateMany({ id: { $in: distinctCommitmentIds } }, { $set: { batched: true } }, { upsert: false }).lean());
|
|
562
|
+
} catch (err) {
|
|
563
|
+
throw new common.ProsopoEnvError(err, "DATABASE.COMMITMENT_FLAG_FAILED", {}, commitmentIds);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* @description Get the last batch commit time or return 0 if none
|
|
568
|
+
*/
|
|
569
|
+
async getLastBatchCommitTime() {
|
|
570
|
+
var _a, _b;
|
|
571
|
+
const cursor = (_b = (_a = this.tables) == null ? void 0 : _a.scheduler) == null ? void 0 : _b.findOne({ processName: types.ScheduledTaskNames.BatchCommitment, status: types.ScheduledTaskStatus.Completed }).sort({ timestamp: -1 });
|
|
572
|
+
const doc = await (cursor == null ? void 0 : cursor.lean());
|
|
573
|
+
if (doc) {
|
|
574
|
+
return doc.datetime;
|
|
575
|
+
}
|
|
576
|
+
return /* @__PURE__ */ new Date(0);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* @description Get a scheduled task status record by task ID and status
|
|
580
|
+
*/
|
|
581
|
+
async getScheduledTaskStatus(taskId, status) {
|
|
582
|
+
var _a, _b;
|
|
583
|
+
const cursor = await ((_b = (_a = this.tables) == null ? void 0 : _a.scheduler) == null ? void 0 : _b.findOne({ taskId, status }).lean());
|
|
584
|
+
return cursor ? cursor : void 0;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* @description Get the most recent scheduled task status record for a given task
|
|
588
|
+
*/
|
|
589
|
+
async getLastScheduledTaskStatus(task, status) {
|
|
590
|
+
var _a, _b;
|
|
591
|
+
const lookup = { processName: task };
|
|
592
|
+
if (status) {
|
|
593
|
+
lookup["status"] = status;
|
|
594
|
+
}
|
|
595
|
+
const cursor = await ((_b = (_a = this.tables) == null ? void 0 : _a.scheduler) == null ? void 0 : _b.findOne(lookup).sort({ datetime: -1 }).lean());
|
|
596
|
+
return cursor ? cursor : void 0;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* @description Store the status of a scheduled task and an optional result
|
|
600
|
+
*/
|
|
601
|
+
async storeScheduledTaskStatus(taskId, task, status, result) {
|
|
602
|
+
var _a;
|
|
603
|
+
const now = /* @__PURE__ */ new Date();
|
|
604
|
+
const doc = typesDatabase.ScheduledTaskSchema.parse({
|
|
605
|
+
taskId,
|
|
606
|
+
processName: task,
|
|
607
|
+
datetime: now,
|
|
608
|
+
status,
|
|
609
|
+
...result && { result }
|
|
610
|
+
});
|
|
611
|
+
await ((_a = this.tables) == null ? void 0 : _a.scheduler.create(doc));
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
exports.ProsopoDatabase = ProsopoDatabase;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const mongo = require("./mongo.cjs");
|
|
4
|
+
const mongodbMemoryServer = require("mongodb-memory-server");
|
|
5
|
+
class MongoMemoryDatabase extends mongo.ProsopoDatabase {
|
|
6
|
+
async init(url, dbname, logger, authSource) {
|
|
7
|
+
const mongod = await mongodbMemoryServer.MongoMemoryServer.create();
|
|
8
|
+
const mongoMemoryURL = mongod.getUri();
|
|
9
|
+
await super.init(mongoMemoryURL, dbname, logger, authSource);
|
|
10
|
+
return this;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.MongoMemoryDatabase = MongoMemoryDatabase;
|
package/package.json
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prosopo/database",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Prosopo database plugins for provider",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"require": "./dist/cjs/index.cjs"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
7
13
|
"scripts": {
|
|
8
14
|
"clean": "tsc --build --clean",
|
|
9
|
-
"build": "tsc --build --verbose",
|
|
15
|
+
"build": "tsc --build --verbose tsconfig.json",
|
|
16
|
+
"build:cjs": "npx vite --config vite.cjs.config.ts build",
|
|
10
17
|
"lint": "npx eslint .",
|
|
11
18
|
"lint:fix": "npx eslint . --fix --config ../../.eslintrc.js"
|
|
12
19
|
},
|
|
@@ -21,9 +28,9 @@
|
|
|
21
28
|
},
|
|
22
29
|
"homepage": "https://github.com/prosopo/captcha#readme",
|
|
23
30
|
"dependencies": {
|
|
24
|
-
"@prosopo/common": "0.2.
|
|
25
|
-
"@prosopo/types": "0.2.
|
|
26
|
-
"@prosopo/types-database": "0.2.
|
|
31
|
+
"@prosopo/common": "0.2.1",
|
|
32
|
+
"@prosopo/types": "0.2.1",
|
|
33
|
+
"@prosopo/types-database": "0.2.1",
|
|
27
34
|
"mongodb": "5.8.0",
|
|
28
35
|
"mongodb-memory-server": "^8.7.2",
|
|
29
36
|
"mongoose": "^7.3.3",
|