@vue-skuilder/db 0.1.1
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/README.md +26 -0
- package/dist/core/index.d.mts +3 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +7906 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/index.mjs +7886 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/index-QMtzQI65.d.mts +734 -0
- package/dist/index-QMtzQI65.d.ts +734 -0
- package/dist/index.d.mts +133 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.js +8726 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +8699 -0
- package/dist/index.mjs.map +1 -0
- package/eslint.config.mjs +20 -0
- package/package.json +47 -0
- package/src/core/bulkImport/cardProcessor.ts +165 -0
- package/src/core/bulkImport/index.ts +2 -0
- package/src/core/bulkImport/types.ts +27 -0
- package/src/core/index.ts +9 -0
- package/src/core/interfaces/adminDB.ts +27 -0
- package/src/core/interfaces/classroomDB.ts +75 -0
- package/src/core/interfaces/contentSource.ts +64 -0
- package/src/core/interfaces/courseDB.ts +139 -0
- package/src/core/interfaces/dataLayerProvider.ts +46 -0
- package/src/core/interfaces/index.ts +7 -0
- package/src/core/interfaces/navigationStrategyManager.ts +46 -0
- package/src/core/interfaces/userDB.ts +183 -0
- package/src/core/navigators/elo.ts +76 -0
- package/src/core/navigators/index.ts +57 -0
- package/src/core/readme.md +9 -0
- package/src/core/types/contentNavigationStrategy.ts +21 -0
- package/src/core/types/db.ts +7 -0
- package/src/core/types/types-legacy.ts +155 -0
- package/src/core/types/user.ts +70 -0
- package/src/core/util/index.ts +42 -0
- package/src/factory.ts +86 -0
- package/src/impl/pouch/PouchDataLayerProvider.ts +102 -0
- package/src/impl/pouch/adminDB.ts +91 -0
- package/src/impl/pouch/auth.ts +48 -0
- package/src/impl/pouch/classroomDB.ts +306 -0
- package/src/impl/pouch/clientCache.ts +19 -0
- package/src/impl/pouch/courseAPI.ts +245 -0
- package/src/impl/pouch/courseDB.ts +772 -0
- package/src/impl/pouch/courseLookupDB.ts +135 -0
- package/src/impl/pouch/index.ts +235 -0
- package/src/impl/pouch/pouchdb-setup.ts +16 -0
- package/src/impl/pouch/types.ts +7 -0
- package/src/impl/pouch/updateQueue.ts +89 -0
- package/src/impl/pouch/user-course-relDB.ts +73 -0
- package/src/impl/pouch/userDB.ts +1097 -0
- package/src/index.ts +8 -0
- package/src/study/SessionController.ts +401 -0
- package/src/study/SpacedRepetition.ts +128 -0
- package/src/study/getCardDataShape.ts +34 -0
- package/src/study/index.ts +2 -0
- package/src/util/Loggable.ts +11 -0
- package/src/util/index.ts +1 -0
- package/src/util/logger.ts +55 -0
- package/tsconfig.json +12 -0
- package/tsup.config.ts +17 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import backendConfig from '../../eslint.config.backend.mjs';
|
|
2
|
+
|
|
3
|
+
export default [
|
|
4
|
+
...backendConfig,
|
|
5
|
+
{
|
|
6
|
+
ignores: ['node_modules/**', 'dist/**', 'eslint.config.mjs', 'tsup.config.ts'],
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
languageOptions: {
|
|
10
|
+
parserOptions: {
|
|
11
|
+
project: './tsconfig.json',
|
|
12
|
+
tsconfigRootDir: import.meta.dirname,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
rules: {
|
|
16
|
+
// Database-specific rules
|
|
17
|
+
'@typescript-eslint/no-explicit-any': 'off', // PouchDB types often use any
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vue-skuilder/db",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "0.1.1",
|
|
7
|
+
"description": "Database layer for vue-skuilder",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"module": "dist/index.mjs",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"require": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./core": {
|
|
18
|
+
"types": "./dist/core/index.d.ts",
|
|
19
|
+
"import": "./dist/core/index.mjs",
|
|
20
|
+
"require": "./dist/core/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./pouch": {
|
|
23
|
+
"types": "./dist/pouch/index.d.ts",
|
|
24
|
+
"import": "./dist/pouch/index.mjs",
|
|
25
|
+
"require": "./dist/pouch/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"build:debug": "tsup --sourcemap inline",
|
|
31
|
+
"dev": "tsup --watch",
|
|
32
|
+
"lint": "npx eslint .",
|
|
33
|
+
"lint:fix": "npx eslint . --fix",
|
|
34
|
+
"lint:check": "npx eslint . --max-warnings 0"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@nilock2/pouchdb-authentication": "^1.0.2",
|
|
38
|
+
"@vue-skuilder/common": "workspace:*",
|
|
39
|
+
"moment": "^2.29.4",
|
|
40
|
+
"pouchdb": "^9.0.0",
|
|
41
|
+
"pouchdb-find": "^9.0.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"tsup": "^8.0.2",
|
|
45
|
+
"typescript": "~5.7.2"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { CourseElo, Status, ParsedCard, BulkImportCardData } from '@vue-skuilder/common';
|
|
2
|
+
import { CourseDBInterface } from '../../core/interfaces/courseDB';
|
|
3
|
+
import { ImportResult, BulkCardProcessorConfig } from './types';
|
|
4
|
+
import { logger } from '../../util/logger';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Processes multiple cards from bulk text input
|
|
8
|
+
*
|
|
9
|
+
* @param parsedCards - Array of parsed cards to import
|
|
10
|
+
* @param courseDB - Course database interface
|
|
11
|
+
* @param config - Configuration for the card processor
|
|
12
|
+
* @returns Array of import results
|
|
13
|
+
*/
|
|
14
|
+
export async function importParsedCards(
|
|
15
|
+
parsedCards: ParsedCard[],
|
|
16
|
+
courseDB: CourseDBInterface,
|
|
17
|
+
config: BulkCardProcessorConfig
|
|
18
|
+
): Promise<ImportResult[]> {
|
|
19
|
+
const results: ImportResult[] = [];
|
|
20
|
+
|
|
21
|
+
for (const parsedCard of parsedCards) {
|
|
22
|
+
try {
|
|
23
|
+
// processCard takes a ParsedCard and returns an ImportResult
|
|
24
|
+
const result = await processCard(parsedCard, courseDB, config);
|
|
25
|
+
results.push(result);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
logger.error('Error processing card:', error);
|
|
28
|
+
// Reconstruct originalText from parsedCard for this specific catch block
|
|
29
|
+
// This is a fallback if processCard itself throws an unhandled error.
|
|
30
|
+
// Normally, processCard should return an ImportResult with status 'error'.
|
|
31
|
+
let errorOriginalText = parsedCard.markdown;
|
|
32
|
+
if (parsedCard.tags && parsedCard.tags.length > 0) {
|
|
33
|
+
errorOriginalText += `\ntags: ${parsedCard.tags.join(', ')}`;
|
|
34
|
+
}
|
|
35
|
+
if (parsedCard.elo !== undefined) {
|
|
36
|
+
errorOriginalText += `\nelo: ${parsedCard.elo}`;
|
|
37
|
+
}
|
|
38
|
+
results.push({
|
|
39
|
+
originalText: errorOriginalText,
|
|
40
|
+
status: 'error',
|
|
41
|
+
message: `Error processing card: ${
|
|
42
|
+
error instanceof Error ? error.message : 'Unknown error'
|
|
43
|
+
}`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return results;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Processes a single parsed card
|
|
53
|
+
*
|
|
54
|
+
* @param parsedCard - Parsed card data
|
|
55
|
+
* @param courseDB - Course database interface
|
|
56
|
+
* @param config - Configuration for the card processor
|
|
57
|
+
* @returns Import result for the card
|
|
58
|
+
*/
|
|
59
|
+
async function processCard(
|
|
60
|
+
parsedCard: ParsedCard,
|
|
61
|
+
courseDB: CourseDBInterface,
|
|
62
|
+
config: BulkCardProcessorConfig
|
|
63
|
+
): Promise<ImportResult> {
|
|
64
|
+
const { markdown, tags, elo } = parsedCard;
|
|
65
|
+
|
|
66
|
+
// Build the original text representation including metadata
|
|
67
|
+
let originalText = markdown;
|
|
68
|
+
if (tags.length > 0) {
|
|
69
|
+
originalText += `\ntags: ${tags.join(', ')}`;
|
|
70
|
+
}
|
|
71
|
+
if (elo !== undefined) {
|
|
72
|
+
originalText += `\nelo: ${elo}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Create card data object
|
|
76
|
+
const cardData: BulkImportCardData = {
|
|
77
|
+
Input: markdown,
|
|
78
|
+
Uploads: [], // No uploads for bulk import
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const tagsElo: CourseElo['tags'] = {};
|
|
82
|
+
for (const tag of tags) {
|
|
83
|
+
tagsElo[tag] = {
|
|
84
|
+
score: elo || 0,
|
|
85
|
+
count: 1,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const result = await courseDB.addNote(
|
|
91
|
+
config.courseCode,
|
|
92
|
+
config.dataShape,
|
|
93
|
+
cardData,
|
|
94
|
+
config.userName,
|
|
95
|
+
tags,
|
|
96
|
+
undefined, // attachments
|
|
97
|
+
elo
|
|
98
|
+
? {
|
|
99
|
+
global: {
|
|
100
|
+
score: elo,
|
|
101
|
+
count: 1,
|
|
102
|
+
},
|
|
103
|
+
tags: tagsElo,
|
|
104
|
+
misc: {},
|
|
105
|
+
}
|
|
106
|
+
: undefined
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (result.status === Status.ok) {
|
|
110
|
+
return {
|
|
111
|
+
originalText,
|
|
112
|
+
status: 'success',
|
|
113
|
+
message: 'Card added successfully.',
|
|
114
|
+
cardId: result.id ? result.id : '(unknown)',
|
|
115
|
+
};
|
|
116
|
+
} else {
|
|
117
|
+
return {
|
|
118
|
+
originalText,
|
|
119
|
+
status: 'error',
|
|
120
|
+
message: result.message || 'Failed to add card to database. Unknown error.',
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
logger.error('Error adding note:', error);
|
|
125
|
+
return {
|
|
126
|
+
originalText,
|
|
127
|
+
status: 'error',
|
|
128
|
+
message: `Error adding card: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Validates the configuration for bulk card processing
|
|
135
|
+
*
|
|
136
|
+
* @param config - Configuration to validate
|
|
137
|
+
* @returns Object with validation result and error message if any
|
|
138
|
+
*/
|
|
139
|
+
export function validateProcessorConfig(config: Partial<BulkCardProcessorConfig>): {
|
|
140
|
+
isValid: boolean;
|
|
141
|
+
errorMessage?: string;
|
|
142
|
+
} {
|
|
143
|
+
if (!config.dataShape) {
|
|
144
|
+
return {
|
|
145
|
+
isValid: false,
|
|
146
|
+
errorMessage: 'No data shape provided for card processing',
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!config.courseCode) {
|
|
151
|
+
return {
|
|
152
|
+
isValid: false,
|
|
153
|
+
errorMessage: 'No course code provided for card processing',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!config.userName) {
|
|
158
|
+
return {
|
|
159
|
+
isValid: false,
|
|
160
|
+
errorMessage: 'No user name provided for card processing',
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return { isValid: true };
|
|
165
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DataShape } from '@vue-skuilder/common';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interface representing the result of a bulk import operation for a single card
|
|
5
|
+
*/
|
|
6
|
+
export interface ImportResult {
|
|
7
|
+
/** The original text input for the card */
|
|
8
|
+
originalText: string;
|
|
9
|
+
/** Status of the import operation */
|
|
10
|
+
status: 'success' | 'error';
|
|
11
|
+
/** Message describing the result or error */
|
|
12
|
+
message: string;
|
|
13
|
+
/** ID of the newly created card (only for success) */
|
|
14
|
+
cardId?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Configuration for the bulk card processor
|
|
19
|
+
*/
|
|
20
|
+
export interface BulkCardProcessorConfig {
|
|
21
|
+
/** The data shape to use for the cards */
|
|
22
|
+
dataShape: DataShape;
|
|
23
|
+
/** The course code used for adding notes */
|
|
24
|
+
courseCode: string;
|
|
25
|
+
/** The username of the current user */
|
|
26
|
+
userName: string;
|
|
27
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Export all core interfaces and types
|
|
2
|
+
|
|
3
|
+
export * from './interfaces';
|
|
4
|
+
export * from './types/types-legacy';
|
|
5
|
+
export * from './types/user';
|
|
6
|
+
export * from '../util/Loggable';
|
|
7
|
+
export * from './util';
|
|
8
|
+
export * from './navigators';
|
|
9
|
+
export * from './bulkImport';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ClassroomConfig, CourseConfig } from '@vue-skuilder/common';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Admin functionality
|
|
5
|
+
*/
|
|
6
|
+
export interface AdminDBInterface {
|
|
7
|
+
/**
|
|
8
|
+
* Get all users
|
|
9
|
+
*/
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
11
|
+
getUsers(): Promise<PouchDB.Core.Document<{}>[]>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get all courses
|
|
15
|
+
*/
|
|
16
|
+
getCourses(): Promise<CourseConfig[]>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Remove a course
|
|
20
|
+
*/
|
|
21
|
+
removeCourse(id: string): Promise<PouchDB.Core.Response>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get all classrooms
|
|
25
|
+
*/
|
|
26
|
+
getClassrooms(): Promise<(ClassroomConfig & { _id: string })[]>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ClassroomConfig } from '@vue-skuilder/common';
|
|
2
|
+
import { ScheduledCard } from '../types/user';
|
|
3
|
+
import { StudySessionNewItem, StudySessionReviewItem } from './contentSource';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Classroom management
|
|
7
|
+
*/
|
|
8
|
+
export interface ClassroomDBInterface {
|
|
9
|
+
/**
|
|
10
|
+
* Get classroom config
|
|
11
|
+
*/
|
|
12
|
+
getConfig(): ClassroomConfig;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get assigned content
|
|
16
|
+
*/
|
|
17
|
+
getAssignedContent(): Promise<AssignedContent[]>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TeacherClassroomDBInterface extends ClassroomDBInterface {
|
|
21
|
+
/**
|
|
22
|
+
* For teacher interfaces: assign content
|
|
23
|
+
*/
|
|
24
|
+
assignContent?(content: AssignedContent): Promise<boolean>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* For teacher interfaces: remove content
|
|
28
|
+
*/
|
|
29
|
+
removeContent?(content: AssignedContent): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface StudentClassroomDBInterface extends ClassroomDBInterface {
|
|
33
|
+
/**
|
|
34
|
+
* For student interfaces: get pending reviews
|
|
35
|
+
*/
|
|
36
|
+
getPendingReviews?(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* For student interfaces: get new cards
|
|
40
|
+
*/
|
|
41
|
+
getNewCards?(limit?: number): Promise<StudySessionNewItem[]>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type AssignedContent = AssignedCourse | AssignedTag | AssignedCard;
|
|
45
|
+
|
|
46
|
+
export interface AssignedTag extends ContentBase {
|
|
47
|
+
type: 'tag';
|
|
48
|
+
tagID: string;
|
|
49
|
+
}
|
|
50
|
+
export interface AssignedCourse extends ContentBase {
|
|
51
|
+
type: 'course';
|
|
52
|
+
}
|
|
53
|
+
export interface AssignedCard extends ContentBase {
|
|
54
|
+
type: 'card';
|
|
55
|
+
cardID: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface ContentBase {
|
|
59
|
+
type: 'course' | 'tag' | 'card';
|
|
60
|
+
/**
|
|
61
|
+
* Username of the assigning teacher.
|
|
62
|
+
*/
|
|
63
|
+
assignedBy: string;
|
|
64
|
+
/**
|
|
65
|
+
* Date the content was assigned.
|
|
66
|
+
*/
|
|
67
|
+
assignedOn: moment.Moment;
|
|
68
|
+
/**
|
|
69
|
+
* A 'due' date for this assigned content, for scheduling content
|
|
70
|
+
* in advance. Content will not be actively pushed to students until
|
|
71
|
+
* this date.
|
|
72
|
+
*/
|
|
73
|
+
activeOn: moment.Moment;
|
|
74
|
+
courseID: string;
|
|
75
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { UserDBInterface } from '..';
|
|
2
|
+
import { StudentClassroomDB } from '../../impl/pouch/classroomDB';
|
|
3
|
+
import { CourseDB } from '../../impl/pouch/courseDB';
|
|
4
|
+
import { ScheduledCard } from '@/core/types/user';
|
|
5
|
+
|
|
6
|
+
export type StudySessionFailedItem = StudySessionFailedNewItem | StudySessionFailedReviewItem;
|
|
7
|
+
|
|
8
|
+
export interface StudySessionFailedNewItem extends StudySessionItem {
|
|
9
|
+
status: 'failed-new';
|
|
10
|
+
}
|
|
11
|
+
export interface StudySessionFailedReviewItem extends StudySessionReviewItem {
|
|
12
|
+
status: 'failed-review';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface StudySessionNewItem extends StudySessionItem {
|
|
16
|
+
status: 'new';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface StudySessionReviewItem extends StudySessionItem {
|
|
20
|
+
reviewID: string;
|
|
21
|
+
status: 'review' | 'failed-review';
|
|
22
|
+
}
|
|
23
|
+
export function isReview(item: StudySessionItem): item is StudySessionReviewItem {
|
|
24
|
+
const ret = item.status === 'review' || item.status === 'failed-review' || 'reviewID' in item;
|
|
25
|
+
|
|
26
|
+
// console.log(`itemIsReview: ${ret}
|
|
27
|
+
// \t${JSON.stringify(item)}`);
|
|
28
|
+
|
|
29
|
+
return ret;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface StudySessionItem {
|
|
33
|
+
status: 'new' | 'review' | 'failed-new' | 'failed-review';
|
|
34
|
+
qualifiedID: `${string}-${string}` | `${string}-${string}-${number}`;
|
|
35
|
+
cardID: string;
|
|
36
|
+
contentSourceType: 'course' | 'classroom';
|
|
37
|
+
contentSourceID: string;
|
|
38
|
+
courseID: string;
|
|
39
|
+
// reviewID?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ContentSourceID {
|
|
43
|
+
type: 'course' | 'classroom';
|
|
44
|
+
id: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface StudyContentSource {
|
|
48
|
+
getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;
|
|
49
|
+
getNewCards(n?: number): Promise<StudySessionNewItem[]>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function getStudySource(
|
|
53
|
+
source: ContentSourceID,
|
|
54
|
+
user: UserDBInterface
|
|
55
|
+
): Promise<StudyContentSource> {
|
|
56
|
+
if (source.type === 'classroom') {
|
|
57
|
+
return await StudentClassroomDB.factory(source.id, user);
|
|
58
|
+
} else {
|
|
59
|
+
// if (source.type === 'course') - removed so tsc is certain something returns
|
|
60
|
+
return new CourseDB(source.id, async () => {
|
|
61
|
+
return user;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { CourseConfig, CourseElo, DataShape, SkuilderCourseData } from '@vue-skuilder/common';
|
|
2
|
+
import { StudySessionNewItem, StudySessionItem } from './contentSource';
|
|
3
|
+
import { TagStub, Tag } from '../types/types-legacy';
|
|
4
|
+
import { DataLayerResult } from '../types/db';
|
|
5
|
+
import { NavigationStrategyManager } from './navigationStrategyManager';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Course content and management
|
|
9
|
+
*/
|
|
10
|
+
export interface CoursesDBInterface {
|
|
11
|
+
/**
|
|
12
|
+
* Get course config
|
|
13
|
+
*/
|
|
14
|
+
getCourseConfig(courseId: string): Promise<CourseConfig>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get a list of all courses
|
|
18
|
+
*/
|
|
19
|
+
getCourseList(): Promise<CourseConfig[]>;
|
|
20
|
+
|
|
21
|
+
disambiguateCourse(courseId: string, disambiguator: string): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CourseInfo {
|
|
25
|
+
cardCount: number;
|
|
26
|
+
registeredUsers: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface CourseDBInterface extends NavigationStrategyManager {
|
|
30
|
+
/**
|
|
31
|
+
* Get course config
|
|
32
|
+
*/
|
|
33
|
+
getCourseConfig(): Promise<CourseConfig>;
|
|
34
|
+
|
|
35
|
+
getCourseID(): string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Set course config
|
|
39
|
+
*/
|
|
40
|
+
updateCourseConfig(cfg: CourseConfig): Promise<PouchDB.Core.Response>;
|
|
41
|
+
|
|
42
|
+
getCourseInfo(): Promise<CourseInfo>;
|
|
43
|
+
|
|
44
|
+
getCourseDoc<T extends SkuilderCourseData>(
|
|
45
|
+
id: string,
|
|
46
|
+
options?: PouchDB.Core.GetOptions
|
|
47
|
+
): Promise<T>;
|
|
48
|
+
getCourseDocs<T extends SkuilderCourseData>(
|
|
49
|
+
ids: string[],
|
|
50
|
+
options?: PouchDB.Core.AllDocsOptions
|
|
51
|
+
): Promise<PouchDB.Core.AllDocsWithKeysResponse<{} & T>>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get cards sorted by ELO rating
|
|
55
|
+
*/
|
|
56
|
+
getCardsByELO(elo: number, limit?: number): Promise<string[]>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get ELO data for specific cards
|
|
60
|
+
*/
|
|
61
|
+
getCardEloData(cardIds: string[]): Promise<CourseElo[]>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Update card ELO rating
|
|
65
|
+
*/
|
|
66
|
+
updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get new cards for study
|
|
70
|
+
*/
|
|
71
|
+
getNewCards(limit?: number): Promise<StudySessionNewItem[]>;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get cards centered at a particular ELO rating
|
|
75
|
+
*/
|
|
76
|
+
getCardsCenteredAtELO(
|
|
77
|
+
options: { limit: number; elo: 'user' | 'random' | number },
|
|
78
|
+
filter?: (id: string) => boolean
|
|
79
|
+
): Promise<StudySessionItem[]>;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get tags for a card
|
|
83
|
+
*/
|
|
84
|
+
getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Add a tag to a card
|
|
88
|
+
*/
|
|
89
|
+
addTagToCard(cardId: string, tagId: string, updateELO?: boolean): Promise<PouchDB.Core.Response>;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Remove a tag from a card
|
|
93
|
+
*/
|
|
94
|
+
removeTagFromCard(cardId: string, tagId: string): Promise<PouchDB.Core.Response>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Create a new tag
|
|
98
|
+
*/
|
|
99
|
+
createTag(tagName: string): Promise<PouchDB.Core.Response>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get a tag by name
|
|
103
|
+
*/
|
|
104
|
+
getTag(tagName: string): Promise<Tag>;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Update a tag
|
|
108
|
+
*/
|
|
109
|
+
updateTag(tag: Tag): Promise<PouchDB.Core.Response>;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get all tag stubs for a course
|
|
113
|
+
*/
|
|
114
|
+
getCourseTagStubs(): Promise<PouchDB.Core.AllDocsResponse<Tag>>;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Add a note to the course
|
|
118
|
+
*/
|
|
119
|
+
addNote(
|
|
120
|
+
codeCourse: string,
|
|
121
|
+
shape: DataShape,
|
|
122
|
+
data: unknown,
|
|
123
|
+
author: string,
|
|
124
|
+
tags: string[],
|
|
125
|
+
uploads?: { [key: string]: PouchDB.Core.FullAttachment },
|
|
126
|
+
elo?: CourseElo
|
|
127
|
+
): Promise<DataLayerResult>;
|
|
128
|
+
|
|
129
|
+
removeCard(cardId: string): Promise<PouchDB.Core.Response>;
|
|
130
|
+
|
|
131
|
+
getInexperiencedCards(): Promise<
|
|
132
|
+
{
|
|
133
|
+
courseId: string;
|
|
134
|
+
cardId: string;
|
|
135
|
+
count: number;
|
|
136
|
+
elo: CourseElo;
|
|
137
|
+
}[]
|
|
138
|
+
>;
|
|
139
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// db/src/core/interfaces.ts
|
|
2
|
+
|
|
3
|
+
import { UserDBInterface } from './userDB';
|
|
4
|
+
import { CourseDBInterface, CoursesDBInterface } from './courseDB';
|
|
5
|
+
import { ClassroomDBInterface } from './classroomDB';
|
|
6
|
+
import { AdminDBInterface } from './adminDB';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Main factory interface for data access
|
|
10
|
+
*/
|
|
11
|
+
export interface DataLayerProvider {
|
|
12
|
+
/**
|
|
13
|
+
* Get the user database interface
|
|
14
|
+
*/
|
|
15
|
+
getUserDB(): UserDBInterface;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get a course database interface
|
|
19
|
+
*/
|
|
20
|
+
getCourseDB(courseId: string): CourseDBInterface;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get the courses-lookup interface
|
|
24
|
+
*/
|
|
25
|
+
getCoursesDB(): CoursesDBInterface;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get a classroom database interface
|
|
29
|
+
*/
|
|
30
|
+
getClassroomDB(classId: string, type: 'student' | 'teacher'): Promise<ClassroomDBInterface>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get the admin database interface
|
|
34
|
+
*/
|
|
35
|
+
getAdminDB(): AdminDBInterface;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Initialize the data layer
|
|
39
|
+
*/
|
|
40
|
+
initialize(): Promise<void>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Teardown the data layer
|
|
44
|
+
*/
|
|
45
|
+
teardown(): Promise<void>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A NavigationStrategyManager is an entity which may contain multiple strategies.
|
|
5
|
+
*
|
|
6
|
+
* This interface defines strategy CRUD.
|
|
7
|
+
*/
|
|
8
|
+
export interface NavigationStrategyManager {
|
|
9
|
+
/**
|
|
10
|
+
* Get the navigation strategy for a given course
|
|
11
|
+
* @returns The navigation strategy for the course
|
|
12
|
+
*/
|
|
13
|
+
getNavigationStrategy(id: string): Promise<ContentNavigationStrategyData>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get all available navigation strategies
|
|
17
|
+
* @returns An array of all available navigation strategies
|
|
18
|
+
*/
|
|
19
|
+
getAllNavigationStrategies(): Promise<ContentNavigationStrategyData[]>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Add a new navigation strategy
|
|
23
|
+
* @param data The data for the new navigation strategy
|
|
24
|
+
* @returns A promise that resolves when the strategy has been added
|
|
25
|
+
*/
|
|
26
|
+
addNavigationStrategy(data: ContentNavigationStrategyData): Promise<void>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Update an existing navigation strategy
|
|
30
|
+
* @param id The ID of the navigation strategy to update
|
|
31
|
+
* @param data The new data for the navigation strategy
|
|
32
|
+
* @returns A promise that resolves when the update is complete
|
|
33
|
+
*/
|
|
34
|
+
updateNavigationStrategy(id: string, data: ContentNavigationStrategyData): Promise<void>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @returns A content navigation strategy suitable to the current context.
|
|
38
|
+
*/
|
|
39
|
+
surfaceNavigationStrategy(): Promise<ContentNavigationStrategyData>;
|
|
40
|
+
|
|
41
|
+
// [ ] addons here like:
|
|
42
|
+
// - determining Navigation Strategy from context of current user
|
|
43
|
+
// - determining weighted averages of navigation strategies
|
|
44
|
+
// - expressing A/B testing results of 'ecosystem of strategies'
|
|
45
|
+
// - etc etc
|
|
46
|
+
}
|