@vue-skuilder/db 0.1.7 → 0.1.8-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{SyncStrategy-DnJRj-Xp.d.mts → SyncStrategy-CyATpyLQ.d.mts} +6 -0
- package/dist/{SyncStrategy-DnJRj-Xp.d.ts → SyncStrategy-CyATpyLQ.d.ts} +6 -0
- package/dist/core/index.d.mts +5 -5
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +131 -118
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +128 -115
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-BbW9EnZK.d.mts → dataLayerProvider-BInqI_RF.d.mts} +1 -1
- package/dist/{dataLayerProvider-6stCgDME.d.ts → dataLayerProvider-DqtNroSh.d.ts} +1 -1
- package/dist/impl/couch/index.d.mts +6 -6
- package/dist/impl/couch/index.d.ts +6 -6
- package/dist/impl/couch/index.js +1365 -1252
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +1359 -1246
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.mts +8 -6
- package/dist/impl/static/index.d.ts +8 -6
- package/dist/impl/static/index.js +253 -843
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +250 -842
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index-CLL31bEy.d.ts +137 -0
- package/dist/index-CUNnL38E.d.mts +137 -0
- package/dist/index.d.mts +10 -55
- package/dist/index.d.ts +10 -55
- package/dist/index.js +343 -170
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +340 -167
- package/dist/index.mjs.map +1 -1
- package/dist/{types-BvzcRAys.d.ts → types-BefDGkKa.d.ts} +1 -1
- package/dist/{types-CQQ80R5N.d.mts → types-DC-ckZug.d.mts} +1 -1
- package/dist/{types-legacy-CtrmkOLu.d.mts → types-legacy-Birv-Jx6.d.mts} +2 -2
- package/dist/{types-legacy-CtrmkOLu.d.ts → types-legacy-Birv-Jx6.d.ts} +2 -2
- package/dist/{userDB-DUY63VMN.d.ts → userDB-C33Hzjgn.d.mts} +10 -3
- package/dist/{userDB-7fM4tpgr.d.mts → userDB-DusL7OXe.d.ts} +10 -3
- package/dist/util/packer/index.d.mts +3 -63
- package/dist/util/packer/index.d.ts +3 -63
- package/dist/util/packer/index.js +53 -1
- package/dist/util/packer/index.js.map +1 -1
- package/dist/util/packer/index.mjs +53 -1
- package/dist/util/packer/index.mjs.map +1 -1
- package/package.json +7 -4
- package/src/core/types/types-legacy.ts +13 -1
- package/src/core/types/user.ts +9 -2
- package/src/core/util/index.ts +5 -4
- package/src/impl/common/BaseUserDB.ts +33 -22
- package/src/impl/common/SyncStrategy.ts +7 -0
- package/src/impl/common/index.ts +0 -1
- package/src/impl/common/userDBHelpers.ts +4 -4
- package/src/impl/couch/CouchDBSyncStrategy.ts +10 -0
- package/src/impl/couch/courseAPI.ts +7 -6
- package/src/impl/couch/index.ts +10 -5
- package/src/impl/couch/updateQueue.ts +12 -8
- package/src/impl/couch/user-course-relDB.ts +17 -27
- package/src/impl/static/NoOpSyncStrategy.ts +5 -0
- package/src/impl/static/StaticDataUnpacker.ts +18 -36
- package/src/impl/static/courseDB.ts +135 -17
- package/src/util/migrator/FileSystemAdapter.ts +20 -0
- package/src/util/migrator/StaticToCouchDBMigrator.ts +6 -0
- package/src/util/packer/CouchDBToStaticPacker.ts +92 -2
|
@@ -102,32 +102,31 @@ var init_SyncStrategy = __esm({
|
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
// src/core/types/types-legacy.ts
|
|
105
|
-
var GuestUsername,
|
|
105
|
+
var GuestUsername, DocTypePrefixes;
|
|
106
106
|
var init_types_legacy = __esm({
|
|
107
107
|
"src/core/types/types-legacy.ts"() {
|
|
108
108
|
"use strict";
|
|
109
109
|
init_logger();
|
|
110
110
|
GuestUsername = "Guest";
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
cardHistoryPrefix = "cardH";
|
|
111
|
+
DocTypePrefixes = {
|
|
112
|
+
["CARD" /* CARD */]: "c",
|
|
113
|
+
["DISPLAYABLE_DATA" /* DISPLAYABLE_DATA */]: "dd",
|
|
114
|
+
["TAG" /* TAG */]: "TAG",
|
|
115
|
+
["CARDRECORD" /* CARDRECORD */]: "cardH",
|
|
116
|
+
["SCHEDULED_CARD" /* SCHEDULED_CARD */]: "card_review_",
|
|
117
|
+
// Add other doctypes here as they get prefixed IDs
|
|
118
|
+
["DATASHAPE" /* DATASHAPE */]: "DATASHAPE",
|
|
119
|
+
["QUESTION" /* QUESTIONTYPE */]: "QUESTION",
|
|
120
|
+
["VIEW" /* VIEW */]: "VIEW",
|
|
121
|
+
["PEDAGOGY" /* PEDAGOGY */]: "PEDAGOGY",
|
|
122
|
+
["NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */]: "NAVIGATION_STRATEGY"
|
|
123
|
+
};
|
|
125
124
|
}
|
|
126
125
|
});
|
|
127
126
|
|
|
128
127
|
// src/core/util/index.ts
|
|
129
128
|
function getCardHistoryID(courseID, cardID) {
|
|
130
|
-
return `${
|
|
129
|
+
return `${DocTypePrefixes["CARDRECORD" /* CARDRECORD */]}-${courseID}-${cardID}`;
|
|
131
130
|
}
|
|
132
131
|
var init_util = __esm({
|
|
133
132
|
"src/core/util/index.ts"() {
|
|
@@ -210,11 +209,11 @@ function scheduleCardReviewLocal(userDB, review) {
|
|
|
210
209
|
const now = import_moment.default.utc();
|
|
211
210
|
logger.info(`Scheduling for review in: ${review.time.diff(now, "h") / 24} days`);
|
|
212
211
|
void userDB.put({
|
|
213
|
-
_id:
|
|
212
|
+
_id: DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */] + review.time.format(REVIEW_TIME_FORMAT),
|
|
214
213
|
cardId: review.card_id,
|
|
215
|
-
reviewTime: review.time,
|
|
214
|
+
reviewTime: review.time.toISOString(),
|
|
216
215
|
courseId: review.course_id,
|
|
217
|
-
scheduledAt: now,
|
|
216
|
+
scheduledAt: now.toISOString(),
|
|
218
217
|
scheduledFor: review.scheduledFor,
|
|
219
218
|
schedulingAgentId: review.schedulingAgentId
|
|
220
219
|
});
|
|
@@ -230,15 +229,15 @@ async function removeScheduledCardReviewLocal(userDB, reviewDocID) {
|
|
|
230
229
|
${JSON.stringify(err)}`);
|
|
231
230
|
});
|
|
232
231
|
}
|
|
233
|
-
var import_moment,
|
|
232
|
+
var import_moment, REVIEW_TIME_FORMAT, log;
|
|
234
233
|
var init_userDBHelpers = __esm({
|
|
235
234
|
"src/impl/common/userDBHelpers.ts"() {
|
|
236
235
|
"use strict";
|
|
237
236
|
import_moment = __toESM(require("moment"));
|
|
237
|
+
init_core();
|
|
238
238
|
init_logger();
|
|
239
239
|
init_pouchdb_setup();
|
|
240
240
|
init_dataDirectory();
|
|
241
|
-
REVIEW_PREFIX = "card_review_";
|
|
242
241
|
REVIEW_TIME_FORMAT = "YYYY-MM-DD--kk:mm:ss-SSS";
|
|
243
242
|
log = (s) => {
|
|
244
243
|
logger.info(s);
|
|
@@ -273,7 +272,10 @@ var init_updateQueue = __esm({
|
|
|
273
272
|
_className = "UpdateQueue";
|
|
274
273
|
pendingUpdates = {};
|
|
275
274
|
inprogressUpdates = {};
|
|
276
|
-
|
|
275
|
+
readDB;
|
|
276
|
+
// Database for read operations
|
|
277
|
+
writeDB;
|
|
278
|
+
// Database for write operations (local-first)
|
|
277
279
|
update(id, update) {
|
|
278
280
|
logger.debug(`Update requested on doc: ${id}`);
|
|
279
281
|
if (this.pendingUpdates[id]) {
|
|
@@ -283,24 +285,25 @@ var init_updateQueue = __esm({
|
|
|
283
285
|
}
|
|
284
286
|
return this.applyUpdates(id);
|
|
285
287
|
}
|
|
286
|
-
constructor(
|
|
288
|
+
constructor(readDB, writeDB) {
|
|
287
289
|
super();
|
|
288
|
-
this.
|
|
290
|
+
this.readDB = readDB;
|
|
291
|
+
this.writeDB = writeDB || readDB;
|
|
289
292
|
logger.debug(`UpdateQ initialized...`);
|
|
290
|
-
void this.
|
|
293
|
+
void this.readDB.info().then((i) => {
|
|
291
294
|
logger.debug(`db info: ${JSON.stringify(i)}`);
|
|
292
295
|
});
|
|
293
296
|
}
|
|
294
297
|
async applyUpdates(id) {
|
|
295
298
|
logger.debug(`Applying updates on doc: ${id}`);
|
|
296
299
|
if (this.inprogressUpdates[id]) {
|
|
297
|
-
await this.
|
|
300
|
+
await this.readDB.info();
|
|
298
301
|
return this.applyUpdates(id);
|
|
299
302
|
} else {
|
|
300
303
|
if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {
|
|
301
304
|
this.inprogressUpdates[id] = true;
|
|
302
305
|
try {
|
|
303
|
-
let doc = await this.
|
|
306
|
+
let doc = await this.readDB.get(id);
|
|
304
307
|
logger.debug(`Retrieved doc: ${id}`);
|
|
305
308
|
while (this.pendingUpdates[id].length !== 0) {
|
|
306
309
|
const update = this.pendingUpdates[id].splice(0, 1)[0];
|
|
@@ -313,7 +316,7 @@ var init_updateQueue = __esm({
|
|
|
313
316
|
};
|
|
314
317
|
}
|
|
315
318
|
}
|
|
316
|
-
await this.
|
|
319
|
+
await this.writeDB.put(doc);
|
|
317
320
|
logger.debug(`Put doc: ${id}`);
|
|
318
321
|
if (this.pendingUpdates[id].length === 0) {
|
|
319
322
|
this.inprogressUpdates[id] = false;
|
|
@@ -339,114 +342,68 @@ var init_updateQueue = __esm({
|
|
|
339
342
|
}
|
|
340
343
|
});
|
|
341
344
|
|
|
342
|
-
// src/impl/couch/
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
345
|
+
// src/impl/couch/user-course-relDB.ts
|
|
346
|
+
var import_moment2, UsrCrsData;
|
|
347
|
+
var init_user_course_relDB = __esm({
|
|
348
|
+
"src/impl/couch/user-course-relDB.ts"() {
|
|
349
|
+
"use strict";
|
|
350
|
+
import_moment2 = __toESM(require("moment"));
|
|
351
|
+
init_logger();
|
|
352
|
+
UsrCrsData = class {
|
|
353
|
+
user;
|
|
354
|
+
_courseId;
|
|
355
|
+
constructor(user, courseId) {
|
|
356
|
+
this.user = user;
|
|
357
|
+
this._courseId = courseId;
|
|
358
|
+
}
|
|
359
|
+
async getReviewsForcast(daysCount) {
|
|
360
|
+
const time = import_moment2.default.utc().add(daysCount, "days");
|
|
361
|
+
return this.getReviewstoDate(time);
|
|
362
|
+
}
|
|
363
|
+
async getPendingReviews() {
|
|
364
|
+
const now = import_moment2.default.utc();
|
|
365
|
+
return this.getReviewstoDate(now);
|
|
366
|
+
}
|
|
367
|
+
async getScheduledReviewCount() {
|
|
368
|
+
return (await this.getPendingReviews()).length;
|
|
369
|
+
}
|
|
370
|
+
async getCourseSettings() {
|
|
371
|
+
const regDoc = await this.user.getCourseRegistrationsDoc();
|
|
372
|
+
const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);
|
|
373
|
+
if (crsDoc && crsDoc.settings) {
|
|
374
|
+
return crsDoc.settings;
|
|
375
|
+
} else {
|
|
376
|
+
logger.warn(`no settings found during lookup on course ${this._courseId}`);
|
|
377
|
+
return {};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
updateCourseSettings(updates) {
|
|
381
|
+
if ("updateCourseSettings" in this.user) {
|
|
382
|
+
void this.user.updateCourseSettings(this._courseId, updates);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
async getReviewstoDate(targetDate) {
|
|
386
|
+
const allReviews = await this.user.getPendingReviews(this._courseId);
|
|
387
|
+
logger.debug(
|
|
388
|
+
`Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`
|
|
389
|
+
);
|
|
390
|
+
return allReviews.filter((review) => {
|
|
391
|
+
const reviewTime = import_moment2.default.utc(review.reviewTime);
|
|
392
|
+
return targetDate.isAfter(reviewTime);
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
};
|
|
346
396
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
async function GET_ITEM(k) {
|
|
351
|
-
throw new Error(`No implementation found for GET_CACHED(${k})`);
|
|
352
|
-
}
|
|
353
|
-
var CLIENT_CACHE;
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// src/impl/couch/clientCache.ts
|
|
354
400
|
var init_clientCache = __esm({
|
|
355
401
|
"src/impl/couch/clientCache.ts"() {
|
|
356
402
|
"use strict";
|
|
357
|
-
CLIENT_CACHE = {};
|
|
358
403
|
}
|
|
359
404
|
});
|
|
360
405
|
|
|
361
406
|
// src/impl/couch/courseAPI.ts
|
|
362
|
-
async function addNote55(courseID, codeCourse, shape, data, author, tags, uploads, elo = (0, import_common2.blankCourseElo)()) {
|
|
363
|
-
const db = getCourseDB(courseID);
|
|
364
|
-
const payload = (0, import_common3.prepareNote55)(courseID, codeCourse, shape, data, author, tags, uploads);
|
|
365
|
-
const result = await db.post(payload);
|
|
366
|
-
const dataShapeId = import_common.NameSpacer.getDataShapeString({
|
|
367
|
-
course: codeCourse,
|
|
368
|
-
dataShape: shape.name
|
|
369
|
-
});
|
|
370
|
-
if (result.ok) {
|
|
371
|
-
try {
|
|
372
|
-
await createCards(courseID, dataShapeId, result.id, tags, elo, author);
|
|
373
|
-
} catch (error) {
|
|
374
|
-
let errorMessage = "Unknown error";
|
|
375
|
-
if (error instanceof Error) {
|
|
376
|
-
errorMessage = error.message;
|
|
377
|
-
} else if (error && typeof error === "object" && "reason" in error) {
|
|
378
|
-
errorMessage = error.reason;
|
|
379
|
-
} else if (error && typeof error === "object" && "message" in error) {
|
|
380
|
-
errorMessage = error.message;
|
|
381
|
-
} else {
|
|
382
|
-
errorMessage = String(error);
|
|
383
|
-
}
|
|
384
|
-
logger.error(`[addNote55] Failed to create cards for note ${result.id}: ${errorMessage}`);
|
|
385
|
-
result.cardCreationFailed = true;
|
|
386
|
-
result.cardCreationError = errorMessage;
|
|
387
|
-
}
|
|
388
|
-
} else {
|
|
389
|
-
logger.error(`[addNote55] Error adding note. Result: ${JSON.stringify(result)}`);
|
|
390
|
-
}
|
|
391
|
-
return result;
|
|
392
|
-
}
|
|
393
|
-
async function createCards(courseID, datashapeID, noteID, tags, elo = (0, import_common2.blankCourseElo)(), author) {
|
|
394
|
-
const cfg = await getCredentialledCourseConfig(courseID);
|
|
395
|
-
const dsDescriptor = import_common.NameSpacer.getDataShapeDescriptor(datashapeID);
|
|
396
|
-
let questionViewTypes = [];
|
|
397
|
-
for (const ds of cfg.dataShapes) {
|
|
398
|
-
if (ds.name === datashapeID) {
|
|
399
|
-
questionViewTypes = ds.questionTypes;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
if (questionViewTypes.length === 0) {
|
|
403
|
-
const errorMsg = `No questionViewTypes found for datashapeID: ${datashapeID} in course config. Cards cannot be created.`;
|
|
404
|
-
logger.error(errorMsg);
|
|
405
|
-
throw new Error(errorMsg);
|
|
406
|
-
}
|
|
407
|
-
for (const questionView of questionViewTypes) {
|
|
408
|
-
await createCard(questionView, courseID, dsDescriptor, noteID, tags, elo, author);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
async function createCard(questionViewName, courseID, dsDescriptor, noteID, tags, elo = (0, import_common2.blankCourseElo)(), author) {
|
|
412
|
-
const qDescriptor = import_common.NameSpacer.getQuestionDescriptor(questionViewName);
|
|
413
|
-
const cfg = await getCredentialledCourseConfig(courseID);
|
|
414
|
-
for (const rQ of cfg.questionTypes) {
|
|
415
|
-
if (rQ.name === questionViewName) {
|
|
416
|
-
for (const view of rQ.viewList) {
|
|
417
|
-
await addCard(
|
|
418
|
-
courseID,
|
|
419
|
-
dsDescriptor.course,
|
|
420
|
-
[noteID],
|
|
421
|
-
import_common.NameSpacer.getViewString({
|
|
422
|
-
course: qDescriptor.course,
|
|
423
|
-
questionType: qDescriptor.questionType,
|
|
424
|
-
view
|
|
425
|
-
}),
|
|
426
|
-
elo,
|
|
427
|
-
tags,
|
|
428
|
-
author
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
async function addCard(courseID, course, id_displayable_data, id_view, elo, tags, author) {
|
|
435
|
-
const db = getCourseDB(courseID);
|
|
436
|
-
const card = await db.post({
|
|
437
|
-
course,
|
|
438
|
-
id_displayable_data,
|
|
439
|
-
id_view,
|
|
440
|
-
docType: "CARD" /* CARD */,
|
|
441
|
-
elo: elo || (0, import_common2.toCourseElo)(990 + Math.round(20 * Math.random())),
|
|
442
|
-
author
|
|
443
|
-
});
|
|
444
|
-
for (const tag of tags) {
|
|
445
|
-
logger.info(`adding tag: ${tag} to card ${card.id}`);
|
|
446
|
-
await addTagToCard(courseID, card.id, tag, author, false);
|
|
447
|
-
}
|
|
448
|
-
return card;
|
|
449
|
-
}
|
|
450
407
|
async function getCredentialledCourseConfig(courseID) {
|
|
451
408
|
try {
|
|
452
409
|
const db = getCourseDB(courseID);
|
|
@@ -459,66 +416,6 @@ async function getCredentialledCourseConfig(courseID) {
|
|
|
459
416
|
throw e;
|
|
460
417
|
}
|
|
461
418
|
}
|
|
462
|
-
async function addTagToCard(courseID, cardID, tagID, author, updateELO = true) {
|
|
463
|
-
const prefixedTagID = getTagID(tagID);
|
|
464
|
-
const courseDB = getCourseDB(courseID);
|
|
465
|
-
const courseApi = new CourseDB(courseID, async () => {
|
|
466
|
-
const dummySyncStrategy = {
|
|
467
|
-
setupRemoteDB: () => null,
|
|
468
|
-
startSync: () => {
|
|
469
|
-
},
|
|
470
|
-
canCreateAccount: () => false,
|
|
471
|
-
canAuthenticate: () => false,
|
|
472
|
-
getCurrentUsername: async () => "DummyUser"
|
|
473
|
-
};
|
|
474
|
-
return BaseUser.Dummy(dummySyncStrategy);
|
|
475
|
-
});
|
|
476
|
-
try {
|
|
477
|
-
logger.info(`Applying tag ${tagID} to card ${courseID + "-" + cardID}...`);
|
|
478
|
-
const tag = await courseDB.get(prefixedTagID);
|
|
479
|
-
if (!tag.taggedCards.includes(cardID)) {
|
|
480
|
-
tag.taggedCards.push(cardID);
|
|
481
|
-
if (updateELO) {
|
|
482
|
-
try {
|
|
483
|
-
const eloData = await courseApi.getCardEloData([cardID]);
|
|
484
|
-
const elo = eloData[0];
|
|
485
|
-
elo.tags[tagID] = {
|
|
486
|
-
count: 0,
|
|
487
|
-
score: elo.global.score
|
|
488
|
-
// todo: or 1000?
|
|
489
|
-
};
|
|
490
|
-
await updateCardElo(courseID, cardID, elo);
|
|
491
|
-
} catch (error) {
|
|
492
|
-
logger.error("Failed to update ELO data for card:", cardID, error);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
return courseDB.put(tag);
|
|
496
|
-
} else throw new AlreadyTaggedErr(`Card ${cardID} is already tagged with ${tagID}`);
|
|
497
|
-
} catch (e) {
|
|
498
|
-
if (e instanceof AlreadyTaggedErr) {
|
|
499
|
-
throw e;
|
|
500
|
-
}
|
|
501
|
-
await createTag(courseID, tagID, author);
|
|
502
|
-
return addTagToCard(courseID, cardID, tagID, author, updateELO);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
async function updateCardElo(courseID, cardID, elo) {
|
|
506
|
-
if (elo) {
|
|
507
|
-
const cDB = getCourseDB(courseID);
|
|
508
|
-
const card = await cDB.get(cardID);
|
|
509
|
-
logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);
|
|
510
|
-
card.elo = elo;
|
|
511
|
-
return cDB.put(card);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
function getTagID(tagName) {
|
|
515
|
-
const tagPrefix = "TAG" /* TAG */.valueOf() + "-";
|
|
516
|
-
if (tagName.indexOf(tagPrefix) === 0) {
|
|
517
|
-
return tagName;
|
|
518
|
-
} else {
|
|
519
|
-
return tagPrefix + tagName;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
419
|
function getCourseDB(courseID) {
|
|
523
420
|
const dbName = `coursedb-${courseID}`;
|
|
524
421
|
return new pouchdb_setup_default(
|
|
@@ -526,7 +423,7 @@ function getCourseDB(courseID) {
|
|
|
526
423
|
pouchDBincludeCredentialsConfig
|
|
527
424
|
);
|
|
528
425
|
}
|
|
529
|
-
var import_common, import_common2, import_common3,
|
|
426
|
+
var import_common, import_common2, import_common3, import_uuid;
|
|
530
427
|
var init_courseAPI = __esm({
|
|
531
428
|
"src/impl/couch/courseAPI.ts"() {
|
|
532
429
|
"use strict";
|
|
@@ -540,12 +437,7 @@ var init_courseAPI = __esm({
|
|
|
540
437
|
import_common3 = require("@vue-skuilder/common");
|
|
541
438
|
init_common();
|
|
542
439
|
init_logger();
|
|
543
|
-
|
|
544
|
-
constructor(message) {
|
|
545
|
-
super(message);
|
|
546
|
-
this.name = "AlreadyTaggedErr";
|
|
547
|
-
}
|
|
548
|
-
};
|
|
440
|
+
import_uuid = require("uuid");
|
|
549
441
|
}
|
|
550
442
|
});
|
|
551
443
|
|
|
@@ -680,82 +572,7 @@ var init_navigators = __esm({
|
|
|
680
572
|
});
|
|
681
573
|
|
|
682
574
|
// src/impl/couch/courseDB.ts
|
|
683
|
-
|
|
684
|
-
return Math.floor(Math.random() * Math.random() * Math.random() * n);
|
|
685
|
-
}
|
|
686
|
-
async function getCourseTagStubs(courseID) {
|
|
687
|
-
logger.debug(`Getting tag stubs for course: ${courseID}`);
|
|
688
|
-
const stubs = await filterAllDocsByPrefix2(
|
|
689
|
-
getCourseDB2(courseID),
|
|
690
|
-
"TAG" /* TAG */.valueOf() + "-"
|
|
691
|
-
);
|
|
692
|
-
stubs.rows.forEach((row) => {
|
|
693
|
-
logger.debug(` Tag stub for doc: ${row.id}`);
|
|
694
|
-
});
|
|
695
|
-
return stubs;
|
|
696
|
-
}
|
|
697
|
-
async function createTag(courseID, tagName, author) {
|
|
698
|
-
logger.debug(`Creating tag: ${tagName}...`);
|
|
699
|
-
const tagID = getTagID(tagName);
|
|
700
|
-
const courseDB = getCourseDB2(courseID);
|
|
701
|
-
const resp = await courseDB.put({
|
|
702
|
-
course: courseID,
|
|
703
|
-
docType: "TAG" /* TAG */,
|
|
704
|
-
name: tagName,
|
|
705
|
-
snippet: "",
|
|
706
|
-
taggedCards: [],
|
|
707
|
-
wiki: "",
|
|
708
|
-
author,
|
|
709
|
-
_id: tagID
|
|
710
|
-
});
|
|
711
|
-
return resp;
|
|
712
|
-
}
|
|
713
|
-
async function updateTag(tag) {
|
|
714
|
-
const prior = await getTag(tag.course, tag.name);
|
|
715
|
-
return await getCourseDB2(tag.course).put({
|
|
716
|
-
...tag,
|
|
717
|
-
_rev: prior._rev
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
async function getTag(courseID, tagName) {
|
|
721
|
-
const tagID = getTagID(tagName);
|
|
722
|
-
const courseDB = getCourseDB2(courseID);
|
|
723
|
-
return courseDB.get(tagID);
|
|
724
|
-
}
|
|
725
|
-
async function removeTagFromCard(courseID, cardID, tagID) {
|
|
726
|
-
tagID = getTagID(tagID);
|
|
727
|
-
const courseDB = getCourseDB2(courseID);
|
|
728
|
-
const tag = await courseDB.get(tagID);
|
|
729
|
-
tag.taggedCards = tag.taggedCards.filter((taggedID) => {
|
|
730
|
-
return cardID !== taggedID;
|
|
731
|
-
});
|
|
732
|
-
return courseDB.put(tag);
|
|
733
|
-
}
|
|
734
|
-
async function getAppliedTags(id_course, id_card) {
|
|
735
|
-
const db = getCourseDB2(id_course);
|
|
736
|
-
const result = await db.query("getTags", {
|
|
737
|
-
startkey: id_card,
|
|
738
|
-
endkey: id_card
|
|
739
|
-
// include_docs: true
|
|
740
|
-
});
|
|
741
|
-
return result;
|
|
742
|
-
}
|
|
743
|
-
async function updateCredentialledCourseConfig(courseID, config) {
|
|
744
|
-
logger.debug(`Updating course config:
|
|
745
|
-
|
|
746
|
-
${JSON.stringify(config)}
|
|
747
|
-
`);
|
|
748
|
-
const db = getCourseDB2(courseID);
|
|
749
|
-
const old = await getCredentialledCourseConfig(courseID);
|
|
750
|
-
return await db.put({
|
|
751
|
-
...config,
|
|
752
|
-
_rev: old._rev
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
function isSuccessRow(row) {
|
|
756
|
-
return "doc" in row && row.doc !== null && row.doc !== void 0;
|
|
757
|
-
}
|
|
758
|
-
var import_common5, CourseDB;
|
|
575
|
+
var import_common5;
|
|
759
576
|
var init_courseDB = __esm({
|
|
760
577
|
"src/impl/couch/courseDB.ts"() {
|
|
761
578
|
"use strict";
|
|
@@ -768,428 +585,17 @@ var init_courseDB = __esm({
|
|
|
768
585
|
init_courseAPI();
|
|
769
586
|
init_courseLookupDB();
|
|
770
587
|
init_navigators();
|
|
771
|
-
CourseDB = class {
|
|
772
|
-
// private log(msg: string): void {
|
|
773
|
-
// log(`CourseLog: ${this.id}\n ${msg}`);
|
|
774
|
-
// }
|
|
775
|
-
db;
|
|
776
|
-
id;
|
|
777
|
-
_getCurrentUser;
|
|
778
|
-
updateQueue;
|
|
779
|
-
constructor(id, userLookup) {
|
|
780
|
-
this.id = id;
|
|
781
|
-
this.db = getCourseDB2(this.id);
|
|
782
|
-
this._getCurrentUser = userLookup;
|
|
783
|
-
this.updateQueue = new UpdateQueue(this.db);
|
|
784
|
-
}
|
|
785
|
-
getCourseID() {
|
|
786
|
-
return this.id;
|
|
787
|
-
}
|
|
788
|
-
async getCourseInfo() {
|
|
789
|
-
const cardCount = (await this.db.find({
|
|
790
|
-
selector: {
|
|
791
|
-
docType: "CARD" /* CARD */
|
|
792
|
-
},
|
|
793
|
-
limit: 1e3
|
|
794
|
-
})).docs.length;
|
|
795
|
-
return {
|
|
796
|
-
cardCount,
|
|
797
|
-
registeredUsers: 0
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
async getInexperiencedCards(limit = 2) {
|
|
801
|
-
return (await this.db.query("cardsByInexperience", {
|
|
802
|
-
limit
|
|
803
|
-
})).rows.map((r) => {
|
|
804
|
-
const ret = {
|
|
805
|
-
courseId: this.id,
|
|
806
|
-
cardId: r.id,
|
|
807
|
-
count: r.key,
|
|
808
|
-
elo: r.value
|
|
809
|
-
};
|
|
810
|
-
return ret;
|
|
811
|
-
});
|
|
812
|
-
}
|
|
813
|
-
async getCardsByEloLimits(options = {
|
|
814
|
-
low: 0,
|
|
815
|
-
high: Number.MIN_SAFE_INTEGER,
|
|
816
|
-
limit: 25,
|
|
817
|
-
page: 0
|
|
818
|
-
}) {
|
|
819
|
-
return (await this.db.query("elo", {
|
|
820
|
-
startkey: options.low,
|
|
821
|
-
endkey: options.high,
|
|
822
|
-
limit: options.limit,
|
|
823
|
-
skip: options.limit * options.page
|
|
824
|
-
})).rows.map((r) => {
|
|
825
|
-
return `${this.id}-${r.id}-${r.key}`;
|
|
826
|
-
});
|
|
827
|
-
}
|
|
828
|
-
async getCardEloData(id) {
|
|
829
|
-
const docs = await this.db.allDocs({
|
|
830
|
-
keys: id,
|
|
831
|
-
include_docs: true
|
|
832
|
-
});
|
|
833
|
-
const ret = [];
|
|
834
|
-
docs.rows.forEach((r) => {
|
|
835
|
-
if (isSuccessRow(r)) {
|
|
836
|
-
if (r.doc && r.doc.elo) {
|
|
837
|
-
ret.push((0, import_common5.toCourseElo)(r.doc.elo));
|
|
838
|
-
} else {
|
|
839
|
-
logger.warn("no elo data for card: " + r.id);
|
|
840
|
-
ret.push((0, import_common5.blankCourseElo)());
|
|
841
|
-
}
|
|
842
|
-
} else {
|
|
843
|
-
logger.warn("no elo data for card: " + JSON.stringify(r));
|
|
844
|
-
ret.push((0, import_common5.blankCourseElo)());
|
|
845
|
-
}
|
|
846
|
-
});
|
|
847
|
-
return ret;
|
|
848
|
-
}
|
|
849
|
-
/**
|
|
850
|
-
* Returns the lowest and highest `global` ELO ratings in the course
|
|
851
|
-
*/
|
|
852
|
-
async getELOBounds() {
|
|
853
|
-
const [low, high] = await Promise.all([
|
|
854
|
-
(await this.db.query("elo", {
|
|
855
|
-
startkey: 0,
|
|
856
|
-
limit: 1,
|
|
857
|
-
include_docs: false
|
|
858
|
-
})).rows[0].key,
|
|
859
|
-
(await this.db.query("elo", {
|
|
860
|
-
limit: 1,
|
|
861
|
-
descending: true,
|
|
862
|
-
startkey: 1e5
|
|
863
|
-
})).rows[0].key
|
|
864
|
-
]);
|
|
865
|
-
return {
|
|
866
|
-
low,
|
|
867
|
-
high
|
|
868
|
-
};
|
|
869
|
-
}
|
|
870
|
-
async removeCard(id) {
|
|
871
|
-
const doc = await this.db.get(id);
|
|
872
|
-
if (!doc.docType || !(doc.docType === "CARD" /* CARD */)) {
|
|
873
|
-
throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);
|
|
874
|
-
}
|
|
875
|
-
return this.db.remove(doc);
|
|
876
|
-
}
|
|
877
|
-
async getCardDisplayableDataIDs(id) {
|
|
878
|
-
logger.debug(id.join(", "));
|
|
879
|
-
const cards = await this.db.allDocs({
|
|
880
|
-
keys: id,
|
|
881
|
-
include_docs: true
|
|
882
|
-
});
|
|
883
|
-
const ret = {};
|
|
884
|
-
cards.rows.forEach((r) => {
|
|
885
|
-
if (isSuccessRow(r)) {
|
|
886
|
-
ret[r.id] = r.doc.id_displayable_data;
|
|
887
|
-
}
|
|
888
|
-
});
|
|
889
|
-
await Promise.all(
|
|
890
|
-
cards.rows.map((r) => {
|
|
891
|
-
return async () => {
|
|
892
|
-
if (isSuccessRow(r)) {
|
|
893
|
-
ret[r.id] = r.doc.id_displayable_data;
|
|
894
|
-
}
|
|
895
|
-
};
|
|
896
|
-
})
|
|
897
|
-
);
|
|
898
|
-
return ret;
|
|
899
|
-
}
|
|
900
|
-
async getCardsByELO(elo, cardLimit) {
|
|
901
|
-
elo = parseInt(elo);
|
|
902
|
-
const limit = cardLimit ? cardLimit : 25;
|
|
903
|
-
const below = await this.db.query("elo", {
|
|
904
|
-
limit: Math.ceil(limit / 2),
|
|
905
|
-
startkey: elo,
|
|
906
|
-
descending: true
|
|
907
|
-
});
|
|
908
|
-
const aboveLimit = limit - below.rows.length;
|
|
909
|
-
const above = await this.db.query("elo", {
|
|
910
|
-
limit: aboveLimit,
|
|
911
|
-
startkey: elo + 1
|
|
912
|
-
});
|
|
913
|
-
let cards = below.rows;
|
|
914
|
-
cards = cards.concat(above.rows);
|
|
915
|
-
const ret = cards.sort((a, b) => {
|
|
916
|
-
const s = Math.abs(a.key - elo) - Math.abs(b.key - elo);
|
|
917
|
-
if (s === 0) {
|
|
918
|
-
return Math.random() - 0.5;
|
|
919
|
-
} else {
|
|
920
|
-
return s;
|
|
921
|
-
}
|
|
922
|
-
}).map((c) => `${this.id}-${c.id}-${c.key}`);
|
|
923
|
-
const str = `below:
|
|
924
|
-
${below.rows.map((r) => ` ${r.id}-${r.key}
|
|
925
|
-
`)}
|
|
926
|
-
|
|
927
|
-
above:
|
|
928
|
-
${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
929
|
-
`)}`;
|
|
930
|
-
logger.debug(`Getting ${limit} cards centered around elo: ${elo}:
|
|
931
|
-
|
|
932
|
-
` + str);
|
|
933
|
-
return ret;
|
|
934
|
-
}
|
|
935
|
-
async getCourseConfig() {
|
|
936
|
-
const ret = await getCredentialledCourseConfig(this.id);
|
|
937
|
-
if (ret) {
|
|
938
|
-
return ret;
|
|
939
|
-
} else {
|
|
940
|
-
throw new Error(`Course config not found for course ID: ${this.id}`);
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
async updateCourseConfig(cfg) {
|
|
944
|
-
logger.debug(`Updating: ${JSON.stringify(cfg)}`);
|
|
945
|
-
try {
|
|
946
|
-
return await updateCredentialledCourseConfig(this.id, cfg);
|
|
947
|
-
} catch (error) {
|
|
948
|
-
logger.error(`Error updating course config in course DB: ${error}`);
|
|
949
|
-
throw error;
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
async updateCardElo(cardId, elo) {
|
|
953
|
-
if (!elo) {
|
|
954
|
-
throw new Error(`Cannot update card elo with null or undefined value for card ID: ${cardId}`);
|
|
955
|
-
}
|
|
956
|
-
try {
|
|
957
|
-
const result = await this.updateQueue.update(cardId, (card) => {
|
|
958
|
-
logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);
|
|
959
|
-
card.elo = elo;
|
|
960
|
-
return card;
|
|
961
|
-
});
|
|
962
|
-
return { ok: true, id: cardId, rev: result._rev };
|
|
963
|
-
} catch (error) {
|
|
964
|
-
logger.error(`Failed to update card elo for card ID: ${cardId}`, error);
|
|
965
|
-
throw new Error(`Failed to update card elo for card ID: ${cardId}`);
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
async getAppliedTags(cardId) {
|
|
969
|
-
const ret = await getAppliedTags(this.id, cardId);
|
|
970
|
-
if (ret) {
|
|
971
|
-
return ret;
|
|
972
|
-
} else {
|
|
973
|
-
throw new Error(`Failed to find tags for card ${this.id}-${cardId}`);
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
async addTagToCard(cardId, tagId, updateELO) {
|
|
977
|
-
return await addTagToCard(this.id, cardId, tagId, (await this._getCurrentUser()).getUsername(), updateELO);
|
|
978
|
-
}
|
|
979
|
-
async removeTagFromCard(cardId, tagId) {
|
|
980
|
-
return await removeTagFromCard(this.id, cardId, tagId);
|
|
981
|
-
}
|
|
982
|
-
async createTag(name, author) {
|
|
983
|
-
return await createTag(this.id, name, author);
|
|
984
|
-
}
|
|
985
|
-
async getTag(tagId) {
|
|
986
|
-
return await getTag(this.id, tagId);
|
|
987
|
-
}
|
|
988
|
-
async updateTag(tag) {
|
|
989
|
-
if (tag.course !== this.id) {
|
|
990
|
-
throw new Error(`Tag ${JSON.stringify(tag)} does not belong to course ${this.id}`);
|
|
991
|
-
}
|
|
992
|
-
return await updateTag(tag);
|
|
993
|
-
}
|
|
994
|
-
async getCourseTagStubs() {
|
|
995
|
-
return getCourseTagStubs(this.id);
|
|
996
|
-
}
|
|
997
|
-
async addNote(codeCourse, shape, data, author, tags, uploads, elo = (0, import_common5.blankCourseElo)()) {
|
|
998
|
-
try {
|
|
999
|
-
const resp = await addNote55(this.id, codeCourse, shape, data, author, tags, uploads, elo);
|
|
1000
|
-
if (resp.ok) {
|
|
1001
|
-
if (resp.cardCreationFailed) {
|
|
1002
|
-
logger.warn(
|
|
1003
|
-
`[courseDB.addNote] Note added but card creation failed: ${resp.cardCreationError}`
|
|
1004
|
-
);
|
|
1005
|
-
return {
|
|
1006
|
-
status: import_common5.Status.error,
|
|
1007
|
-
message: `Note was added but no cards were created: ${resp.cardCreationError}`,
|
|
1008
|
-
id: resp.id
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
return {
|
|
1012
|
-
status: import_common5.Status.ok,
|
|
1013
|
-
message: "",
|
|
1014
|
-
id: resp.id
|
|
1015
|
-
};
|
|
1016
|
-
} else {
|
|
1017
|
-
return {
|
|
1018
|
-
status: import_common5.Status.error,
|
|
1019
|
-
message: "Unexpected error adding note"
|
|
1020
|
-
};
|
|
1021
|
-
}
|
|
1022
|
-
} catch (e) {
|
|
1023
|
-
const err = e;
|
|
1024
|
-
logger.error(
|
|
1025
|
-
`[addNote] error ${err.name}
|
|
1026
|
-
reason: ${err.reason}
|
|
1027
|
-
message: ${err.message}`
|
|
1028
|
-
);
|
|
1029
|
-
return {
|
|
1030
|
-
status: import_common5.Status.error,
|
|
1031
|
-
message: `Error adding note to course. ${e.reason || err.message}`
|
|
1032
|
-
};
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
async getCourseDoc(id, options) {
|
|
1036
|
-
return await getCourseDoc(this.id, id, options);
|
|
1037
|
-
}
|
|
1038
|
-
async getCourseDocs(ids, options = {}) {
|
|
1039
|
-
return await getCourseDocs(this.id, ids, options);
|
|
1040
|
-
}
|
|
1041
|
-
////////////////////////////////////
|
|
1042
|
-
// NavigationStrategyManager implementation
|
|
1043
|
-
////////////////////////////////////
|
|
1044
|
-
getNavigationStrategy(id) {
|
|
1045
|
-
logger.debug(`[courseDB] Getting navigation strategy: ${id}`);
|
|
1046
|
-
const strategy = {
|
|
1047
|
-
id: "ELO",
|
|
1048
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
1049
|
-
name: "ELO",
|
|
1050
|
-
description: "ELO-based navigation strategy for ordering content by difficulty",
|
|
1051
|
-
implementingClass: "elo" /* ELO */,
|
|
1052
|
-
course: this.id,
|
|
1053
|
-
serializedData: ""
|
|
1054
|
-
// serde is a noop for ELO navigator.
|
|
1055
|
-
};
|
|
1056
|
-
return Promise.resolve(strategy);
|
|
1057
|
-
}
|
|
1058
|
-
getAllNavigationStrategies() {
|
|
1059
|
-
logger.debug("[courseDB] Returning hard-coded navigation strategies");
|
|
1060
|
-
const strategies = [
|
|
1061
|
-
{
|
|
1062
|
-
id: "ELO",
|
|
1063
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
1064
|
-
name: "ELO",
|
|
1065
|
-
description: "ELO-based navigation strategy for ordering content by difficulty",
|
|
1066
|
-
implementingClass: "elo" /* ELO */,
|
|
1067
|
-
course: this.id,
|
|
1068
|
-
serializedData: ""
|
|
1069
|
-
// serde is a noop for ELO navigator.
|
|
1070
|
-
}
|
|
1071
|
-
];
|
|
1072
|
-
return Promise.resolve(strategies);
|
|
1073
|
-
}
|
|
1074
|
-
addNavigationStrategy(data) {
|
|
1075
|
-
logger.debug(`[courseDB] Adding navigation strategy: ${data.id}`);
|
|
1076
|
-
logger.debug(JSON.stringify(data));
|
|
1077
|
-
return Promise.resolve();
|
|
1078
|
-
}
|
|
1079
|
-
updateNavigationStrategy(id, data) {
|
|
1080
|
-
logger.debug(`[courseDB] Updating navigation strategy: ${id}`);
|
|
1081
|
-
logger.debug(JSON.stringify(data));
|
|
1082
|
-
return Promise.resolve();
|
|
1083
|
-
}
|
|
1084
|
-
async surfaceNavigationStrategy() {
|
|
1085
|
-
logger.warn(`Returning hard-coded default ELO navigator`);
|
|
1086
|
-
const ret = {
|
|
1087
|
-
id: "ELO",
|
|
1088
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
1089
|
-
name: "ELO",
|
|
1090
|
-
description: "ELO-based navigation strategy",
|
|
1091
|
-
implementingClass: "elo" /* ELO */,
|
|
1092
|
-
course: this.id,
|
|
1093
|
-
serializedData: ""
|
|
1094
|
-
// serde is a noop for ELO navigator.
|
|
1095
|
-
};
|
|
1096
|
-
return Promise.resolve(ret);
|
|
1097
|
-
}
|
|
1098
|
-
////////////////////////////////////
|
|
1099
|
-
// END NavigationStrategyManager implementation
|
|
1100
|
-
////////////////////////////////////
|
|
1101
|
-
////////////////////////////////////
|
|
1102
|
-
// StudyContentSource implementation
|
|
1103
|
-
////////////////////////////////////
|
|
1104
|
-
async getNewCards(limit = 99) {
|
|
1105
|
-
const u = await this._getCurrentUser();
|
|
1106
|
-
try {
|
|
1107
|
-
const strategy = await this.surfaceNavigationStrategy();
|
|
1108
|
-
const navigator = await ContentNavigator.create(u, this, strategy);
|
|
1109
|
-
return navigator.getNewCards(limit);
|
|
1110
|
-
} catch (e) {
|
|
1111
|
-
logger.error(`[courseDB] Error surfacing a NavigationStrategy: ${e}`);
|
|
1112
|
-
throw e;
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
async getPendingReviews() {
|
|
1116
|
-
const u = await this._getCurrentUser();
|
|
1117
|
-
try {
|
|
1118
|
-
const strategy = await this.surfaceNavigationStrategy();
|
|
1119
|
-
const navigator = await ContentNavigator.create(u, this, strategy);
|
|
1120
|
-
return navigator.getPendingReviews();
|
|
1121
|
-
} catch (e) {
|
|
1122
|
-
logger.error(`[courseDB] Error surfacing a NavigationStrategy: ${e}`);
|
|
1123
|
-
throw e;
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
async getCardsCenteredAtELO(options = {
|
|
1127
|
-
limit: 99,
|
|
1128
|
-
elo: "user"
|
|
1129
|
-
}, filter) {
|
|
1130
|
-
let targetElo;
|
|
1131
|
-
if (options.elo === "user") {
|
|
1132
|
-
const u = await this._getCurrentUser();
|
|
1133
|
-
targetElo = -1;
|
|
1134
|
-
try {
|
|
1135
|
-
const courseDoc = (await u.getCourseRegistrationsDoc()).courses.find((c) => {
|
|
1136
|
-
return c.courseID === this.id;
|
|
1137
|
-
});
|
|
1138
|
-
targetElo = (0, import_common5.EloToNumber)(courseDoc.elo);
|
|
1139
|
-
} catch {
|
|
1140
|
-
targetElo = 1e3;
|
|
1141
|
-
}
|
|
1142
|
-
} else if (options.elo === "random") {
|
|
1143
|
-
const bounds = await GET_CACHED(`elo-bounds-${this.id}`, () => this.getELOBounds());
|
|
1144
|
-
targetElo = Math.round(bounds.low + Math.random() * (bounds.high - bounds.low));
|
|
1145
|
-
} else {
|
|
1146
|
-
targetElo = options.elo;
|
|
1147
|
-
}
|
|
1148
|
-
let cards = [];
|
|
1149
|
-
let mult = 4;
|
|
1150
|
-
let previousCount = -1;
|
|
1151
|
-
let newCount = 0;
|
|
1152
|
-
while (cards.length < options.limit && newCount !== previousCount) {
|
|
1153
|
-
cards = await this.getCardsByELO(targetElo, mult * options.limit);
|
|
1154
|
-
previousCount = newCount;
|
|
1155
|
-
newCount = cards.length;
|
|
1156
|
-
logger.debug(`Found ${cards.length} elo neighbor cards...`);
|
|
1157
|
-
if (filter) {
|
|
1158
|
-
cards = cards.filter(filter);
|
|
1159
|
-
logger.debug(`Filtered to ${cards.length} cards...`);
|
|
1160
|
-
}
|
|
1161
|
-
mult *= 2;
|
|
1162
|
-
}
|
|
1163
|
-
const selectedCards = [];
|
|
1164
|
-
while (selectedCards.length < options.limit && cards.length > 0) {
|
|
1165
|
-
const index = randIntWeightedTowardZero(cards.length);
|
|
1166
|
-
const card = cards.splice(index, 1)[0];
|
|
1167
|
-
selectedCards.push(card);
|
|
1168
|
-
}
|
|
1169
|
-
return selectedCards.map((c) => {
|
|
1170
|
-
const split = c.split("-");
|
|
1171
|
-
return {
|
|
1172
|
-
courseID: this.id,
|
|
1173
|
-
cardID: split[1],
|
|
1174
|
-
qualifiedID: `${split[0]}-${split[1]}`,
|
|
1175
|
-
contentSourceType: "course",
|
|
1176
|
-
contentSourceID: this.id,
|
|
1177
|
-
status: "new"
|
|
1178
|
-
};
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
|
-
};
|
|
1182
588
|
}
|
|
1183
589
|
});
|
|
1184
590
|
|
|
1185
591
|
// src/impl/couch/classroomDB.ts
|
|
1186
|
-
var
|
|
592
|
+
var import_moment3;
|
|
1187
593
|
var init_classroomDB2 = __esm({
|
|
1188
594
|
"src/impl/couch/classroomDB.ts"() {
|
|
1189
595
|
"use strict";
|
|
1190
596
|
init_factory();
|
|
1191
597
|
init_logger();
|
|
1192
|
-
|
|
598
|
+
import_moment3 = __toESM(require("moment"));
|
|
1193
599
|
init_pouchdb_setup();
|
|
1194
600
|
init_couch();
|
|
1195
601
|
init_courseDB();
|
|
@@ -1236,45 +642,13 @@ var init_CouchDBSyncStrategy = __esm({
|
|
|
1236
642
|
});
|
|
1237
643
|
|
|
1238
644
|
// src/impl/couch/index.ts
|
|
1239
|
-
|
|
1240
|
-
return new pouchdb_setup_default(
|
|
1241
|
-
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "coursedb-" + courseID,
|
|
1242
|
-
pouchDBincludeCredentialsConfig
|
|
1243
|
-
);
|
|
1244
|
-
}
|
|
1245
|
-
function getCourseDocs(courseID, docIDs, options = {}) {
|
|
1246
|
-
return getCourseDB2(courseID).allDocs({
|
|
1247
|
-
...options,
|
|
1248
|
-
keys: docIDs
|
|
1249
|
-
});
|
|
1250
|
-
}
|
|
1251
|
-
function getCourseDoc(courseID, docID, options = {}) {
|
|
1252
|
-
return getCourseDB2(courseID).get(docID, options);
|
|
1253
|
-
}
|
|
1254
|
-
function filterAllDocsByPrefix2(db, prefix, opts) {
|
|
1255
|
-
const options = {
|
|
1256
|
-
startkey: prefix,
|
|
1257
|
-
endkey: prefix + "\uFFF0",
|
|
1258
|
-
include_docs: true
|
|
1259
|
-
};
|
|
1260
|
-
if (opts) {
|
|
1261
|
-
Object.assign(options, opts);
|
|
1262
|
-
}
|
|
1263
|
-
return db.allDocs(options);
|
|
1264
|
-
}
|
|
1265
|
-
function getStartAndEndKeys2(key) {
|
|
1266
|
-
return {
|
|
1267
|
-
startkey: key,
|
|
1268
|
-
endkey: key + "\uFFF0"
|
|
1269
|
-
};
|
|
1270
|
-
}
|
|
1271
|
-
var import_moment3, import_process, isBrowser, GUEST_LOCAL_DB, localUserDB, pouchDBincludeCredentialsConfig, REVIEW_PREFIX2, REVIEW_TIME_FORMAT2;
|
|
645
|
+
var import_moment4, import_process, isBrowser, GUEST_LOCAL_DB, localUserDB, pouchDBincludeCredentialsConfig;
|
|
1272
646
|
var init_couch = __esm({
|
|
1273
647
|
"src/impl/couch/index.ts"() {
|
|
1274
648
|
"use strict";
|
|
1275
649
|
init_factory();
|
|
1276
650
|
init_types_legacy();
|
|
1277
|
-
|
|
651
|
+
import_moment4 = __toESM(require("moment"));
|
|
1278
652
|
init_logger();
|
|
1279
653
|
init_pouchdb_setup();
|
|
1280
654
|
import_process = __toESM(require("process"));
|
|
@@ -1296,75 +670,6 @@ var init_couch = __esm({
|
|
|
1296
670
|
return pouchdb_setup_default.fetch(url, opts);
|
|
1297
671
|
}
|
|
1298
672
|
};
|
|
1299
|
-
REVIEW_PREFIX2 = "card_review_";
|
|
1300
|
-
REVIEW_TIME_FORMAT2 = "YYYY-MM-DD--kk:mm:ss-SSS";
|
|
1301
|
-
}
|
|
1302
|
-
});
|
|
1303
|
-
|
|
1304
|
-
// src/impl/couch/user-course-relDB.ts
|
|
1305
|
-
var import_moment4, UsrCrsData;
|
|
1306
|
-
var init_user_course_relDB = __esm({
|
|
1307
|
-
"src/impl/couch/user-course-relDB.ts"() {
|
|
1308
|
-
"use strict";
|
|
1309
|
-
import_moment4 = __toESM(require("moment"));
|
|
1310
|
-
init_couch();
|
|
1311
|
-
init_courseDB();
|
|
1312
|
-
init_logger();
|
|
1313
|
-
UsrCrsData = class {
|
|
1314
|
-
user;
|
|
1315
|
-
course;
|
|
1316
|
-
_courseId;
|
|
1317
|
-
constructor(user, courseId) {
|
|
1318
|
-
this.user = user;
|
|
1319
|
-
this.course = new CourseDB(courseId, async () => this.user);
|
|
1320
|
-
this._courseId = courseId;
|
|
1321
|
-
}
|
|
1322
|
-
async getReviewsForcast(daysCount) {
|
|
1323
|
-
const time = import_moment4.default.utc().add(daysCount, "days");
|
|
1324
|
-
return this.getReviewstoDate(time);
|
|
1325
|
-
}
|
|
1326
|
-
async getPendingReviews() {
|
|
1327
|
-
const now = import_moment4.default.utc();
|
|
1328
|
-
return this.getReviewstoDate(now);
|
|
1329
|
-
}
|
|
1330
|
-
async getScheduledReviewCount() {
|
|
1331
|
-
return (await this.getPendingReviews()).length;
|
|
1332
|
-
}
|
|
1333
|
-
async getCourseSettings() {
|
|
1334
|
-
const regDoc = await this.user.getCourseRegistrationsDoc();
|
|
1335
|
-
const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);
|
|
1336
|
-
if (crsDoc && crsDoc.settings) {
|
|
1337
|
-
return crsDoc.settings;
|
|
1338
|
-
} else {
|
|
1339
|
-
logger.warn(`no settings found during lookup on course ${this._courseId}`);
|
|
1340
|
-
return {};
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
updateCourseSettings(updates) {
|
|
1344
|
-
void this.user.updateCourseSettings(this._courseId, updates);
|
|
1345
|
-
}
|
|
1346
|
-
async getReviewstoDate(targetDate) {
|
|
1347
|
-
const keys = getStartAndEndKeys2(REVIEW_PREFIX2);
|
|
1348
|
-
const reviews = await this.user.remote().allDocs({
|
|
1349
|
-
startkey: keys.startkey,
|
|
1350
|
-
endkey: keys.endkey,
|
|
1351
|
-
include_docs: true
|
|
1352
|
-
});
|
|
1353
|
-
logger.debug(
|
|
1354
|
-
`Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`
|
|
1355
|
-
);
|
|
1356
|
-
return reviews.rows.filter((r) => {
|
|
1357
|
-
if (r.id.startsWith(REVIEW_PREFIX2)) {
|
|
1358
|
-
const date = import_moment4.default.utc(r.id.substr(REVIEW_PREFIX2.length), REVIEW_TIME_FORMAT2);
|
|
1359
|
-
if (targetDate.isAfter(date)) {
|
|
1360
|
-
if (this._courseId === void 0 || r.doc.courseId === this._courseId) {
|
|
1361
|
-
return true;
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
}).map((r) => r.doc);
|
|
1366
|
-
}
|
|
1367
|
-
};
|
|
1368
673
|
}
|
|
1369
674
|
});
|
|
1370
675
|
|
|
@@ -1461,10 +766,11 @@ async function dropUserFromClassroom(user, classID) {
|
|
|
1461
766
|
async function getUserClassrooms(user) {
|
|
1462
767
|
return getOrCreateClassroomRegistrationsDoc(user);
|
|
1463
768
|
}
|
|
1464
|
-
var import_common8, import_moment5, log3,
|
|
769
|
+
var import_common8, import_moment5, log3, BaseUser, userCoursesDoc, userClassroomsDoc;
|
|
1465
770
|
var init_BaseUserDB = __esm({
|
|
1466
771
|
"src/impl/common/BaseUserDB.ts"() {
|
|
1467
772
|
"use strict";
|
|
773
|
+
init_core();
|
|
1468
774
|
init_util();
|
|
1469
775
|
import_common8 = require("@vue-skuilder/common");
|
|
1470
776
|
import_moment5 = __toESM(require("moment"));
|
|
@@ -1477,7 +783,6 @@ var init_BaseUserDB = __esm({
|
|
|
1477
783
|
log3 = (s) => {
|
|
1478
784
|
logger.info(s);
|
|
1479
785
|
};
|
|
1480
|
-
cardHistoryPrefix2 = "cardH-";
|
|
1481
786
|
BaseUser = class _BaseUser {
|
|
1482
787
|
static _instance;
|
|
1483
788
|
static _initialized = false;
|
|
@@ -1498,11 +803,13 @@ var init_BaseUserDB = __esm({
|
|
|
1498
803
|
isLoggedIn() {
|
|
1499
804
|
return !this._username.startsWith(GuestUsername);
|
|
1500
805
|
}
|
|
1501
|
-
remoteDB;
|
|
1502
806
|
remote() {
|
|
1503
807
|
return this.remoteDB;
|
|
1504
808
|
}
|
|
1505
809
|
localDB;
|
|
810
|
+
remoteDB;
|
|
811
|
+
writeDB;
|
|
812
|
+
// Database to use for write operations (local-first approach)
|
|
1506
813
|
updateQueue;
|
|
1507
814
|
async createAccount(username, password) {
|
|
1508
815
|
if (!this.syncStrategy.canCreateAccount()) {
|
|
@@ -1566,8 +873,8 @@ Currently logged-in as ${this._username}.`
|
|
|
1566
873
|
const allDocs = await localDB.allDocs({ include_docs: false });
|
|
1567
874
|
const docsToDelete = allDocs.rows.filter((row) => {
|
|
1568
875
|
const id = row.id;
|
|
1569
|
-
return id.startsWith(
|
|
1570
|
-
id.startsWith(
|
|
876
|
+
return id.startsWith(DocTypePrefixes["CARDRECORD" /* CARDRECORD */]) || // Card interaction history
|
|
877
|
+
id.startsWith(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]) || // Scheduled reviews
|
|
1571
878
|
id === _BaseUser.DOC_IDS.COURSE_REGISTRATIONS || // Course registrations
|
|
1572
879
|
id === _BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS || // Classroom registrations
|
|
1573
880
|
id === _BaseUser.DOC_IDS.CONFIG;
|
|
@@ -1636,7 +943,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1636
943
|
*
|
|
1637
944
|
*/
|
|
1638
945
|
async getActiveCards() {
|
|
1639
|
-
const keys = getStartAndEndKeys(
|
|
946
|
+
const keys = getStartAndEndKeys(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]);
|
|
1640
947
|
const reviews = await this.remoteDB.allDocs({
|
|
1641
948
|
startkey: keys.startkey,
|
|
1642
949
|
endkey: keys.endkey,
|
|
@@ -1708,7 +1015,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1708
1015
|
}
|
|
1709
1016
|
}
|
|
1710
1017
|
async getReviewstoDate(targetDate, course_id) {
|
|
1711
|
-
const keys = getStartAndEndKeys(
|
|
1018
|
+
const keys = getStartAndEndKeys(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]);
|
|
1712
1019
|
const reviews = await this.remoteDB.allDocs({
|
|
1713
1020
|
startkey: keys.startkey,
|
|
1714
1021
|
endkey: keys.endkey,
|
|
@@ -1718,8 +1025,11 @@ Currently logged-in as ${this._username}.`
|
|
|
1718
1025
|
`Fetching ${this._username}'s scheduled reviews${course_id ? ` for course ${course_id}` : ""}.`
|
|
1719
1026
|
);
|
|
1720
1027
|
return reviews.rows.filter((r) => {
|
|
1721
|
-
if (r.id.startsWith(
|
|
1722
|
-
const date = import_moment5.default.utc(
|
|
1028
|
+
if (r.id.startsWith(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */])) {
|
|
1029
|
+
const date = import_moment5.default.utc(
|
|
1030
|
+
r.id.substr(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */].length),
|
|
1031
|
+
REVIEW_TIME_FORMAT
|
|
1032
|
+
);
|
|
1723
1033
|
if (targetDate.isAfter(date)) {
|
|
1724
1034
|
if (course_id === void 0 || r.doc.courseId === course_id) {
|
|
1725
1035
|
return true;
|
|
@@ -1834,7 +1144,8 @@ Currently logged-in as ${this._username}.`
|
|
|
1834
1144
|
const defaultConfig = {
|
|
1835
1145
|
_id: _BaseUser.DOC_IDS.CONFIG,
|
|
1836
1146
|
darkMode: false,
|
|
1837
|
-
likesConfetti: false
|
|
1147
|
+
likesConfetti: false,
|
|
1148
|
+
sessionTimeLimit: 5
|
|
1838
1149
|
};
|
|
1839
1150
|
try {
|
|
1840
1151
|
const cfg = await this.localDB.get(_BaseUser.DOC_IDS.CONFIG);
|
|
@@ -1907,7 +1218,8 @@ Currently logged-in as ${this._username}.`
|
|
|
1907
1218
|
setDBandQ() {
|
|
1908
1219
|
this.localDB = getLocalUserDB(this._username);
|
|
1909
1220
|
this.remoteDB = this.syncStrategy.setupRemoteDB(this._username);
|
|
1910
|
-
this.
|
|
1221
|
+
this.writeDB = this.syncStrategy.getWriteDB ? this.syncStrategy.getWriteDB(this._username) : this.localDB;
|
|
1222
|
+
this.updateQueue = new UpdateQueue(this.localDB, this.writeDB);
|
|
1911
1223
|
}
|
|
1912
1224
|
async init() {
|
|
1913
1225
|
_BaseUser._initialized = false;
|
|
@@ -2025,8 +1337,8 @@ Currently logged-in as ${this._username}.`
|
|
|
2025
1337
|
streak: 0,
|
|
2026
1338
|
bestInterval: 0
|
|
2027
1339
|
};
|
|
2028
|
-
|
|
2029
|
-
return initCardHistory;
|
|
1340
|
+
const putResult = await this.writeDB.put(initCardHistory);
|
|
1341
|
+
return { ...initCardHistory, _rev: putResult.rev };
|
|
2030
1342
|
} else {
|
|
2031
1343
|
throw new Error(`putCardRecord failed because of:
|
|
2032
1344
|
name:${reason.name}
|
|
@@ -2061,7 +1373,7 @@ Currently logged-in as ${this._username}.`
|
|
|
2061
1373
|
const deletePromises = duplicateDocIds.map(async (docId) => {
|
|
2062
1374
|
try {
|
|
2063
1375
|
const doc = await this.remoteDB.get(docId);
|
|
2064
|
-
await this.
|
|
1376
|
+
await this.writeDB.remove(doc);
|
|
2065
1377
|
log3(`Successfully removed duplicate review: ${docId}`);
|
|
2066
1378
|
} catch (error) {
|
|
2067
1379
|
log3(`Failed to remove duplicate review ${docId}: ${error}`);
|
|
@@ -2083,7 +1395,7 @@ Currently logged-in as ${this._username}.`
|
|
|
2083
1395
|
* @param course_id optional specification of individual course
|
|
2084
1396
|
*/
|
|
2085
1397
|
async getSeenCards(course_id) {
|
|
2086
|
-
let prefix =
|
|
1398
|
+
let prefix = DocTypePrefixes["CARDRECORD" /* CARDRECORD */];
|
|
2087
1399
|
if (course_id) {
|
|
2088
1400
|
prefix += course_id;
|
|
2089
1401
|
}
|
|
@@ -2092,8 +1404,8 @@ Currently logged-in as ${this._username}.`
|
|
|
2092
1404
|
});
|
|
2093
1405
|
const ret = [];
|
|
2094
1406
|
docs.rows.forEach((row) => {
|
|
2095
|
-
if (row.id.startsWith(
|
|
2096
|
-
ret.push(row.id.substr(
|
|
1407
|
+
if (row.id.startsWith(DocTypePrefixes["CARDRECORD" /* CARDRECORD */])) {
|
|
1408
|
+
ret.push(row.id.substr(DocTypePrefixes["CARDRECORD" /* CARDRECORD */].length));
|
|
2097
1409
|
}
|
|
2098
1410
|
});
|
|
2099
1411
|
return ret;
|
|
@@ -2105,7 +1417,7 @@ Currently logged-in as ${this._username}.`
|
|
|
2105
1417
|
async getHistory() {
|
|
2106
1418
|
const cards = await filterAllDocsByPrefix(
|
|
2107
1419
|
this.remoteDB,
|
|
2108
|
-
|
|
1420
|
+
DocTypePrefixes["CARDRECORD" /* CARDRECORD */],
|
|
2109
1421
|
{
|
|
2110
1422
|
include_docs: true,
|
|
2111
1423
|
attachments: false
|
|
@@ -2146,7 +1458,7 @@ Currently logged-in as ${this._username}.`
|
|
|
2146
1458
|
} catch (e) {
|
|
2147
1459
|
const err = e;
|
|
2148
1460
|
if (err.status === 404) {
|
|
2149
|
-
await this.
|
|
1461
|
+
await this.writeDB.put({
|
|
2150
1462
|
_id: _BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS,
|
|
2151
1463
|
registrations: []
|
|
2152
1464
|
});
|
|
@@ -2194,10 +1506,10 @@ Currently logged-in as ${this._username}.`
|
|
|
2194
1506
|
}
|
|
2195
1507
|
}
|
|
2196
1508
|
async scheduleCardReview(review) {
|
|
2197
|
-
return scheduleCardReviewLocal(this.
|
|
1509
|
+
return scheduleCardReviewLocal(this.writeDB, review);
|
|
2198
1510
|
}
|
|
2199
1511
|
async removeScheduledCardReview(reviewId) {
|
|
2200
|
-
return removeScheduledCardReviewLocal(this.
|
|
1512
|
+
return removeScheduledCardReviewLocal(this.writeDB, reviewId);
|
|
2201
1513
|
}
|
|
2202
1514
|
async registerForClassroom(_classId, _registerAs) {
|
|
2203
1515
|
return registerUserForClassroom(this._username, _classId, _registerAs);
|
|
@@ -2428,18 +1740,21 @@ var init_StaticDataUnpacker = __esm({
|
|
|
2428
1740
|
async getTagsIndex() {
|
|
2429
1741
|
return await this.loadIndex("tags");
|
|
2430
1742
|
}
|
|
1743
|
+
getDocTypeFromId(id) {
|
|
1744
|
+
for (const docTypeKey in DocTypePrefixes) {
|
|
1745
|
+
const prefix = DocTypePrefixes[docTypeKey];
|
|
1746
|
+
if (id.startsWith(`${prefix}-`)) {
|
|
1747
|
+
return docTypeKey;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
return void 0;
|
|
1751
|
+
}
|
|
2431
1752
|
/**
|
|
2432
1753
|
* Find which chunk contains a specific document ID
|
|
2433
1754
|
*/
|
|
2434
1755
|
async findChunkForDocument(docId) {
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
if (docId.startsWith(`${docType}-`)) {
|
|
2438
|
-
expectedDocType = docType;
|
|
2439
|
-
break;
|
|
2440
|
-
}
|
|
2441
|
-
}
|
|
2442
|
-
if (expectedDocType !== void 0) {
|
|
1756
|
+
const expectedDocType = this.getDocTypeFromId(docId);
|
|
1757
|
+
if (expectedDocType) {
|
|
2443
1758
|
const typeChunks = this.manifest.chunks.filter((c) => c.docType === expectedDocType);
|
|
2444
1759
|
for (const chunk of typeChunks) {
|
|
2445
1760
|
if (docId >= chunk.startKey && docId <= chunk.endKey) {
|
|
@@ -2449,21 +1764,8 @@ var init_StaticDataUnpacker = __esm({
|
|
|
2449
1764
|
}
|
|
2450
1765
|
}
|
|
2451
1766
|
}
|
|
2452
|
-
return void 0;
|
|
2453
1767
|
} else {
|
|
2454
|
-
const
|
|
2455
|
-
(c) => c.docType === "DISPLAYABLE_DATA"
|
|
2456
|
-
);
|
|
2457
|
-
for (const chunk of displayableChunks) {
|
|
2458
|
-
if (docId >= chunk.startKey && docId <= chunk.endKey) {
|
|
2459
|
-
const exists = await this.verifyDocumentInChunk(docId, chunk);
|
|
2460
|
-
if (exists) {
|
|
2461
|
-
return chunk;
|
|
2462
|
-
}
|
|
2463
|
-
}
|
|
2464
|
-
}
|
|
2465
|
-
const cardChunks = this.manifest.chunks.filter((c) => c.docType === "CARD");
|
|
2466
|
-
for (const chunk of cardChunks) {
|
|
1768
|
+
for (const chunk of this.manifest.chunks) {
|
|
2467
1769
|
if (docId >= chunk.startKey && docId <= chunk.endKey) {
|
|
2468
1770
|
const exists = await this.verifyDocumentInChunk(docId, chunk);
|
|
2469
1771
|
if (exists) {
|
|
@@ -2484,6 +1786,7 @@ var init_StaticDataUnpacker = __esm({
|
|
|
2484
1786
|
}
|
|
2485
1787
|
return void 0;
|
|
2486
1788
|
}
|
|
1789
|
+
return void 0;
|
|
2487
1790
|
}
|
|
2488
1791
|
/**
|
|
2489
1792
|
* Verify that a document actually exists in a specific chunk by loading and checking it
|
|
@@ -2727,6 +2030,7 @@ var init_courseDB3 = __esm({
|
|
|
2727
2030
|
import_common11 = require("@vue-skuilder/common");
|
|
2728
2031
|
init_types_legacy();
|
|
2729
2032
|
init_navigators();
|
|
2033
|
+
init_logger();
|
|
2730
2034
|
StaticCourseDB = class {
|
|
2731
2035
|
constructor(courseId, unpacker, userDB, manifest) {
|
|
2732
2036
|
this.courseId = courseId;
|
|
@@ -2748,10 +2052,11 @@ var init_courseDB3 = __esm({
|
|
|
2748
2052
|
throw new Error("Cannot update course config in static mode");
|
|
2749
2053
|
}
|
|
2750
2054
|
async getCourseInfo() {
|
|
2055
|
+
const cardCount = this.manifest.chunks.filter((chunk) => chunk.docType === "CARD" /* CARD */).reduce((total, chunk) => total + chunk.documentCount, 0);
|
|
2751
2056
|
return {
|
|
2752
|
-
cardCount
|
|
2753
|
-
// Would come from manifest
|
|
2057
|
+
cardCount,
|
|
2754
2058
|
registeredUsers: 0
|
|
2059
|
+
// Always 0 in static mode
|
|
2755
2060
|
};
|
|
2756
2061
|
}
|
|
2757
2062
|
async getCourseDoc(id, _options) {
|
|
@@ -2840,12 +2145,56 @@ var init_courseDB3 = __esm({
|
|
|
2840
2145
|
courseID: this.courseId
|
|
2841
2146
|
}));
|
|
2842
2147
|
}
|
|
2843
|
-
async getAppliedTags(
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
rows
|
|
2848
|
-
|
|
2148
|
+
async getAppliedTags(cardId) {
|
|
2149
|
+
try {
|
|
2150
|
+
const tagsIndex = await this.unpacker.getTagsIndex();
|
|
2151
|
+
const cardTags = tagsIndex.byCard[cardId] || [];
|
|
2152
|
+
const rows = await Promise.all(
|
|
2153
|
+
cardTags.map(async (tagName) => {
|
|
2154
|
+
const tagId = `${"TAG" /* TAG */}-${tagName}`;
|
|
2155
|
+
try {
|
|
2156
|
+
const tagDoc = await this.unpacker.getDocument(tagId);
|
|
2157
|
+
return {
|
|
2158
|
+
id: tagId,
|
|
2159
|
+
key: cardId,
|
|
2160
|
+
value: {
|
|
2161
|
+
name: tagDoc.name,
|
|
2162
|
+
snippet: tagDoc.snippet,
|
|
2163
|
+
count: tagDoc.taggedCards?.length || 0
|
|
2164
|
+
}
|
|
2165
|
+
};
|
|
2166
|
+
} catch (error) {
|
|
2167
|
+
if (error && error.status === 404) {
|
|
2168
|
+
logger.warn(`Tag document not found for ${tagName}, creating stub`);
|
|
2169
|
+
} else {
|
|
2170
|
+
logger.error(`Error getting tag document for ${tagName}:`, error);
|
|
2171
|
+
throw error;
|
|
2172
|
+
}
|
|
2173
|
+
return {
|
|
2174
|
+
id: tagId,
|
|
2175
|
+
key: cardId,
|
|
2176
|
+
value: {
|
|
2177
|
+
name: tagName,
|
|
2178
|
+
snippet: `Tag: ${tagName}`,
|
|
2179
|
+
count: tagsIndex.byTag[tagName]?.length || 0
|
|
2180
|
+
}
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
})
|
|
2184
|
+
);
|
|
2185
|
+
return {
|
|
2186
|
+
total_rows: rows.length,
|
|
2187
|
+
offset: 0,
|
|
2188
|
+
rows
|
|
2189
|
+
};
|
|
2190
|
+
} catch (error) {
|
|
2191
|
+
logger.error(`Error getting applied tags for card ${cardId}:`, error);
|
|
2192
|
+
return {
|
|
2193
|
+
total_rows: 0,
|
|
2194
|
+
offset: 0,
|
|
2195
|
+
rows: []
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2849
2198
|
}
|
|
2850
2199
|
async addTagToCard(_cardId, _tagId) {
|
|
2851
2200
|
throw new Error("Cannot modify tags in static mode");
|
|
@@ -2863,11 +2212,69 @@ var init_courseDB3 = __esm({
|
|
|
2863
2212
|
throw new Error("Cannot update tags in static mode");
|
|
2864
2213
|
}
|
|
2865
2214
|
async getCourseTagStubs() {
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2215
|
+
try {
|
|
2216
|
+
const tagsIndex = await this.unpacker.getTagsIndex();
|
|
2217
|
+
if (!tagsIndex || !tagsIndex.byTag) {
|
|
2218
|
+
logger.warn("Tags index not found or empty");
|
|
2219
|
+
return {
|
|
2220
|
+
total_rows: 0,
|
|
2221
|
+
offset: 0,
|
|
2222
|
+
rows: []
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
const tagNames = Object.keys(tagsIndex.byTag);
|
|
2226
|
+
const rows = await Promise.all(
|
|
2227
|
+
tagNames.map(async (tagName) => {
|
|
2228
|
+
const cardIds = tagsIndex.byTag[tagName] || [];
|
|
2229
|
+
const tagId = `${"TAG" /* TAG */}-${tagName}`;
|
|
2230
|
+
try {
|
|
2231
|
+
const tagDoc = await this.unpacker.getDocument(tagId);
|
|
2232
|
+
return {
|
|
2233
|
+
id: tagId,
|
|
2234
|
+
key: tagId,
|
|
2235
|
+
value: { rev: "1-static" },
|
|
2236
|
+
doc: tagDoc
|
|
2237
|
+
};
|
|
2238
|
+
} catch (error) {
|
|
2239
|
+
if (error && error.status === 404) {
|
|
2240
|
+
logger.warn(`Tag document not found for ${tagName}, creating stub`);
|
|
2241
|
+
const stubDoc = {
|
|
2242
|
+
_id: tagId,
|
|
2243
|
+
_rev: "1-static",
|
|
2244
|
+
course: this.courseId,
|
|
2245
|
+
docType: "TAG" /* TAG */,
|
|
2246
|
+
name: tagName,
|
|
2247
|
+
snippet: `Tag: ${tagName}`,
|
|
2248
|
+
wiki: "",
|
|
2249
|
+
taggedCards: cardIds,
|
|
2250
|
+
author: "system"
|
|
2251
|
+
};
|
|
2252
|
+
return {
|
|
2253
|
+
id: tagId,
|
|
2254
|
+
key: tagId,
|
|
2255
|
+
value: { rev: "1-static" },
|
|
2256
|
+
doc: stubDoc
|
|
2257
|
+
};
|
|
2258
|
+
} else {
|
|
2259
|
+
logger.error(`Error getting tag document for ${tagName}:`, error);
|
|
2260
|
+
throw error;
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
})
|
|
2264
|
+
);
|
|
2265
|
+
return {
|
|
2266
|
+
total_rows: rows.length,
|
|
2267
|
+
offset: 0,
|
|
2268
|
+
rows
|
|
2269
|
+
};
|
|
2270
|
+
} catch (error) {
|
|
2271
|
+
logger.error("Failed to get course tag stubs:", error);
|
|
2272
|
+
return {
|
|
2273
|
+
total_rows: 0,
|
|
2274
|
+
offset: 0,
|
|
2275
|
+
rows: []
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2871
2278
|
}
|
|
2872
2279
|
async addNote(_codeCourse, _shape, _data, _author, _tags, _uploads, _elo) {
|
|
2873
2280
|
return {
|
|
@@ -2973,6 +2380,9 @@ var init_NoOpSyncStrategy = __esm({
|
|
|
2973
2380
|
setupRemoteDB(username) {
|
|
2974
2381
|
return getLocalUserDB(username);
|
|
2975
2382
|
}
|
|
2383
|
+
getWriteDB(username) {
|
|
2384
|
+
return getLocalUserDB(username);
|
|
2385
|
+
}
|
|
2976
2386
|
startSync(_localDB, _remoteDB) {
|
|
2977
2387
|
}
|
|
2978
2388
|
stopSync() {
|