@vue-skuilder/db 0.1.11-7 → 0.1.11
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/core/index.d.mts +5 -5
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +212 -50
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +212 -50
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-DqtNroSh.d.ts → dataLayerProvider-VieuAAkV.d.mts} +8 -1
- package/dist/{dataLayerProvider-BInqI_RF.d.mts → dataLayerProvider-juuqUHOP.d.ts} +8 -1
- package/dist/impl/couch/index.d.mts +19 -7
- package/dist/impl/couch/index.d.ts +19 -7
- package/dist/impl/couch/index.js +229 -63
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +228 -62
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.mts +13 -6
- package/dist/impl/static/index.d.ts +13 -6
- package/dist/impl/static/index.js +142 -46
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +142 -46
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/{index-CLL31bEy.d.ts → index-CWY6yhkV.d.ts} +1 -1
- package/dist/{index-CUNnL38E.d.mts → index-DZyxHCcf.d.mts} +1 -1
- package/dist/index.d.mts +28 -20
- package/dist/index.d.ts +28 -20
- package/dist/index.js +374 -108
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +378 -108
- package/dist/index.mjs.map +1 -1
- package/dist/pouch/index.d.mts +1 -0
- package/dist/pouch/index.d.ts +1 -0
- package/dist/pouch/index.js +49 -0
- package/dist/pouch/index.js.map +1 -0
- package/dist/pouch/index.mjs +16 -0
- package/dist/pouch/index.mjs.map +1 -0
- package/dist/{types-DC-ckZug.d.mts → types-Che4wTwA.d.mts} +1 -1
- package/dist/{types-BefDGkKa.d.ts → types-DtoI27Xh.d.ts} +1 -1
- package/dist/{types-legacy-Birv-Jx6.d.mts → types-legacy-B8ahaCbj.d.mts} +5 -1
- package/dist/{types-legacy-Birv-Jx6.d.ts → types-legacy-B8ahaCbj.d.ts} +5 -1
- package/dist/{userDB-C33Hzjgn.d.mts → userDB-B7zTQ123.d.ts} +88 -55
- package/dist/{userDB-DusL7OXe.d.ts → userDB-DJ8HMw83.d.mts} +88 -55
- package/dist/util/packer/index.d.mts +3 -3
- package/dist/util/packer/index.d.ts +3 -3
- package/package.json +3 -3
- package/src/core/interfaces/contentSource.ts +3 -2
- package/src/core/interfaces/courseDB.ts +26 -3
- package/src/core/interfaces/dataLayerProvider.ts +9 -1
- package/src/core/interfaces/userDB.ts +80 -64
- package/src/core/navigators/elo.ts +10 -7
- package/src/core/navigators/index.ts +1 -1
- package/src/core/types/types-legacy.ts +5 -0
- package/src/impl/common/BaseUserDB.ts +45 -3
- package/src/impl/couch/CouchDBSyncStrategy.ts +2 -2
- package/src/impl/couch/PouchDataLayerProvider.ts +21 -0
- package/src/impl/couch/adminDB.ts +2 -2
- package/src/impl/couch/auth.ts +13 -4
- package/src/impl/couch/classroomDB.ts +10 -12
- package/src/impl/couch/courseAPI.ts +2 -2
- package/src/impl/couch/courseDB.ts +130 -11
- package/src/impl/couch/courseLookupDB.ts +4 -3
- package/src/impl/couch/index.ts +36 -4
- package/src/impl/couch/pouchdb-setup.ts +3 -3
- package/src/impl/couch/updateQueue.ts +51 -33
- package/src/impl/static/StaticDataLayerProvider.ts +11 -0
- package/src/impl/static/courseDB.ts +47 -8
- package/src/pouch/index.ts +2 -0
- package/src/study/SessionController.ts +168 -51
- package/src/study/SpacedRepetition.ts +1 -1
- package/tsup.config.ts +1 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CourseConfig, CourseElo, DataShape, SkuilderCourseData } from '@vue-skuilder/common';
|
|
2
2
|
import { StudySessionNewItem, StudySessionItem } from './contentSource';
|
|
3
|
-
import { TagStub, Tag } from '../types/types-legacy';
|
|
3
|
+
import { TagStub, Tag, QualifiedCardID } from '../types/types-legacy';
|
|
4
4
|
import { DataLayerResult } from '../types/db';
|
|
5
5
|
import { NavigationStrategyManager } from './navigationStrategyManager';
|
|
6
6
|
|
|
@@ -53,7 +53,16 @@ export interface CourseDBInterface extends NavigationStrategyManager {
|
|
|
53
53
|
/**
|
|
54
54
|
* Get cards sorted by ELO rating
|
|
55
55
|
*/
|
|
56
|
-
getCardsByELO(
|
|
56
|
+
getCardsByELO(
|
|
57
|
+
elo: number,
|
|
58
|
+
limit?: number
|
|
59
|
+
): Promise<
|
|
60
|
+
{
|
|
61
|
+
courseID: string;
|
|
62
|
+
cardID: string;
|
|
63
|
+
elo?: number;
|
|
64
|
+
}[]
|
|
65
|
+
>;
|
|
57
66
|
|
|
58
67
|
/**
|
|
59
68
|
* Get ELO data for specific cards
|
|
@@ -75,7 +84,7 @@ export interface CourseDBInterface extends NavigationStrategyManager {
|
|
|
75
84
|
*/
|
|
76
85
|
getCardsCenteredAtELO(
|
|
77
86
|
options: { limit: number; elo: 'user' | 'random' | number },
|
|
78
|
-
filter?: (
|
|
87
|
+
filter?: (card: QualifiedCardID) => boolean
|
|
79
88
|
): Promise<StudySessionItem[]>;
|
|
80
89
|
|
|
81
90
|
/**
|
|
@@ -136,4 +145,18 @@ export interface CourseDBInterface extends NavigationStrategyManager {
|
|
|
136
145
|
elo: CourseElo;
|
|
137
146
|
}[]
|
|
138
147
|
>;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Search for cards by text content
|
|
151
|
+
* @param query Text to search for
|
|
152
|
+
* @returns Array of matching card data
|
|
153
|
+
*/
|
|
154
|
+
searchCards(query: string): Promise<any[]>;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Find documents using PouchDB query syntax
|
|
158
|
+
* @param request PouchDB find request
|
|
159
|
+
* @returns Query response
|
|
160
|
+
*/
|
|
161
|
+
find(request: PouchDB.Find.FindRequest<any>): Promise<PouchDB.Find.FindResponse<any>>;
|
|
139
162
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// db/src/core/interfaces.ts
|
|
2
2
|
|
|
3
|
-
import { UserDBInterface } from './userDB';
|
|
3
|
+
import { UserDBInterface, UserDBReader } from './userDB';
|
|
4
4
|
import { CourseDBInterface, CoursesDBInterface } from './courseDB';
|
|
5
5
|
import { ClassroomDBInterface } from './classroomDB';
|
|
6
6
|
import { AdminDBInterface } from './adminDB';
|
|
@@ -14,6 +14,14 @@ export interface DataLayerProvider {
|
|
|
14
14
|
*/
|
|
15
15
|
getUserDB(): UserDBInterface;
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Create a UserDBReader for a specific user (admin access required)
|
|
19
|
+
* Uses session authentication to verify requesting user is admin
|
|
20
|
+
* @param targetUsername - The username to create a reader for
|
|
21
|
+
* @throws Error if requesting user is not 'admin'
|
|
22
|
+
*/
|
|
23
|
+
createUserReaderForUser(targetUsername: string): Promise<UserDBReader>;
|
|
24
|
+
|
|
17
25
|
/**
|
|
18
26
|
* Get a course database interface
|
|
19
27
|
*/
|
|
@@ -6,46 +6,16 @@ import {
|
|
|
6
6
|
} from '@db/core/types/user';
|
|
7
7
|
import { CourseElo, Status } from '@vue-skuilder/common';
|
|
8
8
|
import { Moment } from 'moment';
|
|
9
|
-
import { CardHistory, CardRecord } from '../types/types-legacy';
|
|
9
|
+
import { CardHistory, CardRecord, QualifiedCardID } from '../types/types-legacy';
|
|
10
10
|
import { UserConfig } from '../types/user';
|
|
11
11
|
import { DocumentUpdater } from '@db/study';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* Read-only user data operations
|
|
15
15
|
*/
|
|
16
|
-
export interface
|
|
17
|
-
|
|
18
|
-
* Create a new user account
|
|
19
|
-
*/
|
|
20
|
-
createAccount(
|
|
21
|
-
username: string,
|
|
22
|
-
password: string
|
|
23
|
-
): Promise<{
|
|
24
|
-
status: Status;
|
|
25
|
-
error: string;
|
|
26
|
-
}>;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Log in as a user
|
|
30
|
-
*/
|
|
31
|
-
login(
|
|
32
|
-
username: string,
|
|
33
|
-
password: string
|
|
34
|
-
): Promise<{
|
|
35
|
-
ok: boolean;
|
|
36
|
-
name?: string;
|
|
37
|
-
roles?: string[];
|
|
38
|
-
}>;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Log out the current user
|
|
42
|
-
*/
|
|
43
|
-
logout(): Promise<{
|
|
44
|
-
ok: boolean;
|
|
45
|
-
}>;
|
|
46
|
-
|
|
16
|
+
export interface UserDBReader {
|
|
17
|
+
get<T>(id: string): Promise<T & PouchDB.Core.RevisionIdMeta>;
|
|
47
18
|
getUsername(): string;
|
|
48
|
-
|
|
49
19
|
isLoggedIn(): boolean;
|
|
50
20
|
|
|
51
21
|
/**
|
|
@@ -53,16 +23,6 @@ export interface UserDBInterface extends DocumentUpdater {
|
|
|
53
23
|
*/
|
|
54
24
|
getConfig(): Promise<UserConfig>;
|
|
55
25
|
|
|
56
|
-
/**
|
|
57
|
-
* Update user configuration
|
|
58
|
-
*/
|
|
59
|
-
setConfig(config: Partial<UserConfig>): Promise<void>;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Record a user's interaction with a card
|
|
63
|
-
*/
|
|
64
|
-
putCardRecord<T extends CardRecord>(record: T): Promise<CardHistory<CardRecord>>;
|
|
65
|
-
|
|
66
26
|
/**
|
|
67
27
|
* Get cards that the user has seen
|
|
68
28
|
*/
|
|
@@ -71,17 +31,7 @@ export interface UserDBInterface extends DocumentUpdater {
|
|
|
71
31
|
/**
|
|
72
32
|
* Get cards that are actively scheduled for review
|
|
73
33
|
*/
|
|
74
|
-
getActiveCards(): Promise<
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Register user for a course
|
|
78
|
-
*/
|
|
79
|
-
registerForCourse(courseId: string, previewMode?: boolean): Promise<PouchDB.Core.Response>;
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Drop a course registration
|
|
83
|
-
*/
|
|
84
|
-
dropCourse(courseId: string, dropStatus?: string): Promise<PouchDB.Core.Response>;
|
|
34
|
+
getActiveCards(): Promise<QualifiedCardID[]>;
|
|
85
35
|
|
|
86
36
|
/**
|
|
87
37
|
* Get user's course registrations
|
|
@@ -106,6 +56,43 @@ export interface UserDBInterface extends DocumentUpdater {
|
|
|
106
56
|
|
|
107
57
|
getActivityRecords(): Promise<ActivityRecord[]>;
|
|
108
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Get user's classroom registrations
|
|
61
|
+
*/
|
|
62
|
+
getUserClassrooms(): Promise<ClassroomRegistrationDoc>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get user's active classes
|
|
66
|
+
*/
|
|
67
|
+
getActiveClasses(): Promise<string[]>;
|
|
68
|
+
|
|
69
|
+
getCourseInterface(courseId: string): Promise<UsrCrsDataInterface>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* User data mutation operations
|
|
74
|
+
*/
|
|
75
|
+
export interface UserDBWriter extends DocumentUpdater {
|
|
76
|
+
/**
|
|
77
|
+
* Update user configuration
|
|
78
|
+
*/
|
|
79
|
+
setConfig(config: Partial<UserConfig>): Promise<void>;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Record a user's interaction with a card
|
|
83
|
+
*/
|
|
84
|
+
putCardRecord<T extends CardRecord>(record: T): Promise<CardHistory<CardRecord>>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Register user for a course
|
|
88
|
+
*/
|
|
89
|
+
registerForCourse(courseId: string, previewMode?: boolean): Promise<PouchDB.Core.Response>;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Drop a course registration
|
|
93
|
+
*/
|
|
94
|
+
dropCourse(courseId: string, dropStatus?: string): Promise<PouchDB.Core.Response>;
|
|
95
|
+
|
|
109
96
|
/**
|
|
110
97
|
* Schedule a card for review
|
|
111
98
|
*/
|
|
@@ -137,28 +124,57 @@ export interface UserDBInterface extends DocumentUpdater {
|
|
|
137
124
|
dropFromClassroom(classId: string): Promise<PouchDB.Core.Response>;
|
|
138
125
|
|
|
139
126
|
/**
|
|
140
|
-
*
|
|
127
|
+
* Update user's ELO rating for a course
|
|
141
128
|
*/
|
|
142
|
-
|
|
129
|
+
updateUserElo(courseId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;
|
|
143
130
|
|
|
144
131
|
/**
|
|
145
|
-
*
|
|
132
|
+
* Reset all user data (progress, registrations, etc.) while preserving authentication
|
|
146
133
|
*/
|
|
147
|
-
|
|
134
|
+
resetUserData(): Promise<{ status: Status; error?: string }>;
|
|
135
|
+
}
|
|
148
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Authentication and account management operations
|
|
139
|
+
*/
|
|
140
|
+
export interface UserDBAuthenticator {
|
|
149
141
|
/**
|
|
150
|
-
*
|
|
142
|
+
* Create a new user account
|
|
151
143
|
*/
|
|
152
|
-
|
|
144
|
+
createAccount(
|
|
145
|
+
username: string,
|
|
146
|
+
password: string
|
|
147
|
+
): Promise<{
|
|
148
|
+
status: Status;
|
|
149
|
+
error: string;
|
|
150
|
+
}>;
|
|
153
151
|
|
|
154
152
|
/**
|
|
155
|
-
*
|
|
153
|
+
* Log in as a user
|
|
156
154
|
*/
|
|
157
|
-
|
|
155
|
+
login(
|
|
156
|
+
username: string,
|
|
157
|
+
password: string
|
|
158
|
+
): Promise<{
|
|
159
|
+
ok: boolean;
|
|
160
|
+
name?: string;
|
|
161
|
+
roles?: string[];
|
|
162
|
+
}>;
|
|
158
163
|
|
|
159
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Log out the current user
|
|
166
|
+
*/
|
|
167
|
+
logout(): Promise<{
|
|
168
|
+
ok: boolean;
|
|
169
|
+
}>;
|
|
160
170
|
}
|
|
161
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Complete user database interface - combines all user operations
|
|
174
|
+
* This maintains backward compatibility with existing code
|
|
175
|
+
*/
|
|
176
|
+
export interface UserDBInterface extends UserDBReader, UserDBWriter, UserDBAuthenticator {}
|
|
177
|
+
|
|
162
178
|
export interface UserCourseSettings {
|
|
163
179
|
[setting: string]: string | number | boolean;
|
|
164
180
|
}
|
|
@@ -3,7 +3,7 @@ import { CourseDBInterface } from '../interfaces/courseDB';
|
|
|
3
3
|
import { UserDBInterface } from '../interfaces/userDB';
|
|
4
4
|
import { ContentNavigator } from './index';
|
|
5
5
|
import { CourseElo } from '@vue-skuilder/common';
|
|
6
|
-
import { StudySessionReviewItem, StudySessionNewItem } from '..';
|
|
6
|
+
import { StudySessionReviewItem, StudySessionNewItem, QualifiedCardID } from '..';
|
|
7
7
|
|
|
8
8
|
export default class ELONavigator extends ContentNavigator {
|
|
9
9
|
user: UserDBInterface;
|
|
@@ -59,13 +59,16 @@ export default class ELONavigator extends ContentNavigator {
|
|
|
59
59
|
async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {
|
|
60
60
|
const activeCards = await this.user.getActiveCards();
|
|
61
61
|
return (
|
|
62
|
-
await this.course.getCardsCenteredAtELO(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
await this.course.getCardsCenteredAtELO(
|
|
63
|
+
{ limit: limit, elo: 'user' },
|
|
64
|
+
(c: QualifiedCardID) => {
|
|
65
|
+
if (activeCards.some((ac) => c.cardID === ac.cardID)) {
|
|
66
|
+
return false;
|
|
67
|
+
} else {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
67
70
|
}
|
|
68
|
-
|
|
71
|
+
)
|
|
69
72
|
).map((c) => {
|
|
70
73
|
return {
|
|
71
74
|
...c,
|
|
@@ -32,7 +32,7 @@ export abstract class ContentNavigator implements StudyContentSource {
|
|
|
32
32
|
let NavigatorImpl;
|
|
33
33
|
|
|
34
34
|
// Try different extension variations
|
|
35
|
-
const variations = ['', '.js', '
|
|
35
|
+
const variations = ['.ts', '.js', ''];
|
|
36
36
|
|
|
37
37
|
for (const ext of variations) {
|
|
38
38
|
try {
|
|
@@ -21,6 +21,11 @@ export enum DocType {
|
|
|
21
21
|
NAVIGATION_STRATEGY = 'NAVIGATION_STRATEGY',
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export interface QualifiedCardID {
|
|
25
|
+
courseID: string;
|
|
26
|
+
cardID: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
/**
|
|
25
30
|
* Interface for all data on course content and pedagogy stored
|
|
26
31
|
* in the c/pouch database.
|
|
@@ -217,6 +217,10 @@ Currently logged-in as ${this._username}.`
|
|
|
217
217
|
return ret;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
public async get<T>(id: string): Promise<T & PouchDB.Core.RevisionIdMeta> {
|
|
221
|
+
return this.localDB.get<T>(id);
|
|
222
|
+
}
|
|
223
|
+
|
|
220
224
|
public update<T extends PouchDB.Core.Document<object>>(id: string, update: Update<T>) {
|
|
221
225
|
return this.updateQueue.update(id, update);
|
|
222
226
|
}
|
|
@@ -273,7 +277,12 @@ Currently logged-in as ${this._username}.`
|
|
|
273
277
|
include_docs: true,
|
|
274
278
|
});
|
|
275
279
|
|
|
276
|
-
return reviews.rows.map((r) =>
|
|
280
|
+
return reviews.rows.map((r) => {
|
|
281
|
+
return {
|
|
282
|
+
courseID: r.doc!.courseId,
|
|
283
|
+
cardID: r.doc!.cardId,
|
|
284
|
+
};
|
|
285
|
+
});
|
|
277
286
|
}
|
|
278
287
|
|
|
279
288
|
public async getActivityRecords(): Promise<ActivityRecord[]> {
|
|
@@ -620,8 +629,18 @@ Currently logged-in as ${this._username}.`
|
|
|
620
629
|
this.setDBandQ();
|
|
621
630
|
|
|
622
631
|
this.syncStrategy.startSync(this.localDB, this.remoteDB);
|
|
623
|
-
|
|
624
|
-
|
|
632
|
+
this.applyDesignDocs().catch((error) => {
|
|
633
|
+
log(`Error in applyDesignDocs background task: ${error}`);
|
|
634
|
+
if (error && typeof error === 'object') {
|
|
635
|
+
log(`Full error details in applyDesignDocs: ${JSON.stringify(error)}`);
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
this.deduplicateReviews().catch((error) => {
|
|
639
|
+
log(`Error in deduplicateReviews background task: ${error}`);
|
|
640
|
+
if (error && typeof error === 'object') {
|
|
641
|
+
log(`Full error details in background task: ${JSON.stringify(error)}`);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
625
644
|
BaseUser._initialized = true;
|
|
626
645
|
}
|
|
627
646
|
|
|
@@ -641,12 +660,18 @@ Currently logged-in as ${this._username}.`
|
|
|
641
660
|
];
|
|
642
661
|
|
|
643
662
|
private async applyDesignDocs() {
|
|
663
|
+
log(`Starting applyDesignDocs for user: ${this._username}`);
|
|
664
|
+
log(`Remote DB name: ${this.remoteDB.name || 'unknown'}`);
|
|
665
|
+
|
|
644
666
|
if (this._username === 'admin') {
|
|
645
667
|
// Skip admin user
|
|
668
|
+
log('Skipping design docs for admin user');
|
|
646
669
|
return;
|
|
647
670
|
}
|
|
648
671
|
|
|
672
|
+
log(`Applying ${BaseUser.designDocs.length} design docs`);
|
|
649
673
|
for (const doc of BaseUser.designDocs) {
|
|
674
|
+
log(`Applying design doc: ${doc._id}`);
|
|
650
675
|
try {
|
|
651
676
|
// Try to get existing doc
|
|
652
677
|
try {
|
|
@@ -759,6 +784,8 @@ Currently logged-in as ${this._username}.`
|
|
|
759
784
|
private async deduplicateReviews() {
|
|
760
785
|
try {
|
|
761
786
|
log('Starting deduplication of scheduled reviews...');
|
|
787
|
+
log(`Remote DB name: ${this.remoteDB.name || 'unknown'}`);
|
|
788
|
+
log(`Write DB name: ${this.writeDB.name || 'unknown'}`);
|
|
762
789
|
/**
|
|
763
790
|
* Maps the qualified-id of a scheduled review card to
|
|
764
791
|
* the docId of the same scheduled review.
|
|
@@ -770,6 +797,9 @@ Currently logged-in as ${this._username}.`
|
|
|
770
797
|
const reviewsMap: { [index: string]: string } = {};
|
|
771
798
|
const duplicateDocIds: string[] = [];
|
|
772
799
|
|
|
800
|
+
log(
|
|
801
|
+
`Attempting to query remoteDB for reviewCards/reviewCards. Database: ${this.remoteDB.name || 'unknown'}`
|
|
802
|
+
);
|
|
773
803
|
const scheduledReviews = await this.remoteDB.query<{
|
|
774
804
|
id: string;
|
|
775
805
|
value: string;
|
|
@@ -817,6 +847,18 @@ Currently logged-in as ${this._username}.`
|
|
|
817
847
|
}
|
|
818
848
|
} catch (error) {
|
|
819
849
|
log(`Error during review deduplication: ${error}`);
|
|
850
|
+
if (error && typeof error === 'object' && 'status' in error && error.status === 404) {
|
|
851
|
+
log(
|
|
852
|
+
`Database not found (404) during review deduplication. Database: ${this.remoteDB.name || 'unknown'}`
|
|
853
|
+
);
|
|
854
|
+
log(
|
|
855
|
+
`This might indicate the user database doesn't exist or the reviewCards view isn't available`
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
// Log full error details for debugging
|
|
859
|
+
if (error && typeof error === 'object') {
|
|
860
|
+
log(`Full error details: ${JSON.stringify(error)}`);
|
|
861
|
+
}
|
|
820
862
|
}
|
|
821
863
|
}
|
|
822
864
|
|
|
@@ -8,7 +8,7 @@ import type { SyncStrategy } from '../common/SyncStrategy';
|
|
|
8
8
|
import type { AccountCreationResult, AuthenticationResult } from '../common/types';
|
|
9
9
|
import { getLocalUserDB, hexEncode, updateGuestAccountExpirationDate } from '../common';
|
|
10
10
|
import pouch from './pouchdb-setup';
|
|
11
|
-
import {
|
|
11
|
+
import { createPouchDBConfig } from './index';
|
|
12
12
|
import { getLoggedInUsername } from './auth';
|
|
13
13
|
|
|
14
14
|
const log = (s: any) => {
|
|
@@ -207,7 +207,7 @@ export class CouchDBSyncStrategy implements SyncStrategy {
|
|
|
207
207
|
// see: https://github.com/pouchdb-community/pouchdb-authentication/issues/239
|
|
208
208
|
const ret = new pouch(
|
|
209
209
|
ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,
|
|
210
|
-
|
|
210
|
+
createPouchDBConfig()
|
|
211
211
|
);
|
|
212
212
|
|
|
213
213
|
if (guestAccount) {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
CourseDBInterface,
|
|
8
8
|
DataLayerProvider,
|
|
9
9
|
UserDBInterface,
|
|
10
|
+
UserDBReader,
|
|
10
11
|
} from '../../core/interfaces';
|
|
11
12
|
import { logger } from '../../util/logger';
|
|
12
13
|
import { initializeDataDirectory } from '../../util/dataDirectory';
|
|
@@ -107,6 +108,26 @@ export class CouchDataLayerProvider implements DataLayerProvider {
|
|
|
107
108
|
return new AdminDB();
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
async createUserReaderForUser(targetUsername: string): Promise<UserDBReader> {
|
|
112
|
+
// Security check: only admin can access other users' data
|
|
113
|
+
const requestingUsername = await getLoggedInUsername();
|
|
114
|
+
if (requestingUsername !== 'admin') {
|
|
115
|
+
throw new Error('Unauthorized: Only admin users can access other users\' data');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
logger.info(`Admin user '${requestingUsername}' requesting UserDBReader for '${targetUsername}'`);
|
|
119
|
+
|
|
120
|
+
// Create a new sync strategy for the target user
|
|
121
|
+
const syncStrategy = new CouchDBSyncStrategy();
|
|
122
|
+
|
|
123
|
+
// Create a BaseUser instance for the target user
|
|
124
|
+
// Note: This creates a read-capable user instance without affecting the current session
|
|
125
|
+
const targetUserDB = await BaseUser.instance(syncStrategy, targetUsername);
|
|
126
|
+
|
|
127
|
+
// Return as UserDBReader (which BaseUser implements since UserDBInterface extends UserDBReader)
|
|
128
|
+
return targetUserDB as UserDBReader;
|
|
129
|
+
}
|
|
130
|
+
|
|
110
131
|
isReadOnly(): boolean {
|
|
111
132
|
return false;
|
|
112
133
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import pouch from './pouchdb-setup';
|
|
2
2
|
import { ENV } from '@db/factory';
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
createPouchDBConfig,
|
|
5
5
|
getStartAndEndKeys,
|
|
6
6
|
getCredentialledCourseConfig,
|
|
7
7
|
updateCredentialledCourseConfig,
|
|
@@ -21,7 +21,7 @@ export class AdminDB implements AdminDBInterface {
|
|
|
21
21
|
// if the user is not an admin
|
|
22
22
|
this.usersDB = new pouch(
|
|
23
23
|
ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + '_users',
|
|
24
|
-
|
|
24
|
+
createPouchDBConfig()
|
|
25
25
|
);
|
|
26
26
|
}
|
|
27
27
|
|
package/src/impl/couch/auth.ts
CHANGED
|
@@ -39,10 +39,14 @@ export async function getCurrentSession(): Promise<SessionResponse> {
|
|
|
39
39
|
try {
|
|
40
40
|
// Handle case where ENV variables might not be properly set
|
|
41
41
|
if (ENV.COUCHDB_SERVER_URL === NOT_SET || ENV.COUCHDB_SERVER_PROTOCOL === NOT_SET) {
|
|
42
|
-
throw new Error(
|
|
42
|
+
throw new Error(`CouchDB server configuration not properly initialized. Protocol: "${ENV.COUCHDB_SERVER_PROTOCOL}", URL: "${ENV.COUCHDB_SERVER_URL}"`);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
// Ensure URL has proper slash before _session endpoint
|
|
46
|
+
const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith('/')
|
|
47
|
+
? ENV.COUCHDB_SERVER_URL.slice(0, -1)
|
|
48
|
+
: ENV.COUCHDB_SERVER_URL;
|
|
49
|
+
const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;
|
|
46
50
|
logger.debug(`Attempting session check at: ${url}`);
|
|
47
51
|
|
|
48
52
|
const response = await fetch(url, {
|
|
@@ -57,8 +61,13 @@ export async function getCurrentSession(): Promise<SessionResponse> {
|
|
|
57
61
|
const resp: SessionResponse = await response.json();
|
|
58
62
|
return resp;
|
|
59
63
|
} catch (error) {
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
// Use same URL construction logic for error reporting
|
|
65
|
+
const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith('/')
|
|
66
|
+
? ENV.COUCHDB_SERVER_URL.slice(0, -1)
|
|
67
|
+
: ENV.COUCHDB_SERVER_URL;
|
|
68
|
+
const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;
|
|
69
|
+
logger.error(`Session check error attempting to connect to: ${url} - ${error}`);
|
|
70
|
+
throw new Error(`Session check failed connecting to ${url}: ${error}`);
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
73
|
|
|
@@ -8,12 +8,7 @@ import { ENV } from '@db/factory';
|
|
|
8
8
|
import { logger } from '@db/util/logger';
|
|
9
9
|
import moment from 'moment';
|
|
10
10
|
import pouch from './pouchdb-setup';
|
|
11
|
-
import {
|
|
12
|
-
getCourseDB,
|
|
13
|
-
getStartAndEndKeys,
|
|
14
|
-
pouchDBincludeCredentialsConfig,
|
|
15
|
-
REVIEW_TIME_FORMAT,
|
|
16
|
-
} from '.';
|
|
11
|
+
import { getCourseDB, getStartAndEndKeys, createPouchDBConfig, REVIEW_TIME_FORMAT } from '.';
|
|
17
12
|
import { CourseDB, getTag } from './courseDB';
|
|
18
13
|
|
|
19
14
|
import { UserDBInterface } from '@db/core';
|
|
@@ -97,7 +92,7 @@ export class StudentClassroomDB
|
|
|
97
92
|
const dbName = `classdb-student-${this._id}`;
|
|
98
93
|
this._db = new pouch(
|
|
99
94
|
ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,
|
|
100
|
-
|
|
95
|
+
createPouchDBConfig()
|
|
101
96
|
);
|
|
102
97
|
try {
|
|
103
98
|
const cfg = await this._db.get<ClassroomConfig>(CLASSROOM_CONFIG);
|
|
@@ -181,10 +176,13 @@ export class StudentClassroomDB
|
|
|
181
176
|
}
|
|
182
177
|
}
|
|
183
178
|
|
|
184
|
-
logger.info(
|
|
179
|
+
logger.info(
|
|
180
|
+
`New Cards from classroom ${this._cfg.name}: ${ret.map((c) => `${c.courseID}-${c.cardID}`)}`
|
|
181
|
+
);
|
|
185
182
|
|
|
186
183
|
return ret.filter((c) => {
|
|
187
|
-
if (activeCards.some((ac) => c.
|
|
184
|
+
if (activeCards.some((ac) => c.cardID === ac.cardID)) {
|
|
185
|
+
// [ ] almost certainly broken after removing qualifiedID from StudySessionItem
|
|
188
186
|
return false;
|
|
189
187
|
} else {
|
|
190
188
|
return true;
|
|
@@ -209,11 +207,11 @@ export class TeacherClassroomDB extends ClassroomDBBase implements TeacherClassr
|
|
|
209
207
|
const stuDbName = `classdb-student-${this._id}`;
|
|
210
208
|
this._db = new pouch(
|
|
211
209
|
ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,
|
|
212
|
-
|
|
210
|
+
createPouchDBConfig()
|
|
213
211
|
);
|
|
214
212
|
this._stuDb = new pouch(
|
|
215
213
|
ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + stuDbName,
|
|
216
|
-
|
|
214
|
+
createPouchDBConfig()
|
|
217
215
|
);
|
|
218
216
|
try {
|
|
219
217
|
return this._db
|
|
@@ -297,7 +295,7 @@ export function getClassroomDB(classID: string, version: 'student' | 'teacher'):
|
|
|
297
295
|
|
|
298
296
|
return new pouch(
|
|
299
297
|
ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,
|
|
300
|
-
|
|
298
|
+
createPouchDBConfig()
|
|
301
299
|
);
|
|
302
300
|
}
|
|
303
301
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import pouch from './pouchdb-setup';
|
|
2
|
-
import {
|
|
2
|
+
import { createPouchDBConfig } from '.';
|
|
3
3
|
import { ENV } from '@db/factory';
|
|
4
4
|
// import { DataShape } from '../..base-course/Interfaces/DataShape';
|
|
5
5
|
import { NameSpacer, ShapeDescriptor } from '@vue-skuilder/common';
|
|
@@ -279,6 +279,6 @@ export function getCourseDB(courseID: string): PouchDB.Database {
|
|
|
279
279
|
const dbName = `coursedb-${courseID}`;
|
|
280
280
|
return new pouch(
|
|
281
281
|
ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,
|
|
282
|
-
|
|
282
|
+
createPouchDBConfig()
|
|
283
283
|
);
|
|
284
284
|
}
|