@vue-skuilder/db 0.1.6 → 0.1.8-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{SyncStrategy-DnJRj-Xp.d.mts → SyncStrategy-CyATpyLQ.d.mts} +6 -0
- package/dist/{SyncStrategy-DnJRj-Xp.d.ts → SyncStrategy-CyATpyLQ.d.ts} +6 -0
- package/dist/core/index.d.mts +5 -5
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +825 -762
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +812 -750
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-BZmLyBVw.d.mts → dataLayerProvider-BInqI_RF.d.mts} +1 -1
- package/dist/{dataLayerProvider-BuntXkCs.d.ts → dataLayerProvider-DqtNroSh.d.ts} +1 -1
- package/dist/impl/couch/index.d.mts +6 -6
- package/dist/impl/couch/index.d.ts +6 -6
- package/dist/impl/couch/index.js +2261 -2081
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +2274 -2095
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.mts +8 -6
- package/dist/impl/static/index.d.ts +8 -6
- package/dist/impl/static/index.js +524 -1064
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +515 -1058
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index-CLL31bEy.d.ts +137 -0
- package/dist/index-CUNnL38E.d.mts +137 -0
- package/dist/index.d.mts +200 -9
- package/dist/index.d.ts +200 -9
- package/dist/index.js +4123 -2820
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4119 -2830
- package/dist/index.mjs.map +1 -1
- package/dist/{types-D6SnlHPm.d.ts → types-BefDGkKa.d.ts} +1 -1
- package/dist/{types-DPRvCrIk.d.mts → types-DC-ckZug.d.mts} +1 -1
- package/dist/{types-legacy-WPe8CtO-.d.mts → types-legacy-Birv-Jx6.d.mts} +2 -2
- package/dist/{types-legacy-WPe8CtO-.d.ts → types-legacy-Birv-Jx6.d.ts} +2 -2
- package/dist/{userDB-D9EuWTp1.d.ts → userDB-C33Hzjgn.d.mts} +11 -4
- package/dist/{userDB-31gsvxyd.d.mts → userDB-DusL7OXe.d.ts} +11 -4
- package/dist/util/packer/index.d.mts +3 -63
- package/dist/util/packer/index.d.ts +3 -63
- package/dist/util/packer/index.js +53 -1
- package/dist/util/packer/index.js.map +1 -1
- package/dist/util/packer/index.mjs +53 -1
- package/dist/util/packer/index.mjs.map +1 -1
- package/package.json +7 -4
- package/src/core/types/types-legacy.ts +13 -1
- package/src/core/types/user.ts +9 -2
- package/src/core/util/index.ts +5 -4
- package/src/factory.ts +25 -0
- package/src/impl/common/BaseUserDB.ts +62 -28
- package/src/impl/common/SyncStrategy.ts +7 -0
- package/src/impl/common/index.ts +0 -1
- package/src/impl/common/userDBHelpers.ts +15 -5
- package/src/impl/couch/CouchDBSyncStrategy.ts +10 -0
- package/src/impl/couch/courseAPI.ts +7 -6
- package/src/impl/couch/courseLookupDB.ts +24 -0
- package/src/impl/couch/index.ts +10 -5
- package/src/impl/couch/updateQueue.ts +12 -8
- package/src/impl/couch/user-course-relDB.ts +17 -27
- package/src/impl/static/NoOpSyncStrategy.ts +5 -0
- package/src/impl/static/StaticDataUnpacker.ts +18 -36
- package/src/impl/static/courseDB.ts +135 -17
- package/src/util/dataDirectory.test.ts +53 -0
- package/src/util/dataDirectory.ts +52 -0
- package/src/util/index.ts +3 -0
- package/src/util/migrator/FileSystemAdapter.ts +79 -0
- package/src/util/migrator/StaticToCouchDBMigrator.ts +713 -0
- package/src/util/migrator/index.ts +18 -0
- package/src/util/migrator/types.ts +84 -0
- package/src/util/migrator/validation.ts +517 -0
- package/src/util/packer/CouchDBToStaticPacker.ts +92 -2
- package/src/util/tuiLogger.ts +139 -0
|
@@ -5,10 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __glob = (map) => (
|
|
9
|
-
var fn = map[
|
|
8
|
+
var __glob = (map) => (path2) => {
|
|
9
|
+
var fn = map[path2];
|
|
10
10
|
if (fn) return fn();
|
|
11
|
-
throw new Error("Module not found in bundle: " +
|
|
11
|
+
throw new Error("Module not found in bundle: " + path2);
|
|
12
12
|
};
|
|
13
13
|
var __esm = (fn, res) => function __init() {
|
|
14
14
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
@@ -94,19 +94,47 @@ var init_classroomDB = __esm({
|
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
// src/
|
|
98
|
-
var
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
// src/impl/common/SyncStrategy.ts
|
|
98
|
+
var init_SyncStrategy = __esm({
|
|
99
|
+
"src/impl/common/SyncStrategy.ts"() {
|
|
100
|
+
"use strict";
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// src/core/types/types-legacy.ts
|
|
105
|
+
var GuestUsername, DocTypePrefixes;
|
|
106
|
+
var init_types_legacy = __esm({
|
|
107
|
+
"src/core/types/types-legacy.ts"() {
|
|
101
108
|
"use strict";
|
|
102
109
|
init_logger();
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
GuestUsername = "Guest";
|
|
111
|
+
DocTypePrefixes = {
|
|
112
|
+
["CARD" /* CARD */]: "c",
|
|
113
|
+
["DISPLAYABLE_DATA" /* DISPLAYABLE_DATA */]: "dd",
|
|
114
|
+
["TAG" /* TAG */]: "TAG",
|
|
115
|
+
["CARDRECORD" /* CARDRECORD */]: "cardH",
|
|
116
|
+
["SCHEDULED_CARD" /* SCHEDULED_CARD */]: "card_review_",
|
|
117
|
+
// Add other doctypes here as they get prefixed IDs
|
|
118
|
+
["DATASHAPE" /* DATASHAPE */]: "DATASHAPE",
|
|
119
|
+
["QUESTION" /* QUESTIONTYPE */]: "QUESTION",
|
|
120
|
+
["VIEW" /* VIEW */]: "VIEW",
|
|
121
|
+
["PEDAGOGY" /* PEDAGOGY */]: "PEDAGOGY",
|
|
122
|
+
["NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */]: "NAVIGATION_STRATEGY"
|
|
106
123
|
};
|
|
107
124
|
}
|
|
108
125
|
});
|
|
109
126
|
|
|
127
|
+
// src/core/util/index.ts
|
|
128
|
+
function getCardHistoryID(courseID, cardID) {
|
|
129
|
+
return `${DocTypePrefixes["CARDRECORD" /* CARDRECORD */]}-${courseID}-${cardID}`;
|
|
130
|
+
}
|
|
131
|
+
var init_util = __esm({
|
|
132
|
+
"src/core/util/index.ts"() {
|
|
133
|
+
"use strict";
|
|
134
|
+
init_types_legacy();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
110
138
|
// src/impl/couch/pouchdb-setup.ts
|
|
111
139
|
var import_pouchdb, import_pouchdb_find, import_pouchdb_authentication, pouchdb_setup_default;
|
|
112
140
|
var init_pouchdb_setup = __esm({
|
|
@@ -126,51 +154,94 @@ var init_pouchdb_setup = __esm({
|
|
|
126
154
|
}
|
|
127
155
|
});
|
|
128
156
|
|
|
129
|
-
// src/
|
|
130
|
-
var
|
|
131
|
-
|
|
132
|
-
"src/core/types/types-legacy.ts"() {
|
|
157
|
+
// src/util/tuiLogger.ts
|
|
158
|
+
var init_tuiLogger = __esm({
|
|
159
|
+
"src/util/tuiLogger.ts"() {
|
|
133
160
|
"use strict";
|
|
134
|
-
|
|
135
|
-
GuestUsername = "Guest";
|
|
136
|
-
DocType = /* @__PURE__ */ ((DocType2) => {
|
|
137
|
-
DocType2["DISPLAYABLE_DATA"] = "DISPLAYABLE_DATA";
|
|
138
|
-
DocType2["CARD"] = "CARD";
|
|
139
|
-
DocType2["DATASHAPE"] = "DATASHAPE";
|
|
140
|
-
DocType2["QUESTIONTYPE"] = "QUESTION";
|
|
141
|
-
DocType2["VIEW"] = "VIEW";
|
|
142
|
-
DocType2["PEDAGOGY"] = "PEDAGOGY";
|
|
143
|
-
DocType2["CARDRECORD"] = "CARDRECORD";
|
|
144
|
-
DocType2["SCHEDULED_CARD"] = "SCHEDULED_CARD";
|
|
145
|
-
DocType2["TAG"] = "TAG";
|
|
146
|
-
DocType2["NAVIGATION_STRATEGY"] = "NAVIGATION_STRATEGY";
|
|
147
|
-
return DocType2;
|
|
148
|
-
})(DocType || {});
|
|
149
|
-
cardHistoryPrefix = "cardH";
|
|
161
|
+
init_dataDirectory();
|
|
150
162
|
}
|
|
151
163
|
});
|
|
152
164
|
|
|
153
|
-
// src/
|
|
154
|
-
|
|
155
|
-
"
|
|
165
|
+
// src/util/dataDirectory.ts
|
|
166
|
+
function getAppDataDirectory() {
|
|
167
|
+
return path.join(os.homedir(), ".tuilder");
|
|
168
|
+
}
|
|
169
|
+
function getDbPath(dbName) {
|
|
170
|
+
return path.join(getAppDataDirectory(), dbName);
|
|
171
|
+
}
|
|
172
|
+
var path, os;
|
|
173
|
+
var init_dataDirectory = __esm({
|
|
174
|
+
"src/util/dataDirectory.ts"() {
|
|
156
175
|
"use strict";
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
logger.debug(`COURSELOOKUP FILE RUNNING`);
|
|
176
|
+
path = __toESM(require("path"));
|
|
177
|
+
os = __toESM(require("os"));
|
|
178
|
+
init_tuiLogger();
|
|
161
179
|
}
|
|
162
180
|
});
|
|
163
181
|
|
|
164
|
-
// src/impl/
|
|
165
|
-
|
|
166
|
-
|
|
182
|
+
// src/impl/common/userDBHelpers.ts
|
|
183
|
+
function filterAllDocsByPrefix(db, prefix, opts) {
|
|
184
|
+
const options = {
|
|
185
|
+
startkey: prefix,
|
|
186
|
+
endkey: prefix + "\uFFF0",
|
|
187
|
+
include_docs: true
|
|
188
|
+
};
|
|
189
|
+
if (opts) {
|
|
190
|
+
Object.assign(options, opts);
|
|
191
|
+
}
|
|
192
|
+
return db.allDocs(options);
|
|
193
|
+
}
|
|
194
|
+
function getStartAndEndKeys(key) {
|
|
195
|
+
return {
|
|
196
|
+
startkey: key,
|
|
197
|
+
endkey: key + "\uFFF0"
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function getLocalUserDB(username) {
|
|
201
|
+
const dbName = `userdb-${username}`;
|
|
202
|
+
if (typeof window === "undefined") {
|
|
203
|
+
return new pouchdb_setup_default(getDbPath(dbName), {});
|
|
204
|
+
} else {
|
|
205
|
+
return new pouchdb_setup_default(dbName, {});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function scheduleCardReviewLocal(userDB, review) {
|
|
209
|
+
const now = import_moment.default.utc();
|
|
210
|
+
logger.info(`Scheduling for review in: ${review.time.diff(now, "h") / 24} days`);
|
|
211
|
+
void userDB.put({
|
|
212
|
+
_id: DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */] + review.time.format(REVIEW_TIME_FORMAT),
|
|
213
|
+
cardId: review.card_id,
|
|
214
|
+
reviewTime: review.time.toISOString(),
|
|
215
|
+
courseId: review.course_id,
|
|
216
|
+
scheduledAt: now.toISOString(),
|
|
217
|
+
scheduledFor: review.scheduledFor,
|
|
218
|
+
schedulingAgentId: review.schedulingAgentId
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
async function removeScheduledCardReviewLocal(userDB, reviewDocID) {
|
|
222
|
+
const reviewDoc = await userDB.get(reviewDocID);
|
|
223
|
+
userDB.remove(reviewDoc).then((res) => {
|
|
224
|
+
if (res.ok) {
|
|
225
|
+
log(`Removed Review Doc: ${reviewDocID}`);
|
|
226
|
+
}
|
|
227
|
+
}).catch((err) => {
|
|
228
|
+
log(`Failed to remove Review Doc: ${reviewDocID},
|
|
229
|
+
${JSON.stringify(err)}`);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
var import_moment, REVIEW_TIME_FORMAT, log;
|
|
233
|
+
var init_userDBHelpers = __esm({
|
|
234
|
+
"src/impl/common/userDBHelpers.ts"() {
|
|
167
235
|
"use strict";
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
init_couch();
|
|
171
|
-
init_classroomDB2();
|
|
172
|
-
init_courseLookupDB();
|
|
236
|
+
import_moment = __toESM(require("moment"));
|
|
237
|
+
init_core();
|
|
173
238
|
init_logger();
|
|
239
|
+
init_pouchdb_setup();
|
|
240
|
+
init_dataDirectory();
|
|
241
|
+
REVIEW_TIME_FORMAT = "YYYY-MM-DD--kk:mm:ss-SSS";
|
|
242
|
+
log = (s) => {
|
|
243
|
+
logger.info(s);
|
|
244
|
+
};
|
|
174
245
|
}
|
|
175
246
|
});
|
|
176
247
|
|
|
@@ -201,7 +272,10 @@ var init_updateQueue = __esm({
|
|
|
201
272
|
_className = "UpdateQueue";
|
|
202
273
|
pendingUpdates = {};
|
|
203
274
|
inprogressUpdates = {};
|
|
204
|
-
|
|
275
|
+
readDB;
|
|
276
|
+
// Database for read operations
|
|
277
|
+
writeDB;
|
|
278
|
+
// Database for write operations (local-first)
|
|
205
279
|
update(id, update) {
|
|
206
280
|
logger.debug(`Update requested on doc: ${id}`);
|
|
207
281
|
if (this.pendingUpdates[id]) {
|
|
@@ -211,24 +285,25 @@ var init_updateQueue = __esm({
|
|
|
211
285
|
}
|
|
212
286
|
return this.applyUpdates(id);
|
|
213
287
|
}
|
|
214
|
-
constructor(
|
|
288
|
+
constructor(readDB, writeDB) {
|
|
215
289
|
super();
|
|
216
|
-
this.
|
|
290
|
+
this.readDB = readDB;
|
|
291
|
+
this.writeDB = writeDB || readDB;
|
|
217
292
|
logger.debug(`UpdateQ initialized...`);
|
|
218
|
-
void this.
|
|
293
|
+
void this.readDB.info().then((i) => {
|
|
219
294
|
logger.debug(`db info: ${JSON.stringify(i)}`);
|
|
220
295
|
});
|
|
221
296
|
}
|
|
222
297
|
async applyUpdates(id) {
|
|
223
298
|
logger.debug(`Applying updates on doc: ${id}`);
|
|
224
299
|
if (this.inprogressUpdates[id]) {
|
|
225
|
-
await this.
|
|
300
|
+
await this.readDB.info();
|
|
226
301
|
return this.applyUpdates(id);
|
|
227
302
|
} else {
|
|
228
303
|
if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {
|
|
229
304
|
this.inprogressUpdates[id] = true;
|
|
230
305
|
try {
|
|
231
|
-
let doc = await this.
|
|
306
|
+
let doc = await this.readDB.get(id);
|
|
232
307
|
logger.debug(`Retrieved doc: ${id}`);
|
|
233
308
|
while (this.pendingUpdates[id].length !== 0) {
|
|
234
309
|
const update = this.pendingUpdates[id].splice(0, 1)[0];
|
|
@@ -241,7 +316,7 @@ var init_updateQueue = __esm({
|
|
|
241
316
|
};
|
|
242
317
|
}
|
|
243
318
|
}
|
|
244
|
-
await this.
|
|
319
|
+
await this.writeDB.put(doc);
|
|
245
320
|
logger.debug(`Put doc: ${id}`);
|
|
246
321
|
if (this.pendingUpdates[id].length === 0) {
|
|
247
322
|
this.inprogressUpdates[id] = false;
|
|
@@ -267,22 +342,113 @@ var init_updateQueue = __esm({
|
|
|
267
342
|
}
|
|
268
343
|
});
|
|
269
344
|
|
|
345
|
+
// src/impl/couch/user-course-relDB.ts
|
|
346
|
+
var import_moment2, UsrCrsData;
|
|
347
|
+
var init_user_course_relDB = __esm({
|
|
348
|
+
"src/impl/couch/user-course-relDB.ts"() {
|
|
349
|
+
"use strict";
|
|
350
|
+
import_moment2 = __toESM(require("moment"));
|
|
351
|
+
init_logger();
|
|
352
|
+
UsrCrsData = class {
|
|
353
|
+
user;
|
|
354
|
+
_courseId;
|
|
355
|
+
constructor(user, courseId) {
|
|
356
|
+
this.user = user;
|
|
357
|
+
this._courseId = courseId;
|
|
358
|
+
}
|
|
359
|
+
async getReviewsForcast(daysCount) {
|
|
360
|
+
const time = import_moment2.default.utc().add(daysCount, "days");
|
|
361
|
+
return this.getReviewstoDate(time);
|
|
362
|
+
}
|
|
363
|
+
async getPendingReviews() {
|
|
364
|
+
const now = import_moment2.default.utc();
|
|
365
|
+
return this.getReviewstoDate(now);
|
|
366
|
+
}
|
|
367
|
+
async getScheduledReviewCount() {
|
|
368
|
+
return (await this.getPendingReviews()).length;
|
|
369
|
+
}
|
|
370
|
+
async getCourseSettings() {
|
|
371
|
+
const regDoc = await this.user.getCourseRegistrationsDoc();
|
|
372
|
+
const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);
|
|
373
|
+
if (crsDoc && crsDoc.settings) {
|
|
374
|
+
return crsDoc.settings;
|
|
375
|
+
} else {
|
|
376
|
+
logger.warn(`no settings found during lookup on course ${this._courseId}`);
|
|
377
|
+
return {};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
updateCourseSettings(updates) {
|
|
381
|
+
if ("updateCourseSettings" in this.user) {
|
|
382
|
+
void this.user.updateCourseSettings(this._courseId, updates);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
async getReviewstoDate(targetDate) {
|
|
386
|
+
const allReviews = await this.user.getPendingReviews(this._courseId);
|
|
387
|
+
logger.debug(
|
|
388
|
+
`Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`
|
|
389
|
+
);
|
|
390
|
+
return allReviews.filter((review) => {
|
|
391
|
+
const reviewTime = import_moment2.default.utc(review.reviewTime);
|
|
392
|
+
return targetDate.isAfter(reviewTime);
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
270
399
|
// src/impl/couch/clientCache.ts
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
400
|
+
var init_clientCache = __esm({
|
|
401
|
+
"src/impl/couch/clientCache.ts"() {
|
|
402
|
+
"use strict";
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// src/impl/couch/courseAPI.ts
|
|
407
|
+
async function getCredentialledCourseConfig(courseID) {
|
|
408
|
+
try {
|
|
409
|
+
const db = getCourseDB(courseID);
|
|
410
|
+
const ret = await db.get("CourseConfig");
|
|
411
|
+
ret.courseID = courseID;
|
|
412
|
+
logger.info(`Returning course config: ${JSON.stringify(ret)}`);
|
|
413
|
+
return ret;
|
|
414
|
+
} catch (e) {
|
|
415
|
+
logger.error(`Error fetching config for ${courseID}:`, e);
|
|
416
|
+
throw e;
|
|
274
417
|
}
|
|
275
|
-
CLIENT_CACHE[k] = f ? await f(k) : await GET_ITEM(k);
|
|
276
|
-
return GET_CACHED(k);
|
|
277
418
|
}
|
|
278
|
-
|
|
279
|
-
|
|
419
|
+
function getCourseDB(courseID) {
|
|
420
|
+
const dbName = `coursedb-${courseID}`;
|
|
421
|
+
return new pouchdb_setup_default(
|
|
422
|
+
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
423
|
+
pouchDBincludeCredentialsConfig
|
|
424
|
+
);
|
|
280
425
|
}
|
|
281
|
-
var
|
|
282
|
-
var
|
|
283
|
-
"src/impl/couch/
|
|
426
|
+
var import_common, import_common2, import_common3, import_uuid;
|
|
427
|
+
var init_courseAPI = __esm({
|
|
428
|
+
"src/impl/couch/courseAPI.ts"() {
|
|
429
|
+
"use strict";
|
|
430
|
+
init_pouchdb_setup();
|
|
431
|
+
init_couch();
|
|
432
|
+
init_factory();
|
|
433
|
+
import_common = require("@vue-skuilder/common");
|
|
434
|
+
import_common2 = require("@vue-skuilder/common");
|
|
435
|
+
init_courseDB();
|
|
436
|
+
init_types_legacy();
|
|
437
|
+
import_common3 = require("@vue-skuilder/common");
|
|
438
|
+
init_common();
|
|
439
|
+
init_logger();
|
|
440
|
+
import_uuid = require("uuid");
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// src/impl/couch/courseLookupDB.ts
|
|
445
|
+
var init_courseLookupDB = __esm({
|
|
446
|
+
"src/impl/couch/courseLookupDB.ts"() {
|
|
284
447
|
"use strict";
|
|
285
|
-
|
|
448
|
+
init_pouchdb_setup();
|
|
449
|
+
init_factory();
|
|
450
|
+
init_logger();
|
|
451
|
+
logger.debug(`COURSELOOKUP FILE RUNNING`);
|
|
286
452
|
}
|
|
287
453
|
});
|
|
288
454
|
|
|
@@ -406,86 +572,11 @@ var init_navigators = __esm({
|
|
|
406
572
|
});
|
|
407
573
|
|
|
408
574
|
// src/impl/couch/courseDB.ts
|
|
409
|
-
|
|
410
|
-
return Math.floor(Math.random() * Math.random() * Math.random() * n);
|
|
411
|
-
}
|
|
412
|
-
async function getCourseTagStubs(courseID) {
|
|
413
|
-
logger.debug(`Getting tag stubs for course: ${courseID}`);
|
|
414
|
-
const stubs = await filterAllDocsByPrefix(
|
|
415
|
-
getCourseDB(courseID),
|
|
416
|
-
"TAG" /* TAG */.valueOf() + "-"
|
|
417
|
-
);
|
|
418
|
-
stubs.rows.forEach((row) => {
|
|
419
|
-
logger.debug(` Tag stub for doc: ${row.id}`);
|
|
420
|
-
});
|
|
421
|
-
return stubs;
|
|
422
|
-
}
|
|
423
|
-
async function createTag(courseID, tagName, author) {
|
|
424
|
-
logger.debug(`Creating tag: ${tagName}...`);
|
|
425
|
-
const tagID = getTagID(tagName);
|
|
426
|
-
const courseDB = getCourseDB(courseID);
|
|
427
|
-
const resp = await courseDB.put({
|
|
428
|
-
course: courseID,
|
|
429
|
-
docType: "TAG" /* TAG */,
|
|
430
|
-
name: tagName,
|
|
431
|
-
snippet: "",
|
|
432
|
-
taggedCards: [],
|
|
433
|
-
wiki: "",
|
|
434
|
-
author,
|
|
435
|
-
_id: tagID
|
|
436
|
-
});
|
|
437
|
-
return resp;
|
|
438
|
-
}
|
|
439
|
-
async function updateTag(tag) {
|
|
440
|
-
const prior = await getTag(tag.course, tag.name);
|
|
441
|
-
return await getCourseDB(tag.course).put({
|
|
442
|
-
...tag,
|
|
443
|
-
_rev: prior._rev
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
async function getTag(courseID, tagName) {
|
|
447
|
-
const tagID = getTagID(tagName);
|
|
448
|
-
const courseDB = getCourseDB(courseID);
|
|
449
|
-
return courseDB.get(tagID);
|
|
450
|
-
}
|
|
451
|
-
async function removeTagFromCard(courseID, cardID, tagID) {
|
|
452
|
-
tagID = getTagID(tagID);
|
|
453
|
-
const courseDB = getCourseDB(courseID);
|
|
454
|
-
const tag = await courseDB.get(tagID);
|
|
455
|
-
tag.taggedCards = tag.taggedCards.filter((taggedID) => {
|
|
456
|
-
return cardID !== taggedID;
|
|
457
|
-
});
|
|
458
|
-
return courseDB.put(tag);
|
|
459
|
-
}
|
|
460
|
-
async function getAppliedTags(id_course, id_card) {
|
|
461
|
-
const db = getCourseDB(id_course);
|
|
462
|
-
const result = await db.query("getTags", {
|
|
463
|
-
startkey: id_card,
|
|
464
|
-
endkey: id_card
|
|
465
|
-
// include_docs: true
|
|
466
|
-
});
|
|
467
|
-
return result;
|
|
468
|
-
}
|
|
469
|
-
async function updateCredentialledCourseConfig(courseID, config) {
|
|
470
|
-
logger.debug(`Updating course config:
|
|
471
|
-
|
|
472
|
-
${JSON.stringify(config)}
|
|
473
|
-
`);
|
|
474
|
-
const db = getCourseDB(courseID);
|
|
475
|
-
const old = await getCredentialledCourseConfig(courseID);
|
|
476
|
-
return await db.put({
|
|
477
|
-
...config,
|
|
478
|
-
_rev: old._rev
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
function isSuccessRow(row) {
|
|
482
|
-
return "doc" in row && row.doc !== null && row.doc !== void 0;
|
|
483
|
-
}
|
|
484
|
-
var import_common, CourseDB;
|
|
575
|
+
var import_common5;
|
|
485
576
|
var init_courseDB = __esm({
|
|
486
577
|
"src/impl/couch/courseDB.ts"() {
|
|
487
578
|
"use strict";
|
|
488
|
-
|
|
579
|
+
import_common5 = require("@vue-skuilder/common");
|
|
489
580
|
init_couch();
|
|
490
581
|
init_updateQueue();
|
|
491
582
|
init_types_legacy();
|
|
@@ -494,560 +585,89 @@ var init_courseDB = __esm({
|
|
|
494
585
|
init_courseAPI();
|
|
495
586
|
init_courseLookupDB();
|
|
496
587
|
init_navigators();
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
// log(`CourseLog: ${this.id}\n ${msg}`);
|
|
500
|
-
// }
|
|
501
|
-
db;
|
|
502
|
-
id;
|
|
503
|
-
_getCurrentUser;
|
|
504
|
-
updateQueue;
|
|
505
|
-
constructor(id, userLookup) {
|
|
506
|
-
this.id = id;
|
|
507
|
-
this.db = getCourseDB(this.id);
|
|
508
|
-
this._getCurrentUser = userLookup;
|
|
509
|
-
this.updateQueue = new UpdateQueue(this.db);
|
|
510
|
-
}
|
|
511
|
-
getCourseID() {
|
|
512
|
-
return this.id;
|
|
513
|
-
}
|
|
514
|
-
async getCourseInfo() {
|
|
515
|
-
const cardCount = (await this.db.find({
|
|
516
|
-
selector: {
|
|
517
|
-
docType: "CARD" /* CARD */
|
|
518
|
-
},
|
|
519
|
-
limit: 1e3
|
|
520
|
-
})).docs.length;
|
|
521
|
-
return {
|
|
522
|
-
cardCount,
|
|
523
|
-
registeredUsers: 0
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
async getInexperiencedCards(limit = 2) {
|
|
527
|
-
return (await this.db.query("cardsByInexperience", {
|
|
528
|
-
limit
|
|
529
|
-
})).rows.map((r) => {
|
|
530
|
-
const ret = {
|
|
531
|
-
courseId: this.id,
|
|
532
|
-
cardId: r.id,
|
|
533
|
-
count: r.key,
|
|
534
|
-
elo: r.value
|
|
535
|
-
};
|
|
536
|
-
return ret;
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
async getCardsByEloLimits(options = {
|
|
540
|
-
low: 0,
|
|
541
|
-
high: Number.MIN_SAFE_INTEGER,
|
|
542
|
-
limit: 25,
|
|
543
|
-
page: 0
|
|
544
|
-
}) {
|
|
545
|
-
return (await this.db.query("elo", {
|
|
546
|
-
startkey: options.low,
|
|
547
|
-
endkey: options.high,
|
|
548
|
-
limit: options.limit,
|
|
549
|
-
skip: options.limit * options.page
|
|
550
|
-
})).rows.map((r) => {
|
|
551
|
-
return `${this.id}-${r.id}-${r.key}`;
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
async getCardEloData(id) {
|
|
555
|
-
const docs = await this.db.allDocs({
|
|
556
|
-
keys: id,
|
|
557
|
-
include_docs: true
|
|
558
|
-
});
|
|
559
|
-
const ret = [];
|
|
560
|
-
docs.rows.forEach((r) => {
|
|
561
|
-
if (isSuccessRow(r)) {
|
|
562
|
-
if (r.doc && r.doc.elo) {
|
|
563
|
-
ret.push((0, import_common.toCourseElo)(r.doc.elo));
|
|
564
|
-
} else {
|
|
565
|
-
logger.warn("no elo data for card: " + r.id);
|
|
566
|
-
ret.push((0, import_common.blankCourseElo)());
|
|
567
|
-
}
|
|
568
|
-
} else {
|
|
569
|
-
logger.warn("no elo data for card: " + JSON.stringify(r));
|
|
570
|
-
ret.push((0, import_common.blankCourseElo)());
|
|
571
|
-
}
|
|
572
|
-
});
|
|
573
|
-
return ret;
|
|
574
|
-
}
|
|
575
|
-
/**
|
|
576
|
-
* Returns the lowest and highest `global` ELO ratings in the course
|
|
577
|
-
*/
|
|
578
|
-
async getELOBounds() {
|
|
579
|
-
const [low, high] = await Promise.all([
|
|
580
|
-
(await this.db.query("elo", {
|
|
581
|
-
startkey: 0,
|
|
582
|
-
limit: 1,
|
|
583
|
-
include_docs: false
|
|
584
|
-
})).rows[0].key,
|
|
585
|
-
(await this.db.query("elo", {
|
|
586
|
-
limit: 1,
|
|
587
|
-
descending: true,
|
|
588
|
-
startkey: 1e5
|
|
589
|
-
})).rows[0].key
|
|
590
|
-
]);
|
|
591
|
-
return {
|
|
592
|
-
low,
|
|
593
|
-
high
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
async removeCard(id) {
|
|
597
|
-
const doc = await this.db.get(id);
|
|
598
|
-
if (!doc.docType || !(doc.docType === "CARD" /* CARD */)) {
|
|
599
|
-
throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);
|
|
600
|
-
}
|
|
601
|
-
return this.db.remove(doc);
|
|
602
|
-
}
|
|
603
|
-
async getCardDisplayableDataIDs(id) {
|
|
604
|
-
logger.debug(id.join(", "));
|
|
605
|
-
const cards = await this.db.allDocs({
|
|
606
|
-
keys: id,
|
|
607
|
-
include_docs: true
|
|
608
|
-
});
|
|
609
|
-
const ret = {};
|
|
610
|
-
cards.rows.forEach((r) => {
|
|
611
|
-
if (isSuccessRow(r)) {
|
|
612
|
-
ret[r.id] = r.doc.id_displayable_data;
|
|
613
|
-
}
|
|
614
|
-
});
|
|
615
|
-
await Promise.all(
|
|
616
|
-
cards.rows.map((r) => {
|
|
617
|
-
return async () => {
|
|
618
|
-
if (isSuccessRow(r)) {
|
|
619
|
-
ret[r.id] = r.doc.id_displayable_data;
|
|
620
|
-
}
|
|
621
|
-
};
|
|
622
|
-
})
|
|
623
|
-
);
|
|
624
|
-
return ret;
|
|
625
|
-
}
|
|
626
|
-
async getCardsByELO(elo, cardLimit) {
|
|
627
|
-
elo = parseInt(elo);
|
|
628
|
-
const limit = cardLimit ? cardLimit : 25;
|
|
629
|
-
const below = await this.db.query("elo", {
|
|
630
|
-
limit: Math.ceil(limit / 2),
|
|
631
|
-
startkey: elo,
|
|
632
|
-
descending: true
|
|
633
|
-
});
|
|
634
|
-
const aboveLimit = limit - below.rows.length;
|
|
635
|
-
const above = await this.db.query("elo", {
|
|
636
|
-
limit: aboveLimit,
|
|
637
|
-
startkey: elo + 1
|
|
638
|
-
});
|
|
639
|
-
let cards = below.rows;
|
|
640
|
-
cards = cards.concat(above.rows);
|
|
641
|
-
const ret = cards.sort((a, b) => {
|
|
642
|
-
const s = Math.abs(a.key - elo) - Math.abs(b.key - elo);
|
|
643
|
-
if (s === 0) {
|
|
644
|
-
return Math.random() - 0.5;
|
|
645
|
-
} else {
|
|
646
|
-
return s;
|
|
647
|
-
}
|
|
648
|
-
}).map((c) => `${this.id}-${c.id}-${c.key}`);
|
|
649
|
-
const str = `below:
|
|
650
|
-
${below.rows.map((r) => ` ${r.id}-${r.key}
|
|
651
|
-
`)}
|
|
652
|
-
|
|
653
|
-
above:
|
|
654
|
-
${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
655
|
-
`)}`;
|
|
656
|
-
logger.debug(`Getting ${limit} cards centered around elo: ${elo}:
|
|
588
|
+
}
|
|
589
|
+
});
|
|
657
590
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
async updateCourseConfig(cfg) {
|
|
670
|
-
logger.debug(`Updating: ${JSON.stringify(cfg)}`);
|
|
671
|
-
try {
|
|
672
|
-
return await updateCredentialledCourseConfig(this.id, cfg);
|
|
673
|
-
} catch (error) {
|
|
674
|
-
logger.error(`Error updating course config in course DB: ${error}`);
|
|
675
|
-
throw error;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
async updateCardElo(cardId, elo) {
|
|
679
|
-
if (!elo) {
|
|
680
|
-
throw new Error(`Cannot update card elo with null or undefined value for card ID: ${cardId}`);
|
|
681
|
-
}
|
|
682
|
-
try {
|
|
683
|
-
const result = await this.updateQueue.update(cardId, (card) => {
|
|
684
|
-
logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);
|
|
685
|
-
card.elo = elo;
|
|
686
|
-
return card;
|
|
687
|
-
});
|
|
688
|
-
return { ok: true, id: cardId, rev: result._rev };
|
|
689
|
-
} catch (error) {
|
|
690
|
-
logger.error(`Failed to update card elo for card ID: ${cardId}`, error);
|
|
691
|
-
throw new Error(`Failed to update card elo for card ID: ${cardId}`);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
async getAppliedTags(cardId) {
|
|
695
|
-
const ret = await getAppliedTags(this.id, cardId);
|
|
696
|
-
if (ret) {
|
|
697
|
-
return ret;
|
|
698
|
-
} else {
|
|
699
|
-
throw new Error(`Failed to find tags for card ${this.id}-${cardId}`);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
async addTagToCard(cardId, tagId, updateELO) {
|
|
703
|
-
return await addTagToCard(this.id, cardId, tagId, (await this._getCurrentUser()).getUsername(), updateELO);
|
|
704
|
-
}
|
|
705
|
-
async removeTagFromCard(cardId, tagId) {
|
|
706
|
-
return await removeTagFromCard(this.id, cardId, tagId);
|
|
707
|
-
}
|
|
708
|
-
async createTag(name, author) {
|
|
709
|
-
return await createTag(this.id, name, author);
|
|
710
|
-
}
|
|
711
|
-
async getTag(tagId) {
|
|
712
|
-
return await getTag(this.id, tagId);
|
|
713
|
-
}
|
|
714
|
-
async updateTag(tag) {
|
|
715
|
-
if (tag.course !== this.id) {
|
|
716
|
-
throw new Error(`Tag ${JSON.stringify(tag)} does not belong to course ${this.id}`);
|
|
717
|
-
}
|
|
718
|
-
return await updateTag(tag);
|
|
719
|
-
}
|
|
720
|
-
async getCourseTagStubs() {
|
|
721
|
-
return getCourseTagStubs(this.id);
|
|
722
|
-
}
|
|
723
|
-
async addNote(codeCourse, shape, data, author, tags, uploads, elo = (0, import_common.blankCourseElo)()) {
|
|
724
|
-
try {
|
|
725
|
-
const resp = await addNote55(this.id, codeCourse, shape, data, author, tags, uploads, elo);
|
|
726
|
-
if (resp.ok) {
|
|
727
|
-
if (resp.cardCreationFailed) {
|
|
728
|
-
logger.warn(
|
|
729
|
-
`[courseDB.addNote] Note added but card creation failed: ${resp.cardCreationError}`
|
|
730
|
-
);
|
|
731
|
-
return {
|
|
732
|
-
status: import_common.Status.error,
|
|
733
|
-
message: `Note was added but no cards were created: ${resp.cardCreationError}`,
|
|
734
|
-
id: resp.id
|
|
735
|
-
};
|
|
736
|
-
}
|
|
737
|
-
return {
|
|
738
|
-
status: import_common.Status.ok,
|
|
739
|
-
message: "",
|
|
740
|
-
id: resp.id
|
|
741
|
-
};
|
|
742
|
-
} else {
|
|
743
|
-
return {
|
|
744
|
-
status: import_common.Status.error,
|
|
745
|
-
message: "Unexpected error adding note"
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
} catch (e) {
|
|
749
|
-
const err = e;
|
|
750
|
-
logger.error(
|
|
751
|
-
`[addNote] error ${err.name}
|
|
752
|
-
reason: ${err.reason}
|
|
753
|
-
message: ${err.message}`
|
|
754
|
-
);
|
|
755
|
-
return {
|
|
756
|
-
status: import_common.Status.error,
|
|
757
|
-
message: `Error adding note to course. ${e.reason || err.message}`
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
async getCourseDoc(id, options) {
|
|
762
|
-
return await getCourseDoc(this.id, id, options);
|
|
763
|
-
}
|
|
764
|
-
async getCourseDocs(ids, options = {}) {
|
|
765
|
-
return await getCourseDocs(this.id, ids, options);
|
|
766
|
-
}
|
|
767
|
-
////////////////////////////////////
|
|
768
|
-
// NavigationStrategyManager implementation
|
|
769
|
-
////////////////////////////////////
|
|
770
|
-
getNavigationStrategy(id) {
|
|
771
|
-
logger.debug(`[courseDB] Getting navigation strategy: ${id}`);
|
|
772
|
-
const strategy = {
|
|
773
|
-
id: "ELO",
|
|
774
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
775
|
-
name: "ELO",
|
|
776
|
-
description: "ELO-based navigation strategy for ordering content by difficulty",
|
|
777
|
-
implementingClass: "elo" /* ELO */,
|
|
778
|
-
course: this.id,
|
|
779
|
-
serializedData: ""
|
|
780
|
-
// serde is a noop for ELO navigator.
|
|
781
|
-
};
|
|
782
|
-
return Promise.resolve(strategy);
|
|
783
|
-
}
|
|
784
|
-
getAllNavigationStrategies() {
|
|
785
|
-
logger.debug("[courseDB] Returning hard-coded navigation strategies");
|
|
786
|
-
const strategies = [
|
|
787
|
-
{
|
|
788
|
-
id: "ELO",
|
|
789
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
790
|
-
name: "ELO",
|
|
791
|
-
description: "ELO-based navigation strategy for ordering content by difficulty",
|
|
792
|
-
implementingClass: "elo" /* ELO */,
|
|
793
|
-
course: this.id,
|
|
794
|
-
serializedData: ""
|
|
795
|
-
// serde is a noop for ELO navigator.
|
|
796
|
-
}
|
|
797
|
-
];
|
|
798
|
-
return Promise.resolve(strategies);
|
|
799
|
-
}
|
|
800
|
-
addNavigationStrategy(data) {
|
|
801
|
-
logger.debug(`[courseDB] Adding navigation strategy: ${data.id}`);
|
|
802
|
-
logger.debug(JSON.stringify(data));
|
|
803
|
-
return Promise.resolve();
|
|
804
|
-
}
|
|
805
|
-
updateNavigationStrategy(id, data) {
|
|
806
|
-
logger.debug(`[courseDB] Updating navigation strategy: ${id}`);
|
|
807
|
-
logger.debug(JSON.stringify(data));
|
|
808
|
-
return Promise.resolve();
|
|
809
|
-
}
|
|
810
|
-
async surfaceNavigationStrategy() {
|
|
811
|
-
logger.warn(`Returning hard-coded default ELO navigator`);
|
|
812
|
-
const ret = {
|
|
813
|
-
id: "ELO",
|
|
814
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
815
|
-
name: "ELO",
|
|
816
|
-
description: "ELO-based navigation strategy",
|
|
817
|
-
implementingClass: "elo" /* ELO */,
|
|
818
|
-
course: this.id,
|
|
819
|
-
serializedData: ""
|
|
820
|
-
// serde is a noop for ELO navigator.
|
|
821
|
-
};
|
|
822
|
-
return Promise.resolve(ret);
|
|
823
|
-
}
|
|
824
|
-
////////////////////////////////////
|
|
825
|
-
// END NavigationStrategyManager implementation
|
|
826
|
-
////////////////////////////////////
|
|
827
|
-
////////////////////////////////////
|
|
828
|
-
// StudyContentSource implementation
|
|
829
|
-
////////////////////////////////////
|
|
830
|
-
async getNewCards(limit = 99) {
|
|
831
|
-
const u = await this._getCurrentUser();
|
|
832
|
-
try {
|
|
833
|
-
const strategy = await this.surfaceNavigationStrategy();
|
|
834
|
-
const navigator = await ContentNavigator.create(u, this, strategy);
|
|
835
|
-
return navigator.getNewCards(limit);
|
|
836
|
-
} catch (e) {
|
|
837
|
-
logger.error(`[courseDB] Error surfacing a NavigationStrategy: ${e}`);
|
|
838
|
-
throw e;
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
async getPendingReviews() {
|
|
842
|
-
const u = await this._getCurrentUser();
|
|
843
|
-
try {
|
|
844
|
-
const strategy = await this.surfaceNavigationStrategy();
|
|
845
|
-
const navigator = await ContentNavigator.create(u, this, strategy);
|
|
846
|
-
return navigator.getPendingReviews();
|
|
847
|
-
} catch (e) {
|
|
848
|
-
logger.error(`[courseDB] Error surfacing a NavigationStrategy: ${e}`);
|
|
849
|
-
throw e;
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
async getCardsCenteredAtELO(options = {
|
|
853
|
-
limit: 99,
|
|
854
|
-
elo: "user"
|
|
855
|
-
}, filter) {
|
|
856
|
-
let targetElo;
|
|
857
|
-
if (options.elo === "user") {
|
|
858
|
-
const u = await this._getCurrentUser();
|
|
859
|
-
targetElo = -1;
|
|
860
|
-
try {
|
|
861
|
-
const courseDoc = (await u.getCourseRegistrationsDoc()).courses.find((c) => {
|
|
862
|
-
return c.courseID === this.id;
|
|
863
|
-
});
|
|
864
|
-
targetElo = (0, import_common.EloToNumber)(courseDoc.elo);
|
|
865
|
-
} catch {
|
|
866
|
-
targetElo = 1e3;
|
|
867
|
-
}
|
|
868
|
-
} else if (options.elo === "random") {
|
|
869
|
-
const bounds = await GET_CACHED(`elo-bounds-${this.id}`, () => this.getELOBounds());
|
|
870
|
-
targetElo = Math.round(bounds.low + Math.random() * (bounds.high - bounds.low));
|
|
871
|
-
} else {
|
|
872
|
-
targetElo = options.elo;
|
|
873
|
-
}
|
|
874
|
-
let cards = [];
|
|
875
|
-
let mult = 4;
|
|
876
|
-
let previousCount = -1;
|
|
877
|
-
let newCount = 0;
|
|
878
|
-
while (cards.length < options.limit && newCount !== previousCount) {
|
|
879
|
-
cards = await this.getCardsByELO(targetElo, mult * options.limit);
|
|
880
|
-
previousCount = newCount;
|
|
881
|
-
newCount = cards.length;
|
|
882
|
-
logger.debug(`Found ${cards.length} elo neighbor cards...`);
|
|
883
|
-
if (filter) {
|
|
884
|
-
cards = cards.filter(filter);
|
|
885
|
-
logger.debug(`Filtered to ${cards.length} cards...`);
|
|
886
|
-
}
|
|
887
|
-
mult *= 2;
|
|
888
|
-
}
|
|
889
|
-
const selectedCards = [];
|
|
890
|
-
while (selectedCards.length < options.limit && cards.length > 0) {
|
|
891
|
-
const index = randIntWeightedTowardZero(cards.length);
|
|
892
|
-
const card = cards.splice(index, 1)[0];
|
|
893
|
-
selectedCards.push(card);
|
|
894
|
-
}
|
|
895
|
-
return selectedCards.map((c) => {
|
|
896
|
-
const split = c.split("-");
|
|
897
|
-
return {
|
|
898
|
-
courseID: this.id,
|
|
899
|
-
cardID: split[1],
|
|
900
|
-
qualifiedID: `${split[0]}-${split[1]}`,
|
|
901
|
-
contentSourceType: "course",
|
|
902
|
-
contentSourceID: this.id,
|
|
903
|
-
status: "new"
|
|
904
|
-
};
|
|
905
|
-
});
|
|
906
|
-
}
|
|
907
|
-
};
|
|
591
|
+
// src/impl/couch/classroomDB.ts
|
|
592
|
+
var import_moment3;
|
|
593
|
+
var init_classroomDB2 = __esm({
|
|
594
|
+
"src/impl/couch/classroomDB.ts"() {
|
|
595
|
+
"use strict";
|
|
596
|
+
init_factory();
|
|
597
|
+
init_logger();
|
|
598
|
+
import_moment3 = __toESM(require("moment"));
|
|
599
|
+
init_pouchdb_setup();
|
|
600
|
+
init_couch();
|
|
601
|
+
init_courseDB();
|
|
908
602
|
}
|
|
909
603
|
});
|
|
910
604
|
|
|
911
|
-
// src/impl/
|
|
912
|
-
var
|
|
913
|
-
"src/impl/
|
|
605
|
+
// src/impl/couch/adminDB.ts
|
|
606
|
+
var init_adminDB2 = __esm({
|
|
607
|
+
"src/impl/couch/adminDB.ts"() {
|
|
914
608
|
"use strict";
|
|
609
|
+
init_pouchdb_setup();
|
|
610
|
+
init_factory();
|
|
611
|
+
init_couch();
|
|
612
|
+
init_classroomDB2();
|
|
613
|
+
init_courseLookupDB();
|
|
614
|
+
init_logger();
|
|
915
615
|
}
|
|
916
616
|
});
|
|
917
617
|
|
|
918
|
-
// src/
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
}
|
|
922
|
-
var init_util = __esm({
|
|
923
|
-
"src/core/util/index.ts"() {
|
|
618
|
+
// src/impl/couch/auth.ts
|
|
619
|
+
var init_auth = __esm({
|
|
620
|
+
"src/impl/couch/auth.ts"() {
|
|
924
621
|
"use strict";
|
|
622
|
+
init_factory();
|
|
925
623
|
init_types_legacy();
|
|
624
|
+
init_logger();
|
|
926
625
|
}
|
|
927
626
|
});
|
|
928
627
|
|
|
929
|
-
// src/impl/
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
endkey: prefix + "\uFFF0",
|
|
934
|
-
include_docs: true
|
|
935
|
-
};
|
|
936
|
-
if (opts) {
|
|
937
|
-
Object.assign(options, opts);
|
|
938
|
-
}
|
|
939
|
-
return db.allDocs(options);
|
|
940
|
-
}
|
|
941
|
-
function getStartAndEndKeys2(key) {
|
|
942
|
-
return {
|
|
943
|
-
startkey: key,
|
|
944
|
-
endkey: key + "\uFFF0"
|
|
945
|
-
};
|
|
946
|
-
}
|
|
947
|
-
function getLocalUserDB(username) {
|
|
948
|
-
return new pouchdb_setup_default(`userdb-${username}`, {});
|
|
949
|
-
}
|
|
950
|
-
function scheduleCardReviewLocal(userDB, review) {
|
|
951
|
-
const now = import_moment.default.utc();
|
|
952
|
-
logger.info(`Scheduling for review in: ${review.time.diff(now, "h") / 24} days`);
|
|
953
|
-
void userDB.put({
|
|
954
|
-
_id: REVIEW_PREFIX + review.time.format(REVIEW_TIME_FORMAT),
|
|
955
|
-
cardId: review.card_id,
|
|
956
|
-
reviewTime: review.time,
|
|
957
|
-
courseId: review.course_id,
|
|
958
|
-
scheduledAt: now,
|
|
959
|
-
scheduledFor: review.scheduledFor,
|
|
960
|
-
schedulingAgentId: review.schedulingAgentId
|
|
961
|
-
});
|
|
962
|
-
}
|
|
963
|
-
async function removeScheduledCardReviewLocal(userDB, reviewDocID) {
|
|
964
|
-
const reviewDoc = await userDB.get(reviewDocID);
|
|
965
|
-
userDB.remove(reviewDoc).then((res) => {
|
|
966
|
-
if (res.ok) {
|
|
967
|
-
log(`Removed Review Doc: ${reviewDocID}`);
|
|
968
|
-
}
|
|
969
|
-
}).catch((err) => {
|
|
970
|
-
log(`Failed to remove Review Doc: ${reviewDocID},
|
|
971
|
-
${JSON.stringify(err)}`);
|
|
972
|
-
});
|
|
973
|
-
}
|
|
974
|
-
var import_moment, REVIEW_PREFIX, REVIEW_TIME_FORMAT, log;
|
|
975
|
-
var init_userDBHelpers = __esm({
|
|
976
|
-
"src/impl/common/userDBHelpers.ts"() {
|
|
628
|
+
// src/impl/couch/CouchDBSyncStrategy.ts
|
|
629
|
+
var import_common6;
|
|
630
|
+
var init_CouchDBSyncStrategy = __esm({
|
|
631
|
+
"src/impl/couch/CouchDBSyncStrategy.ts"() {
|
|
977
632
|
"use strict";
|
|
978
|
-
|
|
633
|
+
init_factory();
|
|
634
|
+
init_types_legacy();
|
|
979
635
|
init_logger();
|
|
636
|
+
import_common6 = require("@vue-skuilder/common");
|
|
637
|
+
init_common();
|
|
980
638
|
init_pouchdb_setup();
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
log = (s) => {
|
|
984
|
-
logger.info(s);
|
|
985
|
-
};
|
|
639
|
+
init_couch();
|
|
640
|
+
init_auth();
|
|
986
641
|
}
|
|
987
642
|
});
|
|
988
643
|
|
|
989
|
-
// src/impl/couch/
|
|
990
|
-
var
|
|
991
|
-
var
|
|
992
|
-
"src/impl/couch/
|
|
644
|
+
// src/impl/couch/index.ts
|
|
645
|
+
var import_moment4, import_process, isBrowser, GUEST_LOCAL_DB, localUserDB, pouchDBincludeCredentialsConfig;
|
|
646
|
+
var init_couch = __esm({
|
|
647
|
+
"src/impl/couch/index.ts"() {
|
|
993
648
|
"use strict";
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
649
|
+
init_factory();
|
|
650
|
+
init_types_legacy();
|
|
651
|
+
import_moment4 = __toESM(require("moment"));
|
|
997
652
|
init_logger();
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
return (await this.getPendingReviews()).length;
|
|
1017
|
-
}
|
|
1018
|
-
async getCourseSettings() {
|
|
1019
|
-
const regDoc = await this.user.getCourseRegistrationsDoc();
|
|
1020
|
-
const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);
|
|
1021
|
-
if (crsDoc && crsDoc.settings) {
|
|
1022
|
-
return crsDoc.settings;
|
|
1023
|
-
} else {
|
|
1024
|
-
logger.warn(`no settings found during lookup on course ${this._courseId}`);
|
|
1025
|
-
return {};
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
updateCourseSettings(updates) {
|
|
1029
|
-
void this.user.updateCourseSettings(this._courseId, updates);
|
|
1030
|
-
}
|
|
1031
|
-
async getReviewstoDate(targetDate) {
|
|
1032
|
-
const keys = getStartAndEndKeys(REVIEW_PREFIX2);
|
|
1033
|
-
const reviews = await this.user.remote().allDocs({
|
|
1034
|
-
startkey: keys.startkey,
|
|
1035
|
-
endkey: keys.endkey,
|
|
1036
|
-
include_docs: true
|
|
1037
|
-
});
|
|
1038
|
-
logger.debug(
|
|
1039
|
-
`Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`
|
|
1040
|
-
);
|
|
1041
|
-
return reviews.rows.filter((r) => {
|
|
1042
|
-
if (r.id.startsWith(REVIEW_PREFIX2)) {
|
|
1043
|
-
const date = import_moment2.default.utc(r.id.substr(REVIEW_PREFIX2.length), REVIEW_TIME_FORMAT2);
|
|
1044
|
-
if (targetDate.isAfter(date)) {
|
|
1045
|
-
if (this._courseId === void 0 || r.doc.courseId === this._courseId) {
|
|
1046
|
-
return true;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
}).map((r) => r.doc);
|
|
653
|
+
init_pouchdb_setup();
|
|
654
|
+
import_process = __toESM(require("process"));
|
|
655
|
+
init_contentSource();
|
|
656
|
+
init_adminDB2();
|
|
657
|
+
init_classroomDB2();
|
|
658
|
+
init_courseAPI();
|
|
659
|
+
init_courseDB();
|
|
660
|
+
init_CouchDBSyncStrategy();
|
|
661
|
+
isBrowser = typeof window !== "undefined";
|
|
662
|
+
if (isBrowser) {
|
|
663
|
+
window.process = import_process.default;
|
|
664
|
+
}
|
|
665
|
+
GUEST_LOCAL_DB = `userdb-${GuestUsername}`;
|
|
666
|
+
localUserDB = new pouchdb_setup_default(GUEST_LOCAL_DB);
|
|
667
|
+
pouchDBincludeCredentialsConfig = {
|
|
668
|
+
fetch(url, opts) {
|
|
669
|
+
opts.credentials = "include";
|
|
670
|
+
return pouchdb_setup_default.fetch(url, opts);
|
|
1051
671
|
}
|
|
1052
672
|
};
|
|
1053
673
|
}
|
|
@@ -1113,7 +733,7 @@ async function updateUserElo(user, course_id, elo) {
|
|
|
1113
733
|
return getLocalUserDB(user).put(regDoc);
|
|
1114
734
|
}
|
|
1115
735
|
async function registerUserForClassroom(user, classID, registerAs) {
|
|
1116
|
-
|
|
736
|
+
log3(`Registering user: ${user} in course: ${classID}`);
|
|
1117
737
|
return getOrCreateClassroomRegistrationsDoc(user).then((doc) => {
|
|
1118
738
|
const regItem = {
|
|
1119
739
|
classID,
|
|
@@ -1124,7 +744,7 @@ async function registerUserForClassroom(user, classID, registerAs) {
|
|
|
1124
744
|
}).length === 0) {
|
|
1125
745
|
doc.registrations.push(regItem);
|
|
1126
746
|
} else {
|
|
1127
|
-
|
|
747
|
+
log3(`User ${user} is already registered for class ${classID}`);
|
|
1128
748
|
}
|
|
1129
749
|
return getLocalUserDB(user).put(doc);
|
|
1130
750
|
});
|
|
@@ -1146,23 +766,23 @@ async function dropUserFromClassroom(user, classID) {
|
|
|
1146
766
|
async function getUserClassrooms(user) {
|
|
1147
767
|
return getOrCreateClassroomRegistrationsDoc(user);
|
|
1148
768
|
}
|
|
1149
|
-
var
|
|
769
|
+
var import_common8, import_moment5, log3, BaseUser, userCoursesDoc, userClassroomsDoc;
|
|
1150
770
|
var init_BaseUserDB = __esm({
|
|
1151
771
|
"src/impl/common/BaseUserDB.ts"() {
|
|
1152
772
|
"use strict";
|
|
773
|
+
init_core();
|
|
1153
774
|
init_util();
|
|
1154
|
-
|
|
1155
|
-
|
|
775
|
+
import_common8 = require("@vue-skuilder/common");
|
|
776
|
+
import_moment5 = __toESM(require("moment"));
|
|
1156
777
|
init_types_legacy();
|
|
1157
778
|
init_logger();
|
|
1158
779
|
init_userDBHelpers();
|
|
1159
780
|
init_updateQueue();
|
|
1160
781
|
init_user_course_relDB();
|
|
1161
782
|
init_couch();
|
|
1162
|
-
|
|
783
|
+
log3 = (s) => {
|
|
1163
784
|
logger.info(s);
|
|
1164
785
|
};
|
|
1165
|
-
cardHistoryPrefix2 = "cardH-";
|
|
1166
786
|
BaseUser = class _BaseUser {
|
|
1167
787
|
static _instance;
|
|
1168
788
|
static _initialized = false;
|
|
@@ -1183,11 +803,13 @@ var init_BaseUserDB = __esm({
|
|
|
1183
803
|
isLoggedIn() {
|
|
1184
804
|
return !this._username.startsWith(GuestUsername);
|
|
1185
805
|
}
|
|
1186
|
-
remoteDB;
|
|
1187
806
|
remote() {
|
|
1188
807
|
return this.remoteDB;
|
|
1189
808
|
}
|
|
1190
809
|
localDB;
|
|
810
|
+
remoteDB;
|
|
811
|
+
writeDB;
|
|
812
|
+
// Database to use for write operations (local-first approach)
|
|
1191
813
|
updateQueue;
|
|
1192
814
|
async createAccount(username, password) {
|
|
1193
815
|
if (!this.syncStrategy.canCreateAccount()) {
|
|
@@ -1200,10 +822,14 @@ Currently logged-in as ${this._username}.`
|
|
|
1200
822
|
);
|
|
1201
823
|
}
|
|
1202
824
|
const result = await this.syncStrategy.createAccount(username, password);
|
|
1203
|
-
if (result.status ===
|
|
1204
|
-
|
|
825
|
+
if (result.status === import_common8.Status.ok) {
|
|
826
|
+
log3(`Account created successfully, updating username to ${username}`);
|
|
1205
827
|
this._username = username;
|
|
1206
|
-
|
|
828
|
+
try {
|
|
829
|
+
localStorage.removeItem("dbUUID");
|
|
830
|
+
} catch (e) {
|
|
831
|
+
logger.warn("localStorage not available (Node.js environment):", e);
|
|
832
|
+
}
|
|
1207
833
|
await this.init();
|
|
1208
834
|
}
|
|
1209
835
|
return {
|
|
@@ -1215,15 +841,22 @@ Currently logged-in as ${this._username}.`
|
|
|
1215
841
|
if (!this.syncStrategy.canAuthenticate()) {
|
|
1216
842
|
throw new Error("Authentication not supported by current sync strategy");
|
|
1217
843
|
}
|
|
1218
|
-
if (!this._username.startsWith(GuestUsername)) {
|
|
1219
|
-
|
|
1220
|
-
|
|
844
|
+
if (!this._username.startsWith(GuestUsername) && this._username != username) {
|
|
845
|
+
if (this._username != username) {
|
|
846
|
+
throw new Error(`Cannot change accounts while logged in.
|
|
847
|
+
Log out of account ${this.getUsername()} before logging in as ${username}.`);
|
|
848
|
+
}
|
|
849
|
+
logger.warn(`User ${this._username} is already logged in, but executing login again.`);
|
|
1221
850
|
}
|
|
1222
851
|
const loginResult = await this.syncStrategy.authenticate(username, password);
|
|
1223
852
|
if (loginResult.ok) {
|
|
1224
|
-
|
|
853
|
+
log3(`Logged in as ${username}`);
|
|
1225
854
|
this._username = username;
|
|
1226
|
-
|
|
855
|
+
try {
|
|
856
|
+
localStorage.removeItem("dbUUID");
|
|
857
|
+
} catch (e) {
|
|
858
|
+
logger.warn("localStorage not available (Node.js environment):", e);
|
|
859
|
+
}
|
|
1227
860
|
await this.init();
|
|
1228
861
|
}
|
|
1229
862
|
return loginResult;
|
|
@@ -1231,7 +864,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1231
864
|
async resetUserData() {
|
|
1232
865
|
if (this.syncStrategy.canAuthenticate()) {
|
|
1233
866
|
return {
|
|
1234
|
-
status:
|
|
867
|
+
status: import_common8.Status.error,
|
|
1235
868
|
error: "Reset user data is only available for local-only mode. Use logout instead for remote sync."
|
|
1236
869
|
};
|
|
1237
870
|
}
|
|
@@ -1240,8 +873,8 @@ Currently logged-in as ${this._username}.`
|
|
|
1240
873
|
const allDocs = await localDB.allDocs({ include_docs: false });
|
|
1241
874
|
const docsToDelete = allDocs.rows.filter((row) => {
|
|
1242
875
|
const id = row.id;
|
|
1243
|
-
return id.startsWith(
|
|
1244
|
-
id.startsWith(
|
|
876
|
+
return id.startsWith(DocTypePrefixes["CARDRECORD" /* CARDRECORD */]) || // Card interaction history
|
|
877
|
+
id.startsWith(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]) || // Scheduled reviews
|
|
1245
878
|
id === _BaseUser.DOC_IDS.COURSE_REGISTRATIONS || // Course registrations
|
|
1246
879
|
id === _BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS || // Classroom registrations
|
|
1247
880
|
id === _BaseUser.DOC_IDS.CONFIG;
|
|
@@ -1250,11 +883,11 @@ Currently logged-in as ${this._username}.`
|
|
|
1250
883
|
await localDB.bulkDocs(docsToDelete);
|
|
1251
884
|
}
|
|
1252
885
|
await this.init();
|
|
1253
|
-
return { status:
|
|
886
|
+
return { status: import_common8.Status.ok };
|
|
1254
887
|
} catch (error) {
|
|
1255
888
|
logger.error("Failed to reset user data:", error);
|
|
1256
889
|
return {
|
|
1257
|
-
status:
|
|
890
|
+
status: import_common8.Status.error,
|
|
1258
891
|
error: error instanceof Error ? error.message : "Unknown error during reset"
|
|
1259
892
|
};
|
|
1260
893
|
}
|
|
@@ -1310,7 +943,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1310
943
|
*
|
|
1311
944
|
*/
|
|
1312
945
|
async getActiveCards() {
|
|
1313
|
-
const keys =
|
|
946
|
+
const keys = getStartAndEndKeys(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]);
|
|
1314
947
|
const reviews = await this.remoteDB.allDocs({
|
|
1315
948
|
startkey: keys.startkey,
|
|
1316
949
|
endkey: keys.endkey,
|
|
@@ -1382,18 +1015,21 @@ Currently logged-in as ${this._username}.`
|
|
|
1382
1015
|
}
|
|
1383
1016
|
}
|
|
1384
1017
|
async getReviewstoDate(targetDate, course_id) {
|
|
1385
|
-
const keys =
|
|
1018
|
+
const keys = getStartAndEndKeys(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]);
|
|
1386
1019
|
const reviews = await this.remoteDB.allDocs({
|
|
1387
1020
|
startkey: keys.startkey,
|
|
1388
1021
|
endkey: keys.endkey,
|
|
1389
1022
|
include_docs: true
|
|
1390
1023
|
});
|
|
1391
|
-
|
|
1024
|
+
log3(
|
|
1392
1025
|
`Fetching ${this._username}'s scheduled reviews${course_id ? ` for course ${course_id}` : ""}.`
|
|
1393
1026
|
);
|
|
1394
1027
|
return reviews.rows.filter((r) => {
|
|
1395
|
-
if (r.id.startsWith(
|
|
1396
|
-
const date =
|
|
1028
|
+
if (r.id.startsWith(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */])) {
|
|
1029
|
+
const date = import_moment5.default.utc(
|
|
1030
|
+
r.id.substr(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */].length),
|
|
1031
|
+
REVIEW_TIME_FORMAT
|
|
1032
|
+
);
|
|
1397
1033
|
if (targetDate.isAfter(date)) {
|
|
1398
1034
|
if (course_id === void 0 || r.doc.courseId === course_id) {
|
|
1399
1035
|
return true;
|
|
@@ -1403,11 +1039,11 @@ Currently logged-in as ${this._username}.`
|
|
|
1403
1039
|
}).map((r) => r.doc);
|
|
1404
1040
|
}
|
|
1405
1041
|
async getReviewsForcast(daysCount) {
|
|
1406
|
-
const time =
|
|
1042
|
+
const time = import_moment5.default.utc().add(daysCount, "days");
|
|
1407
1043
|
return this.getReviewstoDate(time);
|
|
1408
1044
|
}
|
|
1409
1045
|
async getPendingReviews(course_id) {
|
|
1410
|
-
const now =
|
|
1046
|
+
const now = import_moment5.default.utc();
|
|
1411
1047
|
return this.getReviewstoDate(now, course_id);
|
|
1412
1048
|
}
|
|
1413
1049
|
async getScheduledReviewCount(course_id) {
|
|
@@ -1450,12 +1086,12 @@ Currently logged-in as ${this._username}.`
|
|
|
1450
1086
|
if (doc.courses.filter((course) => {
|
|
1451
1087
|
return course.courseID === regItem.courseID;
|
|
1452
1088
|
}).length === 0) {
|
|
1453
|
-
|
|
1089
|
+
log3(`It's a new course registration!`);
|
|
1454
1090
|
doc.courses.push(regItem);
|
|
1455
1091
|
doc.studyWeight[course_id] = 1;
|
|
1456
1092
|
} else {
|
|
1457
1093
|
doc.courses.forEach((c) => {
|
|
1458
|
-
|
|
1094
|
+
log3(`Found the previously registered course!`);
|
|
1459
1095
|
if (c.courseID === course_id) {
|
|
1460
1096
|
c.status = status;
|
|
1461
1097
|
}
|
|
@@ -1463,7 +1099,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1463
1099
|
}
|
|
1464
1100
|
return this.localDB.put(doc);
|
|
1465
1101
|
}).catch((e) => {
|
|
1466
|
-
|
|
1102
|
+
log3(`Registration failed because of: ${JSON.stringify(e)}`);
|
|
1467
1103
|
throw e;
|
|
1468
1104
|
});
|
|
1469
1105
|
}
|
|
@@ -1508,7 +1144,8 @@ Currently logged-in as ${this._username}.`
|
|
|
1508
1144
|
const defaultConfig = {
|
|
1509
1145
|
_id: _BaseUser.DOC_IDS.CONFIG,
|
|
1510
1146
|
darkMode: false,
|
|
1511
|
-
likesConfetti: false
|
|
1147
|
+
likesConfetti: false,
|
|
1148
|
+
sessionTimeLimit: 5
|
|
1512
1149
|
};
|
|
1513
1150
|
try {
|
|
1514
1151
|
const cfg = await this.localDB.get(_BaseUser.DOC_IDS.CONFIG);
|
|
@@ -1581,10 +1218,15 @@ Currently logged-in as ${this._username}.`
|
|
|
1581
1218
|
setDBandQ() {
|
|
1582
1219
|
this.localDB = getLocalUserDB(this._username);
|
|
1583
1220
|
this.remoteDB = this.syncStrategy.setupRemoteDB(this._username);
|
|
1584
|
-
this.
|
|
1221
|
+
this.writeDB = this.syncStrategy.getWriteDB ? this.syncStrategy.getWriteDB(this._username) : this.localDB;
|
|
1222
|
+
this.updateQueue = new UpdateQueue(this.localDB, this.writeDB);
|
|
1585
1223
|
}
|
|
1586
1224
|
async init() {
|
|
1587
1225
|
_BaseUser._initialized = false;
|
|
1226
|
+
if (this._username === "admin") {
|
|
1227
|
+
_BaseUser._initialized = true;
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1588
1230
|
this.setDBandQ();
|
|
1589
1231
|
this.syncStrategy.startSync(this.localDB, this.remoteDB);
|
|
1590
1232
|
void this.applyDesignDocs();
|
|
@@ -1606,6 +1248,9 @@ Currently logged-in as ${this._username}.`
|
|
|
1606
1248
|
}
|
|
1607
1249
|
];
|
|
1608
1250
|
async applyDesignDocs() {
|
|
1251
|
+
if (this._username === "admin") {
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1609
1254
|
for (const doc of _BaseUser.designDocs) {
|
|
1610
1255
|
try {
|
|
1611
1256
|
try {
|
|
@@ -1622,7 +1267,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1622
1267
|
}
|
|
1623
1268
|
}
|
|
1624
1269
|
} catch (error) {
|
|
1625
|
-
if (error
|
|
1270
|
+
if (error.name && error.name === "conflict") {
|
|
1626
1271
|
logger.warn(`Design doc ${doc._id} update conflict - will retry`);
|
|
1627
1272
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1628
1273
|
await this.applyDesignDoc(doc);
|
|
@@ -1660,7 +1305,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1660
1305
|
*/
|
|
1661
1306
|
async putCardRecord(record) {
|
|
1662
1307
|
const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);
|
|
1663
|
-
record.timeStamp =
|
|
1308
|
+
record.timeStamp = import_moment5.default.utc(record.timeStamp).toString();
|
|
1664
1309
|
try {
|
|
1665
1310
|
const cardHistory = await this.update(
|
|
1666
1311
|
cardHistoryID,
|
|
@@ -1676,7 +1321,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1676
1321
|
const ret = {
|
|
1677
1322
|
...record2
|
|
1678
1323
|
};
|
|
1679
|
-
ret.timeStamp =
|
|
1324
|
+
ret.timeStamp = import_moment5.default.utc(record2.timeStamp);
|
|
1680
1325
|
return ret;
|
|
1681
1326
|
});
|
|
1682
1327
|
return cardHistory;
|
|
@@ -1692,8 +1337,8 @@ Currently logged-in as ${this._username}.`
|
|
|
1692
1337
|
streak: 0,
|
|
1693
1338
|
bestInterval: 0
|
|
1694
1339
|
};
|
|
1695
|
-
|
|
1696
|
-
return initCardHistory;
|
|
1340
|
+
const putResult = await this.writeDB.put(initCardHistory);
|
|
1341
|
+
return { ...initCardHistory, _rev: putResult.rev };
|
|
1697
1342
|
} else {
|
|
1698
1343
|
throw new Error(`putCardRecord failed because of:
|
|
1699
1344
|
name:${reason.name}
|
|
@@ -1704,17 +1349,17 @@ Currently logged-in as ${this._username}.`
|
|
|
1704
1349
|
}
|
|
1705
1350
|
async deduplicateReviews() {
|
|
1706
1351
|
try {
|
|
1707
|
-
|
|
1352
|
+
log3("Starting deduplication of scheduled reviews...");
|
|
1708
1353
|
const reviewsMap = {};
|
|
1709
1354
|
const duplicateDocIds = [];
|
|
1710
1355
|
const scheduledReviews = await this.remoteDB.query("reviewCards/reviewCards");
|
|
1711
|
-
|
|
1356
|
+
log3(`Found ${scheduledReviews.rows.length} scheduled reviews to process`);
|
|
1712
1357
|
scheduledReviews.rows.forEach((r) => {
|
|
1713
1358
|
const qualifiedCardId = r.value;
|
|
1714
1359
|
const docId = r.key;
|
|
1715
1360
|
if (reviewsMap[qualifiedCardId]) {
|
|
1716
|
-
|
|
1717
|
-
|
|
1361
|
+
log3(`Found duplicate scheduled review for card: ${qualifiedCardId}`);
|
|
1362
|
+
log3(
|
|
1718
1363
|
`Marking earlier review ${reviewsMap[qualifiedCardId]} for deletion, keeping ${docId}`
|
|
1719
1364
|
);
|
|
1720
1365
|
duplicateDocIds.push(reviewsMap[qualifiedCardId]);
|
|
@@ -1724,23 +1369,23 @@ Currently logged-in as ${this._username}.`
|
|
|
1724
1369
|
}
|
|
1725
1370
|
});
|
|
1726
1371
|
if (duplicateDocIds.length > 0) {
|
|
1727
|
-
|
|
1372
|
+
log3(`Removing ${duplicateDocIds.length} duplicate reviews...`);
|
|
1728
1373
|
const deletePromises = duplicateDocIds.map(async (docId) => {
|
|
1729
1374
|
try {
|
|
1730
1375
|
const doc = await this.remoteDB.get(docId);
|
|
1731
|
-
await this.
|
|
1732
|
-
|
|
1376
|
+
await this.writeDB.remove(doc);
|
|
1377
|
+
log3(`Successfully removed duplicate review: ${docId}`);
|
|
1733
1378
|
} catch (error) {
|
|
1734
|
-
|
|
1379
|
+
log3(`Failed to remove duplicate review ${docId}: ${error}`);
|
|
1735
1380
|
}
|
|
1736
1381
|
});
|
|
1737
1382
|
await Promise.all(deletePromises);
|
|
1738
|
-
|
|
1383
|
+
log3(`Deduplication complete. Processed ${duplicateDocIds.length} duplicates`);
|
|
1739
1384
|
} else {
|
|
1740
|
-
|
|
1385
|
+
log3("No duplicate reviews found");
|
|
1741
1386
|
}
|
|
1742
1387
|
} catch (error) {
|
|
1743
|
-
|
|
1388
|
+
log3(`Error during review deduplication: ${error}`);
|
|
1744
1389
|
}
|
|
1745
1390
|
}
|
|
1746
1391
|
/**
|
|
@@ -1750,17 +1395,17 @@ Currently logged-in as ${this._username}.`
|
|
|
1750
1395
|
* @param course_id optional specification of individual course
|
|
1751
1396
|
*/
|
|
1752
1397
|
async getSeenCards(course_id) {
|
|
1753
|
-
let prefix =
|
|
1398
|
+
let prefix = DocTypePrefixes["CARDRECORD" /* CARDRECORD */];
|
|
1754
1399
|
if (course_id) {
|
|
1755
1400
|
prefix += course_id;
|
|
1756
1401
|
}
|
|
1757
|
-
const docs = await
|
|
1402
|
+
const docs = await filterAllDocsByPrefix(this.localDB, prefix, {
|
|
1758
1403
|
include_docs: false
|
|
1759
1404
|
});
|
|
1760
1405
|
const ret = [];
|
|
1761
1406
|
docs.rows.forEach((row) => {
|
|
1762
|
-
if (row.id.startsWith(
|
|
1763
|
-
ret.push(row.id.substr(
|
|
1407
|
+
if (row.id.startsWith(DocTypePrefixes["CARDRECORD" /* CARDRECORD */])) {
|
|
1408
|
+
ret.push(row.id.substr(DocTypePrefixes["CARDRECORD" /* CARDRECORD */].length));
|
|
1764
1409
|
}
|
|
1765
1410
|
});
|
|
1766
1411
|
return ret;
|
|
@@ -1770,9 +1415,9 @@ Currently logged-in as ${this._username}.`
|
|
|
1770
1415
|
* @returns A promise of the cards that the user has seen in the past.
|
|
1771
1416
|
*/
|
|
1772
1417
|
async getHistory() {
|
|
1773
|
-
const cards = await
|
|
1418
|
+
const cards = await filterAllDocsByPrefix(
|
|
1774
1419
|
this.remoteDB,
|
|
1775
|
-
|
|
1420
|
+
DocTypePrefixes["CARDRECORD" /* CARDRECORD */],
|
|
1776
1421
|
{
|
|
1777
1422
|
include_docs: true,
|
|
1778
1423
|
attachments: false
|
|
@@ -1813,7 +1458,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1813
1458
|
} catch (e) {
|
|
1814
1459
|
const err = e;
|
|
1815
1460
|
if (err.status === 404) {
|
|
1816
|
-
await this.
|
|
1461
|
+
await this.writeDB.put({
|
|
1817
1462
|
_id: _BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS,
|
|
1818
1463
|
registrations: []
|
|
1819
1464
|
});
|
|
@@ -1861,10 +1506,10 @@ Currently logged-in as ${this._username}.`
|
|
|
1861
1506
|
}
|
|
1862
1507
|
}
|
|
1863
1508
|
async scheduleCardReview(review) {
|
|
1864
|
-
return scheduleCardReviewLocal(this.
|
|
1509
|
+
return scheduleCardReviewLocal(this.writeDB, review);
|
|
1865
1510
|
}
|
|
1866
1511
|
async removeScheduledCardReview(reviewId) {
|
|
1867
|
-
return removeScheduledCardReviewLocal(this.
|
|
1512
|
+
return removeScheduledCardReviewLocal(this.writeDB, reviewId);
|
|
1868
1513
|
}
|
|
1869
1514
|
async registerForClassroom(_classId, _registerAs) {
|
|
1870
1515
|
return registerUserForClassroom(this._username, _classId, _registerAs);
|
|
@@ -1894,300 +1539,17 @@ var init_common = __esm({
|
|
|
1894
1539
|
}
|
|
1895
1540
|
});
|
|
1896
1541
|
|
|
1897
|
-
// src/
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
const result = await db.post(payload);
|
|
1902
|
-
const dataShapeId = import_common3.NameSpacer.getDataShapeString({
|
|
1903
|
-
course: codeCourse,
|
|
1904
|
-
dataShape: shape.name
|
|
1905
|
-
});
|
|
1906
|
-
if (result.ok) {
|
|
1907
|
-
try {
|
|
1908
|
-
await createCards(courseID, dataShapeId, result.id, tags, elo, author);
|
|
1909
|
-
} catch (error) {
|
|
1910
|
-
let errorMessage = "Unknown error";
|
|
1911
|
-
if (error instanceof Error) {
|
|
1912
|
-
errorMessage = error.message;
|
|
1913
|
-
} else if (error && typeof error === "object" && "reason" in error) {
|
|
1914
|
-
errorMessage = error.reason;
|
|
1915
|
-
} else if (error && typeof error === "object" && "message" in error) {
|
|
1916
|
-
errorMessage = error.message;
|
|
1917
|
-
} else {
|
|
1918
|
-
errorMessage = String(error);
|
|
1919
|
-
}
|
|
1920
|
-
logger.error(`[addNote55] Failed to create cards for note ${result.id}: ${errorMessage}`);
|
|
1921
|
-
result.cardCreationFailed = true;
|
|
1922
|
-
result.cardCreationError = errorMessage;
|
|
1923
|
-
}
|
|
1924
|
-
} else {
|
|
1925
|
-
logger.error(`[addNote55] Error adding note. Result: ${JSON.stringify(result)}`);
|
|
1926
|
-
}
|
|
1927
|
-
return result;
|
|
1928
|
-
}
|
|
1929
|
-
async function createCards(courseID, datashapeID, noteID, tags, elo = (0, import_common4.blankCourseElo)(), author) {
|
|
1930
|
-
const cfg = await getCredentialledCourseConfig(courseID);
|
|
1931
|
-
const dsDescriptor = import_common3.NameSpacer.getDataShapeDescriptor(datashapeID);
|
|
1932
|
-
let questionViewTypes = [];
|
|
1933
|
-
for (const ds of cfg.dataShapes) {
|
|
1934
|
-
if (ds.name === datashapeID) {
|
|
1935
|
-
questionViewTypes = ds.questionTypes;
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
if (questionViewTypes.length === 0) {
|
|
1939
|
-
const errorMsg = `No questionViewTypes found for datashapeID: ${datashapeID} in course config. Cards cannot be created.`;
|
|
1940
|
-
logger.error(errorMsg);
|
|
1941
|
-
throw new Error(errorMsg);
|
|
1942
|
-
}
|
|
1943
|
-
for (const questionView of questionViewTypes) {
|
|
1944
|
-
await createCard(questionView, courseID, dsDescriptor, noteID, tags, elo, author);
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
async function createCard(questionViewName, courseID, dsDescriptor, noteID, tags, elo = (0, import_common4.blankCourseElo)(), author) {
|
|
1948
|
-
const qDescriptor = import_common3.NameSpacer.getQuestionDescriptor(questionViewName);
|
|
1949
|
-
const cfg = await getCredentialledCourseConfig(courseID);
|
|
1950
|
-
for (const rQ of cfg.questionTypes) {
|
|
1951
|
-
if (rQ.name === questionViewName) {
|
|
1952
|
-
for (const view of rQ.viewList) {
|
|
1953
|
-
await addCard(
|
|
1954
|
-
courseID,
|
|
1955
|
-
dsDescriptor.course,
|
|
1956
|
-
[noteID],
|
|
1957
|
-
import_common3.NameSpacer.getViewString({
|
|
1958
|
-
course: qDescriptor.course,
|
|
1959
|
-
questionType: qDescriptor.questionType,
|
|
1960
|
-
view
|
|
1961
|
-
}),
|
|
1962
|
-
elo,
|
|
1963
|
-
tags,
|
|
1964
|
-
author
|
|
1965
|
-
);
|
|
1966
|
-
}
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1970
|
-
async function addCard(courseID, course, id_displayable_data, id_view, elo, tags, author) {
|
|
1971
|
-
const db = getCourseDB2(courseID);
|
|
1972
|
-
const card = await db.post({
|
|
1973
|
-
course,
|
|
1974
|
-
id_displayable_data,
|
|
1975
|
-
id_view,
|
|
1976
|
-
docType: "CARD" /* CARD */,
|
|
1977
|
-
elo: elo || (0, import_common4.toCourseElo)(990 + Math.round(20 * Math.random())),
|
|
1978
|
-
author
|
|
1979
|
-
});
|
|
1980
|
-
for (const tag of tags) {
|
|
1981
|
-
logger.info(`adding tag: ${tag} to card ${card.id}`);
|
|
1982
|
-
await addTagToCard(courseID, card.id, tag, author, false);
|
|
1983
|
-
}
|
|
1984
|
-
return card;
|
|
1985
|
-
}
|
|
1986
|
-
async function getCredentialledCourseConfig(courseID) {
|
|
1987
|
-
try {
|
|
1988
|
-
const db = getCourseDB2(courseID);
|
|
1989
|
-
const ret = await db.get("CourseConfig");
|
|
1990
|
-
ret.courseID = courseID;
|
|
1991
|
-
logger.info(`Returning course config: ${JSON.stringify(ret)}`);
|
|
1992
|
-
return ret;
|
|
1993
|
-
} catch (e) {
|
|
1994
|
-
logger.error(`Error fetching config for ${courseID}:`, e);
|
|
1995
|
-
throw e;
|
|
1996
|
-
}
|
|
1997
|
-
}
|
|
1998
|
-
async function addTagToCard(courseID, cardID, tagID, author, updateELO = true) {
|
|
1999
|
-
const prefixedTagID = getTagID(tagID);
|
|
2000
|
-
const courseDB = getCourseDB2(courseID);
|
|
2001
|
-
const courseApi = new CourseDB(courseID, async () => {
|
|
2002
|
-
const dummySyncStrategy = {
|
|
2003
|
-
setupRemoteDB: () => null,
|
|
2004
|
-
startSync: () => {
|
|
2005
|
-
},
|
|
2006
|
-
canCreateAccount: () => false,
|
|
2007
|
-
canAuthenticate: () => false,
|
|
2008
|
-
getCurrentUsername: async () => "DummyUser"
|
|
2009
|
-
};
|
|
2010
|
-
return BaseUser.Dummy(dummySyncStrategy);
|
|
2011
|
-
});
|
|
2012
|
-
try {
|
|
2013
|
-
logger.info(`Applying tag ${tagID} to card ${courseID + "-" + cardID}...`);
|
|
2014
|
-
const tag = await courseDB.get(prefixedTagID);
|
|
2015
|
-
if (!tag.taggedCards.includes(cardID)) {
|
|
2016
|
-
tag.taggedCards.push(cardID);
|
|
2017
|
-
if (updateELO) {
|
|
2018
|
-
try {
|
|
2019
|
-
const eloData = await courseApi.getCardEloData([cardID]);
|
|
2020
|
-
const elo = eloData[0];
|
|
2021
|
-
elo.tags[tagID] = {
|
|
2022
|
-
count: 0,
|
|
2023
|
-
score: elo.global.score
|
|
2024
|
-
// todo: or 1000?
|
|
2025
|
-
};
|
|
2026
|
-
await updateCardElo(courseID, cardID, elo);
|
|
2027
|
-
} catch (error) {
|
|
2028
|
-
logger.error("Failed to update ELO data for card:", cardID, error);
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
return courseDB.put(tag);
|
|
2032
|
-
} else throw new AlreadyTaggedErr(`Card ${cardID} is already tagged with ${tagID}`);
|
|
2033
|
-
} catch (e) {
|
|
2034
|
-
if (e instanceof AlreadyTaggedErr) {
|
|
2035
|
-
throw e;
|
|
2036
|
-
}
|
|
2037
|
-
await createTag(courseID, tagID, author);
|
|
2038
|
-
return addTagToCard(courseID, cardID, tagID, author, updateELO);
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
async function updateCardElo(courseID, cardID, elo) {
|
|
2042
|
-
if (elo) {
|
|
2043
|
-
const cDB = getCourseDB2(courseID);
|
|
2044
|
-
const card = await cDB.get(cardID);
|
|
2045
|
-
logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);
|
|
2046
|
-
card.elo = elo;
|
|
2047
|
-
return cDB.put(card);
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2050
|
-
function getTagID(tagName) {
|
|
2051
|
-
const tagPrefix = "TAG" /* TAG */.valueOf() + "-";
|
|
2052
|
-
if (tagName.indexOf(tagPrefix) === 0) {
|
|
2053
|
-
return tagName;
|
|
2054
|
-
} else {
|
|
2055
|
-
return tagPrefix + tagName;
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
|
-
function getCourseDB2(courseID) {
|
|
2059
|
-
const dbName = `coursedb-${courseID}`;
|
|
2060
|
-
return new pouchdb_setup_default(
|
|
2061
|
-
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
2062
|
-
pouchDBincludeCredentialsConfig
|
|
2063
|
-
);
|
|
2064
|
-
}
|
|
2065
|
-
var import_common3, import_common4, import_common5, AlreadyTaggedErr;
|
|
2066
|
-
var init_courseAPI = __esm({
|
|
2067
|
-
"src/impl/couch/courseAPI.ts"() {
|
|
2068
|
-
"use strict";
|
|
2069
|
-
init_pouchdb_setup();
|
|
2070
|
-
init_couch();
|
|
2071
|
-
init_factory();
|
|
2072
|
-
import_common3 = require("@vue-skuilder/common");
|
|
2073
|
-
import_common4 = require("@vue-skuilder/common");
|
|
2074
|
-
init_courseDB();
|
|
2075
|
-
init_types_legacy();
|
|
2076
|
-
import_common5 = require("@vue-skuilder/common");
|
|
2077
|
-
init_common();
|
|
2078
|
-
init_logger();
|
|
2079
|
-
AlreadyTaggedErr = class extends Error {
|
|
2080
|
-
constructor(message) {
|
|
2081
|
-
super(message);
|
|
2082
|
-
this.name = "AlreadyTaggedErr";
|
|
2083
|
-
}
|
|
2084
|
-
};
|
|
2085
|
-
}
|
|
2086
|
-
});
|
|
2087
|
-
|
|
2088
|
-
// src/impl/couch/auth.ts
|
|
2089
|
-
var init_auth = __esm({
|
|
2090
|
-
"src/impl/couch/auth.ts"() {
|
|
2091
|
-
"use strict";
|
|
2092
|
-
init_factory();
|
|
2093
|
-
init_types_legacy();
|
|
2094
|
-
init_logger();
|
|
2095
|
-
}
|
|
2096
|
-
});
|
|
2097
|
-
|
|
2098
|
-
// src/impl/couch/CouchDBSyncStrategy.ts
|
|
2099
|
-
var import_common7;
|
|
2100
|
-
var init_CouchDBSyncStrategy = __esm({
|
|
2101
|
-
"src/impl/couch/CouchDBSyncStrategy.ts"() {
|
|
1542
|
+
// src/factory.ts
|
|
1543
|
+
var ENV;
|
|
1544
|
+
var init_factory = __esm({
|
|
1545
|
+
"src/factory.ts"() {
|
|
2102
1546
|
"use strict";
|
|
2103
|
-
init_factory();
|
|
2104
|
-
init_types_legacy();
|
|
2105
|
-
init_logger();
|
|
2106
|
-
import_common7 = require("@vue-skuilder/common");
|
|
2107
1547
|
init_common();
|
|
2108
|
-
init_pouchdb_setup();
|
|
2109
|
-
init_couch();
|
|
2110
|
-
init_auth();
|
|
2111
|
-
}
|
|
2112
|
-
});
|
|
2113
|
-
|
|
2114
|
-
// src/impl/couch/index.ts
|
|
2115
|
-
function getCourseDB(courseID) {
|
|
2116
|
-
return new pouchdb_setup_default(
|
|
2117
|
-
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "coursedb-" + courseID,
|
|
2118
|
-
pouchDBincludeCredentialsConfig
|
|
2119
|
-
);
|
|
2120
|
-
}
|
|
2121
|
-
function getCourseDocs(courseID, docIDs, options = {}) {
|
|
2122
|
-
return getCourseDB(courseID).allDocs({
|
|
2123
|
-
...options,
|
|
2124
|
-
keys: docIDs
|
|
2125
|
-
});
|
|
2126
|
-
}
|
|
2127
|
-
function getCourseDoc(courseID, docID, options = {}) {
|
|
2128
|
-
return getCourseDB(courseID).get(docID, options);
|
|
2129
|
-
}
|
|
2130
|
-
function filterAllDocsByPrefix(db, prefix, opts) {
|
|
2131
|
-
const options = {
|
|
2132
|
-
startkey: prefix,
|
|
2133
|
-
endkey: prefix + "\uFFF0",
|
|
2134
|
-
include_docs: true
|
|
2135
|
-
};
|
|
2136
|
-
if (opts) {
|
|
2137
|
-
Object.assign(options, opts);
|
|
2138
|
-
}
|
|
2139
|
-
return db.allDocs(options);
|
|
2140
|
-
}
|
|
2141
|
-
function getStartAndEndKeys(key) {
|
|
2142
|
-
return {
|
|
2143
|
-
startkey: key,
|
|
2144
|
-
endkey: key + "\uFFF0"
|
|
2145
|
-
};
|
|
2146
|
-
}
|
|
2147
|
-
var import_moment4, import_process, isBrowser, GUEST_LOCAL_DB, localUserDB, pouchDBincludeCredentialsConfig, REVIEW_PREFIX2, REVIEW_TIME_FORMAT2;
|
|
2148
|
-
var init_couch = __esm({
|
|
2149
|
-
"src/impl/couch/index.ts"() {
|
|
2150
|
-
"use strict";
|
|
2151
|
-
init_factory();
|
|
2152
|
-
init_types_legacy();
|
|
2153
|
-
import_moment4 = __toESM(require("moment"));
|
|
2154
1548
|
init_logger();
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
init_adminDB2();
|
|
2159
|
-
init_classroomDB2();
|
|
2160
|
-
init_courseAPI();
|
|
2161
|
-
init_courseDB();
|
|
2162
|
-
init_CouchDBSyncStrategy();
|
|
2163
|
-
isBrowser = typeof window !== "undefined";
|
|
2164
|
-
if (isBrowser) {
|
|
2165
|
-
window.process = import_process.default;
|
|
2166
|
-
}
|
|
2167
|
-
GUEST_LOCAL_DB = `userdb-${GuestUsername}`;
|
|
2168
|
-
localUserDB = new pouchdb_setup_default(GUEST_LOCAL_DB);
|
|
2169
|
-
pouchDBincludeCredentialsConfig = {
|
|
2170
|
-
fetch(url, opts) {
|
|
2171
|
-
opts.credentials = "include";
|
|
2172
|
-
return pouchdb_setup_default.fetch(url, opts);
|
|
2173
|
-
}
|
|
1549
|
+
ENV = {
|
|
1550
|
+
COUCHDB_SERVER_PROTOCOL: "NOT_SET",
|
|
1551
|
+
COUCHDB_SERVER_URL: "NOT_SET"
|
|
2174
1552
|
};
|
|
2175
|
-
REVIEW_PREFIX2 = "card_review_";
|
|
2176
|
-
REVIEW_TIME_FORMAT2 = "YYYY-MM-DD--kk:mm:ss-SSS";
|
|
2177
|
-
}
|
|
2178
|
-
});
|
|
2179
|
-
|
|
2180
|
-
// src/impl/couch/classroomDB.ts
|
|
2181
|
-
var import_moment5;
|
|
2182
|
-
var init_classroomDB2 = __esm({
|
|
2183
|
-
"src/impl/couch/classroomDB.ts"() {
|
|
2184
|
-
"use strict";
|
|
2185
|
-
init_factory();
|
|
2186
|
-
init_logger();
|
|
2187
|
-
import_moment5 = __toESM(require("moment"));
|
|
2188
|
-
init_pouchdb_setup();
|
|
2189
|
-
init_couch();
|
|
2190
|
-
init_courseDB();
|
|
2191
1553
|
}
|
|
2192
1554
|
});
|
|
2193
1555
|
|
|
@@ -2242,11 +1604,11 @@ var init_user = __esm({
|
|
|
2242
1604
|
});
|
|
2243
1605
|
|
|
2244
1606
|
// src/core/bulkImport/cardProcessor.ts
|
|
2245
|
-
var
|
|
1607
|
+
var import_common10;
|
|
2246
1608
|
var init_cardProcessor = __esm({
|
|
2247
1609
|
"src/core/bulkImport/cardProcessor.ts"() {
|
|
2248
1610
|
"use strict";
|
|
2249
|
-
|
|
1611
|
+
import_common10 = require("@vue-skuilder/common");
|
|
2250
1612
|
init_logger();
|
|
2251
1613
|
}
|
|
2252
1614
|
});
|
|
@@ -2289,11 +1651,11 @@ var init_StaticDataUnpacker = __esm({
|
|
|
2289
1651
|
init_logger();
|
|
2290
1652
|
init_core();
|
|
2291
1653
|
pathUtils = {
|
|
2292
|
-
isAbsolute: (
|
|
2293
|
-
if (/^[a-zA-Z]:[\\/]/.test(
|
|
1654
|
+
isAbsolute: (path2) => {
|
|
1655
|
+
if (/^[a-zA-Z]:[\\/]/.test(path2) || /^\\\\/.test(path2)) {
|
|
2294
1656
|
return true;
|
|
2295
1657
|
}
|
|
2296
|
-
if (
|
|
1658
|
+
if (path2.startsWith("/")) {
|
|
2297
1659
|
return true;
|
|
2298
1660
|
}
|
|
2299
1661
|
return false;
|
|
@@ -2378,18 +1740,21 @@ var init_StaticDataUnpacker = __esm({
|
|
|
2378
1740
|
async getTagsIndex() {
|
|
2379
1741
|
return await this.loadIndex("tags");
|
|
2380
1742
|
}
|
|
1743
|
+
getDocTypeFromId(id) {
|
|
1744
|
+
for (const docTypeKey in DocTypePrefixes) {
|
|
1745
|
+
const prefix = DocTypePrefixes[docTypeKey];
|
|
1746
|
+
if (id.startsWith(`${prefix}-`)) {
|
|
1747
|
+
return docTypeKey;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
return void 0;
|
|
1751
|
+
}
|
|
2381
1752
|
/**
|
|
2382
1753
|
* Find which chunk contains a specific document ID
|
|
2383
1754
|
*/
|
|
2384
1755
|
async findChunkForDocument(docId) {
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
if (docId.startsWith(`${docType}-`)) {
|
|
2388
|
-
expectedDocType = docType;
|
|
2389
|
-
break;
|
|
2390
|
-
}
|
|
2391
|
-
}
|
|
2392
|
-
if (expectedDocType !== void 0) {
|
|
1756
|
+
const expectedDocType = this.getDocTypeFromId(docId);
|
|
1757
|
+
if (expectedDocType) {
|
|
2393
1758
|
const typeChunks = this.manifest.chunks.filter((c) => c.docType === expectedDocType);
|
|
2394
1759
|
for (const chunk of typeChunks) {
|
|
2395
1760
|
if (docId >= chunk.startKey && docId <= chunk.endKey) {
|
|
@@ -2399,21 +1764,8 @@ var init_StaticDataUnpacker = __esm({
|
|
|
2399
1764
|
}
|
|
2400
1765
|
}
|
|
2401
1766
|
}
|
|
2402
|
-
return void 0;
|
|
2403
1767
|
} else {
|
|
2404
|
-
const
|
|
2405
|
-
(c) => c.docType === "DISPLAYABLE_DATA"
|
|
2406
|
-
);
|
|
2407
|
-
for (const chunk of displayableChunks) {
|
|
2408
|
-
if (docId >= chunk.startKey && docId <= chunk.endKey) {
|
|
2409
|
-
const exists = await this.verifyDocumentInChunk(docId, chunk);
|
|
2410
|
-
if (exists) {
|
|
2411
|
-
return chunk;
|
|
2412
|
-
}
|
|
2413
|
-
}
|
|
2414
|
-
}
|
|
2415
|
-
const cardChunks = this.manifest.chunks.filter((c) => c.docType === "CARD");
|
|
2416
|
-
for (const chunk of cardChunks) {
|
|
1768
|
+
for (const chunk of this.manifest.chunks) {
|
|
2417
1769
|
if (docId >= chunk.startKey && docId <= chunk.endKey) {
|
|
2418
1770
|
const exists = await this.verifyDocumentInChunk(docId, chunk);
|
|
2419
1771
|
if (exists) {
|
|
@@ -2434,6 +1786,7 @@ var init_StaticDataUnpacker = __esm({
|
|
|
2434
1786
|
}
|
|
2435
1787
|
return void 0;
|
|
2436
1788
|
}
|
|
1789
|
+
return void 0;
|
|
2437
1790
|
}
|
|
2438
1791
|
/**
|
|
2439
1792
|
* Verify that a document actually exists in a specific chunk by loading and checking it
|
|
@@ -2670,13 +2023,14 @@ var init_StaticDataUnpacker = __esm({
|
|
|
2670
2023
|
});
|
|
2671
2024
|
|
|
2672
2025
|
// src/impl/static/courseDB.ts
|
|
2673
|
-
var
|
|
2026
|
+
var import_common11, StaticCourseDB;
|
|
2674
2027
|
var init_courseDB3 = __esm({
|
|
2675
2028
|
"src/impl/static/courseDB.ts"() {
|
|
2676
2029
|
"use strict";
|
|
2677
|
-
|
|
2030
|
+
import_common11 = require("@vue-skuilder/common");
|
|
2678
2031
|
init_types_legacy();
|
|
2679
2032
|
init_navigators();
|
|
2033
|
+
init_logger();
|
|
2680
2034
|
StaticCourseDB = class {
|
|
2681
2035
|
constructor(courseId, unpacker, userDB, manifest) {
|
|
2682
2036
|
this.courseId = courseId;
|
|
@@ -2698,10 +2052,11 @@ var init_courseDB3 = __esm({
|
|
|
2698
2052
|
throw new Error("Cannot update course config in static mode");
|
|
2699
2053
|
}
|
|
2700
2054
|
async getCourseInfo() {
|
|
2055
|
+
const cardCount = this.manifest.chunks.filter((chunk) => chunk.docType === "CARD" /* CARD */).reduce((total, chunk) => total + chunk.documentCount, 0);
|
|
2701
2056
|
return {
|
|
2702
|
-
cardCount
|
|
2703
|
-
// Would come from manifest
|
|
2057
|
+
cardCount,
|
|
2704
2058
|
registeredUsers: 0
|
|
2059
|
+
// Always 0 in static mode
|
|
2705
2060
|
};
|
|
2706
2061
|
}
|
|
2707
2062
|
async getCourseDoc(id, _options) {
|
|
@@ -2790,12 +2145,56 @@ var init_courseDB3 = __esm({
|
|
|
2790
2145
|
courseID: this.courseId
|
|
2791
2146
|
}));
|
|
2792
2147
|
}
|
|
2793
|
-
async getAppliedTags(
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
rows
|
|
2798
|
-
|
|
2148
|
+
async getAppliedTags(cardId) {
|
|
2149
|
+
try {
|
|
2150
|
+
const tagsIndex = await this.unpacker.getTagsIndex();
|
|
2151
|
+
const cardTags = tagsIndex.byCard[cardId] || [];
|
|
2152
|
+
const rows = await Promise.all(
|
|
2153
|
+
cardTags.map(async (tagName) => {
|
|
2154
|
+
const tagId = `${"TAG" /* TAG */}-${tagName}`;
|
|
2155
|
+
try {
|
|
2156
|
+
const tagDoc = await this.unpacker.getDocument(tagId);
|
|
2157
|
+
return {
|
|
2158
|
+
id: tagId,
|
|
2159
|
+
key: cardId,
|
|
2160
|
+
value: {
|
|
2161
|
+
name: tagDoc.name,
|
|
2162
|
+
snippet: tagDoc.snippet,
|
|
2163
|
+
count: tagDoc.taggedCards?.length || 0
|
|
2164
|
+
}
|
|
2165
|
+
};
|
|
2166
|
+
} catch (error) {
|
|
2167
|
+
if (error && error.status === 404) {
|
|
2168
|
+
logger.warn(`Tag document not found for ${tagName}, creating stub`);
|
|
2169
|
+
} else {
|
|
2170
|
+
logger.error(`Error getting tag document for ${tagName}:`, error);
|
|
2171
|
+
throw error;
|
|
2172
|
+
}
|
|
2173
|
+
return {
|
|
2174
|
+
id: tagId,
|
|
2175
|
+
key: cardId,
|
|
2176
|
+
value: {
|
|
2177
|
+
name: tagName,
|
|
2178
|
+
snippet: `Tag: ${tagName}`,
|
|
2179
|
+
count: tagsIndex.byTag[tagName]?.length || 0
|
|
2180
|
+
}
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
})
|
|
2184
|
+
);
|
|
2185
|
+
return {
|
|
2186
|
+
total_rows: rows.length,
|
|
2187
|
+
offset: 0,
|
|
2188
|
+
rows
|
|
2189
|
+
};
|
|
2190
|
+
} catch (error) {
|
|
2191
|
+
logger.error(`Error getting applied tags for card ${cardId}:`, error);
|
|
2192
|
+
return {
|
|
2193
|
+
total_rows: 0,
|
|
2194
|
+
offset: 0,
|
|
2195
|
+
rows: []
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2799
2198
|
}
|
|
2800
2199
|
async addTagToCard(_cardId, _tagId) {
|
|
2801
2200
|
throw new Error("Cannot modify tags in static mode");
|
|
@@ -2813,15 +2212,73 @@ var init_courseDB3 = __esm({
|
|
|
2813
2212
|
throw new Error("Cannot update tags in static mode");
|
|
2814
2213
|
}
|
|
2815
2214
|
async getCourseTagStubs() {
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2215
|
+
try {
|
|
2216
|
+
const tagsIndex = await this.unpacker.getTagsIndex();
|
|
2217
|
+
if (!tagsIndex || !tagsIndex.byTag) {
|
|
2218
|
+
logger.warn("Tags index not found or empty");
|
|
2219
|
+
return {
|
|
2220
|
+
total_rows: 0,
|
|
2221
|
+
offset: 0,
|
|
2222
|
+
rows: []
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
const tagNames = Object.keys(tagsIndex.byTag);
|
|
2226
|
+
const rows = await Promise.all(
|
|
2227
|
+
tagNames.map(async (tagName) => {
|
|
2228
|
+
const cardIds = tagsIndex.byTag[tagName] || [];
|
|
2229
|
+
const tagId = `${"TAG" /* TAG */}-${tagName}`;
|
|
2230
|
+
try {
|
|
2231
|
+
const tagDoc = await this.unpacker.getDocument(tagId);
|
|
2232
|
+
return {
|
|
2233
|
+
id: tagId,
|
|
2234
|
+
key: tagId,
|
|
2235
|
+
value: { rev: "1-static" },
|
|
2236
|
+
doc: tagDoc
|
|
2237
|
+
};
|
|
2238
|
+
} catch (error) {
|
|
2239
|
+
if (error && error.status === 404) {
|
|
2240
|
+
logger.warn(`Tag document not found for ${tagName}, creating stub`);
|
|
2241
|
+
const stubDoc = {
|
|
2242
|
+
_id: tagId,
|
|
2243
|
+
_rev: "1-static",
|
|
2244
|
+
course: this.courseId,
|
|
2245
|
+
docType: "TAG" /* TAG */,
|
|
2246
|
+
name: tagName,
|
|
2247
|
+
snippet: `Tag: ${tagName}`,
|
|
2248
|
+
wiki: "",
|
|
2249
|
+
taggedCards: cardIds,
|
|
2250
|
+
author: "system"
|
|
2251
|
+
};
|
|
2252
|
+
return {
|
|
2253
|
+
id: tagId,
|
|
2254
|
+
key: tagId,
|
|
2255
|
+
value: { rev: "1-static" },
|
|
2256
|
+
doc: stubDoc
|
|
2257
|
+
};
|
|
2258
|
+
} else {
|
|
2259
|
+
logger.error(`Error getting tag document for ${tagName}:`, error);
|
|
2260
|
+
throw error;
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
})
|
|
2264
|
+
);
|
|
2265
|
+
return {
|
|
2266
|
+
total_rows: rows.length,
|
|
2267
|
+
offset: 0,
|
|
2268
|
+
rows
|
|
2269
|
+
};
|
|
2270
|
+
} catch (error) {
|
|
2271
|
+
logger.error("Failed to get course tag stubs:", error);
|
|
2272
|
+
return {
|
|
2273
|
+
total_rows: 0,
|
|
2274
|
+
offset: 0,
|
|
2275
|
+
rows: []
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2821
2278
|
}
|
|
2822
2279
|
async addNote(_codeCourse, _shape, _data, _author, _tags, _uploads, _elo) {
|
|
2823
2280
|
return {
|
|
2824
|
-
status:
|
|
2281
|
+
status: import_common11.Status.error,
|
|
2825
2282
|
message: "Cannot add notes in static mode"
|
|
2826
2283
|
};
|
|
2827
2284
|
}
|
|
@@ -2923,6 +2380,9 @@ var init_NoOpSyncStrategy = __esm({
|
|
|
2923
2380
|
setupRemoteDB(username) {
|
|
2924
2381
|
return getLocalUserDB(username);
|
|
2925
2382
|
}
|
|
2383
|
+
getWriteDB(username) {
|
|
2384
|
+
return getLocalUserDB(username);
|
|
2385
|
+
}
|
|
2926
2386
|
startSync(_localDB, _remoteDB) {
|
|
2927
2387
|
}
|
|
2928
2388
|
stopSync() {
|