@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
@@ -1,9 +1,9 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __glob = (map) => (path) => {
4
- var fn = map[path];
3
+ var __glob = (map) => (path2) => {
4
+ var fn = map[path2];
5
5
  if (fn) return fn();
6
- throw new Error("Module not found in bundle: " + path);
6
+ throw new Error("Module not found in bundle: " + path2);
7
7
  };
8
8
  var __esm = (fn, res) => function __init() {
9
9
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -27,6 +27,13 @@ var init_classroomDB = __esm({
27
27
  }
28
28
  });
29
29
 
30
+ // src/impl/common/SyncStrategy.ts
31
+ var init_SyncStrategy = __esm({
32
+ "src/impl/common/SyncStrategy.ts"() {
33
+ "use strict";
34
+ }
35
+ });
36
+
30
37
  // src/util/logger.ts
31
38
  var isDevelopment, logger;
32
39
  var init_logger = __esm({
@@ -72,23 +79,78 @@ var init_logger = __esm({
72
79
  }
73
80
  });
74
81
 
75
- // src/factory.ts
76
- function getDataLayer() {
77
- if (!dataLayerInstance) {
78
- throw new Error("Data layer not initialized. Call initializeDataLayer first.");
79
- }
80
- return dataLayerInstance;
81
- }
82
- var ENV, dataLayerInstance;
83
- var init_factory = __esm({
84
- "src/factory.ts"() {
82
+ // src/core/types/types-legacy.ts
83
+ var GuestUsername, log, DocType, DocTypePrefixes;
84
+ var init_types_legacy = __esm({
85
+ "src/core/types/types-legacy.ts"() {
85
86
  "use strict";
86
87
  init_logger();
87
- ENV = {
88
- COUCHDB_SERVER_PROTOCOL: "NOT_SET",
89
- COUCHDB_SERVER_URL: "NOT_SET"
88
+ GuestUsername = "Guest";
89
+ log = (message) => {
90
+ logger.log(message);
90
91
  };
91
- dataLayerInstance = null;
92
+ DocType = /* @__PURE__ */ ((DocType2) => {
93
+ DocType2["DISPLAYABLE_DATA"] = "DISPLAYABLE_DATA";
94
+ DocType2["CARD"] = "CARD";
95
+ DocType2["DATASHAPE"] = "DATASHAPE";
96
+ DocType2["QUESTIONTYPE"] = "QUESTION";
97
+ DocType2["VIEW"] = "VIEW";
98
+ DocType2["PEDAGOGY"] = "PEDAGOGY";
99
+ DocType2["CARDRECORD"] = "CARDRECORD";
100
+ DocType2["SCHEDULED_CARD"] = "SCHEDULED_CARD";
101
+ DocType2["TAG"] = "TAG";
102
+ DocType2["NAVIGATION_STRATEGY"] = "NAVIGATION_STRATEGY";
103
+ return DocType2;
104
+ })(DocType || {});
105
+ DocTypePrefixes = {
106
+ ["CARD" /* CARD */]: "c",
107
+ ["DISPLAYABLE_DATA" /* DISPLAYABLE_DATA */]: "dd",
108
+ ["TAG" /* TAG */]: "TAG",
109
+ ["CARDRECORD" /* CARDRECORD */]: "cardH",
110
+ ["SCHEDULED_CARD" /* SCHEDULED_CARD */]: "card_review_",
111
+ // Add other doctypes here as they get prefixed IDs
112
+ ["DATASHAPE" /* DATASHAPE */]: "DATASHAPE",
113
+ ["QUESTION" /* QUESTIONTYPE */]: "QUESTION",
114
+ ["VIEW" /* VIEW */]: "VIEW",
115
+ ["PEDAGOGY" /* PEDAGOGY */]: "PEDAGOGY",
116
+ ["NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */]: "NAVIGATION_STRATEGY"
117
+ };
118
+ }
119
+ });
120
+
121
+ // src/core/util/index.ts
122
+ function areQuestionRecords(h) {
123
+ return isQuestionRecord(h.records[0]);
124
+ }
125
+ function isQuestionRecord(c) {
126
+ return c.userAnswer !== void 0;
127
+ }
128
+ function getCardHistoryID(courseID, cardID) {
129
+ return `${DocTypePrefixes["CARDRECORD" /* CARDRECORD */]}-${courseID}-${cardID}`;
130
+ }
131
+ function parseCardHistoryID(id) {
132
+ const split = id.split("-");
133
+ let error = "";
134
+ error += split.length === 3 ? "" : `
135
+ given ID has incorrect number of '-' characters`;
136
+ error += split[0] === DocTypePrefixes["CARDRECORD" /* CARDRECORD */] ? "" : `
137
+ given ID does not start with ${DocTypePrefixes["CARDRECORD" /* CARDRECORD */]}`;
138
+ if (split.length === 3 && split[0] === DocTypePrefixes["CARDRECORD" /* CARDRECORD */]) {
139
+ return {
140
+ courseID: split[1],
141
+ cardID: split[2]
142
+ };
143
+ } else {
144
+ throw new Error("parseCardHistory Error:" + error);
145
+ }
146
+ }
147
+ function docIsDeleted(e) {
148
+ return Boolean(e?.error === "not_found" && e?.reason === "deleted");
149
+ }
150
+ var init_util = __esm({
151
+ "src/core/util/index.ts"() {
152
+ "use strict";
153
+ init_types_legacy();
92
154
  }
93
155
  });
94
156
 
@@ -111,54 +173,93 @@ var init_pouchdb_setup = __esm({
111
173
  }
112
174
  });
113
175
 
114
- // src/core/types/types-legacy.ts
115
- var GuestUsername, log, DocType, cardHistoryPrefix;
116
- var init_types_legacy = __esm({
117
- "src/core/types/types-legacy.ts"() {
176
+ // src/util/tuiLogger.ts
177
+ var init_tuiLogger = __esm({
178
+ "src/util/tuiLogger.ts"() {
118
179
  "use strict";
119
- init_logger();
120
- GuestUsername = "Guest";
121
- log = (message) => {
122
- logger.log(message);
123
- };
124
- DocType = /* @__PURE__ */ ((DocType2) => {
125
- DocType2["DISPLAYABLE_DATA"] = "DISPLAYABLE_DATA";
126
- DocType2["CARD"] = "CARD";
127
- DocType2["DATASHAPE"] = "DATASHAPE";
128
- DocType2["QUESTIONTYPE"] = "QUESTION";
129
- DocType2["VIEW"] = "VIEW";
130
- DocType2["PEDAGOGY"] = "PEDAGOGY";
131
- DocType2["CARDRECORD"] = "CARDRECORD";
132
- DocType2["SCHEDULED_CARD"] = "SCHEDULED_CARD";
133
- DocType2["TAG"] = "TAG";
134
- DocType2["NAVIGATION_STRATEGY"] = "NAVIGATION_STRATEGY";
135
- return DocType2;
136
- })(DocType || {});
137
- cardHistoryPrefix = "cardH";
180
+ init_dataDirectory();
138
181
  }
139
182
  });
140
183
 
141
- // src/impl/couch/courseLookupDB.ts
142
- var init_courseLookupDB = __esm({
143
- "src/impl/couch/courseLookupDB.ts"() {
184
+ // src/util/dataDirectory.ts
185
+ import * as path from "path";
186
+ import * as os from "os";
187
+ function getAppDataDirectory() {
188
+ return path.join(os.homedir(), ".tuilder");
189
+ }
190
+ function getDbPath(dbName) {
191
+ return path.join(getAppDataDirectory(), dbName);
192
+ }
193
+ var init_dataDirectory = __esm({
194
+ "src/util/dataDirectory.ts"() {
144
195
  "use strict";
145
- init_pouchdb_setup();
146
- init_factory();
147
- init_logger();
148
- logger.debug(`COURSELOOKUP FILE RUNNING`);
196
+ init_tuiLogger();
149
197
  }
150
198
  });
151
199
 
152
- // src/impl/couch/adminDB.ts
153
- var init_adminDB2 = __esm({
154
- "src/impl/couch/adminDB.ts"() {
200
+ // src/impl/common/userDBHelpers.ts
201
+ import moment from "moment";
202
+ function filterAllDocsByPrefix(db, prefix, opts) {
203
+ const options = {
204
+ startkey: prefix,
205
+ endkey: prefix + "\uFFF0",
206
+ include_docs: true
207
+ };
208
+ if (opts) {
209
+ Object.assign(options, opts);
210
+ }
211
+ return db.allDocs(options);
212
+ }
213
+ function getStartAndEndKeys(key) {
214
+ return {
215
+ startkey: key,
216
+ endkey: key + "\uFFF0"
217
+ };
218
+ }
219
+ function getLocalUserDB(username) {
220
+ const dbName = `userdb-${username}`;
221
+ if (typeof window === "undefined") {
222
+ return new pouchdb_setup_default(getDbPath(dbName), {});
223
+ } else {
224
+ return new pouchdb_setup_default(dbName, {});
225
+ }
226
+ }
227
+ function scheduleCardReviewLocal(userDB, review) {
228
+ const now = moment.utc();
229
+ logger.info(`Scheduling for review in: ${review.time.diff(now, "h") / 24} days`);
230
+ void userDB.put({
231
+ _id: DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */] + review.time.format(REVIEW_TIME_FORMAT),
232
+ cardId: review.card_id,
233
+ reviewTime: review.time.toISOString(),
234
+ courseId: review.course_id,
235
+ scheduledAt: now.toISOString(),
236
+ scheduledFor: review.scheduledFor,
237
+ schedulingAgentId: review.schedulingAgentId
238
+ });
239
+ }
240
+ async function removeScheduledCardReviewLocal(userDB, reviewDocID) {
241
+ const reviewDoc = await userDB.get(reviewDocID);
242
+ userDB.remove(reviewDoc).then((res) => {
243
+ if (res.ok) {
244
+ log2(`Removed Review Doc: ${reviewDocID}`);
245
+ }
246
+ }).catch((err) => {
247
+ log2(`Failed to remove Review Doc: ${reviewDocID},
248
+ ${JSON.stringify(err)}`);
249
+ });
250
+ }
251
+ var REVIEW_TIME_FORMAT, log2;
252
+ var init_userDBHelpers = __esm({
253
+ "src/impl/common/userDBHelpers.ts"() {
155
254
  "use strict";
156
- init_pouchdb_setup();
157
- init_factory();
158
- init_couch();
159
- init_classroomDB2();
160
- init_courseLookupDB();
255
+ init_core();
161
256
  init_logger();
257
+ init_pouchdb_setup();
258
+ init_dataDirectory();
259
+ REVIEW_TIME_FORMAT = "YYYY-MM-DD--kk:mm:ss-SSS";
260
+ log2 = (s) => {
261
+ logger.info(s);
262
+ };
162
263
  }
163
264
  });
164
265
 
@@ -189,7 +290,10 @@ var init_updateQueue = __esm({
189
290
  _className = "UpdateQueue";
190
291
  pendingUpdates = {};
191
292
  inprogressUpdates = {};
192
- db;
293
+ readDB;
294
+ // Database for read operations
295
+ writeDB;
296
+ // Database for write operations (local-first)
193
297
  update(id, update) {
194
298
  logger.debug(`Update requested on doc: ${id}`);
195
299
  if (this.pendingUpdates[id]) {
@@ -199,24 +303,25 @@ var init_updateQueue = __esm({
199
303
  }
200
304
  return this.applyUpdates(id);
201
305
  }
202
- constructor(db) {
306
+ constructor(readDB, writeDB) {
203
307
  super();
204
- this.db = db;
308
+ this.readDB = readDB;
309
+ this.writeDB = writeDB || readDB;
205
310
  logger.debug(`UpdateQ initialized...`);
206
- void this.db.info().then((i) => {
311
+ void this.readDB.info().then((i) => {
207
312
  logger.debug(`db info: ${JSON.stringify(i)}`);
208
313
  });
209
314
  }
210
315
  async applyUpdates(id) {
211
316
  logger.debug(`Applying updates on doc: ${id}`);
212
317
  if (this.inprogressUpdates[id]) {
213
- await this.db.info();
318
+ await this.readDB.info();
214
319
  return this.applyUpdates(id);
215
320
  } else {
216
321
  if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {
217
322
  this.inprogressUpdates[id] = true;
218
323
  try {
219
- let doc = await this.db.get(id);
324
+ let doc = await this.readDB.get(id);
220
325
  logger.debug(`Retrieved doc: ${id}`);
221
326
  while (this.pendingUpdates[id].length !== 0) {
222
327
  const update = this.pendingUpdates[id].splice(0, 1)[0];
@@ -229,7 +334,7 @@ var init_updateQueue = __esm({
229
334
  };
230
335
  }
231
336
  }
232
- await this.db.put(doc);
337
+ await this.writeDB.put(doc);
233
338
  logger.debug(`Put doc: ${id}`);
234
339
  if (this.pendingUpdates[id].length === 0) {
235
340
  this.inprogressUpdates[id] = false;
@@ -255,6 +360,60 @@ var init_updateQueue = __esm({
255
360
  }
256
361
  });
257
362
 
363
+ // src/impl/couch/user-course-relDB.ts
364
+ import moment2 from "moment";
365
+ var UsrCrsData;
366
+ var init_user_course_relDB = __esm({
367
+ "src/impl/couch/user-course-relDB.ts"() {
368
+ "use strict";
369
+ init_logger();
370
+ UsrCrsData = class {
371
+ user;
372
+ _courseId;
373
+ constructor(user, courseId) {
374
+ this.user = user;
375
+ this._courseId = courseId;
376
+ }
377
+ async getReviewsForcast(daysCount) {
378
+ const time = moment2.utc().add(daysCount, "days");
379
+ return this.getReviewstoDate(time);
380
+ }
381
+ async getPendingReviews() {
382
+ const now = moment2.utc();
383
+ return this.getReviewstoDate(now);
384
+ }
385
+ async getScheduledReviewCount() {
386
+ return (await this.getPendingReviews()).length;
387
+ }
388
+ async getCourseSettings() {
389
+ const regDoc = await this.user.getCourseRegistrationsDoc();
390
+ const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);
391
+ if (crsDoc && crsDoc.settings) {
392
+ return crsDoc.settings;
393
+ } else {
394
+ logger.warn(`no settings found during lookup on course ${this._courseId}`);
395
+ return {};
396
+ }
397
+ }
398
+ updateCourseSettings(updates) {
399
+ if ("updateCourseSettings" in this.user) {
400
+ void this.user.updateCourseSettings(this._courseId, updates);
401
+ }
402
+ }
403
+ async getReviewstoDate(targetDate) {
404
+ const allReviews = await this.user.getPendingReviews(this._courseId);
405
+ logger.debug(
406
+ `Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`
407
+ );
408
+ return allReviews.filter((review) => {
409
+ const reviewTime = moment2.utc(review.reviewTime);
410
+ return targetDate.isAfter(reviewTime);
411
+ });
412
+ }
413
+ };
414
+ }
415
+ });
416
+
258
417
  // src/impl/couch/clientCache.ts
259
418
  async function GET_CACHED(k, f) {
260
419
  if (CLIENT_CACHE[k]) {
@@ -274,30 +433,236 @@ var init_clientCache = __esm({
274
433
  }
275
434
  });
276
435
 
277
- // src/core/navigators/elo.ts
278
- var elo_exports = {};
279
- __export(elo_exports, {
280
- default: () => ELONavigator
281
- });
282
- var ELONavigator;
283
- var init_elo = __esm({
284
- "src/core/navigators/elo.ts"() {
285
- "use strict";
286
- init_navigators();
287
- ELONavigator = class extends ContentNavigator {
288
- user;
289
- course;
290
- constructor(user, course) {
291
- super();
292
- this.user = user;
293
- this.course = course;
436
+ // src/impl/couch/courseAPI.ts
437
+ import { NameSpacer } from "@vue-skuilder/common";
438
+ import { blankCourseElo, toCourseElo } from "@vue-skuilder/common";
439
+ import { prepareNote55 } from "@vue-skuilder/common";
440
+ import { v4 as uuidv4 } from "uuid";
441
+ async function addNote55(courseID, codeCourse, shape, data, author, tags, uploads, elo = blankCourseElo()) {
442
+ const db = getCourseDB(courseID);
443
+ const payload = prepareNote55(courseID, codeCourse, shape, data, author, tags, uploads);
444
+ const _id = `${DocTypePrefixes["DISPLAYABLE_DATA" /* DISPLAYABLE_DATA */]}-${uuidv4()}`;
445
+ const result = await db.put({ ...payload, _id });
446
+ const dataShapeId = NameSpacer.getDataShapeString({
447
+ course: codeCourse,
448
+ dataShape: shape.name
449
+ });
450
+ if (result.ok) {
451
+ try {
452
+ await createCards(courseID, dataShapeId, result.id, tags, elo, author);
453
+ } catch (error) {
454
+ let errorMessage = "Unknown error";
455
+ if (error instanceof Error) {
456
+ errorMessage = error.message;
457
+ } else if (error && typeof error === "object" && "reason" in error) {
458
+ errorMessage = error.reason;
459
+ } else if (error && typeof error === "object" && "message" in error) {
460
+ errorMessage = error.message;
461
+ } else {
462
+ errorMessage = String(error);
294
463
  }
295
- async getPendingReviews() {
296
- const reviews = await this.user.getPendingReviews(this.course.getCourseID());
297
- const elo = await this.course.getCardEloData(reviews.map((r) => r.cardId));
298
- const ratedReviews = reviews.map((r, i) => {
299
- const ratedR = {
300
- ...r,
464
+ logger.error(`[addNote55] Failed to create cards for note ${result.id}: ${errorMessage}`);
465
+ result.cardCreationFailed = true;
466
+ result.cardCreationError = errorMessage;
467
+ }
468
+ } else {
469
+ logger.error(`[addNote55] Error adding note. Result: ${JSON.stringify(result)}`);
470
+ }
471
+ return result;
472
+ }
473
+ async function createCards(courseID, datashapeID, noteID, tags, elo = blankCourseElo(), author) {
474
+ const cfg = await getCredentialledCourseConfig(courseID);
475
+ const dsDescriptor = NameSpacer.getDataShapeDescriptor(datashapeID);
476
+ let questionViewTypes = [];
477
+ for (const ds of cfg.dataShapes) {
478
+ if (ds.name === datashapeID) {
479
+ questionViewTypes = ds.questionTypes;
480
+ }
481
+ }
482
+ if (questionViewTypes.length === 0) {
483
+ const errorMsg = `No questionViewTypes found for datashapeID: ${datashapeID} in course config. Cards cannot be created.`;
484
+ logger.error(errorMsg);
485
+ throw new Error(errorMsg);
486
+ }
487
+ for (const questionView of questionViewTypes) {
488
+ await createCard(questionView, courseID, dsDescriptor, noteID, tags, elo, author);
489
+ }
490
+ }
491
+ async function createCard(questionViewName, courseID, dsDescriptor, noteID, tags, elo = blankCourseElo(), author) {
492
+ const qDescriptor = NameSpacer.getQuestionDescriptor(questionViewName);
493
+ const cfg = await getCredentialledCourseConfig(courseID);
494
+ for (const rQ of cfg.questionTypes) {
495
+ if (rQ.name === questionViewName) {
496
+ for (const view of rQ.viewList) {
497
+ await addCard(
498
+ courseID,
499
+ dsDescriptor.course,
500
+ [noteID],
501
+ NameSpacer.getViewString({
502
+ course: qDescriptor.course,
503
+ questionType: qDescriptor.questionType,
504
+ view
505
+ }),
506
+ elo,
507
+ tags,
508
+ author
509
+ );
510
+ }
511
+ }
512
+ }
513
+ }
514
+ async function addCard(courseID, course, id_displayable_data, id_view, elo, tags, author) {
515
+ const db = getCourseDB(courseID);
516
+ const _id = `${DocTypePrefixes["CARD" /* CARD */]}-${uuidv4()}`;
517
+ const card = await db.put({
518
+ _id,
519
+ course,
520
+ id_displayable_data,
521
+ id_view,
522
+ docType: "CARD" /* CARD */,
523
+ elo: elo || toCourseElo(990 + Math.round(20 * Math.random())),
524
+ author
525
+ });
526
+ for (const tag of tags) {
527
+ logger.info(`adding tag: ${tag} to card ${card.id}`);
528
+ await addTagToCard(courseID, card.id, tag, author, false);
529
+ }
530
+ return card;
531
+ }
532
+ async function getCredentialledCourseConfig(courseID) {
533
+ try {
534
+ const db = getCourseDB(courseID);
535
+ const ret = await db.get("CourseConfig");
536
+ ret.courseID = courseID;
537
+ logger.info(`Returning course config: ${JSON.stringify(ret)}`);
538
+ return ret;
539
+ } catch (e) {
540
+ logger.error(`Error fetching config for ${courseID}:`, e);
541
+ throw e;
542
+ }
543
+ }
544
+ async function addTagToCard(courseID, cardID, tagID, author, updateELO = true) {
545
+ const prefixedTagID = getTagID(tagID);
546
+ const courseDB = getCourseDB(courseID);
547
+ const courseApi = new CourseDB(courseID, async () => {
548
+ const dummySyncStrategy = {
549
+ setupRemoteDB: () => null,
550
+ startSync: () => {
551
+ },
552
+ canCreateAccount: () => false,
553
+ canAuthenticate: () => false,
554
+ getCurrentUsername: async () => "DummyUser"
555
+ };
556
+ return BaseUser.Dummy(dummySyncStrategy);
557
+ });
558
+ try {
559
+ logger.info(`Applying tag ${tagID} to card ${courseID + "-" + cardID}...`);
560
+ const tag = await courseDB.get(prefixedTagID);
561
+ if (!tag.taggedCards.includes(cardID)) {
562
+ tag.taggedCards.push(cardID);
563
+ if (updateELO) {
564
+ try {
565
+ const eloData = await courseApi.getCardEloData([cardID]);
566
+ const elo = eloData[0];
567
+ elo.tags[tagID] = {
568
+ count: 0,
569
+ score: elo.global.score
570
+ // todo: or 1000?
571
+ };
572
+ await updateCardElo(courseID, cardID, elo);
573
+ } catch (error) {
574
+ logger.error("Failed to update ELO data for card:", cardID, error);
575
+ }
576
+ }
577
+ return courseDB.put(tag);
578
+ } else throw new AlreadyTaggedErr(`Card ${cardID} is already tagged with ${tagID}`);
579
+ } catch (e) {
580
+ if (e instanceof AlreadyTaggedErr) {
581
+ throw e;
582
+ }
583
+ await createTag(courseID, tagID, author);
584
+ return addTagToCard(courseID, cardID, tagID, author, updateELO);
585
+ }
586
+ }
587
+ async function updateCardElo(courseID, cardID, elo) {
588
+ if (elo) {
589
+ const cDB = getCourseDB(courseID);
590
+ const card = await cDB.get(cardID);
591
+ logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);
592
+ card.elo = elo;
593
+ return cDB.put(card);
594
+ }
595
+ }
596
+ function getTagID(tagName) {
597
+ const tagPrefix = "TAG" /* TAG */.valueOf() + "-";
598
+ if (tagName.indexOf(tagPrefix) === 0) {
599
+ return tagName;
600
+ } else {
601
+ return tagPrefix + tagName;
602
+ }
603
+ }
604
+ function getCourseDB(courseID) {
605
+ const dbName = `coursedb-${courseID}`;
606
+ return new pouchdb_setup_default(
607
+ ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
608
+ pouchDBincludeCredentialsConfig
609
+ );
610
+ }
611
+ var AlreadyTaggedErr;
612
+ var init_courseAPI = __esm({
613
+ "src/impl/couch/courseAPI.ts"() {
614
+ "use strict";
615
+ init_pouchdb_setup();
616
+ init_couch();
617
+ init_factory();
618
+ init_courseDB();
619
+ init_types_legacy();
620
+ init_common();
621
+ init_logger();
622
+ AlreadyTaggedErr = class extends Error {
623
+ constructor(message) {
624
+ super(message);
625
+ this.name = "AlreadyTaggedErr";
626
+ }
627
+ };
628
+ }
629
+ });
630
+
631
+ // src/impl/couch/courseLookupDB.ts
632
+ var init_courseLookupDB = __esm({
633
+ "src/impl/couch/courseLookupDB.ts"() {
634
+ "use strict";
635
+ init_pouchdb_setup();
636
+ init_factory();
637
+ init_logger();
638
+ logger.debug(`COURSELOOKUP FILE RUNNING`);
639
+ }
640
+ });
641
+
642
+ // src/core/navigators/elo.ts
643
+ var elo_exports = {};
644
+ __export(elo_exports, {
645
+ default: () => ELONavigator
646
+ });
647
+ var ELONavigator;
648
+ var init_elo = __esm({
649
+ "src/core/navigators/elo.ts"() {
650
+ "use strict";
651
+ init_navigators();
652
+ ELONavigator = class extends ContentNavigator {
653
+ user;
654
+ course;
655
+ constructor(user, course) {
656
+ super();
657
+ this.user = user;
658
+ this.course = course;
659
+ }
660
+ async getPendingReviews() {
661
+ const reviews = await this.user.getPendingReviews(this.course.getCourseID());
662
+ const elo = await this.course.getCardEloData(reviews.map((r) => r.cardId));
663
+ const ratedReviews = reviews.map((r, i) => {
664
+ const ratedR = {
665
+ ...r,
301
666
  ...elo[i]
302
667
  };
303
668
  return ratedR;
@@ -397,16 +762,16 @@ var init_navigators = __esm({
397
762
  import {
398
763
  EloToNumber,
399
764
  Status,
400
- blankCourseElo,
401
- toCourseElo
765
+ blankCourseElo as blankCourseElo2,
766
+ toCourseElo as toCourseElo2
402
767
  } from "@vue-skuilder/common";
403
768
  function randIntWeightedTowardZero(n) {
404
769
  return Math.floor(Math.random() * Math.random() * Math.random() * n);
405
770
  }
406
771
  async function getCourseTagStubs(courseID) {
407
772
  logger.debug(`Getting tag stubs for course: ${courseID}`);
408
- const stubs = await filterAllDocsByPrefix(
409
- getCourseDB(courseID),
773
+ const stubs = await filterAllDocsByPrefix2(
774
+ getCourseDB2(courseID),
410
775
  "TAG" /* TAG */.valueOf() + "-"
411
776
  );
412
777
  stubs.rows.forEach((row) => {
@@ -417,7 +782,7 @@ async function getCourseTagStubs(courseID) {
417
782
  async function createTag(courseID, tagName, author) {
418
783
  logger.debug(`Creating tag: ${tagName}...`);
419
784
  const tagID = getTagID(tagName);
420
- const courseDB = getCourseDB(courseID);
785
+ const courseDB = getCourseDB2(courseID);
421
786
  const resp = await courseDB.put({
422
787
  course: courseID,
423
788
  docType: "TAG" /* TAG */,
@@ -432,19 +797,19 @@ async function createTag(courseID, tagName, author) {
432
797
  }
433
798
  async function updateTag(tag) {
434
799
  const prior = await getTag(tag.course, tag.name);
435
- return await getCourseDB(tag.course).put({
800
+ return await getCourseDB2(tag.course).put({
436
801
  ...tag,
437
802
  _rev: prior._rev
438
803
  });
439
804
  }
440
805
  async function getTag(courseID, tagName) {
441
806
  const tagID = getTagID(tagName);
442
- const courseDB = getCourseDB(courseID);
807
+ const courseDB = getCourseDB2(courseID);
443
808
  return courseDB.get(tagID);
444
809
  }
445
810
  async function removeTagFromCard(courseID, cardID, tagID) {
446
811
  tagID = getTagID(tagID);
447
- const courseDB = getCourseDB(courseID);
812
+ const courseDB = getCourseDB2(courseID);
448
813
  const tag = await courseDB.get(tagID);
449
814
  tag.taggedCards = tag.taggedCards.filter((taggedID) => {
450
815
  return cardID !== taggedID;
@@ -452,7 +817,7 @@ async function removeTagFromCard(courseID, cardID, tagID) {
452
817
  return courseDB.put(tag);
453
818
  }
454
819
  async function getAppliedTags(id_course, id_card) {
455
- const db = getCourseDB(id_course);
820
+ const db = getCourseDB2(id_course);
456
821
  const result = await db.query("getTags", {
457
822
  startkey: id_card,
458
823
  endkey: id_card
@@ -465,7 +830,7 @@ async function updateCredentialledCourseConfig(courseID, config) {
465
830
 
466
831
  ${JSON.stringify(config)}
467
832
  `);
468
- const db = getCourseDB(courseID);
833
+ const db = getCourseDB2(courseID);
469
834
  const old = await getCredentialledCourseConfig(courseID);
470
835
  return await db.put({
471
836
  ...config,
@@ -497,7 +862,7 @@ var init_courseDB = __esm({
497
862
  updateQueue;
498
863
  constructor(id, userLookup) {
499
864
  this.id = id;
500
- this.db = getCourseDB(this.id);
865
+ this.db = getCourseDB2(this.id);
501
866
  this._getCurrentUser = userLookup;
502
867
  this.updateQueue = new UpdateQueue(this.db);
503
868
  }
@@ -553,14 +918,14 @@ var init_courseDB = __esm({
553
918
  docs.rows.forEach((r) => {
554
919
  if (isSuccessRow(r)) {
555
920
  if (r.doc && r.doc.elo) {
556
- ret.push(toCourseElo(r.doc.elo));
921
+ ret.push(toCourseElo2(r.doc.elo));
557
922
  } else {
558
923
  logger.warn("no elo data for card: " + r.id);
559
- ret.push(blankCourseElo());
924
+ ret.push(blankCourseElo2());
560
925
  }
561
926
  } else {
562
927
  logger.warn("no elo data for card: " + JSON.stringify(r));
563
- ret.push(blankCourseElo());
928
+ ret.push(blankCourseElo2());
564
929
  }
565
930
  });
566
931
  return ret;
@@ -713,7 +1078,7 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
713
1078
  async getCourseTagStubs() {
714
1079
  return getCourseTagStubs(this.id);
715
1080
  }
716
- async addNote(codeCourse, shape, data, author, tags, uploads, elo = blankCourseElo()) {
1081
+ async addNote(codeCourse, shape, data, author, tags, uploads, elo = blankCourseElo2()) {
717
1082
  try {
718
1083
  const resp = await addNote55(this.id, codeCourse, shape, data, author, tags, uploads, elo);
719
1084
  if (resp.ok) {
@@ -901,223 +1266,298 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
901
1266
  }
902
1267
  });
903
1268
 
904
- // src/impl/common/SyncStrategy.ts
905
- var init_SyncStrategy = __esm({
906
- "src/impl/common/SyncStrategy.ts"() {
907
- "use strict";
908
- }
909
- });
910
-
911
- // src/core/util/index.ts
912
- function areQuestionRecords(h) {
913
- return isQuestionRecord(h.records[0]);
914
- }
915
- function isQuestionRecord(c) {
916
- return c.userAnswer !== void 0;
917
- }
918
- function getCardHistoryID(courseID, cardID) {
919
- return `${cardHistoryPrefix}-${courseID}-${cardID}`;
920
- }
921
- function parseCardHistoryID(id) {
922
- const split = id.split("-");
923
- let error = "";
924
- error += split.length === 3 ? "" : `
925
- given ID has incorrect number of '-' characters`;
926
- error += split[0] === cardHistoryPrefix ? "" : `
927
- given ID does not start with ${cardHistoryPrefix}`;
928
- if (split.length === 3 && split[0] === cardHistoryPrefix) {
929
- return {
930
- courseID: split[1],
931
- cardID: split[2]
932
- };
933
- } else {
934
- throw new Error("parseCardHistory Error:" + error);
935
- }
936
- }
937
- function docIsDeleted(e) {
938
- return Boolean(e?.error === "not_found" && e?.reason === "deleted");
939
- }
940
- var init_util = __esm({
941
- "src/core/util/index.ts"() {
942
- "use strict";
943
- init_types_legacy();
944
- }
945
- });
946
-
947
- // src/impl/common/userDBHelpers.ts
948
- import moment from "moment";
949
- function filterAllDocsByPrefix2(db, prefix, opts) {
950
- const options = {
951
- startkey: prefix,
952
- endkey: prefix + "\uFFF0",
953
- include_docs: true
954
- };
955
- if (opts) {
956
- Object.assign(options, opts);
957
- }
958
- return db.allDocs(options);
959
- }
960
- function getStartAndEndKeys2(key) {
961
- return {
962
- startkey: key,
963
- endkey: key + "\uFFF0"
964
- };
965
- }
966
- function getLocalUserDB(username) {
967
- return new pouchdb_setup_default(`userdb-${username}`, {});
968
- }
969
- function scheduleCardReviewLocal(userDB, review) {
970
- const now = moment.utc();
971
- logger.info(`Scheduling for review in: ${review.time.diff(now, "h") / 24} days`);
972
- void userDB.put({
973
- _id: REVIEW_PREFIX + review.time.format(REVIEW_TIME_FORMAT),
974
- cardId: review.card_id,
975
- reviewTime: review.time,
976
- courseId: review.course_id,
977
- scheduledAt: now,
978
- scheduledFor: review.scheduledFor,
979
- schedulingAgentId: review.schedulingAgentId
980
- });
981
- }
982
- async function removeScheduledCardReviewLocal(userDB, reviewDocID) {
983
- const reviewDoc = await userDB.get(reviewDocID);
984
- userDB.remove(reviewDoc).then((res) => {
985
- if (res.ok) {
986
- log2(`Removed Review Doc: ${reviewDocID}`);
987
- }
988
- }).catch((err) => {
989
- log2(`Failed to remove Review Doc: ${reviewDocID},
990
- ${JSON.stringify(err)}`);
991
- });
992
- }
993
- var REVIEW_PREFIX, REVIEW_TIME_FORMAT, log2;
994
- var init_userDBHelpers = __esm({
995
- "src/impl/common/userDBHelpers.ts"() {
1269
+ // src/impl/couch/classroomDB.ts
1270
+ import moment3 from "moment";
1271
+ var CLASSROOM_CONFIG, ClassroomDBBase, StudentClassroomDB;
1272
+ var init_classroomDB2 = __esm({
1273
+ "src/impl/couch/classroomDB.ts"() {
996
1274
  "use strict";
1275
+ init_factory();
997
1276
  init_logger();
998
1277
  init_pouchdb_setup();
999
- REVIEW_PREFIX = "card_review_";
1000
- REVIEW_TIME_FORMAT = "YYYY-MM-DD--kk:mm:ss-SSS";
1001
- log2 = (s) => {
1002
- logger.info(s);
1003
- };
1004
- }
1005
- });
1006
-
1007
- // src/impl/couch/user-course-relDB.ts
1008
- import moment2 from "moment";
1009
- var UsrCrsData;
1010
- var init_user_course_relDB = __esm({
1011
- "src/impl/couch/user-course-relDB.ts"() {
1012
- "use strict";
1013
1278
  init_couch();
1014
1279
  init_courseDB();
1015
- init_logger();
1016
- UsrCrsData = class {
1017
- user;
1018
- course;
1019
- _courseId;
1020
- constructor(user, courseId) {
1021
- this.user = user;
1022
- this.course = new CourseDB(courseId, async () => this.user);
1023
- this._courseId = courseId;
1280
+ CLASSROOM_CONFIG = "ClassroomConfig";
1281
+ ClassroomDBBase = class {
1282
+ _id;
1283
+ _db;
1284
+ _cfg;
1285
+ _initComplete = false;
1286
+ _content_prefix = "content";
1287
+ get _content_searchkeys() {
1288
+ return getStartAndEndKeys2(this._content_prefix);
1024
1289
  }
1025
- async getReviewsForcast(daysCount) {
1026
- const time = moment2.utc().add(daysCount, "days");
1027
- return this.getReviewstoDate(time);
1290
+ async getAssignedContent() {
1291
+ logger.info(`Getting assigned content...`);
1292
+ const docRows = await this._db.allDocs({
1293
+ startkey: this._content_prefix,
1294
+ endkey: this._content_prefix + `\uFFF0`,
1295
+ include_docs: true
1296
+ });
1297
+ const ret = docRows.rows.map((row) => {
1298
+ return row.doc;
1299
+ });
1300
+ return ret;
1028
1301
  }
1029
- async getPendingReviews() {
1030
- const now = moment2.utc();
1031
- return this.getReviewstoDate(now);
1302
+ getContentId(content) {
1303
+ if (content.type === "tag") {
1304
+ return `${this._content_prefix}-${content.courseID}-${content.tagID}`;
1305
+ } else {
1306
+ return `${this._content_prefix}-${content.courseID}`;
1307
+ }
1032
1308
  }
1033
- async getScheduledReviewCount() {
1034
- return (await this.getPendingReviews()).length;
1309
+ get ready() {
1310
+ return this._initComplete;
1035
1311
  }
1036
- async getCourseSettings() {
1037
- const regDoc = await this.user.getCourseRegistrationsDoc();
1038
- const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);
1039
- if (crsDoc && crsDoc.settings) {
1040
- return crsDoc.settings;
1041
- } else {
1042
- logger.warn(`no settings found during lookup on course ${this._courseId}`);
1043
- return {};
1312
+ getConfig() {
1313
+ return this._cfg;
1314
+ }
1315
+ };
1316
+ StudentClassroomDB = class _StudentClassroomDB extends ClassroomDBBase {
1317
+ // private readonly _prefix: string = 'content';
1318
+ userMessages;
1319
+ _user;
1320
+ constructor(classID, user) {
1321
+ super();
1322
+ this._id = classID;
1323
+ this._user = user;
1324
+ }
1325
+ async init() {
1326
+ const dbName = `classdb-student-${this._id}`;
1327
+ this._db = new pouchdb_setup_default(
1328
+ ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
1329
+ pouchDBincludeCredentialsConfig
1330
+ );
1331
+ try {
1332
+ const cfg = await this._db.get(CLASSROOM_CONFIG);
1333
+ this._cfg = cfg;
1334
+ this.userMessages = this._db.changes({
1335
+ since: "now",
1336
+ live: true,
1337
+ include_docs: true
1338
+ });
1339
+ this._initComplete = true;
1340
+ return;
1341
+ } catch (e) {
1342
+ throw new Error(`Error in StudentClassroomDB constructor: ${JSON.stringify(e)}`);
1044
1343
  }
1045
1344
  }
1046
- updateCourseSettings(updates) {
1047
- void this.user.updateCourseSettings(this._courseId, updates);
1345
+ static async factory(classID, user) {
1346
+ const ret = new _StudentClassroomDB(classID, user);
1347
+ await ret.init();
1348
+ return ret;
1048
1349
  }
1049
- async getReviewstoDate(targetDate) {
1050
- const keys = getStartAndEndKeys(REVIEW_PREFIX2);
1051
- const reviews = await this.user.remote().allDocs({
1052
- startkey: keys.startkey,
1053
- endkey: keys.endkey,
1054
- include_docs: true
1350
+ setChangeFcn(f) {
1351
+ void this.userMessages.on("change", f);
1352
+ }
1353
+ async getPendingReviews() {
1354
+ const u = this._user;
1355
+ return (await u.getPendingReviews()).filter((r) => r.scheduledFor === "classroom" && r.schedulingAgentId === this._id).map((r) => {
1356
+ return {
1357
+ ...r,
1358
+ qualifiedID: `${r.courseId}-${r.cardId}`,
1359
+ courseID: r.courseId,
1360
+ cardID: r.cardId,
1361
+ contentSourceType: "classroom",
1362
+ contentSourceID: this._id,
1363
+ reviewID: r._id,
1364
+ status: "review"
1365
+ };
1055
1366
  });
1056
- logger.debug(
1057
- `Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`
1058
- );
1059
- return reviews.rows.filter((r) => {
1060
- if (r.id.startsWith(REVIEW_PREFIX2)) {
1061
- const date = moment2.utc(r.id.substr(REVIEW_PREFIX2.length), REVIEW_TIME_FORMAT2);
1062
- if (targetDate.isAfter(date)) {
1063
- if (this._courseId === void 0 || r.doc.courseId === this._courseId) {
1064
- return true;
1065
- }
1066
- }
1367
+ }
1368
+ async getNewCards() {
1369
+ const activeCards = await this._user.getActiveCards();
1370
+ const now = moment3.utc();
1371
+ const assigned = await this.getAssignedContent();
1372
+ const due = assigned.filter((c) => now.isAfter(moment3.utc(c.activeOn, REVIEW_TIME_FORMAT2)));
1373
+ logger.info(`Due content: ${JSON.stringify(due)}`);
1374
+ let ret = [];
1375
+ for (let i = 0; i < due.length; i++) {
1376
+ const content = due[i];
1377
+ if (content.type === "course") {
1378
+ const db = new CourseDB(content.courseID, async () => this._user);
1379
+ ret = ret.concat(await db.getNewCards());
1380
+ } else if (content.type === "tag") {
1381
+ const tagDoc = await getTag(content.courseID, content.tagID);
1382
+ ret = ret.concat(
1383
+ tagDoc.taggedCards.map((c) => {
1384
+ return {
1385
+ courseID: content.courseID,
1386
+ cardID: c,
1387
+ qualifiedID: `${content.courseID}-${c}`,
1388
+ contentSourceType: "classroom",
1389
+ contentSourceID: this._id,
1390
+ status: "new"
1391
+ };
1392
+ })
1393
+ );
1394
+ } else if (content.type === "card") {
1395
+ ret.push(await getCourseDB2(content.courseID).get(content.cardID));
1067
1396
  }
1068
- }).map((r) => r.doc);
1397
+ }
1398
+ logger.info(`New Cards from classroom ${this._cfg.name}: ${ret.map((c) => c.qualifiedID)}`);
1399
+ return ret.filter((c) => {
1400
+ if (activeCards.some((ac) => c.qualifiedID.includes(ac))) {
1401
+ return false;
1402
+ } else {
1403
+ return true;
1404
+ }
1405
+ });
1069
1406
  }
1070
1407
  };
1071
1408
  }
1072
1409
  });
1073
1410
 
1074
- // src/impl/common/BaseUserDB.ts
1075
- import { Status as Status2 } from "@vue-skuilder/common";
1076
- import moment3 from "moment";
1077
- async function getOrCreateClassroomRegistrationsDoc(user) {
1078
- let ret;
1079
- try {
1080
- ret = await getLocalUserDB(user).get(userClassroomsDoc);
1081
- } catch (e) {
1082
- const err = e;
1083
- if (err.status === 404) {
1084
- await getLocalUserDB(user).put({
1085
- _id: userClassroomsDoc,
1086
- registrations: []
1087
- });
1088
- ret = await getOrCreateClassroomRegistrationsDoc(user);
1089
- } else {
1090
- const errorDetails = {
1091
- name: err.name,
1092
- status: err.status,
1093
- message: err.message,
1094
- reason: err.reason,
1095
- error: err.error
1096
- };
1097
- logger.error(
1098
- "Database error in getOrCreateClassroomRegistrationsDoc (standalone function):",
1099
- errorDetails
1100
- );
1101
- throw new Error(
1102
- `Database error accessing classroom registrations: ${err.message || err.name || "Unknown error"} (status: ${err.status})`
1103
- );
1104
- }
1411
+ // src/impl/couch/adminDB.ts
1412
+ var init_adminDB2 = __esm({
1413
+ "src/impl/couch/adminDB.ts"() {
1414
+ "use strict";
1415
+ init_pouchdb_setup();
1416
+ init_factory();
1417
+ init_couch();
1418
+ init_classroomDB2();
1419
+ init_courseLookupDB();
1420
+ init_logger();
1105
1421
  }
1106
- return ret;
1107
- }
1108
- async function getOrCreateCourseRegistrationsDoc(user) {
1109
- let ret;
1110
- try {
1111
- ret = await getLocalUserDB(user).get(userCoursesDoc);
1112
- } catch (e) {
1113
- const err = e;
1114
- if (err.status === 404) {
1115
- await getLocalUserDB(user).put({
1116
- _id: userCoursesDoc,
1117
- courses: [],
1118
- studyWeight: {}
1119
- });
1120
- ret = await getOrCreateCourseRegistrationsDoc(user);
1422
+ });
1423
+
1424
+ // src/impl/couch/auth.ts
1425
+ var init_auth = __esm({
1426
+ "src/impl/couch/auth.ts"() {
1427
+ "use strict";
1428
+ init_factory();
1429
+ init_types_legacy();
1430
+ init_logger();
1431
+ }
1432
+ });
1433
+
1434
+ // src/impl/couch/CouchDBSyncStrategy.ts
1435
+ import { Status as Status2 } from "@vue-skuilder/common";
1436
+ var init_CouchDBSyncStrategy = __esm({
1437
+ "src/impl/couch/CouchDBSyncStrategy.ts"() {
1438
+ "use strict";
1439
+ init_factory();
1440
+ init_types_legacy();
1441
+ init_logger();
1442
+ init_common();
1443
+ init_pouchdb_setup();
1444
+ init_couch();
1445
+ init_auth();
1446
+ }
1447
+ });
1448
+
1449
+ // src/impl/couch/index.ts
1450
+ import moment4 from "moment";
1451
+ import process2 from "process";
1452
+ function getCourseDB2(courseID) {
1453
+ return new pouchdb_setup_default(
1454
+ ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "coursedb-" + courseID,
1455
+ pouchDBincludeCredentialsConfig
1456
+ );
1457
+ }
1458
+ function getCourseDocs(courseID, docIDs, options = {}) {
1459
+ return getCourseDB2(courseID).allDocs({
1460
+ ...options,
1461
+ keys: docIDs
1462
+ });
1463
+ }
1464
+ function getCourseDoc(courseID, docID, options = {}) {
1465
+ return getCourseDB2(courseID).get(docID, options);
1466
+ }
1467
+ function filterAllDocsByPrefix2(db, prefix, opts) {
1468
+ const options = {
1469
+ startkey: prefix,
1470
+ endkey: prefix + "\uFFF0",
1471
+ include_docs: true
1472
+ };
1473
+ if (opts) {
1474
+ Object.assign(options, opts);
1475
+ }
1476
+ return db.allDocs(options);
1477
+ }
1478
+ function getStartAndEndKeys2(key) {
1479
+ return {
1480
+ startkey: key,
1481
+ endkey: key + "\uFFF0"
1482
+ };
1483
+ }
1484
+ var isBrowser, GUEST_LOCAL_DB, localUserDB, pouchDBincludeCredentialsConfig, REVIEW_TIME_FORMAT2;
1485
+ var init_couch = __esm({
1486
+ "src/impl/couch/index.ts"() {
1487
+ "use strict";
1488
+ init_factory();
1489
+ init_types_legacy();
1490
+ init_logger();
1491
+ init_pouchdb_setup();
1492
+ init_contentSource();
1493
+ init_adminDB2();
1494
+ init_classroomDB2();
1495
+ init_courseAPI();
1496
+ init_courseDB();
1497
+ init_CouchDBSyncStrategy();
1498
+ isBrowser = typeof window !== "undefined";
1499
+ if (isBrowser) {
1500
+ window.process = process2;
1501
+ }
1502
+ GUEST_LOCAL_DB = `userdb-${GuestUsername}`;
1503
+ localUserDB = new pouchdb_setup_default(GUEST_LOCAL_DB);
1504
+ pouchDBincludeCredentialsConfig = {
1505
+ fetch(url, opts) {
1506
+ opts.credentials = "include";
1507
+ return pouchdb_setup_default.fetch(url, opts);
1508
+ }
1509
+ };
1510
+ REVIEW_TIME_FORMAT2 = "YYYY-MM-DD--kk:mm:ss-SSS";
1511
+ }
1512
+ });
1513
+
1514
+ // src/impl/common/BaseUserDB.ts
1515
+ import { Status as Status3 } from "@vue-skuilder/common";
1516
+ import moment5 from "moment";
1517
+ async function getOrCreateClassroomRegistrationsDoc(user) {
1518
+ let ret;
1519
+ try {
1520
+ ret = await getLocalUserDB(user).get(userClassroomsDoc);
1521
+ } catch (e) {
1522
+ const err = e;
1523
+ if (err.status === 404) {
1524
+ await getLocalUserDB(user).put({
1525
+ _id: userClassroomsDoc,
1526
+ registrations: []
1527
+ });
1528
+ ret = await getOrCreateClassroomRegistrationsDoc(user);
1529
+ } else {
1530
+ const errorDetails = {
1531
+ name: err.name,
1532
+ status: err.status,
1533
+ message: err.message,
1534
+ reason: err.reason,
1535
+ error: err.error
1536
+ };
1537
+ logger.error(
1538
+ "Database error in getOrCreateClassroomRegistrationsDoc (standalone function):",
1539
+ errorDetails
1540
+ );
1541
+ throw new Error(
1542
+ `Database error accessing classroom registrations: ${err.message || err.name || "Unknown error"} (status: ${err.status})`
1543
+ );
1544
+ }
1545
+ }
1546
+ return ret;
1547
+ }
1548
+ async function getOrCreateCourseRegistrationsDoc(user) {
1549
+ let ret;
1550
+ try {
1551
+ ret = await getLocalUserDB(user).get(userCoursesDoc);
1552
+ } catch (e) {
1553
+ const err = e;
1554
+ if (err.status === 404) {
1555
+ await getLocalUserDB(user).put({
1556
+ _id: userCoursesDoc,
1557
+ courses: [],
1558
+ studyWeight: {}
1559
+ });
1560
+ ret = await getOrCreateCourseRegistrationsDoc(user);
1121
1561
  } else {
1122
1562
  throw new Error(
1123
1563
  `Unexpected error ${JSON.stringify(e)} in getOrCreateCourseRegistrationDoc...`
@@ -1166,10 +1606,11 @@ async function dropUserFromClassroom(user, classID) {
1166
1606
  async function getUserClassrooms(user) {
1167
1607
  return getOrCreateClassroomRegistrationsDoc(user);
1168
1608
  }
1169
- var log3, cardHistoryPrefix2, BaseUser, userCoursesDoc, userClassroomsDoc;
1609
+ var log3, BaseUser, userCoursesDoc, userClassroomsDoc;
1170
1610
  var init_BaseUserDB = __esm({
1171
1611
  "src/impl/common/BaseUserDB.ts"() {
1172
1612
  "use strict";
1613
+ init_core();
1173
1614
  init_util();
1174
1615
  init_types_legacy();
1175
1616
  init_logger();
@@ -1180,7 +1621,6 @@ var init_BaseUserDB = __esm({
1180
1621
  log3 = (s) => {
1181
1622
  logger.info(s);
1182
1623
  };
1183
- cardHistoryPrefix2 = "cardH-";
1184
1624
  BaseUser = class _BaseUser {
1185
1625
  static _instance;
1186
1626
  static _initialized = false;
@@ -1201,11 +1641,13 @@ var init_BaseUserDB = __esm({
1201
1641
  isLoggedIn() {
1202
1642
  return !this._username.startsWith(GuestUsername);
1203
1643
  }
1204
- remoteDB;
1205
1644
  remote() {
1206
1645
  return this.remoteDB;
1207
1646
  }
1208
1647
  localDB;
1648
+ remoteDB;
1649
+ writeDB;
1650
+ // Database to use for write operations (local-first approach)
1209
1651
  updateQueue;
1210
1652
  async createAccount(username, password) {
1211
1653
  if (!this.syncStrategy.canCreateAccount()) {
@@ -1218,10 +1660,14 @@ Currently logged-in as ${this._username}.`
1218
1660
  );
1219
1661
  }
1220
1662
  const result = await this.syncStrategy.createAccount(username, password);
1221
- if (result.status === Status2.ok) {
1663
+ if (result.status === Status3.ok) {
1222
1664
  log3(`Account created successfully, updating username to ${username}`);
1223
1665
  this._username = username;
1224
- localStorage.removeItem("dbUUID");
1666
+ try {
1667
+ localStorage.removeItem("dbUUID");
1668
+ } catch (e) {
1669
+ logger.warn("localStorage not available (Node.js environment):", e);
1670
+ }
1225
1671
  await this.init();
1226
1672
  }
1227
1673
  return {
@@ -1233,15 +1679,22 @@ Currently logged-in as ${this._username}.`
1233
1679
  if (!this.syncStrategy.canAuthenticate()) {
1234
1680
  throw new Error("Authentication not supported by current sync strategy");
1235
1681
  }
1236
- if (!this._username.startsWith(GuestUsername)) {
1237
- throw new Error(`Cannot change accounts while logged in.
1238
- Log out of account ${this.getUsername()} before logging in as ${username}.`);
1682
+ if (!this._username.startsWith(GuestUsername) && this._username != username) {
1683
+ if (this._username != username) {
1684
+ throw new Error(`Cannot change accounts while logged in.
1685
+ Log out of account ${this.getUsername()} before logging in as ${username}.`);
1686
+ }
1687
+ logger.warn(`User ${this._username} is already logged in, but executing login again.`);
1239
1688
  }
1240
1689
  const loginResult = await this.syncStrategy.authenticate(username, password);
1241
1690
  if (loginResult.ok) {
1242
1691
  log3(`Logged in as ${username}`);
1243
1692
  this._username = username;
1244
- localStorage.removeItem("dbUUID");
1693
+ try {
1694
+ localStorage.removeItem("dbUUID");
1695
+ } catch (e) {
1696
+ logger.warn("localStorage not available (Node.js environment):", e);
1697
+ }
1245
1698
  await this.init();
1246
1699
  }
1247
1700
  return loginResult;
@@ -1249,7 +1702,7 @@ Currently logged-in as ${this._username}.`
1249
1702
  async resetUserData() {
1250
1703
  if (this.syncStrategy.canAuthenticate()) {
1251
1704
  return {
1252
- status: Status2.error,
1705
+ status: Status3.error,
1253
1706
  error: "Reset user data is only available for local-only mode. Use logout instead for remote sync."
1254
1707
  };
1255
1708
  }
@@ -1258,8 +1711,8 @@ Currently logged-in as ${this._username}.`
1258
1711
  const allDocs = await localDB.allDocs({ include_docs: false });
1259
1712
  const docsToDelete = allDocs.rows.filter((row) => {
1260
1713
  const id = row.id;
1261
- return id.startsWith(cardHistoryPrefix2) || // Card interaction history
1262
- id.startsWith(REVIEW_PREFIX) || // Scheduled reviews
1714
+ return id.startsWith(DocTypePrefixes["CARDRECORD" /* CARDRECORD */]) || // Card interaction history
1715
+ id.startsWith(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]) || // Scheduled reviews
1263
1716
  id === _BaseUser.DOC_IDS.COURSE_REGISTRATIONS || // Course registrations
1264
1717
  id === _BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS || // Classroom registrations
1265
1718
  id === _BaseUser.DOC_IDS.CONFIG;
@@ -1268,11 +1721,11 @@ Currently logged-in as ${this._username}.`
1268
1721
  await localDB.bulkDocs(docsToDelete);
1269
1722
  }
1270
1723
  await this.init();
1271
- return { status: Status2.ok };
1724
+ return { status: Status3.ok };
1272
1725
  } catch (error) {
1273
1726
  logger.error("Failed to reset user data:", error);
1274
1727
  return {
1275
- status: Status2.error,
1728
+ status: Status3.error,
1276
1729
  error: error instanceof Error ? error.message : "Unknown error during reset"
1277
1730
  };
1278
1731
  }
@@ -1328,7 +1781,7 @@ Currently logged-in as ${this._username}.`
1328
1781
  *
1329
1782
  */
1330
1783
  async getActiveCards() {
1331
- const keys = getStartAndEndKeys2(REVIEW_PREFIX);
1784
+ const keys = getStartAndEndKeys(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]);
1332
1785
  const reviews = await this.remoteDB.allDocs({
1333
1786
  startkey: keys.startkey,
1334
1787
  endkey: keys.endkey,
@@ -1400,7 +1853,7 @@ Currently logged-in as ${this._username}.`
1400
1853
  }
1401
1854
  }
1402
1855
  async getReviewstoDate(targetDate, course_id) {
1403
- const keys = getStartAndEndKeys2(REVIEW_PREFIX);
1856
+ const keys = getStartAndEndKeys(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]);
1404
1857
  const reviews = await this.remoteDB.allDocs({
1405
1858
  startkey: keys.startkey,
1406
1859
  endkey: keys.endkey,
@@ -1410,8 +1863,11 @@ Currently logged-in as ${this._username}.`
1410
1863
  `Fetching ${this._username}'s scheduled reviews${course_id ? ` for course ${course_id}` : ""}.`
1411
1864
  );
1412
1865
  return reviews.rows.filter((r) => {
1413
- if (r.id.startsWith(REVIEW_PREFIX)) {
1414
- const date = moment3.utc(r.id.substr(REVIEW_PREFIX.length), REVIEW_TIME_FORMAT);
1866
+ if (r.id.startsWith(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */])) {
1867
+ const date = moment5.utc(
1868
+ r.id.substr(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */].length),
1869
+ REVIEW_TIME_FORMAT
1870
+ );
1415
1871
  if (targetDate.isAfter(date)) {
1416
1872
  if (course_id === void 0 || r.doc.courseId === course_id) {
1417
1873
  return true;
@@ -1421,11 +1877,11 @@ Currently logged-in as ${this._username}.`
1421
1877
  }).map((r) => r.doc);
1422
1878
  }
1423
1879
  async getReviewsForcast(daysCount) {
1424
- const time = moment3.utc().add(daysCount, "days");
1880
+ const time = moment5.utc().add(daysCount, "days");
1425
1881
  return this.getReviewstoDate(time);
1426
1882
  }
1427
1883
  async getPendingReviews(course_id) {
1428
- const now = moment3.utc();
1884
+ const now = moment5.utc();
1429
1885
  return this.getReviewstoDate(now, course_id);
1430
1886
  }
1431
1887
  async getScheduledReviewCount(course_id) {
@@ -1526,7 +1982,8 @@ Currently logged-in as ${this._username}.`
1526
1982
  const defaultConfig = {
1527
1983
  _id: _BaseUser.DOC_IDS.CONFIG,
1528
1984
  darkMode: false,
1529
- likesConfetti: false
1985
+ likesConfetti: false,
1986
+ sessionTimeLimit: 5
1530
1987
  };
1531
1988
  try {
1532
1989
  const cfg = await this.localDB.get(_BaseUser.DOC_IDS.CONFIG);
@@ -1599,10 +2056,15 @@ Currently logged-in as ${this._username}.`
1599
2056
  setDBandQ() {
1600
2057
  this.localDB = getLocalUserDB(this._username);
1601
2058
  this.remoteDB = this.syncStrategy.setupRemoteDB(this._username);
1602
- this.updateQueue = new UpdateQueue(this.localDB);
2059
+ this.writeDB = this.syncStrategy.getWriteDB ? this.syncStrategy.getWriteDB(this._username) : this.localDB;
2060
+ this.updateQueue = new UpdateQueue(this.localDB, this.writeDB);
1603
2061
  }
1604
2062
  async init() {
1605
2063
  _BaseUser._initialized = false;
2064
+ if (this._username === "admin") {
2065
+ _BaseUser._initialized = true;
2066
+ return;
2067
+ }
1606
2068
  this.setDBandQ();
1607
2069
  this.syncStrategy.startSync(this.localDB, this.remoteDB);
1608
2070
  void this.applyDesignDocs();
@@ -1624,6 +2086,9 @@ Currently logged-in as ${this._username}.`
1624
2086
  }
1625
2087
  ];
1626
2088
  async applyDesignDocs() {
2089
+ if (this._username === "admin") {
2090
+ return;
2091
+ }
1627
2092
  for (const doc of _BaseUser.designDocs) {
1628
2093
  try {
1629
2094
  try {
@@ -1640,7 +2105,7 @@ Currently logged-in as ${this._username}.`
1640
2105
  }
1641
2106
  }
1642
2107
  } catch (error) {
1643
- if (error instanceof Error && error.name === "conflict") {
2108
+ if (error.name && error.name === "conflict") {
1644
2109
  logger.warn(`Design doc ${doc._id} update conflict - will retry`);
1645
2110
  await new Promise((resolve) => setTimeout(resolve, 1e3));
1646
2111
  await this.applyDesignDoc(doc);
@@ -1678,7 +2143,7 @@ Currently logged-in as ${this._username}.`
1678
2143
  */
1679
2144
  async putCardRecord(record) {
1680
2145
  const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);
1681
- record.timeStamp = moment3.utc(record.timeStamp).toString();
2146
+ record.timeStamp = moment5.utc(record.timeStamp).toString();
1682
2147
  try {
1683
2148
  const cardHistory = await this.update(
1684
2149
  cardHistoryID,
@@ -1694,7 +2159,7 @@ Currently logged-in as ${this._username}.`
1694
2159
  const ret = {
1695
2160
  ...record2
1696
2161
  };
1697
- ret.timeStamp = moment3.utc(record2.timeStamp);
2162
+ ret.timeStamp = moment5.utc(record2.timeStamp);
1698
2163
  return ret;
1699
2164
  });
1700
2165
  return cardHistory;
@@ -1710,8 +2175,8 @@ Currently logged-in as ${this._username}.`
1710
2175
  streak: 0,
1711
2176
  bestInterval: 0
1712
2177
  };
1713
- void this.remoteDB.put(initCardHistory);
1714
- return initCardHistory;
2178
+ const putResult = await this.writeDB.put(initCardHistory);
2179
+ return { ...initCardHistory, _rev: putResult.rev };
1715
2180
  } else {
1716
2181
  throw new Error(`putCardRecord failed because of:
1717
2182
  name:${reason.name}
@@ -1746,7 +2211,7 @@ Currently logged-in as ${this._username}.`
1746
2211
  const deletePromises = duplicateDocIds.map(async (docId) => {
1747
2212
  try {
1748
2213
  const doc = await this.remoteDB.get(docId);
1749
- await this.remoteDB.remove(doc);
2214
+ await this.writeDB.remove(doc);
1750
2215
  log3(`Successfully removed duplicate review: ${docId}`);
1751
2216
  } catch (error) {
1752
2217
  log3(`Failed to remove duplicate review ${docId}: ${error}`);
@@ -1768,17 +2233,17 @@ Currently logged-in as ${this._username}.`
1768
2233
  * @param course_id optional specification of individual course
1769
2234
  */
1770
2235
  async getSeenCards(course_id) {
1771
- let prefix = cardHistoryPrefix2;
2236
+ let prefix = DocTypePrefixes["CARDRECORD" /* CARDRECORD */];
1772
2237
  if (course_id) {
1773
2238
  prefix += course_id;
1774
2239
  }
1775
- const docs = await filterAllDocsByPrefix2(this.localDB, prefix, {
2240
+ const docs = await filterAllDocsByPrefix(this.localDB, prefix, {
1776
2241
  include_docs: false
1777
2242
  });
1778
2243
  const ret = [];
1779
2244
  docs.rows.forEach((row) => {
1780
- if (row.id.startsWith(cardHistoryPrefix2)) {
1781
- ret.push(row.id.substr(cardHistoryPrefix2.length));
2245
+ if (row.id.startsWith(DocTypePrefixes["CARDRECORD" /* CARDRECORD */])) {
2246
+ ret.push(row.id.substr(DocTypePrefixes["CARDRECORD" /* CARDRECORD */].length));
1782
2247
  }
1783
2248
  });
1784
2249
  return ret;
@@ -1788,9 +2253,9 @@ Currently logged-in as ${this._username}.`
1788
2253
  * @returns A promise of the cards that the user has seen in the past.
1789
2254
  */
1790
2255
  async getHistory() {
1791
- const cards = await filterAllDocsByPrefix2(
2256
+ const cards = await filterAllDocsByPrefix(
1792
2257
  this.remoteDB,
1793
- cardHistoryPrefix2,
2258
+ DocTypePrefixes["CARDRECORD" /* CARDRECORD */],
1794
2259
  {
1795
2260
  include_docs: true,
1796
2261
  attachments: false
@@ -1831,7 +2296,7 @@ Currently logged-in as ${this._username}.`
1831
2296
  } catch (e) {
1832
2297
  const err = e;
1833
2298
  if (err.status === 404) {
1834
- await this.remoteDB.put({
2299
+ await this.writeDB.put({
1835
2300
  _id: _BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS,
1836
2301
  registrations: []
1837
2302
  });
@@ -1879,10 +2344,10 @@ Currently logged-in as ${this._username}.`
1879
2344
  }
1880
2345
  }
1881
2346
  async scheduleCardReview(review) {
1882
- return scheduleCardReviewLocal(this.remoteDB, review);
2347
+ return scheduleCardReviewLocal(this.writeDB, review);
1883
2348
  }
1884
2349
  async removeScheduledCardReview(reviewId) {
1885
- return removeScheduledCardReviewLocal(this.remoteDB, reviewId);
2350
+ return removeScheduledCardReviewLocal(this.writeDB, reviewId);
1886
2351
  }
1887
2352
  async registerForClassroom(_classId, _registerAs) {
1888
2353
  return registerUserForClassroom(this._username, _classId, _registerAs);
@@ -1912,427 +2377,24 @@ var init_common = __esm({
1912
2377
  }
1913
2378
  });
1914
2379
 
1915
- // src/impl/couch/courseAPI.ts
1916
- import { NameSpacer } from "@vue-skuilder/common";
1917
- import { blankCourseElo as blankCourseElo2, toCourseElo as toCourseElo2 } from "@vue-skuilder/common";
1918
- import { prepareNote55 } from "@vue-skuilder/common";
1919
- async function addNote55(courseID, codeCourse, shape, data, author, tags, uploads, elo = blankCourseElo2()) {
1920
- const db = getCourseDB2(courseID);
1921
- const payload = prepareNote55(courseID, codeCourse, shape, data, author, tags, uploads);
1922
- const result = await db.post(payload);
1923
- const dataShapeId = NameSpacer.getDataShapeString({
1924
- course: codeCourse,
1925
- dataShape: shape.name
1926
- });
1927
- if (result.ok) {
1928
- try {
1929
- await createCards(courseID, dataShapeId, result.id, tags, elo, author);
1930
- } catch (error) {
1931
- let errorMessage = "Unknown error";
1932
- if (error instanceof Error) {
1933
- errorMessage = error.message;
1934
- } else if (error && typeof error === "object" && "reason" in error) {
1935
- errorMessage = error.reason;
1936
- } else if (error && typeof error === "object" && "message" in error) {
1937
- errorMessage = error.message;
1938
- } else {
1939
- errorMessage = String(error);
1940
- }
1941
- logger.error(`[addNote55] Failed to create cards for note ${result.id}: ${errorMessage}`);
1942
- result.cardCreationFailed = true;
1943
- result.cardCreationError = errorMessage;
1944
- }
1945
- } else {
1946
- logger.error(`[addNote55] Error adding note. Result: ${JSON.stringify(result)}`);
1947
- }
1948
- return result;
1949
- }
1950
- async function createCards(courseID, datashapeID, noteID, tags, elo = blankCourseElo2(), author) {
1951
- const cfg = await getCredentialledCourseConfig(courseID);
1952
- const dsDescriptor = NameSpacer.getDataShapeDescriptor(datashapeID);
1953
- let questionViewTypes = [];
1954
- for (const ds of cfg.dataShapes) {
1955
- if (ds.name === datashapeID) {
1956
- questionViewTypes = ds.questionTypes;
1957
- }
1958
- }
1959
- if (questionViewTypes.length === 0) {
1960
- const errorMsg = `No questionViewTypes found for datashapeID: ${datashapeID} in course config. Cards cannot be created.`;
1961
- logger.error(errorMsg);
1962
- throw new Error(errorMsg);
1963
- }
1964
- for (const questionView of questionViewTypes) {
1965
- await createCard(questionView, courseID, dsDescriptor, noteID, tags, elo, author);
1966
- }
1967
- }
1968
- async function createCard(questionViewName, courseID, dsDescriptor, noteID, tags, elo = blankCourseElo2(), author) {
1969
- const qDescriptor = NameSpacer.getQuestionDescriptor(questionViewName);
1970
- const cfg = await getCredentialledCourseConfig(courseID);
1971
- for (const rQ of cfg.questionTypes) {
1972
- if (rQ.name === questionViewName) {
1973
- for (const view of rQ.viewList) {
1974
- await addCard(
1975
- courseID,
1976
- dsDescriptor.course,
1977
- [noteID],
1978
- NameSpacer.getViewString({
1979
- course: qDescriptor.course,
1980
- questionType: qDescriptor.questionType,
1981
- view
1982
- }),
1983
- elo,
1984
- tags,
1985
- author
1986
- );
1987
- }
1988
- }
1989
- }
1990
- }
1991
- async function addCard(courseID, course, id_displayable_data, id_view, elo, tags, author) {
1992
- const db = getCourseDB2(courseID);
1993
- const card = await db.post({
1994
- course,
1995
- id_displayable_data,
1996
- id_view,
1997
- docType: "CARD" /* CARD */,
1998
- elo: elo || toCourseElo2(990 + Math.round(20 * Math.random())),
1999
- author
2000
- });
2001
- for (const tag of tags) {
2002
- logger.info(`adding tag: ${tag} to card ${card.id}`);
2003
- await addTagToCard(courseID, card.id, tag, author, false);
2004
- }
2005
- return card;
2006
- }
2007
- async function getCredentialledCourseConfig(courseID) {
2008
- try {
2009
- const db = getCourseDB2(courseID);
2010
- const ret = await db.get("CourseConfig");
2011
- ret.courseID = courseID;
2012
- logger.info(`Returning course config: ${JSON.stringify(ret)}`);
2013
- return ret;
2014
- } catch (e) {
2015
- logger.error(`Error fetching config for ${courseID}:`, e);
2016
- throw e;
2380
+ // src/factory.ts
2381
+ function getDataLayer() {
2382
+ if (!dataLayerInstance) {
2383
+ throw new Error("Data layer not initialized. Call initializeDataLayer first.");
2017
2384
  }
2385
+ return dataLayerInstance;
2018
2386
  }
2019
- async function addTagToCard(courseID, cardID, tagID, author, updateELO = true) {
2020
- const prefixedTagID = getTagID(tagID);
2021
- const courseDB = getCourseDB2(courseID);
2022
- const courseApi = new CourseDB(courseID, async () => {
2023
- const dummySyncStrategy = {
2024
- setupRemoteDB: () => null,
2025
- startSync: () => {
2026
- },
2027
- canCreateAccount: () => false,
2028
- canAuthenticate: () => false,
2029
- getCurrentUsername: async () => "DummyUser"
2030
- };
2031
- return BaseUser.Dummy(dummySyncStrategy);
2032
- });
2033
- try {
2034
- logger.info(`Applying tag ${tagID} to card ${courseID + "-" + cardID}...`);
2035
- const tag = await courseDB.get(prefixedTagID);
2036
- if (!tag.taggedCards.includes(cardID)) {
2037
- tag.taggedCards.push(cardID);
2038
- if (updateELO) {
2039
- try {
2040
- const eloData = await courseApi.getCardEloData([cardID]);
2041
- const elo = eloData[0];
2042
- elo.tags[tagID] = {
2043
- count: 0,
2044
- score: elo.global.score
2045
- // todo: or 1000?
2046
- };
2047
- await updateCardElo(courseID, cardID, elo);
2048
- } catch (error) {
2049
- logger.error("Failed to update ELO data for card:", cardID, error);
2050
- }
2051
- }
2052
- return courseDB.put(tag);
2053
- } else throw new AlreadyTaggedErr(`Card ${cardID} is already tagged with ${tagID}`);
2054
- } catch (e) {
2055
- if (e instanceof AlreadyTaggedErr) {
2056
- throw e;
2057
- }
2058
- await createTag(courseID, tagID, author);
2059
- return addTagToCard(courseID, cardID, tagID, author, updateELO);
2060
- }
2061
- }
2062
- async function updateCardElo(courseID, cardID, elo) {
2063
- if (elo) {
2064
- const cDB = getCourseDB2(courseID);
2065
- const card = await cDB.get(cardID);
2066
- logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);
2067
- card.elo = elo;
2068
- return cDB.put(card);
2069
- }
2070
- }
2071
- function getTagID(tagName) {
2072
- const tagPrefix = "TAG" /* TAG */.valueOf() + "-";
2073
- if (tagName.indexOf(tagPrefix) === 0) {
2074
- return tagName;
2075
- } else {
2076
- return tagPrefix + tagName;
2077
- }
2078
- }
2079
- function getCourseDB2(courseID) {
2080
- const dbName = `coursedb-${courseID}`;
2081
- return new pouchdb_setup_default(
2082
- ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
2083
- pouchDBincludeCredentialsConfig
2084
- );
2085
- }
2086
- var AlreadyTaggedErr;
2087
- var init_courseAPI = __esm({
2088
- "src/impl/couch/courseAPI.ts"() {
2089
- "use strict";
2090
- init_pouchdb_setup();
2091
- init_couch();
2092
- init_factory();
2093
- init_courseDB();
2094
- init_types_legacy();
2095
- init_common();
2096
- init_logger();
2097
- AlreadyTaggedErr = class extends Error {
2098
- constructor(message) {
2099
- super(message);
2100
- this.name = "AlreadyTaggedErr";
2101
- }
2102
- };
2103
- }
2104
- });
2105
-
2106
- // src/impl/couch/auth.ts
2107
- var init_auth = __esm({
2108
- "src/impl/couch/auth.ts"() {
2109
- "use strict";
2110
- init_factory();
2111
- init_types_legacy();
2112
- init_logger();
2113
- }
2114
- });
2115
-
2116
- // src/impl/couch/CouchDBSyncStrategy.ts
2117
- import { Status as Status3 } from "@vue-skuilder/common";
2118
- var init_CouchDBSyncStrategy = __esm({
2119
- "src/impl/couch/CouchDBSyncStrategy.ts"() {
2387
+ var ENV, dataLayerInstance;
2388
+ var init_factory = __esm({
2389
+ "src/factory.ts"() {
2120
2390
  "use strict";
2121
- init_factory();
2122
- init_types_legacy();
2123
- init_logger();
2124
2391
  init_common();
2125
- init_pouchdb_setup();
2126
- init_couch();
2127
- init_auth();
2128
- }
2129
- });
2130
-
2131
- // src/impl/couch/index.ts
2132
- import moment4 from "moment";
2133
- import process2 from "process";
2134
- function getCourseDB(courseID) {
2135
- return new pouchdb_setup_default(
2136
- ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "coursedb-" + courseID,
2137
- pouchDBincludeCredentialsConfig
2138
- );
2139
- }
2140
- function getCourseDocs(courseID, docIDs, options = {}) {
2141
- return getCourseDB(courseID).allDocs({
2142
- ...options,
2143
- keys: docIDs
2144
- });
2145
- }
2146
- function getCourseDoc(courseID, docID, options = {}) {
2147
- return getCourseDB(courseID).get(docID, options);
2148
- }
2149
- function filterAllDocsByPrefix(db, prefix, opts) {
2150
- const options = {
2151
- startkey: prefix,
2152
- endkey: prefix + "\uFFF0",
2153
- include_docs: true
2154
- };
2155
- if (opts) {
2156
- Object.assign(options, opts);
2157
- }
2158
- return db.allDocs(options);
2159
- }
2160
- function getStartAndEndKeys(key) {
2161
- return {
2162
- startkey: key,
2163
- endkey: key + "\uFFF0"
2164
- };
2165
- }
2166
- var isBrowser, GUEST_LOCAL_DB, localUserDB, pouchDBincludeCredentialsConfig, REVIEW_PREFIX2, REVIEW_TIME_FORMAT2;
2167
- var init_couch = __esm({
2168
- "src/impl/couch/index.ts"() {
2169
- "use strict";
2170
- init_factory();
2171
- init_types_legacy();
2172
- init_logger();
2173
- init_pouchdb_setup();
2174
- init_contentSource();
2175
- init_adminDB2();
2176
- init_classroomDB2();
2177
- init_courseAPI();
2178
- init_courseDB();
2179
- init_CouchDBSyncStrategy();
2180
- isBrowser = typeof window !== "undefined";
2181
- if (isBrowser) {
2182
- window.process = process2;
2183
- }
2184
- GUEST_LOCAL_DB = `userdb-${GuestUsername}`;
2185
- localUserDB = new pouchdb_setup_default(GUEST_LOCAL_DB);
2186
- pouchDBincludeCredentialsConfig = {
2187
- fetch(url, opts) {
2188
- opts.credentials = "include";
2189
- return pouchdb_setup_default.fetch(url, opts);
2190
- }
2191
- };
2192
- REVIEW_PREFIX2 = "card_review_";
2193
- REVIEW_TIME_FORMAT2 = "YYYY-MM-DD--kk:mm:ss-SSS";
2194
- }
2195
- });
2196
-
2197
- // src/impl/couch/classroomDB.ts
2198
- import moment5 from "moment";
2199
- var CLASSROOM_CONFIG, ClassroomDBBase, StudentClassroomDB;
2200
- var init_classroomDB2 = __esm({
2201
- "src/impl/couch/classroomDB.ts"() {
2202
- "use strict";
2203
- init_factory();
2204
2392
  init_logger();
2205
- init_pouchdb_setup();
2206
- init_couch();
2207
- init_courseDB();
2208
- CLASSROOM_CONFIG = "ClassroomConfig";
2209
- ClassroomDBBase = class {
2210
- _id;
2211
- _db;
2212
- _cfg;
2213
- _initComplete = false;
2214
- _content_prefix = "content";
2215
- get _content_searchkeys() {
2216
- return getStartAndEndKeys(this._content_prefix);
2217
- }
2218
- async getAssignedContent() {
2219
- logger.info(`Getting assigned content...`);
2220
- const docRows = await this._db.allDocs({
2221
- startkey: this._content_prefix,
2222
- endkey: this._content_prefix + `\uFFF0`,
2223
- include_docs: true
2224
- });
2225
- const ret = docRows.rows.map((row) => {
2226
- return row.doc;
2227
- });
2228
- return ret;
2229
- }
2230
- getContentId(content) {
2231
- if (content.type === "tag") {
2232
- return `${this._content_prefix}-${content.courseID}-${content.tagID}`;
2233
- } else {
2234
- return `${this._content_prefix}-${content.courseID}`;
2235
- }
2236
- }
2237
- get ready() {
2238
- return this._initComplete;
2239
- }
2240
- getConfig() {
2241
- return this._cfg;
2242
- }
2243
- };
2244
- StudentClassroomDB = class _StudentClassroomDB extends ClassroomDBBase {
2245
- // private readonly _prefix: string = 'content';
2246
- userMessages;
2247
- _user;
2248
- constructor(classID, user) {
2249
- super();
2250
- this._id = classID;
2251
- this._user = user;
2252
- }
2253
- async init() {
2254
- const dbName = `classdb-student-${this._id}`;
2255
- this._db = new pouchdb_setup_default(
2256
- ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
2257
- pouchDBincludeCredentialsConfig
2258
- );
2259
- try {
2260
- const cfg = await this._db.get(CLASSROOM_CONFIG);
2261
- this._cfg = cfg;
2262
- this.userMessages = this._db.changes({
2263
- since: "now",
2264
- live: true,
2265
- include_docs: true
2266
- });
2267
- this._initComplete = true;
2268
- return;
2269
- } catch (e) {
2270
- throw new Error(`Error in StudentClassroomDB constructor: ${JSON.stringify(e)}`);
2271
- }
2272
- }
2273
- static async factory(classID, user) {
2274
- const ret = new _StudentClassroomDB(classID, user);
2275
- await ret.init();
2276
- return ret;
2277
- }
2278
- setChangeFcn(f) {
2279
- void this.userMessages.on("change", f);
2280
- }
2281
- async getPendingReviews() {
2282
- const u = this._user;
2283
- return (await u.getPendingReviews()).filter((r) => r.scheduledFor === "classroom" && r.schedulingAgentId === this._id).map((r) => {
2284
- return {
2285
- ...r,
2286
- qualifiedID: `${r.courseId}-${r.cardId}`,
2287
- courseID: r.courseId,
2288
- cardID: r.cardId,
2289
- contentSourceType: "classroom",
2290
- contentSourceID: this._id,
2291
- reviewID: r._id,
2292
- status: "review"
2293
- };
2294
- });
2295
- }
2296
- async getNewCards() {
2297
- const activeCards = await this._user.getActiveCards();
2298
- const now = moment5.utc();
2299
- const assigned = await this.getAssignedContent();
2300
- const due = assigned.filter((c) => now.isAfter(moment5.utc(c.activeOn, REVIEW_TIME_FORMAT2)));
2301
- logger.info(`Due content: ${JSON.stringify(due)}`);
2302
- let ret = [];
2303
- for (let i = 0; i < due.length; i++) {
2304
- const content = due[i];
2305
- if (content.type === "course") {
2306
- const db = new CourseDB(content.courseID, async () => this._user);
2307
- ret = ret.concat(await db.getNewCards());
2308
- } else if (content.type === "tag") {
2309
- const tagDoc = await getTag(content.courseID, content.tagID);
2310
- ret = ret.concat(
2311
- tagDoc.taggedCards.map((c) => {
2312
- return {
2313
- courseID: content.courseID,
2314
- cardID: c,
2315
- qualifiedID: `${content.courseID}-${c}`,
2316
- contentSourceType: "classroom",
2317
- contentSourceID: this._id,
2318
- status: "new"
2319
- };
2320
- })
2321
- );
2322
- } else if (content.type === "card") {
2323
- ret.push(await getCourseDB(content.courseID).get(content.cardID));
2324
- }
2325
- }
2326
- logger.info(`New Cards from classroom ${this._cfg.name}: ${ret.map((c) => c.qualifiedID)}`);
2327
- return ret.filter((c) => {
2328
- if (activeCards.some((ac) => c.qualifiedID.includes(ac))) {
2329
- return false;
2330
- } else {
2331
- return true;
2332
- }
2333
- });
2334
- }
2393
+ ENV = {
2394
+ COUCHDB_SERVER_PROTOCOL: "NOT_SET",
2395
+ COUCHDB_SERVER_URL: "NOT_SET"
2335
2396
  };
2397
+ dataLayerInstance = null;
2336
2398
  }
2337
2399
  });
2338
2400
 
@@ -2549,11 +2611,11 @@ init_core();
2549
2611
  export {
2550
2612
  ContentNavigator,
2551
2613
  DocType,
2614
+ DocTypePrefixes,
2552
2615
  GuestUsername,
2553
2616
  Loggable,
2554
2617
  Navigators,
2555
2618
  areQuestionRecords,
2556
- cardHistoryPrefix,
2557
2619
  docIsDeleted,
2558
2620
  getCardHistoryID,
2559
2621
  getStudySource,