@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
|
@@ -90,9 +90,9 @@ var init_pouchdb_setup = __esm({
|
|
|
90
90
|
PouchDB.plugin(PouchDBFind);
|
|
91
91
|
PouchDB.plugin(PouchDBAuth);
|
|
92
92
|
PouchDB.defaults({
|
|
93
|
-
ajax: {
|
|
94
|
-
|
|
95
|
-
}
|
|
93
|
+
// ajax: {
|
|
94
|
+
// timeout: 60000,
|
|
95
|
+
// },
|
|
96
96
|
});
|
|
97
97
|
pouchdb_setup_default = PouchDB;
|
|
98
98
|
}
|
|
@@ -150,42 +150,58 @@ var init_updateQueue = __esm({
|
|
|
150
150
|
async applyUpdates(id) {
|
|
151
151
|
logger.debug(`Applying updates on doc: ${id}`);
|
|
152
152
|
if (this.inprogressUpdates[id]) {
|
|
153
|
-
|
|
153
|
+
while (this.inprogressUpdates[id]) {
|
|
154
|
+
await new Promise((resolve) => setTimeout(resolve, Math.random() * 50));
|
|
155
|
+
}
|
|
154
156
|
return this.applyUpdates(id);
|
|
155
157
|
} else {
|
|
156
158
|
if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {
|
|
157
159
|
this.inprogressUpdates[id] = true;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
160
|
+
const MAX_RETRIES = 5;
|
|
161
|
+
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
162
|
+
try {
|
|
163
|
+
const doc = await this.readDB.get(id);
|
|
164
|
+
logger.debug(`Retrieved doc: ${id}`);
|
|
165
|
+
let updatedDoc = { ...doc };
|
|
166
|
+
const updatesToApply = [...this.pendingUpdates[id]];
|
|
167
|
+
for (const update of updatesToApply) {
|
|
168
|
+
if (typeof update === "function") {
|
|
169
|
+
updatedDoc = { ...updatedDoc, ...update(updatedDoc) };
|
|
170
|
+
} else {
|
|
171
|
+
updatedDoc = {
|
|
172
|
+
...updatedDoc,
|
|
173
|
+
...update
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
await this.writeDB.put(updatedDoc);
|
|
178
|
+
logger.debug(`Put doc: ${id}`);
|
|
179
|
+
this.pendingUpdates[id].splice(0, updatesToApply.length);
|
|
180
|
+
if (this.pendingUpdates[id].length === 0) {
|
|
181
|
+
this.inprogressUpdates[id] = false;
|
|
182
|
+
delete this.inprogressUpdates[id];
|
|
165
183
|
} else {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
184
|
+
return this.applyUpdates(id);
|
|
185
|
+
}
|
|
186
|
+
return updatedDoc;
|
|
187
|
+
} catch (e) {
|
|
188
|
+
if (e.name === "conflict" && i < MAX_RETRIES - 1) {
|
|
189
|
+
logger.warn(`Conflict on update for doc ${id}, retry #${i + 1}`);
|
|
190
|
+
await new Promise((res) => setTimeout(res, 50 * Math.random()));
|
|
191
|
+
} else if (e.name === "not_found" && i === 0) {
|
|
192
|
+
logger.warn(`Update failed for ${id} - does not exist. Throwing to caller.`);
|
|
193
|
+
throw e;
|
|
194
|
+
} else {
|
|
195
|
+
delete this.inprogressUpdates[id];
|
|
196
|
+
if (this.pendingUpdates[id]) {
|
|
197
|
+
delete this.pendingUpdates[id];
|
|
198
|
+
}
|
|
199
|
+
logger.error(`Error on attemped update (retry ${i}): ${JSON.stringify(e)}`);
|
|
200
|
+
throw e;
|
|
170
201
|
}
|
|
171
202
|
}
|
|
172
|
-
await this.writeDB.put(doc);
|
|
173
|
-
logger.debug(`Put doc: ${id}`);
|
|
174
|
-
if (this.pendingUpdates[id].length === 0) {
|
|
175
|
-
this.inprogressUpdates[id] = false;
|
|
176
|
-
delete this.inprogressUpdates[id];
|
|
177
|
-
} else {
|
|
178
|
-
return this.applyUpdates(id);
|
|
179
|
-
}
|
|
180
|
-
return doc;
|
|
181
|
-
} catch (e) {
|
|
182
|
-
delete this.inprogressUpdates[id];
|
|
183
|
-
if (this.pendingUpdates[id]) {
|
|
184
|
-
delete this.pendingUpdates[id];
|
|
185
|
-
}
|
|
186
|
-
logger.error(`Error on attemped update: ${JSON.stringify(e)}`);
|
|
187
|
-
throw e;
|
|
188
203
|
}
|
|
204
|
+
throw new Error(`UpdateQueue failed for doc ${id} after ${MAX_RETRIES} retries.`);
|
|
189
205
|
} else {
|
|
190
206
|
throw new Error(`Empty Updates Queue Triggered`);
|
|
191
207
|
}
|
|
@@ -412,7 +428,7 @@ function getCourseDB(courseID) {
|
|
|
412
428
|
const dbName = `coursedb-${courseID}`;
|
|
413
429
|
return new pouchdb_setup_default(
|
|
414
430
|
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
415
|
-
|
|
431
|
+
createPouchDBConfig()
|
|
416
432
|
);
|
|
417
433
|
}
|
|
418
434
|
var AlreadyTaggedErr;
|
|
@@ -534,6 +550,7 @@ var init_courseLookupDB = __esm({
|
|
|
534
550
|
const doc = await _CourseLookup._db.get(courseID);
|
|
535
551
|
return await _CourseLookup._db.remove(doc);
|
|
536
552
|
}
|
|
553
|
+
// [ ] rename to allCourses()
|
|
537
554
|
static async allCourseWare() {
|
|
538
555
|
const resp = await _CourseLookup._db.allDocs({
|
|
539
556
|
include_docs: true
|
|
@@ -604,13 +621,16 @@ var init_elo = __esm({
|
|
|
604
621
|
}
|
|
605
622
|
async getNewCards(limit = 99) {
|
|
606
623
|
const activeCards = await this.user.getActiveCards();
|
|
607
|
-
return (await this.course.getCardsCenteredAtELO(
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
624
|
+
return (await this.course.getCardsCenteredAtELO(
|
|
625
|
+
{ limit, elo: "user" },
|
|
626
|
+
(c) => {
|
|
627
|
+
if (activeCards.some((ac) => c.cardID === ac.cardID)) {
|
|
628
|
+
return false;
|
|
629
|
+
} else {
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
612
632
|
}
|
|
613
|
-
|
|
633
|
+
)).map((c) => {
|
|
614
634
|
return {
|
|
615
635
|
...c,
|
|
616
636
|
status: "new"
|
|
@@ -621,12 +641,74 @@ var init_elo = __esm({
|
|
|
621
641
|
}
|
|
622
642
|
});
|
|
623
643
|
|
|
644
|
+
// src/core/navigators/hardcodedOrder.ts
|
|
645
|
+
var hardcodedOrder_exports = {};
|
|
646
|
+
__export(hardcodedOrder_exports, {
|
|
647
|
+
default: () => HardcodedOrderNavigator
|
|
648
|
+
});
|
|
649
|
+
var HardcodedOrderNavigator;
|
|
650
|
+
var init_hardcodedOrder = __esm({
|
|
651
|
+
"src/core/navigators/hardcodedOrder.ts"() {
|
|
652
|
+
"use strict";
|
|
653
|
+
init_navigators();
|
|
654
|
+
init_logger();
|
|
655
|
+
HardcodedOrderNavigator = class extends ContentNavigator {
|
|
656
|
+
orderedCardIds = [];
|
|
657
|
+
user;
|
|
658
|
+
course;
|
|
659
|
+
constructor(user, course, strategyData) {
|
|
660
|
+
super();
|
|
661
|
+
this.user = user;
|
|
662
|
+
this.course = course;
|
|
663
|
+
if (strategyData.serializedData) {
|
|
664
|
+
try {
|
|
665
|
+
this.orderedCardIds = JSON.parse(strategyData.serializedData);
|
|
666
|
+
} catch (e) {
|
|
667
|
+
logger.error("Failed to parse serializedData for HardcodedOrderNavigator", e);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
async getPendingReviews() {
|
|
672
|
+
const reviews = await this.user.getPendingReviews(this.course.getCourseID());
|
|
673
|
+
return reviews.map((r) => {
|
|
674
|
+
return {
|
|
675
|
+
...r,
|
|
676
|
+
contentSourceType: "course",
|
|
677
|
+
contentSourceID: this.course.getCourseID(),
|
|
678
|
+
cardID: r.cardId,
|
|
679
|
+
courseID: r.courseId,
|
|
680
|
+
reviewID: r._id,
|
|
681
|
+
status: "review"
|
|
682
|
+
};
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
async getNewCards(limit = 99) {
|
|
686
|
+
const activeCardIds = (await this.user.getActiveCards()).map((c) => c.cardID);
|
|
687
|
+
const newCardIds = this.orderedCardIds.filter(
|
|
688
|
+
(cardId) => !activeCardIds.includes(cardId)
|
|
689
|
+
);
|
|
690
|
+
const cardsToReturn = newCardIds.slice(0, limit);
|
|
691
|
+
return cardsToReturn.map((cardId) => {
|
|
692
|
+
return {
|
|
693
|
+
cardID: cardId,
|
|
694
|
+
courseID: this.course.getCourseID(),
|
|
695
|
+
contentSourceType: "course",
|
|
696
|
+
contentSourceID: this.course.getCourseID(),
|
|
697
|
+
status: "new"
|
|
698
|
+
};
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
|
|
624
705
|
// import("./**/*") in src/core/navigators/index.ts
|
|
625
706
|
var globImport;
|
|
626
707
|
var init_ = __esm({
|
|
627
708
|
'import("./**/*") in src/core/navigators/index.ts'() {
|
|
628
709
|
globImport = __glob({
|
|
629
710
|
"./elo.ts": () => Promise.resolve().then(() => (init_elo(), elo_exports)),
|
|
711
|
+
"./hardcodedOrder.ts": () => Promise.resolve().then(() => (init_hardcodedOrder(), hardcodedOrder_exports)),
|
|
630
712
|
"./index.ts": () => Promise.resolve().then(() => (init_navigators(), navigators_exports))
|
|
631
713
|
});
|
|
632
714
|
}
|
|
@@ -646,6 +728,7 @@ var init_navigators = __esm({
|
|
|
646
728
|
init_();
|
|
647
729
|
Navigators = /* @__PURE__ */ ((Navigators2) => {
|
|
648
730
|
Navigators2["ELO"] = "elo";
|
|
731
|
+
Navigators2["HARDCODED"] = "hardcodedOrder";
|
|
649
732
|
return Navigators2;
|
|
650
733
|
})(Navigators || {});
|
|
651
734
|
ContentNavigator = class {
|
|
@@ -658,7 +741,7 @@ var init_navigators = __esm({
|
|
|
658
741
|
static async create(user, course, strategyData) {
|
|
659
742
|
const implementingClass = strategyData.implementingClass;
|
|
660
743
|
let NavigatorImpl;
|
|
661
|
-
const variations = ["", ".js", "
|
|
744
|
+
const variations = [".ts", ".js", ""];
|
|
662
745
|
for (const ext of variations) {
|
|
663
746
|
try {
|
|
664
747
|
const module = await globImport(`./${implementingClass}${ext}`);
|
|
@@ -962,6 +1045,23 @@ var init_courseDB = __esm({
|
|
|
962
1045
|
if (!doc.docType || !(doc.docType === "CARD" /* CARD */)) {
|
|
963
1046
|
throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);
|
|
964
1047
|
}
|
|
1048
|
+
try {
|
|
1049
|
+
const appliedTags = await this.getAppliedTags(id);
|
|
1050
|
+
const results = await Promise.allSettled(
|
|
1051
|
+
appliedTags.rows.map(async (tagRow) => {
|
|
1052
|
+
const tagId = tagRow.id;
|
|
1053
|
+
await this.removeTagFromCard(id, tagId);
|
|
1054
|
+
})
|
|
1055
|
+
);
|
|
1056
|
+
results.forEach((result, index) => {
|
|
1057
|
+
if (result.status === "rejected") {
|
|
1058
|
+
const tagId = appliedTags.rows[index].id;
|
|
1059
|
+
logger.error(`Failed to remove card ${id} from tag ${tagId}: ${result.reason}`);
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
logger.error(`Error removing card ${id} from tags: ${error}`);
|
|
1064
|
+
}
|
|
965
1065
|
return this.db.remove(doc);
|
|
966
1066
|
}
|
|
967
1067
|
async getCardDisplayableDataIDs(id) {
|
|
@@ -1009,7 +1109,13 @@ var init_courseDB = __esm({
|
|
|
1009
1109
|
} else {
|
|
1010
1110
|
return s;
|
|
1011
1111
|
}
|
|
1012
|
-
}).map((c) =>
|
|
1112
|
+
}).map((c) => {
|
|
1113
|
+
return {
|
|
1114
|
+
courseID: this.id,
|
|
1115
|
+
cardID: c.id,
|
|
1116
|
+
elo: c.key
|
|
1117
|
+
};
|
|
1118
|
+
});
|
|
1013
1119
|
const str = `below:
|
|
1014
1120
|
${below.rows.map((r) => ` ${r.id}-${r.key}
|
|
1015
1121
|
`)}
|
|
@@ -1064,7 +1170,13 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
1064
1170
|
}
|
|
1065
1171
|
}
|
|
1066
1172
|
async addTagToCard(cardId, tagId, updateELO) {
|
|
1067
|
-
return await addTagToCard(
|
|
1173
|
+
return await addTagToCard(
|
|
1174
|
+
this.id,
|
|
1175
|
+
cardId,
|
|
1176
|
+
tagId,
|
|
1177
|
+
(await this._getCurrentUser()).getUsername(),
|
|
1178
|
+
updateELO
|
|
1179
|
+
);
|
|
1068
1180
|
}
|
|
1069
1181
|
async removeTagFromCard(cardId, tagId) {
|
|
1070
1182
|
return await removeTagFromCard(this.id, cardId, tagId);
|
|
@@ -1133,23 +1245,9 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
1133
1245
|
////////////////////////////////////
|
|
1134
1246
|
getNavigationStrategy(id) {
|
|
1135
1247
|
logger.debug(`[courseDB] Getting navigation strategy: ${id}`);
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
name: "ELO",
|
|
1140
|
-
description: "ELO-based navigation strategy for ordering content by difficulty",
|
|
1141
|
-
implementingClass: "elo" /* ELO */,
|
|
1142
|
-
course: this.id,
|
|
1143
|
-
serializedData: ""
|
|
1144
|
-
// serde is a noop for ELO navigator.
|
|
1145
|
-
};
|
|
1146
|
-
return Promise.resolve(strategy);
|
|
1147
|
-
}
|
|
1148
|
-
getAllNavigationStrategies() {
|
|
1149
|
-
logger.debug("[courseDB] Returning hard-coded navigation strategies");
|
|
1150
|
-
const strategies = [
|
|
1151
|
-
{
|
|
1152
|
-
id: "ELO",
|
|
1248
|
+
if (id == "") {
|
|
1249
|
+
const strategy = {
|
|
1250
|
+
_id: "NAVIGATION_STRATEGY-ELO",
|
|
1153
1251
|
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
1154
1252
|
name: "ELO",
|
|
1155
1253
|
description: "ELO-based navigation strategy for ordering content by difficulty",
|
|
@@ -1157,14 +1255,25 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
1157
1255
|
course: this.id,
|
|
1158
1256
|
serializedData: ""
|
|
1159
1257
|
// serde is a noop for ELO navigator.
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1258
|
+
};
|
|
1259
|
+
return Promise.resolve(strategy);
|
|
1260
|
+
} else {
|
|
1261
|
+
return this.db.get(id);
|
|
1262
|
+
}
|
|
1163
1263
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1264
|
+
async getAllNavigationStrategies() {
|
|
1265
|
+
const prefix = DocTypePrefixes["NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */];
|
|
1266
|
+
const result = await this.db.allDocs({
|
|
1267
|
+
startkey: prefix,
|
|
1268
|
+
endkey: `${prefix}\uFFF0`,
|
|
1269
|
+
include_docs: true
|
|
1270
|
+
});
|
|
1271
|
+
return result.rows.map((row) => row.doc);
|
|
1272
|
+
}
|
|
1273
|
+
async addNavigationStrategy(data) {
|
|
1274
|
+
logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);
|
|
1275
|
+
return this.db.put(data).then(() => {
|
|
1276
|
+
});
|
|
1168
1277
|
}
|
|
1169
1278
|
updateNavigationStrategy(id, data) {
|
|
1170
1279
|
logger.debug(`[courseDB] Updating navigation strategy: ${id}`);
|
|
@@ -1172,9 +1281,32 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
1172
1281
|
return Promise.resolve();
|
|
1173
1282
|
}
|
|
1174
1283
|
async surfaceNavigationStrategy() {
|
|
1284
|
+
try {
|
|
1285
|
+
const config = await this.getCourseConfig();
|
|
1286
|
+
if (config.defaultNavigationStrategyId) {
|
|
1287
|
+
try {
|
|
1288
|
+
const strategy = await this.getNavigationStrategy(config.defaultNavigationStrategyId);
|
|
1289
|
+
if (strategy) {
|
|
1290
|
+
logger.debug(`Surfacing strategy ${strategy.name} from course config`);
|
|
1291
|
+
return strategy;
|
|
1292
|
+
}
|
|
1293
|
+
} catch (e) {
|
|
1294
|
+
logger.warn(
|
|
1295
|
+
// @ts-expect-error tmp: defaultNavigationStrategyId property does not yet exist
|
|
1296
|
+
`Failed to load strategy '${config.defaultNavigationStrategyId}' specified in course config. Falling back to ELO.`,
|
|
1297
|
+
e
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
} catch (e) {
|
|
1302
|
+
logger.warn(
|
|
1303
|
+
"Could not retrieve course config to determine navigation strategy. Falling back to ELO.",
|
|
1304
|
+
e
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1175
1307
|
logger.warn(`Returning hard-coded default ELO navigator`);
|
|
1176
1308
|
const ret = {
|
|
1177
|
-
|
|
1309
|
+
_id: "NAVIGATION_STRATEGY-ELO",
|
|
1178
1310
|
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
1179
1311
|
name: "ELO",
|
|
1180
1312
|
description: "ELO-based navigation strategy",
|
|
@@ -1257,17 +1389,93 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
1257
1389
|
selectedCards.push(card);
|
|
1258
1390
|
}
|
|
1259
1391
|
return selectedCards.map((c) => {
|
|
1260
|
-
const split = c.split("-");
|
|
1261
1392
|
return {
|
|
1262
1393
|
courseID: this.id,
|
|
1263
|
-
cardID:
|
|
1264
|
-
qualifiedID: `${split[0]}-${split[1]}`,
|
|
1394
|
+
cardID: c.cardID,
|
|
1265
1395
|
contentSourceType: "course",
|
|
1266
1396
|
contentSourceID: this.id,
|
|
1397
|
+
elo: c.elo,
|
|
1267
1398
|
status: "new"
|
|
1268
1399
|
};
|
|
1269
1400
|
});
|
|
1270
1401
|
}
|
|
1402
|
+
// Admin search methods
|
|
1403
|
+
async searchCards(query) {
|
|
1404
|
+
logger.log(`[CourseDB ${this.id}] Searching for: "${query}"`);
|
|
1405
|
+
let displayableData;
|
|
1406
|
+
try {
|
|
1407
|
+
displayableData = await this.db.find({
|
|
1408
|
+
selector: {
|
|
1409
|
+
docType: "DISPLAYABLE_DATA",
|
|
1410
|
+
"data.0.data": { $regex: `.*${query}.*` }
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`);
|
|
1414
|
+
} catch (regexError) {
|
|
1415
|
+
logger.log(
|
|
1416
|
+
`[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,
|
|
1417
|
+
regexError
|
|
1418
|
+
);
|
|
1419
|
+
const allDisplayable = await this.db.find({
|
|
1420
|
+
selector: {
|
|
1421
|
+
docType: "DISPLAYABLE_DATA"
|
|
1422
|
+
}
|
|
1423
|
+
});
|
|
1424
|
+
logger.log(
|
|
1425
|
+
`[CourseDB ${this.id}] Retrieved ${allDisplayable.docs.length} documents for manual filtering`
|
|
1426
|
+
);
|
|
1427
|
+
displayableData = {
|
|
1428
|
+
docs: allDisplayable.docs.filter((doc) => {
|
|
1429
|
+
const docString = JSON.stringify(doc).toLowerCase();
|
|
1430
|
+
const match = docString.includes(query.toLowerCase());
|
|
1431
|
+
if (match) {
|
|
1432
|
+
logger.log(`[CourseDB ${this.id}] Manual match found in document: ${doc._id}`);
|
|
1433
|
+
}
|
|
1434
|
+
return match;
|
|
1435
|
+
})
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
logger.log(
|
|
1439
|
+
`[CourseDB ${this.id}] Found ${displayableData.docs.length} displayable data documents`
|
|
1440
|
+
);
|
|
1441
|
+
if (displayableData.docs.length === 0) {
|
|
1442
|
+
const allDisplayableData = await this.db.find({
|
|
1443
|
+
selector: {
|
|
1444
|
+
docType: "DISPLAYABLE_DATA"
|
|
1445
|
+
},
|
|
1446
|
+
limit: 5
|
|
1447
|
+
// Just sample a few
|
|
1448
|
+
});
|
|
1449
|
+
logger.log(
|
|
1450
|
+
`[CourseDB ${this.id}] Sample displayable data:`,
|
|
1451
|
+
allDisplayableData.docs.map((d) => ({
|
|
1452
|
+
id: d._id,
|
|
1453
|
+
docType: d.docType,
|
|
1454
|
+
dataStructure: d.data ? Object.keys(d.data) : "no data field",
|
|
1455
|
+
dataContent: d.data,
|
|
1456
|
+
fullDoc: d
|
|
1457
|
+
}))
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
const allResults = [];
|
|
1461
|
+
for (const dd of displayableData.docs) {
|
|
1462
|
+
const cards = await this.db.find({
|
|
1463
|
+
selector: {
|
|
1464
|
+
docType: "CARD",
|
|
1465
|
+
id_displayable_data: { $in: [dd._id] }
|
|
1466
|
+
}
|
|
1467
|
+
});
|
|
1468
|
+
logger.log(
|
|
1469
|
+
`[CourseDB ${this.id}] Displayable data ${dd._id} linked to ${cards.docs.length} cards`
|
|
1470
|
+
);
|
|
1471
|
+
allResults.push(...cards.docs);
|
|
1472
|
+
}
|
|
1473
|
+
logger.log(`[CourseDB ${this.id}] Total cards found: ${allResults.length}`);
|
|
1474
|
+
return allResults;
|
|
1475
|
+
}
|
|
1476
|
+
async find(request) {
|
|
1477
|
+
return this.db.find(request);
|
|
1478
|
+
}
|
|
1271
1479
|
};
|
|
1272
1480
|
}
|
|
1273
1481
|
});
|
|
@@ -1279,7 +1487,7 @@ function getClassroomDB(classID, version) {
|
|
|
1279
1487
|
logger.info(`Retrieving classroom db: ${dbName}`);
|
|
1280
1488
|
return new pouchdb_setup_default(
|
|
1281
1489
|
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
1282
|
-
|
|
1490
|
+
createPouchDBConfig()
|
|
1283
1491
|
);
|
|
1284
1492
|
}
|
|
1285
1493
|
async function getClassroomConfig(classID) {
|
|
@@ -1344,7 +1552,7 @@ var init_classroomDB2 = __esm({
|
|
|
1344
1552
|
const dbName = `classdb-student-${this._id}`;
|
|
1345
1553
|
this._db = new pouchdb_setup_default(
|
|
1346
1554
|
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
1347
|
-
|
|
1555
|
+
createPouchDBConfig()
|
|
1348
1556
|
);
|
|
1349
1557
|
try {
|
|
1350
1558
|
const cfg = await this._db.get(CLASSROOM_CONFIG);
|
|
@@ -1413,9 +1621,11 @@ var init_classroomDB2 = __esm({
|
|
|
1413
1621
|
ret.push(await getCourseDB2(content.courseID).get(content.cardID));
|
|
1414
1622
|
}
|
|
1415
1623
|
}
|
|
1416
|
-
logger.info(
|
|
1624
|
+
logger.info(
|
|
1625
|
+
`New Cards from classroom ${this._cfg.name}: ${ret.map((c) => `${c.courseID}-${c.cardID}`)}`
|
|
1626
|
+
);
|
|
1417
1627
|
return ret.filter((c) => {
|
|
1418
|
-
if (activeCards.some((ac) => c.
|
|
1628
|
+
if (activeCards.some((ac) => c.cardID === ac.cardID)) {
|
|
1419
1629
|
return false;
|
|
1420
1630
|
} else {
|
|
1421
1631
|
return true;
|
|
@@ -1434,11 +1644,11 @@ var init_classroomDB2 = __esm({
|
|
|
1434
1644
|
const stuDbName = `classdb-student-${this._id}`;
|
|
1435
1645
|
this._db = new pouchdb_setup_default(
|
|
1436
1646
|
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
1437
|
-
|
|
1647
|
+
createPouchDBConfig()
|
|
1438
1648
|
);
|
|
1439
1649
|
this._stuDb = new pouchdb_setup_default(
|
|
1440
1650
|
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + stuDbName,
|
|
1441
|
-
|
|
1651
|
+
createPouchDBConfig()
|
|
1442
1652
|
);
|
|
1443
1653
|
try {
|
|
1444
1654
|
return this._db.get(CLASSROOM_CONFIG).then((cfg) => {
|
|
@@ -2023,6 +2233,9 @@ Currently logged-in as ${this._username}.`
|
|
|
2023
2233
|
await this.init();
|
|
2024
2234
|
return ret;
|
|
2025
2235
|
}
|
|
2236
|
+
async get(id) {
|
|
2237
|
+
return this.localDB.get(id);
|
|
2238
|
+
}
|
|
2026
2239
|
update(id, update) {
|
|
2027
2240
|
return this.updateQueue.update(id, update);
|
|
2028
2241
|
}
|
|
@@ -2069,7 +2282,12 @@ Currently logged-in as ${this._username}.`
|
|
|
2069
2282
|
endkey: keys.endkey,
|
|
2070
2283
|
include_docs: true
|
|
2071
2284
|
});
|
|
2072
|
-
return reviews.rows.map((r) =>
|
|
2285
|
+
return reviews.rows.map((r) => {
|
|
2286
|
+
return {
|
|
2287
|
+
courseID: r.doc.courseId,
|
|
2288
|
+
cardID: r.doc.cardId
|
|
2289
|
+
};
|
|
2290
|
+
});
|
|
2073
2291
|
}
|
|
2074
2292
|
async getActivityRecords() {
|
|
2075
2293
|
try {
|
|
@@ -2349,8 +2567,18 @@ Currently logged-in as ${this._username}.`
|
|
|
2349
2567
|
}
|
|
2350
2568
|
this.setDBandQ();
|
|
2351
2569
|
this.syncStrategy.startSync(this.localDB, this.remoteDB);
|
|
2352
|
-
|
|
2353
|
-
|
|
2570
|
+
this.applyDesignDocs().catch((error) => {
|
|
2571
|
+
log3(`Error in applyDesignDocs background task: ${error}`);
|
|
2572
|
+
if (error && typeof error === "object") {
|
|
2573
|
+
log3(`Full error details in applyDesignDocs: ${JSON.stringify(error)}`);
|
|
2574
|
+
}
|
|
2575
|
+
});
|
|
2576
|
+
this.deduplicateReviews().catch((error) => {
|
|
2577
|
+
log3(`Error in deduplicateReviews background task: ${error}`);
|
|
2578
|
+
if (error && typeof error === "object") {
|
|
2579
|
+
log3(`Full error details in background task: ${JSON.stringify(error)}`);
|
|
2580
|
+
}
|
|
2581
|
+
});
|
|
2354
2582
|
_BaseUser._initialized = true;
|
|
2355
2583
|
}
|
|
2356
2584
|
static designDocs = [
|
|
@@ -2368,10 +2596,15 @@ Currently logged-in as ${this._username}.`
|
|
|
2368
2596
|
}
|
|
2369
2597
|
];
|
|
2370
2598
|
async applyDesignDocs() {
|
|
2599
|
+
log3(`Starting applyDesignDocs for user: ${this._username}`);
|
|
2600
|
+
log3(`Remote DB name: ${this.remoteDB.name || "unknown"}`);
|
|
2371
2601
|
if (this._username === "admin") {
|
|
2602
|
+
log3("Skipping design docs for admin user");
|
|
2372
2603
|
return;
|
|
2373
2604
|
}
|
|
2605
|
+
log3(`Applying ${_BaseUser.designDocs.length} design docs`);
|
|
2374
2606
|
for (const doc of _BaseUser.designDocs) {
|
|
2607
|
+
log3(`Applying design doc: ${doc._id}`);
|
|
2375
2608
|
try {
|
|
2376
2609
|
try {
|
|
2377
2610
|
const existingDoc = await this.remoteDB.get(doc._id);
|
|
@@ -2448,17 +2681,21 @@ Currently logged-in as ${this._username}.`
|
|
|
2448
2681
|
} catch (e) {
|
|
2449
2682
|
const reason = e;
|
|
2450
2683
|
if (reason.status === 404) {
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2684
|
+
try {
|
|
2685
|
+
const initCardHistory = {
|
|
2686
|
+
_id: cardHistoryID,
|
|
2687
|
+
cardID: record.cardID,
|
|
2688
|
+
courseID: record.courseID,
|
|
2689
|
+
records: [record],
|
|
2690
|
+
lapses: 0,
|
|
2691
|
+
streak: 0,
|
|
2692
|
+
bestInterval: 0
|
|
2693
|
+
};
|
|
2694
|
+
const putResult = await this.writeDB.put(initCardHistory);
|
|
2695
|
+
return { ...initCardHistory, _rev: putResult.rev };
|
|
2696
|
+
} catch (creationError) {
|
|
2697
|
+
throw new Error(`Failed to create CardHistory for ${cardHistoryID}. Reason: ${creationError}`);
|
|
2698
|
+
}
|
|
2462
2699
|
} else {
|
|
2463
2700
|
throw new Error(`putCardRecord failed because of:
|
|
2464
2701
|
name:${reason.name}
|
|
@@ -2470,8 +2707,13 @@ Currently logged-in as ${this._username}.`
|
|
|
2470
2707
|
async deduplicateReviews() {
|
|
2471
2708
|
try {
|
|
2472
2709
|
log3("Starting deduplication of scheduled reviews...");
|
|
2710
|
+
log3(`Remote DB name: ${this.remoteDB.name || "unknown"}`);
|
|
2711
|
+
log3(`Write DB name: ${this.writeDB.name || "unknown"}`);
|
|
2473
2712
|
const reviewsMap = {};
|
|
2474
2713
|
const duplicateDocIds = [];
|
|
2714
|
+
log3(
|
|
2715
|
+
`Attempting to query remoteDB for reviewCards/reviewCards. Database: ${this.remoteDB.name || "unknown"}`
|
|
2716
|
+
);
|
|
2475
2717
|
const scheduledReviews = await this.remoteDB.query("reviewCards/reviewCards");
|
|
2476
2718
|
log3(`Found ${scheduledReviews.rows.length} scheduled reviews to process`);
|
|
2477
2719
|
scheduledReviews.rows.forEach((r) => {
|
|
@@ -2506,6 +2748,17 @@ Currently logged-in as ${this._username}.`
|
|
|
2506
2748
|
}
|
|
2507
2749
|
} catch (error) {
|
|
2508
2750
|
log3(`Error during review deduplication: ${error}`);
|
|
2751
|
+
if (error && typeof error === "object" && "status" in error && error.status === 404) {
|
|
2752
|
+
log3(
|
|
2753
|
+
`Database not found (404) during review deduplication. Database: ${this.remoteDB.name || "unknown"}`
|
|
2754
|
+
);
|
|
2755
|
+
log3(
|
|
2756
|
+
`This might indicate the user database doesn't exist or the reviewCards view isn't available`
|
|
2757
|
+
);
|
|
2758
|
+
}
|
|
2759
|
+
if (error && typeof error === "object") {
|
|
2760
|
+
log3(`Full error details: ${JSON.stringify(error)}`);
|
|
2761
|
+
}
|
|
2509
2762
|
}
|
|
2510
2763
|
}
|
|
2511
2764
|
/**
|
|
@@ -2698,7 +2951,7 @@ var init_adminDB2 = __esm({
|
|
|
2698
2951
|
constructor() {
|
|
2699
2952
|
this.usersDB = new pouchdb_setup_default(
|
|
2700
2953
|
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "_users",
|
|
2701
|
-
|
|
2954
|
+
createPouchDBConfig()
|
|
2702
2955
|
);
|
|
2703
2956
|
}
|
|
2704
2957
|
async getUsers() {
|
|
@@ -2760,9 +3013,10 @@ import fetch from "cross-fetch";
|
|
|
2760
3013
|
async function getCurrentSession() {
|
|
2761
3014
|
try {
|
|
2762
3015
|
if (ENV.COUCHDB_SERVER_URL === NOT_SET || ENV.COUCHDB_SERVER_PROTOCOL === NOT_SET) {
|
|
2763
|
-
throw new Error(
|
|
3016
|
+
throw new Error(`CouchDB server configuration not properly initialized. Protocol: "${ENV.COUCHDB_SERVER_PROTOCOL}", URL: "${ENV.COUCHDB_SERVER_URL}"`);
|
|
2764
3017
|
}
|
|
2765
|
-
const
|
|
3018
|
+
const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith("/") ? ENV.COUCHDB_SERVER_URL.slice(0, -1) : ENV.COUCHDB_SERVER_URL;
|
|
3019
|
+
const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;
|
|
2766
3020
|
logger.debug(`Attempting session check at: ${url}`);
|
|
2767
3021
|
const response = await fetch(url, {
|
|
2768
3022
|
method: "GET",
|
|
@@ -2774,8 +3028,10 @@ async function getCurrentSession() {
|
|
|
2774
3028
|
const resp = await response.json();
|
|
2775
3029
|
return resp;
|
|
2776
3030
|
} catch (error) {
|
|
2777
|
-
|
|
2778
|
-
|
|
3031
|
+
const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith("/") ? ENV.COUCHDB_SERVER_URL.slice(0, -1) : ENV.COUCHDB_SERVER_URL;
|
|
3032
|
+
const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;
|
|
3033
|
+
logger.error(`Session check error attempting to connect to: ${url} - ${error}`);
|
|
3034
|
+
throw new Error(`Session check failed connecting to ${url}: ${error}`);
|
|
2779
3035
|
}
|
|
2780
3036
|
}
|
|
2781
3037
|
async function getLoggedInUsername() {
|
|
@@ -2964,7 +3220,7 @@ var init_CouchDBSyncStrategy = __esm({
|
|
|
2964
3220
|
log4(`Fetching user database: ${dbName} (${username})`);
|
|
2965
3221
|
const ret = new pouchdb_setup_default(
|
|
2966
3222
|
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
2967
|
-
|
|
3223
|
+
createPouchDBConfig()
|
|
2968
3224
|
);
|
|
2969
3225
|
if (guestAccount) {
|
|
2970
3226
|
updateGuestAccountExpirationDate(ret);
|
|
@@ -2988,16 +3244,35 @@ function hexEncode2(str) {
|
|
|
2988
3244
|
}
|
|
2989
3245
|
return returnStr;
|
|
2990
3246
|
}
|
|
3247
|
+
function createPouchDBConfig() {
|
|
3248
|
+
const hasExplicitCredentials = ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD;
|
|
3249
|
+
const isNodeEnvironment = typeof window === "undefined";
|
|
3250
|
+
if (hasExplicitCredentials && isNodeEnvironment) {
|
|
3251
|
+
return {
|
|
3252
|
+
fetch(url, opts = {}) {
|
|
3253
|
+
const basicAuth = btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`);
|
|
3254
|
+
const headers = new Headers(opts.headers || {});
|
|
3255
|
+
headers.set("Authorization", `Basic ${basicAuth}`);
|
|
3256
|
+
const newOpts = {
|
|
3257
|
+
...opts,
|
|
3258
|
+
headers
|
|
3259
|
+
};
|
|
3260
|
+
return pouchdb_setup_default.fetch(url, newOpts);
|
|
3261
|
+
}
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
return pouchDBincludeCredentialsConfig;
|
|
3265
|
+
}
|
|
2991
3266
|
function getCouchDB(dbName) {
|
|
2992
3267
|
return new pouchdb_setup_default(
|
|
2993
3268
|
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
2994
|
-
|
|
3269
|
+
createPouchDBConfig()
|
|
2995
3270
|
);
|
|
2996
3271
|
}
|
|
2997
3272
|
function getCourseDB2(courseID) {
|
|
2998
3273
|
return new pouchdb_setup_default(
|
|
2999
3274
|
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "coursedb-" + courseID,
|
|
3000
|
-
|
|
3275
|
+
createPouchDBConfig()
|
|
3001
3276
|
);
|
|
3002
3277
|
}
|
|
3003
3278
|
async function getLatestVersion() {
|
|
@@ -3082,7 +3357,7 @@ function getCouchUserDB(username) {
|
|
|
3082
3357
|
log(`Fetching user database: ${dbName} (${username})`);
|
|
3083
3358
|
const ret = new pouchdb_setup_default(
|
|
3084
3359
|
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
3085
|
-
|
|
3360
|
+
createPouchDBConfig()
|
|
3086
3361
|
);
|
|
3087
3362
|
if (guestAccount) {
|
|
3088
3363
|
updateGuestAccountExpirationDate2(ret);
|
|
@@ -3161,6 +3436,7 @@ export {
|
|
|
3161
3436
|
TeacherClassroomDB,
|
|
3162
3437
|
addNote55,
|
|
3163
3438
|
addTagToCard,
|
|
3439
|
+
createPouchDBConfig,
|
|
3164
3440
|
createTag,
|
|
3165
3441
|
deleteTag,
|
|
3166
3442
|
filterAllDocsByPrefix,
|
|
@@ -3187,7 +3463,6 @@ export {
|
|
|
3187
3463
|
hexEncode2 as hexEncode,
|
|
3188
3464
|
isReview,
|
|
3189
3465
|
localUserDB,
|
|
3190
|
-
pouchDBincludeCredentialsConfig,
|
|
3191
3466
|
removeTagFromCard,
|
|
3192
3467
|
scheduleCardReview,
|
|
3193
3468
|
updateCardElo2 as updateCardElo,
|