@vue-skuilder/db 0.1.6 → 0.1.8-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{SyncStrategy-DnJRj-Xp.d.mts → SyncStrategy-CyATpyLQ.d.mts} +6 -0
- package/dist/{SyncStrategy-DnJRj-Xp.d.ts → SyncStrategy-CyATpyLQ.d.ts} +6 -0
- package/dist/core/index.d.mts +5 -5
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +825 -762
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +812 -750
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-BZmLyBVw.d.mts → dataLayerProvider-BInqI_RF.d.mts} +1 -1
- package/dist/{dataLayerProvider-BuntXkCs.d.ts → dataLayerProvider-DqtNroSh.d.ts} +1 -1
- package/dist/impl/couch/index.d.mts +6 -6
- package/dist/impl/couch/index.d.ts +6 -6
- package/dist/impl/couch/index.js +2261 -2081
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +2274 -2095
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.mts +8 -6
- package/dist/impl/static/index.d.ts +8 -6
- package/dist/impl/static/index.js +524 -1064
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +515 -1058
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index-CLL31bEy.d.ts +137 -0
- package/dist/index-CUNnL38E.d.mts +137 -0
- package/dist/index.d.mts +200 -9
- package/dist/index.d.ts +200 -9
- package/dist/index.js +4123 -2820
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4119 -2830
- package/dist/index.mjs.map +1 -1
- package/dist/{types-D6SnlHPm.d.ts → types-BefDGkKa.d.ts} +1 -1
- package/dist/{types-DPRvCrIk.d.mts → types-DC-ckZug.d.mts} +1 -1
- package/dist/{types-legacy-WPe8CtO-.d.mts → types-legacy-Birv-Jx6.d.mts} +2 -2
- package/dist/{types-legacy-WPe8CtO-.d.ts → types-legacy-Birv-Jx6.d.ts} +2 -2
- package/dist/{userDB-D9EuWTp1.d.ts → userDB-C33Hzjgn.d.mts} +11 -4
- package/dist/{userDB-31gsvxyd.d.mts → userDB-DusL7OXe.d.ts} +11 -4
- package/dist/util/packer/index.d.mts +3 -63
- package/dist/util/packer/index.d.ts +3 -63
- package/dist/util/packer/index.js +53 -1
- package/dist/util/packer/index.js.map +1 -1
- package/dist/util/packer/index.mjs +53 -1
- package/dist/util/packer/index.mjs.map +1 -1
- package/package.json +7 -4
- package/src/core/types/types-legacy.ts +13 -1
- package/src/core/types/user.ts +9 -2
- package/src/core/util/index.ts +5 -4
- package/src/factory.ts +25 -0
- package/src/impl/common/BaseUserDB.ts +62 -28
- package/src/impl/common/SyncStrategy.ts +7 -0
- package/src/impl/common/index.ts +0 -1
- package/src/impl/common/userDBHelpers.ts +15 -5
- package/src/impl/couch/CouchDBSyncStrategy.ts +10 -0
- package/src/impl/couch/courseAPI.ts +7 -6
- package/src/impl/couch/courseLookupDB.ts +24 -0
- package/src/impl/couch/index.ts +10 -5
- package/src/impl/couch/updateQueue.ts +12 -8
- package/src/impl/couch/user-course-relDB.ts +17 -27
- package/src/impl/static/NoOpSyncStrategy.ts +5 -0
- package/src/impl/static/StaticDataUnpacker.ts +18 -36
- package/src/impl/static/courseDB.ts +135 -17
- package/src/util/dataDirectory.test.ts +53 -0
- package/src/util/dataDirectory.ts +52 -0
- package/src/util/index.ts +3 -0
- package/src/util/migrator/FileSystemAdapter.ts +79 -0
- package/src/util/migrator/StaticToCouchDBMigrator.ts +713 -0
- package/src/util/migrator/index.ts +18 -0
- package/src/util/migrator/types.ts +84 -0
- package/src/util/migrator/validation.ts +517 -0
- package/src/util/packer/CouchDBToStaticPacker.ts +92 -2
- package/src/util/tuiLogger.ts +139 -0
package/dist/core/index.js
CHANGED
|
@@ -5,10 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __glob = (map) => (
|
|
9
|
-
var fn = map[
|
|
8
|
+
var __glob = (map) => (path2) => {
|
|
9
|
+
var fn = map[path2];
|
|
10
10
|
if (fn) return fn();
|
|
11
|
-
throw new Error("Module not found in bundle: " +
|
|
11
|
+
throw new Error("Module not found in bundle: " + path2);
|
|
12
12
|
};
|
|
13
13
|
var __esm = (fn, res) => function __init() {
|
|
14
14
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
@@ -49,6 +49,13 @@ var init_classroomDB = __esm({
|
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
+
// src/impl/common/SyncStrategy.ts
|
|
53
|
+
var init_SyncStrategy = __esm({
|
|
54
|
+
"src/impl/common/SyncStrategy.ts"() {
|
|
55
|
+
"use strict";
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
52
59
|
// src/util/logger.ts
|
|
53
60
|
var isDevelopment, logger;
|
|
54
61
|
var init_logger = __esm({
|
|
@@ -94,23 +101,78 @@ var init_logger = __esm({
|
|
|
94
101
|
}
|
|
95
102
|
});
|
|
96
103
|
|
|
97
|
-
// src/
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
return dataLayerInstance;
|
|
103
|
-
}
|
|
104
|
-
var ENV, dataLayerInstance;
|
|
105
|
-
var init_factory = __esm({
|
|
106
|
-
"src/factory.ts"() {
|
|
104
|
+
// src/core/types/types-legacy.ts
|
|
105
|
+
var GuestUsername, log, DocType, DocTypePrefixes;
|
|
106
|
+
var init_types_legacy = __esm({
|
|
107
|
+
"src/core/types/types-legacy.ts"() {
|
|
107
108
|
"use strict";
|
|
108
109
|
init_logger();
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
GuestUsername = "Guest";
|
|
111
|
+
log = (message) => {
|
|
112
|
+
logger.log(message);
|
|
112
113
|
};
|
|
113
|
-
|
|
114
|
+
DocType = /* @__PURE__ */ ((DocType2) => {
|
|
115
|
+
DocType2["DISPLAYABLE_DATA"] = "DISPLAYABLE_DATA";
|
|
116
|
+
DocType2["CARD"] = "CARD";
|
|
117
|
+
DocType2["DATASHAPE"] = "DATASHAPE";
|
|
118
|
+
DocType2["QUESTIONTYPE"] = "QUESTION";
|
|
119
|
+
DocType2["VIEW"] = "VIEW";
|
|
120
|
+
DocType2["PEDAGOGY"] = "PEDAGOGY";
|
|
121
|
+
DocType2["CARDRECORD"] = "CARDRECORD";
|
|
122
|
+
DocType2["SCHEDULED_CARD"] = "SCHEDULED_CARD";
|
|
123
|
+
DocType2["TAG"] = "TAG";
|
|
124
|
+
DocType2["NAVIGATION_STRATEGY"] = "NAVIGATION_STRATEGY";
|
|
125
|
+
return DocType2;
|
|
126
|
+
})(DocType || {});
|
|
127
|
+
DocTypePrefixes = {
|
|
128
|
+
["CARD" /* CARD */]: "c",
|
|
129
|
+
["DISPLAYABLE_DATA" /* DISPLAYABLE_DATA */]: "dd",
|
|
130
|
+
["TAG" /* TAG */]: "TAG",
|
|
131
|
+
["CARDRECORD" /* CARDRECORD */]: "cardH",
|
|
132
|
+
["SCHEDULED_CARD" /* SCHEDULED_CARD */]: "card_review_",
|
|
133
|
+
// Add other doctypes here as they get prefixed IDs
|
|
134
|
+
["DATASHAPE" /* DATASHAPE */]: "DATASHAPE",
|
|
135
|
+
["QUESTION" /* QUESTIONTYPE */]: "QUESTION",
|
|
136
|
+
["VIEW" /* VIEW */]: "VIEW",
|
|
137
|
+
["PEDAGOGY" /* PEDAGOGY */]: "PEDAGOGY",
|
|
138
|
+
["NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */]: "NAVIGATION_STRATEGY"
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// src/core/util/index.ts
|
|
144
|
+
function areQuestionRecords(h) {
|
|
145
|
+
return isQuestionRecord(h.records[0]);
|
|
146
|
+
}
|
|
147
|
+
function isQuestionRecord(c) {
|
|
148
|
+
return c.userAnswer !== void 0;
|
|
149
|
+
}
|
|
150
|
+
function getCardHistoryID(courseID, cardID) {
|
|
151
|
+
return `${DocTypePrefixes["CARDRECORD" /* CARDRECORD */]}-${courseID}-${cardID}`;
|
|
152
|
+
}
|
|
153
|
+
function parseCardHistoryID(id) {
|
|
154
|
+
const split = id.split("-");
|
|
155
|
+
let error = "";
|
|
156
|
+
error += split.length === 3 ? "" : `
|
|
157
|
+
given ID has incorrect number of '-' characters`;
|
|
158
|
+
error += split[0] === DocTypePrefixes["CARDRECORD" /* CARDRECORD */] ? "" : `
|
|
159
|
+
given ID does not start with ${DocTypePrefixes["CARDRECORD" /* CARDRECORD */]}`;
|
|
160
|
+
if (split.length === 3 && split[0] === DocTypePrefixes["CARDRECORD" /* CARDRECORD */]) {
|
|
161
|
+
return {
|
|
162
|
+
courseID: split[1],
|
|
163
|
+
cardID: split[2]
|
|
164
|
+
};
|
|
165
|
+
} else {
|
|
166
|
+
throw new Error("parseCardHistory Error:" + error);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function docIsDeleted(e) {
|
|
170
|
+
return Boolean(e?.error === "not_found" && e?.reason === "deleted");
|
|
171
|
+
}
|
|
172
|
+
var init_util = __esm({
|
|
173
|
+
"src/core/util/index.ts"() {
|
|
174
|
+
"use strict";
|
|
175
|
+
init_types_legacy();
|
|
114
176
|
}
|
|
115
177
|
});
|
|
116
178
|
|
|
@@ -133,54 +195,94 @@ var init_pouchdb_setup = __esm({
|
|
|
133
195
|
}
|
|
134
196
|
});
|
|
135
197
|
|
|
136
|
-
// src/
|
|
137
|
-
var
|
|
138
|
-
|
|
139
|
-
"src/core/types/types-legacy.ts"() {
|
|
198
|
+
// src/util/tuiLogger.ts
|
|
199
|
+
var init_tuiLogger = __esm({
|
|
200
|
+
"src/util/tuiLogger.ts"() {
|
|
140
201
|
"use strict";
|
|
141
|
-
|
|
142
|
-
GuestUsername = "Guest";
|
|
143
|
-
log = (message) => {
|
|
144
|
-
logger.log(message);
|
|
145
|
-
};
|
|
146
|
-
DocType = /* @__PURE__ */ ((DocType2) => {
|
|
147
|
-
DocType2["DISPLAYABLE_DATA"] = "DISPLAYABLE_DATA";
|
|
148
|
-
DocType2["CARD"] = "CARD";
|
|
149
|
-
DocType2["DATASHAPE"] = "DATASHAPE";
|
|
150
|
-
DocType2["QUESTIONTYPE"] = "QUESTION";
|
|
151
|
-
DocType2["VIEW"] = "VIEW";
|
|
152
|
-
DocType2["PEDAGOGY"] = "PEDAGOGY";
|
|
153
|
-
DocType2["CARDRECORD"] = "CARDRECORD";
|
|
154
|
-
DocType2["SCHEDULED_CARD"] = "SCHEDULED_CARD";
|
|
155
|
-
DocType2["TAG"] = "TAG";
|
|
156
|
-
DocType2["NAVIGATION_STRATEGY"] = "NAVIGATION_STRATEGY";
|
|
157
|
-
return DocType2;
|
|
158
|
-
})(DocType || {});
|
|
159
|
-
cardHistoryPrefix = "cardH";
|
|
202
|
+
init_dataDirectory();
|
|
160
203
|
}
|
|
161
204
|
});
|
|
162
205
|
|
|
163
|
-
// src/
|
|
164
|
-
|
|
165
|
-
"
|
|
206
|
+
// src/util/dataDirectory.ts
|
|
207
|
+
function getAppDataDirectory() {
|
|
208
|
+
return path.join(os.homedir(), ".tuilder");
|
|
209
|
+
}
|
|
210
|
+
function getDbPath(dbName) {
|
|
211
|
+
return path.join(getAppDataDirectory(), dbName);
|
|
212
|
+
}
|
|
213
|
+
var path, os;
|
|
214
|
+
var init_dataDirectory = __esm({
|
|
215
|
+
"src/util/dataDirectory.ts"() {
|
|
166
216
|
"use strict";
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
logger.debug(`COURSELOOKUP FILE RUNNING`);
|
|
217
|
+
path = __toESM(require("path"));
|
|
218
|
+
os = __toESM(require("os"));
|
|
219
|
+
init_tuiLogger();
|
|
171
220
|
}
|
|
172
221
|
});
|
|
173
222
|
|
|
174
|
-
// src/impl/
|
|
175
|
-
|
|
176
|
-
|
|
223
|
+
// src/impl/common/userDBHelpers.ts
|
|
224
|
+
function filterAllDocsByPrefix(db, prefix, opts) {
|
|
225
|
+
const options = {
|
|
226
|
+
startkey: prefix,
|
|
227
|
+
endkey: prefix + "\uFFF0",
|
|
228
|
+
include_docs: true
|
|
229
|
+
};
|
|
230
|
+
if (opts) {
|
|
231
|
+
Object.assign(options, opts);
|
|
232
|
+
}
|
|
233
|
+
return db.allDocs(options);
|
|
234
|
+
}
|
|
235
|
+
function getStartAndEndKeys(key) {
|
|
236
|
+
return {
|
|
237
|
+
startkey: key,
|
|
238
|
+
endkey: key + "\uFFF0"
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function getLocalUserDB(username) {
|
|
242
|
+
const dbName = `userdb-${username}`;
|
|
243
|
+
if (typeof window === "undefined") {
|
|
244
|
+
return new pouchdb_setup_default(getDbPath(dbName), {});
|
|
245
|
+
} else {
|
|
246
|
+
return new pouchdb_setup_default(dbName, {});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function scheduleCardReviewLocal(userDB, review) {
|
|
250
|
+
const now = import_moment.default.utc();
|
|
251
|
+
logger.info(`Scheduling for review in: ${review.time.diff(now, "h") / 24} days`);
|
|
252
|
+
void userDB.put({
|
|
253
|
+
_id: DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */] + review.time.format(REVIEW_TIME_FORMAT),
|
|
254
|
+
cardId: review.card_id,
|
|
255
|
+
reviewTime: review.time.toISOString(),
|
|
256
|
+
courseId: review.course_id,
|
|
257
|
+
scheduledAt: now.toISOString(),
|
|
258
|
+
scheduledFor: review.scheduledFor,
|
|
259
|
+
schedulingAgentId: review.schedulingAgentId
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
async function removeScheduledCardReviewLocal(userDB, reviewDocID) {
|
|
263
|
+
const reviewDoc = await userDB.get(reviewDocID);
|
|
264
|
+
userDB.remove(reviewDoc).then((res) => {
|
|
265
|
+
if (res.ok) {
|
|
266
|
+
log2(`Removed Review Doc: ${reviewDocID}`);
|
|
267
|
+
}
|
|
268
|
+
}).catch((err) => {
|
|
269
|
+
log2(`Failed to remove Review Doc: ${reviewDocID},
|
|
270
|
+
${JSON.stringify(err)}`);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
var import_moment, REVIEW_TIME_FORMAT, log2;
|
|
274
|
+
var init_userDBHelpers = __esm({
|
|
275
|
+
"src/impl/common/userDBHelpers.ts"() {
|
|
177
276
|
"use strict";
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
init_couch();
|
|
181
|
-
init_classroomDB2();
|
|
182
|
-
init_courseLookupDB();
|
|
277
|
+
import_moment = __toESM(require("moment"));
|
|
278
|
+
init_core();
|
|
183
279
|
init_logger();
|
|
280
|
+
init_pouchdb_setup();
|
|
281
|
+
init_dataDirectory();
|
|
282
|
+
REVIEW_TIME_FORMAT = "YYYY-MM-DD--kk:mm:ss-SSS";
|
|
283
|
+
log2 = (s) => {
|
|
284
|
+
logger.info(s);
|
|
285
|
+
};
|
|
184
286
|
}
|
|
185
287
|
});
|
|
186
288
|
|
|
@@ -211,7 +313,10 @@ var init_updateQueue = __esm({
|
|
|
211
313
|
_className = "UpdateQueue";
|
|
212
314
|
pendingUpdates = {};
|
|
213
315
|
inprogressUpdates = {};
|
|
214
|
-
|
|
316
|
+
readDB;
|
|
317
|
+
// Database for read operations
|
|
318
|
+
writeDB;
|
|
319
|
+
// Database for write operations (local-first)
|
|
215
320
|
update(id, update) {
|
|
216
321
|
logger.debug(`Update requested on doc: ${id}`);
|
|
217
322
|
if (this.pendingUpdates[id]) {
|
|
@@ -221,24 +326,25 @@ var init_updateQueue = __esm({
|
|
|
221
326
|
}
|
|
222
327
|
return this.applyUpdates(id);
|
|
223
328
|
}
|
|
224
|
-
constructor(
|
|
329
|
+
constructor(readDB, writeDB) {
|
|
225
330
|
super();
|
|
226
|
-
this.
|
|
331
|
+
this.readDB = readDB;
|
|
332
|
+
this.writeDB = writeDB || readDB;
|
|
227
333
|
logger.debug(`UpdateQ initialized...`);
|
|
228
|
-
void this.
|
|
334
|
+
void this.readDB.info().then((i) => {
|
|
229
335
|
logger.debug(`db info: ${JSON.stringify(i)}`);
|
|
230
336
|
});
|
|
231
337
|
}
|
|
232
338
|
async applyUpdates(id) {
|
|
233
339
|
logger.debug(`Applying updates on doc: ${id}`);
|
|
234
340
|
if (this.inprogressUpdates[id]) {
|
|
235
|
-
await this.
|
|
341
|
+
await this.readDB.info();
|
|
236
342
|
return this.applyUpdates(id);
|
|
237
343
|
} else {
|
|
238
344
|
if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {
|
|
239
345
|
this.inprogressUpdates[id] = true;
|
|
240
346
|
try {
|
|
241
|
-
let doc = await this.
|
|
347
|
+
let doc = await this.readDB.get(id);
|
|
242
348
|
logger.debug(`Retrieved doc: ${id}`);
|
|
243
349
|
while (this.pendingUpdates[id].length !== 0) {
|
|
244
350
|
const update = this.pendingUpdates[id].splice(0, 1)[0];
|
|
@@ -251,7 +357,7 @@ var init_updateQueue = __esm({
|
|
|
251
357
|
};
|
|
252
358
|
}
|
|
253
359
|
}
|
|
254
|
-
await this.
|
|
360
|
+
await this.writeDB.put(doc);
|
|
255
361
|
logger.debug(`Put doc: ${id}`);
|
|
256
362
|
if (this.pendingUpdates[id].length === 0) {
|
|
257
363
|
this.inprogressUpdates[id] = false;
|
|
@@ -277,6 +383,60 @@ var init_updateQueue = __esm({
|
|
|
277
383
|
}
|
|
278
384
|
});
|
|
279
385
|
|
|
386
|
+
// src/impl/couch/user-course-relDB.ts
|
|
387
|
+
var import_moment2, UsrCrsData;
|
|
388
|
+
var init_user_course_relDB = __esm({
|
|
389
|
+
"src/impl/couch/user-course-relDB.ts"() {
|
|
390
|
+
"use strict";
|
|
391
|
+
import_moment2 = __toESM(require("moment"));
|
|
392
|
+
init_logger();
|
|
393
|
+
UsrCrsData = class {
|
|
394
|
+
user;
|
|
395
|
+
_courseId;
|
|
396
|
+
constructor(user, courseId) {
|
|
397
|
+
this.user = user;
|
|
398
|
+
this._courseId = courseId;
|
|
399
|
+
}
|
|
400
|
+
async getReviewsForcast(daysCount) {
|
|
401
|
+
const time = import_moment2.default.utc().add(daysCount, "days");
|
|
402
|
+
return this.getReviewstoDate(time);
|
|
403
|
+
}
|
|
404
|
+
async getPendingReviews() {
|
|
405
|
+
const now = import_moment2.default.utc();
|
|
406
|
+
return this.getReviewstoDate(now);
|
|
407
|
+
}
|
|
408
|
+
async getScheduledReviewCount() {
|
|
409
|
+
return (await this.getPendingReviews()).length;
|
|
410
|
+
}
|
|
411
|
+
async getCourseSettings() {
|
|
412
|
+
const regDoc = await this.user.getCourseRegistrationsDoc();
|
|
413
|
+
const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);
|
|
414
|
+
if (crsDoc && crsDoc.settings) {
|
|
415
|
+
return crsDoc.settings;
|
|
416
|
+
} else {
|
|
417
|
+
logger.warn(`no settings found during lookup on course ${this._courseId}`);
|
|
418
|
+
return {};
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
updateCourseSettings(updates) {
|
|
422
|
+
if ("updateCourseSettings" in this.user) {
|
|
423
|
+
void this.user.updateCourseSettings(this._courseId, updates);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
async getReviewstoDate(targetDate) {
|
|
427
|
+
const allReviews = await this.user.getPendingReviews(this._courseId);
|
|
428
|
+
logger.debug(
|
|
429
|
+
`Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`
|
|
430
|
+
);
|
|
431
|
+
return allReviews.filter((review) => {
|
|
432
|
+
const reviewTime = import_moment2.default.utc(review.reviewTime);
|
|
433
|
+
return targetDate.isAfter(reviewTime);
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
|
|
280
440
|
// src/impl/couch/clientCache.ts
|
|
281
441
|
async function GET_CACHED(k, f) {
|
|
282
442
|
if (CLIENT_CACHE[k]) {
|
|
@@ -296,34 +456,240 @@ var init_clientCache = __esm({
|
|
|
296
456
|
}
|
|
297
457
|
});
|
|
298
458
|
|
|
299
|
-
// src/
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
459
|
+
// src/impl/couch/courseAPI.ts
|
|
460
|
+
async function addNote55(courseID, codeCourse, shape, data, author, tags, uploads, elo = (0, import_common2.blankCourseElo)()) {
|
|
461
|
+
const db = getCourseDB(courseID);
|
|
462
|
+
const payload = (0, import_common3.prepareNote55)(courseID, codeCourse, shape, data, author, tags, uploads);
|
|
463
|
+
const _id = `${DocTypePrefixes["DISPLAYABLE_DATA" /* DISPLAYABLE_DATA */]}-${(0, import_uuid.v4)()}`;
|
|
464
|
+
const result = await db.put({ ...payload, _id });
|
|
465
|
+
const dataShapeId = import_common.NameSpacer.getDataShapeString({
|
|
466
|
+
course: codeCourse,
|
|
467
|
+
dataShape: shape.name
|
|
468
|
+
});
|
|
469
|
+
if (result.ok) {
|
|
470
|
+
try {
|
|
471
|
+
await createCards(courseID, dataShapeId, result.id, tags, elo, author);
|
|
472
|
+
} catch (error) {
|
|
473
|
+
let errorMessage = "Unknown error";
|
|
474
|
+
if (error instanceof Error) {
|
|
475
|
+
errorMessage = error.message;
|
|
476
|
+
} else if (error && typeof error === "object" && "reason" in error) {
|
|
477
|
+
errorMessage = error.reason;
|
|
478
|
+
} else if (error && typeof error === "object" && "message" in error) {
|
|
479
|
+
errorMessage = error.message;
|
|
480
|
+
} else {
|
|
481
|
+
errorMessage = String(error);
|
|
316
482
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
483
|
+
logger.error(`[addNote55] Failed to create cards for note ${result.id}: ${errorMessage}`);
|
|
484
|
+
result.cardCreationFailed = true;
|
|
485
|
+
result.cardCreationError = errorMessage;
|
|
486
|
+
}
|
|
487
|
+
} else {
|
|
488
|
+
logger.error(`[addNote55] Error adding note. Result: ${JSON.stringify(result)}`);
|
|
489
|
+
}
|
|
490
|
+
return result;
|
|
491
|
+
}
|
|
492
|
+
async function createCards(courseID, datashapeID, noteID, tags, elo = (0, import_common2.blankCourseElo)(), author) {
|
|
493
|
+
const cfg = await getCredentialledCourseConfig(courseID);
|
|
494
|
+
const dsDescriptor = import_common.NameSpacer.getDataShapeDescriptor(datashapeID);
|
|
495
|
+
let questionViewTypes = [];
|
|
496
|
+
for (const ds of cfg.dataShapes) {
|
|
497
|
+
if (ds.name === datashapeID) {
|
|
498
|
+
questionViewTypes = ds.questionTypes;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (questionViewTypes.length === 0) {
|
|
502
|
+
const errorMsg = `No questionViewTypes found for datashapeID: ${datashapeID} in course config. Cards cannot be created.`;
|
|
503
|
+
logger.error(errorMsg);
|
|
504
|
+
throw new Error(errorMsg);
|
|
505
|
+
}
|
|
506
|
+
for (const questionView of questionViewTypes) {
|
|
507
|
+
await createCard(questionView, courseID, dsDescriptor, noteID, tags, elo, author);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
async function createCard(questionViewName, courseID, dsDescriptor, noteID, tags, elo = (0, import_common2.blankCourseElo)(), author) {
|
|
511
|
+
const qDescriptor = import_common.NameSpacer.getQuestionDescriptor(questionViewName);
|
|
512
|
+
const cfg = await getCredentialledCourseConfig(courseID);
|
|
513
|
+
for (const rQ of cfg.questionTypes) {
|
|
514
|
+
if (rQ.name === questionViewName) {
|
|
515
|
+
for (const view of rQ.viewList) {
|
|
516
|
+
await addCard(
|
|
517
|
+
courseID,
|
|
518
|
+
dsDescriptor.course,
|
|
519
|
+
[noteID],
|
|
520
|
+
import_common.NameSpacer.getViewString({
|
|
521
|
+
course: qDescriptor.course,
|
|
522
|
+
questionType: qDescriptor.questionType,
|
|
523
|
+
view
|
|
524
|
+
}),
|
|
525
|
+
elo,
|
|
526
|
+
tags,
|
|
527
|
+
author
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
async function addCard(courseID, course, id_displayable_data, id_view, elo, tags, author) {
|
|
534
|
+
const db = getCourseDB(courseID);
|
|
535
|
+
const _id = `${DocTypePrefixes["CARD" /* CARD */]}-${(0, import_uuid.v4)()}`;
|
|
536
|
+
const card = await db.put({
|
|
537
|
+
_id,
|
|
538
|
+
course,
|
|
539
|
+
id_displayable_data,
|
|
540
|
+
id_view,
|
|
541
|
+
docType: "CARD" /* CARD */,
|
|
542
|
+
elo: elo || (0, import_common2.toCourseElo)(990 + Math.round(20 * Math.random())),
|
|
543
|
+
author
|
|
544
|
+
});
|
|
545
|
+
for (const tag of tags) {
|
|
546
|
+
logger.info(`adding tag: ${tag} to card ${card.id}`);
|
|
547
|
+
await addTagToCard(courseID, card.id, tag, author, false);
|
|
548
|
+
}
|
|
549
|
+
return card;
|
|
550
|
+
}
|
|
551
|
+
async function getCredentialledCourseConfig(courseID) {
|
|
552
|
+
try {
|
|
553
|
+
const db = getCourseDB(courseID);
|
|
554
|
+
const ret = await db.get("CourseConfig");
|
|
555
|
+
ret.courseID = courseID;
|
|
556
|
+
logger.info(`Returning course config: ${JSON.stringify(ret)}`);
|
|
557
|
+
return ret;
|
|
558
|
+
} catch (e) {
|
|
559
|
+
logger.error(`Error fetching config for ${courseID}:`, e);
|
|
560
|
+
throw e;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
async function addTagToCard(courseID, cardID, tagID, author, updateELO = true) {
|
|
564
|
+
const prefixedTagID = getTagID(tagID);
|
|
565
|
+
const courseDB = getCourseDB(courseID);
|
|
566
|
+
const courseApi = new CourseDB(courseID, async () => {
|
|
567
|
+
const dummySyncStrategy = {
|
|
568
|
+
setupRemoteDB: () => null,
|
|
569
|
+
startSync: () => {
|
|
570
|
+
},
|
|
571
|
+
canCreateAccount: () => false,
|
|
572
|
+
canAuthenticate: () => false,
|
|
573
|
+
getCurrentUsername: async () => "DummyUser"
|
|
574
|
+
};
|
|
575
|
+
return BaseUser.Dummy(dummySyncStrategy);
|
|
576
|
+
});
|
|
577
|
+
try {
|
|
578
|
+
logger.info(`Applying tag ${tagID} to card ${courseID + "-" + cardID}...`);
|
|
579
|
+
const tag = await courseDB.get(prefixedTagID);
|
|
580
|
+
if (!tag.taggedCards.includes(cardID)) {
|
|
581
|
+
tag.taggedCards.push(cardID);
|
|
582
|
+
if (updateELO) {
|
|
583
|
+
try {
|
|
584
|
+
const eloData = await courseApi.getCardEloData([cardID]);
|
|
585
|
+
const elo = eloData[0];
|
|
586
|
+
elo.tags[tagID] = {
|
|
587
|
+
count: 0,
|
|
588
|
+
score: elo.global.score
|
|
589
|
+
// todo: or 1000?
|
|
590
|
+
};
|
|
591
|
+
await updateCardElo(courseID, cardID, elo);
|
|
592
|
+
} catch (error) {
|
|
593
|
+
logger.error("Failed to update ELO data for card:", cardID, error);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return courseDB.put(tag);
|
|
597
|
+
} else throw new AlreadyTaggedErr(`Card ${cardID} is already tagged with ${tagID}`);
|
|
598
|
+
} catch (e) {
|
|
599
|
+
if (e instanceof AlreadyTaggedErr) {
|
|
600
|
+
throw e;
|
|
601
|
+
}
|
|
602
|
+
await createTag(courseID, tagID, author);
|
|
603
|
+
return addTagToCard(courseID, cardID, tagID, author, updateELO);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
async function updateCardElo(courseID, cardID, elo) {
|
|
607
|
+
if (elo) {
|
|
608
|
+
const cDB = getCourseDB(courseID);
|
|
609
|
+
const card = await cDB.get(cardID);
|
|
610
|
+
logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);
|
|
611
|
+
card.elo = elo;
|
|
612
|
+
return cDB.put(card);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function getTagID(tagName) {
|
|
616
|
+
const tagPrefix = "TAG" /* TAG */.valueOf() + "-";
|
|
617
|
+
if (tagName.indexOf(tagPrefix) === 0) {
|
|
618
|
+
return tagName;
|
|
619
|
+
} else {
|
|
620
|
+
return tagPrefix + tagName;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function getCourseDB(courseID) {
|
|
624
|
+
const dbName = `coursedb-${courseID}`;
|
|
625
|
+
return new pouchdb_setup_default(
|
|
626
|
+
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
627
|
+
pouchDBincludeCredentialsConfig
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
var import_common, import_common2, import_common3, import_uuid, AlreadyTaggedErr;
|
|
631
|
+
var init_courseAPI = __esm({
|
|
632
|
+
"src/impl/couch/courseAPI.ts"() {
|
|
633
|
+
"use strict";
|
|
634
|
+
init_pouchdb_setup();
|
|
635
|
+
init_couch();
|
|
636
|
+
init_factory();
|
|
637
|
+
import_common = require("@vue-skuilder/common");
|
|
638
|
+
import_common2 = require("@vue-skuilder/common");
|
|
639
|
+
init_courseDB();
|
|
640
|
+
init_types_legacy();
|
|
641
|
+
import_common3 = require("@vue-skuilder/common");
|
|
642
|
+
init_common();
|
|
643
|
+
init_logger();
|
|
644
|
+
import_uuid = require("uuid");
|
|
645
|
+
AlreadyTaggedErr = class extends Error {
|
|
646
|
+
constructor(message) {
|
|
647
|
+
super(message);
|
|
648
|
+
this.name = "AlreadyTaggedErr";
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
// src/impl/couch/courseLookupDB.ts
|
|
655
|
+
var init_courseLookupDB = __esm({
|
|
656
|
+
"src/impl/couch/courseLookupDB.ts"() {
|
|
657
|
+
"use strict";
|
|
658
|
+
init_pouchdb_setup();
|
|
659
|
+
init_factory();
|
|
660
|
+
init_logger();
|
|
661
|
+
logger.debug(`COURSELOOKUP FILE RUNNING`);
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// src/core/navigators/elo.ts
|
|
666
|
+
var elo_exports = {};
|
|
667
|
+
__export(elo_exports, {
|
|
668
|
+
default: () => ELONavigator
|
|
669
|
+
});
|
|
670
|
+
var ELONavigator;
|
|
671
|
+
var init_elo = __esm({
|
|
672
|
+
"src/core/navigators/elo.ts"() {
|
|
673
|
+
"use strict";
|
|
674
|
+
init_navigators();
|
|
675
|
+
ELONavigator = class extends ContentNavigator {
|
|
676
|
+
user;
|
|
677
|
+
course;
|
|
678
|
+
constructor(user, course) {
|
|
679
|
+
super();
|
|
680
|
+
this.user = user;
|
|
681
|
+
this.course = course;
|
|
682
|
+
}
|
|
683
|
+
async getPendingReviews() {
|
|
684
|
+
const reviews = await this.user.getPendingReviews(this.course.getCourseID());
|
|
685
|
+
const elo = await this.course.getCardEloData(reviews.map((r) => r.cardId));
|
|
686
|
+
const ratedReviews = reviews.map((r, i) => {
|
|
687
|
+
const ratedR = {
|
|
688
|
+
...r,
|
|
689
|
+
...elo[i]
|
|
690
|
+
};
|
|
691
|
+
return ratedR;
|
|
692
|
+
});
|
|
327
693
|
ratedReviews.sort((a, b) => {
|
|
328
694
|
return a.global.score - b.global.score;
|
|
329
695
|
});
|
|
@@ -421,8 +787,8 @@ function randIntWeightedTowardZero(n) {
|
|
|
421
787
|
}
|
|
422
788
|
async function getCourseTagStubs(courseID) {
|
|
423
789
|
logger.debug(`Getting tag stubs for course: ${courseID}`);
|
|
424
|
-
const stubs = await
|
|
425
|
-
|
|
790
|
+
const stubs = await filterAllDocsByPrefix2(
|
|
791
|
+
getCourseDB2(courseID),
|
|
426
792
|
"TAG" /* TAG */.valueOf() + "-"
|
|
427
793
|
);
|
|
428
794
|
stubs.rows.forEach((row) => {
|
|
@@ -433,7 +799,7 @@ async function getCourseTagStubs(courseID) {
|
|
|
433
799
|
async function createTag(courseID, tagName, author) {
|
|
434
800
|
logger.debug(`Creating tag: ${tagName}...`);
|
|
435
801
|
const tagID = getTagID(tagName);
|
|
436
|
-
const courseDB =
|
|
802
|
+
const courseDB = getCourseDB2(courseID);
|
|
437
803
|
const resp = await courseDB.put({
|
|
438
804
|
course: courseID,
|
|
439
805
|
docType: "TAG" /* TAG */,
|
|
@@ -448,19 +814,19 @@ async function createTag(courseID, tagName, author) {
|
|
|
448
814
|
}
|
|
449
815
|
async function updateTag(tag) {
|
|
450
816
|
const prior = await getTag(tag.course, tag.name);
|
|
451
|
-
return await
|
|
817
|
+
return await getCourseDB2(tag.course).put({
|
|
452
818
|
...tag,
|
|
453
819
|
_rev: prior._rev
|
|
454
820
|
});
|
|
455
821
|
}
|
|
456
822
|
async function getTag(courseID, tagName) {
|
|
457
823
|
const tagID = getTagID(tagName);
|
|
458
|
-
const courseDB =
|
|
824
|
+
const courseDB = getCourseDB2(courseID);
|
|
459
825
|
return courseDB.get(tagID);
|
|
460
826
|
}
|
|
461
827
|
async function removeTagFromCard(courseID, cardID, tagID) {
|
|
462
828
|
tagID = getTagID(tagID);
|
|
463
|
-
const courseDB =
|
|
829
|
+
const courseDB = getCourseDB2(courseID);
|
|
464
830
|
const tag = await courseDB.get(tagID);
|
|
465
831
|
tag.taggedCards = tag.taggedCards.filter((taggedID) => {
|
|
466
832
|
return cardID !== taggedID;
|
|
@@ -468,7 +834,7 @@ async function removeTagFromCard(courseID, cardID, tagID) {
|
|
|
468
834
|
return courseDB.put(tag);
|
|
469
835
|
}
|
|
470
836
|
async function getAppliedTags(id_course, id_card) {
|
|
471
|
-
const db =
|
|
837
|
+
const db = getCourseDB2(id_course);
|
|
472
838
|
const result = await db.query("getTags", {
|
|
473
839
|
startkey: id_card,
|
|
474
840
|
endkey: id_card
|
|
@@ -481,7 +847,7 @@ async function updateCredentialledCourseConfig(courseID, config) {
|
|
|
481
847
|
|
|
482
848
|
${JSON.stringify(config)}
|
|
483
849
|
`);
|
|
484
|
-
const db =
|
|
850
|
+
const db = getCourseDB2(courseID);
|
|
485
851
|
const old = await getCredentialledCourseConfig(courseID);
|
|
486
852
|
return await db.put({
|
|
487
853
|
...config,
|
|
@@ -491,11 +857,11 @@ ${JSON.stringify(config)}
|
|
|
491
857
|
function isSuccessRow(row) {
|
|
492
858
|
return "doc" in row && row.doc !== null && row.doc !== void 0;
|
|
493
859
|
}
|
|
494
|
-
var
|
|
860
|
+
var import_common5, CourseDB;
|
|
495
861
|
var init_courseDB = __esm({
|
|
496
862
|
"src/impl/couch/courseDB.ts"() {
|
|
497
863
|
"use strict";
|
|
498
|
-
|
|
864
|
+
import_common5 = require("@vue-skuilder/common");
|
|
499
865
|
init_couch();
|
|
500
866
|
init_updateQueue();
|
|
501
867
|
init_types_legacy();
|
|
@@ -514,7 +880,7 @@ var init_courseDB = __esm({
|
|
|
514
880
|
updateQueue;
|
|
515
881
|
constructor(id, userLookup) {
|
|
516
882
|
this.id = id;
|
|
517
|
-
this.db =
|
|
883
|
+
this.db = getCourseDB2(this.id);
|
|
518
884
|
this._getCurrentUser = userLookup;
|
|
519
885
|
this.updateQueue = new UpdateQueue(this.db);
|
|
520
886
|
}
|
|
@@ -570,14 +936,14 @@ var init_courseDB = __esm({
|
|
|
570
936
|
docs.rows.forEach((r) => {
|
|
571
937
|
if (isSuccessRow(r)) {
|
|
572
938
|
if (r.doc && r.doc.elo) {
|
|
573
|
-
ret.push((0,
|
|
939
|
+
ret.push((0, import_common5.toCourseElo)(r.doc.elo));
|
|
574
940
|
} else {
|
|
575
941
|
logger.warn("no elo data for card: " + r.id);
|
|
576
|
-
ret.push((0,
|
|
942
|
+
ret.push((0, import_common5.blankCourseElo)());
|
|
577
943
|
}
|
|
578
944
|
} else {
|
|
579
945
|
logger.warn("no elo data for card: " + JSON.stringify(r));
|
|
580
|
-
ret.push((0,
|
|
946
|
+
ret.push((0, import_common5.blankCourseElo)());
|
|
581
947
|
}
|
|
582
948
|
});
|
|
583
949
|
return ret;
|
|
@@ -730,7 +1096,7 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
730
1096
|
async getCourseTagStubs() {
|
|
731
1097
|
return getCourseTagStubs(this.id);
|
|
732
1098
|
}
|
|
733
|
-
async addNote(codeCourse, shape, data, author, tags, uploads, elo = (0,
|
|
1099
|
+
async addNote(codeCourse, shape, data, author, tags, uploads, elo = (0, import_common5.blankCourseElo)()) {
|
|
734
1100
|
try {
|
|
735
1101
|
const resp = await addNote55(this.id, codeCourse, shape, data, author, tags, uploads, elo);
|
|
736
1102
|
if (resp.ok) {
|
|
@@ -739,19 +1105,19 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
739
1105
|
`[courseDB.addNote] Note added but card creation failed: ${resp.cardCreationError}`
|
|
740
1106
|
);
|
|
741
1107
|
return {
|
|
742
|
-
status:
|
|
1108
|
+
status: import_common5.Status.error,
|
|
743
1109
|
message: `Note was added but no cards were created: ${resp.cardCreationError}`,
|
|
744
1110
|
id: resp.id
|
|
745
1111
|
};
|
|
746
1112
|
}
|
|
747
1113
|
return {
|
|
748
|
-
status:
|
|
1114
|
+
status: import_common5.Status.ok,
|
|
749
1115
|
message: "",
|
|
750
1116
|
id: resp.id
|
|
751
1117
|
};
|
|
752
1118
|
} else {
|
|
753
1119
|
return {
|
|
754
|
-
status:
|
|
1120
|
+
status: import_common5.Status.error,
|
|
755
1121
|
message: "Unexpected error adding note"
|
|
756
1122
|
};
|
|
757
1123
|
}
|
|
@@ -763,7 +1129,7 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
763
1129
|
message: ${err.message}`
|
|
764
1130
|
);
|
|
765
1131
|
return {
|
|
766
|
-
status:
|
|
1132
|
+
status: import_common5.Status.error,
|
|
767
1133
|
message: `Error adding note to course. ${e.reason || err.message}`
|
|
768
1134
|
};
|
|
769
1135
|
}
|
|
@@ -871,7 +1237,7 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
871
1237
|
const courseDoc = (await u.getCourseRegistrationsDoc()).courses.find((c) => {
|
|
872
1238
|
return c.courseID === this.id;
|
|
873
1239
|
});
|
|
874
|
-
targetElo = (0,
|
|
1240
|
+
targetElo = (0, import_common5.EloToNumber)(courseDoc.elo);
|
|
875
1241
|
} catch {
|
|
876
1242
|
targetElo = 1e3;
|
|
877
1243
|
}
|
|
@@ -918,219 +1284,295 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
918
1284
|
}
|
|
919
1285
|
});
|
|
920
1286
|
|
|
921
|
-
// src/impl/
|
|
922
|
-
var
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
}
|
|
926
|
-
});
|
|
927
|
-
|
|
928
|
-
// src/core/util/index.ts
|
|
929
|
-
function areQuestionRecords(h) {
|
|
930
|
-
return isQuestionRecord(h.records[0]);
|
|
931
|
-
}
|
|
932
|
-
function isQuestionRecord(c) {
|
|
933
|
-
return c.userAnswer !== void 0;
|
|
934
|
-
}
|
|
935
|
-
function getCardHistoryID(courseID, cardID) {
|
|
936
|
-
return `${cardHistoryPrefix}-${courseID}-${cardID}`;
|
|
937
|
-
}
|
|
938
|
-
function parseCardHistoryID(id) {
|
|
939
|
-
const split = id.split("-");
|
|
940
|
-
let error = "";
|
|
941
|
-
error += split.length === 3 ? "" : `
|
|
942
|
-
given ID has incorrect number of '-' characters`;
|
|
943
|
-
error += split[0] === cardHistoryPrefix ? "" : `
|
|
944
|
-
given ID does not start with ${cardHistoryPrefix}`;
|
|
945
|
-
if (split.length === 3 && split[0] === cardHistoryPrefix) {
|
|
946
|
-
return {
|
|
947
|
-
courseID: split[1],
|
|
948
|
-
cardID: split[2]
|
|
949
|
-
};
|
|
950
|
-
} else {
|
|
951
|
-
throw new Error("parseCardHistory Error:" + error);
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
function docIsDeleted(e) {
|
|
955
|
-
return Boolean(e?.error === "not_found" && e?.reason === "deleted");
|
|
956
|
-
}
|
|
957
|
-
var init_util = __esm({
|
|
958
|
-
"src/core/util/index.ts"() {
|
|
1287
|
+
// src/impl/couch/classroomDB.ts
|
|
1288
|
+
var import_moment3, CLASSROOM_CONFIG, ClassroomDBBase, StudentClassroomDB;
|
|
1289
|
+
var init_classroomDB2 = __esm({
|
|
1290
|
+
"src/impl/couch/classroomDB.ts"() {
|
|
959
1291
|
"use strict";
|
|
960
|
-
|
|
961
|
-
}
|
|
962
|
-
});
|
|
963
|
-
|
|
964
|
-
// src/impl/common/userDBHelpers.ts
|
|
965
|
-
function filterAllDocsByPrefix2(db, prefix, opts) {
|
|
966
|
-
const options = {
|
|
967
|
-
startkey: prefix,
|
|
968
|
-
endkey: prefix + "\uFFF0",
|
|
969
|
-
include_docs: true
|
|
970
|
-
};
|
|
971
|
-
if (opts) {
|
|
972
|
-
Object.assign(options, opts);
|
|
973
|
-
}
|
|
974
|
-
return db.allDocs(options);
|
|
975
|
-
}
|
|
976
|
-
function getStartAndEndKeys2(key) {
|
|
977
|
-
return {
|
|
978
|
-
startkey: key,
|
|
979
|
-
endkey: key + "\uFFF0"
|
|
980
|
-
};
|
|
981
|
-
}
|
|
982
|
-
function getLocalUserDB(username) {
|
|
983
|
-
return new pouchdb_setup_default(`userdb-${username}`, {});
|
|
984
|
-
}
|
|
985
|
-
function scheduleCardReviewLocal(userDB, review) {
|
|
986
|
-
const now = import_moment.default.utc();
|
|
987
|
-
logger.info(`Scheduling for review in: ${review.time.diff(now, "h") / 24} days`);
|
|
988
|
-
void userDB.put({
|
|
989
|
-
_id: REVIEW_PREFIX + review.time.format(REVIEW_TIME_FORMAT),
|
|
990
|
-
cardId: review.card_id,
|
|
991
|
-
reviewTime: review.time,
|
|
992
|
-
courseId: review.course_id,
|
|
993
|
-
scheduledAt: now,
|
|
994
|
-
scheduledFor: review.scheduledFor,
|
|
995
|
-
schedulingAgentId: review.schedulingAgentId
|
|
996
|
-
});
|
|
997
|
-
}
|
|
998
|
-
async function removeScheduledCardReviewLocal(userDB, reviewDocID) {
|
|
999
|
-
const reviewDoc = await userDB.get(reviewDocID);
|
|
1000
|
-
userDB.remove(reviewDoc).then((res) => {
|
|
1001
|
-
if (res.ok) {
|
|
1002
|
-
log2(`Removed Review Doc: ${reviewDocID}`);
|
|
1003
|
-
}
|
|
1004
|
-
}).catch((err) => {
|
|
1005
|
-
log2(`Failed to remove Review Doc: ${reviewDocID},
|
|
1006
|
-
${JSON.stringify(err)}`);
|
|
1007
|
-
});
|
|
1008
|
-
}
|
|
1009
|
-
var import_moment, REVIEW_PREFIX, REVIEW_TIME_FORMAT, log2;
|
|
1010
|
-
var init_userDBHelpers = __esm({
|
|
1011
|
-
"src/impl/common/userDBHelpers.ts"() {
|
|
1012
|
-
"use strict";
|
|
1013
|
-
import_moment = __toESM(require("moment"));
|
|
1292
|
+
init_factory();
|
|
1014
1293
|
init_logger();
|
|
1294
|
+
import_moment3 = __toESM(require("moment"));
|
|
1015
1295
|
init_pouchdb_setup();
|
|
1016
|
-
REVIEW_PREFIX = "card_review_";
|
|
1017
|
-
REVIEW_TIME_FORMAT = "YYYY-MM-DD--kk:mm:ss-SSS";
|
|
1018
|
-
log2 = (s) => {
|
|
1019
|
-
logger.info(s);
|
|
1020
|
-
};
|
|
1021
|
-
}
|
|
1022
|
-
});
|
|
1023
|
-
|
|
1024
|
-
// src/impl/couch/user-course-relDB.ts
|
|
1025
|
-
var import_moment2, UsrCrsData;
|
|
1026
|
-
var init_user_course_relDB = __esm({
|
|
1027
|
-
"src/impl/couch/user-course-relDB.ts"() {
|
|
1028
|
-
"use strict";
|
|
1029
|
-
import_moment2 = __toESM(require("moment"));
|
|
1030
1296
|
init_couch();
|
|
1031
1297
|
init_courseDB();
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
this.
|
|
1298
|
+
CLASSROOM_CONFIG = "ClassroomConfig";
|
|
1299
|
+
ClassroomDBBase = class {
|
|
1300
|
+
_id;
|
|
1301
|
+
_db;
|
|
1302
|
+
_cfg;
|
|
1303
|
+
_initComplete = false;
|
|
1304
|
+
_content_prefix = "content";
|
|
1305
|
+
get _content_searchkeys() {
|
|
1306
|
+
return getStartAndEndKeys2(this._content_prefix);
|
|
1041
1307
|
}
|
|
1042
|
-
async
|
|
1043
|
-
|
|
1044
|
-
|
|
1308
|
+
async getAssignedContent() {
|
|
1309
|
+
logger.info(`Getting assigned content...`);
|
|
1310
|
+
const docRows = await this._db.allDocs({
|
|
1311
|
+
startkey: this._content_prefix,
|
|
1312
|
+
endkey: this._content_prefix + `\uFFF0`,
|
|
1313
|
+
include_docs: true
|
|
1314
|
+
});
|
|
1315
|
+
const ret = docRows.rows.map((row) => {
|
|
1316
|
+
return row.doc;
|
|
1317
|
+
});
|
|
1318
|
+
return ret;
|
|
1045
1319
|
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1320
|
+
getContentId(content) {
|
|
1321
|
+
if (content.type === "tag") {
|
|
1322
|
+
return `${this._content_prefix}-${content.courseID}-${content.tagID}`;
|
|
1323
|
+
} else {
|
|
1324
|
+
return `${this._content_prefix}-${content.courseID}`;
|
|
1325
|
+
}
|
|
1049
1326
|
}
|
|
1050
|
-
|
|
1051
|
-
return
|
|
1327
|
+
get ready() {
|
|
1328
|
+
return this._initComplete;
|
|
1052
1329
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1330
|
+
getConfig() {
|
|
1331
|
+
return this._cfg;
|
|
1332
|
+
}
|
|
1333
|
+
};
|
|
1334
|
+
StudentClassroomDB = class _StudentClassroomDB extends ClassroomDBBase {
|
|
1335
|
+
// private readonly _prefix: string = 'content';
|
|
1336
|
+
userMessages;
|
|
1337
|
+
_user;
|
|
1338
|
+
constructor(classID, user) {
|
|
1339
|
+
super();
|
|
1340
|
+
this._id = classID;
|
|
1341
|
+
this._user = user;
|
|
1342
|
+
}
|
|
1343
|
+
async init() {
|
|
1344
|
+
const dbName = `classdb-student-${this._id}`;
|
|
1345
|
+
this._db = new pouchdb_setup_default(
|
|
1346
|
+
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
1347
|
+
pouchDBincludeCredentialsConfig
|
|
1348
|
+
);
|
|
1349
|
+
try {
|
|
1350
|
+
const cfg = await this._db.get(CLASSROOM_CONFIG);
|
|
1351
|
+
this._cfg = cfg;
|
|
1352
|
+
this.userMessages = this._db.changes({
|
|
1353
|
+
since: "now",
|
|
1354
|
+
live: true,
|
|
1355
|
+
include_docs: true
|
|
1356
|
+
});
|
|
1357
|
+
this._initComplete = true;
|
|
1358
|
+
return;
|
|
1359
|
+
} catch (e) {
|
|
1360
|
+
throw new Error(`Error in StudentClassroomDB constructor: ${JSON.stringify(e)}`);
|
|
1061
1361
|
}
|
|
1062
1362
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1363
|
+
static async factory(classID, user) {
|
|
1364
|
+
const ret = new _StudentClassroomDB(classID, user);
|
|
1365
|
+
await ret.init();
|
|
1366
|
+
return ret;
|
|
1065
1367
|
}
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1368
|
+
setChangeFcn(f) {
|
|
1369
|
+
void this.userMessages.on("change", f);
|
|
1370
|
+
}
|
|
1371
|
+
async getPendingReviews() {
|
|
1372
|
+
const u = this._user;
|
|
1373
|
+
return (await u.getPendingReviews()).filter((r) => r.scheduledFor === "classroom" && r.schedulingAgentId === this._id).map((r) => {
|
|
1374
|
+
return {
|
|
1375
|
+
...r,
|
|
1376
|
+
qualifiedID: `${r.courseId}-${r.cardId}`,
|
|
1377
|
+
courseID: r.courseId,
|
|
1378
|
+
cardID: r.cardId,
|
|
1379
|
+
contentSourceType: "classroom",
|
|
1380
|
+
contentSourceID: this._id,
|
|
1381
|
+
reviewID: r._id,
|
|
1382
|
+
status: "review"
|
|
1383
|
+
};
|
|
1072
1384
|
});
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
);
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1385
|
+
}
|
|
1386
|
+
async getNewCards() {
|
|
1387
|
+
const activeCards = await this._user.getActiveCards();
|
|
1388
|
+
const now = import_moment3.default.utc();
|
|
1389
|
+
const assigned = await this.getAssignedContent();
|
|
1390
|
+
const due = assigned.filter((c) => now.isAfter(import_moment3.default.utc(c.activeOn, REVIEW_TIME_FORMAT2)));
|
|
1391
|
+
logger.info(`Due content: ${JSON.stringify(due)}`);
|
|
1392
|
+
let ret = [];
|
|
1393
|
+
for (let i = 0; i < due.length; i++) {
|
|
1394
|
+
const content = due[i];
|
|
1395
|
+
if (content.type === "course") {
|
|
1396
|
+
const db = new CourseDB(content.courseID, async () => this._user);
|
|
1397
|
+
ret = ret.concat(await db.getNewCards());
|
|
1398
|
+
} else if (content.type === "tag") {
|
|
1399
|
+
const tagDoc = await getTag(content.courseID, content.tagID);
|
|
1400
|
+
ret = ret.concat(
|
|
1401
|
+
tagDoc.taggedCards.map((c) => {
|
|
1402
|
+
return {
|
|
1403
|
+
courseID: content.courseID,
|
|
1404
|
+
cardID: c,
|
|
1405
|
+
qualifiedID: `${content.courseID}-${c}`,
|
|
1406
|
+
contentSourceType: "classroom",
|
|
1407
|
+
contentSourceID: this._id,
|
|
1408
|
+
status: "new"
|
|
1409
|
+
};
|
|
1410
|
+
})
|
|
1411
|
+
);
|
|
1412
|
+
} else if (content.type === "card") {
|
|
1413
|
+
ret.push(await getCourseDB2(content.courseID).get(content.cardID));
|
|
1084
1414
|
}
|
|
1085
|
-
}
|
|
1415
|
+
}
|
|
1416
|
+
logger.info(`New Cards from classroom ${this._cfg.name}: ${ret.map((c) => c.qualifiedID)}`);
|
|
1417
|
+
return ret.filter((c) => {
|
|
1418
|
+
if (activeCards.some((ac) => c.qualifiedID.includes(ac))) {
|
|
1419
|
+
return false;
|
|
1420
|
+
} else {
|
|
1421
|
+
return true;
|
|
1422
|
+
}
|
|
1423
|
+
});
|
|
1086
1424
|
}
|
|
1087
1425
|
};
|
|
1088
1426
|
}
|
|
1089
1427
|
});
|
|
1090
1428
|
|
|
1091
|
-
// src/impl/
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
registrations: []
|
|
1102
|
-
});
|
|
1103
|
-
ret = await getOrCreateClassroomRegistrationsDoc(user);
|
|
1104
|
-
} else {
|
|
1105
|
-
const errorDetails = {
|
|
1106
|
-
name: err.name,
|
|
1107
|
-
status: err.status,
|
|
1108
|
-
message: err.message,
|
|
1109
|
-
reason: err.reason,
|
|
1110
|
-
error: err.error
|
|
1111
|
-
};
|
|
1112
|
-
logger.error(
|
|
1113
|
-
"Database error in getOrCreateClassroomRegistrationsDoc (standalone function):",
|
|
1114
|
-
errorDetails
|
|
1115
|
-
);
|
|
1116
|
-
throw new Error(
|
|
1117
|
-
`Database error accessing classroom registrations: ${err.message || err.name || "Unknown error"} (status: ${err.status})`
|
|
1118
|
-
);
|
|
1119
|
-
}
|
|
1429
|
+
// src/impl/couch/adminDB.ts
|
|
1430
|
+
var init_adminDB2 = __esm({
|
|
1431
|
+
"src/impl/couch/adminDB.ts"() {
|
|
1432
|
+
"use strict";
|
|
1433
|
+
init_pouchdb_setup();
|
|
1434
|
+
init_factory();
|
|
1435
|
+
init_couch();
|
|
1436
|
+
init_classroomDB2();
|
|
1437
|
+
init_courseLookupDB();
|
|
1438
|
+
init_logger();
|
|
1120
1439
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1442
|
+
// src/impl/couch/auth.ts
|
|
1443
|
+
var init_auth = __esm({
|
|
1444
|
+
"src/impl/couch/auth.ts"() {
|
|
1445
|
+
"use strict";
|
|
1446
|
+
init_factory();
|
|
1447
|
+
init_types_legacy();
|
|
1448
|
+
init_logger();
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1451
|
+
|
|
1452
|
+
// src/impl/couch/CouchDBSyncStrategy.ts
|
|
1453
|
+
var import_common6;
|
|
1454
|
+
var init_CouchDBSyncStrategy = __esm({
|
|
1455
|
+
"src/impl/couch/CouchDBSyncStrategy.ts"() {
|
|
1456
|
+
"use strict";
|
|
1457
|
+
init_factory();
|
|
1458
|
+
init_types_legacy();
|
|
1459
|
+
init_logger();
|
|
1460
|
+
import_common6 = require("@vue-skuilder/common");
|
|
1461
|
+
init_common();
|
|
1462
|
+
init_pouchdb_setup();
|
|
1463
|
+
init_couch();
|
|
1464
|
+
init_auth();
|
|
1465
|
+
}
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
// src/impl/couch/index.ts
|
|
1469
|
+
function getCourseDB2(courseID) {
|
|
1470
|
+
return new pouchdb_setup_default(
|
|
1471
|
+
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "coursedb-" + courseID,
|
|
1472
|
+
pouchDBincludeCredentialsConfig
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
function getCourseDocs(courseID, docIDs, options = {}) {
|
|
1476
|
+
return getCourseDB2(courseID).allDocs({
|
|
1477
|
+
...options,
|
|
1478
|
+
keys: docIDs
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
function getCourseDoc(courseID, docID, options = {}) {
|
|
1482
|
+
return getCourseDB2(courseID).get(docID, options);
|
|
1483
|
+
}
|
|
1484
|
+
function filterAllDocsByPrefix2(db, prefix, opts) {
|
|
1485
|
+
const options = {
|
|
1486
|
+
startkey: prefix,
|
|
1487
|
+
endkey: prefix + "\uFFF0",
|
|
1488
|
+
include_docs: true
|
|
1489
|
+
};
|
|
1490
|
+
if (opts) {
|
|
1491
|
+
Object.assign(options, opts);
|
|
1492
|
+
}
|
|
1493
|
+
return db.allDocs(options);
|
|
1494
|
+
}
|
|
1495
|
+
function getStartAndEndKeys2(key) {
|
|
1496
|
+
return {
|
|
1497
|
+
startkey: key,
|
|
1498
|
+
endkey: key + "\uFFF0"
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
var import_moment4, import_process, isBrowser, GUEST_LOCAL_DB, localUserDB, pouchDBincludeCredentialsConfig, REVIEW_TIME_FORMAT2;
|
|
1502
|
+
var init_couch = __esm({
|
|
1503
|
+
"src/impl/couch/index.ts"() {
|
|
1504
|
+
"use strict";
|
|
1505
|
+
init_factory();
|
|
1506
|
+
init_types_legacy();
|
|
1507
|
+
import_moment4 = __toESM(require("moment"));
|
|
1508
|
+
init_logger();
|
|
1509
|
+
init_pouchdb_setup();
|
|
1510
|
+
import_process = __toESM(require("process"));
|
|
1511
|
+
init_contentSource();
|
|
1512
|
+
init_adminDB2();
|
|
1513
|
+
init_classroomDB2();
|
|
1514
|
+
init_courseAPI();
|
|
1515
|
+
init_courseDB();
|
|
1516
|
+
init_CouchDBSyncStrategy();
|
|
1517
|
+
isBrowser = typeof window !== "undefined";
|
|
1518
|
+
if (isBrowser) {
|
|
1519
|
+
window.process = import_process.default;
|
|
1520
|
+
}
|
|
1521
|
+
GUEST_LOCAL_DB = `userdb-${GuestUsername}`;
|
|
1522
|
+
localUserDB = new pouchdb_setup_default(GUEST_LOCAL_DB);
|
|
1523
|
+
pouchDBincludeCredentialsConfig = {
|
|
1524
|
+
fetch(url, opts) {
|
|
1525
|
+
opts.credentials = "include";
|
|
1526
|
+
return pouchdb_setup_default.fetch(url, opts);
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
REVIEW_TIME_FORMAT2 = "YYYY-MM-DD--kk:mm:ss-SSS";
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
1532
|
+
|
|
1533
|
+
// src/impl/common/BaseUserDB.ts
|
|
1534
|
+
async function getOrCreateClassroomRegistrationsDoc(user) {
|
|
1535
|
+
let ret;
|
|
1536
|
+
try {
|
|
1537
|
+
ret = await getLocalUserDB(user).get(userClassroomsDoc);
|
|
1538
|
+
} catch (e) {
|
|
1539
|
+
const err = e;
|
|
1540
|
+
if (err.status === 404) {
|
|
1541
|
+
await getLocalUserDB(user).put({
|
|
1542
|
+
_id: userClassroomsDoc,
|
|
1543
|
+
registrations: []
|
|
1544
|
+
});
|
|
1545
|
+
ret = await getOrCreateClassroomRegistrationsDoc(user);
|
|
1546
|
+
} else {
|
|
1547
|
+
const errorDetails = {
|
|
1548
|
+
name: err.name,
|
|
1549
|
+
status: err.status,
|
|
1550
|
+
message: err.message,
|
|
1551
|
+
reason: err.reason,
|
|
1552
|
+
error: err.error
|
|
1553
|
+
};
|
|
1554
|
+
logger.error(
|
|
1555
|
+
"Database error in getOrCreateClassroomRegistrationsDoc (standalone function):",
|
|
1556
|
+
errorDetails
|
|
1557
|
+
);
|
|
1558
|
+
throw new Error(
|
|
1559
|
+
`Database error accessing classroom registrations: ${err.message || err.name || "Unknown error"} (status: ${err.status})`
|
|
1560
|
+
);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
return ret;
|
|
1564
|
+
}
|
|
1565
|
+
async function getOrCreateCourseRegistrationsDoc(user) {
|
|
1566
|
+
let ret;
|
|
1567
|
+
try {
|
|
1568
|
+
ret = await getLocalUserDB(user).get(userCoursesDoc);
|
|
1569
|
+
} catch (e) {
|
|
1570
|
+
const err = e;
|
|
1571
|
+
if (err.status === 404) {
|
|
1572
|
+
await getLocalUserDB(user).put({
|
|
1573
|
+
_id: userCoursesDoc,
|
|
1574
|
+
courses: [],
|
|
1575
|
+
studyWeight: {}
|
|
1134
1576
|
});
|
|
1135
1577
|
ret = await getOrCreateCourseRegistrationsDoc(user);
|
|
1136
1578
|
} else {
|
|
@@ -1181,13 +1623,14 @@ async function dropUserFromClassroom(user, classID) {
|
|
|
1181
1623
|
async function getUserClassrooms(user) {
|
|
1182
1624
|
return getOrCreateClassroomRegistrationsDoc(user);
|
|
1183
1625
|
}
|
|
1184
|
-
var
|
|
1626
|
+
var import_common8, import_moment5, log3, BaseUser, userCoursesDoc, userClassroomsDoc;
|
|
1185
1627
|
var init_BaseUserDB = __esm({
|
|
1186
1628
|
"src/impl/common/BaseUserDB.ts"() {
|
|
1187
1629
|
"use strict";
|
|
1630
|
+
init_core();
|
|
1188
1631
|
init_util();
|
|
1189
|
-
|
|
1190
|
-
|
|
1632
|
+
import_common8 = require("@vue-skuilder/common");
|
|
1633
|
+
import_moment5 = __toESM(require("moment"));
|
|
1191
1634
|
init_types_legacy();
|
|
1192
1635
|
init_logger();
|
|
1193
1636
|
init_userDBHelpers();
|
|
@@ -1197,7 +1640,6 @@ var init_BaseUserDB = __esm({
|
|
|
1197
1640
|
log3 = (s) => {
|
|
1198
1641
|
logger.info(s);
|
|
1199
1642
|
};
|
|
1200
|
-
cardHistoryPrefix2 = "cardH-";
|
|
1201
1643
|
BaseUser = class _BaseUser {
|
|
1202
1644
|
static _instance;
|
|
1203
1645
|
static _initialized = false;
|
|
@@ -1218,11 +1660,13 @@ var init_BaseUserDB = __esm({
|
|
|
1218
1660
|
isLoggedIn() {
|
|
1219
1661
|
return !this._username.startsWith(GuestUsername);
|
|
1220
1662
|
}
|
|
1221
|
-
remoteDB;
|
|
1222
1663
|
remote() {
|
|
1223
1664
|
return this.remoteDB;
|
|
1224
1665
|
}
|
|
1225
1666
|
localDB;
|
|
1667
|
+
remoteDB;
|
|
1668
|
+
writeDB;
|
|
1669
|
+
// Database to use for write operations (local-first approach)
|
|
1226
1670
|
updateQueue;
|
|
1227
1671
|
async createAccount(username, password) {
|
|
1228
1672
|
if (!this.syncStrategy.canCreateAccount()) {
|
|
@@ -1235,10 +1679,14 @@ Currently logged-in as ${this._username}.`
|
|
|
1235
1679
|
);
|
|
1236
1680
|
}
|
|
1237
1681
|
const result = await this.syncStrategy.createAccount(username, password);
|
|
1238
|
-
if (result.status ===
|
|
1682
|
+
if (result.status === import_common8.Status.ok) {
|
|
1239
1683
|
log3(`Account created successfully, updating username to ${username}`);
|
|
1240
1684
|
this._username = username;
|
|
1241
|
-
|
|
1685
|
+
try {
|
|
1686
|
+
localStorage.removeItem("dbUUID");
|
|
1687
|
+
} catch (e) {
|
|
1688
|
+
logger.warn("localStorage not available (Node.js environment):", e);
|
|
1689
|
+
}
|
|
1242
1690
|
await this.init();
|
|
1243
1691
|
}
|
|
1244
1692
|
return {
|
|
@@ -1250,15 +1698,22 @@ Currently logged-in as ${this._username}.`
|
|
|
1250
1698
|
if (!this.syncStrategy.canAuthenticate()) {
|
|
1251
1699
|
throw new Error("Authentication not supported by current sync strategy");
|
|
1252
1700
|
}
|
|
1253
|
-
if (!this._username.startsWith(GuestUsername)) {
|
|
1254
|
-
|
|
1255
|
-
|
|
1701
|
+
if (!this._username.startsWith(GuestUsername) && this._username != username) {
|
|
1702
|
+
if (this._username != username) {
|
|
1703
|
+
throw new Error(`Cannot change accounts while logged in.
|
|
1704
|
+
Log out of account ${this.getUsername()} before logging in as ${username}.`);
|
|
1705
|
+
}
|
|
1706
|
+
logger.warn(`User ${this._username} is already logged in, but executing login again.`);
|
|
1256
1707
|
}
|
|
1257
1708
|
const loginResult = await this.syncStrategy.authenticate(username, password);
|
|
1258
1709
|
if (loginResult.ok) {
|
|
1259
1710
|
log3(`Logged in as ${username}`);
|
|
1260
1711
|
this._username = username;
|
|
1261
|
-
|
|
1712
|
+
try {
|
|
1713
|
+
localStorage.removeItem("dbUUID");
|
|
1714
|
+
} catch (e) {
|
|
1715
|
+
logger.warn("localStorage not available (Node.js environment):", e);
|
|
1716
|
+
}
|
|
1262
1717
|
await this.init();
|
|
1263
1718
|
}
|
|
1264
1719
|
return loginResult;
|
|
@@ -1266,7 +1721,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1266
1721
|
async resetUserData() {
|
|
1267
1722
|
if (this.syncStrategy.canAuthenticate()) {
|
|
1268
1723
|
return {
|
|
1269
|
-
status:
|
|
1724
|
+
status: import_common8.Status.error,
|
|
1270
1725
|
error: "Reset user data is only available for local-only mode. Use logout instead for remote sync."
|
|
1271
1726
|
};
|
|
1272
1727
|
}
|
|
@@ -1275,8 +1730,8 @@ Currently logged-in as ${this._username}.`
|
|
|
1275
1730
|
const allDocs = await localDB.allDocs({ include_docs: false });
|
|
1276
1731
|
const docsToDelete = allDocs.rows.filter((row) => {
|
|
1277
1732
|
const id = row.id;
|
|
1278
|
-
return id.startsWith(
|
|
1279
|
-
id.startsWith(
|
|
1733
|
+
return id.startsWith(DocTypePrefixes["CARDRECORD" /* CARDRECORD */]) || // Card interaction history
|
|
1734
|
+
id.startsWith(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]) || // Scheduled reviews
|
|
1280
1735
|
id === _BaseUser.DOC_IDS.COURSE_REGISTRATIONS || // Course registrations
|
|
1281
1736
|
id === _BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS || // Classroom registrations
|
|
1282
1737
|
id === _BaseUser.DOC_IDS.CONFIG;
|
|
@@ -1285,11 +1740,11 @@ Currently logged-in as ${this._username}.`
|
|
|
1285
1740
|
await localDB.bulkDocs(docsToDelete);
|
|
1286
1741
|
}
|
|
1287
1742
|
await this.init();
|
|
1288
|
-
return { status:
|
|
1743
|
+
return { status: import_common8.Status.ok };
|
|
1289
1744
|
} catch (error) {
|
|
1290
1745
|
logger.error("Failed to reset user data:", error);
|
|
1291
1746
|
return {
|
|
1292
|
-
status:
|
|
1747
|
+
status: import_common8.Status.error,
|
|
1293
1748
|
error: error instanceof Error ? error.message : "Unknown error during reset"
|
|
1294
1749
|
};
|
|
1295
1750
|
}
|
|
@@ -1345,7 +1800,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1345
1800
|
*
|
|
1346
1801
|
*/
|
|
1347
1802
|
async getActiveCards() {
|
|
1348
|
-
const keys =
|
|
1803
|
+
const keys = getStartAndEndKeys(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]);
|
|
1349
1804
|
const reviews = await this.remoteDB.allDocs({
|
|
1350
1805
|
startkey: keys.startkey,
|
|
1351
1806
|
endkey: keys.endkey,
|
|
@@ -1417,7 +1872,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1417
1872
|
}
|
|
1418
1873
|
}
|
|
1419
1874
|
async getReviewstoDate(targetDate, course_id) {
|
|
1420
|
-
const keys =
|
|
1875
|
+
const keys = getStartAndEndKeys(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]);
|
|
1421
1876
|
const reviews = await this.remoteDB.allDocs({
|
|
1422
1877
|
startkey: keys.startkey,
|
|
1423
1878
|
endkey: keys.endkey,
|
|
@@ -1427,8 +1882,11 @@ Currently logged-in as ${this._username}.`
|
|
|
1427
1882
|
`Fetching ${this._username}'s scheduled reviews${course_id ? ` for course ${course_id}` : ""}.`
|
|
1428
1883
|
);
|
|
1429
1884
|
return reviews.rows.filter((r) => {
|
|
1430
|
-
if (r.id.startsWith(
|
|
1431
|
-
const date =
|
|
1885
|
+
if (r.id.startsWith(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */])) {
|
|
1886
|
+
const date = import_moment5.default.utc(
|
|
1887
|
+
r.id.substr(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */].length),
|
|
1888
|
+
REVIEW_TIME_FORMAT
|
|
1889
|
+
);
|
|
1432
1890
|
if (targetDate.isAfter(date)) {
|
|
1433
1891
|
if (course_id === void 0 || r.doc.courseId === course_id) {
|
|
1434
1892
|
return true;
|
|
@@ -1438,11 +1896,11 @@ Currently logged-in as ${this._username}.`
|
|
|
1438
1896
|
}).map((r) => r.doc);
|
|
1439
1897
|
}
|
|
1440
1898
|
async getReviewsForcast(daysCount) {
|
|
1441
|
-
const time =
|
|
1899
|
+
const time = import_moment5.default.utc().add(daysCount, "days");
|
|
1442
1900
|
return this.getReviewstoDate(time);
|
|
1443
1901
|
}
|
|
1444
1902
|
async getPendingReviews(course_id) {
|
|
1445
|
-
const now =
|
|
1903
|
+
const now = import_moment5.default.utc();
|
|
1446
1904
|
return this.getReviewstoDate(now, course_id);
|
|
1447
1905
|
}
|
|
1448
1906
|
async getScheduledReviewCount(course_id) {
|
|
@@ -1543,7 +2001,8 @@ Currently logged-in as ${this._username}.`
|
|
|
1543
2001
|
const defaultConfig = {
|
|
1544
2002
|
_id: _BaseUser.DOC_IDS.CONFIG,
|
|
1545
2003
|
darkMode: false,
|
|
1546
|
-
likesConfetti: false
|
|
2004
|
+
likesConfetti: false,
|
|
2005
|
+
sessionTimeLimit: 5
|
|
1547
2006
|
};
|
|
1548
2007
|
try {
|
|
1549
2008
|
const cfg = await this.localDB.get(_BaseUser.DOC_IDS.CONFIG);
|
|
@@ -1616,10 +2075,15 @@ Currently logged-in as ${this._username}.`
|
|
|
1616
2075
|
setDBandQ() {
|
|
1617
2076
|
this.localDB = getLocalUserDB(this._username);
|
|
1618
2077
|
this.remoteDB = this.syncStrategy.setupRemoteDB(this._username);
|
|
1619
|
-
this.
|
|
2078
|
+
this.writeDB = this.syncStrategy.getWriteDB ? this.syncStrategy.getWriteDB(this._username) : this.localDB;
|
|
2079
|
+
this.updateQueue = new UpdateQueue(this.localDB, this.writeDB);
|
|
1620
2080
|
}
|
|
1621
2081
|
async init() {
|
|
1622
2082
|
_BaseUser._initialized = false;
|
|
2083
|
+
if (this._username === "admin") {
|
|
2084
|
+
_BaseUser._initialized = true;
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
1623
2087
|
this.setDBandQ();
|
|
1624
2088
|
this.syncStrategy.startSync(this.localDB, this.remoteDB);
|
|
1625
2089
|
void this.applyDesignDocs();
|
|
@@ -1641,6 +2105,9 @@ Currently logged-in as ${this._username}.`
|
|
|
1641
2105
|
}
|
|
1642
2106
|
];
|
|
1643
2107
|
async applyDesignDocs() {
|
|
2108
|
+
if (this._username === "admin") {
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
1644
2111
|
for (const doc of _BaseUser.designDocs) {
|
|
1645
2112
|
try {
|
|
1646
2113
|
try {
|
|
@@ -1657,7 +2124,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1657
2124
|
}
|
|
1658
2125
|
}
|
|
1659
2126
|
} catch (error) {
|
|
1660
|
-
if (error
|
|
2127
|
+
if (error.name && error.name === "conflict") {
|
|
1661
2128
|
logger.warn(`Design doc ${doc._id} update conflict - will retry`);
|
|
1662
2129
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1663
2130
|
await this.applyDesignDoc(doc);
|
|
@@ -1695,7 +2162,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1695
2162
|
*/
|
|
1696
2163
|
async putCardRecord(record) {
|
|
1697
2164
|
const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);
|
|
1698
|
-
record.timeStamp =
|
|
2165
|
+
record.timeStamp = import_moment5.default.utc(record.timeStamp).toString();
|
|
1699
2166
|
try {
|
|
1700
2167
|
const cardHistory = await this.update(
|
|
1701
2168
|
cardHistoryID,
|
|
@@ -1711,7 +2178,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1711
2178
|
const ret = {
|
|
1712
2179
|
...record2
|
|
1713
2180
|
};
|
|
1714
|
-
ret.timeStamp =
|
|
2181
|
+
ret.timeStamp = import_moment5.default.utc(record2.timeStamp);
|
|
1715
2182
|
return ret;
|
|
1716
2183
|
});
|
|
1717
2184
|
return cardHistory;
|
|
@@ -1727,8 +2194,8 @@ Currently logged-in as ${this._username}.`
|
|
|
1727
2194
|
streak: 0,
|
|
1728
2195
|
bestInterval: 0
|
|
1729
2196
|
};
|
|
1730
|
-
|
|
1731
|
-
return initCardHistory;
|
|
2197
|
+
const putResult = await this.writeDB.put(initCardHistory);
|
|
2198
|
+
return { ...initCardHistory, _rev: putResult.rev };
|
|
1732
2199
|
} else {
|
|
1733
2200
|
throw new Error(`putCardRecord failed because of:
|
|
1734
2201
|
name:${reason.name}
|
|
@@ -1763,7 +2230,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1763
2230
|
const deletePromises = duplicateDocIds.map(async (docId) => {
|
|
1764
2231
|
try {
|
|
1765
2232
|
const doc = await this.remoteDB.get(docId);
|
|
1766
|
-
await this.
|
|
2233
|
+
await this.writeDB.remove(doc);
|
|
1767
2234
|
log3(`Successfully removed duplicate review: ${docId}`);
|
|
1768
2235
|
} catch (error) {
|
|
1769
2236
|
log3(`Failed to remove duplicate review ${docId}: ${error}`);
|
|
@@ -1785,17 +2252,17 @@ Currently logged-in as ${this._username}.`
|
|
|
1785
2252
|
* @param course_id optional specification of individual course
|
|
1786
2253
|
*/
|
|
1787
2254
|
async getSeenCards(course_id) {
|
|
1788
|
-
let prefix =
|
|
2255
|
+
let prefix = DocTypePrefixes["CARDRECORD" /* CARDRECORD */];
|
|
1789
2256
|
if (course_id) {
|
|
1790
2257
|
prefix += course_id;
|
|
1791
2258
|
}
|
|
1792
|
-
const docs = await
|
|
2259
|
+
const docs = await filterAllDocsByPrefix(this.localDB, prefix, {
|
|
1793
2260
|
include_docs: false
|
|
1794
2261
|
});
|
|
1795
2262
|
const ret = [];
|
|
1796
2263
|
docs.rows.forEach((row) => {
|
|
1797
|
-
if (row.id.startsWith(
|
|
1798
|
-
ret.push(row.id.substr(
|
|
2264
|
+
if (row.id.startsWith(DocTypePrefixes["CARDRECORD" /* CARDRECORD */])) {
|
|
2265
|
+
ret.push(row.id.substr(DocTypePrefixes["CARDRECORD" /* CARDRECORD */].length));
|
|
1799
2266
|
}
|
|
1800
2267
|
});
|
|
1801
2268
|
return ret;
|
|
@@ -1805,9 +2272,9 @@ Currently logged-in as ${this._username}.`
|
|
|
1805
2272
|
* @returns A promise of the cards that the user has seen in the past.
|
|
1806
2273
|
*/
|
|
1807
2274
|
async getHistory() {
|
|
1808
|
-
const cards = await
|
|
2275
|
+
const cards = await filterAllDocsByPrefix(
|
|
1809
2276
|
this.remoteDB,
|
|
1810
|
-
|
|
2277
|
+
DocTypePrefixes["CARDRECORD" /* CARDRECORD */],
|
|
1811
2278
|
{
|
|
1812
2279
|
include_docs: true,
|
|
1813
2280
|
attachments: false
|
|
@@ -1848,7 +2315,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1848
2315
|
} catch (e) {
|
|
1849
2316
|
const err = e;
|
|
1850
2317
|
if (err.status === 404) {
|
|
1851
|
-
await this.
|
|
2318
|
+
await this.writeDB.put({
|
|
1852
2319
|
_id: _BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS,
|
|
1853
2320
|
registrations: []
|
|
1854
2321
|
});
|
|
@@ -1896,10 +2363,10 @@ Currently logged-in as ${this._username}.`
|
|
|
1896
2363
|
}
|
|
1897
2364
|
}
|
|
1898
2365
|
async scheduleCardReview(review) {
|
|
1899
|
-
return scheduleCardReviewLocal(this.
|
|
2366
|
+
return scheduleCardReviewLocal(this.writeDB, review);
|
|
1900
2367
|
}
|
|
1901
2368
|
async removeScheduledCardReview(reviewId) {
|
|
1902
|
-
return removeScheduledCardReviewLocal(this.
|
|
2369
|
+
return removeScheduledCardReviewLocal(this.writeDB, reviewId);
|
|
1903
2370
|
}
|
|
1904
2371
|
async registerForClassroom(_classId, _registerAs) {
|
|
1905
2372
|
return registerUserForClassroom(this._username, _classId, _registerAs);
|
|
@@ -1929,428 +2396,24 @@ var init_common = __esm({
|
|
|
1929
2396
|
}
|
|
1930
2397
|
});
|
|
1931
2398
|
|
|
1932
|
-
// src/
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
const result = await db.post(payload);
|
|
1937
|
-
const dataShapeId = import_common3.NameSpacer.getDataShapeString({
|
|
1938
|
-
course: codeCourse,
|
|
1939
|
-
dataShape: shape.name
|
|
1940
|
-
});
|
|
1941
|
-
if (result.ok) {
|
|
1942
|
-
try {
|
|
1943
|
-
await createCards(courseID, dataShapeId, result.id, tags, elo, author);
|
|
1944
|
-
} catch (error) {
|
|
1945
|
-
let errorMessage = "Unknown error";
|
|
1946
|
-
if (error instanceof Error) {
|
|
1947
|
-
errorMessage = error.message;
|
|
1948
|
-
} else if (error && typeof error === "object" && "reason" in error) {
|
|
1949
|
-
errorMessage = error.reason;
|
|
1950
|
-
} else if (error && typeof error === "object" && "message" in error) {
|
|
1951
|
-
errorMessage = error.message;
|
|
1952
|
-
} else {
|
|
1953
|
-
errorMessage = String(error);
|
|
1954
|
-
}
|
|
1955
|
-
logger.error(`[addNote55] Failed to create cards for note ${result.id}: ${errorMessage}`);
|
|
1956
|
-
result.cardCreationFailed = true;
|
|
1957
|
-
result.cardCreationError = errorMessage;
|
|
1958
|
-
}
|
|
1959
|
-
} else {
|
|
1960
|
-
logger.error(`[addNote55] Error adding note. Result: ${JSON.stringify(result)}`);
|
|
1961
|
-
}
|
|
1962
|
-
return result;
|
|
1963
|
-
}
|
|
1964
|
-
async function createCards(courseID, datashapeID, noteID, tags, elo = (0, import_common4.blankCourseElo)(), author) {
|
|
1965
|
-
const cfg = await getCredentialledCourseConfig(courseID);
|
|
1966
|
-
const dsDescriptor = import_common3.NameSpacer.getDataShapeDescriptor(datashapeID);
|
|
1967
|
-
let questionViewTypes = [];
|
|
1968
|
-
for (const ds of cfg.dataShapes) {
|
|
1969
|
-
if (ds.name === datashapeID) {
|
|
1970
|
-
questionViewTypes = ds.questionTypes;
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
if (questionViewTypes.length === 0) {
|
|
1974
|
-
const errorMsg = `No questionViewTypes found for datashapeID: ${datashapeID} in course config. Cards cannot be created.`;
|
|
1975
|
-
logger.error(errorMsg);
|
|
1976
|
-
throw new Error(errorMsg);
|
|
1977
|
-
}
|
|
1978
|
-
for (const questionView of questionViewTypes) {
|
|
1979
|
-
await createCard(questionView, courseID, dsDescriptor, noteID, tags, elo, author);
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
async function createCard(questionViewName, courseID, dsDescriptor, noteID, tags, elo = (0, import_common4.blankCourseElo)(), author) {
|
|
1983
|
-
const qDescriptor = import_common3.NameSpacer.getQuestionDescriptor(questionViewName);
|
|
1984
|
-
const cfg = await getCredentialledCourseConfig(courseID);
|
|
1985
|
-
for (const rQ of cfg.questionTypes) {
|
|
1986
|
-
if (rQ.name === questionViewName) {
|
|
1987
|
-
for (const view of rQ.viewList) {
|
|
1988
|
-
await addCard(
|
|
1989
|
-
courseID,
|
|
1990
|
-
dsDescriptor.course,
|
|
1991
|
-
[noteID],
|
|
1992
|
-
import_common3.NameSpacer.getViewString({
|
|
1993
|
-
course: qDescriptor.course,
|
|
1994
|
-
questionType: qDescriptor.questionType,
|
|
1995
|
-
view
|
|
1996
|
-
}),
|
|
1997
|
-
elo,
|
|
1998
|
-
tags,
|
|
1999
|
-
author
|
|
2000
|
-
);
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
async function addCard(courseID, course, id_displayable_data, id_view, elo, tags, author) {
|
|
2006
|
-
const db = getCourseDB2(courseID);
|
|
2007
|
-
const card = await db.post({
|
|
2008
|
-
course,
|
|
2009
|
-
id_displayable_data,
|
|
2010
|
-
id_view,
|
|
2011
|
-
docType: "CARD" /* CARD */,
|
|
2012
|
-
elo: elo || (0, import_common4.toCourseElo)(990 + Math.round(20 * Math.random())),
|
|
2013
|
-
author
|
|
2014
|
-
});
|
|
2015
|
-
for (const tag of tags) {
|
|
2016
|
-
logger.info(`adding tag: ${tag} to card ${card.id}`);
|
|
2017
|
-
await addTagToCard(courseID, card.id, tag, author, false);
|
|
2018
|
-
}
|
|
2019
|
-
return card;
|
|
2020
|
-
}
|
|
2021
|
-
async function getCredentialledCourseConfig(courseID) {
|
|
2022
|
-
try {
|
|
2023
|
-
const db = getCourseDB2(courseID);
|
|
2024
|
-
const ret = await db.get("CourseConfig");
|
|
2025
|
-
ret.courseID = courseID;
|
|
2026
|
-
logger.info(`Returning course config: ${JSON.stringify(ret)}`);
|
|
2027
|
-
return ret;
|
|
2028
|
-
} catch (e) {
|
|
2029
|
-
logger.error(`Error fetching config for ${courseID}:`, e);
|
|
2030
|
-
throw e;
|
|
2399
|
+
// src/factory.ts
|
|
2400
|
+
function getDataLayer() {
|
|
2401
|
+
if (!dataLayerInstance) {
|
|
2402
|
+
throw new Error("Data layer not initialized. Call initializeDataLayer first.");
|
|
2031
2403
|
}
|
|
2404
|
+
return dataLayerInstance;
|
|
2032
2405
|
}
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
const courseApi = new CourseDB(courseID, async () => {
|
|
2037
|
-
const dummySyncStrategy = {
|
|
2038
|
-
setupRemoteDB: () => null,
|
|
2039
|
-
startSync: () => {
|
|
2040
|
-
},
|
|
2041
|
-
canCreateAccount: () => false,
|
|
2042
|
-
canAuthenticate: () => false,
|
|
2043
|
-
getCurrentUsername: async () => "DummyUser"
|
|
2044
|
-
};
|
|
2045
|
-
return BaseUser.Dummy(dummySyncStrategy);
|
|
2046
|
-
});
|
|
2047
|
-
try {
|
|
2048
|
-
logger.info(`Applying tag ${tagID} to card ${courseID + "-" + cardID}...`);
|
|
2049
|
-
const tag = await courseDB.get(prefixedTagID);
|
|
2050
|
-
if (!tag.taggedCards.includes(cardID)) {
|
|
2051
|
-
tag.taggedCards.push(cardID);
|
|
2052
|
-
if (updateELO) {
|
|
2053
|
-
try {
|
|
2054
|
-
const eloData = await courseApi.getCardEloData([cardID]);
|
|
2055
|
-
const elo = eloData[0];
|
|
2056
|
-
elo.tags[tagID] = {
|
|
2057
|
-
count: 0,
|
|
2058
|
-
score: elo.global.score
|
|
2059
|
-
// todo: or 1000?
|
|
2060
|
-
};
|
|
2061
|
-
await updateCardElo(courseID, cardID, elo);
|
|
2062
|
-
} catch (error) {
|
|
2063
|
-
logger.error("Failed to update ELO data for card:", cardID, error);
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2066
|
-
return courseDB.put(tag);
|
|
2067
|
-
} else throw new AlreadyTaggedErr(`Card ${cardID} is already tagged with ${tagID}`);
|
|
2068
|
-
} catch (e) {
|
|
2069
|
-
if (e instanceof AlreadyTaggedErr) {
|
|
2070
|
-
throw e;
|
|
2071
|
-
}
|
|
2072
|
-
await createTag(courseID, tagID, author);
|
|
2073
|
-
return addTagToCard(courseID, cardID, tagID, author, updateELO);
|
|
2074
|
-
}
|
|
2075
|
-
}
|
|
2076
|
-
async function updateCardElo(courseID, cardID, elo) {
|
|
2077
|
-
if (elo) {
|
|
2078
|
-
const cDB = getCourseDB2(courseID);
|
|
2079
|
-
const card = await cDB.get(cardID);
|
|
2080
|
-
logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);
|
|
2081
|
-
card.elo = elo;
|
|
2082
|
-
return cDB.put(card);
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
function getTagID(tagName) {
|
|
2086
|
-
const tagPrefix = "TAG" /* TAG */.valueOf() + "-";
|
|
2087
|
-
if (tagName.indexOf(tagPrefix) === 0) {
|
|
2088
|
-
return tagName;
|
|
2089
|
-
} else {
|
|
2090
|
-
return tagPrefix + tagName;
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
function getCourseDB2(courseID) {
|
|
2094
|
-
const dbName = `coursedb-${courseID}`;
|
|
2095
|
-
return new pouchdb_setup_default(
|
|
2096
|
-
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
2097
|
-
pouchDBincludeCredentialsConfig
|
|
2098
|
-
);
|
|
2099
|
-
}
|
|
2100
|
-
var import_common3, import_common4, import_common5, AlreadyTaggedErr;
|
|
2101
|
-
var init_courseAPI = __esm({
|
|
2102
|
-
"src/impl/couch/courseAPI.ts"() {
|
|
2103
|
-
"use strict";
|
|
2104
|
-
init_pouchdb_setup();
|
|
2105
|
-
init_couch();
|
|
2106
|
-
init_factory();
|
|
2107
|
-
import_common3 = require("@vue-skuilder/common");
|
|
2108
|
-
import_common4 = require("@vue-skuilder/common");
|
|
2109
|
-
init_courseDB();
|
|
2110
|
-
init_types_legacy();
|
|
2111
|
-
import_common5 = require("@vue-skuilder/common");
|
|
2112
|
-
init_common();
|
|
2113
|
-
init_logger();
|
|
2114
|
-
AlreadyTaggedErr = class extends Error {
|
|
2115
|
-
constructor(message) {
|
|
2116
|
-
super(message);
|
|
2117
|
-
this.name = "AlreadyTaggedErr";
|
|
2118
|
-
}
|
|
2119
|
-
};
|
|
2120
|
-
}
|
|
2121
|
-
});
|
|
2122
|
-
|
|
2123
|
-
// src/impl/couch/auth.ts
|
|
2124
|
-
var init_auth = __esm({
|
|
2125
|
-
"src/impl/couch/auth.ts"() {
|
|
2126
|
-
"use strict";
|
|
2127
|
-
init_factory();
|
|
2128
|
-
init_types_legacy();
|
|
2129
|
-
init_logger();
|
|
2130
|
-
}
|
|
2131
|
-
});
|
|
2132
|
-
|
|
2133
|
-
// src/impl/couch/CouchDBSyncStrategy.ts
|
|
2134
|
-
var import_common7;
|
|
2135
|
-
var init_CouchDBSyncStrategy = __esm({
|
|
2136
|
-
"src/impl/couch/CouchDBSyncStrategy.ts"() {
|
|
2406
|
+
var ENV, dataLayerInstance;
|
|
2407
|
+
var init_factory = __esm({
|
|
2408
|
+
"src/factory.ts"() {
|
|
2137
2409
|
"use strict";
|
|
2138
|
-
init_factory();
|
|
2139
|
-
init_types_legacy();
|
|
2140
|
-
init_logger();
|
|
2141
|
-
import_common7 = require("@vue-skuilder/common");
|
|
2142
2410
|
init_common();
|
|
2143
|
-
init_pouchdb_setup();
|
|
2144
|
-
init_couch();
|
|
2145
|
-
init_auth();
|
|
2146
|
-
}
|
|
2147
|
-
});
|
|
2148
|
-
|
|
2149
|
-
// src/impl/couch/index.ts
|
|
2150
|
-
function getCourseDB(courseID) {
|
|
2151
|
-
return new pouchdb_setup_default(
|
|
2152
|
-
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "coursedb-" + courseID,
|
|
2153
|
-
pouchDBincludeCredentialsConfig
|
|
2154
|
-
);
|
|
2155
|
-
}
|
|
2156
|
-
function getCourseDocs(courseID, docIDs, options = {}) {
|
|
2157
|
-
return getCourseDB(courseID).allDocs({
|
|
2158
|
-
...options,
|
|
2159
|
-
keys: docIDs
|
|
2160
|
-
});
|
|
2161
|
-
}
|
|
2162
|
-
function getCourseDoc(courseID, docID, options = {}) {
|
|
2163
|
-
return getCourseDB(courseID).get(docID, options);
|
|
2164
|
-
}
|
|
2165
|
-
function filterAllDocsByPrefix(db, prefix, opts) {
|
|
2166
|
-
const options = {
|
|
2167
|
-
startkey: prefix,
|
|
2168
|
-
endkey: prefix + "\uFFF0",
|
|
2169
|
-
include_docs: true
|
|
2170
|
-
};
|
|
2171
|
-
if (opts) {
|
|
2172
|
-
Object.assign(options, opts);
|
|
2173
|
-
}
|
|
2174
|
-
return db.allDocs(options);
|
|
2175
|
-
}
|
|
2176
|
-
function getStartAndEndKeys(key) {
|
|
2177
|
-
return {
|
|
2178
|
-
startkey: key,
|
|
2179
|
-
endkey: key + "\uFFF0"
|
|
2180
|
-
};
|
|
2181
|
-
}
|
|
2182
|
-
var import_moment4, import_process, isBrowser, GUEST_LOCAL_DB, localUserDB, pouchDBincludeCredentialsConfig, REVIEW_PREFIX2, REVIEW_TIME_FORMAT2;
|
|
2183
|
-
var init_couch = __esm({
|
|
2184
|
-
"src/impl/couch/index.ts"() {
|
|
2185
|
-
"use strict";
|
|
2186
|
-
init_factory();
|
|
2187
|
-
init_types_legacy();
|
|
2188
|
-
import_moment4 = __toESM(require("moment"));
|
|
2189
|
-
init_logger();
|
|
2190
|
-
init_pouchdb_setup();
|
|
2191
|
-
import_process = __toESM(require("process"));
|
|
2192
|
-
init_contentSource();
|
|
2193
|
-
init_adminDB2();
|
|
2194
|
-
init_classroomDB2();
|
|
2195
|
-
init_courseAPI();
|
|
2196
|
-
init_courseDB();
|
|
2197
|
-
init_CouchDBSyncStrategy();
|
|
2198
|
-
isBrowser = typeof window !== "undefined";
|
|
2199
|
-
if (isBrowser) {
|
|
2200
|
-
window.process = import_process.default;
|
|
2201
|
-
}
|
|
2202
|
-
GUEST_LOCAL_DB = `userdb-${GuestUsername}`;
|
|
2203
|
-
localUserDB = new pouchdb_setup_default(GUEST_LOCAL_DB);
|
|
2204
|
-
pouchDBincludeCredentialsConfig = {
|
|
2205
|
-
fetch(url, opts) {
|
|
2206
|
-
opts.credentials = "include";
|
|
2207
|
-
return pouchdb_setup_default.fetch(url, opts);
|
|
2208
|
-
}
|
|
2209
|
-
};
|
|
2210
|
-
REVIEW_PREFIX2 = "card_review_";
|
|
2211
|
-
REVIEW_TIME_FORMAT2 = "YYYY-MM-DD--kk:mm:ss-SSS";
|
|
2212
|
-
}
|
|
2213
|
-
});
|
|
2214
|
-
|
|
2215
|
-
// src/impl/couch/classroomDB.ts
|
|
2216
|
-
var import_moment5, CLASSROOM_CONFIG, ClassroomDBBase, StudentClassroomDB;
|
|
2217
|
-
var init_classroomDB2 = __esm({
|
|
2218
|
-
"src/impl/couch/classroomDB.ts"() {
|
|
2219
|
-
"use strict";
|
|
2220
|
-
init_factory();
|
|
2221
2411
|
init_logger();
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
init_courseDB();
|
|
2226
|
-
CLASSROOM_CONFIG = "ClassroomConfig";
|
|
2227
|
-
ClassroomDBBase = class {
|
|
2228
|
-
_id;
|
|
2229
|
-
_db;
|
|
2230
|
-
_cfg;
|
|
2231
|
-
_initComplete = false;
|
|
2232
|
-
_content_prefix = "content";
|
|
2233
|
-
get _content_searchkeys() {
|
|
2234
|
-
return getStartAndEndKeys(this._content_prefix);
|
|
2235
|
-
}
|
|
2236
|
-
async getAssignedContent() {
|
|
2237
|
-
logger.info(`Getting assigned content...`);
|
|
2238
|
-
const docRows = await this._db.allDocs({
|
|
2239
|
-
startkey: this._content_prefix,
|
|
2240
|
-
endkey: this._content_prefix + `\uFFF0`,
|
|
2241
|
-
include_docs: true
|
|
2242
|
-
});
|
|
2243
|
-
const ret = docRows.rows.map((row) => {
|
|
2244
|
-
return row.doc;
|
|
2245
|
-
});
|
|
2246
|
-
return ret;
|
|
2247
|
-
}
|
|
2248
|
-
getContentId(content) {
|
|
2249
|
-
if (content.type === "tag") {
|
|
2250
|
-
return `${this._content_prefix}-${content.courseID}-${content.tagID}`;
|
|
2251
|
-
} else {
|
|
2252
|
-
return `${this._content_prefix}-${content.courseID}`;
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
get ready() {
|
|
2256
|
-
return this._initComplete;
|
|
2257
|
-
}
|
|
2258
|
-
getConfig() {
|
|
2259
|
-
return this._cfg;
|
|
2260
|
-
}
|
|
2261
|
-
};
|
|
2262
|
-
StudentClassroomDB = class _StudentClassroomDB extends ClassroomDBBase {
|
|
2263
|
-
// private readonly _prefix: string = 'content';
|
|
2264
|
-
userMessages;
|
|
2265
|
-
_user;
|
|
2266
|
-
constructor(classID, user) {
|
|
2267
|
-
super();
|
|
2268
|
-
this._id = classID;
|
|
2269
|
-
this._user = user;
|
|
2270
|
-
}
|
|
2271
|
-
async init() {
|
|
2272
|
-
const dbName = `classdb-student-${this._id}`;
|
|
2273
|
-
this._db = new pouchdb_setup_default(
|
|
2274
|
-
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
2275
|
-
pouchDBincludeCredentialsConfig
|
|
2276
|
-
);
|
|
2277
|
-
try {
|
|
2278
|
-
const cfg = await this._db.get(CLASSROOM_CONFIG);
|
|
2279
|
-
this._cfg = cfg;
|
|
2280
|
-
this.userMessages = this._db.changes({
|
|
2281
|
-
since: "now",
|
|
2282
|
-
live: true,
|
|
2283
|
-
include_docs: true
|
|
2284
|
-
});
|
|
2285
|
-
this._initComplete = true;
|
|
2286
|
-
return;
|
|
2287
|
-
} catch (e) {
|
|
2288
|
-
throw new Error(`Error in StudentClassroomDB constructor: ${JSON.stringify(e)}`);
|
|
2289
|
-
}
|
|
2290
|
-
}
|
|
2291
|
-
static async factory(classID, user) {
|
|
2292
|
-
const ret = new _StudentClassroomDB(classID, user);
|
|
2293
|
-
await ret.init();
|
|
2294
|
-
return ret;
|
|
2295
|
-
}
|
|
2296
|
-
setChangeFcn(f) {
|
|
2297
|
-
void this.userMessages.on("change", f);
|
|
2298
|
-
}
|
|
2299
|
-
async getPendingReviews() {
|
|
2300
|
-
const u = this._user;
|
|
2301
|
-
return (await u.getPendingReviews()).filter((r) => r.scheduledFor === "classroom" && r.schedulingAgentId === this._id).map((r) => {
|
|
2302
|
-
return {
|
|
2303
|
-
...r,
|
|
2304
|
-
qualifiedID: `${r.courseId}-${r.cardId}`,
|
|
2305
|
-
courseID: r.courseId,
|
|
2306
|
-
cardID: r.cardId,
|
|
2307
|
-
contentSourceType: "classroom",
|
|
2308
|
-
contentSourceID: this._id,
|
|
2309
|
-
reviewID: r._id,
|
|
2310
|
-
status: "review"
|
|
2311
|
-
};
|
|
2312
|
-
});
|
|
2313
|
-
}
|
|
2314
|
-
async getNewCards() {
|
|
2315
|
-
const activeCards = await this._user.getActiveCards();
|
|
2316
|
-
const now = import_moment5.default.utc();
|
|
2317
|
-
const assigned = await this.getAssignedContent();
|
|
2318
|
-
const due = assigned.filter((c) => now.isAfter(import_moment5.default.utc(c.activeOn, REVIEW_TIME_FORMAT2)));
|
|
2319
|
-
logger.info(`Due content: ${JSON.stringify(due)}`);
|
|
2320
|
-
let ret = [];
|
|
2321
|
-
for (let i = 0; i < due.length; i++) {
|
|
2322
|
-
const content = due[i];
|
|
2323
|
-
if (content.type === "course") {
|
|
2324
|
-
const db = new CourseDB(content.courseID, async () => this._user);
|
|
2325
|
-
ret = ret.concat(await db.getNewCards());
|
|
2326
|
-
} else if (content.type === "tag") {
|
|
2327
|
-
const tagDoc = await getTag(content.courseID, content.tagID);
|
|
2328
|
-
ret = ret.concat(
|
|
2329
|
-
tagDoc.taggedCards.map((c) => {
|
|
2330
|
-
return {
|
|
2331
|
-
courseID: content.courseID,
|
|
2332
|
-
cardID: c,
|
|
2333
|
-
qualifiedID: `${content.courseID}-${c}`,
|
|
2334
|
-
contentSourceType: "classroom",
|
|
2335
|
-
contentSourceID: this._id,
|
|
2336
|
-
status: "new"
|
|
2337
|
-
};
|
|
2338
|
-
})
|
|
2339
|
-
);
|
|
2340
|
-
} else if (content.type === "card") {
|
|
2341
|
-
ret.push(await getCourseDB(content.courseID).get(content.cardID));
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
logger.info(`New Cards from classroom ${this._cfg.name}: ${ret.map((c) => c.qualifiedID)}`);
|
|
2345
|
-
return ret.filter((c) => {
|
|
2346
|
-
if (activeCards.some((ac) => c.qualifiedID.includes(ac))) {
|
|
2347
|
-
return false;
|
|
2348
|
-
} else {
|
|
2349
|
-
return true;
|
|
2350
|
-
}
|
|
2351
|
-
});
|
|
2352
|
-
}
|
|
2412
|
+
ENV = {
|
|
2413
|
+
COUCHDB_SERVER_PROTOCOL: "NOT_SET",
|
|
2414
|
+
COUCHDB_SERVER_URL: "NOT_SET"
|
|
2353
2415
|
};
|
|
2416
|
+
dataLayerInstance = null;
|
|
2354
2417
|
}
|
|
2355
2418
|
});
|
|
2356
2419
|
|
|
@@ -2483,7 +2546,7 @@ elo: ${elo}`;
|
|
|
2483
2546
|
misc: {}
|
|
2484
2547
|
} : void 0
|
|
2485
2548
|
);
|
|
2486
|
-
if (result.status ===
|
|
2549
|
+
if (result.status === import_common10.Status.ok) {
|
|
2487
2550
|
return {
|
|
2488
2551
|
originalText,
|
|
2489
2552
|
status: "success",
|
|
@@ -2527,11 +2590,11 @@ function validateProcessorConfig(config) {
|
|
|
2527
2590
|
}
|
|
2528
2591
|
return { isValid: true };
|
|
2529
2592
|
}
|
|
2530
|
-
var
|
|
2593
|
+
var import_common10;
|
|
2531
2594
|
var init_cardProcessor = __esm({
|
|
2532
2595
|
"src/core/bulkImport/cardProcessor.ts"() {
|
|
2533
2596
|
"use strict";
|
|
2534
|
-
|
|
2597
|
+
import_common10 = require("@vue-skuilder/common");
|
|
2535
2598
|
init_logger();
|
|
2536
2599
|
}
|
|
2537
2600
|
});
|
|
@@ -2557,11 +2620,11 @@ var core_exports = {};
|
|
|
2557
2620
|
__export(core_exports, {
|
|
2558
2621
|
ContentNavigator: () => ContentNavigator,
|
|
2559
2622
|
DocType: () => DocType,
|
|
2623
|
+
DocTypePrefixes: () => DocTypePrefixes,
|
|
2560
2624
|
GuestUsername: () => GuestUsername,
|
|
2561
2625
|
Loggable: () => Loggable,
|
|
2562
2626
|
Navigators: () => Navigators,
|
|
2563
2627
|
areQuestionRecords: () => areQuestionRecords,
|
|
2564
|
-
cardHistoryPrefix: () => cardHistoryPrefix,
|
|
2565
2628
|
docIsDeleted: () => docIsDeleted,
|
|
2566
2629
|
getCardHistoryID: () => getCardHistoryID,
|
|
2567
2630
|
getStudySource: () => getStudySource,
|
|
@@ -2589,11 +2652,11 @@ init_core();
|
|
|
2589
2652
|
0 && (module.exports = {
|
|
2590
2653
|
ContentNavigator,
|
|
2591
2654
|
DocType,
|
|
2655
|
+
DocTypePrefixes,
|
|
2592
2656
|
GuestUsername,
|
|
2593
2657
|
Loggable,
|
|
2594
2658
|
Navigators,
|
|
2595
2659
|
areQuestionRecords,
|
|
2596
|
-
cardHistoryPrefix,
|
|
2597
2660
|
docIsDeleted,
|
|
2598
2661
|
getCardHistoryID,
|
|
2599
2662
|
getStudySource,
|