@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.
Files changed (70) hide show
  1. package/dist/{SyncStrategy-DnJRj-Xp.d.mts → SyncStrategy-CyATpyLQ.d.mts} +6 -0
  2. package/dist/{SyncStrategy-DnJRj-Xp.d.ts → SyncStrategy-CyATpyLQ.d.ts} +6 -0
  3. package/dist/core/index.d.mts +5 -5
  4. package/dist/core/index.d.ts +5 -5
  5. package/dist/core/index.js +825 -762
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/core/index.mjs +812 -750
  8. package/dist/core/index.mjs.map +1 -1
  9. package/dist/{dataLayerProvider-BZmLyBVw.d.mts → dataLayerProvider-BInqI_RF.d.mts} +1 -1
  10. package/dist/{dataLayerProvider-BuntXkCs.d.ts → dataLayerProvider-DqtNroSh.d.ts} +1 -1
  11. package/dist/impl/couch/index.d.mts +6 -6
  12. package/dist/impl/couch/index.d.ts +6 -6
  13. package/dist/impl/couch/index.js +2261 -2081
  14. package/dist/impl/couch/index.js.map +1 -1
  15. package/dist/impl/couch/index.mjs +2274 -2095
  16. package/dist/impl/couch/index.mjs.map +1 -1
  17. package/dist/impl/static/index.d.mts +8 -6
  18. package/dist/impl/static/index.d.ts +8 -6
  19. package/dist/impl/static/index.js +524 -1064
  20. package/dist/impl/static/index.js.map +1 -1
  21. package/dist/impl/static/index.mjs +515 -1058
  22. package/dist/impl/static/index.mjs.map +1 -1
  23. package/dist/index-CLL31bEy.d.ts +137 -0
  24. package/dist/index-CUNnL38E.d.mts +137 -0
  25. package/dist/index.d.mts +200 -9
  26. package/dist/index.d.ts +200 -9
  27. package/dist/index.js +4123 -2820
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +4119 -2830
  30. package/dist/index.mjs.map +1 -1
  31. package/dist/{types-D6SnlHPm.d.ts → types-BefDGkKa.d.ts} +1 -1
  32. package/dist/{types-DPRvCrIk.d.mts → types-DC-ckZug.d.mts} +1 -1
  33. package/dist/{types-legacy-WPe8CtO-.d.mts → types-legacy-Birv-Jx6.d.mts} +2 -2
  34. package/dist/{types-legacy-WPe8CtO-.d.ts → types-legacy-Birv-Jx6.d.ts} +2 -2
  35. package/dist/{userDB-D9EuWTp1.d.ts → userDB-C33Hzjgn.d.mts} +11 -4
  36. package/dist/{userDB-31gsvxyd.d.mts → userDB-DusL7OXe.d.ts} +11 -4
  37. package/dist/util/packer/index.d.mts +3 -63
  38. package/dist/util/packer/index.d.ts +3 -63
  39. package/dist/util/packer/index.js +53 -1
  40. package/dist/util/packer/index.js.map +1 -1
  41. package/dist/util/packer/index.mjs +53 -1
  42. package/dist/util/packer/index.mjs.map +1 -1
  43. package/package.json +7 -4
  44. package/src/core/types/types-legacy.ts +13 -1
  45. package/src/core/types/user.ts +9 -2
  46. package/src/core/util/index.ts +5 -4
  47. package/src/factory.ts +25 -0
  48. package/src/impl/common/BaseUserDB.ts +62 -28
  49. package/src/impl/common/SyncStrategy.ts +7 -0
  50. package/src/impl/common/index.ts +0 -1
  51. package/src/impl/common/userDBHelpers.ts +15 -5
  52. package/src/impl/couch/CouchDBSyncStrategy.ts +10 -0
  53. package/src/impl/couch/courseAPI.ts +7 -6
  54. package/src/impl/couch/courseLookupDB.ts +24 -0
  55. package/src/impl/couch/index.ts +10 -5
  56. package/src/impl/couch/updateQueue.ts +12 -8
  57. package/src/impl/couch/user-course-relDB.ts +17 -27
  58. package/src/impl/static/NoOpSyncStrategy.ts +5 -0
  59. package/src/impl/static/StaticDataUnpacker.ts +18 -36
  60. package/src/impl/static/courseDB.ts +135 -17
  61. package/src/util/dataDirectory.test.ts +53 -0
  62. package/src/util/dataDirectory.ts +52 -0
  63. package/src/util/index.ts +3 -0
  64. package/src/util/migrator/FileSystemAdapter.ts +79 -0
  65. package/src/util/migrator/StaticToCouchDBMigrator.ts +713 -0
  66. package/src/util/migrator/index.ts +18 -0
  67. package/src/util/migrator/types.ts +84 -0
  68. package/src/util/migrator/validation.ts +517 -0
  69. package/src/util/packer/CouchDBToStaticPacker.ts +92 -2
  70. package/src/util/tuiLogger.ts +139 -0
