@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.mjs
CHANGED
|
@@ -165,9 +165,9 @@ var init_pouchdb_setup = __esm({
|
|
|
165
165
|
PouchDB.plugin(PouchDBFind);
|
|
166
166
|
PouchDB.plugin(PouchDBAuth);
|
|
167
167
|
PouchDB.defaults({
|
|
168
|
-
ajax: {
|
|
169
|
-
|
|
170
|
-
}
|
|
168
|
+
// ajax: {
|
|
169
|
+
// timeout: 60000,
|
|
170
|
+
// },
|
|
171
171
|
});
|
|
172
172
|
pouchdb_setup_default = PouchDB;
|
|
173
173
|
}
|
|
@@ -320,42 +320,58 @@ var init_updateQueue = __esm({
|
|
|
320
320
|
async applyUpdates(id) {
|
|
321
321
|
logger.debug(`Applying updates on doc: ${id}`);
|
|
322
322
|
if (this.inprogressUpdates[id]) {
|
|
323
|
-
|
|
323
|
+
while (this.inprogressUpdates[id]) {
|
|
324
|
+
await new Promise((resolve) => setTimeout(resolve, Math.random() * 50));
|
|
325
|
+
}
|
|
324
326
|
return this.applyUpdates(id);
|
|
325
327
|
} else {
|
|
326
328
|
if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {
|
|
327
329
|
this.inprogressUpdates[id] = true;
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
330
|
+
const MAX_RETRIES = 5;
|
|
331
|
+
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
332
|
+
try {
|
|
333
|
+
const doc = await this.readDB.get(id);
|
|
334
|
+
logger.debug(`Retrieved doc: ${id}`);
|
|
335
|
+
let updatedDoc = { ...doc };
|
|
336
|
+
const updatesToApply = [...this.pendingUpdates[id]];
|
|
337
|
+
for (const update of updatesToApply) {
|
|
338
|
+
if (typeof update === "function") {
|
|
339
|
+
updatedDoc = { ...updatedDoc, ...update(updatedDoc) };
|
|
340
|
+
} else {
|
|
341
|
+
updatedDoc = {
|
|
342
|
+
...updatedDoc,
|
|
343
|
+
...update
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
await this.writeDB.put(updatedDoc);
|
|
348
|
+
logger.debug(`Put doc: ${id}`);
|
|
349
|
+
this.pendingUpdates[id].splice(0, updatesToApply.length);
|
|
350
|
+
if (this.pendingUpdates[id].length === 0) {
|
|
351
|
+
this.inprogressUpdates[id] = false;
|
|
352
|
+
delete this.inprogressUpdates[id];
|
|
335
353
|
} else {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
354
|
+
return this.applyUpdates(id);
|
|
355
|
+
}
|
|
356
|
+
return updatedDoc;
|
|
357
|
+
} catch (e) {
|
|
358
|
+
if (e.name === "conflict" && i < MAX_RETRIES - 1) {
|
|
359
|
+
logger.warn(`Conflict on update for doc ${id}, retry #${i + 1}`);
|
|
360
|
+
await new Promise((res) => setTimeout(res, 50 * Math.random()));
|
|
361
|
+
} else if (e.name === "not_found" && i === 0) {
|
|
362
|
+
logger.warn(`Update failed for ${id} - does not exist. Throwing to caller.`);
|
|
363
|
+
throw e;
|
|
364
|
+
} else {
|
|
365
|
+
delete this.inprogressUpdates[id];
|
|
366
|
+
if (this.pendingUpdates[id]) {
|
|
367
|
+
delete this.pendingUpdates[id];
|
|
368
|
+
}
|
|
369
|
+
logger.error(`Error on attemped update (retry ${i}): ${JSON.stringify(e)}`);
|
|
370
|
+
throw e;
|
|
340
371
|
}
|
|
341
372
|
}
|
|
342
|
-
await this.writeDB.put(doc);
|
|
343
|
-
logger.debug(`Put doc: ${id}`);
|
|
344
|
-
if (this.pendingUpdates[id].length === 0) {
|
|
345
|
-
this.inprogressUpdates[id] = false;
|
|
346
|
-
delete this.inprogressUpdates[id];
|
|
347
|
-
} else {
|
|
348
|
-
return this.applyUpdates(id);
|
|
349
|
-
}
|
|
350
|
-
return doc;
|
|
351
|
-
} catch (e) {
|
|
352
|
-
delete this.inprogressUpdates[id];
|
|
353
|
-
if (this.pendingUpdates[id]) {
|
|
354
|
-
delete this.pendingUpdates[id];
|
|
355
|
-
}
|
|
356
|
-
logger.error(`Error on attemped update: ${JSON.stringify(e)}`);
|
|
357
|
-
throw e;
|
|
358
373
|
}
|
|
374
|
+
throw new Error(`UpdateQueue failed for doc ${id} after ${MAX_RETRIES} retries.`);
|
|
359
375
|
} else {
|
|
360
376
|
throw new Error(`Empty Updates Queue Triggered`);
|
|
361
377
|
}
|
|
@@ -610,7 +626,7 @@ function getCourseDB(courseID) {
|
|
|
610
626
|
const dbName = `coursedb-${courseID}`;
|
|
611
627
|
return new pouchdb_setup_default(
|
|
612
628
|
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
613
|
-
|
|
629
|
+
createPouchDBConfig()
|
|
614
630
|
);
|
|
615
631
|
}
|
|
616
632
|
var AlreadyTaggedErr;
|
|
@@ -690,13 +706,16 @@ var init_elo = __esm({
|
|
|
690
706
|
}
|
|
691
707
|
async getNewCards(limit = 99) {
|
|
692
708
|
const activeCards = await this.user.getActiveCards();
|
|
693
|
-
return (await this.course.getCardsCenteredAtELO(
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
709
|
+
return (await this.course.getCardsCenteredAtELO(
|
|
710
|
+
{ limit, elo: "user" },
|
|
711
|
+
(c) => {
|
|
712
|
+
if (activeCards.some((ac) => c.cardID === ac.cardID)) {
|
|
713
|
+
return false;
|
|
714
|
+
} else {
|
|
715
|
+
return true;
|
|
716
|
+
}
|
|
698
717
|
}
|
|
699
|
-
|
|
718
|
+
)).map((c) => {
|
|
700
719
|
return {
|
|
701
720
|
...c,
|
|
702
721
|
status: "new"
|
|
@@ -707,12 +726,74 @@ var init_elo = __esm({
|
|
|
707
726
|
}
|
|
708
727
|
});
|
|
709
728
|
|
|
729
|
+
// src/core/navigators/hardcodedOrder.ts
|
|
730
|
+
var hardcodedOrder_exports = {};
|
|
731
|
+
__export(hardcodedOrder_exports, {
|
|
732
|
+
default: () => HardcodedOrderNavigator
|
|
733
|
+
});
|
|
734
|
+
var HardcodedOrderNavigator;
|
|
735
|
+
var init_hardcodedOrder = __esm({
|
|
736
|
+
"src/core/navigators/hardcodedOrder.ts"() {
|
|
737
|
+
"use strict";
|
|
738
|
+
init_navigators();
|
|
739
|
+
init_logger();
|
|
740
|
+
HardcodedOrderNavigator = class extends ContentNavigator {
|
|
741
|
+
orderedCardIds = [];
|
|
742
|
+
user;
|
|
743
|
+
course;
|
|
744
|
+
constructor(user, course, strategyData) {
|
|
745
|
+
super();
|
|
746
|
+
this.user = user;
|
|
747
|
+
this.course = course;
|
|
748
|
+
if (strategyData.serializedData) {
|
|
749
|
+
try {
|
|
750
|
+
this.orderedCardIds = JSON.parse(strategyData.serializedData);
|
|
751
|
+
} catch (e) {
|
|
752
|
+
logger.error("Failed to parse serializedData for HardcodedOrderNavigator", e);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
async getPendingReviews() {
|
|
757
|
+
const reviews = await this.user.getPendingReviews(this.course.getCourseID());
|
|
758
|
+
return reviews.map((r) => {
|
|
759
|
+
return {
|
|
760
|
+
...r,
|
|
761
|
+
contentSourceType: "course",
|
|
762
|
+
contentSourceID: this.course.getCourseID(),
|
|
763
|
+
cardID: r.cardId,
|
|
764
|
+
courseID: r.courseId,
|
|
765
|
+
reviewID: r._id,
|
|
766
|
+
status: "review"
|
|
767
|
+
};
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
async getNewCards(limit = 99) {
|
|
771
|
+
const activeCardIds = (await this.user.getActiveCards()).map((c) => c.cardID);
|
|
772
|
+
const newCardIds = this.orderedCardIds.filter(
|
|
773
|
+
(cardId) => !activeCardIds.includes(cardId)
|
|
774
|
+
);
|
|
775
|
+
const cardsToReturn = newCardIds.slice(0, limit);
|
|
776
|
+
return cardsToReturn.map((cardId) => {
|
|
777
|
+
return {
|
|
778
|
+
cardID: cardId,
|
|
779
|
+
courseID: this.course.getCourseID(),
|
|
780
|
+
contentSourceType: "course",
|
|
781
|
+
contentSourceID: this.course.getCourseID(),
|
|
782
|
+
status: "new"
|
|
783
|
+
};
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
|
|
710
790
|
// import("./**/*") in src/core/navigators/index.ts
|
|
711
791
|
var globImport;
|
|
712
792
|
var init_ = __esm({
|
|
713
793
|
'import("./**/*") in src/core/navigators/index.ts'() {
|
|
714
794
|
globImport = __glob({
|
|
715
795
|
"./elo.ts": () => Promise.resolve().then(() => (init_elo(), elo_exports)),
|
|
796
|
+
"./hardcodedOrder.ts": () => Promise.resolve().then(() => (init_hardcodedOrder(), hardcodedOrder_exports)),
|
|
716
797
|
"./index.ts": () => Promise.resolve().then(() => (init_navigators(), navigators_exports))
|
|
717
798
|
});
|
|
718
799
|
}
|
|
@@ -732,6 +813,7 @@ var init_navigators = __esm({
|
|
|
732
813
|
init_();
|
|
733
814
|
Navigators = /* @__PURE__ */ ((Navigators2) => {
|
|
734
815
|
Navigators2["ELO"] = "elo";
|
|
816
|
+
Navigators2["HARDCODED"] = "hardcodedOrder";
|
|
735
817
|
return Navigators2;
|
|
736
818
|
})(Navigators || {});
|
|
737
819
|
ContentNavigator = class {
|
|
@@ -744,7 +826,7 @@ var init_navigators = __esm({
|
|
|
744
826
|
static async create(user, course, strategyData) {
|
|
745
827
|
const implementingClass = strategyData.implementingClass;
|
|
746
828
|
let NavigatorImpl;
|
|
747
|
-
const variations = ["", ".js", "
|
|
829
|
+
const variations = [".ts", ".js", ""];
|
|
748
830
|
for (const ext of variations) {
|
|
749
831
|
try {
|
|
750
832
|
const module = await globImport(`./${implementingClass}${ext}`);
|
|
@@ -961,6 +1043,23 @@ var init_courseDB = __esm({
|
|
|
961
1043
|
if (!doc.docType || !(doc.docType === "CARD" /* CARD */)) {
|
|
962
1044
|
throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);
|
|
963
1045
|
}
|
|
1046
|
+
try {
|
|
1047
|
+
const appliedTags = await this.getAppliedTags(id);
|
|
1048
|
+
const results = await Promise.allSettled(
|
|
1049
|
+
appliedTags.rows.map(async (tagRow) => {
|
|
1050
|
+
const tagId = tagRow.id;
|
|
1051
|
+
await this.removeTagFromCard(id, tagId);
|
|
1052
|
+
})
|
|
1053
|
+
);
|
|
1054
|
+
results.forEach((result, index) => {
|
|
1055
|
+
if (result.status === "rejected") {
|
|
1056
|
+
const tagId = appliedTags.rows[index].id;
|
|
1057
|
+
logger.error(`Failed to remove card ${id} from tag ${tagId}: ${result.reason}`);
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
} catch (error) {
|
|
1061
|
+
logger.error(`Error removing card ${id} from tags: ${error}`);
|
|
1062
|
+
}
|
|
964
1063
|
return this.db.remove(doc);
|
|
965
1064
|
}
|
|
966
1065
|
async getCardDisplayableDataIDs(id) {
|
|
@@ -1008,7 +1107,13 @@ var init_courseDB = __esm({
|
|
|
1008
1107
|
} else {
|
|
1009
1108
|
return s;
|
|
1010
1109
|
}
|
|
1011
|
-
}).map((c) =>
|
|
1110
|
+
}).map((c) => {
|
|
1111
|
+
return {
|
|
1112
|
+
courseID: this.id,
|
|
1113
|
+
cardID: c.id,
|
|
1114
|
+
elo: c.key
|
|
1115
|
+
};
|
|
1116
|
+
});
|
|
1012
1117
|
const str = `below:
|
|
1013
1118
|
${below.rows.map((r) => ` ${r.id}-${r.key}
|
|
1014
1119
|
`)}
|
|
@@ -1063,7 +1168,13 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
1063
1168
|
}
|
|
1064
1169
|
}
|
|
1065
1170
|
async addTagToCard(cardId, tagId, updateELO) {
|
|
1066
|
-
return await addTagToCard(
|
|
1171
|
+
return await addTagToCard(
|
|
1172
|
+
this.id,
|
|
1173
|
+
cardId,
|
|
1174
|
+
tagId,
|
|
1175
|
+
(await this._getCurrentUser()).getUsername(),
|
|
1176
|
+
updateELO
|
|
1177
|
+
);
|
|
1067
1178
|
}
|
|
1068
1179
|
async removeTagFromCard(cardId, tagId) {
|
|
1069
1180
|
return await removeTagFromCard(this.id, cardId, tagId);
|
|
@@ -1132,23 +1243,9 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
1132
1243
|
////////////////////////////////////
|
|
1133
1244
|
getNavigationStrategy(id) {
|
|
1134
1245
|
logger.debug(`[courseDB] Getting navigation strategy: ${id}`);
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
name: "ELO",
|
|
1139
|
-
description: "ELO-based navigation strategy for ordering content by difficulty",
|
|
1140
|
-
implementingClass: "elo" /* ELO */,
|
|
1141
|
-
course: this.id,
|
|
1142
|
-
serializedData: ""
|
|
1143
|
-
// serde is a noop for ELO navigator.
|
|
1144
|
-
};
|
|
1145
|
-
return Promise.resolve(strategy);
|
|
1146
|
-
}
|
|
1147
|
-
getAllNavigationStrategies() {
|
|
1148
|
-
logger.debug("[courseDB] Returning hard-coded navigation strategies");
|
|
1149
|
-
const strategies = [
|
|
1150
|
-
{
|
|
1151
|
-
id: "ELO",
|
|
1246
|
+
if (id == "") {
|
|
1247
|
+
const strategy = {
|
|
1248
|
+
_id: "NAVIGATION_STRATEGY-ELO",
|
|
1152
1249
|
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
1153
1250
|
name: "ELO",
|
|
1154
1251
|
description: "ELO-based navigation strategy for ordering content by difficulty",
|
|
@@ -1156,14 +1253,25 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
1156
1253
|
course: this.id,
|
|
1157
1254
|
serializedData: ""
|
|
1158
1255
|
// serde is a noop for ELO navigator.
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1256
|
+
};
|
|
1257
|
+
return Promise.resolve(strategy);
|
|
1258
|
+
} else {
|
|
1259
|
+
return this.db.get(id);
|
|
1260
|
+
}
|
|
1162
1261
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1262
|
+
async getAllNavigationStrategies() {
|
|
1263
|
+
const prefix = DocTypePrefixes["NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */];
|
|
1264
|
+
const result = await this.db.allDocs({
|
|
1265
|
+
startkey: prefix,
|
|
1266
|
+
endkey: `${prefix}\uFFF0`,
|
|
1267
|
+
include_docs: true
|
|
1268
|
+
});
|
|
1269
|
+
return result.rows.map((row) => row.doc);
|
|
1270
|
+
}
|
|
1271
|
+
async addNavigationStrategy(data) {
|
|
1272
|
+
logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);
|
|
1273
|
+
return this.db.put(data).then(() => {
|
|
1274
|
+
});
|
|
1167
1275
|
}
|
|
1168
1276
|
updateNavigationStrategy(id, data) {
|
|
1169
1277
|
logger.debug(`[courseDB] Updating navigation strategy: ${id}`);
|
|
@@ -1171,9 +1279,32 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
1171
1279
|
return Promise.resolve();
|
|
1172
1280
|
}
|
|
1173
1281
|
async surfaceNavigationStrategy() {
|
|
1282
|
+
try {
|
|
1283
|
+
const config = await this.getCourseConfig();
|
|
1284
|
+
if (config.defaultNavigationStrategyId) {
|
|
1285
|
+
try {
|
|
1286
|
+
const strategy = await this.getNavigationStrategy(config.defaultNavigationStrategyId);
|
|
1287
|
+
if (strategy) {
|
|
1288
|
+
logger.debug(`Surfacing strategy ${strategy.name} from course config`);
|
|
1289
|
+
return strategy;
|
|
1290
|
+
}
|
|
1291
|
+
} catch (e) {
|
|
1292
|
+
logger.warn(
|
|
1293
|
+
// @ts-expect-error tmp: defaultNavigationStrategyId property does not yet exist
|
|
1294
|
+
`Failed to load strategy '${config.defaultNavigationStrategyId}' specified in course config. Falling back to ELO.`,
|
|
1295
|
+
e
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
} catch (e) {
|
|
1300
|
+
logger.warn(
|
|
1301
|
+
"Could not retrieve course config to determine navigation strategy. Falling back to ELO.",
|
|
1302
|
+
e
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1174
1305
|
logger.warn(`Returning hard-coded default ELO navigator`);
|
|
1175
1306
|
const ret = {
|
|
1176
|
-
|
|
1307
|
+
_id: "NAVIGATION_STRATEGY-ELO",
|
|
1177
1308
|
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
1178
1309
|
name: "ELO",
|
|
1179
1310
|
description: "ELO-based navigation strategy",
|
|
@@ -1256,17 +1387,93 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
1256
1387
|
selectedCards.push(card);
|
|
1257
1388
|
}
|
|
1258
1389
|
return selectedCards.map((c) => {
|
|
1259
|
-
const split = c.split("-");
|
|
1260
1390
|
return {
|
|
1261
1391
|
courseID: this.id,
|
|
1262
|
-
cardID:
|
|
1263
|
-
qualifiedID: `${split[0]}-${split[1]}`,
|
|
1392
|
+
cardID: c.cardID,
|
|
1264
1393
|
contentSourceType: "course",
|
|
1265
1394
|
contentSourceID: this.id,
|
|
1395
|
+
elo: c.elo,
|
|
1266
1396
|
status: "new"
|
|
1267
1397
|
};
|
|
1268
1398
|
});
|
|
1269
1399
|
}
|
|
1400
|
+
// Admin search methods
|
|
1401
|
+
async searchCards(query) {
|
|
1402
|
+
logger.log(`[CourseDB ${this.id}] Searching for: "${query}"`);
|
|
1403
|
+
let displayableData;
|
|
1404
|
+
try {
|
|
1405
|
+
displayableData = await this.db.find({
|
|
1406
|
+
selector: {
|
|
1407
|
+
docType: "DISPLAYABLE_DATA",
|
|
1408
|
+
"data.0.data": { $regex: `.*${query}.*` }
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`);
|
|
1412
|
+
} catch (regexError) {
|
|
1413
|
+
logger.log(
|
|
1414
|
+
`[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,
|
|
1415
|
+
regexError
|
|
1416
|
+
);
|
|
1417
|
+
const allDisplayable = await this.db.find({
|
|
1418
|
+
selector: {
|
|
1419
|
+
docType: "DISPLAYABLE_DATA"
|
|
1420
|
+
}
|
|
1421
|
+
});
|
|
1422
|
+
logger.log(
|
|
1423
|
+
`[CourseDB ${this.id}] Retrieved ${allDisplayable.docs.length} documents for manual filtering`
|
|
1424
|
+
);
|
|
1425
|
+
displayableData = {
|
|
1426
|
+
docs: allDisplayable.docs.filter((doc) => {
|
|
1427
|
+
const docString = JSON.stringify(doc).toLowerCase();
|
|
1428
|
+
const match = docString.includes(query.toLowerCase());
|
|
1429
|
+
if (match) {
|
|
1430
|
+
logger.log(`[CourseDB ${this.id}] Manual match found in document: ${doc._id}`);
|
|
1431
|
+
}
|
|
1432
|
+
return match;
|
|
1433
|
+
})
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
logger.log(
|
|
1437
|
+
`[CourseDB ${this.id}] Found ${displayableData.docs.length} displayable data documents`
|
|
1438
|
+
);
|
|
1439
|
+
if (displayableData.docs.length === 0) {
|
|
1440
|
+
const allDisplayableData = await this.db.find({
|
|
1441
|
+
selector: {
|
|
1442
|
+
docType: "DISPLAYABLE_DATA"
|
|
1443
|
+
},
|
|
1444
|
+
limit: 5
|
|
1445
|
+
// Just sample a few
|
|
1446
|
+
});
|
|
1447
|
+
logger.log(
|
|
1448
|
+
`[CourseDB ${this.id}] Sample displayable data:`,
|
|
1449
|
+
allDisplayableData.docs.map((d) => ({
|
|
1450
|
+
id: d._id,
|
|
1451
|
+
docType: d.docType,
|
|
1452
|
+
dataStructure: d.data ? Object.keys(d.data) : "no data field",
|
|
1453
|
+
dataContent: d.data,
|
|
1454
|
+
fullDoc: d
|
|
1455
|
+
}))
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
const allResults = [];
|
|
1459
|
+
for (const dd of displayableData.docs) {
|
|
1460
|
+
const cards = await this.db.find({
|
|
1461
|
+
selector: {
|
|
1462
|
+
docType: "CARD",
|
|
1463
|
+
id_displayable_data: { $in: [dd._id] }
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
logger.log(
|
|
1467
|
+
`[CourseDB ${this.id}] Displayable data ${dd._id} linked to ${cards.docs.length} cards`
|
|
1468
|
+
);
|
|
1469
|
+
allResults.push(...cards.docs);
|
|
1470
|
+
}
|
|
1471
|
+
logger.log(`[CourseDB ${this.id}] Total cards found: ${allResults.length}`);
|
|
1472
|
+
return allResults;
|
|
1473
|
+
}
|
|
1474
|
+
async find(request) {
|
|
1475
|
+
return this.db.find(request);
|
|
1476
|
+
}
|
|
1270
1477
|
};
|
|
1271
1478
|
}
|
|
1272
1479
|
});
|
|
@@ -1331,7 +1538,7 @@ var init_classroomDB2 = __esm({
|
|
|
1331
1538
|
const dbName = `classdb-student-${this._id}`;
|
|
1332
1539
|
this._db = new pouchdb_setup_default(
|
|
1333
1540
|
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
1334
|
-
|
|
1541
|
+
createPouchDBConfig()
|
|
1335
1542
|
);
|
|
1336
1543
|
try {
|
|
1337
1544
|
const cfg = await this._db.get(CLASSROOM_CONFIG);
|
|
@@ -1400,9 +1607,11 @@ var init_classroomDB2 = __esm({
|
|
|
1400
1607
|
ret.push(await getCourseDB2(content.courseID).get(content.cardID));
|
|
1401
1608
|
}
|
|
1402
1609
|
}
|
|
1403
|
-
logger.info(
|
|
1610
|
+
logger.info(
|
|
1611
|
+
`New Cards from classroom ${this._cfg.name}: ${ret.map((c) => `${c.courseID}-${c.cardID}`)}`
|
|
1612
|
+
);
|
|
1404
1613
|
return ret.filter((c) => {
|
|
1405
|
-
if (activeCards.some((ac) => c.
|
|
1614
|
+
if (activeCards.some((ac) => c.cardID === ac.cardID)) {
|
|
1406
1615
|
return false;
|
|
1407
1616
|
} else {
|
|
1408
1617
|
return true;
|
|
@@ -1456,10 +1665,29 @@ var init_CouchDBSyncStrategy = __esm({
|
|
|
1456
1665
|
import fetch2 from "cross-fetch";
|
|
1457
1666
|
import moment4 from "moment";
|
|
1458
1667
|
import process2 from "process";
|
|
1668
|
+
function createPouchDBConfig() {
|
|
1669
|
+
const hasExplicitCredentials = ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD;
|
|
1670
|
+
const isNodeEnvironment = typeof window === "undefined";
|
|
1671
|
+
if (hasExplicitCredentials && isNodeEnvironment) {
|
|
1672
|
+
return {
|
|
1673
|
+
fetch(url, opts = {}) {
|
|
1674
|
+
const basicAuth = btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`);
|
|
1675
|
+
const headers = new Headers(opts.headers || {});
|
|
1676
|
+
headers.set("Authorization", `Basic ${basicAuth}`);
|
|
1677
|
+
const newOpts = {
|
|
1678
|
+
...opts,
|
|
1679
|
+
headers
|
|
1680
|
+
};
|
|
1681
|
+
return pouchdb_setup_default.fetch(url, newOpts);
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
return pouchDBincludeCredentialsConfig;
|
|
1686
|
+
}
|
|
1459
1687
|
function getCourseDB2(courseID) {
|
|
1460
1688
|
return new pouchdb_setup_default(
|
|
1461
1689
|
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "coursedb-" + courseID,
|
|
1462
|
-
|
|
1690
|
+
createPouchDBConfig()
|
|
1463
1691
|
);
|
|
1464
1692
|
}
|
|
1465
1693
|
function getCourseDocs(courseID, docIDs, options = {}) {
|
|
@@ -1748,6 +1976,9 @@ Currently logged-in as ${this._username}.`
|
|
|
1748
1976
|
await this.init();
|
|
1749
1977
|
return ret;
|
|
1750
1978
|
}
|
|
1979
|
+
async get(id) {
|
|
1980
|
+
return this.localDB.get(id);
|
|
1981
|
+
}
|
|
1751
1982
|
update(id, update) {
|
|
1752
1983
|
return this.updateQueue.update(id, update);
|
|
1753
1984
|
}
|
|
@@ -1794,7 +2025,12 @@ Currently logged-in as ${this._username}.`
|
|
|
1794
2025
|
endkey: keys.endkey,
|
|
1795
2026
|
include_docs: true
|
|
1796
2027
|
});
|
|
1797
|
-
return reviews.rows.map((r) =>
|
|
2028
|
+
return reviews.rows.map((r) => {
|
|
2029
|
+
return {
|
|
2030
|
+
courseID: r.doc.courseId,
|
|
2031
|
+
cardID: r.doc.cardId
|
|
2032
|
+
};
|
|
2033
|
+
});
|
|
1798
2034
|
}
|
|
1799
2035
|
async getActivityRecords() {
|
|
1800
2036
|
try {
|
|
@@ -2074,8 +2310,18 @@ Currently logged-in as ${this._username}.`
|
|
|
2074
2310
|
}
|
|
2075
2311
|
this.setDBandQ();
|
|
2076
2312
|
this.syncStrategy.startSync(this.localDB, this.remoteDB);
|
|
2077
|
-
|
|
2078
|
-
|
|
2313
|
+
this.applyDesignDocs().catch((error) => {
|
|
2314
|
+
log3(`Error in applyDesignDocs background task: ${error}`);
|
|
2315
|
+
if (error && typeof error === "object") {
|
|
2316
|
+
log3(`Full error details in applyDesignDocs: ${JSON.stringify(error)}`);
|
|
2317
|
+
}
|
|
2318
|
+
});
|
|
2319
|
+
this.deduplicateReviews().catch((error) => {
|
|
2320
|
+
log3(`Error in deduplicateReviews background task: ${error}`);
|
|
2321
|
+
if (error && typeof error === "object") {
|
|
2322
|
+
log3(`Full error details in background task: ${JSON.stringify(error)}`);
|
|
2323
|
+
}
|
|
2324
|
+
});
|
|
2079
2325
|
_BaseUser._initialized = true;
|
|
2080
2326
|
}
|
|
2081
2327
|
static designDocs = [
|
|
@@ -2093,10 +2339,15 @@ Currently logged-in as ${this._username}.`
|
|
|
2093
2339
|
}
|
|
2094
2340
|
];
|
|
2095
2341
|
async applyDesignDocs() {
|
|
2342
|
+
log3(`Starting applyDesignDocs for user: ${this._username}`);
|
|
2343
|
+
log3(`Remote DB name: ${this.remoteDB.name || "unknown"}`);
|
|
2096
2344
|
if (this._username === "admin") {
|
|
2345
|
+
log3("Skipping design docs for admin user");
|
|
2097
2346
|
return;
|
|
2098
2347
|
}
|
|
2348
|
+
log3(`Applying ${_BaseUser.designDocs.length} design docs`);
|
|
2099
2349
|
for (const doc of _BaseUser.designDocs) {
|
|
2350
|
+
log3(`Applying design doc: ${doc._id}`);
|
|
2100
2351
|
try {
|
|
2101
2352
|
try {
|
|
2102
2353
|
const existingDoc = await this.remoteDB.get(doc._id);
|
|
@@ -2173,17 +2424,21 @@ Currently logged-in as ${this._username}.`
|
|
|
2173
2424
|
} catch (e) {
|
|
2174
2425
|
const reason = e;
|
|
2175
2426
|
if (reason.status === 404) {
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2427
|
+
try {
|
|
2428
|
+
const initCardHistory = {
|
|
2429
|
+
_id: cardHistoryID,
|
|
2430
|
+
cardID: record.cardID,
|
|
2431
|
+
courseID: record.courseID,
|
|
2432
|
+
records: [record],
|
|
2433
|
+
lapses: 0,
|
|
2434
|
+
streak: 0,
|
|
2435
|
+
bestInterval: 0
|
|
2436
|
+
};
|
|
2437
|
+
const putResult = await this.writeDB.put(initCardHistory);
|
|
2438
|
+
return { ...initCardHistory, _rev: putResult.rev };
|
|
2439
|
+
} catch (creationError) {
|
|
2440
|
+
throw new Error(`Failed to create CardHistory for ${cardHistoryID}. Reason: ${creationError}`);
|
|
2441
|
+
}
|
|
2187
2442
|
} else {
|
|
2188
2443
|
throw new Error(`putCardRecord failed because of:
|
|
2189
2444
|
name:${reason.name}
|
|
@@ -2195,8 +2450,13 @@ Currently logged-in as ${this._username}.`
|
|
|
2195
2450
|
async deduplicateReviews() {
|
|
2196
2451
|
try {
|
|
2197
2452
|
log3("Starting deduplication of scheduled reviews...");
|
|
2453
|
+
log3(`Remote DB name: ${this.remoteDB.name || "unknown"}`);
|
|
2454
|
+
log3(`Write DB name: ${this.writeDB.name || "unknown"}`);
|
|
2198
2455
|
const reviewsMap = {};
|
|
2199
2456
|
const duplicateDocIds = [];
|
|
2457
|
+
log3(
|
|
2458
|
+
`Attempting to query remoteDB for reviewCards/reviewCards. Database: ${this.remoteDB.name || "unknown"}`
|
|
2459
|
+
);
|
|
2200
2460
|
const scheduledReviews = await this.remoteDB.query("reviewCards/reviewCards");
|
|
2201
2461
|
log3(`Found ${scheduledReviews.rows.length} scheduled reviews to process`);
|
|
2202
2462
|
scheduledReviews.rows.forEach((r) => {
|
|
@@ -2231,6 +2491,17 @@ Currently logged-in as ${this._username}.`
|
|
|
2231
2491
|
}
|
|
2232
2492
|
} catch (error) {
|
|
2233
2493
|
log3(`Error during review deduplication: ${error}`);
|
|
2494
|
+
if (error && typeof error === "object" && "status" in error && error.status === 404) {
|
|
2495
|
+
log3(
|
|
2496
|
+
`Database not found (404) during review deduplication. Database: ${this.remoteDB.name || "unknown"}`
|
|
2497
|
+
);
|
|
2498
|
+
log3(
|
|
2499
|
+
`This might indicate the user database doesn't exist or the reviewCards view isn't available`
|
|
2500
|
+
);
|
|
2501
|
+
}
|
|
2502
|
+
if (error && typeof error === "object") {
|
|
2503
|
+
log3(`Full error details: ${JSON.stringify(error)}`);
|
|
2504
|
+
}
|
|
2234
2505
|
}
|
|
2235
2506
|
}
|
|
2236
2507
|
/**
|