@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
|
@@ -80,32 +80,31 @@ var init_SyncStrategy = __esm({
|
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
// src/core/types/types-legacy.ts
|
|
83
|
-
var GuestUsername,
|
|
83
|
+
var GuestUsername, DocTypePrefixes;
|
|
84
84
|
var init_types_legacy = __esm({
|
|
85
85
|
"src/core/types/types-legacy.ts"() {
|
|
86
86
|
"use strict";
|
|
87
87
|
init_logger();
|
|
88
88
|
GuestUsername = "Guest";
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
cardHistoryPrefix = "cardH";
|
|
89
|
+
DocTypePrefixes = {
|
|
90
|
+
["CARD" /* CARD */]: "c",
|
|
91
|
+
["DISPLAYABLE_DATA" /* DISPLAYABLE_DATA */]: "dd",
|
|
92
|
+
["TAG" /* TAG */]: "TAG",
|
|
93
|
+
["CARDRECORD" /* CARDRECORD */]: "cardH",
|
|
94
|
+
["SCHEDULED_CARD" /* SCHEDULED_CARD */]: "card_review_",
|
|
95
|
+
// Add other doctypes here as they get prefixed IDs
|
|
96
|
+
["DATASHAPE" /* DATASHAPE */]: "DATASHAPE",
|
|
97
|
+
["QUESTION" /* QUESTIONTYPE */]: "QUESTION",
|
|
98
|
+
["VIEW" /* VIEW */]: "VIEW",
|
|
99
|
+
["PEDAGOGY" /* PEDAGOGY */]: "PEDAGOGY",
|
|
100
|
+
["NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */]: "NAVIGATION_STRATEGY"
|
|
101
|
+
};
|
|
103
102
|
}
|
|
104
103
|
});
|
|
105
104
|
|
|
106
105
|
// src/core/util/index.ts
|
|
107
106
|
function getCardHistoryID(courseID, cardID) {
|
|
108
|
-
return `${
|
|
107
|
+
return `${DocTypePrefixes["CARDRECORD" /* CARDRECORD */]}-${courseID}-${cardID}`;
|
|
109
108
|
}
|
|
110
109
|
var init_util = __esm({
|
|
111
110
|
"src/core/util/index.ts"() {
|
|
@@ -188,11 +187,11 @@ function scheduleCardReviewLocal(userDB, review) {
|
|
|
188
187
|
const now = moment.utc();
|
|
189
188
|
logger.info(`Scheduling for review in: ${review.time.diff(now, "h") / 24} days`);
|
|
190
189
|
void userDB.put({
|
|
191
|
-
_id:
|
|
190
|
+
_id: DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */] + review.time.format(REVIEW_TIME_FORMAT),
|
|
192
191
|
cardId: review.card_id,
|
|
193
|
-
reviewTime: review.time,
|
|
192
|
+
reviewTime: review.time.toISOString(),
|
|
194
193
|
courseId: review.course_id,
|
|
195
|
-
scheduledAt: now,
|
|
194
|
+
scheduledAt: now.toISOString(),
|
|
196
195
|
scheduledFor: review.scheduledFor,
|
|
197
196
|
schedulingAgentId: review.schedulingAgentId
|
|
198
197
|
});
|
|
@@ -208,14 +207,14 @@ async function removeScheduledCardReviewLocal(userDB, reviewDocID) {
|
|
|
208
207
|
${JSON.stringify(err)}`);
|
|
209
208
|
});
|
|
210
209
|
}
|
|
211
|
-
var
|
|
210
|
+
var REVIEW_TIME_FORMAT, log;
|
|
212
211
|
var init_userDBHelpers = __esm({
|
|
213
212
|
"src/impl/common/userDBHelpers.ts"() {
|
|
214
213
|
"use strict";
|
|
214
|
+
init_core();
|
|
215
215
|
init_logger();
|
|
216
216
|
init_pouchdb_setup();
|
|
217
217
|
init_dataDirectory();
|
|
218
|
-
REVIEW_PREFIX = "card_review_";
|
|
219
218
|
REVIEW_TIME_FORMAT = "YYYY-MM-DD--kk:mm:ss-SSS";
|
|
220
219
|
log = (s) => {
|
|
221
220
|
logger.info(s);
|
|
@@ -250,7 +249,10 @@ var init_updateQueue = __esm({
|
|
|
250
249
|
_className = "UpdateQueue";
|
|
251
250
|
pendingUpdates = {};
|
|
252
251
|
inprogressUpdates = {};
|
|
253
|
-
|
|
252
|
+
readDB;
|
|
253
|
+
// Database for read operations
|
|
254
|
+
writeDB;
|
|
255
|
+
// Database for write operations (local-first)
|
|
254
256
|
update(id, update) {
|
|
255
257
|
logger.debug(`Update requested on doc: ${id}`);
|
|
256
258
|
if (this.pendingUpdates[id]) {
|
|
@@ -260,24 +262,25 @@ var init_updateQueue = __esm({
|
|
|
260
262
|
}
|
|
261
263
|
return this.applyUpdates(id);
|
|
262
264
|
}
|
|
263
|
-
constructor(
|
|
265
|
+
constructor(readDB, writeDB) {
|
|
264
266
|
super();
|
|
265
|
-
this.
|
|
267
|
+
this.readDB = readDB;
|
|
268
|
+
this.writeDB = writeDB || readDB;
|
|
266
269
|
logger.debug(`UpdateQ initialized...`);
|
|
267
|
-
void this.
|
|
270
|
+
void this.readDB.info().then((i) => {
|
|
268
271
|
logger.debug(`db info: ${JSON.stringify(i)}`);
|
|
269
272
|
});
|
|
270
273
|
}
|
|
271
274
|
async applyUpdates(id) {
|
|
272
275
|
logger.debug(`Applying updates on doc: ${id}`);
|
|
273
276
|
if (this.inprogressUpdates[id]) {
|
|
274
|
-
await this.
|
|
277
|
+
await this.readDB.info();
|
|
275
278
|
return this.applyUpdates(id);
|
|
276
279
|
} else {
|
|
277
280
|
if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {
|
|
278
281
|
this.inprogressUpdates[id] = true;
|
|
279
282
|
try {
|
|
280
|
-
let doc = await this.
|
|
283
|
+
let doc = await this.readDB.get(id);
|
|
281
284
|
logger.debug(`Retrieved doc: ${id}`);
|
|
282
285
|
while (this.pendingUpdates[id].length !== 0) {
|
|
283
286
|
const update = this.pendingUpdates[id].splice(0, 1)[0];
|
|
@@ -290,7 +293,7 @@ var init_updateQueue = __esm({
|
|
|
290
293
|
};
|
|
291
294
|
}
|
|
292
295
|
}
|
|
293
|
-
await this.
|
|
296
|
+
await this.writeDB.put(doc);
|
|
294
297
|
logger.debug(`Put doc: ${id}`);
|
|
295
298
|
if (this.pendingUpdates[id].length === 0) {
|
|
296
299
|
this.inprogressUpdates[id] = false;
|
|
@@ -316,22 +319,64 @@ var init_updateQueue = __esm({
|
|
|
316
319
|
}
|
|
317
320
|
});
|
|
318
321
|
|
|
319
|
-
// src/impl/couch/
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
322
|
+
// src/impl/couch/user-course-relDB.ts
|
|
323
|
+
import moment2 from "moment";
|
|
324
|
+
var UsrCrsData;
|
|
325
|
+
var init_user_course_relDB = __esm({
|
|
326
|
+
"src/impl/couch/user-course-relDB.ts"() {
|
|
327
|
+
"use strict";
|
|
328
|
+
init_logger();
|
|
329
|
+
UsrCrsData = class {
|
|
330
|
+
user;
|
|
331
|
+
_courseId;
|
|
332
|
+
constructor(user, courseId) {
|
|
333
|
+
this.user = user;
|
|
334
|
+
this._courseId = courseId;
|
|
335
|
+
}
|
|
336
|
+
async getReviewsForcast(daysCount) {
|
|
337
|
+
const time = moment2.utc().add(daysCount, "days");
|
|
338
|
+
return this.getReviewstoDate(time);
|
|
339
|
+
}
|
|
340
|
+
async getPendingReviews() {
|
|
341
|
+
const now = moment2.utc();
|
|
342
|
+
return this.getReviewstoDate(now);
|
|
343
|
+
}
|
|
344
|
+
async getScheduledReviewCount() {
|
|
345
|
+
return (await this.getPendingReviews()).length;
|
|
346
|
+
}
|
|
347
|
+
async getCourseSettings() {
|
|
348
|
+
const regDoc = await this.user.getCourseRegistrationsDoc();
|
|
349
|
+
const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);
|
|
350
|
+
if (crsDoc && crsDoc.settings) {
|
|
351
|
+
return crsDoc.settings;
|
|
352
|
+
} else {
|
|
353
|
+
logger.warn(`no settings found during lookup on course ${this._courseId}`);
|
|
354
|
+
return {};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
updateCourseSettings(updates) {
|
|
358
|
+
if ("updateCourseSettings" in this.user) {
|
|
359
|
+
void this.user.updateCourseSettings(this._courseId, updates);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
async getReviewstoDate(targetDate) {
|
|
363
|
+
const allReviews = await this.user.getPendingReviews(this._courseId);
|
|
364
|
+
logger.debug(
|
|
365
|
+
`Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`
|
|
366
|
+
);
|
|
367
|
+
return allReviews.filter((review) => {
|
|
368
|
+
const reviewTime = moment2.utc(review.reviewTime);
|
|
369
|
+
return targetDate.isAfter(reviewTime);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
};
|
|
323
373
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
async function GET_ITEM(k) {
|
|
328
|
-
throw new Error(`No implementation found for GET_CACHED(${k})`);
|
|
329
|
-
}
|
|
330
|
-
var CLIENT_CACHE;
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// src/impl/couch/clientCache.ts
|
|
331
377
|
var init_clientCache = __esm({
|
|
332
378
|
"src/impl/couch/clientCache.ts"() {
|
|
333
379
|
"use strict";
|
|
334
|
-
CLIENT_CACHE = {};
|
|
335
380
|
}
|
|
336
381
|
});
|
|
337
382
|
|
|
@@ -339,94 +384,7 @@ var init_clientCache = __esm({
|
|
|
339
384
|
import { NameSpacer } from "@vue-skuilder/common";
|
|
340
385
|
import { blankCourseElo, toCourseElo } from "@vue-skuilder/common";
|
|
341
386
|
import { prepareNote55 } from "@vue-skuilder/common";
|
|
342
|
-
|
|
343
|
-
const db = getCourseDB(courseID);
|
|
344
|
-
const payload = prepareNote55(courseID, codeCourse, shape, data, author, tags, uploads);
|
|
345
|
-
const result = await db.post(payload);
|
|
346
|
-
const dataShapeId = NameSpacer.getDataShapeString({
|
|
347
|
-
course: codeCourse,
|
|
348
|
-
dataShape: shape.name
|
|
349
|
-
});
|
|
350
|
-
if (result.ok) {
|
|
351
|
-
try {
|
|
352
|
-
await createCards(courseID, dataShapeId, result.id, tags, elo, author);
|
|
353
|
-
} catch (error) {
|
|
354
|
-
let errorMessage = "Unknown error";
|
|
355
|
-
if (error instanceof Error) {
|
|
356
|
-
errorMessage = error.message;
|
|
357
|
-
} else if (error && typeof error === "object" && "reason" in error) {
|
|
358
|
-
errorMessage = error.reason;
|
|
359
|
-
} else if (error && typeof error === "object" && "message" in error) {
|
|
360
|
-
errorMessage = error.message;
|
|
361
|
-
} else {
|
|
362
|
-
errorMessage = String(error);
|
|
363
|
-
}
|
|
364
|
-
logger.error(`[addNote55] Failed to create cards for note ${result.id}: ${errorMessage}`);
|
|
365
|
-
result.cardCreationFailed = true;
|
|
366
|
-
result.cardCreationError = errorMessage;
|
|
367
|
-
}
|
|
368
|
-
} else {
|
|
369
|
-
logger.error(`[addNote55] Error adding note. Result: ${JSON.stringify(result)}`);
|
|
370
|
-
}
|
|
371
|
-
return result;
|
|
372
|
-
}
|
|
373
|
-
async function createCards(courseID, datashapeID, noteID, tags, elo = blankCourseElo(), author) {
|
|
374
|
-
const cfg = await getCredentialledCourseConfig(courseID);
|
|
375
|
-
const dsDescriptor = NameSpacer.getDataShapeDescriptor(datashapeID);
|
|
376
|
-
let questionViewTypes = [];
|
|
377
|
-
for (const ds of cfg.dataShapes) {
|
|
378
|
-
if (ds.name === datashapeID) {
|
|
379
|
-
questionViewTypes = ds.questionTypes;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
if (questionViewTypes.length === 0) {
|
|
383
|
-
const errorMsg = `No questionViewTypes found for datashapeID: ${datashapeID} in course config. Cards cannot be created.`;
|
|
384
|
-
logger.error(errorMsg);
|
|
385
|
-
throw new Error(errorMsg);
|
|
386
|
-
}
|
|
387
|
-
for (const questionView of questionViewTypes) {
|
|
388
|
-
await createCard(questionView, courseID, dsDescriptor, noteID, tags, elo, author);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
async function createCard(questionViewName, courseID, dsDescriptor, noteID, tags, elo = blankCourseElo(), author) {
|
|
392
|
-
const qDescriptor = NameSpacer.getQuestionDescriptor(questionViewName);
|
|
393
|
-
const cfg = await getCredentialledCourseConfig(courseID);
|
|
394
|
-
for (const rQ of cfg.questionTypes) {
|
|
395
|
-
if (rQ.name === questionViewName) {
|
|
396
|
-
for (const view of rQ.viewList) {
|
|
397
|
-
await addCard(
|
|
398
|
-
courseID,
|
|
399
|
-
dsDescriptor.course,
|
|
400
|
-
[noteID],
|
|
401
|
-
NameSpacer.getViewString({
|
|
402
|
-
course: qDescriptor.course,
|
|
403
|
-
questionType: qDescriptor.questionType,
|
|
404
|
-
view
|
|
405
|
-
}),
|
|
406
|
-
elo,
|
|
407
|
-
tags,
|
|
408
|
-
author
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
async function addCard(courseID, course, id_displayable_data, id_view, elo, tags, author) {
|
|
415
|
-
const db = getCourseDB(courseID);
|
|
416
|
-
const card = await db.post({
|
|
417
|
-
course,
|
|
418
|
-
id_displayable_data,
|
|
419
|
-
id_view,
|
|
420
|
-
docType: "CARD" /* CARD */,
|
|
421
|
-
elo: elo || toCourseElo(990 + Math.round(20 * Math.random())),
|
|
422
|
-
author
|
|
423
|
-
});
|
|
424
|
-
for (const tag of tags) {
|
|
425
|
-
logger.info(`adding tag: ${tag} to card ${card.id}`);
|
|
426
|
-
await addTagToCard(courseID, card.id, tag, author, false);
|
|
427
|
-
}
|
|
428
|
-
return card;
|
|
429
|
-
}
|
|
387
|
+
import { v4 as uuidv4 } from "uuid";
|
|
430
388
|
async function getCredentialledCourseConfig(courseID) {
|
|
431
389
|
try {
|
|
432
390
|
const db = getCourseDB(courseID);
|
|
@@ -439,66 +397,6 @@ async function getCredentialledCourseConfig(courseID) {
|
|
|
439
397
|
throw e;
|
|
440
398
|
}
|
|
441
399
|
}
|
|
442
|
-
async function addTagToCard(courseID, cardID, tagID, author, updateELO = true) {
|
|
443
|
-
const prefixedTagID = getTagID(tagID);
|
|
444
|
-
const courseDB = getCourseDB(courseID);
|
|
445
|
-
const courseApi = new CourseDB(courseID, async () => {
|
|
446
|
-
const dummySyncStrategy = {
|
|
447
|
-
setupRemoteDB: () => null,
|
|
448
|
-
startSync: () => {
|
|
449
|
-
},
|
|
450
|
-
canCreateAccount: () => false,
|
|
451
|
-
canAuthenticate: () => false,
|
|
452
|
-
getCurrentUsername: async () => "DummyUser"
|
|
453
|
-
};
|
|
454
|
-
return BaseUser.Dummy(dummySyncStrategy);
|
|
455
|
-
});
|
|
456
|
-
try {
|
|
457
|
-
logger.info(`Applying tag ${tagID} to card ${courseID + "-" + cardID}...`);
|
|
458
|
-
const tag = await courseDB.get(prefixedTagID);
|
|
459
|
-
if (!tag.taggedCards.includes(cardID)) {
|
|
460
|
-
tag.taggedCards.push(cardID);
|
|
461
|
-
if (updateELO) {
|
|
462
|
-
try {
|
|
463
|
-
const eloData = await courseApi.getCardEloData([cardID]);
|
|
464
|
-
const elo = eloData[0];
|
|
465
|
-
elo.tags[tagID] = {
|
|
466
|
-
count: 0,
|
|
467
|
-
score: elo.global.score
|
|
468
|
-
// todo: or 1000?
|
|
469
|
-
};
|
|
470
|
-
await updateCardElo(courseID, cardID, elo);
|
|
471
|
-
} catch (error) {
|
|
472
|
-
logger.error("Failed to update ELO data for card:", cardID, error);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
return courseDB.put(tag);
|
|
476
|
-
} else throw new AlreadyTaggedErr(`Card ${cardID} is already tagged with ${tagID}`);
|
|
477
|
-
} catch (e) {
|
|
478
|
-
if (e instanceof AlreadyTaggedErr) {
|
|
479
|
-
throw e;
|
|
480
|
-
}
|
|
481
|
-
await createTag(courseID, tagID, author);
|
|
482
|
-
return addTagToCard(courseID, cardID, tagID, author, updateELO);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
async function updateCardElo(courseID, cardID, elo) {
|
|
486
|
-
if (elo) {
|
|
487
|
-
const cDB = getCourseDB(courseID);
|
|
488
|
-
const card = await cDB.get(cardID);
|
|
489
|
-
logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);
|
|
490
|
-
card.elo = elo;
|
|
491
|
-
return cDB.put(card);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
function getTagID(tagName) {
|
|
495
|
-
const tagPrefix = "TAG" /* TAG */.valueOf() + "-";
|
|
496
|
-
if (tagName.indexOf(tagPrefix) === 0) {
|
|
497
|
-
return tagName;
|
|
498
|
-
} else {
|
|
499
|
-
return tagPrefix + tagName;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
400
|
function getCourseDB(courseID) {
|
|
503
401
|
const dbName = `coursedb-${courseID}`;
|
|
504
402
|
return new pouchdb_setup_default(
|
|
@@ -506,7 +404,6 @@ function getCourseDB(courseID) {
|
|
|
506
404
|
pouchDBincludeCredentialsConfig
|
|
507
405
|
);
|
|
508
406
|
}
|
|
509
|
-
var AlreadyTaggedErr;
|
|
510
407
|
var init_courseAPI = __esm({
|
|
511
408
|
"src/impl/couch/courseAPI.ts"() {
|
|
512
409
|
"use strict";
|
|
@@ -517,12 +414,6 @@ var init_courseAPI = __esm({
|
|
|
517
414
|
init_types_legacy();
|
|
518
415
|
init_common();
|
|
519
416
|
init_logger();
|
|
520
|
-
AlreadyTaggedErr = class extends Error {
|
|
521
|
-
constructor(message) {
|
|
522
|
-
super(message);
|
|
523
|
-
this.name = "AlreadyTaggedErr";
|
|
524
|
-
}
|
|
525
|
-
};
|
|
526
417
|
}
|
|
527
418
|
});
|
|
528
419
|
|
|
@@ -663,82 +554,6 @@ import {
|
|
|
663
554
|
blankCourseElo as blankCourseElo2,
|
|
664
555
|
toCourseElo as toCourseElo2
|
|
665
556
|
} from "@vue-skuilder/common";
|
|
666
|
-
function randIntWeightedTowardZero(n) {
|
|
667
|
-
return Math.floor(Math.random() * Math.random() * Math.random() * n);
|
|
668
|
-
}
|
|
669
|
-
async function getCourseTagStubs(courseID) {
|
|
670
|
-
logger.debug(`Getting tag stubs for course: ${courseID}`);
|
|
671
|
-
const stubs = await filterAllDocsByPrefix2(
|
|
672
|
-
getCourseDB2(courseID),
|
|
673
|
-
"TAG" /* TAG */.valueOf() + "-"
|
|
674
|
-
);
|
|
675
|
-
stubs.rows.forEach((row) => {
|
|
676
|
-
logger.debug(` Tag stub for doc: ${row.id}`);
|
|
677
|
-
});
|
|
678
|
-
return stubs;
|
|
679
|
-
}
|
|
680
|
-
async function createTag(courseID, tagName, author) {
|
|
681
|
-
logger.debug(`Creating tag: ${tagName}...`);
|
|
682
|
-
const tagID = getTagID(tagName);
|
|
683
|
-
const courseDB = getCourseDB2(courseID);
|
|
684
|
-
const resp = await courseDB.put({
|
|
685
|
-
course: courseID,
|
|
686
|
-
docType: "TAG" /* TAG */,
|
|
687
|
-
name: tagName,
|
|
688
|
-
snippet: "",
|
|
689
|
-
taggedCards: [],
|
|
690
|
-
wiki: "",
|
|
691
|
-
author,
|
|
692
|
-
_id: tagID
|
|
693
|
-
});
|
|
694
|
-
return resp;
|
|
695
|
-
}
|
|
696
|
-
async function updateTag(tag) {
|
|
697
|
-
const prior = await getTag(tag.course, tag.name);
|
|
698
|
-
return await getCourseDB2(tag.course).put({
|
|
699
|
-
...tag,
|
|
700
|
-
_rev: prior._rev
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
async function getTag(courseID, tagName) {
|
|
704
|
-
const tagID = getTagID(tagName);
|
|
705
|
-
const courseDB = getCourseDB2(courseID);
|
|
706
|
-
return courseDB.get(tagID);
|
|
707
|
-
}
|
|
708
|
-
async function removeTagFromCard(courseID, cardID, tagID) {
|
|
709
|
-
tagID = getTagID(tagID);
|
|
710
|
-
const courseDB = getCourseDB2(courseID);
|
|
711
|
-
const tag = await courseDB.get(tagID);
|
|
712
|
-
tag.taggedCards = tag.taggedCards.filter((taggedID) => {
|
|
713
|
-
return cardID !== taggedID;
|
|
714
|
-
});
|
|
715
|
-
return courseDB.put(tag);
|
|
716
|
-
}
|
|
717
|
-
async function getAppliedTags(id_course, id_card) {
|
|
718
|
-
const db = getCourseDB2(id_course);
|
|
719
|
-
const result = await db.query("getTags", {
|
|
720
|
-
startkey: id_card,
|
|
721
|
-
endkey: id_card
|
|
722
|
-
// include_docs: true
|
|
723
|
-
});
|
|
724
|
-
return result;
|
|
725
|
-
}
|
|
726
|
-
async function updateCredentialledCourseConfig(courseID, config) {
|
|
727
|
-
logger.debug(`Updating course config:
|
|
728
|
-
|
|
729
|
-
${JSON.stringify(config)}
|
|
730
|
-
`);
|
|
731
|
-
const db = getCourseDB2(courseID);
|
|
732
|
-
const old = await getCredentialledCourseConfig(courseID);
|
|
733
|
-
return await db.put({
|
|
734
|
-
...config,
|
|
735
|
-
_rev: old._rev
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
function isSuccessRow(row) {
|
|
739
|
-
return "doc" in row && row.doc !== null && row.doc !== void 0;
|
|
740
|
-
}
|
|
741
|
-
var CourseDB;
|
|
742
557
|
var init_courseDB = __esm({
|
|
743
558
|
"src/impl/couch/courseDB.ts"() {
|
|
744
559
|
"use strict";
|
|
@@ -750,422 +565,11 @@ var init_courseDB = __esm({
|
|
|
750
565
|
init_courseAPI();
|
|
751
566
|
init_courseLookupDB();
|
|
752
567
|
init_navigators();
|
|
753
|
-
CourseDB = class {
|
|
754
|
-
// private log(msg: string): void {
|
|
755
|
-
// log(`CourseLog: ${this.id}\n ${msg}`);
|
|
756
|
-
// }
|
|
757
|
-
db;
|
|
758
|
-
id;
|
|
759
|
-
_getCurrentUser;
|
|
760
|
-
updateQueue;
|
|
761
|
-
constructor(id, userLookup) {
|
|
762
|
-
this.id = id;
|
|
763
|
-
this.db = getCourseDB2(this.id);
|
|
764
|
-
this._getCurrentUser = userLookup;
|
|
765
|
-
this.updateQueue = new UpdateQueue(this.db);
|
|
766
|
-
}
|
|
767
|
-
getCourseID() {
|
|
768
|
-
return this.id;
|
|
769
|
-
}
|
|
770
|
-
async getCourseInfo() {
|
|
771
|
-
const cardCount = (await this.db.find({
|
|
772
|
-
selector: {
|
|
773
|
-
docType: "CARD" /* CARD */
|
|
774
|
-
},
|
|
775
|
-
limit: 1e3
|
|
776
|
-
})).docs.length;
|
|
777
|
-
return {
|
|
778
|
-
cardCount,
|
|
779
|
-
registeredUsers: 0
|
|
780
|
-
};
|
|
781
|
-
}
|
|
782
|
-
async getInexperiencedCards(limit = 2) {
|
|
783
|
-
return (await this.db.query("cardsByInexperience", {
|
|
784
|
-
limit
|
|
785
|
-
})).rows.map((r) => {
|
|
786
|
-
const ret = {
|
|
787
|
-
courseId: this.id,
|
|
788
|
-
cardId: r.id,
|
|
789
|
-
count: r.key,
|
|
790
|
-
elo: r.value
|
|
791
|
-
};
|
|
792
|
-
return ret;
|
|
793
|
-
});
|
|
794
|
-
}
|
|
795
|
-
async getCardsByEloLimits(options = {
|
|
796
|
-
low: 0,
|
|
797
|
-
high: Number.MIN_SAFE_INTEGER,
|
|
798
|
-
limit: 25,
|
|
799
|
-
page: 0
|
|
800
|
-
}) {
|
|
801
|
-
return (await this.db.query("elo", {
|
|
802
|
-
startkey: options.low,
|
|
803
|
-
endkey: options.high,
|
|
804
|
-
limit: options.limit,
|
|
805
|
-
skip: options.limit * options.page
|
|
806
|
-
})).rows.map((r) => {
|
|
807
|
-
return `${this.id}-${r.id}-${r.key}`;
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
async getCardEloData(id) {
|
|
811
|
-
const docs = await this.db.allDocs({
|
|
812
|
-
keys: id,
|
|
813
|
-
include_docs: true
|
|
814
|
-
});
|
|
815
|
-
const ret = [];
|
|
816
|
-
docs.rows.forEach((r) => {
|
|
817
|
-
if (isSuccessRow(r)) {
|
|
818
|
-
if (r.doc && r.doc.elo) {
|
|
819
|
-
ret.push(toCourseElo2(r.doc.elo));
|
|
820
|
-
} else {
|
|
821
|
-
logger.warn("no elo data for card: " + r.id);
|
|
822
|
-
ret.push(blankCourseElo2());
|
|
823
|
-
}
|
|
824
|
-
} else {
|
|
825
|
-
logger.warn("no elo data for card: " + JSON.stringify(r));
|
|
826
|
-
ret.push(blankCourseElo2());
|
|
827
|
-
}
|
|
828
|
-
});
|
|
829
|
-
return ret;
|
|
830
|
-
}
|
|
831
|
-
/**
|
|
832
|
-
* Returns the lowest and highest `global` ELO ratings in the course
|
|
833
|
-
*/
|
|
834
|
-
async getELOBounds() {
|
|
835
|
-
const [low, high] = await Promise.all([
|
|
836
|
-
(await this.db.query("elo", {
|
|
837
|
-
startkey: 0,
|
|
838
|
-
limit: 1,
|
|
839
|
-
include_docs: false
|
|
840
|
-
})).rows[0].key,
|
|
841
|
-
(await this.db.query("elo", {
|
|
842
|
-
limit: 1,
|
|
843
|
-
descending: true,
|
|
844
|
-
startkey: 1e5
|
|
845
|
-
})).rows[0].key
|
|
846
|
-
]);
|
|
847
|
-
return {
|
|
848
|
-
low,
|
|
849
|
-
high
|
|
850
|
-
};
|
|
851
|
-
}
|
|
852
|
-
async removeCard(id) {
|
|
853
|
-
const doc = await this.db.get(id);
|
|
854
|
-
if (!doc.docType || !(doc.docType === "CARD" /* CARD */)) {
|
|
855
|
-
throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);
|
|
856
|
-
}
|
|
857
|
-
return this.db.remove(doc);
|
|
858
|
-
}
|
|
859
|
-
async getCardDisplayableDataIDs(id) {
|
|
860
|
-
logger.debug(id.join(", "));
|
|
861
|
-
const cards = await this.db.allDocs({
|
|
862
|
-
keys: id,
|
|
863
|
-
include_docs: true
|
|
864
|
-
});
|
|
865
|
-
const ret = {};
|
|
866
|
-
cards.rows.forEach((r) => {
|
|
867
|
-
if (isSuccessRow(r)) {
|
|
868
|
-
ret[r.id] = r.doc.id_displayable_data;
|
|
869
|
-
}
|
|
870
|
-
});
|
|
871
|
-
await Promise.all(
|
|
872
|
-
cards.rows.map((r) => {
|
|
873
|
-
return async () => {
|
|
874
|
-
if (isSuccessRow(r)) {
|
|
875
|
-
ret[r.id] = r.doc.id_displayable_data;
|
|
876
|
-
}
|
|
877
|
-
};
|
|
878
|
-
})
|
|
879
|
-
);
|
|
880
|
-
return ret;
|
|
881
|
-
}
|
|
882
|
-
async getCardsByELO(elo, cardLimit) {
|
|
883
|
-
elo = parseInt(elo);
|
|
884
|
-
const limit = cardLimit ? cardLimit : 25;
|
|
885
|
-
const below = await this.db.query("elo", {
|
|
886
|
-
limit: Math.ceil(limit / 2),
|
|
887
|
-
startkey: elo,
|
|
888
|
-
descending: true
|
|
889
|
-
});
|
|
890
|
-
const aboveLimit = limit - below.rows.length;
|
|
891
|
-
const above = await this.db.query("elo", {
|
|
892
|
-
limit: aboveLimit,
|
|
893
|
-
startkey: elo + 1
|
|
894
|
-
});
|
|
895
|
-
let cards = below.rows;
|
|
896
|
-
cards = cards.concat(above.rows);
|
|
897
|
-
const ret = cards.sort((a, b) => {
|
|
898
|
-
const s = Math.abs(a.key - elo) - Math.abs(b.key - elo);
|
|
899
|
-
if (s === 0) {
|
|
900
|
-
return Math.random() - 0.5;
|
|
901
|
-
} else {
|
|
902
|
-
return s;
|
|
903
|
-
}
|
|
904
|
-
}).map((c) => `${this.id}-${c.id}-${c.key}`);
|
|
905
|
-
const str = `below:
|
|
906
|
-
${below.rows.map((r) => ` ${r.id}-${r.key}
|
|
907
|
-
`)}
|
|
908
|
-
|
|
909
|
-
above:
|
|
910
|
-
${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
911
|
-
`)}`;
|
|
912
|
-
logger.debug(`Getting ${limit} cards centered around elo: ${elo}:
|
|
913
|
-
|
|
914
|
-
` + str);
|
|
915
|
-
return ret;
|
|
916
|
-
}
|
|
917
|
-
async getCourseConfig() {
|
|
918
|
-
const ret = await getCredentialledCourseConfig(this.id);
|
|
919
|
-
if (ret) {
|
|
920
|
-
return ret;
|
|
921
|
-
} else {
|
|
922
|
-
throw new Error(`Course config not found for course ID: ${this.id}`);
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
async updateCourseConfig(cfg) {
|
|
926
|
-
logger.debug(`Updating: ${JSON.stringify(cfg)}`);
|
|
927
|
-
try {
|
|
928
|
-
return await updateCredentialledCourseConfig(this.id, cfg);
|
|
929
|
-
} catch (error) {
|
|
930
|
-
logger.error(`Error updating course config in course DB: ${error}`);
|
|
931
|
-
throw error;
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
async updateCardElo(cardId, elo) {
|
|
935
|
-
if (!elo) {
|
|
936
|
-
throw new Error(`Cannot update card elo with null or undefined value for card ID: ${cardId}`);
|
|
937
|
-
}
|
|
938
|
-
try {
|
|
939
|
-
const result = await this.updateQueue.update(cardId, (card) => {
|
|
940
|
-
logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);
|
|
941
|
-
card.elo = elo;
|
|
942
|
-
return card;
|
|
943
|
-
});
|
|
944
|
-
return { ok: true, id: cardId, rev: result._rev };
|
|
945
|
-
} catch (error) {
|
|
946
|
-
logger.error(`Failed to update card elo for card ID: ${cardId}`, error);
|
|
947
|
-
throw new Error(`Failed to update card elo for card ID: ${cardId}`);
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
async getAppliedTags(cardId) {
|
|
951
|
-
const ret = await getAppliedTags(this.id, cardId);
|
|
952
|
-
if (ret) {
|
|
953
|
-
return ret;
|
|
954
|
-
} else {
|
|
955
|
-
throw new Error(`Failed to find tags for card ${this.id}-${cardId}`);
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
async addTagToCard(cardId, tagId, updateELO) {
|
|
959
|
-
return await addTagToCard(this.id, cardId, tagId, (await this._getCurrentUser()).getUsername(), updateELO);
|
|
960
|
-
}
|
|
961
|
-
async removeTagFromCard(cardId, tagId) {
|
|
962
|
-
return await removeTagFromCard(this.id, cardId, tagId);
|
|
963
|
-
}
|
|
964
|
-
async createTag(name, author) {
|
|
965
|
-
return await createTag(this.id, name, author);
|
|
966
|
-
}
|
|
967
|
-
async getTag(tagId) {
|
|
968
|
-
return await getTag(this.id, tagId);
|
|
969
|
-
}
|
|
970
|
-
async updateTag(tag) {
|
|
971
|
-
if (tag.course !== this.id) {
|
|
972
|
-
throw new Error(`Tag ${JSON.stringify(tag)} does not belong to course ${this.id}`);
|
|
973
|
-
}
|
|
974
|
-
return await updateTag(tag);
|
|
975
|
-
}
|
|
976
|
-
async getCourseTagStubs() {
|
|
977
|
-
return getCourseTagStubs(this.id);
|
|
978
|
-
}
|
|
979
|
-
async addNote(codeCourse, shape, data, author, tags, uploads, elo = blankCourseElo2()) {
|
|
980
|
-
try {
|
|
981
|
-
const resp = await addNote55(this.id, codeCourse, shape, data, author, tags, uploads, elo);
|
|
982
|
-
if (resp.ok) {
|
|
983
|
-
if (resp.cardCreationFailed) {
|
|
984
|
-
logger.warn(
|
|
985
|
-
`[courseDB.addNote] Note added but card creation failed: ${resp.cardCreationError}`
|
|
986
|
-
);
|
|
987
|
-
return {
|
|
988
|
-
status: Status.error,
|
|
989
|
-
message: `Note was added but no cards were created: ${resp.cardCreationError}`,
|
|
990
|
-
id: resp.id
|
|
991
|
-
};
|
|
992
|
-
}
|
|
993
|
-
return {
|
|
994
|
-
status: Status.ok,
|
|
995
|
-
message: "",
|
|
996
|
-
id: resp.id
|
|
997
|
-
};
|
|
998
|
-
} else {
|
|
999
|
-
return {
|
|
1000
|
-
status: Status.error,
|
|
1001
|
-
message: "Unexpected error adding note"
|
|
1002
|
-
};
|
|
1003
|
-
}
|
|
1004
|
-
} catch (e) {
|
|
1005
|
-
const err = e;
|
|
1006
|
-
logger.error(
|
|
1007
|
-
`[addNote] error ${err.name}
|
|
1008
|
-
reason: ${err.reason}
|
|
1009
|
-
message: ${err.message}`
|
|
1010
|
-
);
|
|
1011
|
-
return {
|
|
1012
|
-
status: Status.error,
|
|
1013
|
-
message: `Error adding note to course. ${e.reason || err.message}`
|
|
1014
|
-
};
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
async getCourseDoc(id, options) {
|
|
1018
|
-
return await getCourseDoc(this.id, id, options);
|
|
1019
|
-
}
|
|
1020
|
-
async getCourseDocs(ids, options = {}) {
|
|
1021
|
-
return await getCourseDocs(this.id, ids, options);
|
|
1022
|
-
}
|
|
1023
|
-
////////////////////////////////////
|
|
1024
|
-
// NavigationStrategyManager implementation
|
|
1025
|
-
////////////////////////////////////
|
|
1026
|
-
getNavigationStrategy(id) {
|
|
1027
|
-
logger.debug(`[courseDB] Getting navigation strategy: ${id}`);
|
|
1028
|
-
const strategy = {
|
|
1029
|
-
id: "ELO",
|
|
1030
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
1031
|
-
name: "ELO",
|
|
1032
|
-
description: "ELO-based navigation strategy for ordering content by difficulty",
|
|
1033
|
-
implementingClass: "elo" /* ELO */,
|
|
1034
|
-
course: this.id,
|
|
1035
|
-
serializedData: ""
|
|
1036
|
-
// serde is a noop for ELO navigator.
|
|
1037
|
-
};
|
|
1038
|
-
return Promise.resolve(strategy);
|
|
1039
|
-
}
|
|
1040
|
-
getAllNavigationStrategies() {
|
|
1041
|
-
logger.debug("[courseDB] Returning hard-coded navigation strategies");
|
|
1042
|
-
const strategies = [
|
|
1043
|
-
{
|
|
1044
|
-
id: "ELO",
|
|
1045
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
1046
|
-
name: "ELO",
|
|
1047
|
-
description: "ELO-based navigation strategy for ordering content by difficulty",
|
|
1048
|
-
implementingClass: "elo" /* ELO */,
|
|
1049
|
-
course: this.id,
|
|
1050
|
-
serializedData: ""
|
|
1051
|
-
// serde is a noop for ELO navigator.
|
|
1052
|
-
}
|
|
1053
|
-
];
|
|
1054
|
-
return Promise.resolve(strategies);
|
|
1055
|
-
}
|
|
1056
|
-
addNavigationStrategy(data) {
|
|
1057
|
-
logger.debug(`[courseDB] Adding navigation strategy: ${data.id}`);
|
|
1058
|
-
logger.debug(JSON.stringify(data));
|
|
1059
|
-
return Promise.resolve();
|
|
1060
|
-
}
|
|
1061
|
-
updateNavigationStrategy(id, data) {
|
|
1062
|
-
logger.debug(`[courseDB] Updating navigation strategy: ${id}`);
|
|
1063
|
-
logger.debug(JSON.stringify(data));
|
|
1064
|
-
return Promise.resolve();
|
|
1065
|
-
}
|
|
1066
|
-
async surfaceNavigationStrategy() {
|
|
1067
|
-
logger.warn(`Returning hard-coded default ELO navigator`);
|
|
1068
|
-
const ret = {
|
|
1069
|
-
id: "ELO",
|
|
1070
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
1071
|
-
name: "ELO",
|
|
1072
|
-
description: "ELO-based navigation strategy",
|
|
1073
|
-
implementingClass: "elo" /* ELO */,
|
|
1074
|
-
course: this.id,
|
|
1075
|
-
serializedData: ""
|
|
1076
|
-
// serde is a noop for ELO navigator.
|
|
1077
|
-
};
|
|
1078
|
-
return Promise.resolve(ret);
|
|
1079
|
-
}
|
|
1080
|
-
////////////////////////////////////
|
|
1081
|
-
// END NavigationStrategyManager implementation
|
|
1082
|
-
////////////////////////////////////
|
|
1083
|
-
////////////////////////////////////
|
|
1084
|
-
// StudyContentSource implementation
|
|
1085
|
-
////////////////////////////////////
|
|
1086
|
-
async getNewCards(limit = 99) {
|
|
1087
|
-
const u = await this._getCurrentUser();
|
|
1088
|
-
try {
|
|
1089
|
-
const strategy = await this.surfaceNavigationStrategy();
|
|
1090
|
-
const navigator = await ContentNavigator.create(u, this, strategy);
|
|
1091
|
-
return navigator.getNewCards(limit);
|
|
1092
|
-
} catch (e) {
|
|
1093
|
-
logger.error(`[courseDB] Error surfacing a NavigationStrategy: ${e}`);
|
|
1094
|
-
throw e;
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
async getPendingReviews() {
|
|
1098
|
-
const u = await this._getCurrentUser();
|
|
1099
|
-
try {
|
|
1100
|
-
const strategy = await this.surfaceNavigationStrategy();
|
|
1101
|
-
const navigator = await ContentNavigator.create(u, this, strategy);
|
|
1102
|
-
return navigator.getPendingReviews();
|
|
1103
|
-
} catch (e) {
|
|
1104
|
-
logger.error(`[courseDB] Error surfacing a NavigationStrategy: ${e}`);
|
|
1105
|
-
throw e;
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
async getCardsCenteredAtELO(options = {
|
|
1109
|
-
limit: 99,
|
|
1110
|
-
elo: "user"
|
|
1111
|
-
}, filter) {
|
|
1112
|
-
let targetElo;
|
|
1113
|
-
if (options.elo === "user") {
|
|
1114
|
-
const u = await this._getCurrentUser();
|
|
1115
|
-
targetElo = -1;
|
|
1116
|
-
try {
|
|
1117
|
-
const courseDoc = (await u.getCourseRegistrationsDoc()).courses.find((c) => {
|
|
1118
|
-
return c.courseID === this.id;
|
|
1119
|
-
});
|
|
1120
|
-
targetElo = EloToNumber(courseDoc.elo);
|
|
1121
|
-
} catch {
|
|
1122
|
-
targetElo = 1e3;
|
|
1123
|
-
}
|
|
1124
|
-
} else if (options.elo === "random") {
|
|
1125
|
-
const bounds = await GET_CACHED(`elo-bounds-${this.id}`, () => this.getELOBounds());
|
|
1126
|
-
targetElo = Math.round(bounds.low + Math.random() * (bounds.high - bounds.low));
|
|
1127
|
-
} else {
|
|
1128
|
-
targetElo = options.elo;
|
|
1129
|
-
}
|
|
1130
|
-
let cards = [];
|
|
1131
|
-
let mult = 4;
|
|
1132
|
-
let previousCount = -1;
|
|
1133
|
-
let newCount = 0;
|
|
1134
|
-
while (cards.length < options.limit && newCount !== previousCount) {
|
|
1135
|
-
cards = await this.getCardsByELO(targetElo, mult * options.limit);
|
|
1136
|
-
previousCount = newCount;
|
|
1137
|
-
newCount = cards.length;
|
|
1138
|
-
logger.debug(`Found ${cards.length} elo neighbor cards...`);
|
|
1139
|
-
if (filter) {
|
|
1140
|
-
cards = cards.filter(filter);
|
|
1141
|
-
logger.debug(`Filtered to ${cards.length} cards...`);
|
|
1142
|
-
}
|
|
1143
|
-
mult *= 2;
|
|
1144
|
-
}
|
|
1145
|
-
const selectedCards = [];
|
|
1146
|
-
while (selectedCards.length < options.limit && cards.length > 0) {
|
|
1147
|
-
const index = randIntWeightedTowardZero(cards.length);
|
|
1148
|
-
const card = cards.splice(index, 1)[0];
|
|
1149
|
-
selectedCards.push(card);
|
|
1150
|
-
}
|
|
1151
|
-
return selectedCards.map((c) => {
|
|
1152
|
-
const split = c.split("-");
|
|
1153
|
-
return {
|
|
1154
|
-
courseID: this.id,
|
|
1155
|
-
cardID: split[1],
|
|
1156
|
-
qualifiedID: `${split[0]}-${split[1]}`,
|
|
1157
|
-
contentSourceType: "course",
|
|
1158
|
-
contentSourceID: this.id,
|
|
1159
|
-
status: "new"
|
|
1160
|
-
};
|
|
1161
|
-
});
|
|
1162
|
-
}
|
|
1163
|
-
};
|
|
1164
568
|
}
|
|
1165
569
|
});
|
|
1166
570
|
|
|
1167
571
|
// src/impl/couch/classroomDB.ts
|
|
1168
|
-
import
|
|
572
|
+
import moment3 from "moment";
|
|
1169
573
|
var init_classroomDB2 = __esm({
|
|
1170
574
|
"src/impl/couch/classroomDB.ts"() {
|
|
1171
575
|
"use strict";
|
|
@@ -1216,41 +620,9 @@ var init_CouchDBSyncStrategy = __esm({
|
|
|
1216
620
|
});
|
|
1217
621
|
|
|
1218
622
|
// src/impl/couch/index.ts
|
|
1219
|
-
import
|
|
623
|
+
import moment4 from "moment";
|
|
1220
624
|
import process2 from "process";
|
|
1221
|
-
|
|
1222
|
-
return new pouchdb_setup_default(
|
|
1223
|
-
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "coursedb-" + courseID,
|
|
1224
|
-
pouchDBincludeCredentialsConfig
|
|
1225
|
-
);
|
|
1226
|
-
}
|
|
1227
|
-
function getCourseDocs(courseID, docIDs, options = {}) {
|
|
1228
|
-
return getCourseDB2(courseID).allDocs({
|
|
1229
|
-
...options,
|
|
1230
|
-
keys: docIDs
|
|
1231
|
-
});
|
|
1232
|
-
}
|
|
1233
|
-
function getCourseDoc(courseID, docID, options = {}) {
|
|
1234
|
-
return getCourseDB2(courseID).get(docID, options);
|
|
1235
|
-
}
|
|
1236
|
-
function filterAllDocsByPrefix2(db, prefix, opts) {
|
|
1237
|
-
const options = {
|
|
1238
|
-
startkey: prefix,
|
|
1239
|
-
endkey: prefix + "\uFFF0",
|
|
1240
|
-
include_docs: true
|
|
1241
|
-
};
|
|
1242
|
-
if (opts) {
|
|
1243
|
-
Object.assign(options, opts);
|
|
1244
|
-
}
|
|
1245
|
-
return db.allDocs(options);
|
|
1246
|
-
}
|
|
1247
|
-
function getStartAndEndKeys2(key) {
|
|
1248
|
-
return {
|
|
1249
|
-
startkey: key,
|
|
1250
|
-
endkey: key + "\uFFF0"
|
|
1251
|
-
};
|
|
1252
|
-
}
|
|
1253
|
-
var isBrowser, GUEST_LOCAL_DB, localUserDB, pouchDBincludeCredentialsConfig, REVIEW_PREFIX2, REVIEW_TIME_FORMAT2;
|
|
625
|
+
var isBrowser, GUEST_LOCAL_DB, localUserDB, pouchDBincludeCredentialsConfig;
|
|
1254
626
|
var init_couch = __esm({
|
|
1255
627
|
"src/impl/couch/index.ts"() {
|
|
1256
628
|
"use strict";
|
|
@@ -1276,75 +648,6 @@ var init_couch = __esm({
|
|
|
1276
648
|
return pouchdb_setup_default.fetch(url, opts);
|
|
1277
649
|
}
|
|
1278
650
|
};
|
|
1279
|
-
REVIEW_PREFIX2 = "card_review_";
|
|
1280
|
-
REVIEW_TIME_FORMAT2 = "YYYY-MM-DD--kk:mm:ss-SSS";
|
|
1281
|
-
}
|
|
1282
|
-
});
|
|
1283
|
-
|
|
1284
|
-
// src/impl/couch/user-course-relDB.ts
|
|
1285
|
-
import moment4 from "moment";
|
|
1286
|
-
var UsrCrsData;
|
|
1287
|
-
var init_user_course_relDB = __esm({
|
|
1288
|
-
"src/impl/couch/user-course-relDB.ts"() {
|
|
1289
|
-
"use strict";
|
|
1290
|
-
init_couch();
|
|
1291
|
-
init_courseDB();
|
|
1292
|
-
init_logger();
|
|
1293
|
-
UsrCrsData = class {
|
|
1294
|
-
user;
|
|
1295
|
-
course;
|
|
1296
|
-
_courseId;
|
|
1297
|
-
constructor(user, courseId) {
|
|
1298
|
-
this.user = user;
|
|
1299
|
-
this.course = new CourseDB(courseId, async () => this.user);
|
|
1300
|
-
this._courseId = courseId;
|
|
1301
|
-
}
|
|
1302
|
-
async getReviewsForcast(daysCount) {
|
|
1303
|
-
const time = moment4.utc().add(daysCount, "days");
|
|
1304
|
-
return this.getReviewstoDate(time);
|
|
1305
|
-
}
|
|
1306
|
-
async getPendingReviews() {
|
|
1307
|
-
const now = moment4.utc();
|
|
1308
|
-
return this.getReviewstoDate(now);
|
|
1309
|
-
}
|
|
1310
|
-
async getScheduledReviewCount() {
|
|
1311
|
-
return (await this.getPendingReviews()).length;
|
|
1312
|
-
}
|
|
1313
|
-
async getCourseSettings() {
|
|
1314
|
-
const regDoc = await this.user.getCourseRegistrationsDoc();
|
|
1315
|
-
const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);
|
|
1316
|
-
if (crsDoc && crsDoc.settings) {
|
|
1317
|
-
return crsDoc.settings;
|
|
1318
|
-
} else {
|
|
1319
|
-
logger.warn(`no settings found during lookup on course ${this._courseId}`);
|
|
1320
|
-
return {};
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
updateCourseSettings(updates) {
|
|
1324
|
-
void this.user.updateCourseSettings(this._courseId, updates);
|
|
1325
|
-
}
|
|
1326
|
-
async getReviewstoDate(targetDate) {
|
|
1327
|
-
const keys = getStartAndEndKeys2(REVIEW_PREFIX2);
|
|
1328
|
-
const reviews = await this.user.remote().allDocs({
|
|
1329
|
-
startkey: keys.startkey,
|
|
1330
|
-
endkey: keys.endkey,
|
|
1331
|
-
include_docs: true
|
|
1332
|
-
});
|
|
1333
|
-
logger.debug(
|
|
1334
|
-
`Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`
|
|
1335
|
-
);
|
|
1336
|
-
return reviews.rows.filter((r) => {
|
|
1337
|
-
if (r.id.startsWith(REVIEW_PREFIX2)) {
|
|
1338
|
-
const date = moment4.utc(r.id.substr(REVIEW_PREFIX2.length), REVIEW_TIME_FORMAT2);
|
|
1339
|
-
if (targetDate.isAfter(date)) {
|
|
1340
|
-
if (this._courseId === void 0 || r.doc.courseId === this._courseId) {
|
|
1341
|
-
return true;
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
}).map((r) => r.doc);
|
|
1346
|
-
}
|
|
1347
|
-
};
|
|
1348
651
|
}
|
|
1349
652
|
});
|
|
1350
653
|
|
|
@@ -1443,10 +746,11 @@ async function dropUserFromClassroom(user, classID) {
|
|
|
1443
746
|
async function getUserClassrooms(user) {
|
|
1444
747
|
return getOrCreateClassroomRegistrationsDoc(user);
|
|
1445
748
|
}
|
|
1446
|
-
var log3,
|
|
749
|
+
var log3, BaseUser, userCoursesDoc, userClassroomsDoc;
|
|
1447
750
|
var init_BaseUserDB = __esm({
|
|
1448
751
|
"src/impl/common/BaseUserDB.ts"() {
|
|
1449
752
|
"use strict";
|
|
753
|
+
init_core();
|
|
1450
754
|
init_util();
|
|
1451
755
|
init_types_legacy();
|
|
1452
756
|
init_logger();
|
|
@@ -1457,7 +761,6 @@ var init_BaseUserDB = __esm({
|
|
|
1457
761
|
log3 = (s) => {
|
|
1458
762
|
logger.info(s);
|
|
1459
763
|
};
|
|
1460
|
-
cardHistoryPrefix2 = "cardH-";
|
|
1461
764
|
BaseUser = class _BaseUser {
|
|
1462
765
|
static _instance;
|
|
1463
766
|
static _initialized = false;
|
|
@@ -1478,11 +781,13 @@ var init_BaseUserDB = __esm({
|
|
|
1478
781
|
isLoggedIn() {
|
|
1479
782
|
return !this._username.startsWith(GuestUsername);
|
|
1480
783
|
}
|
|
1481
|
-
remoteDB;
|
|
1482
784
|
remote() {
|
|
1483
785
|
return this.remoteDB;
|
|
1484
786
|
}
|
|
1485
787
|
localDB;
|
|
788
|
+
remoteDB;
|
|
789
|
+
writeDB;
|
|
790
|
+
// Database to use for write operations (local-first approach)
|
|
1486
791
|
updateQueue;
|
|
1487
792
|
async createAccount(username, password) {
|
|
1488
793
|
if (!this.syncStrategy.canCreateAccount()) {
|
|
@@ -1546,8 +851,8 @@ Currently logged-in as ${this._username}.`
|
|
|
1546
851
|
const allDocs = await localDB.allDocs({ include_docs: false });
|
|
1547
852
|
const docsToDelete = allDocs.rows.filter((row) => {
|
|
1548
853
|
const id = row.id;
|
|
1549
|
-
return id.startsWith(
|
|
1550
|
-
id.startsWith(
|
|
854
|
+
return id.startsWith(DocTypePrefixes["CARDRECORD" /* CARDRECORD */]) || // Card interaction history
|
|
855
|
+
id.startsWith(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]) || // Scheduled reviews
|
|
1551
856
|
id === _BaseUser.DOC_IDS.COURSE_REGISTRATIONS || // Course registrations
|
|
1552
857
|
id === _BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS || // Classroom registrations
|
|
1553
858
|
id === _BaseUser.DOC_IDS.CONFIG;
|
|
@@ -1616,7 +921,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1616
921
|
*
|
|
1617
922
|
*/
|
|
1618
923
|
async getActiveCards() {
|
|
1619
|
-
const keys = getStartAndEndKeys(
|
|
924
|
+
const keys = getStartAndEndKeys(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]);
|
|
1620
925
|
const reviews = await this.remoteDB.allDocs({
|
|
1621
926
|
startkey: keys.startkey,
|
|
1622
927
|
endkey: keys.endkey,
|
|
@@ -1688,7 +993,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1688
993
|
}
|
|
1689
994
|
}
|
|
1690
995
|
async getReviewstoDate(targetDate, course_id) {
|
|
1691
|
-
const keys = getStartAndEndKeys(
|
|
996
|
+
const keys = getStartAndEndKeys(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]);
|
|
1692
997
|
const reviews = await this.remoteDB.allDocs({
|
|
1693
998
|
startkey: keys.startkey,
|
|
1694
999
|
endkey: keys.endkey,
|
|
@@ -1698,8 +1003,11 @@ Currently logged-in as ${this._username}.`
|
|
|
1698
1003
|
`Fetching ${this._username}'s scheduled reviews${course_id ? ` for course ${course_id}` : ""}.`
|
|
1699
1004
|
);
|
|
1700
1005
|
return reviews.rows.filter((r) => {
|
|
1701
|
-
if (r.id.startsWith(
|
|
1702
|
-
const date = moment5.utc(
|
|
1006
|
+
if (r.id.startsWith(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */])) {
|
|
1007
|
+
const date = moment5.utc(
|
|
1008
|
+
r.id.substr(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */].length),
|
|
1009
|
+
REVIEW_TIME_FORMAT
|
|
1010
|
+
);
|
|
1703
1011
|
if (targetDate.isAfter(date)) {
|
|
1704
1012
|
if (course_id === void 0 || r.doc.courseId === course_id) {
|
|
1705
1013
|
return true;
|
|
@@ -1814,7 +1122,8 @@ Currently logged-in as ${this._username}.`
|
|
|
1814
1122
|
const defaultConfig = {
|
|
1815
1123
|
_id: _BaseUser.DOC_IDS.CONFIG,
|
|
1816
1124
|
darkMode: false,
|
|
1817
|
-
likesConfetti: false
|
|
1125
|
+
likesConfetti: false,
|
|
1126
|
+
sessionTimeLimit: 5
|
|
1818
1127
|
};
|
|
1819
1128
|
try {
|
|
1820
1129
|
const cfg = await this.localDB.get(_BaseUser.DOC_IDS.CONFIG);
|
|
@@ -1887,7 +1196,8 @@ Currently logged-in as ${this._username}.`
|
|
|
1887
1196
|
setDBandQ() {
|
|
1888
1197
|
this.localDB = getLocalUserDB(this._username);
|
|
1889
1198
|
this.remoteDB = this.syncStrategy.setupRemoteDB(this._username);
|
|
1890
|
-
this.
|
|
1199
|
+
this.writeDB = this.syncStrategy.getWriteDB ? this.syncStrategy.getWriteDB(this._username) : this.localDB;
|
|
1200
|
+
this.updateQueue = new UpdateQueue(this.localDB, this.writeDB);
|
|
1891
1201
|
}
|
|
1892
1202
|
async init() {
|
|
1893
1203
|
_BaseUser._initialized = false;
|
|
@@ -2005,8 +1315,8 @@ Currently logged-in as ${this._username}.`
|
|
|
2005
1315
|
streak: 0,
|
|
2006
1316
|
bestInterval: 0
|
|
2007
1317
|
};
|
|
2008
|
-
|
|
2009
|
-
return initCardHistory;
|
|
1318
|
+
const putResult = await this.writeDB.put(initCardHistory);
|
|
1319
|
+
return { ...initCardHistory, _rev: putResult.rev };
|
|
2010
1320
|
} else {
|
|
2011
1321
|
throw new Error(`putCardRecord failed because of:
|
|
2012
1322
|
name:${reason.name}
|
|
@@ -2041,7 +1351,7 @@ Currently logged-in as ${this._username}.`
|
|
|
2041
1351
|
const deletePromises = duplicateDocIds.map(async (docId) => {
|
|
2042
1352
|
try {
|
|
2043
1353
|
const doc = await this.remoteDB.get(docId);
|
|
2044
|
-
await this.
|
|
1354
|
+
await this.writeDB.remove(doc);
|
|
2045
1355
|
log3(`Successfully removed duplicate review: ${docId}`);
|
|
2046
1356
|
} catch (error) {
|
|
2047
1357
|
log3(`Failed to remove duplicate review ${docId}: ${error}`);
|
|
@@ -2063,7 +1373,7 @@ Currently logged-in as ${this._username}.`
|
|
|
2063
1373
|
* @param course_id optional specification of individual course
|
|
2064
1374
|
*/
|
|
2065
1375
|
async getSeenCards(course_id) {
|
|
2066
|
-
let prefix =
|
|
1376
|
+
let prefix = DocTypePrefixes["CARDRECORD" /* CARDRECORD */];
|
|
2067
1377
|
if (course_id) {
|
|
2068
1378
|
prefix += course_id;
|
|
2069
1379
|
}
|
|
@@ -2072,8 +1382,8 @@ Currently logged-in as ${this._username}.`
|
|
|
2072
1382
|
});
|
|
2073
1383
|
const ret = [];
|
|
2074
1384
|
docs.rows.forEach((row) => {
|
|
2075
|
-
if (row.id.startsWith(
|
|
2076
|
-
ret.push(row.id.substr(
|
|
1385
|
+
if (row.id.startsWith(DocTypePrefixes["CARDRECORD" /* CARDRECORD */])) {
|
|
1386
|
+
ret.push(row.id.substr(DocTypePrefixes["CARDRECORD" /* CARDRECORD */].length));
|
|
2077
1387
|
}
|
|
2078
1388
|
});
|
|
2079
1389
|
return ret;
|
|
@@ -2085,7 +1395,7 @@ Currently logged-in as ${this._username}.`
|
|
|
2085
1395
|
async getHistory() {
|
|
2086
1396
|
const cards = await filterAllDocsByPrefix(
|
|
2087
1397
|
this.remoteDB,
|
|
2088
|
-
|
|
1398
|
+
DocTypePrefixes["CARDRECORD" /* CARDRECORD */],
|
|
2089
1399
|
{
|
|
2090
1400
|
include_docs: true,
|
|
2091
1401
|
attachments: false
|
|
@@ -2126,7 +1436,7 @@ Currently logged-in as ${this._username}.`
|
|
|
2126
1436
|
} catch (e) {
|
|
2127
1437
|
const err = e;
|
|
2128
1438
|
if (err.status === 404) {
|
|
2129
|
-
await this.
|
|
1439
|
+
await this.writeDB.put({
|
|
2130
1440
|
_id: _BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS,
|
|
2131
1441
|
registrations: []
|
|
2132
1442
|
});
|
|
@@ -2174,10 +1484,10 @@ Currently logged-in as ${this._username}.`
|
|
|
2174
1484
|
}
|
|
2175
1485
|
}
|
|
2176
1486
|
async scheduleCardReview(review) {
|
|
2177
|
-
return scheduleCardReviewLocal(this.
|
|
1487
|
+
return scheduleCardReviewLocal(this.writeDB, review);
|
|
2178
1488
|
}
|
|
2179
1489
|
async removeScheduledCardReview(reviewId) {
|
|
2180
|
-
return removeScheduledCardReviewLocal(this.
|
|
1490
|
+
return removeScheduledCardReviewLocal(this.writeDB, reviewId);
|
|
2181
1491
|
}
|
|
2182
1492
|
async registerForClassroom(_classId, _registerAs) {
|
|
2183
1493
|
return registerUserForClassroom(this._username, _classId, _registerAs);
|
|
@@ -2407,18 +1717,21 @@ var init_StaticDataUnpacker = __esm({
|
|
|
2407
1717
|
async getTagsIndex() {
|
|
2408
1718
|
return await this.loadIndex("tags");
|
|
2409
1719
|
}
|
|
1720
|
+
getDocTypeFromId(id) {
|
|
1721
|
+
for (const docTypeKey in DocTypePrefixes) {
|
|
1722
|
+
const prefix = DocTypePrefixes[docTypeKey];
|
|
1723
|
+
if (id.startsWith(`${prefix}-`)) {
|
|
1724
|
+
return docTypeKey;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
return void 0;
|
|
1728
|
+
}
|
|
2410
1729
|
/**
|
|
2411
1730
|
* Find which chunk contains a specific document ID
|
|
2412
1731
|
*/
|
|
2413
1732
|
async findChunkForDocument(docId) {
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
if (docId.startsWith(`${docType}-`)) {
|
|
2417
|
-
expectedDocType = docType;
|
|
2418
|
-
break;
|
|
2419
|
-
}
|
|
2420
|
-
}
|
|
2421
|
-
if (expectedDocType !== void 0) {
|
|
1733
|
+
const expectedDocType = this.getDocTypeFromId(docId);
|
|
1734
|
+
if (expectedDocType) {
|
|
2422
1735
|
const typeChunks = this.manifest.chunks.filter((c) => c.docType === expectedDocType);
|
|
2423
1736
|
for (const chunk of typeChunks) {
|
|
2424
1737
|
if (docId >= chunk.startKey && docId <= chunk.endKey) {
|
|
@@ -2428,21 +1741,8 @@ var init_StaticDataUnpacker = __esm({
|
|
|
2428
1741
|
}
|
|
2429
1742
|
}
|
|
2430
1743
|
}
|
|
2431
|
-
return void 0;
|
|
2432
1744
|
} else {
|
|
2433
|
-
const
|
|
2434
|
-
(c) => c.docType === "DISPLAYABLE_DATA"
|
|
2435
|
-
);
|
|
2436
|
-
for (const chunk of displayableChunks) {
|
|
2437
|
-
if (docId >= chunk.startKey && docId <= chunk.endKey) {
|
|
2438
|
-
const exists = await this.verifyDocumentInChunk(docId, chunk);
|
|
2439
|
-
if (exists) {
|
|
2440
|
-
return chunk;
|
|
2441
|
-
}
|
|
2442
|
-
}
|
|
2443
|
-
}
|
|
2444
|
-
const cardChunks = this.manifest.chunks.filter((c) => c.docType === "CARD");
|
|
2445
|
-
for (const chunk of cardChunks) {
|
|
1745
|
+
for (const chunk of this.manifest.chunks) {
|
|
2446
1746
|
if (docId >= chunk.startKey && docId <= chunk.endKey) {
|
|
2447
1747
|
const exists = await this.verifyDocumentInChunk(docId, chunk);
|
|
2448
1748
|
if (exists) {
|
|
@@ -2463,6 +1763,7 @@ var init_StaticDataUnpacker = __esm({
|
|
|
2463
1763
|
}
|
|
2464
1764
|
return void 0;
|
|
2465
1765
|
}
|
|
1766
|
+
return void 0;
|
|
2466
1767
|
}
|
|
2467
1768
|
/**
|
|
2468
1769
|
* Verify that a document actually exists in a specific chunk by loading and checking it
|
|
@@ -2706,6 +2007,7 @@ var init_courseDB3 = __esm({
|
|
|
2706
2007
|
"use strict";
|
|
2707
2008
|
init_types_legacy();
|
|
2708
2009
|
init_navigators();
|
|
2010
|
+
init_logger();
|
|
2709
2011
|
StaticCourseDB = class {
|
|
2710
2012
|
constructor(courseId, unpacker, userDB, manifest) {
|
|
2711
2013
|
this.courseId = courseId;
|
|
@@ -2727,10 +2029,11 @@ var init_courseDB3 = __esm({
|
|
|
2727
2029
|
throw new Error("Cannot update course config in static mode");
|
|
2728
2030
|
}
|
|
2729
2031
|
async getCourseInfo() {
|
|
2032
|
+
const cardCount = this.manifest.chunks.filter((chunk) => chunk.docType === "CARD" /* CARD */).reduce((total, chunk) => total + chunk.documentCount, 0);
|
|
2730
2033
|
return {
|
|
2731
|
-
cardCount
|
|
2732
|
-
// Would come from manifest
|
|
2034
|
+
cardCount,
|
|
2733
2035
|
registeredUsers: 0
|
|
2036
|
+
// Always 0 in static mode
|
|
2734
2037
|
};
|
|
2735
2038
|
}
|
|
2736
2039
|
async getCourseDoc(id, _options) {
|
|
@@ -2819,12 +2122,56 @@ var init_courseDB3 = __esm({
|
|
|
2819
2122
|
courseID: this.courseId
|
|
2820
2123
|
}));
|
|
2821
2124
|
}
|
|
2822
|
-
async getAppliedTags(
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
rows
|
|
2827
|
-
|
|
2125
|
+
async getAppliedTags(cardId) {
|
|
2126
|
+
try {
|
|
2127
|
+
const tagsIndex = await this.unpacker.getTagsIndex();
|
|
2128
|
+
const cardTags = tagsIndex.byCard[cardId] || [];
|
|
2129
|
+
const rows = await Promise.all(
|
|
2130
|
+
cardTags.map(async (tagName) => {
|
|
2131
|
+
const tagId = `${"TAG" /* TAG */}-${tagName}`;
|
|
2132
|
+
try {
|
|
2133
|
+
const tagDoc = await this.unpacker.getDocument(tagId);
|
|
2134
|
+
return {
|
|
2135
|
+
id: tagId,
|
|
2136
|
+
key: cardId,
|
|
2137
|
+
value: {
|
|
2138
|
+
name: tagDoc.name,
|
|
2139
|
+
snippet: tagDoc.snippet,
|
|
2140
|
+
count: tagDoc.taggedCards?.length || 0
|
|
2141
|
+
}
|
|
2142
|
+
};
|
|
2143
|
+
} catch (error) {
|
|
2144
|
+
if (error && error.status === 404) {
|
|
2145
|
+
logger.warn(`Tag document not found for ${tagName}, creating stub`);
|
|
2146
|
+
} else {
|
|
2147
|
+
logger.error(`Error getting tag document for ${tagName}:`, error);
|
|
2148
|
+
throw error;
|
|
2149
|
+
}
|
|
2150
|
+
return {
|
|
2151
|
+
id: tagId,
|
|
2152
|
+
key: cardId,
|
|
2153
|
+
value: {
|
|
2154
|
+
name: tagName,
|
|
2155
|
+
snippet: `Tag: ${tagName}`,
|
|
2156
|
+
count: tagsIndex.byTag[tagName]?.length || 0
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
})
|
|
2161
|
+
);
|
|
2162
|
+
return {
|
|
2163
|
+
total_rows: rows.length,
|
|
2164
|
+
offset: 0,
|
|
2165
|
+
rows
|
|
2166
|
+
};
|
|
2167
|
+
} catch (error) {
|
|
2168
|
+
logger.error(`Error getting applied tags for card ${cardId}:`, error);
|
|
2169
|
+
return {
|
|
2170
|
+
total_rows: 0,
|
|
2171
|
+
offset: 0,
|
|
2172
|
+
rows: []
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2828
2175
|
}
|
|
2829
2176
|
async addTagToCard(_cardId, _tagId) {
|
|
2830
2177
|
throw new Error("Cannot modify tags in static mode");
|
|
@@ -2842,11 +2189,69 @@ var init_courseDB3 = __esm({
|
|
|
2842
2189
|
throw new Error("Cannot update tags in static mode");
|
|
2843
2190
|
}
|
|
2844
2191
|
async getCourseTagStubs() {
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2192
|
+
try {
|
|
2193
|
+
const tagsIndex = await this.unpacker.getTagsIndex();
|
|
2194
|
+
if (!tagsIndex || !tagsIndex.byTag) {
|
|
2195
|
+
logger.warn("Tags index not found or empty");
|
|
2196
|
+
return {
|
|
2197
|
+
total_rows: 0,
|
|
2198
|
+
offset: 0,
|
|
2199
|
+
rows: []
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
const tagNames = Object.keys(tagsIndex.byTag);
|
|
2203
|
+
const rows = await Promise.all(
|
|
2204
|
+
tagNames.map(async (tagName) => {
|
|
2205
|
+
const cardIds = tagsIndex.byTag[tagName] || [];
|
|
2206
|
+
const tagId = `${"TAG" /* TAG */}-${tagName}`;
|
|
2207
|
+
try {
|
|
2208
|
+
const tagDoc = await this.unpacker.getDocument(tagId);
|
|
2209
|
+
return {
|
|
2210
|
+
id: tagId,
|
|
2211
|
+
key: tagId,
|
|
2212
|
+
value: { rev: "1-static" },
|
|
2213
|
+
doc: tagDoc
|
|
2214
|
+
};
|
|
2215
|
+
} catch (error) {
|
|
2216
|
+
if (error && error.status === 404) {
|
|
2217
|
+
logger.warn(`Tag document not found for ${tagName}, creating stub`);
|
|
2218
|
+
const stubDoc = {
|
|
2219
|
+
_id: tagId,
|
|
2220
|
+
_rev: "1-static",
|
|
2221
|
+
course: this.courseId,
|
|
2222
|
+
docType: "TAG" /* TAG */,
|
|
2223
|
+
name: tagName,
|
|
2224
|
+
snippet: `Tag: ${tagName}`,
|
|
2225
|
+
wiki: "",
|
|
2226
|
+
taggedCards: cardIds,
|
|
2227
|
+
author: "system"
|
|
2228
|
+
};
|
|
2229
|
+
return {
|
|
2230
|
+
id: tagId,
|
|
2231
|
+
key: tagId,
|
|
2232
|
+
value: { rev: "1-static" },
|
|
2233
|
+
doc: stubDoc
|
|
2234
|
+
};
|
|
2235
|
+
} else {
|
|
2236
|
+
logger.error(`Error getting tag document for ${tagName}:`, error);
|
|
2237
|
+
throw error;
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
})
|
|
2241
|
+
);
|
|
2242
|
+
return {
|
|
2243
|
+
total_rows: rows.length,
|
|
2244
|
+
offset: 0,
|
|
2245
|
+
rows
|
|
2246
|
+
};
|
|
2247
|
+
} catch (error) {
|
|
2248
|
+
logger.error("Failed to get course tag stubs:", error);
|
|
2249
|
+
return {
|
|
2250
|
+
total_rows: 0,
|
|
2251
|
+
offset: 0,
|
|
2252
|
+
rows: []
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2850
2255
|
}
|
|
2851
2256
|
async addNote(_codeCourse, _shape, _data, _author, _tags, _uploads, _elo) {
|
|
2852
2257
|
return {
|
|
@@ -2952,6 +2357,9 @@ var init_NoOpSyncStrategy = __esm({
|
|
|
2952
2357
|
setupRemoteDB(username) {
|
|
2953
2358
|
return getLocalUserDB(username);
|
|
2954
2359
|
}
|
|
2360
|
+
getWriteDB(username) {
|
|
2361
|
+
return getLocalUserDB(username);
|
|
2362
|
+
}
|
|
2955
2363
|
startSync(_localDB, _remoteDB) {
|
|
2956
2364
|
}
|
|
2957
2365
|
stopSync() {
|