@vue-skuilder/db 0.1.4 → 0.1.5
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 +74 -0
- package/dist/SyncStrategy-DnJRj-Xp.d.ts +74 -0
- package/dist/core/index.d.mts +90 -2
- package/dist/core/index.d.ts +90 -2
- package/dist/core/index.js +798 -650
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +769 -621
- package/dist/core/index.mjs.map +1 -1
- package/dist/dataLayerProvider-B8wquRiB.d.mts +37 -0
- package/dist/dataLayerProvider-DRjMZMaf.d.ts +37 -0
- package/dist/impl/couch/index.d.mts +292 -0
- package/dist/impl/couch/index.d.ts +292 -0
- package/dist/impl/couch/index.js +8522 -0
- package/dist/impl/couch/index.js.map +1 -0
- package/dist/impl/couch/index.mjs +8474 -0
- package/dist/impl/couch/index.mjs.map +1 -0
- package/dist/impl/static/index.d.mts +204 -0
- package/dist/impl/static/index.d.ts +204 -0
- package/dist/impl/static/index.js +8499 -0
- package/dist/impl/static/index.js.map +1 -0
- package/dist/impl/static/index.mjs +8488 -0
- package/dist/impl/static/index.mjs.map +1 -0
- package/dist/index.d.mts +13 -4
- package/dist/index.d.ts +13 -4
- package/dist/index.js +3280 -2108
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3290 -2118
- package/dist/index.mjs.map +1 -1
- package/dist/types-B0GJsjOr.d.ts +47 -0
- package/dist/types-DIgj8pP7.d.mts +47 -0
- package/dist/types-legacy-CTsJvvxI.d.mts +137 -0
- package/dist/types-legacy-CTsJvvxI.d.ts +137 -0
- package/dist/{index-QMtzQI65.d.mts → userDB-C5dcuRZs.d.ts} +3 -251
- package/dist/{index-QMtzQI65.d.ts → userDB-ZSwOXiYN.d.mts} +3 -251
- package/dist/util/packer/index.d.mts +25 -0
- package/dist/util/packer/index.d.ts +25 -0
- package/dist/util/packer/index.js +307 -0
- package/dist/util/packer/index.js.map +1 -0
- package/dist/util/packer/index.mjs +280 -0
- package/dist/util/packer/index.mjs.map +1 -0
- package/package.json +12 -2
- package/src/core/interfaces/contentSource.ts +8 -6
- package/src/core/interfaces/userDB.ts +2 -2
- package/src/factory.ts +10 -7
- package/src/impl/{pouch/userDB.ts → common/BaseUserDB.ts} +225 -260
- package/src/impl/common/SyncStrategy.ts +90 -0
- package/src/impl/common/index.ts +23 -0
- package/src/impl/common/types.ts +50 -0
- package/src/impl/common/userDBHelpers.ts +144 -0
- package/src/impl/couch/CouchDBSyncStrategy.ts +209 -0
- package/src/impl/{pouch → couch}/PouchDataLayerProvider.ts +12 -7
- package/src/impl/{pouch → couch}/adminDB.ts +3 -3
- package/src/impl/{pouch → couch}/auth.ts +2 -2
- package/src/impl/{pouch → couch}/classroomDB.ts +6 -6
- package/src/impl/{pouch → couch}/courseAPI.ts +28 -5
- package/src/impl/{pouch → couch}/courseDB.ts +24 -10
- package/src/impl/{pouch → couch}/courseLookupDB.ts +1 -1
- package/src/impl/{pouch → couch}/index.ts +27 -20
- package/src/impl/{pouch → couch}/updateQueue.ts +5 -1
- package/src/impl/{pouch → couch}/user-course-relDB.ts +6 -1
- package/src/impl/static/NoOpSyncStrategy.ts +70 -0
- package/src/impl/static/StaticDataLayerProvider.ts +89 -0
- package/src/impl/static/StaticDataUnpacker.ts +376 -0
- package/src/impl/static/courseDB.ts +257 -0
- package/src/impl/static/coursesDB.ts +37 -0
- package/src/impl/static/index.ts +8 -0
- package/src/impl/static/userDB.ts +179 -0
- package/src/index.ts +1 -1
- package/src/study/SessionController.ts +4 -4
- package/src/study/SpacedRepetition.ts +3 -3
- package/src/study/getCardDataShape.ts +2 -2
- package/src/util/index.ts +1 -0
- package/src/util/packer/CouchDBToStaticPacker.ts +349 -0
- package/src/util/packer/index.ts +4 -0
- package/src/util/packer/types.ts +52 -0
- package/tsconfig.json +7 -10
- package/tsup.config.ts +5 -3
- /package/src/impl/{pouch → couch}/clientCache.ts +0 -0
- /package/src/impl/{pouch → couch}/pouchdb-setup.ts +0 -0
- /package/src/impl/{pouch → couch}/types.ts +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import pouch from './pouchdb-setup';
|
|
2
2
|
import { pouchDBincludeCredentialsConfig } from '.';
|
|
3
|
-
import { ENV } from '
|
|
3
|
+
import { ENV } from '@db/factory';
|
|
4
4
|
// import { DataShape } from '../..base-course/Interfaces/DataShape';
|
|
5
5
|
import { NameSpacer, ShapeDescriptor } from '@vue-skuilder/common';
|
|
6
6
|
import { CourseConfig, DataShape } from '@vue-skuilder/common';
|
|
7
7
|
import { CourseElo, blankCourseElo, toCourseElo } from '@vue-skuilder/common';
|
|
8
|
-
import { CourseDB, createTag
|
|
8
|
+
import { CourseDB, createTag } from './courseDB';
|
|
9
9
|
import { CardData, DisplayableData, DocType, Tag } from '../../core/types/types-legacy';
|
|
10
10
|
import { prepareNote55 } from '@vue-skuilder/common';
|
|
11
|
-
import {
|
|
12
|
-
import { logger } from '
|
|
11
|
+
import { BaseUser } from '../common';
|
|
12
|
+
import { logger } from '@db/util/logger';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
*
|
|
@@ -33,6 +33,8 @@ export async function addNote55(
|
|
|
33
33
|
): Promise<PouchDB.Core.Response> {
|
|
34
34
|
const db = getCourseDB(courseID);
|
|
35
35
|
const payload = prepareNote55(courseID, codeCourse, shape, data, author, tags, uploads);
|
|
36
|
+
// [ ] NAMESPACING: consider put( _id: "displayable_data-uuid")
|
|
37
|
+
// consider also semantic hashing
|
|
36
38
|
const result = await db.post<DisplayableData>(payload);
|
|
37
39
|
|
|
38
40
|
const dataShapeId = NameSpacer.getDataShapeString({
|
|
@@ -139,6 +141,7 @@ async function addCard(
|
|
|
139
141
|
elo: CourseElo,
|
|
140
142
|
tags: string[]
|
|
141
143
|
): Promise<PouchDB.Core.Response> {
|
|
144
|
+
// [ ] NAMESPACING: consider put( _id: "card-uuid")
|
|
142
145
|
const card = await getCourseDB(courseID).post<CardData>({
|
|
143
146
|
course,
|
|
144
147
|
id_displayable_data,
|
|
@@ -187,7 +190,16 @@ export async function addTagToCard(
|
|
|
187
190
|
// In this case, should be converted to a server-request
|
|
188
191
|
const prefixedTagID = getTagID(tagID);
|
|
189
192
|
const courseDB = getCourseDB(courseID);
|
|
190
|
-
const courseApi = new CourseDB(courseID, async () =>
|
|
193
|
+
const courseApi = new CourseDB(courseID, async () => {
|
|
194
|
+
const dummySyncStrategy = {
|
|
195
|
+
setupRemoteDB: () => null as any,
|
|
196
|
+
startSync: () => {},
|
|
197
|
+
canCreateAccount: () => false,
|
|
198
|
+
canAuthenticate: () => false,
|
|
199
|
+
getCurrentUsername: async () => 'DummyUser',
|
|
200
|
+
};
|
|
201
|
+
return BaseUser.Dummy(dummySyncStrategy);
|
|
202
|
+
});
|
|
191
203
|
try {
|
|
192
204
|
logger.info(`Applying tag ${tagID} to card ${courseID + '-' + cardID}...`);
|
|
193
205
|
const tag = await courseDB.get<Tag>(prefixedTagID);
|
|
@@ -220,6 +232,17 @@ export async function addTagToCard(
|
|
|
220
232
|
}
|
|
221
233
|
}
|
|
222
234
|
|
|
235
|
+
async function updateCardElo(courseID: string, cardID: string, elo: CourseElo) {
|
|
236
|
+
if (elo) {
|
|
237
|
+
// checking against null, undefined, NaN
|
|
238
|
+
const cDB = getCourseDB(courseID);
|
|
239
|
+
const card = await cDB.get<CardData>(cardID);
|
|
240
|
+
logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);
|
|
241
|
+
card.elo = elo;
|
|
242
|
+
return cDB.put(card); // race conditions - is it important? probably not (net-zero effect)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
223
246
|
class AlreadyTaggedErr extends Error {
|
|
224
247
|
constructor(message: string) {
|
|
225
248
|
super(message);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CourseDBInterface, CourseInfo, CoursesDBInterface, UserDBInterface } from '
|
|
2
|
-
import { ScheduledCard } from '
|
|
1
|
+
import { CourseDBInterface, CourseInfo, CoursesDBInterface, UserDBInterface } from '@db/core';
|
|
2
|
+
import { ScheduledCard } from '@db/core/types/user';
|
|
3
3
|
import {
|
|
4
4
|
CourseConfig,
|
|
5
5
|
CourseElo,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from '@vue-skuilder/common';
|
|
12
12
|
import _ from 'lodash';
|
|
13
13
|
import { filterAllDocsByPrefix, getCourseDB, getCourseDoc, getCourseDocs } from '.';
|
|
14
|
+
import UpdateQueue from './updateQueue';
|
|
14
15
|
import {
|
|
15
16
|
StudyContentSource,
|
|
16
17
|
StudySessionItem,
|
|
@@ -21,11 +22,11 @@ import { CardData, DocType, SkuilderCourseData, Tag, TagStub } from '../../core/
|
|
|
21
22
|
import { logger } from '../../util/logger';
|
|
22
23
|
import { GET_CACHED } from './clientCache';
|
|
23
24
|
import { addNote55, addTagToCard, getCredentialledCourseConfig, getTagID } from './courseAPI';
|
|
24
|
-
import { DataLayerResult } from '
|
|
25
|
+
import { DataLayerResult } from '@db/core/types/db';
|
|
25
26
|
import { PouchError } from './types';
|
|
26
27
|
import CourseLookup from './courseLookupDB';
|
|
27
|
-
import { ContentNavigationStrategyData } from '
|
|
28
|
-
import { ContentNavigator, Navigators } from '
|
|
28
|
+
import { ContentNavigationStrategyData } from '@db/core/types/contentNavigationStrategy';
|
|
29
|
+
import { ContentNavigator, Navigators } from '@db/core/navigators';
|
|
29
30
|
|
|
30
31
|
export class CoursesDB implements CoursesDBInterface {
|
|
31
32
|
_courseIDs: string[] | undefined;
|
|
@@ -92,11 +93,13 @@ export class CourseDB implements StudyContentSource, CourseDBInterface {
|
|
|
92
93
|
private db: PouchDB.Database;
|
|
93
94
|
private id: string;
|
|
94
95
|
private _getCurrentUser: () => Promise<UserDBInterface>;
|
|
96
|
+
private updateQueue: UpdateQueue;
|
|
95
97
|
|
|
96
98
|
constructor(id: string, userLookup: () => Promise<UserDBInterface>) {
|
|
97
99
|
this.id = id;
|
|
98
100
|
this.db = getCourseDB(this.id);
|
|
99
101
|
this._getCurrentUser = userLookup;
|
|
102
|
+
this.updateQueue = new UpdateQueue(this.db);
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
public getCourseID(): string {
|
|
@@ -305,11 +308,22 @@ above:\n${above.rows.map((r) => `\t${r.id}-${r.key}\n`)}`;
|
|
|
305
308
|
}
|
|
306
309
|
}
|
|
307
310
|
|
|
308
|
-
async updateCardElo(cardId: string, elo: CourseElo) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
async updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response> {
|
|
312
|
+
if (!elo) {
|
|
313
|
+
throw new Error(`Cannot update card elo with null or undefined value for card ID: ${cardId}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
const result = await this.updateQueue.update<
|
|
318
|
+
CardData & PouchDB.Core.GetMeta & PouchDB.Core.IdMeta
|
|
319
|
+
>(cardId, (card) => {
|
|
320
|
+
logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);
|
|
321
|
+
card.elo = elo;
|
|
322
|
+
return card;
|
|
323
|
+
});
|
|
324
|
+
return { ok: true, id: cardId, rev: result._rev };
|
|
325
|
+
} catch (error) {
|
|
326
|
+
logger.error(`Failed to update card elo for card ID: ${cardId}`, error);
|
|
313
327
|
throw new Error(`Failed to update card elo for card ID: ${cardId}`);
|
|
314
328
|
}
|
|
315
329
|
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { ENV } from '
|
|
1
|
+
import { ENV } from '@db/factory';
|
|
2
2
|
import { DocType, GuestUsername, log, SkuilderCourseData } from '../../core/types/types-legacy';
|
|
3
3
|
// import { getCurrentUser } from '../../stores/useAuthStore';
|
|
4
4
|
import moment, { Moment } from 'moment';
|
|
5
|
-
import { logger } from '
|
|
5
|
+
import { logger } from '@db/util/logger';
|
|
6
6
|
|
|
7
7
|
import pouch from './pouchdb-setup';
|
|
8
8
|
|
|
9
|
-
import { ScheduledCard } from '
|
|
9
|
+
import { ScheduledCard } from '@db/core/types/user';
|
|
10
10
|
import process from 'process';
|
|
11
|
-
import { getUserDB } from './userDB';
|
|
12
11
|
|
|
13
12
|
const isBrowser = typeof window !== 'undefined';
|
|
14
13
|
|
|
@@ -159,6 +158,28 @@ export async function getRandomCards(courseIDs: string[]) {
|
|
|
159
158
|
export const REVIEW_PREFIX: string = 'card_review_';
|
|
160
159
|
export const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';
|
|
161
160
|
|
|
161
|
+
export function getCouchUserDB(username: string): PouchDB.Database {
|
|
162
|
+
const guestAccount: boolean = false;
|
|
163
|
+
// console.log(`Getting user db: ${username}`);
|
|
164
|
+
|
|
165
|
+
const hexName = hexEncode(username);
|
|
166
|
+
const dbName = `userdb-${hexName}`;
|
|
167
|
+
log(`Fetching user database: ${dbName} (${username})`);
|
|
168
|
+
|
|
169
|
+
// odd construction here the result of a bug in the
|
|
170
|
+
// interaction between pouch, pouch-auth.
|
|
171
|
+
// see: https://github.com/pouchdb-community/pouchdb-authentication/issues/239
|
|
172
|
+
const ret = new pouch(
|
|
173
|
+
ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,
|
|
174
|
+
pouchDBincludeCredentialsConfig
|
|
175
|
+
);
|
|
176
|
+
if (guestAccount) {
|
|
177
|
+
updateGuestAccountExpirationDate(ret);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return ret;
|
|
181
|
+
}
|
|
182
|
+
|
|
162
183
|
export function scheduleCardReview(review: {
|
|
163
184
|
user: string;
|
|
164
185
|
course_id: string;
|
|
@@ -169,7 +190,7 @@ export function scheduleCardReview(review: {
|
|
|
169
190
|
}) {
|
|
170
191
|
const now = moment.utc();
|
|
171
192
|
logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);
|
|
172
|
-
void
|
|
193
|
+
void getCouchUserDB(review.user).put<ScheduledCard>({
|
|
173
194
|
_id: REVIEW_PREFIX + review.time.format(REVIEW_TIME_FORMAT),
|
|
174
195
|
cardId: review.card_id,
|
|
175
196
|
reviewTime: review.time,
|
|
@@ -180,20 +201,6 @@ export function scheduleCardReview(review: {
|
|
|
180
201
|
});
|
|
181
202
|
}
|
|
182
203
|
|
|
183
|
-
export async function removeScheduledCardReview(user: string, reviewDocID: string) {
|
|
184
|
-
const db = getUserDB(user);
|
|
185
|
-
const reviewDoc = await db.get(reviewDocID);
|
|
186
|
-
db.remove(reviewDoc)
|
|
187
|
-
.then((res) => {
|
|
188
|
-
if (res.ok) {
|
|
189
|
-
log(`Removed Review Doc: ${reviewDocID}`);
|
|
190
|
-
}
|
|
191
|
-
})
|
|
192
|
-
.catch((err) => {
|
|
193
|
-
log(`Failed to remove Review Doc: ${reviewDocID},\n${JSON.stringify(err)}`);
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
|
|
197
204
|
export function filterAllDocsByPrefix<T>(
|
|
198
205
|
db: PouchDB.Database,
|
|
199
206
|
prefix: string,
|
|
@@ -232,4 +239,4 @@ export * from './adminDB';
|
|
|
232
239
|
export * from './classroomDB';
|
|
233
240
|
export * from './courseAPI';
|
|
234
241
|
export * from './courseDB';
|
|
235
|
-
export * from './
|
|
242
|
+
export * from './CouchDBSyncStrategy';
|
|
@@ -77,9 +77,13 @@ export default class UpdateQueue extends Loggable {
|
|
|
77
77
|
}
|
|
78
78
|
return doc;
|
|
79
79
|
} catch (e) {
|
|
80
|
+
// Clean up queue state before re-throwing
|
|
80
81
|
delete this.inprogressUpdates[id];
|
|
82
|
+
if (this.pendingUpdates[id]) {
|
|
83
|
+
delete this.pendingUpdates[id];
|
|
84
|
+
}
|
|
81
85
|
logger.error(`Error on attemped update: ${JSON.stringify(e)}`);
|
|
82
|
-
throw e;
|
|
86
|
+
throw e; // Let caller handle (e.g., putCardRecord's 404 handling)
|
|
83
87
|
}
|
|
84
88
|
} else {
|
|
85
89
|
throw new Error(`Empty Updates Queue Triggered`);
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ScheduledCard,
|
|
3
|
+
UserCourseSetting,
|
|
4
|
+
UserCourseSettings,
|
|
5
|
+
UsrCrsDataInterface,
|
|
6
|
+
} from '@db/core';
|
|
2
7
|
import moment, { Moment } from 'moment';
|
|
3
8
|
import { getStartAndEndKeys, REVIEW_PREFIX, REVIEW_TIME_FORMAT } from '.';
|
|
4
9
|
import { CourseDB } from './courseDB';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// packages/db/src/impl/static/NoOpSyncStrategy.ts
|
|
2
|
+
|
|
3
|
+
import { GuestUsername } from '../../core/types/types-legacy';
|
|
4
|
+
import type { SyncStrategy } from '../common/SyncStrategy';
|
|
5
|
+
import type { AccountCreationResult, AuthenticationResult } from '../common/types';
|
|
6
|
+
import { getLocalUserDB } from '../common';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Sync strategy for static deployments that provides no-op implementations
|
|
10
|
+
* for remote operations. Uses only local storage with no remote synchronization.
|
|
11
|
+
*/
|
|
12
|
+
export class NoOpSyncStrategy implements SyncStrategy {
|
|
13
|
+
private currentUsername: string = GuestUsername;
|
|
14
|
+
|
|
15
|
+
setupRemoteDB(username: string): PouchDB.Database {
|
|
16
|
+
// Return the same database instance as local - sync with self is a no-op
|
|
17
|
+
return getLocalUserDB(username);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
startSync(_localDB: PouchDB.Database, _remoteDB: PouchDB.Database): void {
|
|
21
|
+
// No-op - in static mode, local and remote are the same database instance
|
|
22
|
+
// PouchDB sync with itself is harmless and efficient
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
stopSync?(): void {
|
|
26
|
+
// No-op - no sync to stop in static mode
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
canCreateAccount(): boolean {
|
|
30
|
+
return false; // Account creation not supported in static mode
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
canAuthenticate(): boolean {
|
|
34
|
+
return false; // Remote authentication not supported in static mode
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async createAccount(_username: string, _password: string): Promise<AccountCreationResult> {
|
|
38
|
+
throw new Error(
|
|
39
|
+
'Account creation not supported in static mode. Use local account switching instead.'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async authenticate(_username: string, _password: string): Promise<AuthenticationResult> {
|
|
44
|
+
throw new Error(
|
|
45
|
+
'Remote authentication not supported in static mode. Use local account switching instead.'
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async logout(): Promise<AuthenticationResult> {
|
|
50
|
+
// In static mode, "logout" means switch back to guest user
|
|
51
|
+
this.currentUsername = GuestUsername;
|
|
52
|
+
return {
|
|
53
|
+
ok: true,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async getCurrentUsername(): Promise<string> {
|
|
58
|
+
// In static mode, always return guest username
|
|
59
|
+
// TODO: This will be enhanced with local account switching to support multiple local users
|
|
60
|
+
return this.currentUsername;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Set the current username (for local account switching)
|
|
65
|
+
* This method is specific to NoOpSyncStrategy and not part of the base interface
|
|
66
|
+
*/
|
|
67
|
+
setCurrentUsername(username: string): void {
|
|
68
|
+
this.currentUsername = username;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// packages/db/src/impl/static/StaticDataLayerProvider.ts
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AdminDBInterface,
|
|
5
|
+
ClassroomDBInterface,
|
|
6
|
+
CoursesDBInterface,
|
|
7
|
+
CourseDBInterface,
|
|
8
|
+
DataLayerProvider,
|
|
9
|
+
UserDBInterface,
|
|
10
|
+
} from '../../core/interfaces';
|
|
11
|
+
import { logger } from '../../util/logger';
|
|
12
|
+
import { StaticCourseManifest } from '../../util/packer/types';
|
|
13
|
+
import { StaticDataUnpacker } from './StaticDataUnpacker';
|
|
14
|
+
import { StaticCourseDB } from './courseDB';
|
|
15
|
+
import { StaticCoursesDB } from './coursesDB';
|
|
16
|
+
import { BaseUser } from '../common';
|
|
17
|
+
import { NoOpSyncStrategy } from './NoOpSyncStrategy';
|
|
18
|
+
|
|
19
|
+
interface StaticDataLayerConfig {
|
|
20
|
+
staticContentPath: string;
|
|
21
|
+
localStoragePrefix?: string;
|
|
22
|
+
manifests: Record<string, StaticCourseManifest>; // courseId -> manifest
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class StaticDataLayerProvider implements DataLayerProvider {
|
|
26
|
+
private config: StaticDataLayerConfig;
|
|
27
|
+
private initialized: boolean = false;
|
|
28
|
+
private courseUnpackers: Map<string, StaticDataUnpacker> = new Map();
|
|
29
|
+
|
|
30
|
+
constructor(config: Partial<StaticDataLayerConfig>) {
|
|
31
|
+
this.config = {
|
|
32
|
+
staticContentPath: config.staticContentPath || '/static-courses',
|
|
33
|
+
localStoragePrefix: config.localStoragePrefix || 'skuilder-static',
|
|
34
|
+
manifests: config.manifests || {},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async initialize(): Promise<void> {
|
|
39
|
+
if (this.initialized) return;
|
|
40
|
+
|
|
41
|
+
logger.info('Initializing static data layer provider');
|
|
42
|
+
|
|
43
|
+
// Load manifests for all courses
|
|
44
|
+
for (const [courseId, manifest] of Object.entries(this.config.manifests)) {
|
|
45
|
+
const unpacker = new StaticDataUnpacker(
|
|
46
|
+
manifest,
|
|
47
|
+
`${this.config.staticContentPath}/${courseId}`
|
|
48
|
+
);
|
|
49
|
+
this.courseUnpackers.set(courseId, unpacker);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.initialized = true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async teardown(): Promise<void> {
|
|
56
|
+
this.courseUnpackers.clear();
|
|
57
|
+
this.initialized = false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getUserDB(): UserDBInterface {
|
|
61
|
+
const syncStrategy = new NoOpSyncStrategy();
|
|
62
|
+
// For now, use guest user - local account switching will be added later
|
|
63
|
+
return BaseUser.Dummy(syncStrategy);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getCourseDB(courseId: string): CourseDBInterface {
|
|
67
|
+
const unpacker = this.courseUnpackers.get(courseId);
|
|
68
|
+
if (!unpacker) {
|
|
69
|
+
throw new Error(`Course ${courseId} not found in static data`);
|
|
70
|
+
}
|
|
71
|
+
const manifest = this.config.manifests[courseId];
|
|
72
|
+
return new StaticCourseDB(courseId, unpacker, this.getUserDB(), manifest);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getCoursesDB(): CoursesDBInterface {
|
|
76
|
+
return new StaticCoursesDB(this.config.manifests);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async getClassroomDB(
|
|
80
|
+
_classId: string,
|
|
81
|
+
_type: 'student' | 'teacher'
|
|
82
|
+
): Promise<ClassroomDBInterface> {
|
|
83
|
+
throw new Error('Classrooms not supported in static mode');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getAdminDB(): AdminDBInterface {
|
|
87
|
+
throw new Error('Admin functions not supported in static mode');
|
|
88
|
+
}
|
|
89
|
+
}
|