@vue-skuilder/db 0.1.3 → 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
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// packages/db/src/impl/common/SyncStrategy.ts
|
|
2
|
+
|
|
3
|
+
import type { AccountCreationResult, AuthenticationResult } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Strategy interface for handling user data synchronization
|
|
7
|
+
* Different implementations handle remote sync vs local-only storage
|
|
8
|
+
*/
|
|
9
|
+
export interface SyncStrategy {
|
|
10
|
+
/**
|
|
11
|
+
* Set up the remote database for a user
|
|
12
|
+
* @param username The username to set up remote DB for
|
|
13
|
+
* @returns PouchDB database instance (may be same as local for no-op)
|
|
14
|
+
*/
|
|
15
|
+
setupRemoteDB(username: string): PouchDB.Database;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Start synchronization between local and remote databases
|
|
19
|
+
* @param localDB The local PouchDB instance
|
|
20
|
+
* @param remoteDB The remote PouchDB instance
|
|
21
|
+
*/
|
|
22
|
+
startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Stop synchronization (optional - for cleanup)
|
|
26
|
+
*/
|
|
27
|
+
stopSync?(): void;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Whether this strategy supports account creation
|
|
31
|
+
*/
|
|
32
|
+
canCreateAccount(): boolean;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether this strategy supports authentication
|
|
36
|
+
*/
|
|
37
|
+
canAuthenticate(): boolean;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create a new user account (if supported)
|
|
41
|
+
* @param username The username for the new account
|
|
42
|
+
* @param password The password for the new account
|
|
43
|
+
*/
|
|
44
|
+
createAccount?(username: string, password: string): Promise<AccountCreationResult>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Authenticate a user (if supported)
|
|
48
|
+
* @param username The username to authenticate
|
|
49
|
+
* @param password The password to authenticate with
|
|
50
|
+
*/
|
|
51
|
+
authenticate?(username: string, password: string): Promise<AuthenticationResult>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Log out the current user (if supported)
|
|
55
|
+
*/
|
|
56
|
+
logout?(): Promise<AuthenticationResult>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get the current logged-in username
|
|
60
|
+
* Returns the username if logged in, or a default guest username
|
|
61
|
+
*/
|
|
62
|
+
getCurrentUsername(): Promise<string>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Base class for sync strategies with common functionality
|
|
67
|
+
*/
|
|
68
|
+
export abstract class BaseSyncStrategy implements SyncStrategy {
|
|
69
|
+
abstract setupRemoteDB(username: string): PouchDB.Database;
|
|
70
|
+
abstract startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;
|
|
71
|
+
abstract canCreateAccount(): boolean;
|
|
72
|
+
abstract canAuthenticate(): boolean;
|
|
73
|
+
abstract getCurrentUsername(): Promise<string>;
|
|
74
|
+
|
|
75
|
+
stopSync?(): void {
|
|
76
|
+
// Default no-op implementation
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async createAccount(_username: string, _password: string): Promise<AccountCreationResult> {
|
|
80
|
+
throw new Error('Account creation not supported by this sync strategy');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async authenticate(_username: string, _password: string): Promise<AuthenticationResult> {
|
|
84
|
+
throw new Error('Authentication not supported by this sync strategy');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async logout(): Promise<AuthenticationResult> {
|
|
88
|
+
throw new Error('Logout not supported by this sync strategy');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// packages/db/src/impl/common/index.ts
|
|
2
|
+
|
|
3
|
+
export type { SyncStrategy } from './SyncStrategy';
|
|
4
|
+
export { BaseSyncStrategy } from './SyncStrategy';
|
|
5
|
+
export type {
|
|
6
|
+
AccountCreationResult,
|
|
7
|
+
AuthenticationResult,
|
|
8
|
+
UserSession,
|
|
9
|
+
SyncConfig,
|
|
10
|
+
SyncStatus,
|
|
11
|
+
} from './types';
|
|
12
|
+
export { BaseUser } from './BaseUserDB';
|
|
13
|
+
export {
|
|
14
|
+
REVIEW_PREFIX,
|
|
15
|
+
REVIEW_TIME_FORMAT,
|
|
16
|
+
hexEncode,
|
|
17
|
+
filterAllDocsByPrefix,
|
|
18
|
+
getStartAndEndKeys,
|
|
19
|
+
updateGuestAccountExpirationDate,
|
|
20
|
+
getLocalUserDB,
|
|
21
|
+
scheduleCardReviewLocal,
|
|
22
|
+
removeScheduledCardReviewLocal,
|
|
23
|
+
} from './userDBHelpers';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// packages/db/src/impl/common/types.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Common types used by UserDB implementations across different sync strategies
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Status } from '@vue-skuilder/common';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Result type for account creation operations
|
|
11
|
+
*/
|
|
12
|
+
export interface AccountCreationResult {
|
|
13
|
+
status: Status;
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Result type for authentication operations
|
|
19
|
+
*/
|
|
20
|
+
export interface AuthenticationResult {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
error?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* User session information
|
|
27
|
+
*/
|
|
28
|
+
export interface UserSession {
|
|
29
|
+
username: string;
|
|
30
|
+
isLoggedIn: boolean;
|
|
31
|
+
isGuest: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Configuration for sync behavior
|
|
36
|
+
*/
|
|
37
|
+
export interface SyncConfig {
|
|
38
|
+
live?: boolean;
|
|
39
|
+
retry?: boolean;
|
|
40
|
+
continuous?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sync status information
|
|
45
|
+
*/
|
|
46
|
+
export interface SyncStatus {
|
|
47
|
+
active: boolean;
|
|
48
|
+
lastSync?: Date;
|
|
49
|
+
error?: string;
|
|
50
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// packages/db/src/impl/common/userDBHelpers.ts
|
|
2
|
+
|
|
3
|
+
import moment from 'moment';
|
|
4
|
+
import { logger } from '../../util/logger';
|
|
5
|
+
import { ScheduledCard } from '@db/core/types/user';
|
|
6
|
+
|
|
7
|
+
export const REVIEW_PREFIX: string = 'card_review_';
|
|
8
|
+
export const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';
|
|
9
|
+
|
|
10
|
+
import pouch from '../couch/pouchdb-setup';
|
|
11
|
+
|
|
12
|
+
const log = (s: any) => {
|
|
13
|
+
logger.info(s);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function hexEncode(str: string): string {
|
|
17
|
+
let hex: string;
|
|
18
|
+
let returnStr: string = '';
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < str.length; i++) {
|
|
21
|
+
hex = str.charCodeAt(i).toString(16);
|
|
22
|
+
returnStr += ('000' + hex).slice(3);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return returnStr;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function filterAllDocsByPrefix<T>(
|
|
29
|
+
db: PouchDB.Database,
|
|
30
|
+
prefix: string,
|
|
31
|
+
opts?: PouchDB.Core.AllDocsOptions
|
|
32
|
+
) {
|
|
33
|
+
// see couchdb docs 6.2.2:
|
|
34
|
+
// Guide to Views -> Views Collation -> String Ranges
|
|
35
|
+
const options: PouchDB.Core.AllDocsWithinRangeOptions = {
|
|
36
|
+
startkey: prefix,
|
|
37
|
+
endkey: prefix + '\ufff0',
|
|
38
|
+
include_docs: true,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
if (opts) {
|
|
42
|
+
Object.assign(options, opts);
|
|
43
|
+
}
|
|
44
|
+
return db.allDocs<T>(options);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getStartAndEndKeys(key: string): {
|
|
48
|
+
startkey: string;
|
|
49
|
+
endkey: string;
|
|
50
|
+
} {
|
|
51
|
+
return {
|
|
52
|
+
startkey: key,
|
|
53
|
+
endkey: key + '\ufff0',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function updateGuestAccountExpirationDate(guestDB: PouchDB.Database<object>) {
|
|
58
|
+
const currentTime = moment.utc();
|
|
59
|
+
const expirationDate: string = currentTime.add(2, 'months').toISOString();
|
|
60
|
+
const expiryDocID: string = 'GuestAccountExpirationDate';
|
|
61
|
+
|
|
62
|
+
void guestDB
|
|
63
|
+
.get(expiryDocID)
|
|
64
|
+
.then((doc) => {
|
|
65
|
+
return guestDB.put({
|
|
66
|
+
_id: expiryDocID,
|
|
67
|
+
_rev: doc._rev,
|
|
68
|
+
date: expirationDate,
|
|
69
|
+
});
|
|
70
|
+
})
|
|
71
|
+
.catch(() => {
|
|
72
|
+
return guestDB.put({
|
|
73
|
+
_id: expiryDocID,
|
|
74
|
+
date: expirationDate,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get local user database with appropriate adapter for environment
|
|
81
|
+
*/
|
|
82
|
+
export function getLocalUserDB(username: string): PouchDB.Database {
|
|
83
|
+
// // Choose adapter based on environment
|
|
84
|
+
//
|
|
85
|
+
// Not certain of this is required. Let's let pouch's auto detection
|
|
86
|
+
// handle it until we specifically know we need to intervene.
|
|
87
|
+
//
|
|
88
|
+
// let adapter: string;
|
|
89
|
+
// if (typeof window !== 'undefined') {
|
|
90
|
+
// // Browser environment - use IndexedDB
|
|
91
|
+
// adapter = 'idb';
|
|
92
|
+
// } else {
|
|
93
|
+
// // Node.js environment (tests) - use memory adapter
|
|
94
|
+
// adapter = 'memory';
|
|
95
|
+
// }
|
|
96
|
+
|
|
97
|
+
return new pouch(`userdb-${username}`, {});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Schedule a card review (strategy-agnostic version)
|
|
102
|
+
*/
|
|
103
|
+
export function scheduleCardReviewLocal(
|
|
104
|
+
userDB: PouchDB.Database,
|
|
105
|
+
review: {
|
|
106
|
+
card_id: PouchDB.Core.DocumentId;
|
|
107
|
+
time: moment.Moment;
|
|
108
|
+
course_id: string;
|
|
109
|
+
scheduledFor: ScheduledCard['scheduledFor'];
|
|
110
|
+
schedulingAgentId: ScheduledCard['schedulingAgentId'];
|
|
111
|
+
}
|
|
112
|
+
) {
|
|
113
|
+
const now = moment.utc();
|
|
114
|
+
logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);
|
|
115
|
+
void userDB.put<ScheduledCard>({
|
|
116
|
+
_id: REVIEW_PREFIX + review.time.format(REVIEW_TIME_FORMAT),
|
|
117
|
+
cardId: review.card_id,
|
|
118
|
+
reviewTime: review.time,
|
|
119
|
+
courseId: review.course_id,
|
|
120
|
+
scheduledAt: now,
|
|
121
|
+
scheduledFor: review.scheduledFor,
|
|
122
|
+
schedulingAgentId: review.schedulingAgentId,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Remove a scheduled card review (strategy-agnostic version)
|
|
128
|
+
*/
|
|
129
|
+
export async function removeScheduledCardReviewLocal(
|
|
130
|
+
userDB: PouchDB.Database,
|
|
131
|
+
reviewDocID: string
|
|
132
|
+
) {
|
|
133
|
+
const reviewDoc = await userDB.get(reviewDocID);
|
|
134
|
+
userDB
|
|
135
|
+
.remove(reviewDoc)
|
|
136
|
+
.then((res) => {
|
|
137
|
+
if (res.ok) {
|
|
138
|
+
log(`Removed Review Doc: ${reviewDocID}`);
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
.catch((err) => {
|
|
142
|
+
log(`Failed to remove Review Doc: ${reviewDocID},\n${JSON.stringify(err)}`);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// packages/db/src/impl/couch/CouchDBSyncStrategy.ts
|
|
2
|
+
|
|
3
|
+
import { ENV } from '@db/factory';
|
|
4
|
+
import { GuestUsername } from '../../core/types/types-legacy';
|
|
5
|
+
import { logger } from '../../util/logger';
|
|
6
|
+
import { Status } from '@vue-skuilder/common';
|
|
7
|
+
import type { SyncStrategy } from '../common/SyncStrategy';
|
|
8
|
+
import type { AccountCreationResult, AuthenticationResult } from '../common/types';
|
|
9
|
+
import { getLocalUserDB, hexEncode, updateGuestAccountExpirationDate } from '../common';
|
|
10
|
+
import pouch from './pouchdb-setup';
|
|
11
|
+
import { pouchDBincludeCredentialsConfig } from './index';
|
|
12
|
+
import { getLoggedInUsername } from './auth';
|
|
13
|
+
|
|
14
|
+
const log = (s: any) => {
|
|
15
|
+
logger.info(s);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Sync strategy that implements full CouchDB remote synchronization
|
|
20
|
+
* Handles account creation, authentication, and live sync with remote CouchDB server
|
|
21
|
+
*/
|
|
22
|
+
export class CouchDBSyncStrategy implements SyncStrategy {
|
|
23
|
+
private syncHandle?: any; // Handle to cancel sync if needed
|
|
24
|
+
|
|
25
|
+
setupRemoteDB(username: string): PouchDB.Database {
|
|
26
|
+
if (username === GuestUsername || username.startsWith(GuestUsername)) {
|
|
27
|
+
// For guest users, remote is same as local (no remote sync)
|
|
28
|
+
return getLocalUserDB(username);
|
|
29
|
+
} else {
|
|
30
|
+
// For real users, connect to remote CouchDB
|
|
31
|
+
return this.getUserDB(username);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void {
|
|
36
|
+
// Only sync if local and remote are different instances
|
|
37
|
+
if (localDB !== remoteDB) {
|
|
38
|
+
this.syncHandle = pouch.sync(localDB, remoteDB, {
|
|
39
|
+
live: true,
|
|
40
|
+
retry: true,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// If they're the same (guest mode), no sync needed
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
stopSync?(): void {
|
|
47
|
+
if (this.syncHandle) {
|
|
48
|
+
this.syncHandle.cancel();
|
|
49
|
+
this.syncHandle = undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
canCreateAccount(): boolean {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
canAuthenticate(): boolean {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async createAccount(username: string, password: string): Promise<AccountCreationResult> {
|
|
62
|
+
try {
|
|
63
|
+
const signupRequest = await this.getRemoteCouchRootDB().signUp(username, password);
|
|
64
|
+
|
|
65
|
+
if (signupRequest.ok) {
|
|
66
|
+
log(`CREATEACCOUNT: Successfully created account for ${username}`);
|
|
67
|
+
|
|
68
|
+
// Log out any existing session
|
|
69
|
+
try {
|
|
70
|
+
const logoutResult = await this.getRemoteCouchRootDB().logOut();
|
|
71
|
+
log(`CREATEACCOUNT: logged out: ${logoutResult.ok}`);
|
|
72
|
+
} catch {
|
|
73
|
+
// Ignore logout errors - might not be logged in
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Log in as the new user
|
|
77
|
+
const loginResult = await this.getRemoteCouchRootDB().logIn(username, password);
|
|
78
|
+
log(`CREATEACCOUNT: logged in as new user: ${loginResult.ok}`);
|
|
79
|
+
|
|
80
|
+
if (loginResult.ok) {
|
|
81
|
+
// TODO: Handle guest data migration here if needed
|
|
82
|
+
// Set up databases for the new user:
|
|
83
|
+
// const newLocal = getLocalUserDB(username);
|
|
84
|
+
// const newRemote = this.getUserDB(username);
|
|
85
|
+
|
|
86
|
+
// For now, just return success
|
|
87
|
+
return {
|
|
88
|
+
status: Status.ok,
|
|
89
|
+
error: undefined,
|
|
90
|
+
};
|
|
91
|
+
} else {
|
|
92
|
+
return {
|
|
93
|
+
status: Status.error,
|
|
94
|
+
error: 'Failed to log in after account creation',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
logger.warn(`Signup not OK: ${JSON.stringify(signupRequest)}`);
|
|
99
|
+
return {
|
|
100
|
+
status: Status.error,
|
|
101
|
+
error: 'Account creation failed',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
} catch (e: any) {
|
|
105
|
+
if (e.reason === 'Document update conflict.') {
|
|
106
|
+
return {
|
|
107
|
+
status: Status.error,
|
|
108
|
+
error: 'This username is taken!',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
logger.error(`Error on signup: ${JSON.stringify(e)}`);
|
|
112
|
+
return {
|
|
113
|
+
status: Status.error,
|
|
114
|
+
error: e.message || 'Unknown error during account creation',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async authenticate(username: string, password: string): Promise<AuthenticationResult> {
|
|
120
|
+
try {
|
|
121
|
+
const loginResult = await this.getRemoteCouchRootDB().logIn(username, password);
|
|
122
|
+
|
|
123
|
+
if (loginResult.ok) {
|
|
124
|
+
log(`Successfully logged in as ${username}`);
|
|
125
|
+
return {
|
|
126
|
+
ok: true,
|
|
127
|
+
};
|
|
128
|
+
} else {
|
|
129
|
+
log(`Login failed for ${username}`);
|
|
130
|
+
return {
|
|
131
|
+
ok: false,
|
|
132
|
+
error: 'Invalid username or password',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
} catch (error: any) {
|
|
136
|
+
logger.error(`Authentication error for ${username}:`, error);
|
|
137
|
+
return {
|
|
138
|
+
ok: false,
|
|
139
|
+
error: error.message || 'Authentication failed',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async logout(): Promise<AuthenticationResult> {
|
|
145
|
+
try {
|
|
146
|
+
const result = await this.getRemoteCouchRootDB().logOut();
|
|
147
|
+
return {
|
|
148
|
+
ok: result.ok,
|
|
149
|
+
error: result.ok ? undefined : 'Logout failed',
|
|
150
|
+
};
|
|
151
|
+
} catch (error: any) {
|
|
152
|
+
logger.error('Logout error:', error);
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
error: error.message || 'Logout failed',
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async getCurrentUsername(): Promise<string> {
|
|
161
|
+
try {
|
|
162
|
+
return await getLoggedInUsername();
|
|
163
|
+
} catch {
|
|
164
|
+
return GuestUsername;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get remote CouchDB root database for authentication operations
|
|
170
|
+
*/
|
|
171
|
+
private getRemoteCouchRootDB(): PouchDB.Database {
|
|
172
|
+
const remoteStr: string =
|
|
173
|
+
ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + 'skuilder';
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
return new pouch(remoteStr, {
|
|
177
|
+
skip_setup: true,
|
|
178
|
+
});
|
|
179
|
+
} catch (error) {
|
|
180
|
+
logger.error('Failed to initialize remote CouchDB connection:', error);
|
|
181
|
+
throw new Error(`Failed to initialize CouchDB: ${JSON.stringify(error)}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get remote user database for a specific user
|
|
187
|
+
*/
|
|
188
|
+
private getUserDB(username: string): PouchDB.Database {
|
|
189
|
+
const guestAccount: boolean = false;
|
|
190
|
+
|
|
191
|
+
const hexName = hexEncode(username);
|
|
192
|
+
const dbName = `userdb-${hexName}`;
|
|
193
|
+
log(`Fetching user database: ${dbName} (${username})`);
|
|
194
|
+
|
|
195
|
+
// Odd construction here the result of a bug in the
|
|
196
|
+
// interaction between pouch, pouch-auth.
|
|
197
|
+
// see: https://github.com/pouchdb-community/pouchdb-authentication/issues/239
|
|
198
|
+
const ret = new pouch(
|
|
199
|
+
ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,
|
|
200
|
+
pouchDBincludeCredentialsConfig
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
if (guestAccount) {
|
|
204
|
+
updateGuestAccountExpirationDate(ret);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return ret;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// db/src/impl/
|
|
1
|
+
// db/src/impl/couch/PouchDataLayerProvider.ts
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
AdminDBInterface,
|
|
@@ -16,9 +16,10 @@ import { AdminDB } from './adminDB';
|
|
|
16
16
|
import { StudentClassroomDB, TeacherClassroomDB } from './classroomDB';
|
|
17
17
|
import { CourseDB, CoursesDB } from './courseDB';
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { BaseUser } from '../common';
|
|
20
|
+
import { CouchDBSyncStrategy } from './CouchDBSyncStrategy';
|
|
20
21
|
|
|
21
|
-
export class
|
|
22
|
+
export class CouchDataLayerProvider implements DataLayerProvider {
|
|
22
23
|
private initialized: boolean = false;
|
|
23
24
|
private userDB!: UserDBInterface;
|
|
24
25
|
private currentUsername: string = '';
|
|
@@ -42,8 +43,11 @@ export class PouchDataLayerProvider implements DataLayerProvider {
|
|
|
42
43
|
|
|
43
44
|
if (isNodeEnvironment) {
|
|
44
45
|
logger.info(
|
|
45
|
-
'
|
|
46
|
+
'CouchDataLayerProvider: Running in Node.js environment, creating guest UserDB for testing.'
|
|
46
47
|
);
|
|
48
|
+
// In Node.js (testing) environment, create a guest user instance
|
|
49
|
+
const syncStrategy = new CouchDBSyncStrategy();
|
|
50
|
+
this.userDB = await BaseUser.instance(syncStrategy);
|
|
47
51
|
} else {
|
|
48
52
|
// Assume browser-like environment, proceed with user session logic
|
|
49
53
|
try {
|
|
@@ -53,13 +57,14 @@ export class PouchDataLayerProvider implements DataLayerProvider {
|
|
|
53
57
|
|
|
54
58
|
// Create the user db instance if a username was found
|
|
55
59
|
if (this.currentUsername) {
|
|
56
|
-
|
|
60
|
+
const syncStrategy = new CouchDBSyncStrategy();
|
|
61
|
+
this.userDB = await BaseUser.instance(syncStrategy, this.currentUsername);
|
|
57
62
|
} else {
|
|
58
|
-
logger.warn('
|
|
63
|
+
logger.warn('CouchDataLayerProvider: No logged-in username found in session.');
|
|
59
64
|
}
|
|
60
65
|
} catch (error) {
|
|
61
66
|
logger.error(
|
|
62
|
-
'
|
|
67
|
+
'CouchDataLayerProvider: Error during user session check or user DB initialization:',
|
|
63
68
|
error
|
|
64
69
|
);
|
|
65
70
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import pouch from './pouchdb-setup';
|
|
2
|
-
import { ENV } from '
|
|
2
|
+
import { ENV } from '@db/factory';
|
|
3
3
|
import {
|
|
4
4
|
pouchDBincludeCredentialsConfig,
|
|
5
5
|
getStartAndEndKeys,
|
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
import { TeacherClassroomDB, ClassroomLookupDB } from './classroomDB';
|
|
10
10
|
import { PouchError } from './types';
|
|
11
11
|
|
|
12
|
-
import { AdminDBInterface } from '
|
|
12
|
+
import { AdminDBInterface } from '@db/core';
|
|
13
13
|
import CourseLookup from './courseLookupDB';
|
|
14
|
-
import { logger } from '
|
|
14
|
+
import { logger } from '@db/util/logger';
|
|
15
15
|
|
|
16
16
|
export class AdminDB implements AdminDBInterface {
|
|
17
17
|
private usersDB!: PouchDB.Database;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ENV } from '
|
|
1
|
+
import { ENV } from '@db/factory';
|
|
2
2
|
import { GuestUsername } from '../../core/types/types-legacy';
|
|
3
|
-
import { logger } from '
|
|
3
|
+
import { logger } from '@db/util/logger';
|
|
4
4
|
|
|
5
5
|
interface SessionResponse {
|
|
6
6
|
info: unknown;
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
StudyContentSource,
|
|
3
3
|
StudySessionNewItem,
|
|
4
4
|
StudySessionReviewItem,
|
|
5
|
-
} from '
|
|
5
|
+
} from '@db/core/interfaces/contentSource';
|
|
6
6
|
import { ClassroomConfig } from '@vue-skuilder/common';
|
|
7
|
-
import { ENV } from '
|
|
8
|
-
import { logger } from '
|
|
7
|
+
import { ENV } from '@db/factory';
|
|
8
|
+
import { logger } from '@db/util/logger';
|
|
9
9
|
import moment from 'moment';
|
|
10
10
|
import pouch from './pouchdb-setup';
|
|
11
11
|
import {
|
|
@@ -16,15 +16,15 @@ import {
|
|
|
16
16
|
} from '.';
|
|
17
17
|
import { CourseDB, getTag } from './courseDB';
|
|
18
18
|
|
|
19
|
-
import { UserDBInterface } from '
|
|
19
|
+
import { UserDBInterface } from '@db/core';
|
|
20
20
|
import {
|
|
21
21
|
AssignedContent,
|
|
22
22
|
AssignedCourse,
|
|
23
23
|
AssignedTag,
|
|
24
24
|
StudentClassroomDBInterface,
|
|
25
25
|
TeacherClassroomDBInterface,
|
|
26
|
-
} from '
|
|
27
|
-
import { ScheduledCard } from '
|
|
26
|
+
} from '@db/core/interfaces/classroomDB';
|
|
27
|
+
import { ScheduledCard } from '@db/core/types/user';
|
|
28
28
|
|
|
29
29
|
const classroomLookupDBTitle = 'classdb-lookup';
|
|
30
30
|
export const CLASSROOM_CONFIG = 'ClassroomConfig';
|