@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
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
-
var __glob = (map) => (
|
|
4
|
-
var fn = map[
|
|
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: " +
|
|
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;
|
|
@@ -72,19 +72,47 @@ var init_classroomDB = __esm({
|
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
// src/
|
|
76
|
-
var
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
// src/impl/common/SyncStrategy.ts
|
|
76
|
+
var init_SyncStrategy = __esm({
|
|
77
|
+
"src/impl/common/SyncStrategy.ts"() {
|
|
78
|
+
"use strict";
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// src/core/types/types-legacy.ts
|
|
83
|
+
var GuestUsername, DocTypePrefixes;
|
|
84
|
+
var init_types_legacy = __esm({
|
|
85
|
+
"src/core/types/types-legacy.ts"() {
|
|
79
86
|
"use strict";
|
|
80
87
|
init_logger();
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
88
|
+
GuestUsername = "Guest";
|
|
89
|
+
DocTypePrefixes = {
|
|
90
|
+
["CARD" /* CARD */]: "c",
|
|
91
|
+
["DISPLAYABLE_DATA" /* DISPLAYABLE_DATA */]: "dd",
|
|
92
|
+
["TAG" /* TAG */]: "TAG",
|
|
93
|
+
["CARDRECORD" /* CARDRECORD */]: "cardH",
|
|
94
|
+
["SCHEDULED_CARD" /* SCHEDULED_CARD */]: "card_review_",
|
|
95
|
+
// Add other doctypes here as they get prefixed IDs
|
|
96
|
+
["DATASHAPE" /* DATASHAPE */]: "DATASHAPE",
|
|
97
|
+
["QUESTION" /* QUESTIONTYPE */]: "QUESTION",
|
|
98
|
+
["VIEW" /* VIEW */]: "VIEW",
|
|
99
|
+
["PEDAGOGY" /* PEDAGOGY */]: "PEDAGOGY",
|
|
100
|
+
["NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */]: "NAVIGATION_STRATEGY"
|
|
84
101
|
};
|
|
85
102
|
}
|
|
86
103
|
});
|
|
87
104
|
|
|
105
|
+
// src/core/util/index.ts
|
|
106
|
+
function getCardHistoryID(courseID, cardID) {
|
|
107
|
+
return `${DocTypePrefixes["CARDRECORD" /* CARDRECORD */]}-${courseID}-${cardID}`;
|
|
108
|
+
}
|
|
109
|
+
var init_util = __esm({
|
|
110
|
+
"src/core/util/index.ts"() {
|
|
111
|
+
"use strict";
|
|
112
|
+
init_types_legacy();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
88
116
|
// src/impl/couch/pouchdb-setup.ts
|
|
89
117
|
import PouchDB from "pouchdb";
|
|
90
118
|
import PouchDBFind from "pouchdb-find";
|
|
@@ -104,51 +132,93 @@ var init_pouchdb_setup = __esm({
|
|
|
104
132
|
}
|
|
105
133
|
});
|
|
106
134
|
|
|
107
|
-
// src/
|
|
108
|
-
var
|
|
109
|
-
|
|
110
|
-
"src/core/types/types-legacy.ts"() {
|
|
135
|
+
// src/util/tuiLogger.ts
|
|
136
|
+
var init_tuiLogger = __esm({
|
|
137
|
+
"src/util/tuiLogger.ts"() {
|
|
111
138
|
"use strict";
|
|
112
|
-
|
|
113
|
-
GuestUsername = "Guest";
|
|
114
|
-
DocType = /* @__PURE__ */ ((DocType2) => {
|
|
115
|
-
DocType2["DISPLAYABLE_DATA"] = "DISPLAYABLE_DATA";
|
|
116
|
-
DocType2["CARD"] = "CARD";
|
|
117
|
-
DocType2["DATASHAPE"] = "DATASHAPE";
|
|
118
|
-
DocType2["QUESTIONTYPE"] = "QUESTION";
|
|
119
|
-
DocType2["VIEW"] = "VIEW";
|
|
120
|
-
DocType2["PEDAGOGY"] = "PEDAGOGY";
|
|
121
|
-
DocType2["CARDRECORD"] = "CARDRECORD";
|
|
122
|
-
DocType2["SCHEDULED_CARD"] = "SCHEDULED_CARD";
|
|
123
|
-
DocType2["TAG"] = "TAG";
|
|
124
|
-
DocType2["NAVIGATION_STRATEGY"] = "NAVIGATION_STRATEGY";
|
|
125
|
-
return DocType2;
|
|
126
|
-
})(DocType || {});
|
|
127
|
-
cardHistoryPrefix = "cardH";
|
|
139
|
+
init_dataDirectory();
|
|
128
140
|
}
|
|
129
141
|
});
|
|
130
142
|
|
|
131
|
-
// src/
|
|
132
|
-
|
|
133
|
-
|
|
143
|
+
// src/util/dataDirectory.ts
|
|
144
|
+
import * as path from "path";
|
|
145
|
+
import * as os from "os";
|
|
146
|
+
function getAppDataDirectory() {
|
|
147
|
+
return path.join(os.homedir(), ".tuilder");
|
|
148
|
+
}
|
|
149
|
+
function getDbPath(dbName) {
|
|
150
|
+
return path.join(getAppDataDirectory(), dbName);
|
|
151
|
+
}
|
|
152
|
+
var init_dataDirectory = __esm({
|
|
153
|
+
"src/util/dataDirectory.ts"() {
|
|
134
154
|
"use strict";
|
|
135
|
-
|
|
136
|
-
init_factory();
|
|
137
|
-
init_logger();
|
|
138
|
-
logger.debug(`COURSELOOKUP FILE RUNNING`);
|
|
155
|
+
init_tuiLogger();
|
|
139
156
|
}
|
|
140
157
|
});
|
|
141
158
|
|
|
142
|
-
// src/impl/
|
|
143
|
-
|
|
144
|
-
|
|
159
|
+
// src/impl/common/userDBHelpers.ts
|
|
160
|
+
import moment from "moment";
|
|
161
|
+
function filterAllDocsByPrefix(db, prefix, opts) {
|
|
162
|
+
const options = {
|
|
163
|
+
startkey: prefix,
|
|
164
|
+
endkey: prefix + "\uFFF0",
|
|
165
|
+
include_docs: true
|
|
166
|
+
};
|
|
167
|
+
if (opts) {
|
|
168
|
+
Object.assign(options, opts);
|
|
169
|
+
}
|
|
170
|
+
return db.allDocs(options);
|
|
171
|
+
}
|
|
172
|
+
function getStartAndEndKeys(key) {
|
|
173
|
+
return {
|
|
174
|
+
startkey: key,
|
|
175
|
+
endkey: key + "\uFFF0"
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function getLocalUserDB(username) {
|
|
179
|
+
const dbName = `userdb-${username}`;
|
|
180
|
+
if (typeof window === "undefined") {
|
|
181
|
+
return new pouchdb_setup_default(getDbPath(dbName), {});
|
|
182
|
+
} else {
|
|
183
|
+
return new pouchdb_setup_default(dbName, {});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function scheduleCardReviewLocal(userDB, review) {
|
|
187
|
+
const now = moment.utc();
|
|
188
|
+
logger.info(`Scheduling for review in: ${review.time.diff(now, "h") / 24} days`);
|
|
189
|
+
void userDB.put({
|
|
190
|
+
_id: DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */] + review.time.format(REVIEW_TIME_FORMAT),
|
|
191
|
+
cardId: review.card_id,
|
|
192
|
+
reviewTime: review.time.toISOString(),
|
|
193
|
+
courseId: review.course_id,
|
|
194
|
+
scheduledAt: now.toISOString(),
|
|
195
|
+
scheduledFor: review.scheduledFor,
|
|
196
|
+
schedulingAgentId: review.schedulingAgentId
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
async function removeScheduledCardReviewLocal(userDB, reviewDocID) {
|
|
200
|
+
const reviewDoc = await userDB.get(reviewDocID);
|
|
201
|
+
userDB.remove(reviewDoc).then((res) => {
|
|
202
|
+
if (res.ok) {
|
|
203
|
+
log(`Removed Review Doc: ${reviewDocID}`);
|
|
204
|
+
}
|
|
205
|
+
}).catch((err) => {
|
|
206
|
+
log(`Failed to remove Review Doc: ${reviewDocID},
|
|
207
|
+
${JSON.stringify(err)}`);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
var REVIEW_TIME_FORMAT, log;
|
|
211
|
+
var init_userDBHelpers = __esm({
|
|
212
|
+
"src/impl/common/userDBHelpers.ts"() {
|
|
145
213
|
"use strict";
|
|
146
|
-
|
|
147
|
-
init_factory();
|
|
148
|
-
init_couch();
|
|
149
|
-
init_classroomDB2();
|
|
150
|
-
init_courseLookupDB();
|
|
214
|
+
init_core();
|
|
151
215
|
init_logger();
|
|
216
|
+
init_pouchdb_setup();
|
|
217
|
+
init_dataDirectory();
|
|
218
|
+
REVIEW_TIME_FORMAT = "YYYY-MM-DD--kk:mm:ss-SSS";
|
|
219
|
+
log = (s) => {
|
|
220
|
+
logger.info(s);
|
|
221
|
+
};
|
|
152
222
|
}
|
|
153
223
|
});
|
|
154
224
|
|
|
@@ -179,7 +249,10 @@ var init_updateQueue = __esm({
|
|
|
179
249
|
_className = "UpdateQueue";
|
|
180
250
|
pendingUpdates = {};
|
|
181
251
|
inprogressUpdates = {};
|
|
182
|
-
|
|
252
|
+
readDB;
|
|
253
|
+
// Database for read operations
|
|
254
|
+
writeDB;
|
|
255
|
+
// Database for write operations (local-first)
|
|
183
256
|
update(id, update) {
|
|
184
257
|
logger.debug(`Update requested on doc: ${id}`);
|
|
185
258
|
if (this.pendingUpdates[id]) {
|
|
@@ -189,24 +262,25 @@ var init_updateQueue = __esm({
|
|
|
189
262
|
}
|
|
190
263
|
return this.applyUpdates(id);
|
|
191
264
|
}
|
|
192
|
-
constructor(
|
|
265
|
+
constructor(readDB, writeDB) {
|
|
193
266
|
super();
|
|
194
|
-
this.
|
|
267
|
+
this.readDB = readDB;
|
|
268
|
+
this.writeDB = writeDB || readDB;
|
|
195
269
|
logger.debug(`UpdateQ initialized...`);
|
|
196
|
-
void this.
|
|
270
|
+
void this.readDB.info().then((i) => {
|
|
197
271
|
logger.debug(`db info: ${JSON.stringify(i)}`);
|
|
198
272
|
});
|
|
199
273
|
}
|
|
200
274
|
async applyUpdates(id) {
|
|
201
275
|
logger.debug(`Applying updates on doc: ${id}`);
|
|
202
276
|
if (this.inprogressUpdates[id]) {
|
|
203
|
-
await this.
|
|
277
|
+
await this.readDB.info();
|
|
204
278
|
return this.applyUpdates(id);
|
|
205
279
|
} else {
|
|
206
280
|
if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {
|
|
207
281
|
this.inprogressUpdates[id] = true;
|
|
208
282
|
try {
|
|
209
|
-
let doc = await this.
|
|
283
|
+
let doc = await this.readDB.get(id);
|
|
210
284
|
logger.debug(`Retrieved doc: ${id}`);
|
|
211
285
|
while (this.pendingUpdates[id].length !== 0) {
|
|
212
286
|
const update = this.pendingUpdates[id].splice(0, 1)[0];
|
|
@@ -219,7 +293,7 @@ var init_updateQueue = __esm({
|
|
|
219
293
|
};
|
|
220
294
|
}
|
|
221
295
|
}
|
|
222
|
-
await this.
|
|
296
|
+
await this.writeDB.put(doc);
|
|
223
297
|
logger.debug(`Put doc: ${id}`);
|
|
224
298
|
if (this.pendingUpdates[id].length === 0) {
|
|
225
299
|
this.inprogressUpdates[id] = false;
|
|
@@ -245,22 +319,112 @@ var init_updateQueue = __esm({
|
|
|
245
319
|
}
|
|
246
320
|
});
|
|
247
321
|
|
|
322
|
+
// src/impl/couch/user-course-relDB.ts
|
|
323
|
+
import moment2 from "moment";
|
|
324
|
+
var UsrCrsData;
|
|
325
|
+
var init_user_course_relDB = __esm({
|
|
326
|
+
"src/impl/couch/user-course-relDB.ts"() {
|
|
327
|
+
"use strict";
|
|
328
|
+
init_logger();
|
|
329
|
+
UsrCrsData = class {
|
|
330
|
+
user;
|
|
331
|
+
_courseId;
|
|
332
|
+
constructor(user, courseId) {
|
|
333
|
+
this.user = user;
|
|
334
|
+
this._courseId = courseId;
|
|
335
|
+
}
|
|
336
|
+
async getReviewsForcast(daysCount) {
|
|
337
|
+
const time = moment2.utc().add(daysCount, "days");
|
|
338
|
+
return this.getReviewstoDate(time);
|
|
339
|
+
}
|
|
340
|
+
async getPendingReviews() {
|
|
341
|
+
const now = moment2.utc();
|
|
342
|
+
return this.getReviewstoDate(now);
|
|
343
|
+
}
|
|
344
|
+
async getScheduledReviewCount() {
|
|
345
|
+
return (await this.getPendingReviews()).length;
|
|
346
|
+
}
|
|
347
|
+
async getCourseSettings() {
|
|
348
|
+
const regDoc = await this.user.getCourseRegistrationsDoc();
|
|
349
|
+
const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);
|
|
350
|
+
if (crsDoc && crsDoc.settings) {
|
|
351
|
+
return crsDoc.settings;
|
|
352
|
+
} else {
|
|
353
|
+
logger.warn(`no settings found during lookup on course ${this._courseId}`);
|
|
354
|
+
return {};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
updateCourseSettings(updates) {
|
|
358
|
+
if ("updateCourseSettings" in this.user) {
|
|
359
|
+
void this.user.updateCourseSettings(this._courseId, updates);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
async getReviewstoDate(targetDate) {
|
|
363
|
+
const allReviews = await this.user.getPendingReviews(this._courseId);
|
|
364
|
+
logger.debug(
|
|
365
|
+
`Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`
|
|
366
|
+
);
|
|
367
|
+
return allReviews.filter((review) => {
|
|
368
|
+
const reviewTime = moment2.utc(review.reviewTime);
|
|
369
|
+
return targetDate.isAfter(reviewTime);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
248
376
|
// src/impl/couch/clientCache.ts
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
377
|
+
var init_clientCache = __esm({
|
|
378
|
+
"src/impl/couch/clientCache.ts"() {
|
|
379
|
+
"use strict";
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// src/impl/couch/courseAPI.ts
|
|
384
|
+
import { NameSpacer } from "@vue-skuilder/common";
|
|
385
|
+
import { blankCourseElo, toCourseElo } from "@vue-skuilder/common";
|
|
386
|
+
import { prepareNote55 } from "@vue-skuilder/common";
|
|
387
|
+
import { v4 as uuidv4 } from "uuid";
|
|
388
|
+
async function getCredentialledCourseConfig(courseID) {
|
|
389
|
+
try {
|
|
390
|
+
const db = getCourseDB(courseID);
|
|
391
|
+
const ret = await db.get("CourseConfig");
|
|
392
|
+
ret.courseID = courseID;
|
|
393
|
+
logger.info(`Returning course config: ${JSON.stringify(ret)}`);
|
|
394
|
+
return ret;
|
|
395
|
+
} catch (e) {
|
|
396
|
+
logger.error(`Error fetching config for ${courseID}:`, e);
|
|
397
|
+
throw e;
|
|
252
398
|
}
|
|
253
|
-
CLIENT_CACHE[k] = f ? await f(k) : await GET_ITEM(k);
|
|
254
|
-
return GET_CACHED(k);
|
|
255
399
|
}
|
|
256
|
-
|
|
257
|
-
|
|
400
|
+
function getCourseDB(courseID) {
|
|
401
|
+
const dbName = `coursedb-${courseID}`;
|
|
402
|
+
return new pouchdb_setup_default(
|
|
403
|
+
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
404
|
+
pouchDBincludeCredentialsConfig
|
|
405
|
+
);
|
|
258
406
|
}
|
|
259
|
-
var
|
|
260
|
-
|
|
261
|
-
|
|
407
|
+
var init_courseAPI = __esm({
|
|
408
|
+
"src/impl/couch/courseAPI.ts"() {
|
|
409
|
+
"use strict";
|
|
410
|
+
init_pouchdb_setup();
|
|
411
|
+
init_couch();
|
|
412
|
+
init_factory();
|
|
413
|
+
init_courseDB();
|
|
414
|
+
init_types_legacy();
|
|
415
|
+
init_common();
|
|
416
|
+
init_logger();
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// src/impl/couch/courseLookupDB.ts
|
|
421
|
+
var init_courseLookupDB = __esm({
|
|
422
|
+
"src/impl/couch/courseLookupDB.ts"() {
|
|
262
423
|
"use strict";
|
|
263
|
-
|
|
424
|
+
init_pouchdb_setup();
|
|
425
|
+
init_factory();
|
|
426
|
+
init_logger();
|
|
427
|
+
logger.debug(`COURSELOOKUP FILE RUNNING`);
|
|
264
428
|
}
|
|
265
429
|
});
|
|
266
430
|
|
|
@@ -387,85 +551,9 @@ var init_navigators = __esm({
|
|
|
387
551
|
import {
|
|
388
552
|
EloToNumber,
|
|
389
553
|
Status,
|
|
390
|
-
blankCourseElo,
|
|
391
|
-
toCourseElo
|
|
554
|
+
blankCourseElo as blankCourseElo2,
|
|
555
|
+
toCourseElo as toCourseElo2
|
|
392
556
|
} from "@vue-skuilder/common";
|
|
393
|
-
function randIntWeightedTowardZero(n) {
|
|
394
|
-
return Math.floor(Math.random() * Math.random() * Math.random() * n);
|
|
395
|
-
}
|
|
396
|
-
async function getCourseTagStubs(courseID) {
|
|
397
|
-
logger.debug(`Getting tag stubs for course: ${courseID}`);
|
|
398
|
-
const stubs = await filterAllDocsByPrefix(
|
|
399
|
-
getCourseDB(courseID),
|
|
400
|
-
"TAG" /* TAG */.valueOf() + "-"
|
|
401
|
-
);
|
|
402
|
-
stubs.rows.forEach((row) => {
|
|
403
|
-
logger.debug(` Tag stub for doc: ${row.id}`);
|
|
404
|
-
});
|
|
405
|
-
return stubs;
|
|
406
|
-
}
|
|
407
|
-
async function createTag(courseID, tagName, author) {
|
|
408
|
-
logger.debug(`Creating tag: ${tagName}...`);
|
|
409
|
-
const tagID = getTagID(tagName);
|
|
410
|
-
const courseDB = getCourseDB(courseID);
|
|
411
|
-
const resp = await courseDB.put({
|
|
412
|
-
course: courseID,
|
|
413
|
-
docType: "TAG" /* TAG */,
|
|
414
|
-
name: tagName,
|
|
415
|
-
snippet: "",
|
|
416
|
-
taggedCards: [],
|
|
417
|
-
wiki: "",
|
|
418
|
-
author,
|
|
419
|
-
_id: tagID
|
|
420
|
-
});
|
|
421
|
-
return resp;
|
|
422
|
-
}
|
|
423
|
-
async function updateTag(tag) {
|
|
424
|
-
const prior = await getTag(tag.course, tag.name);
|
|
425
|
-
return await getCourseDB(tag.course).put({
|
|
426
|
-
...tag,
|
|
427
|
-
_rev: prior._rev
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
async function getTag(courseID, tagName) {
|
|
431
|
-
const tagID = getTagID(tagName);
|
|
432
|
-
const courseDB = getCourseDB(courseID);
|
|
433
|
-
return courseDB.get(tagID);
|
|
434
|
-
}
|
|
435
|
-
async function removeTagFromCard(courseID, cardID, tagID) {
|
|
436
|
-
tagID = getTagID(tagID);
|
|
437
|
-
const courseDB = getCourseDB(courseID);
|
|
438
|
-
const tag = await courseDB.get(tagID);
|
|
439
|
-
tag.taggedCards = tag.taggedCards.filter((taggedID) => {
|
|
440
|
-
return cardID !== taggedID;
|
|
441
|
-
});
|
|
442
|
-
return courseDB.put(tag);
|
|
443
|
-
}
|
|
444
|
-
async function getAppliedTags(id_course, id_card) {
|
|
445
|
-
const db = getCourseDB(id_course);
|
|
446
|
-
const result = await db.query("getTags", {
|
|
447
|
-
startkey: id_card,
|
|
448
|
-
endkey: id_card
|
|
449
|
-
// include_docs: true
|
|
450
|
-
});
|
|
451
|
-
return result;
|
|
452
|
-
}
|
|
453
|
-
async function updateCredentialledCourseConfig(courseID, config) {
|
|
454
|
-
logger.debug(`Updating course config:
|
|
455
|
-
|
|
456
|
-
${JSON.stringify(config)}
|
|
457
|
-
`);
|
|
458
|
-
const db = getCourseDB(courseID);
|
|
459
|
-
const old = await getCredentialledCourseConfig(courseID);
|
|
460
|
-
return await db.put({
|
|
461
|
-
...config,
|
|
462
|
-
_rev: old._rev
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
function isSuccessRow(row) {
|
|
466
|
-
return "doc" in row && row.doc !== null && row.doc !== void 0;
|
|
467
|
-
}
|
|
468
|
-
var CourseDB;
|
|
469
557
|
var init_courseDB = __esm({
|
|
470
558
|
"src/impl/couch/courseDB.ts"() {
|
|
471
559
|
"use strict";
|
|
@@ -477,568 +565,95 @@ var init_courseDB = __esm({
|
|
|
477
565
|
init_courseAPI();
|
|
478
566
|
init_courseLookupDB();
|
|
479
567
|
init_navigators();
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
// log(`CourseLog: ${this.id}\n ${msg}`);
|
|
483
|
-
// }
|
|
484
|
-
db;
|
|
485
|
-
id;
|
|
486
|
-
_getCurrentUser;
|
|
487
|
-
updateQueue;
|
|
488
|
-
constructor(id, userLookup) {
|
|
489
|
-
this.id = id;
|
|
490
|
-
this.db = getCourseDB(this.id);
|
|
491
|
-
this._getCurrentUser = userLookup;
|
|
492
|
-
this.updateQueue = new UpdateQueue(this.db);
|
|
493
|
-
}
|
|
494
|
-
getCourseID() {
|
|
495
|
-
return this.id;
|
|
496
|
-
}
|
|
497
|
-
async getCourseInfo() {
|
|
498
|
-
const cardCount = (await this.db.find({
|
|
499
|
-
selector: {
|
|
500
|
-
docType: "CARD" /* CARD */
|
|
501
|
-
},
|
|
502
|
-
limit: 1e3
|
|
503
|
-
})).docs.length;
|
|
504
|
-
return {
|
|
505
|
-
cardCount,
|
|
506
|
-
registeredUsers: 0
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
async getInexperiencedCards(limit = 2) {
|
|
510
|
-
return (await this.db.query("cardsByInexperience", {
|
|
511
|
-
limit
|
|
512
|
-
})).rows.map((r) => {
|
|
513
|
-
const ret = {
|
|
514
|
-
courseId: this.id,
|
|
515
|
-
cardId: r.id,
|
|
516
|
-
count: r.key,
|
|
517
|
-
elo: r.value
|
|
518
|
-
};
|
|
519
|
-
return ret;
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
async getCardsByEloLimits(options = {
|
|
523
|
-
low: 0,
|
|
524
|
-
high: Number.MIN_SAFE_INTEGER,
|
|
525
|
-
limit: 25,
|
|
526
|
-
page: 0
|
|
527
|
-
}) {
|
|
528
|
-
return (await this.db.query("elo", {
|
|
529
|
-
startkey: options.low,
|
|
530
|
-
endkey: options.high,
|
|
531
|
-
limit: options.limit,
|
|
532
|
-
skip: options.limit * options.page
|
|
533
|
-
})).rows.map((r) => {
|
|
534
|
-
return `${this.id}-${r.id}-${r.key}`;
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
async getCardEloData(id) {
|
|
538
|
-
const docs = await this.db.allDocs({
|
|
539
|
-
keys: id,
|
|
540
|
-
include_docs: true
|
|
541
|
-
});
|
|
542
|
-
const ret = [];
|
|
543
|
-
docs.rows.forEach((r) => {
|
|
544
|
-
if (isSuccessRow(r)) {
|
|
545
|
-
if (r.doc && r.doc.elo) {
|
|
546
|
-
ret.push(toCourseElo(r.doc.elo));
|
|
547
|
-
} else {
|
|
548
|
-
logger.warn("no elo data for card: " + r.id);
|
|
549
|
-
ret.push(blankCourseElo());
|
|
550
|
-
}
|
|
551
|
-
} else {
|
|
552
|
-
logger.warn("no elo data for card: " + JSON.stringify(r));
|
|
553
|
-
ret.push(blankCourseElo());
|
|
554
|
-
}
|
|
555
|
-
});
|
|
556
|
-
return ret;
|
|
557
|
-
}
|
|
558
|
-
/**
|
|
559
|
-
* Returns the lowest and highest `global` ELO ratings in the course
|
|
560
|
-
*/
|
|
561
|
-
async getELOBounds() {
|
|
562
|
-
const [low, high] = await Promise.all([
|
|
563
|
-
(await this.db.query("elo", {
|
|
564
|
-
startkey: 0,
|
|
565
|
-
limit: 1,
|
|
566
|
-
include_docs: false
|
|
567
|
-
})).rows[0].key,
|
|
568
|
-
(await this.db.query("elo", {
|
|
569
|
-
limit: 1,
|
|
570
|
-
descending: true,
|
|
571
|
-
startkey: 1e5
|
|
572
|
-
})).rows[0].key
|
|
573
|
-
]);
|
|
574
|
-
return {
|
|
575
|
-
low,
|
|
576
|
-
high
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
async removeCard(id) {
|
|
580
|
-
const doc = await this.db.get(id);
|
|
581
|
-
if (!doc.docType || !(doc.docType === "CARD" /* CARD */)) {
|
|
582
|
-
throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);
|
|
583
|
-
}
|
|
584
|
-
return this.db.remove(doc);
|
|
585
|
-
}
|
|
586
|
-
async getCardDisplayableDataIDs(id) {
|
|
587
|
-
logger.debug(id.join(", "));
|
|
588
|
-
const cards = await this.db.allDocs({
|
|
589
|
-
keys: id,
|
|
590
|
-
include_docs: true
|
|
591
|
-
});
|
|
592
|
-
const ret = {};
|
|
593
|
-
cards.rows.forEach((r) => {
|
|
594
|
-
if (isSuccessRow(r)) {
|
|
595
|
-
ret[r.id] = r.doc.id_displayable_data;
|
|
596
|
-
}
|
|
597
|
-
});
|
|
598
|
-
await Promise.all(
|
|
599
|
-
cards.rows.map((r) => {
|
|
600
|
-
return async () => {
|
|
601
|
-
if (isSuccessRow(r)) {
|
|
602
|
-
ret[r.id] = r.doc.id_displayable_data;
|
|
603
|
-
}
|
|
604
|
-
};
|
|
605
|
-
})
|
|
606
|
-
);
|
|
607
|
-
return ret;
|
|
608
|
-
}
|
|
609
|
-
async getCardsByELO(elo, cardLimit) {
|
|
610
|
-
elo = parseInt(elo);
|
|
611
|
-
const limit = cardLimit ? cardLimit : 25;
|
|
612
|
-
const below = await this.db.query("elo", {
|
|
613
|
-
limit: Math.ceil(limit / 2),
|
|
614
|
-
startkey: elo,
|
|
615
|
-
descending: true
|
|
616
|
-
});
|
|
617
|
-
const aboveLimit = limit - below.rows.length;
|
|
618
|
-
const above = await this.db.query("elo", {
|
|
619
|
-
limit: aboveLimit,
|
|
620
|
-
startkey: elo + 1
|
|
621
|
-
});
|
|
622
|
-
let cards = below.rows;
|
|
623
|
-
cards = cards.concat(above.rows);
|
|
624
|
-
const ret = cards.sort((a, b) => {
|
|
625
|
-
const s = Math.abs(a.key - elo) - Math.abs(b.key - elo);
|
|
626
|
-
if (s === 0) {
|
|
627
|
-
return Math.random() - 0.5;
|
|
628
|
-
} else {
|
|
629
|
-
return s;
|
|
630
|
-
}
|
|
631
|
-
}).map((c) => `${this.id}-${c.id}-${c.key}`);
|
|
632
|
-
const str = `below:
|
|
633
|
-
${below.rows.map((r) => ` ${r.id}-${r.key}
|
|
634
|
-
`)}
|
|
635
|
-
|
|
636
|
-
above:
|
|
637
|
-
${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
638
|
-
`)}`;
|
|
639
|
-
logger.debug(`Getting ${limit} cards centered around elo: ${elo}:
|
|
568
|
+
}
|
|
569
|
+
});
|
|
640
570
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
}
|
|
652
|
-
async updateCourseConfig(cfg) {
|
|
653
|
-
logger.debug(`Updating: ${JSON.stringify(cfg)}`);
|
|
654
|
-
try {
|
|
655
|
-
return await updateCredentialledCourseConfig(this.id, cfg);
|
|
656
|
-
} catch (error) {
|
|
657
|
-
logger.error(`Error updating course config in course DB: ${error}`);
|
|
658
|
-
throw error;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
async updateCardElo(cardId, elo) {
|
|
662
|
-
if (!elo) {
|
|
663
|
-
throw new Error(`Cannot update card elo with null or undefined value for card ID: ${cardId}`);
|
|
664
|
-
}
|
|
665
|
-
try {
|
|
666
|
-
const result = await this.updateQueue.update(cardId, (card) => {
|
|
667
|
-
logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);
|
|
668
|
-
card.elo = elo;
|
|
669
|
-
return card;
|
|
670
|
-
});
|
|
671
|
-
return { ok: true, id: cardId, rev: result._rev };
|
|
672
|
-
} catch (error) {
|
|
673
|
-
logger.error(`Failed to update card elo for card ID: ${cardId}`, error);
|
|
674
|
-
throw new Error(`Failed to update card elo for card ID: ${cardId}`);
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
async getAppliedTags(cardId) {
|
|
678
|
-
const ret = await getAppliedTags(this.id, cardId);
|
|
679
|
-
if (ret) {
|
|
680
|
-
return ret;
|
|
681
|
-
} else {
|
|
682
|
-
throw new Error(`Failed to find tags for card ${this.id}-${cardId}`);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
async addTagToCard(cardId, tagId, updateELO) {
|
|
686
|
-
return await addTagToCard(this.id, cardId, tagId, (await this._getCurrentUser()).getUsername(), updateELO);
|
|
687
|
-
}
|
|
688
|
-
async removeTagFromCard(cardId, tagId) {
|
|
689
|
-
return await removeTagFromCard(this.id, cardId, tagId);
|
|
690
|
-
}
|
|
691
|
-
async createTag(name, author) {
|
|
692
|
-
return await createTag(this.id, name, author);
|
|
693
|
-
}
|
|
694
|
-
async getTag(tagId) {
|
|
695
|
-
return await getTag(this.id, tagId);
|
|
696
|
-
}
|
|
697
|
-
async updateTag(tag) {
|
|
698
|
-
if (tag.course !== this.id) {
|
|
699
|
-
throw new Error(`Tag ${JSON.stringify(tag)} does not belong to course ${this.id}`);
|
|
700
|
-
}
|
|
701
|
-
return await updateTag(tag);
|
|
702
|
-
}
|
|
703
|
-
async getCourseTagStubs() {
|
|
704
|
-
return getCourseTagStubs(this.id);
|
|
705
|
-
}
|
|
706
|
-
async addNote(codeCourse, shape, data, author, tags, uploads, elo = blankCourseElo()) {
|
|
707
|
-
try {
|
|
708
|
-
const resp = await addNote55(this.id, codeCourse, shape, data, author, tags, uploads, elo);
|
|
709
|
-
if (resp.ok) {
|
|
710
|
-
if (resp.cardCreationFailed) {
|
|
711
|
-
logger.warn(
|
|
712
|
-
`[courseDB.addNote] Note added but card creation failed: ${resp.cardCreationError}`
|
|
713
|
-
);
|
|
714
|
-
return {
|
|
715
|
-
status: Status.error,
|
|
716
|
-
message: `Note was added but no cards were created: ${resp.cardCreationError}`,
|
|
717
|
-
id: resp.id
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
return {
|
|
721
|
-
status: Status.ok,
|
|
722
|
-
message: "",
|
|
723
|
-
id: resp.id
|
|
724
|
-
};
|
|
725
|
-
} else {
|
|
726
|
-
return {
|
|
727
|
-
status: Status.error,
|
|
728
|
-
message: "Unexpected error adding note"
|
|
729
|
-
};
|
|
730
|
-
}
|
|
731
|
-
} catch (e) {
|
|
732
|
-
const err = e;
|
|
733
|
-
logger.error(
|
|
734
|
-
`[addNote] error ${err.name}
|
|
735
|
-
reason: ${err.reason}
|
|
736
|
-
message: ${err.message}`
|
|
737
|
-
);
|
|
738
|
-
return {
|
|
739
|
-
status: Status.error,
|
|
740
|
-
message: `Error adding note to course. ${e.reason || err.message}`
|
|
741
|
-
};
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
async getCourseDoc(id, options) {
|
|
745
|
-
return await getCourseDoc(this.id, id, options);
|
|
746
|
-
}
|
|
747
|
-
async getCourseDocs(ids, options = {}) {
|
|
748
|
-
return await getCourseDocs(this.id, ids, options);
|
|
749
|
-
}
|
|
750
|
-
////////////////////////////////////
|
|
751
|
-
// NavigationStrategyManager implementation
|
|
752
|
-
////////////////////////////////////
|
|
753
|
-
getNavigationStrategy(id) {
|
|
754
|
-
logger.debug(`[courseDB] Getting navigation strategy: ${id}`);
|
|
755
|
-
const strategy = {
|
|
756
|
-
id: "ELO",
|
|
757
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
758
|
-
name: "ELO",
|
|
759
|
-
description: "ELO-based navigation strategy for ordering content by difficulty",
|
|
760
|
-
implementingClass: "elo" /* ELO */,
|
|
761
|
-
course: this.id,
|
|
762
|
-
serializedData: ""
|
|
763
|
-
// serde is a noop for ELO navigator.
|
|
764
|
-
};
|
|
765
|
-
return Promise.resolve(strategy);
|
|
766
|
-
}
|
|
767
|
-
getAllNavigationStrategies() {
|
|
768
|
-
logger.debug("[courseDB] Returning hard-coded navigation strategies");
|
|
769
|
-
const strategies = [
|
|
770
|
-
{
|
|
771
|
-
id: "ELO",
|
|
772
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
773
|
-
name: "ELO",
|
|
774
|
-
description: "ELO-based navigation strategy for ordering content by difficulty",
|
|
775
|
-
implementingClass: "elo" /* ELO */,
|
|
776
|
-
course: this.id,
|
|
777
|
-
serializedData: ""
|
|
778
|
-
// serde is a noop for ELO navigator.
|
|
779
|
-
}
|
|
780
|
-
];
|
|
781
|
-
return Promise.resolve(strategies);
|
|
782
|
-
}
|
|
783
|
-
addNavigationStrategy(data) {
|
|
784
|
-
logger.debug(`[courseDB] Adding navigation strategy: ${data.id}`);
|
|
785
|
-
logger.debug(JSON.stringify(data));
|
|
786
|
-
return Promise.resolve();
|
|
787
|
-
}
|
|
788
|
-
updateNavigationStrategy(id, data) {
|
|
789
|
-
logger.debug(`[courseDB] Updating navigation strategy: ${id}`);
|
|
790
|
-
logger.debug(JSON.stringify(data));
|
|
791
|
-
return Promise.resolve();
|
|
792
|
-
}
|
|
793
|
-
async surfaceNavigationStrategy() {
|
|
794
|
-
logger.warn(`Returning hard-coded default ELO navigator`);
|
|
795
|
-
const ret = {
|
|
796
|
-
id: "ELO",
|
|
797
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
798
|
-
name: "ELO",
|
|
799
|
-
description: "ELO-based navigation strategy",
|
|
800
|
-
implementingClass: "elo" /* ELO */,
|
|
801
|
-
course: this.id,
|
|
802
|
-
serializedData: ""
|
|
803
|
-
// serde is a noop for ELO navigator.
|
|
804
|
-
};
|
|
805
|
-
return Promise.resolve(ret);
|
|
806
|
-
}
|
|
807
|
-
////////////////////////////////////
|
|
808
|
-
// END NavigationStrategyManager implementation
|
|
809
|
-
////////////////////////////////////
|
|
810
|
-
////////////////////////////////////
|
|
811
|
-
// StudyContentSource implementation
|
|
812
|
-
////////////////////////////////////
|
|
813
|
-
async getNewCards(limit = 99) {
|
|
814
|
-
const u = await this._getCurrentUser();
|
|
815
|
-
try {
|
|
816
|
-
const strategy = await this.surfaceNavigationStrategy();
|
|
817
|
-
const navigator = await ContentNavigator.create(u, this, strategy);
|
|
818
|
-
return navigator.getNewCards(limit);
|
|
819
|
-
} catch (e) {
|
|
820
|
-
logger.error(`[courseDB] Error surfacing a NavigationStrategy: ${e}`);
|
|
821
|
-
throw e;
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
async getPendingReviews() {
|
|
825
|
-
const u = await this._getCurrentUser();
|
|
826
|
-
try {
|
|
827
|
-
const strategy = await this.surfaceNavigationStrategy();
|
|
828
|
-
const navigator = await ContentNavigator.create(u, this, strategy);
|
|
829
|
-
return navigator.getPendingReviews();
|
|
830
|
-
} catch (e) {
|
|
831
|
-
logger.error(`[courseDB] Error surfacing a NavigationStrategy: ${e}`);
|
|
832
|
-
throw e;
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
async getCardsCenteredAtELO(options = {
|
|
836
|
-
limit: 99,
|
|
837
|
-
elo: "user"
|
|
838
|
-
}, filter) {
|
|
839
|
-
let targetElo;
|
|
840
|
-
if (options.elo === "user") {
|
|
841
|
-
const u = await this._getCurrentUser();
|
|
842
|
-
targetElo = -1;
|
|
843
|
-
try {
|
|
844
|
-
const courseDoc = (await u.getCourseRegistrationsDoc()).courses.find((c) => {
|
|
845
|
-
return c.courseID === this.id;
|
|
846
|
-
});
|
|
847
|
-
targetElo = EloToNumber(courseDoc.elo);
|
|
848
|
-
} catch {
|
|
849
|
-
targetElo = 1e3;
|
|
850
|
-
}
|
|
851
|
-
} else if (options.elo === "random") {
|
|
852
|
-
const bounds = await GET_CACHED(`elo-bounds-${this.id}`, () => this.getELOBounds());
|
|
853
|
-
targetElo = Math.round(bounds.low + Math.random() * (bounds.high - bounds.low));
|
|
854
|
-
} else {
|
|
855
|
-
targetElo = options.elo;
|
|
856
|
-
}
|
|
857
|
-
let cards = [];
|
|
858
|
-
let mult = 4;
|
|
859
|
-
let previousCount = -1;
|
|
860
|
-
let newCount = 0;
|
|
861
|
-
while (cards.length < options.limit && newCount !== previousCount) {
|
|
862
|
-
cards = await this.getCardsByELO(targetElo, mult * options.limit);
|
|
863
|
-
previousCount = newCount;
|
|
864
|
-
newCount = cards.length;
|
|
865
|
-
logger.debug(`Found ${cards.length} elo neighbor cards...`);
|
|
866
|
-
if (filter) {
|
|
867
|
-
cards = cards.filter(filter);
|
|
868
|
-
logger.debug(`Filtered to ${cards.length} cards...`);
|
|
869
|
-
}
|
|
870
|
-
mult *= 2;
|
|
871
|
-
}
|
|
872
|
-
const selectedCards = [];
|
|
873
|
-
while (selectedCards.length < options.limit && cards.length > 0) {
|
|
874
|
-
const index = randIntWeightedTowardZero(cards.length);
|
|
875
|
-
const card = cards.splice(index, 1)[0];
|
|
876
|
-
selectedCards.push(card);
|
|
877
|
-
}
|
|
878
|
-
return selectedCards.map((c) => {
|
|
879
|
-
const split = c.split("-");
|
|
880
|
-
return {
|
|
881
|
-
courseID: this.id,
|
|
882
|
-
cardID: split[1],
|
|
883
|
-
qualifiedID: `${split[0]}-${split[1]}`,
|
|
884
|
-
contentSourceType: "course",
|
|
885
|
-
contentSourceID: this.id,
|
|
886
|
-
status: "new"
|
|
887
|
-
};
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
};
|
|
571
|
+
// src/impl/couch/classroomDB.ts
|
|
572
|
+
import moment3 from "moment";
|
|
573
|
+
var init_classroomDB2 = __esm({
|
|
574
|
+
"src/impl/couch/classroomDB.ts"() {
|
|
575
|
+
"use strict";
|
|
576
|
+
init_factory();
|
|
577
|
+
init_logger();
|
|
578
|
+
init_pouchdb_setup();
|
|
579
|
+
init_couch();
|
|
580
|
+
init_courseDB();
|
|
891
581
|
}
|
|
892
582
|
});
|
|
893
583
|
|
|
894
|
-
// src/impl/
|
|
895
|
-
var
|
|
896
|
-
"src/impl/
|
|
584
|
+
// src/impl/couch/adminDB.ts
|
|
585
|
+
var init_adminDB2 = __esm({
|
|
586
|
+
"src/impl/couch/adminDB.ts"() {
|
|
897
587
|
"use strict";
|
|
588
|
+
init_pouchdb_setup();
|
|
589
|
+
init_factory();
|
|
590
|
+
init_couch();
|
|
591
|
+
init_classroomDB2();
|
|
592
|
+
init_courseLookupDB();
|
|
593
|
+
init_logger();
|
|
898
594
|
}
|
|
899
595
|
});
|
|
900
596
|
|
|
901
|
-
// src/
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
}
|
|
905
|
-
var init_util = __esm({
|
|
906
|
-
"src/core/util/index.ts"() {
|
|
597
|
+
// src/impl/couch/auth.ts
|
|
598
|
+
var init_auth = __esm({
|
|
599
|
+
"src/impl/couch/auth.ts"() {
|
|
907
600
|
"use strict";
|
|
601
|
+
init_factory();
|
|
908
602
|
init_types_legacy();
|
|
603
|
+
init_logger();
|
|
909
604
|
}
|
|
910
605
|
});
|
|
911
606
|
|
|
912
|
-
// src/impl/
|
|
913
|
-
import
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
startkey: prefix,
|
|
917
|
-
endkey: prefix + "\uFFF0",
|
|
918
|
-
include_docs: true
|
|
919
|
-
};
|
|
920
|
-
if (opts) {
|
|
921
|
-
Object.assign(options, opts);
|
|
922
|
-
}
|
|
923
|
-
return db.allDocs(options);
|
|
924
|
-
}
|
|
925
|
-
function getStartAndEndKeys2(key) {
|
|
926
|
-
return {
|
|
927
|
-
startkey: key,
|
|
928
|
-
endkey: key + "\uFFF0"
|
|
929
|
-
};
|
|
930
|
-
}
|
|
931
|
-
function getLocalUserDB(username) {
|
|
932
|
-
return new pouchdb_setup_default(`userdb-${username}`, {});
|
|
933
|
-
}
|
|
934
|
-
function scheduleCardReviewLocal(userDB, review) {
|
|
935
|
-
const now = moment.utc();
|
|
936
|
-
logger.info(`Scheduling for review in: ${review.time.diff(now, "h") / 24} days`);
|
|
937
|
-
void userDB.put({
|
|
938
|
-
_id: REVIEW_PREFIX + review.time.format(REVIEW_TIME_FORMAT),
|
|
939
|
-
cardId: review.card_id,
|
|
940
|
-
reviewTime: review.time,
|
|
941
|
-
courseId: review.course_id,
|
|
942
|
-
scheduledAt: now,
|
|
943
|
-
scheduledFor: review.scheduledFor,
|
|
944
|
-
schedulingAgentId: review.schedulingAgentId
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
|
-
async function removeScheduledCardReviewLocal(userDB, reviewDocID) {
|
|
948
|
-
const reviewDoc = await userDB.get(reviewDocID);
|
|
949
|
-
userDB.remove(reviewDoc).then((res) => {
|
|
950
|
-
if (res.ok) {
|
|
951
|
-
log(`Removed Review Doc: ${reviewDocID}`);
|
|
952
|
-
}
|
|
953
|
-
}).catch((err) => {
|
|
954
|
-
log(`Failed to remove Review Doc: ${reviewDocID},
|
|
955
|
-
${JSON.stringify(err)}`);
|
|
956
|
-
});
|
|
957
|
-
}
|
|
958
|
-
var REVIEW_PREFIX, REVIEW_TIME_FORMAT, log;
|
|
959
|
-
var init_userDBHelpers = __esm({
|
|
960
|
-
"src/impl/common/userDBHelpers.ts"() {
|
|
607
|
+
// src/impl/couch/CouchDBSyncStrategy.ts
|
|
608
|
+
import { Status as Status2 } from "@vue-skuilder/common";
|
|
609
|
+
var init_CouchDBSyncStrategy = __esm({
|
|
610
|
+
"src/impl/couch/CouchDBSyncStrategy.ts"() {
|
|
961
611
|
"use strict";
|
|
612
|
+
init_factory();
|
|
613
|
+
init_types_legacy();
|
|
962
614
|
init_logger();
|
|
615
|
+
init_common();
|
|
963
616
|
init_pouchdb_setup();
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
log = (s) => {
|
|
967
|
-
logger.info(s);
|
|
968
|
-
};
|
|
617
|
+
init_couch();
|
|
618
|
+
init_auth();
|
|
969
619
|
}
|
|
970
620
|
});
|
|
971
621
|
|
|
972
|
-
// src/impl/couch/
|
|
973
|
-
import
|
|
974
|
-
|
|
975
|
-
var
|
|
976
|
-
|
|
622
|
+
// src/impl/couch/index.ts
|
|
623
|
+
import moment4 from "moment";
|
|
624
|
+
import process2 from "process";
|
|
625
|
+
var isBrowser, GUEST_LOCAL_DB, localUserDB, pouchDBincludeCredentialsConfig;
|
|
626
|
+
var init_couch = __esm({
|
|
627
|
+
"src/impl/couch/index.ts"() {
|
|
977
628
|
"use strict";
|
|
978
|
-
|
|
979
|
-
|
|
629
|
+
init_factory();
|
|
630
|
+
init_types_legacy();
|
|
980
631
|
init_logger();
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
async getScheduledReviewCount() {
|
|
999
|
-
return (await this.getPendingReviews()).length;
|
|
1000
|
-
}
|
|
1001
|
-
async getCourseSettings() {
|
|
1002
|
-
const regDoc = await this.user.getCourseRegistrationsDoc();
|
|
1003
|
-
const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);
|
|
1004
|
-
if (crsDoc && crsDoc.settings) {
|
|
1005
|
-
return crsDoc.settings;
|
|
1006
|
-
} else {
|
|
1007
|
-
logger.warn(`no settings found during lookup on course ${this._courseId}`);
|
|
1008
|
-
return {};
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
updateCourseSettings(updates) {
|
|
1012
|
-
void this.user.updateCourseSettings(this._courseId, updates);
|
|
1013
|
-
}
|
|
1014
|
-
async getReviewstoDate(targetDate) {
|
|
1015
|
-
const keys = getStartAndEndKeys(REVIEW_PREFIX2);
|
|
1016
|
-
const reviews = await this.user.remote().allDocs({
|
|
1017
|
-
startkey: keys.startkey,
|
|
1018
|
-
endkey: keys.endkey,
|
|
1019
|
-
include_docs: true
|
|
1020
|
-
});
|
|
1021
|
-
logger.debug(
|
|
1022
|
-
`Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`
|
|
1023
|
-
);
|
|
1024
|
-
return reviews.rows.filter((r) => {
|
|
1025
|
-
if (r.id.startsWith(REVIEW_PREFIX2)) {
|
|
1026
|
-
const date = moment2.utc(r.id.substr(REVIEW_PREFIX2.length), REVIEW_TIME_FORMAT2);
|
|
1027
|
-
if (targetDate.isAfter(date)) {
|
|
1028
|
-
if (this._courseId === void 0 || r.doc.courseId === this._courseId) {
|
|
1029
|
-
return true;
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
}).map((r) => r.doc);
|
|
632
|
+
init_pouchdb_setup();
|
|
633
|
+
init_contentSource();
|
|
634
|
+
init_adminDB2();
|
|
635
|
+
init_classroomDB2();
|
|
636
|
+
init_courseAPI();
|
|
637
|
+
init_courseDB();
|
|
638
|
+
init_CouchDBSyncStrategy();
|
|
639
|
+
isBrowser = typeof window !== "undefined";
|
|
640
|
+
if (isBrowser) {
|
|
641
|
+
window.process = process2;
|
|
642
|
+
}
|
|
643
|
+
GUEST_LOCAL_DB = `userdb-${GuestUsername}`;
|
|
644
|
+
localUserDB = new pouchdb_setup_default(GUEST_LOCAL_DB);
|
|
645
|
+
pouchDBincludeCredentialsConfig = {
|
|
646
|
+
fetch(url, opts) {
|
|
647
|
+
opts.credentials = "include";
|
|
648
|
+
return pouchdb_setup_default.fetch(url, opts);
|
|
1034
649
|
}
|
|
1035
650
|
};
|
|
1036
651
|
}
|
|
1037
652
|
});
|
|
1038
653
|
|
|
1039
654
|
// src/impl/common/BaseUserDB.ts
|
|
1040
|
-
import { Status as
|
|
1041
|
-
import
|
|
655
|
+
import { Status as Status3 } from "@vue-skuilder/common";
|
|
656
|
+
import moment5 from "moment";
|
|
1042
657
|
async function getOrCreateClassroomRegistrationsDoc(user) {
|
|
1043
658
|
let ret;
|
|
1044
659
|
try {
|
|
@@ -1098,7 +713,7 @@ async function updateUserElo(user, course_id, elo) {
|
|
|
1098
713
|
return getLocalUserDB(user).put(regDoc);
|
|
1099
714
|
}
|
|
1100
715
|
async function registerUserForClassroom(user, classID, registerAs) {
|
|
1101
|
-
|
|
716
|
+
log3(`Registering user: ${user} in course: ${classID}`);
|
|
1102
717
|
return getOrCreateClassroomRegistrationsDoc(user).then((doc) => {
|
|
1103
718
|
const regItem = {
|
|
1104
719
|
classID,
|
|
@@ -1109,7 +724,7 @@ async function registerUserForClassroom(user, classID, registerAs) {
|
|
|
1109
724
|
}).length === 0) {
|
|
1110
725
|
doc.registrations.push(regItem);
|
|
1111
726
|
} else {
|
|
1112
|
-
|
|
727
|
+
log3(`User ${user} is already registered for class ${classID}`);
|
|
1113
728
|
}
|
|
1114
729
|
return getLocalUserDB(user).put(doc);
|
|
1115
730
|
});
|
|
@@ -1131,10 +746,11 @@ async function dropUserFromClassroom(user, classID) {
|
|
|
1131
746
|
async function getUserClassrooms(user) {
|
|
1132
747
|
return getOrCreateClassroomRegistrationsDoc(user);
|
|
1133
748
|
}
|
|
1134
|
-
var
|
|
749
|
+
var log3, BaseUser, userCoursesDoc, userClassroomsDoc;
|
|
1135
750
|
var init_BaseUserDB = __esm({
|
|
1136
751
|
"src/impl/common/BaseUserDB.ts"() {
|
|
1137
752
|
"use strict";
|
|
753
|
+
init_core();
|
|
1138
754
|
init_util();
|
|
1139
755
|
init_types_legacy();
|
|
1140
756
|
init_logger();
|
|
@@ -1142,10 +758,9 @@ var init_BaseUserDB = __esm({
|
|
|
1142
758
|
init_updateQueue();
|
|
1143
759
|
init_user_course_relDB();
|
|
1144
760
|
init_couch();
|
|
1145
|
-
|
|
761
|
+
log3 = (s) => {
|
|
1146
762
|
logger.info(s);
|
|
1147
763
|
};
|
|
1148
|
-
cardHistoryPrefix2 = "cardH-";
|
|
1149
764
|
BaseUser = class _BaseUser {
|
|
1150
765
|
static _instance;
|
|
1151
766
|
static _initialized = false;
|
|
@@ -1166,11 +781,13 @@ var init_BaseUserDB = __esm({
|
|
|
1166
781
|
isLoggedIn() {
|
|
1167
782
|
return !this._username.startsWith(GuestUsername);
|
|
1168
783
|
}
|
|
1169
|
-
remoteDB;
|
|
1170
784
|
remote() {
|
|
1171
785
|
return this.remoteDB;
|
|
1172
786
|
}
|
|
1173
787
|
localDB;
|
|
788
|
+
remoteDB;
|
|
789
|
+
writeDB;
|
|
790
|
+
// Database to use for write operations (local-first approach)
|
|
1174
791
|
updateQueue;
|
|
1175
792
|
async createAccount(username, password) {
|
|
1176
793
|
if (!this.syncStrategy.canCreateAccount()) {
|
|
@@ -1183,10 +800,14 @@ Currently logged-in as ${this._username}.`
|
|
|
1183
800
|
);
|
|
1184
801
|
}
|
|
1185
802
|
const result = await this.syncStrategy.createAccount(username, password);
|
|
1186
|
-
if (result.status ===
|
|
1187
|
-
|
|
803
|
+
if (result.status === Status3.ok) {
|
|
804
|
+
log3(`Account created successfully, updating username to ${username}`);
|
|
1188
805
|
this._username = username;
|
|
1189
|
-
|
|
806
|
+
try {
|
|
807
|
+
localStorage.removeItem("dbUUID");
|
|
808
|
+
} catch (e) {
|
|
809
|
+
logger.warn("localStorage not available (Node.js environment):", e);
|
|
810
|
+
}
|
|
1190
811
|
await this.init();
|
|
1191
812
|
}
|
|
1192
813
|
return {
|
|
@@ -1198,15 +819,22 @@ Currently logged-in as ${this._username}.`
|
|
|
1198
819
|
if (!this.syncStrategy.canAuthenticate()) {
|
|
1199
820
|
throw new Error("Authentication not supported by current sync strategy");
|
|
1200
821
|
}
|
|
1201
|
-
if (!this._username.startsWith(GuestUsername)) {
|
|
1202
|
-
|
|
1203
|
-
|
|
822
|
+
if (!this._username.startsWith(GuestUsername) && this._username != username) {
|
|
823
|
+
if (this._username != username) {
|
|
824
|
+
throw new Error(`Cannot change accounts while logged in.
|
|
825
|
+
Log out of account ${this.getUsername()} before logging in as ${username}.`);
|
|
826
|
+
}
|
|
827
|
+
logger.warn(`User ${this._username} is already logged in, but executing login again.`);
|
|
1204
828
|
}
|
|
1205
829
|
const loginResult = await this.syncStrategy.authenticate(username, password);
|
|
1206
830
|
if (loginResult.ok) {
|
|
1207
|
-
|
|
831
|
+
log3(`Logged in as ${username}`);
|
|
1208
832
|
this._username = username;
|
|
1209
|
-
|
|
833
|
+
try {
|
|
834
|
+
localStorage.removeItem("dbUUID");
|
|
835
|
+
} catch (e) {
|
|
836
|
+
logger.warn("localStorage not available (Node.js environment):", e);
|
|
837
|
+
}
|
|
1210
838
|
await this.init();
|
|
1211
839
|
}
|
|
1212
840
|
return loginResult;
|
|
@@ -1214,7 +842,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1214
842
|
async resetUserData() {
|
|
1215
843
|
if (this.syncStrategy.canAuthenticate()) {
|
|
1216
844
|
return {
|
|
1217
|
-
status:
|
|
845
|
+
status: Status3.error,
|
|
1218
846
|
error: "Reset user data is only available for local-only mode. Use logout instead for remote sync."
|
|
1219
847
|
};
|
|
1220
848
|
}
|
|
@@ -1223,8 +851,8 @@ Currently logged-in as ${this._username}.`
|
|
|
1223
851
|
const allDocs = await localDB.allDocs({ include_docs: false });
|
|
1224
852
|
const docsToDelete = allDocs.rows.filter((row) => {
|
|
1225
853
|
const id = row.id;
|
|
1226
|
-
return id.startsWith(
|
|
1227
|
-
id.startsWith(
|
|
854
|
+
return id.startsWith(DocTypePrefixes["CARDRECORD" /* CARDRECORD */]) || // Card interaction history
|
|
855
|
+
id.startsWith(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]) || // Scheduled reviews
|
|
1228
856
|
id === _BaseUser.DOC_IDS.COURSE_REGISTRATIONS || // Course registrations
|
|
1229
857
|
id === _BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS || // Classroom registrations
|
|
1230
858
|
id === _BaseUser.DOC_IDS.CONFIG;
|
|
@@ -1233,11 +861,11 @@ Currently logged-in as ${this._username}.`
|
|
|
1233
861
|
await localDB.bulkDocs(docsToDelete);
|
|
1234
862
|
}
|
|
1235
863
|
await this.init();
|
|
1236
|
-
return { status:
|
|
864
|
+
return { status: Status3.ok };
|
|
1237
865
|
} catch (error) {
|
|
1238
866
|
logger.error("Failed to reset user data:", error);
|
|
1239
867
|
return {
|
|
1240
|
-
status:
|
|
868
|
+
status: Status3.error,
|
|
1241
869
|
error: error instanceof Error ? error.message : "Unknown error during reset"
|
|
1242
870
|
};
|
|
1243
871
|
}
|
|
@@ -1293,7 +921,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1293
921
|
*
|
|
1294
922
|
*/
|
|
1295
923
|
async getActiveCards() {
|
|
1296
|
-
const keys =
|
|
924
|
+
const keys = getStartAndEndKeys(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]);
|
|
1297
925
|
const reviews = await this.remoteDB.allDocs({
|
|
1298
926
|
startkey: keys.startkey,
|
|
1299
927
|
endkey: keys.endkey,
|
|
@@ -1365,18 +993,21 @@ Currently logged-in as ${this._username}.`
|
|
|
1365
993
|
}
|
|
1366
994
|
}
|
|
1367
995
|
async getReviewstoDate(targetDate, course_id) {
|
|
1368
|
-
const keys =
|
|
996
|
+
const keys = getStartAndEndKeys(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]);
|
|
1369
997
|
const reviews = await this.remoteDB.allDocs({
|
|
1370
998
|
startkey: keys.startkey,
|
|
1371
999
|
endkey: keys.endkey,
|
|
1372
1000
|
include_docs: true
|
|
1373
1001
|
});
|
|
1374
|
-
|
|
1002
|
+
log3(
|
|
1375
1003
|
`Fetching ${this._username}'s scheduled reviews${course_id ? ` for course ${course_id}` : ""}.`
|
|
1376
1004
|
);
|
|
1377
1005
|
return reviews.rows.filter((r) => {
|
|
1378
|
-
if (r.id.startsWith(
|
|
1379
|
-
const date =
|
|
1006
|
+
if (r.id.startsWith(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */])) {
|
|
1007
|
+
const date = moment5.utc(
|
|
1008
|
+
r.id.substr(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */].length),
|
|
1009
|
+
REVIEW_TIME_FORMAT
|
|
1010
|
+
);
|
|
1380
1011
|
if (targetDate.isAfter(date)) {
|
|
1381
1012
|
if (course_id === void 0 || r.doc.courseId === course_id) {
|
|
1382
1013
|
return true;
|
|
@@ -1386,11 +1017,11 @@ Currently logged-in as ${this._username}.`
|
|
|
1386
1017
|
}).map((r) => r.doc);
|
|
1387
1018
|
}
|
|
1388
1019
|
async getReviewsForcast(daysCount) {
|
|
1389
|
-
const time =
|
|
1020
|
+
const time = moment5.utc().add(daysCount, "days");
|
|
1390
1021
|
return this.getReviewstoDate(time);
|
|
1391
1022
|
}
|
|
1392
1023
|
async getPendingReviews(course_id) {
|
|
1393
|
-
const now =
|
|
1024
|
+
const now = moment5.utc();
|
|
1394
1025
|
return this.getReviewstoDate(now, course_id);
|
|
1395
1026
|
}
|
|
1396
1027
|
async getScheduledReviewCount(course_id) {
|
|
@@ -1433,12 +1064,12 @@ Currently logged-in as ${this._username}.`
|
|
|
1433
1064
|
if (doc.courses.filter((course) => {
|
|
1434
1065
|
return course.courseID === regItem.courseID;
|
|
1435
1066
|
}).length === 0) {
|
|
1436
|
-
|
|
1067
|
+
log3(`It's a new course registration!`);
|
|
1437
1068
|
doc.courses.push(regItem);
|
|
1438
1069
|
doc.studyWeight[course_id] = 1;
|
|
1439
1070
|
} else {
|
|
1440
1071
|
doc.courses.forEach((c) => {
|
|
1441
|
-
|
|
1072
|
+
log3(`Found the previously registered course!`);
|
|
1442
1073
|
if (c.courseID === course_id) {
|
|
1443
1074
|
c.status = status;
|
|
1444
1075
|
}
|
|
@@ -1446,7 +1077,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1446
1077
|
}
|
|
1447
1078
|
return this.localDB.put(doc);
|
|
1448
1079
|
}).catch((e) => {
|
|
1449
|
-
|
|
1080
|
+
log3(`Registration failed because of: ${JSON.stringify(e)}`);
|
|
1450
1081
|
throw e;
|
|
1451
1082
|
});
|
|
1452
1083
|
}
|
|
@@ -1491,7 +1122,8 @@ Currently logged-in as ${this._username}.`
|
|
|
1491
1122
|
const defaultConfig = {
|
|
1492
1123
|
_id: _BaseUser.DOC_IDS.CONFIG,
|
|
1493
1124
|
darkMode: false,
|
|
1494
|
-
likesConfetti: false
|
|
1125
|
+
likesConfetti: false,
|
|
1126
|
+
sessionTimeLimit: 5
|
|
1495
1127
|
};
|
|
1496
1128
|
try {
|
|
1497
1129
|
const cfg = await this.localDB.get(_BaseUser.DOC_IDS.CONFIG);
|
|
@@ -1564,10 +1196,15 @@ Currently logged-in as ${this._username}.`
|
|
|
1564
1196
|
setDBandQ() {
|
|
1565
1197
|
this.localDB = getLocalUserDB(this._username);
|
|
1566
1198
|
this.remoteDB = this.syncStrategy.setupRemoteDB(this._username);
|
|
1567
|
-
this.
|
|
1199
|
+
this.writeDB = this.syncStrategy.getWriteDB ? this.syncStrategy.getWriteDB(this._username) : this.localDB;
|
|
1200
|
+
this.updateQueue = new UpdateQueue(this.localDB, this.writeDB);
|
|
1568
1201
|
}
|
|
1569
1202
|
async init() {
|
|
1570
1203
|
_BaseUser._initialized = false;
|
|
1204
|
+
if (this._username === "admin") {
|
|
1205
|
+
_BaseUser._initialized = true;
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1571
1208
|
this.setDBandQ();
|
|
1572
1209
|
this.syncStrategy.startSync(this.localDB, this.remoteDB);
|
|
1573
1210
|
void this.applyDesignDocs();
|
|
@@ -1589,6 +1226,9 @@ Currently logged-in as ${this._username}.`
|
|
|
1589
1226
|
}
|
|
1590
1227
|
];
|
|
1591
1228
|
async applyDesignDocs() {
|
|
1229
|
+
if (this._username === "admin") {
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1592
1232
|
for (const doc of _BaseUser.designDocs) {
|
|
1593
1233
|
try {
|
|
1594
1234
|
try {
|
|
@@ -1605,7 +1245,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1605
1245
|
}
|
|
1606
1246
|
}
|
|
1607
1247
|
} catch (error) {
|
|
1608
|
-
if (error
|
|
1248
|
+
if (error.name && error.name === "conflict") {
|
|
1609
1249
|
logger.warn(`Design doc ${doc._id} update conflict - will retry`);
|
|
1610
1250
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1611
1251
|
await this.applyDesignDoc(doc);
|
|
@@ -1643,7 +1283,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1643
1283
|
*/
|
|
1644
1284
|
async putCardRecord(record) {
|
|
1645
1285
|
const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);
|
|
1646
|
-
record.timeStamp =
|
|
1286
|
+
record.timeStamp = moment5.utc(record.timeStamp).toString();
|
|
1647
1287
|
try {
|
|
1648
1288
|
const cardHistory = await this.update(
|
|
1649
1289
|
cardHistoryID,
|
|
@@ -1659,7 +1299,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1659
1299
|
const ret = {
|
|
1660
1300
|
...record2
|
|
1661
1301
|
};
|
|
1662
|
-
ret.timeStamp =
|
|
1302
|
+
ret.timeStamp = moment5.utc(record2.timeStamp);
|
|
1663
1303
|
return ret;
|
|
1664
1304
|
});
|
|
1665
1305
|
return cardHistory;
|
|
@@ -1675,8 +1315,8 @@ Currently logged-in as ${this._username}.`
|
|
|
1675
1315
|
streak: 0,
|
|
1676
1316
|
bestInterval: 0
|
|
1677
1317
|
};
|
|
1678
|
-
|
|
1679
|
-
return initCardHistory;
|
|
1318
|
+
const putResult = await this.writeDB.put(initCardHistory);
|
|
1319
|
+
return { ...initCardHistory, _rev: putResult.rev };
|
|
1680
1320
|
} else {
|
|
1681
1321
|
throw new Error(`putCardRecord failed because of:
|
|
1682
1322
|
name:${reason.name}
|
|
@@ -1687,17 +1327,17 @@ Currently logged-in as ${this._username}.`
|
|
|
1687
1327
|
}
|
|
1688
1328
|
async deduplicateReviews() {
|
|
1689
1329
|
try {
|
|
1690
|
-
|
|
1330
|
+
log3("Starting deduplication of scheduled reviews...");
|
|
1691
1331
|
const reviewsMap = {};
|
|
1692
1332
|
const duplicateDocIds = [];
|
|
1693
1333
|
const scheduledReviews = await this.remoteDB.query("reviewCards/reviewCards");
|
|
1694
|
-
|
|
1334
|
+
log3(`Found ${scheduledReviews.rows.length} scheduled reviews to process`);
|
|
1695
1335
|
scheduledReviews.rows.forEach((r) => {
|
|
1696
1336
|
const qualifiedCardId = r.value;
|
|
1697
1337
|
const docId = r.key;
|
|
1698
1338
|
if (reviewsMap[qualifiedCardId]) {
|
|
1699
|
-
|
|
1700
|
-
|
|
1339
|
+
log3(`Found duplicate scheduled review for card: ${qualifiedCardId}`);
|
|
1340
|
+
log3(
|
|
1701
1341
|
`Marking earlier review ${reviewsMap[qualifiedCardId]} for deletion, keeping ${docId}`
|
|
1702
1342
|
);
|
|
1703
1343
|
duplicateDocIds.push(reviewsMap[qualifiedCardId]);
|
|
@@ -1707,23 +1347,23 @@ Currently logged-in as ${this._username}.`
|
|
|
1707
1347
|
}
|
|
1708
1348
|
});
|
|
1709
1349
|
if (duplicateDocIds.length > 0) {
|
|
1710
|
-
|
|
1350
|
+
log3(`Removing ${duplicateDocIds.length} duplicate reviews...`);
|
|
1711
1351
|
const deletePromises = duplicateDocIds.map(async (docId) => {
|
|
1712
1352
|
try {
|
|
1713
1353
|
const doc = await this.remoteDB.get(docId);
|
|
1714
|
-
await this.
|
|
1715
|
-
|
|
1354
|
+
await this.writeDB.remove(doc);
|
|
1355
|
+
log3(`Successfully removed duplicate review: ${docId}`);
|
|
1716
1356
|
} catch (error) {
|
|
1717
|
-
|
|
1357
|
+
log3(`Failed to remove duplicate review ${docId}: ${error}`);
|
|
1718
1358
|
}
|
|
1719
1359
|
});
|
|
1720
1360
|
await Promise.all(deletePromises);
|
|
1721
|
-
|
|
1361
|
+
log3(`Deduplication complete. Processed ${duplicateDocIds.length} duplicates`);
|
|
1722
1362
|
} else {
|
|
1723
|
-
|
|
1363
|
+
log3("No duplicate reviews found");
|
|
1724
1364
|
}
|
|
1725
1365
|
} catch (error) {
|
|
1726
|
-
|
|
1366
|
+
log3(`Error during review deduplication: ${error}`);
|
|
1727
1367
|
}
|
|
1728
1368
|
}
|
|
1729
1369
|
/**
|
|
@@ -1733,17 +1373,17 @@ Currently logged-in as ${this._username}.`
|
|
|
1733
1373
|
* @param course_id optional specification of individual course
|
|
1734
1374
|
*/
|
|
1735
1375
|
async getSeenCards(course_id) {
|
|
1736
|
-
let prefix =
|
|
1376
|
+
let prefix = DocTypePrefixes["CARDRECORD" /* CARDRECORD */];
|
|
1737
1377
|
if (course_id) {
|
|
1738
1378
|
prefix += course_id;
|
|
1739
1379
|
}
|
|
1740
|
-
const docs = await
|
|
1380
|
+
const docs = await filterAllDocsByPrefix(this.localDB, prefix, {
|
|
1741
1381
|
include_docs: false
|
|
1742
1382
|
});
|
|
1743
1383
|
const ret = [];
|
|
1744
1384
|
docs.rows.forEach((row) => {
|
|
1745
|
-
if (row.id.startsWith(
|
|
1746
|
-
ret.push(row.id.substr(
|
|
1385
|
+
if (row.id.startsWith(DocTypePrefixes["CARDRECORD" /* CARDRECORD */])) {
|
|
1386
|
+
ret.push(row.id.substr(DocTypePrefixes["CARDRECORD" /* CARDRECORD */].length));
|
|
1747
1387
|
}
|
|
1748
1388
|
});
|
|
1749
1389
|
return ret;
|
|
@@ -1753,9 +1393,9 @@ Currently logged-in as ${this._username}.`
|
|
|
1753
1393
|
* @returns A promise of the cards that the user has seen in the past.
|
|
1754
1394
|
*/
|
|
1755
1395
|
async getHistory() {
|
|
1756
|
-
const cards = await
|
|
1396
|
+
const cards = await filterAllDocsByPrefix(
|
|
1757
1397
|
this.remoteDB,
|
|
1758
|
-
|
|
1398
|
+
DocTypePrefixes["CARDRECORD" /* CARDRECORD */],
|
|
1759
1399
|
{
|
|
1760
1400
|
include_docs: true,
|
|
1761
1401
|
attachments: false
|
|
@@ -1796,7 +1436,7 @@ Currently logged-in as ${this._username}.`
|
|
|
1796
1436
|
} catch (e) {
|
|
1797
1437
|
const err = e;
|
|
1798
1438
|
if (err.status === 404) {
|
|
1799
|
-
await this.
|
|
1439
|
+
await this.writeDB.put({
|
|
1800
1440
|
_id: _BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS,
|
|
1801
1441
|
registrations: []
|
|
1802
1442
|
});
|
|
@@ -1844,10 +1484,10 @@ Currently logged-in as ${this._username}.`
|
|
|
1844
1484
|
}
|
|
1845
1485
|
}
|
|
1846
1486
|
async scheduleCardReview(review) {
|
|
1847
|
-
return scheduleCardReviewLocal(this.
|
|
1487
|
+
return scheduleCardReviewLocal(this.writeDB, review);
|
|
1848
1488
|
}
|
|
1849
1489
|
async removeScheduledCardReview(reviewId) {
|
|
1850
|
-
return removeScheduledCardReviewLocal(this.
|
|
1490
|
+
return removeScheduledCardReviewLocal(this.writeDB, reviewId);
|
|
1851
1491
|
}
|
|
1852
1492
|
async registerForClassroom(_classId, _registerAs) {
|
|
1853
1493
|
return registerUserForClassroom(this._username, _classId, _registerAs);
|
|
@@ -1877,298 +1517,17 @@ var init_common = __esm({
|
|
|
1877
1517
|
}
|
|
1878
1518
|
});
|
|
1879
1519
|
|
|
1880
|
-
// src/
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
async function addNote55(courseID, codeCourse, shape, data, author, tags, uploads, elo = blankCourseElo2()) {
|
|
1885
|
-
const db = getCourseDB2(courseID);
|
|
1886
|
-
const payload = prepareNote55(courseID, codeCourse, shape, data, author, tags, uploads);
|
|
1887
|
-
const result = await db.post(payload);
|
|
1888
|
-
const dataShapeId = NameSpacer.getDataShapeString({
|
|
1889
|
-
course: codeCourse,
|
|
1890
|
-
dataShape: shape.name
|
|
1891
|
-
});
|
|
1892
|
-
if (result.ok) {
|
|
1893
|
-
try {
|
|
1894
|
-
await createCards(courseID, dataShapeId, result.id, tags, elo, author);
|
|
1895
|
-
} catch (error) {
|
|
1896
|
-
let errorMessage = "Unknown error";
|
|
1897
|
-
if (error instanceof Error) {
|
|
1898
|
-
errorMessage = error.message;
|
|
1899
|
-
} else if (error && typeof error === "object" && "reason" in error) {
|
|
1900
|
-
errorMessage = error.reason;
|
|
1901
|
-
} else if (error && typeof error === "object" && "message" in error) {
|
|
1902
|
-
errorMessage = error.message;
|
|
1903
|
-
} else {
|
|
1904
|
-
errorMessage = String(error);
|
|
1905
|
-
}
|
|
1906
|
-
logger.error(`[addNote55] Failed to create cards for note ${result.id}: ${errorMessage}`);
|
|
1907
|
-
result.cardCreationFailed = true;
|
|
1908
|
-
result.cardCreationError = errorMessage;
|
|
1909
|
-
}
|
|
1910
|
-
} else {
|
|
1911
|
-
logger.error(`[addNote55] Error adding note. Result: ${JSON.stringify(result)}`);
|
|
1912
|
-
}
|
|
1913
|
-
return result;
|
|
1914
|
-
}
|
|
1915
|
-
async function createCards(courseID, datashapeID, noteID, tags, elo = blankCourseElo2(), author) {
|
|
1916
|
-
const cfg = await getCredentialledCourseConfig(courseID);
|
|
1917
|
-
const dsDescriptor = NameSpacer.getDataShapeDescriptor(datashapeID);
|
|
1918
|
-
let questionViewTypes = [];
|
|
1919
|
-
for (const ds of cfg.dataShapes) {
|
|
1920
|
-
if (ds.name === datashapeID) {
|
|
1921
|
-
questionViewTypes = ds.questionTypes;
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
if (questionViewTypes.length === 0) {
|
|
1925
|
-
const errorMsg = `No questionViewTypes found for datashapeID: ${datashapeID} in course config. Cards cannot be created.`;
|
|
1926
|
-
logger.error(errorMsg);
|
|
1927
|
-
throw new Error(errorMsg);
|
|
1928
|
-
}
|
|
1929
|
-
for (const questionView of questionViewTypes) {
|
|
1930
|
-
await createCard(questionView, courseID, dsDescriptor, noteID, tags, elo, author);
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
async function createCard(questionViewName, courseID, dsDescriptor, noteID, tags, elo = blankCourseElo2(), author) {
|
|
1934
|
-
const qDescriptor = NameSpacer.getQuestionDescriptor(questionViewName);
|
|
1935
|
-
const cfg = await getCredentialledCourseConfig(courseID);
|
|
1936
|
-
for (const rQ of cfg.questionTypes) {
|
|
1937
|
-
if (rQ.name === questionViewName) {
|
|
1938
|
-
for (const view of rQ.viewList) {
|
|
1939
|
-
await addCard(
|
|
1940
|
-
courseID,
|
|
1941
|
-
dsDescriptor.course,
|
|
1942
|
-
[noteID],
|
|
1943
|
-
NameSpacer.getViewString({
|
|
1944
|
-
course: qDescriptor.course,
|
|
1945
|
-
questionType: qDescriptor.questionType,
|
|
1946
|
-
view
|
|
1947
|
-
}),
|
|
1948
|
-
elo,
|
|
1949
|
-
tags,
|
|
1950
|
-
author
|
|
1951
|
-
);
|
|
1952
|
-
}
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
async function addCard(courseID, course, id_displayable_data, id_view, elo, tags, author) {
|
|
1957
|
-
const db = getCourseDB2(courseID);
|
|
1958
|
-
const card = await db.post({
|
|
1959
|
-
course,
|
|
1960
|
-
id_displayable_data,
|
|
1961
|
-
id_view,
|
|
1962
|
-
docType: "CARD" /* CARD */,
|
|
1963
|
-
elo: elo || toCourseElo2(990 + Math.round(20 * Math.random())),
|
|
1964
|
-
author
|
|
1965
|
-
});
|
|
1966
|
-
for (const tag of tags) {
|
|
1967
|
-
logger.info(`adding tag: ${tag} to card ${card.id}`);
|
|
1968
|
-
await addTagToCard(courseID, card.id, tag, author, false);
|
|
1969
|
-
}
|
|
1970
|
-
return card;
|
|
1971
|
-
}
|
|
1972
|
-
async function getCredentialledCourseConfig(courseID) {
|
|
1973
|
-
try {
|
|
1974
|
-
const db = getCourseDB2(courseID);
|
|
1975
|
-
const ret = await db.get("CourseConfig");
|
|
1976
|
-
ret.courseID = courseID;
|
|
1977
|
-
logger.info(`Returning course config: ${JSON.stringify(ret)}`);
|
|
1978
|
-
return ret;
|
|
1979
|
-
} catch (e) {
|
|
1980
|
-
logger.error(`Error fetching config for ${courseID}:`, e);
|
|
1981
|
-
throw e;
|
|
1982
|
-
}
|
|
1983
|
-
}
|
|
1984
|
-
async function addTagToCard(courseID, cardID, tagID, author, updateELO = true) {
|
|
1985
|
-
const prefixedTagID = getTagID(tagID);
|
|
1986
|
-
const courseDB = getCourseDB2(courseID);
|
|
1987
|
-
const courseApi = new CourseDB(courseID, async () => {
|
|
1988
|
-
const dummySyncStrategy = {
|
|
1989
|
-
setupRemoteDB: () => null,
|
|
1990
|
-
startSync: () => {
|
|
1991
|
-
},
|
|
1992
|
-
canCreateAccount: () => false,
|
|
1993
|
-
canAuthenticate: () => false,
|
|
1994
|
-
getCurrentUsername: async () => "DummyUser"
|
|
1995
|
-
};
|
|
1996
|
-
return BaseUser.Dummy(dummySyncStrategy);
|
|
1997
|
-
});
|
|
1998
|
-
try {
|
|
1999
|
-
logger.info(`Applying tag ${tagID} to card ${courseID + "-" + cardID}...`);
|
|
2000
|
-
const tag = await courseDB.get(prefixedTagID);
|
|
2001
|
-
if (!tag.taggedCards.includes(cardID)) {
|
|
2002
|
-
tag.taggedCards.push(cardID);
|
|
2003
|
-
if (updateELO) {
|
|
2004
|
-
try {
|
|
2005
|
-
const eloData = await courseApi.getCardEloData([cardID]);
|
|
2006
|
-
const elo = eloData[0];
|
|
2007
|
-
elo.tags[tagID] = {
|
|
2008
|
-
count: 0,
|
|
2009
|
-
score: elo.global.score
|
|
2010
|
-
// todo: or 1000?
|
|
2011
|
-
};
|
|
2012
|
-
await updateCardElo(courseID, cardID, elo);
|
|
2013
|
-
} catch (error) {
|
|
2014
|
-
logger.error("Failed to update ELO data for card:", cardID, error);
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
return courseDB.put(tag);
|
|
2018
|
-
} else throw new AlreadyTaggedErr(`Card ${cardID} is already tagged with ${tagID}`);
|
|
2019
|
-
} catch (e) {
|
|
2020
|
-
if (e instanceof AlreadyTaggedErr) {
|
|
2021
|
-
throw e;
|
|
2022
|
-
}
|
|
2023
|
-
await createTag(courseID, tagID, author);
|
|
2024
|
-
return addTagToCard(courseID, cardID, tagID, author, updateELO);
|
|
2025
|
-
}
|
|
2026
|
-
}
|
|
2027
|
-
async function updateCardElo(courseID, cardID, elo) {
|
|
2028
|
-
if (elo) {
|
|
2029
|
-
const cDB = getCourseDB2(courseID);
|
|
2030
|
-
const card = await cDB.get(cardID);
|
|
2031
|
-
logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);
|
|
2032
|
-
card.elo = elo;
|
|
2033
|
-
return cDB.put(card);
|
|
2034
|
-
}
|
|
2035
|
-
}
|
|
2036
|
-
function getTagID(tagName) {
|
|
2037
|
-
const tagPrefix = "TAG" /* TAG */.valueOf() + "-";
|
|
2038
|
-
if (tagName.indexOf(tagPrefix) === 0) {
|
|
2039
|
-
return tagName;
|
|
2040
|
-
} else {
|
|
2041
|
-
return tagPrefix + tagName;
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
function getCourseDB2(courseID) {
|
|
2045
|
-
const dbName = `coursedb-${courseID}`;
|
|
2046
|
-
return new pouchdb_setup_default(
|
|
2047
|
-
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
|
|
2048
|
-
pouchDBincludeCredentialsConfig
|
|
2049
|
-
);
|
|
2050
|
-
}
|
|
2051
|
-
var AlreadyTaggedErr;
|
|
2052
|
-
var init_courseAPI = __esm({
|
|
2053
|
-
"src/impl/couch/courseAPI.ts"() {
|
|
2054
|
-
"use strict";
|
|
2055
|
-
init_pouchdb_setup();
|
|
2056
|
-
init_couch();
|
|
2057
|
-
init_factory();
|
|
2058
|
-
init_courseDB();
|
|
2059
|
-
init_types_legacy();
|
|
2060
|
-
init_common();
|
|
2061
|
-
init_logger();
|
|
2062
|
-
AlreadyTaggedErr = class extends Error {
|
|
2063
|
-
constructor(message) {
|
|
2064
|
-
super(message);
|
|
2065
|
-
this.name = "AlreadyTaggedErr";
|
|
2066
|
-
}
|
|
2067
|
-
};
|
|
2068
|
-
}
|
|
2069
|
-
});
|
|
2070
|
-
|
|
2071
|
-
// src/impl/couch/auth.ts
|
|
2072
|
-
var init_auth = __esm({
|
|
2073
|
-
"src/impl/couch/auth.ts"() {
|
|
2074
|
-
"use strict";
|
|
2075
|
-
init_factory();
|
|
2076
|
-
init_types_legacy();
|
|
2077
|
-
init_logger();
|
|
2078
|
-
}
|
|
2079
|
-
});
|
|
2080
|
-
|
|
2081
|
-
// src/impl/couch/CouchDBSyncStrategy.ts
|
|
2082
|
-
import { Status as Status3 } from "@vue-skuilder/common";
|
|
2083
|
-
var init_CouchDBSyncStrategy = __esm({
|
|
2084
|
-
"src/impl/couch/CouchDBSyncStrategy.ts"() {
|
|
1520
|
+
// src/factory.ts
|
|
1521
|
+
var ENV;
|
|
1522
|
+
var init_factory = __esm({
|
|
1523
|
+
"src/factory.ts"() {
|
|
2085
1524
|
"use strict";
|
|
2086
|
-
init_factory();
|
|
2087
|
-
init_types_legacy();
|
|
2088
|
-
init_logger();
|
|
2089
1525
|
init_common();
|
|
2090
|
-
init_pouchdb_setup();
|
|
2091
|
-
init_couch();
|
|
2092
|
-
init_auth();
|
|
2093
|
-
}
|
|
2094
|
-
});
|
|
2095
|
-
|
|
2096
|
-
// src/impl/couch/index.ts
|
|
2097
|
-
import moment4 from "moment";
|
|
2098
|
-
import process2 from "process";
|
|
2099
|
-
function getCourseDB(courseID) {
|
|
2100
|
-
return new pouchdb_setup_default(
|
|
2101
|
-
ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "coursedb-" + courseID,
|
|
2102
|
-
pouchDBincludeCredentialsConfig
|
|
2103
|
-
);
|
|
2104
|
-
}
|
|
2105
|
-
function getCourseDocs(courseID, docIDs, options = {}) {
|
|
2106
|
-
return getCourseDB(courseID).allDocs({
|
|
2107
|
-
...options,
|
|
2108
|
-
keys: docIDs
|
|
2109
|
-
});
|
|
2110
|
-
}
|
|
2111
|
-
function getCourseDoc(courseID, docID, options = {}) {
|
|
2112
|
-
return getCourseDB(courseID).get(docID, options);
|
|
2113
|
-
}
|
|
2114
|
-
function filterAllDocsByPrefix(db, prefix, opts) {
|
|
2115
|
-
const options = {
|
|
2116
|
-
startkey: prefix,
|
|
2117
|
-
endkey: prefix + "\uFFF0",
|
|
2118
|
-
include_docs: true
|
|
2119
|
-
};
|
|
2120
|
-
if (opts) {
|
|
2121
|
-
Object.assign(options, opts);
|
|
2122
|
-
}
|
|
2123
|
-
return db.allDocs(options);
|
|
2124
|
-
}
|
|
2125
|
-
function getStartAndEndKeys(key) {
|
|
2126
|
-
return {
|
|
2127
|
-
startkey: key,
|
|
2128
|
-
endkey: key + "\uFFF0"
|
|
2129
|
-
};
|
|
2130
|
-
}
|
|
2131
|
-
var isBrowser, GUEST_LOCAL_DB, localUserDB, pouchDBincludeCredentialsConfig, REVIEW_PREFIX2, REVIEW_TIME_FORMAT2;
|
|
2132
|
-
var init_couch = __esm({
|
|
2133
|
-
"src/impl/couch/index.ts"() {
|
|
2134
|
-
"use strict";
|
|
2135
|
-
init_factory();
|
|
2136
|
-
init_types_legacy();
|
|
2137
1526
|
init_logger();
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
init_classroomDB2();
|
|
2142
|
-
init_courseAPI();
|
|
2143
|
-
init_courseDB();
|
|
2144
|
-
init_CouchDBSyncStrategy();
|
|
2145
|
-
isBrowser = typeof window !== "undefined";
|
|
2146
|
-
if (isBrowser) {
|
|
2147
|
-
window.process = process2;
|
|
2148
|
-
}
|
|
2149
|
-
GUEST_LOCAL_DB = `userdb-${GuestUsername}`;
|
|
2150
|
-
localUserDB = new pouchdb_setup_default(GUEST_LOCAL_DB);
|
|
2151
|
-
pouchDBincludeCredentialsConfig = {
|
|
2152
|
-
fetch(url, opts) {
|
|
2153
|
-
opts.credentials = "include";
|
|
2154
|
-
return pouchdb_setup_default.fetch(url, opts);
|
|
2155
|
-
}
|
|
1527
|
+
ENV = {
|
|
1528
|
+
COUCHDB_SERVER_PROTOCOL: "NOT_SET",
|
|
1529
|
+
COUCHDB_SERVER_URL: "NOT_SET"
|
|
2156
1530
|
};
|
|
2157
|
-
REVIEW_PREFIX2 = "card_review_";
|
|
2158
|
-
REVIEW_TIME_FORMAT2 = "YYYY-MM-DD--kk:mm:ss-SSS";
|
|
2159
|
-
}
|
|
2160
|
-
});
|
|
2161
|
-
|
|
2162
|
-
// src/impl/couch/classroomDB.ts
|
|
2163
|
-
import moment5 from "moment";
|
|
2164
|
-
var init_classroomDB2 = __esm({
|
|
2165
|
-
"src/impl/couch/classroomDB.ts"() {
|
|
2166
|
-
"use strict";
|
|
2167
|
-
init_factory();
|
|
2168
|
-
init_logger();
|
|
2169
|
-
init_pouchdb_setup();
|
|
2170
|
-
init_couch();
|
|
2171
|
-
init_courseDB();
|
|
2172
1531
|
}
|
|
2173
1532
|
});
|
|
2174
1533
|
|
|
@@ -2269,11 +1628,11 @@ var init_StaticDataUnpacker = __esm({
|
|
|
2269
1628
|
init_logger();
|
|
2270
1629
|
init_core();
|
|
2271
1630
|
pathUtils = {
|
|
2272
|
-
isAbsolute: (
|
|
2273
|
-
if (/^[a-zA-Z]:[\\/]/.test(
|
|
1631
|
+
isAbsolute: (path2) => {
|
|
1632
|
+
if (/^[a-zA-Z]:[\\/]/.test(path2) || /^\\\\/.test(path2)) {
|
|
2274
1633
|
return true;
|
|
2275
1634
|
}
|
|
2276
|
-
if (
|
|
1635
|
+
if (path2.startsWith("/")) {
|
|
2277
1636
|
return true;
|
|
2278
1637
|
}
|
|
2279
1638
|
return false;
|
|
@@ -2358,18 +1717,21 @@ var init_StaticDataUnpacker = __esm({
|
|
|
2358
1717
|
async getTagsIndex() {
|
|
2359
1718
|
return await this.loadIndex("tags");
|
|
2360
1719
|
}
|
|
1720
|
+
getDocTypeFromId(id) {
|
|
1721
|
+
for (const docTypeKey in DocTypePrefixes) {
|
|
1722
|
+
const prefix = DocTypePrefixes[docTypeKey];
|
|
1723
|
+
if (id.startsWith(`${prefix}-`)) {
|
|
1724
|
+
return docTypeKey;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
return void 0;
|
|
1728
|
+
}
|
|
2361
1729
|
/**
|
|
2362
1730
|
* Find which chunk contains a specific document ID
|
|
2363
1731
|
*/
|
|
2364
1732
|
async findChunkForDocument(docId) {
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
if (docId.startsWith(`${docType}-`)) {
|
|
2368
|
-
expectedDocType = docType;
|
|
2369
|
-
break;
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
if (expectedDocType !== void 0) {
|
|
1733
|
+
const expectedDocType = this.getDocTypeFromId(docId);
|
|
1734
|
+
if (expectedDocType) {
|
|
2373
1735
|
const typeChunks = this.manifest.chunks.filter((c) => c.docType === expectedDocType);
|
|
2374
1736
|
for (const chunk of typeChunks) {
|
|
2375
1737
|
if (docId >= chunk.startKey && docId <= chunk.endKey) {
|
|
@@ -2379,21 +1741,8 @@ var init_StaticDataUnpacker = __esm({
|
|
|
2379
1741
|
}
|
|
2380
1742
|
}
|
|
2381
1743
|
}
|
|
2382
|
-
return void 0;
|
|
2383
1744
|
} else {
|
|
2384
|
-
const
|
|
2385
|
-
(c) => c.docType === "DISPLAYABLE_DATA"
|
|
2386
|
-
);
|
|
2387
|
-
for (const chunk of displayableChunks) {
|
|
2388
|
-
if (docId >= chunk.startKey && docId <= chunk.endKey) {
|
|
2389
|
-
const exists = await this.verifyDocumentInChunk(docId, chunk);
|
|
2390
|
-
if (exists) {
|
|
2391
|
-
return chunk;
|
|
2392
|
-
}
|
|
2393
|
-
}
|
|
2394
|
-
}
|
|
2395
|
-
const cardChunks = this.manifest.chunks.filter((c) => c.docType === "CARD");
|
|
2396
|
-
for (const chunk of cardChunks) {
|
|
1745
|
+
for (const chunk of this.manifest.chunks) {
|
|
2397
1746
|
if (docId >= chunk.startKey && docId <= chunk.endKey) {
|
|
2398
1747
|
const exists = await this.verifyDocumentInChunk(docId, chunk);
|
|
2399
1748
|
if (exists) {
|
|
@@ -2414,6 +1763,7 @@ var init_StaticDataUnpacker = __esm({
|
|
|
2414
1763
|
}
|
|
2415
1764
|
return void 0;
|
|
2416
1765
|
}
|
|
1766
|
+
return void 0;
|
|
2417
1767
|
}
|
|
2418
1768
|
/**
|
|
2419
1769
|
* Verify that a document actually exists in a specific chunk by loading and checking it
|
|
@@ -2657,6 +2007,7 @@ var init_courseDB3 = __esm({
|
|
|
2657
2007
|
"use strict";
|
|
2658
2008
|
init_types_legacy();
|
|
2659
2009
|
init_navigators();
|
|
2010
|
+
init_logger();
|
|
2660
2011
|
StaticCourseDB = class {
|
|
2661
2012
|
constructor(courseId, unpacker, userDB, manifest) {
|
|
2662
2013
|
this.courseId = courseId;
|
|
@@ -2678,10 +2029,11 @@ var init_courseDB3 = __esm({
|
|
|
2678
2029
|
throw new Error("Cannot update course config in static mode");
|
|
2679
2030
|
}
|
|
2680
2031
|
async getCourseInfo() {
|
|
2032
|
+
const cardCount = this.manifest.chunks.filter((chunk) => chunk.docType === "CARD" /* CARD */).reduce((total, chunk) => total + chunk.documentCount, 0);
|
|
2681
2033
|
return {
|
|
2682
|
-
cardCount
|
|
2683
|
-
// Would come from manifest
|
|
2034
|
+
cardCount,
|
|
2684
2035
|
registeredUsers: 0
|
|
2036
|
+
// Always 0 in static mode
|
|
2685
2037
|
};
|
|
2686
2038
|
}
|
|
2687
2039
|
async getCourseDoc(id, _options) {
|
|
@@ -2770,12 +2122,56 @@ var init_courseDB3 = __esm({
|
|
|
2770
2122
|
courseID: this.courseId
|
|
2771
2123
|
}));
|
|
2772
2124
|
}
|
|
2773
|
-
async getAppliedTags(
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
rows
|
|
2778
|
-
|
|
2125
|
+
async getAppliedTags(cardId) {
|
|
2126
|
+
try {
|
|
2127
|
+
const tagsIndex = await this.unpacker.getTagsIndex();
|
|
2128
|
+
const cardTags = tagsIndex.byCard[cardId] || [];
|
|
2129
|
+
const rows = await Promise.all(
|
|
2130
|
+
cardTags.map(async (tagName) => {
|
|
2131
|
+
const tagId = `${"TAG" /* TAG */}-${tagName}`;
|
|
2132
|
+
try {
|
|
2133
|
+
const tagDoc = await this.unpacker.getDocument(tagId);
|
|
2134
|
+
return {
|
|
2135
|
+
id: tagId,
|
|
2136
|
+
key: cardId,
|
|
2137
|
+
value: {
|
|
2138
|
+
name: tagDoc.name,
|
|
2139
|
+
snippet: tagDoc.snippet,
|
|
2140
|
+
count: tagDoc.taggedCards?.length || 0
|
|
2141
|
+
}
|
|
2142
|
+
};
|
|
2143
|
+
} catch (error) {
|
|
2144
|
+
if (error && error.status === 404) {
|
|
2145
|
+
logger.warn(`Tag document not found for ${tagName}, creating stub`);
|
|
2146
|
+
} else {
|
|
2147
|
+
logger.error(`Error getting tag document for ${tagName}:`, error);
|
|
2148
|
+
throw error;
|
|
2149
|
+
}
|
|
2150
|
+
return {
|
|
2151
|
+
id: tagId,
|
|
2152
|
+
key: cardId,
|
|
2153
|
+
value: {
|
|
2154
|
+
name: tagName,
|
|
2155
|
+
snippet: `Tag: ${tagName}`,
|
|
2156
|
+
count: tagsIndex.byTag[tagName]?.length || 0
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
})
|
|
2161
|
+
);
|
|
2162
|
+
return {
|
|
2163
|
+
total_rows: rows.length,
|
|
2164
|
+
offset: 0,
|
|
2165
|
+
rows
|
|
2166
|
+
};
|
|
2167
|
+
} catch (error) {
|
|
2168
|
+
logger.error(`Error getting applied tags for card ${cardId}:`, error);
|
|
2169
|
+
return {
|
|
2170
|
+
total_rows: 0,
|
|
2171
|
+
offset: 0,
|
|
2172
|
+
rows: []
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2779
2175
|
}
|
|
2780
2176
|
async addTagToCard(_cardId, _tagId) {
|
|
2781
2177
|
throw new Error("Cannot modify tags in static mode");
|
|
@@ -2793,11 +2189,69 @@ var init_courseDB3 = __esm({
|
|
|
2793
2189
|
throw new Error("Cannot update tags in static mode");
|
|
2794
2190
|
}
|
|
2795
2191
|
async getCourseTagStubs() {
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2192
|
+
try {
|
|
2193
|
+
const tagsIndex = await this.unpacker.getTagsIndex();
|
|
2194
|
+
if (!tagsIndex || !tagsIndex.byTag) {
|
|
2195
|
+
logger.warn("Tags index not found or empty");
|
|
2196
|
+
return {
|
|
2197
|
+
total_rows: 0,
|
|
2198
|
+
offset: 0,
|
|
2199
|
+
rows: []
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
const tagNames = Object.keys(tagsIndex.byTag);
|
|
2203
|
+
const rows = await Promise.all(
|
|
2204
|
+
tagNames.map(async (tagName) => {
|
|
2205
|
+
const cardIds = tagsIndex.byTag[tagName] || [];
|
|
2206
|
+
const tagId = `${"TAG" /* TAG */}-${tagName}`;
|
|
2207
|
+
try {
|
|
2208
|
+
const tagDoc = await this.unpacker.getDocument(tagId);
|
|
2209
|
+
return {
|
|
2210
|
+
id: tagId,
|
|
2211
|
+
key: tagId,
|
|
2212
|
+
value: { rev: "1-static" },
|
|
2213
|
+
doc: tagDoc
|
|
2214
|
+
};
|
|
2215
|
+
} catch (error) {
|
|
2216
|
+
if (error && error.status === 404) {
|
|
2217
|
+
logger.warn(`Tag document not found for ${tagName}, creating stub`);
|
|
2218
|
+
const stubDoc = {
|
|
2219
|
+
_id: tagId,
|
|
2220
|
+
_rev: "1-static",
|
|
2221
|
+
course: this.courseId,
|
|
2222
|
+
docType: "TAG" /* TAG */,
|
|
2223
|
+
name: tagName,
|
|
2224
|
+
snippet: `Tag: ${tagName}`,
|
|
2225
|
+
wiki: "",
|
|
2226
|
+
taggedCards: cardIds,
|
|
2227
|
+
author: "system"
|
|
2228
|
+
};
|
|
2229
|
+
return {
|
|
2230
|
+
id: tagId,
|
|
2231
|
+
key: tagId,
|
|
2232
|
+
value: { rev: "1-static" },
|
|
2233
|
+
doc: stubDoc
|
|
2234
|
+
};
|
|
2235
|
+
} else {
|
|
2236
|
+
logger.error(`Error getting tag document for ${tagName}:`, error);
|
|
2237
|
+
throw error;
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
})
|
|
2241
|
+
);
|
|
2242
|
+
return {
|
|
2243
|
+
total_rows: rows.length,
|
|
2244
|
+
offset: 0,
|
|
2245
|
+
rows
|
|
2246
|
+
};
|
|
2247
|
+
} catch (error) {
|
|
2248
|
+
logger.error("Failed to get course tag stubs:", error);
|
|
2249
|
+
return {
|
|
2250
|
+
total_rows: 0,
|
|
2251
|
+
offset: 0,
|
|
2252
|
+
rows: []
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2801
2255
|
}
|
|
2802
2256
|
async addNote(_codeCourse, _shape, _data, _author, _tags, _uploads, _elo) {
|
|
2803
2257
|
return {
|
|
@@ -2903,6 +2357,9 @@ var init_NoOpSyncStrategy = __esm({
|
|
|
2903
2357
|
setupRemoteDB(username) {
|
|
2904
2358
|
return getLocalUserDB(username);
|
|
2905
2359
|
}
|
|
2360
|
+
getWriteDB(username) {
|
|
2361
|
+
return getLocalUserDB(username);
|
|
2362
|
+
}
|
|
2906
2363
|
startSync(_localDB, _remoteDB) {
|
|
2907
2364
|
}
|
|
2908
2365
|
stopSync() {
|