@@ -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) => (path) => {
9
- var fn = map[path];
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: " + path);
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/factory.ts
98
- function getDataLayer() {
99
- if (!dataLayerInstance) {
100
- throw new Error("Data layer not initialized. Call initializeDataLayer first.");
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
- ENV = {
110
- COUCHDB_SERVER_PROTOCOL: "NOT_SET",
111
- COUCHDB_SERVER_URL: "NOT_SET"
110
+ GuestUsername = "Guest";
111
+ log = (message) => {
112
+ logger.log(message);
112
113
  };
113
- dataLayerInstance = null;
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/core/types/types-legacy.ts
137
- var GuestUsername, log, DocType, cardHistoryPrefix;
138
- var init_types_legacy = __esm({
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
- init_logger();
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/impl/couch/courseLookupDB.ts
164
- var init_courseLookupDB = __esm({
165
- "src/impl/couch/courseLookupDB.ts"() {
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
- init_pouchdb_setup();
168
- init_factory();
169
- init_logger();
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/couch/adminDB.ts
175
- var init_adminDB2 = __esm({
176
- "src/impl/couch/adminDB.ts"() {
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
- init_pouchdb_setup();
179
- init_factory();
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
- db;
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(db) {
329
+ constructor(readDB, writeDB) {
225
330
  super();
226
- this.db = db;
331
+ this.readDB = readDB;
332
+ this.writeDB = writeDB || readDB;
227
333
  logger.debug(`UpdateQ initialized...`);
228
- void this.db.info().then((i) => {
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.db.info();
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.db.get(id);
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.db.put(doc);
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/core/navigators/elo.ts
300
- var elo_exports = {};
301
- __export(elo_exports, {
302
- default: () => ELONavigator
303
- });
304
- var ELONavigator;
305
- var init_elo = __esm({
306
- "src/core/navigators/elo.ts"() {
307
- "use strict";
308
- init_navigators();
309
- ELONavigator = class extends ContentNavigator {
310
- user;
311
- course;
312
- constructor(user, course) {
313
- super();
314
- this.user = user;
315
- this.course = course;
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
- async getPendingReviews() {
318
- const reviews = await this.user.getPendingReviews(this.course.getCourseID());
319
- const elo = await this.course.getCardEloData(reviews.map((r) => r.cardId));
320
- const ratedReviews = reviews.map((r, i) => {
321
- const ratedR = {
322
- ...r,
323
- ...elo[i]
324
- };
325
- return ratedR;
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 filterAllDocsByPrefix(
425
- getCourseDB(courseID),
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 = getCourseDB(courseID);
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 getCourseDB(tag.course).put({
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 = getCourseDB(courseID);
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 = getCourseDB(courseID);
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 = getCourseDB(id_course);
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 = getCourseDB(courseID);
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 import_common, CourseDB;
860
+ var import_common5, CourseDB;
495
861
  var init_courseDB = __esm({
496
862
  "src/impl/couch/courseDB.ts"() {
497
863
  "use strict";
498
- import_common = require("@vue-skuilder/common");
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 = getCourseDB(this.id);
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, import_common.toCourseElo)(r.doc.elo));
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, import_common.blankCourseElo)());
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, import_common.blankCourseElo)());
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, import_common.blankCourseElo)()) {
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: import_common.Status.error,
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: import_common.Status.ok,
1114
+ status: import_common5.Status.ok,
749
1115
  message: "",
750
1116
  id: resp.id
751
1117
  };
752
1118
  } else {
753
1119
  return {
754
- status: import_common.Status.error,
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: import_common.Status.error,
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, import_common.EloToNumber)(courseDoc.elo);
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/common/SyncStrategy.ts
922
- var init_SyncStrategy = __esm({
923
- "src/impl/common/SyncStrategy.ts"() {
924
- "use strict";
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
- init_types_legacy();
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
- init_logger();
1033
- UsrCrsData = class {
1034
- user;
1035
- course;
1036
- _courseId;
1037
- constructor(user, courseId) {
1038
- this.user = user;
1039
- this.course = new CourseDB(courseId, async () => this.user);
1040
- this._courseId = courseId;
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 getReviewsForcast(daysCount) {
1043
- const time = import_moment2.default.utc().add(daysCount, "days");
1044
- return this.getReviewstoDate(time);
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
- async getPendingReviews() {
1047
- const now = import_moment2.default.utc();
1048
- return this.getReviewstoDate(now);
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
- async getScheduledReviewCount() {
1051
- return (await this.getPendingReviews()).length;
1327
+ get ready() {
1328
+ return this._initComplete;
1052
1329
  }
1053
- async getCourseSettings() {
1054
- const regDoc = await this.user.getCourseRegistrationsDoc();
1055
- const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);
1056
- if (crsDoc && crsDoc.settings) {
1057
- return crsDoc.settings;
1058
- } else {
1059
- logger.warn(`no settings found during lookup on course ${this._courseId}`);
1060
- return {};
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
- updateCourseSettings(updates) {
1064
- void this.user.updateCourseSettings(this._courseId, updates);
1363
+ static async factory(classID, user) {
1364
+ const ret = new _StudentClassroomDB(classID, user);
1365
+ await ret.init();
1366
+ return ret;
1065
1367
  }
1066
- async getReviewstoDate(targetDate) {
1067
- const keys = getStartAndEndKeys(REVIEW_PREFIX2);
1068
- const reviews = await this.user.remote().allDocs({
1069
- startkey: keys.startkey,
1070
- endkey: keys.endkey,
1071
- include_docs: true
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
- logger.debug(
1074
- `Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`
1075
- );
1076
- return reviews.rows.filter((r) => {
1077
- if (r.id.startsWith(REVIEW_PREFIX2)) {
1078
- const date = import_moment2.default.utc(r.id.substr(REVIEW_PREFIX2.length), REVIEW_TIME_FORMAT2);
1079
- if (targetDate.isAfter(date)) {
1080
- if (this._courseId === void 0 || r.doc.courseId === this._courseId) {
1081
- return true;
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
- }).map((r) => r.doc);
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/common/BaseUserDB.ts
1092
- async function getOrCreateClassroomRegistrationsDoc(user) {
1093
- let ret;
1094
- try {
1095
- ret = await getLocalUserDB(user).get(userClassroomsDoc);
1096
- } catch (e) {
1097
- const err = e;
1098
- if (err.status === 404) {
1099
- await getLocalUserDB(user).put({
1100
- _id: userClassroomsDoc,
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
- return ret;
1122
- }
1123
- async function getOrCreateCourseRegistrationsDoc(user) {
1124
- let ret;
1125
- try {
1126
- ret = await getLocalUserDB(user).get(userCoursesDoc);
1127
- } catch (e) {
1128
- const err = e;
1129
- if (err.status === 404) {
1130
- await getLocalUserDB(user).put({
1131
- _id: userCoursesDoc,
1132
- courses: [],
1133
- studyWeight: {}
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 import_common2, import_moment3, log3, cardHistoryPrefix2, BaseUser, userCoursesDoc, userClassroomsDoc;
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
- import_common2 = require("@vue-skuilder/common");
1190
- import_moment3 = __toESM(require("moment"));
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 === import_common2.Status.ok) {
1682
+ if (result.status === import_common8.Status.ok) {
1239
1683
  log3(`Account created successfully, updating username to ${username}`);
1240
1684
  this._username = username;
1241
- localStorage.removeItem("dbUUID");
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
- throw new Error(`Cannot change accounts while logged in.
1255
- Log out of account ${this.getUsername()} before logging in as ${username}.`);
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
- localStorage.removeItem("dbUUID");
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: import_common2.Status.error,
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(cardHistoryPrefix2) || // Card interaction history
1279
- id.startsWith(REVIEW_PREFIX) || // Scheduled reviews
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: import_common2.Status.ok };
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: import_common2.Status.error,
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 = getStartAndEndKeys2(REVIEW_PREFIX);
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 = getStartAndEndKeys2(REVIEW_PREFIX);
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(REVIEW_PREFIX)) {
1431
- const date = import_moment3.default.utc(r.id.substr(REVIEW_PREFIX.length), REVIEW_TIME_FORMAT);
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 = import_moment3.default.utc().add(daysCount, "days");
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 = import_moment3.default.utc();
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.updateQueue = new UpdateQueue(this.localDB);
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 instanceof Error && error.name === "conflict") {
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 = import_moment3.default.utc(record.timeStamp).toString();
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 = import_moment3.default.utc(record2.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
- void this.remoteDB.put(initCardHistory);
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.remoteDB.remove(doc);
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 = cardHistoryPrefix2;
2255
+ let prefix = DocTypePrefixes["CARDRECORD" /* CARDRECORD */];
1789
2256
  if (course_id) {
1790
2257
  prefix += course_id;
1791
2258
  }
1792
- const docs = await filterAllDocsByPrefix2(this.localDB, prefix, {
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(cardHistoryPrefix2)) {
1798
- ret.push(row.id.substr(cardHistoryPrefix2.length));
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 filterAllDocsByPrefix2(
2275
+ const cards = await filterAllDocsByPrefix(
1809
2276
  this.remoteDB,
1810
- cardHistoryPrefix2,
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.remoteDB.put({
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.remoteDB, review);
2366
+ return scheduleCardReviewLocal(this.writeDB, review);
1900
2367
  }
1901
2368
  async removeScheduledCardReview(reviewId) {
1902
- return removeScheduledCardReviewLocal(this.remoteDB, reviewId);
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/impl/couch/courseAPI.ts
1933
- async function addNote55(courseID, codeCourse, shape, data, author, tags, uploads, elo = (0, import_common4.blankCourseElo)()) {
1934
- const db = getCourseDB2(courseID);
1935
- const payload = (0, import_common5.prepareNote55)(courseID, codeCourse, shape, data, author, tags, uploads);
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
- async function addTagToCard(courseID, cardID, tagID, author, updateELO = true) {
2034
- const prefixedTagID = getTagID(tagID);
2035
- const courseDB = getCourseDB2(courseID);
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
- import_moment5 = __toESM(require("moment"));
2223
- init_pouchdb_setup();
2224
- init_couch();
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 === import_common9.Status.ok) {
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 import_common9;
2593
+ var import_common10;
2531
2594
  var init_cardProcessor = __esm({
2532
2595
  "src/core/bulkImport/cardProcessor.ts"() {
2533
2596
  "use strict";
2534
- import_common9 = require("@vue-skuilder/common");
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,