@vue-skuilder/db 0.1.11-9 → 0.1.12
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/core/index.d.mts +7 -6
- package/dist/core/index.d.ts +7 -6
- package/dist/core/index.js +358 -87
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +358 -87
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-DqtNroSh.d.ts → dataLayerProvider-BiP3kWix.d.mts} +8 -1
- package/dist/{dataLayerProvider-BInqI_RF.d.mts → dataLayerProvider-DSdeyRT3.d.ts} +8 -1
- package/dist/impl/couch/index.d.mts +19 -7
- package/dist/impl/couch/index.d.ts +19 -7
- package/dist/impl/couch/index.js +375 -100
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +374 -99
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.mts +23 -8
- package/dist/impl/static/index.d.ts +23 -8
- package/dist/impl/static/index.js +289 -85
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +289 -85
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/{index-CUNnL38E.d.mts → index-Bmll7Xse.d.mts} +1 -1
- package/dist/{index-CLL31bEy.d.ts → index-CD8BZz2k.d.ts} +1 -1
- package/dist/index.d.mts +123 -20
- package/dist/index.d.ts +123 -20
- package/dist/index.js +1133 -343
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1137 -343
- package/dist/index.mjs.map +1 -1
- package/dist/pouch/index.d.mts +1 -0
- package/dist/pouch/index.d.ts +1 -0
- package/dist/pouch/index.js +49 -0
- package/dist/pouch/index.js.map +1 -0
- package/dist/pouch/index.mjs +16 -0
- package/dist/pouch/index.mjs.map +1 -0
- package/dist/{types-BefDGkKa.d.ts → types-CewsN87z.d.ts} +1 -1
- package/dist/{types-DC-ckZug.d.mts → types-Dbp5DaRR.d.mts} +1 -1
- package/dist/{types-legacy-Birv-Jx6.d.mts → types-legacy-6ettoclI.d.mts} +17 -2
- package/dist/{types-legacy-Birv-Jx6.d.ts → types-legacy-6ettoclI.d.ts} +17 -2
- package/dist/{userDB-DusL7OXe.d.ts → userDB-C4yyAnpp.d.mts} +89 -56
- package/dist/{userDB-C33Hzjgn.d.mts → userDB-CD6s6ZCp.d.ts} +89 -56
- package/dist/util/packer/index.d.mts +3 -3
- package/dist/util/packer/index.d.ts +3 -3
- package/package.json +3 -3
- package/src/core/interfaces/contentSource.ts +3 -2
- package/src/core/interfaces/courseDB.ts +26 -3
- package/src/core/interfaces/dataLayerProvider.ts +9 -1
- package/src/core/interfaces/userDB.ts +80 -64
- package/src/core/navigators/elo.ts +10 -7
- package/src/core/navigators/hardcodedOrder.ts +64 -0
- package/src/core/navigators/index.ts +2 -1
- package/src/core/types/contentNavigationStrategy.ts +2 -1
- package/src/core/types/types-legacy.ts +7 -2
- package/src/impl/common/BaseUserDB.ts +60 -14
- package/src/impl/couch/CouchDBSyncStrategy.ts +2 -2
- package/src/impl/couch/PouchDataLayerProvider.ts +21 -0
- package/src/impl/couch/adminDB.ts +2 -2
- package/src/impl/couch/auth.ts +13 -4
- package/src/impl/couch/classroomDB.ts +10 -12
- package/src/impl/couch/courseAPI.ts +2 -2
- package/src/impl/couch/courseDB.ts +204 -38
- package/src/impl/couch/courseLookupDB.ts +4 -3
- package/src/impl/couch/index.ts +36 -4
- package/src/impl/couch/pouchdb-setup.ts +3 -3
- package/src/impl/couch/updateQueue.ts +59 -36
- package/src/impl/static/StaticDataLayerProvider.ts +68 -17
- package/src/impl/static/courseDB.ts +64 -20
- package/src/impl/static/coursesDB.ts +10 -6
- package/src/pouch/index.ts +2 -0
- package/src/study/ItemQueue.ts +58 -0
- package/src/study/SessionController.ts +182 -111
- package/src/study/SpacedRepetition.ts +1 -1
- package/src/study/services/CardHydrationService.ts +153 -0
- package/src/study/services/EloService.ts +85 -0
- package/src/study/services/ResponseProcessor.ts +224 -0
- package/src/study/services/SrsService.ts +44 -0
- package/tsup.config.ts +1 -0
package/dist/core/index.js
CHANGED
|
@@ -187,9 +187,9 @@ var init_pouchdb_setup = __esm({
|
|
|
187
187
|
import_pouchdb.default.plugin(import_pouchdb_find.default);
|
|
188
188
|
import_pouchdb.default.plugin(import_pouchdb_authentication.default);
|
|
189
189
|
import_pouchdb.default.defaults({
|
|
190
|
-
ajax: {
|
|
191
|
-
|
|
192
|
-
}
|
|
190
|
+
// ajax: {
|
|
191
|
+
// timeout: 60000,
|
|
192
|
+
// },
|
|
193
193
|
});
|
|
194
194
|
pouchdb_setup_default = import_pouchdb.default;
|
|
195
195
|
}
|
|
@@ -343,42 +343,58 @@ var init_updateQueue = __esm({
|
|
|
343
343
|
async applyUpdates(id) {
|
|
344
344
|
logger.debug(`Applying updates on doc: ${id}`);
|
|
345
345
|
if (this.inprogressUpdates[id]) {
|
|
346
|
-
|
|
346
|
+
while (this.inprogressUpdates[id]) {
|
|
347
|
+
await new Promise((resolve) => setTimeout(resolve, Math.random() * 50));
|
|
348
|
+
}
|
|
347
349
|
return this.applyUpdates(id);
|
|
348
350
|
} else {
|
|
349
351
|
if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {
|
|
350
352
|
this.inprogressUpdates[id] = true;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
353
|
+
const MAX_RETRIES = 5;
|
|
354
|
+
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
355
|
+
try {
|
|
356
|
+
const doc = await this.readDB.get(id);
|
|
357
|
+
logger.debug(`Retrieved doc: ${id}`);
|
|
358
|
+
let updatedDoc = { ...doc };
|
|
359
|
+
const updatesToApply = [...this.pendingUpdates[id]];
|
|
360
|
+
for (const update of updatesToApply) {
|
|
361
|
+
if (typeof update === "function") {
|
|
362
|
+
updatedDoc = { ...updatedDoc, ...update(updatedDoc) };
|
|
363
|
+
} else {
|
|
364
|
+
updatedDoc = {
|
|
365
|
+
...updatedDoc,
|
|
366
|
+
...update
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
await this.writeDB.put(updatedDoc);
|
|
371
|
+
logger.debug(`Put doc: ${id}`);
|
|
372
|
+
this.pendingUpdates[id].splice(0, updatesToApply.length);
|
|
373
|
+
if (this.pendingUpdates[id].length === 0) {
|
|
374
|
+
this.inprogressUpdates[id] = false;
|
|
375
|
+
delete this.inprogressUpdates[id];
|
|
358
376
|
} else {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
377
|
+
return this.applyUpdates(id);
|
|
378
|
+
}
|
|
379
|
+
return updatedDoc;
|
|
380
|
+
} catch (e) {
|
|
381
|
+
if (e.name === "conflict" && i < MAX_RETRIES - 1) {
|
|
382
|
+
logger.warn(`Conflict on update for doc ${id}, retry #${i + 1}`);
|
|
383
|
+
await new Promise((res) => setTimeout(res, 50 * Math.random()));
|
|
384
|
+
} else if (e.name === "not_found" && i === 0) {
|
|
385
|
+
logger.warn(`Update failed for ${id} - does not exist. Throwing to caller.`);
|
|
386
|
+
throw e;
|
|
387
|
+
} else {
|
|
388
|
+
delete this.inprogressUpdates[id];
|
|
389
|
+
if (this.pendingUpdates[id]) {
|
|
390
|
+
delete this.pendingUpdates[id];
|
|
391
|
+
}
|
|
392
|
+
logger.error(`Error on attemped update (retry ${i}): ${JSON.stringify(e)}`);
|
|
393
|
+
throw e;
|
|
363
394
|
}
|
|
364
395
|
}
|
|
365
|
-
await this.writeDB.put(doc);
|
|
366
|
-
logger.debug(`Put doc: ${id}`);
|
|
367
|
-
if (this.pendingUpdates[id].length === 0) {
|
|
368
|
-
this.inprogressUpdates[id] = false;
|
|
369
|
-
delete this.inprogressUpdates[id];
|
|
370
|
-
} else {
|
|
371
|
-
return this.applyUpdates(id);
|
|
372
|
-
}
|
|
373
|
-
return doc;
|
|
374
|
-
} catch (e) {
|
|
375
|
-
delete this.inprogressUpdates[id];
|
|
376
|
-
if (this.pendingUpdates[id]) {
|
|
377
|
-
delete this.pendingUpdates[id];
|
|
378
|
-
}
|
|
379
|
-
logger.error(`Error on attemped update: ${JSON.stringify(e)}`);
|
|
380
|
-
throw e;
|
|
381
396
|
}
|
|
397
|
+
throw new Error(`UpdateQueue failed for doc ${id} after ${MAX_RETRIES} retries.`);
|
|
382
398
|
} else {
|
|
383
399
|
throw new Error(`Empty Updates Queue Triggered`);
|
|
384
400
|
}
|
|
@@ -629,7 +645,7 @@ function getCourseDB(courseID) {
|
|
|
629
645
|
const dbName = `coursedb-${courseID}`;
|
|
630
646
|
return new pouchdb_setup_default(
|
|
631
647
|
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
632
|
-
|
|
648
|
+
createPouchDBConfig()
|
|
633
649
|
);
|
|
634
650
|
}
|
|
635
651
|
var import_common, import_common2, import_common3, import_uuid, AlreadyTaggedErr;
|
|
@@ -713,13 +729,16 @@ var init_elo = __esm({
|
|
|
713
729
|
}
|
|
714
730
|
async getNewCards(limit = 99) {
|
|
715
731
|
const activeCards = await this.user.getActiveCards();
|
|
716
|
-
return (await this.course.getCardsCenteredAtELO(
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
732
|
+
return (await this.course.getCardsCenteredAtELO(
|
|
733
|
+
{ limit, elo: "user" },
|
|
734
|
+
(c) => {
|
|
735
|
+
if (activeCards.some((ac) => c.cardID === ac.cardID)) {
|
|
736
|
+
return false;
|
|
737
|
+
} else {
|
|
738
|
+
return true;
|
|
739
|
+
}
|
|
721
740
|
}
|
|
722
|
-
|
|
741
|
+
)).map((c) => {
|
|
723
742
|
return {
|
|
724
743
|
...c,
|
|
725
744
|
status: "new"
|
|
@@ -730,12 +749,74 @@ var init_elo = __esm({
|
|
|
730
749
|
}
|
|
731
750
|
});
|
|
732
751
|
|
|
752
|
+
// src/core/navigators/hardcodedOrder.ts
|
|
753
|
+
var hardcodedOrder_exports = {};
|
|
754
|
+
__export(hardcodedOrder_exports, {
|
|
755
|
+
default: () => HardcodedOrderNavigator
|
|
756
|
+
});
|
|
757
|
+
var HardcodedOrderNavigator;
|
|
758
|
+
var init_hardcodedOrder = __esm({
|
|
759
|
+
"src/core/navigators/hardcodedOrder.ts"() {
|
|
760
|
+
"use strict";
|
|
761
|
+
init_navigators();
|
|
762
|
+
init_logger();
|
|
763
|
+
HardcodedOrderNavigator = class extends ContentNavigator {
|
|
764
|
+
orderedCardIds = [];
|
|
765
|
+
user;
|
|
766
|
+
course;
|
|
767
|
+
constructor(user, course, strategyData) {
|
|
768
|
+
super();
|
|
769
|
+
this.user = user;
|
|
770
|
+
this.course = course;
|
|
771
|
+
if (strategyData.serializedData) {
|
|
772
|
+
try {
|
|
773
|
+
this.orderedCardIds = JSON.parse(strategyData.serializedData);
|
|
774
|
+
} catch (e) {
|
|
775
|
+
logger.error("Failed to parse serializedData for HardcodedOrderNavigator", e);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
async getPendingReviews() {
|
|
780
|
+
const reviews = await this.user.getPendingReviews(this.course.getCourseID());
|
|
781
|
+
return reviews.map((r) => {
|
|
782
|
+
return {
|
|
783
|
+
...r,
|
|
784
|
+
contentSourceType: "course",
|
|
785
|
+
contentSourceID: this.course.getCourseID(),
|
|
786
|
+
cardID: r.cardId,
|
|
787
|
+
courseID: r.courseId,
|
|
788
|
+
reviewID: r._id,
|
|
789
|
+
status: "review"
|
|
790
|
+
};
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
async getNewCards(limit = 99) {
|
|
794
|
+
const activeCardIds = (await this.user.getActiveCards()).map((c) => c.cardID);
|
|
795
|
+
const newCardIds = this.orderedCardIds.filter(
|
|
796
|
+
(cardId) => !activeCardIds.includes(cardId)
|
|
797
|
+
);
|
|
798
|
+
const cardsToReturn = newCardIds.slice(0, limit);
|
|
799
|
+
return cardsToReturn.map((cardId) => {
|
|
800
|
+
return {
|
|
801
|
+
cardID: cardId,
|
|
802
|
+
courseID: this.course.getCourseID(),
|
|
803
|
+
contentSourceType: "course",
|
|
804
|
+
contentSourceID: this.course.getCourseID(),
|
|
805
|
+
status: "new"
|
|
806
|
+
};
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
|
|
733
813
|
// import("./**/*") in src/core/navigators/index.ts
|
|
734
814
|
var globImport;
|
|
735
815
|
var init_ = __esm({
|
|
736
816
|
'import("./**/*") in src/core/navigators/index.ts'() {
|
|
737
817
|
globImport = __glob({
|
|
738
818
|
"./elo.ts": () => Promise.resolve().then(() => (init_elo(), elo_exports)),
|
|
819
|
+
"./hardcodedOrder.ts": () => Promise.resolve().then(() => (init_hardcodedOrder(), hardcodedOrder_exports)),
|
|
739
820
|
"./index.ts": () => Promise.resolve().then(() => (init_navigators(), navigators_exports))
|
|
740
821
|
});
|
|
741
822
|
}
|
|
@@ -755,6 +836,7 @@ var init_navigators = __esm({
|
|
|
755
836
|
init_();
|
|
756
837
|
Navigators = /* @__PURE__ */ ((Navigators2) => {
|
|
757
838
|
Navigators2["ELO"] = "elo";
|
|
839
|
+
Navigators2["HARDCODED"] = "hardcodedOrder";
|
|
758
840
|
return Navigators2;
|
|
759
841
|
})(Navigators || {});
|
|
760
842
|
ContentNavigator = class {
|
|
@@ -767,7 +849,7 @@ var init_navigators = __esm({
|
|
|
767
849
|
static async create(user, course, strategyData) {
|
|
768
850
|
const implementingClass = strategyData.implementingClass;
|
|
769
851
|
let NavigatorImpl;
|
|
770
|
-
const variations = ["", ".js", "
|
|
852
|
+
const variations = [".ts", ".js", ""];
|
|
771
853
|
for (const ext of variations) {
|
|
772
854
|
try {
|
|
773
855
|
const module2 = await globImport(`./${implementingClass}${ext}`);
|
|
@@ -979,6 +1061,23 @@ var init_courseDB = __esm({
|
|
|
979
1061
|
if (!doc.docType || !(doc.docType === "CARD" /* CARD */)) {
|
|
980
1062
|
throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);
|
|
981
1063
|
}
|
|
1064
|
+
try {
|
|
1065
|
+
const appliedTags = await this.getAppliedTags(id);
|
|
1066
|
+
const results = await Promise.allSettled(
|
|
1067
|
+
appliedTags.rows.map(async (tagRow) => {
|
|
1068
|
+
const tagId = tagRow.id;
|
|
1069
|
+
await this.removeTagFromCard(id, tagId);
|
|
1070
|
+
})
|
|
1071
|
+
);
|
|
1072
|
+
results.forEach((result, index) => {
|
|
1073
|
+
if (result.status === "rejected") {
|
|
1074
|
+
const tagId = appliedTags.rows[index].id;
|
|
1075
|
+
logger.error(`Failed to remove card ${id} from tag ${tagId}: ${result.reason}`);
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
} catch (error) {
|
|
1079
|
+
logger.error(`Error removing card ${id} from tags: ${error}`);
|
|
1080
|
+
}
|
|
982
1081
|
return this.db.remove(doc);
|
|
983
1082
|
}
|
|
984
1083
|
async getCardDisplayableDataIDs(id) {
|
|
@@ -1026,7 +1125,13 @@ var init_courseDB = __esm({
|
|
|
1026
1125
|
} else {
|
|
1027
1126
|
return s;
|
|
1028
1127
|
}
|
|
1029
|
-
}).map((c) =>
|
|
1128
|
+
}).map((c) => {
|
|
1129
|
+
return {
|
|
1130
|
+
courseID: this.id,
|
|
1131
|
+
cardID: c.id,
|
|
1132
|
+
elo: c.key
|
|
1133
|
+
};
|
|
1134
|
+
});
|
|
1030
1135
|
const str = `below:
|
|
1031
1136
|
${below.rows.map((r) => ` ${r.id}-${r.key}
|
|
1032
1137
|
`)}
|
|
@@ -1081,7 +1186,13 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
1081
1186
|
}
|
|
1082
1187
|
}
|
|
1083
1188
|
async addTagToCard(cardId, tagId, updateELO) {
|
|
1084
|
-
return await addTagToCard(
|
|
1189
|
+
return await addTagToCard(
|
|
1190
|
+
this.id,
|
|
1191
|
+
cardId,
|
|
1192
|
+
tagId,
|
|
1193
|
+
(await this._getCurrentUser()).getUsername(),
|
|
1194
|
+
updateELO
|
|
1195
|
+
);
|
|
1085
1196
|
}
|
|
1086
1197
|
async removeTagFromCard(cardId, tagId) {
|
|
1087
1198
|
return await removeTagFromCard(this.id, cardId, tagId);
|
|
@@ -1150,23 +1261,9 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
1150
1261
|
////////////////////////////////////
|
|
1151
1262
|
getNavigationStrategy(id) {
|
|
1152
1263
|
logger.debug(`[courseDB] Getting navigation strategy: ${id}`);
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
name: "ELO",
|
|
1157
|
-
description: "ELO-based navigation strategy for ordering content by difficulty",
|
|
1158
|
-
implementingClass: "elo" /* ELO */,
|
|
1159
|
-
course: this.id,
|
|
1160
|
-
serializedData: ""
|
|
1161
|
-
// serde is a noop for ELO navigator.
|
|
1162
|
-
};
|
|
1163
|
-
return Promise.resolve(strategy);
|
|
1164
|
-
}
|
|
1165
|
-
getAllNavigationStrategies() {
|
|
1166
|
-
logger.debug("[courseDB] Returning hard-coded navigation strategies");
|
|
1167
|
-
const strategies = [
|
|
1168
|
-
{
|
|
1169
|
-
id: "ELO",
|
|
1264
|
+
if (id == "") {
|
|
1265
|
+
const strategy = {
|
|
1266
|
+
_id: "NAVIGATION_STRATEGY-ELO",
|
|
1170
1267
|
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
1171
1268
|
name: "ELO",
|
|
1172
1269
|
description: "ELO-based navigation strategy for ordering content by difficulty",
|
|
@@ -1174,14 +1271,25 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
1174
1271
|
course: this.id,
|
|
1175
1272
|
serializedData: ""
|
|
1176
1273
|
// serde is a noop for ELO navigator.
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1274
|
+
};
|
|
1275
|
+
return Promise.resolve(strategy);
|
|
1276
|
+
} else {
|
|
1277
|
+
return this.db.get(id);
|
|
1278
|
+
}
|
|
1180
1279
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1280
|
+
async getAllNavigationStrategies() {
|
|
1281
|
+
const prefix = DocTypePrefixes["NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */];
|
|
1282
|
+
const result = await this.db.allDocs({
|
|
1283
|
+
startkey: prefix,
|
|
1284
|
+
endkey: `${prefix}\uFFF0`,
|
|
1285
|
+
include_docs: true
|
|
1286
|
+
});
|
|
1287
|
+
return result.rows.map((row) => row.doc);
|
|
1288
|
+
}
|
|
1289
|
+
async addNavigationStrategy(data) {
|
|
1290
|
+
logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);
|
|
1291
|
+
return this.db.put(data).then(() => {
|
|
1292
|
+
});
|
|
1185
1293
|
}
|
|
1186
1294
|
updateNavigationStrategy(id, data) {
|
|
1187
1295
|
logger.debug(`[courseDB] Updating navigation strategy: ${id}`);
|
|
@@ -1189,9 +1297,32 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
1189
1297
|
return Promise.resolve();
|
|
1190
1298
|
}
|
|
1191
1299
|
async surfaceNavigationStrategy() {
|
|
1300
|
+
try {
|
|
1301
|
+
const config = await this.getCourseConfig();
|
|
1302
|
+
if (config.defaultNavigationStrategyId) {
|
|
1303
|
+
try {
|
|
1304
|
+
const strategy = await this.getNavigationStrategy(config.defaultNavigationStrategyId);
|
|
1305
|
+
if (strategy) {
|
|
1306
|
+
logger.debug(`Surfacing strategy ${strategy.name} from course config`);
|
|
1307
|
+
return strategy;
|
|
1308
|
+
}
|
|
1309
|
+
} catch (e) {
|
|
1310
|
+
logger.warn(
|
|
1311
|
+
// @ts-expect-error tmp: defaultNavigationStrategyId property does not yet exist
|
|
1312
|
+
`Failed to load strategy '${config.defaultNavigationStrategyId}' specified in course config. Falling back to ELO.`,
|
|
1313
|
+
e
|
|
1314
|
+
);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
} catch (e) {
|
|
1318
|
+
logger.warn(
|
|
1319
|
+
"Could not retrieve course config to determine navigation strategy. Falling back to ELO.",
|
|
1320
|
+
e
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1192
1323
|
logger.warn(`Returning hard-coded default ELO navigator`);
|
|
1193
1324
|
const ret = {
|
|
1194
|
-
|
|
1325
|
+
_id: "NAVIGATION_STRATEGY-ELO",
|
|
1195
1326
|
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
1196
1327
|
name: "ELO",
|
|
1197
1328
|
description: "ELO-based navigation strategy",
|
|
@@ -1274,17 +1405,93 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
1274
1405
|
selectedCards.push(card);
|
|
1275
1406
|
}
|
|
1276
1407
|
return selectedCards.map((c) => {
|
|
1277
|
-
const split = c.split("-");
|
|
1278
1408
|
return {
|
|
1279
1409
|
courseID: this.id,
|
|
1280
|
-
cardID:
|
|
1281
|
-
qualifiedID: `${split[0]}-${split[1]}`,
|
|
1410
|
+
cardID: c.cardID,
|
|
1282
1411
|
contentSourceType: "course",
|
|
1283
1412
|
contentSourceID: this.id,
|
|
1413
|
+
elo: c.elo,
|
|
1284
1414
|
status: "new"
|
|
1285
1415
|
};
|
|
1286
1416
|
});
|
|
1287
1417
|
}
|
|
1418
|
+
// Admin search methods
|
|
1419
|
+
async searchCards(query) {
|
|
1420
|
+
logger.log(`[CourseDB ${this.id}] Searching for: "${query}"`);
|
|
1421
|
+
let displayableData;
|
|
1422
|
+
try {
|
|
1423
|
+
displayableData = await this.db.find({
|
|
1424
|
+
selector: {
|
|
1425
|
+
docType: "DISPLAYABLE_DATA",
|
|
1426
|
+
"data.0.data": { $regex: `.*${query}.*` }
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1429
|
+
logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`);
|
|
1430
|
+
} catch (regexError) {
|
|
1431
|
+
logger.log(
|
|
1432
|
+
`[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,
|
|
1433
|
+
regexError
|
|
1434
|
+
);
|
|
1435
|
+
const allDisplayable = await this.db.find({
|
|
1436
|
+
selector: {
|
|
1437
|
+
docType: "DISPLAYABLE_DATA"
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
1440
|
+
logger.log(
|
|
1441
|
+
`[CourseDB ${this.id}] Retrieved ${allDisplayable.docs.length} documents for manual filtering`
|
|
1442
|
+
);
|
|
1443
|
+
displayableData = {
|
|
1444
|
+
docs: allDisplayable.docs.filter((doc) => {
|
|
1445
|
+
const docString = JSON.stringify(doc).toLowerCase();
|
|
1446
|
+
const match = docString.includes(query.toLowerCase());
|
|
1447
|
+
if (match) {
|
|
1448
|
+
logger.log(`[CourseDB ${this.id}] Manual match found in document: ${doc._id}`);
|
|
1449
|
+
}
|
|
1450
|
+
return match;
|
|
1451
|
+
})
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
logger.log(
|
|
1455
|
+
`[CourseDB ${this.id}] Found ${displayableData.docs.length} displayable data documents`
|
|
1456
|
+
);
|
|
1457
|
+
if (displayableData.docs.length === 0) {
|
|
1458
|
+
const allDisplayableData = await this.db.find({
|
|
1459
|
+
selector: {
|
|
1460
|
+
docType: "DISPLAYABLE_DATA"
|
|
1461
|
+
},
|
|
1462
|
+
limit: 5
|
|
1463
|
+
// Just sample a few
|
|
1464
|
+
});
|
|
1465
|
+
logger.log(
|
|
1466
|
+
`[CourseDB ${this.id}] Sample displayable data:`,
|
|
1467
|
+
allDisplayableData.docs.map((d) => ({
|
|
1468
|
+
id: d._id,
|
|
1469
|
+
docType: d.docType,
|
|
1470
|
+
dataStructure: d.data ? Object.keys(d.data) : "no data field",
|
|
1471
|
+
dataContent: d.data,
|
|
1472
|
+
fullDoc: d
|
|
1473
|
+
}))
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
const allResults = [];
|
|
1477
|
+
for (const dd of displayableData.docs) {
|
|
1478
|
+
const cards = await this.db.find({
|
|
1479
|
+
selector: {
|
|
1480
|
+
docType: "CARD",
|
|
1481
|
+
id_displayable_data: { $in: [dd._id] }
|
|
1482
|
+
}
|
|
1483
|
+
});
|
|
1484
|
+
logger.log(
|
|
1485
|
+
`[CourseDB ${this.id}] Displayable data ${dd._id} linked to ${cards.docs.length} cards`
|
|
1486
|
+
);
|
|
1487
|
+
allResults.push(...cards.docs);
|
|
1488
|
+
}
|
|
1489
|
+
logger.log(`[CourseDB ${this.id}] Total cards found: ${allResults.length}`);
|
|
1490
|
+
return allResults;
|
|
1491
|
+
}
|
|
1492
|
+
async find(request) {
|
|
1493
|
+
return this.db.find(request);
|
|
1494
|
+
}
|
|
1288
1495
|
};
|
|
1289
1496
|
}
|
|
1290
1497
|
});
|
|
@@ -1349,7 +1556,7 @@ var init_classroomDB2 = __esm({
|
|
|
1349
1556
|
const dbName = `classdb-student-${this._id}`;
|
|
1350
1557
|
this._db = new pouchdb_setup_default(
|
|
1351
1558
|
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
1352
|
-
|
|
1559
|
+
createPouchDBConfig()
|
|
1353
1560
|
);
|
|
1354
1561
|
try {
|
|
1355
1562
|
const cfg = await this._db.get(CLASSROOM_CONFIG);
|
|
@@ -1418,9 +1625,11 @@ var init_classroomDB2 = __esm({
|
|
|
1418
1625
|
ret.push(await getCourseDB2(content.courseID).get(content.cardID));
|
|
1419
1626
|
}
|
|
1420
1627
|
}
|
|
1421
|
-
logger.info(
|
|
1628
|
+
logger.info(
|
|
1629
|
+
`New Cards from classroom ${this._cfg.name}: ${ret.map((c) => `${c.courseID}-${c.cardID}`)}`
|
|
1630
|
+
);
|
|
1422
1631
|
return ret.filter((c) => {
|
|
1423
|
-
if (activeCards.some((ac) => c.
|
|
1632
|
+
if (activeCards.some((ac) => c.cardID === ac.cardID)) {
|
|
1424
1633
|
return false;
|
|
1425
1634
|
} else {
|
|
1426
1635
|
return true;
|
|
@@ -1473,10 +1682,29 @@ var init_CouchDBSyncStrategy = __esm({
|
|
|
1473
1682
|
});
|
|
1474
1683
|
|
|
1475
1684
|
// src/impl/couch/index.ts
|
|
1685
|
+
function createPouchDBConfig() {
|
|
1686
|
+
const hasExplicitCredentials = ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD;
|
|
1687
|
+
const isNodeEnvironment = typeof window === "undefined";
|
|
1688
|
+
if (hasExplicitCredentials && isNodeEnvironment) {
|
|
1689
|
+
return {
|
|
1690
|
+
fetch(url, opts = {}) {
|
|
1691
|
+
const basicAuth = btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`);
|
|
1692
|
+
const headers = new Headers(opts.headers || {});
|
|
1693
|
+
headers.set("Authorization", `Basic ${basicAuth}`);
|
|
1694
|
+
const newOpts = {
|
|
1695
|
+
...opts,
|
|
1696
|
+
headers
|
|
1697
|
+
};
|
|
1698
|
+
return pouchdb_setup_default.fetch(url, newOpts);
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
return pouchDBincludeCredentialsConfig;
|
|
1703
|
+
}
|
|
1476
1704
|
function getCourseDB2(courseID) {
|
|
1477
1705
|
return new pouchdb_setup_default(
|
|
1478
1706
|
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "coursedb-" + courseID,
|
|
1479
|
-
|
|
1707
|
+
createPouchDBConfig()
|
|
1480
1708
|
);
|
|
1481
1709
|
}
|
|
1482
1710
|
function getCourseDocs(courseID, docIDs, options = {}) {
|
|
@@ -1768,6 +1996,9 @@ Currently logged-in as ${this._username}.`
|
|
|
1768
1996
|
await this.init();
|
|
1769
1997
|
return ret;
|
|
1770
1998
|
}
|
|
1999
|
+
async get(id) {
|
|
2000
|
+
return this.localDB.get(id);
|
|
2001
|
+
}
|
|
1771
2002
|
update(id, update) {
|
|
1772
2003
|
return this.updateQueue.update(id, update);
|
|
1773
2004
|
}
|
|
@@ -1814,7 +2045,12 @@ Currently logged-in as ${this._username}.`
|
|
|
1814
2045
|
endkey: keys.endkey,
|
|
1815
2046
|
include_docs: true
|
|
1816
2047
|
});
|
|
1817
|
-
return reviews.rows.map((r) =>
|
|
2048
|
+
return reviews.rows.map((r) => {
|
|
2049
|
+
return {
|
|
2050
|
+
courseID: r.doc.courseId,
|
|
2051
|
+
cardID: r.doc.cardId
|
|
2052
|
+
};
|
|
2053
|
+
});
|
|
1818
2054
|
}
|
|
1819
2055
|
async getActivityRecords() {
|
|
1820
2056
|
try {
|
|
@@ -2094,8 +2330,18 @@ Currently logged-in as ${this._username}.`
|
|
|
2094
2330
|
}
|
|
2095
2331
|
this.setDBandQ();
|
|
2096
2332
|
this.syncStrategy.startSync(this.localDB, this.remoteDB);
|
|
2097
|
-
|
|
2098
|
-
|
|
2333
|
+
this.applyDesignDocs().catch((error) => {
|
|
2334
|
+
log3(`Error in applyDesignDocs background task: ${error}`);
|
|
2335
|
+
if (error && typeof error === "object") {
|
|
2336
|
+
log3(`Full error details in applyDesignDocs: ${JSON.stringify(error)}`);
|
|
2337
|
+
}
|
|
2338
|
+
});
|
|
2339
|
+
this.deduplicateReviews().catch((error) => {
|
|
2340
|
+
log3(`Error in deduplicateReviews background task: ${error}`);
|
|
2341
|
+
if (error && typeof error === "object") {
|
|
2342
|
+
log3(`Full error details in background task: ${JSON.stringify(error)}`);
|
|
2343
|
+
}
|
|
2344
|
+
});
|
|
2099
2345
|
_BaseUser._initialized = true;
|
|
2100
2346
|
}
|
|
2101
2347
|
static designDocs = [
|
|
@@ -2113,10 +2359,15 @@ Currently logged-in as ${this._username}.`
|
|
|
2113
2359
|
}
|
|
2114
2360
|
];
|
|
2115
2361
|
async applyDesignDocs() {
|
|
2362
|
+
log3(`Starting applyDesignDocs for user: ${this._username}`);
|
|
2363
|
+
log3(`Remote DB name: ${this.remoteDB.name || "unknown"}`);
|
|
2116
2364
|
if (this._username === "admin") {
|
|
2365
|
+
log3("Skipping design docs for admin user");
|
|
2117
2366
|
return;
|
|
2118
2367
|
}
|
|
2368
|
+
log3(`Applying ${_BaseUser.designDocs.length} design docs`);
|
|
2119
2369
|
for (const doc of _BaseUser.designDocs) {
|
|
2370
|
+
log3(`Applying design doc: ${doc._id}`);
|
|
2120
2371
|
try {
|
|
2121
2372
|
try {
|
|
2122
2373
|
const existingDoc = await this.remoteDB.get(doc._id);
|
|
@@ -2193,17 +2444,21 @@ Currently logged-in as ${this._username}.`
|
|
|
2193
2444
|
} catch (e) {
|
|
2194
2445
|
const reason = e;
|
|
2195
2446
|
if (reason.status === 404) {
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2447
|
+
try {
|
|
2448
|
+
const initCardHistory = {
|
|
2449
|
+
_id: cardHistoryID,
|
|
2450
|
+
cardID: record.cardID,
|
|
2451
|
+
courseID: record.courseID,
|
|
2452
|
+
records: [record],
|
|
2453
|
+
lapses: 0,
|
|
2454
|
+
streak: 0,
|
|
2455
|
+
bestInterval: 0
|
|
2456
|
+
};
|
|
2457
|
+
const putResult = await this.writeDB.put(initCardHistory);
|
|
2458
|
+
return { ...initCardHistory, _rev: putResult.rev };
|
|
2459
|
+
} catch (creationError) {
|
|
2460
|
+
throw new Error(`Failed to create CardHistory for ${cardHistoryID}. Reason: ${creationError}`);
|
|
2461
|
+
}
|
|
2207
2462
|
} else {
|
|
2208
2463
|
throw new Error(`putCardRecord failed because of:
|
|
2209
2464
|
name:${reason.name}
|
|
@@ -2215,8 +2470,13 @@ Currently logged-in as ${this._username}.`
|
|
|
2215
2470
|
async deduplicateReviews() {
|
|
2216
2471
|
try {
|
|
2217
2472
|
log3("Starting deduplication of scheduled reviews...");
|
|
2473
|
+
log3(`Remote DB name: ${this.remoteDB.name || "unknown"}`);
|
|
2474
|
+
log3(`Write DB name: ${this.writeDB.name || "unknown"}`);
|
|
2218
2475
|
const reviewsMap = {};
|
|
2219
2476
|
const duplicateDocIds = [];
|
|
2477
|
+
log3(
|
|
2478
|
+
`Attempting to query remoteDB for reviewCards/reviewCards. Database: ${this.remoteDB.name || "unknown"}`
|
|
2479
|
+
);
|
|
2220
2480
|
const scheduledReviews = await this.remoteDB.query("reviewCards/reviewCards");
|
|
2221
2481
|
log3(`Found ${scheduledReviews.rows.length} scheduled reviews to process`);
|
|
2222
2482
|
scheduledReviews.rows.forEach((r) => {
|
|
@@ -2251,6 +2511,17 @@ Currently logged-in as ${this._username}.`
|
|
|
2251
2511
|
}
|
|
2252
2512
|
} catch (error) {
|
|
2253
2513
|
log3(`Error during review deduplication: ${error}`);
|
|
2514
|
+
if (error && typeof error === "object" && "status" in error && error.status === 404) {
|
|
2515
|
+
log3(
|
|
2516
|
+
`Database not found (404) during review deduplication. Database: ${this.remoteDB.name || "unknown"}`
|
|
2517
|
+
);
|
|
2518
|
+
log3(
|
|
2519
|
+
`This might indicate the user database doesn't exist or the reviewCards view isn't available`
|
|
2520
|
+
);
|
|
2521
|
+
}
|
|
2522
|
+
if (error && typeof error === "object") {
|
|
2523
|
+
log3(`Full error details: ${JSON.stringify(error)}`);
|
|
2524
|
+
}
|
|
2254
2525
|
}
|
|
2255
2526
|
}
|
|
2256
2527
|
/**
|