@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,183 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActivityRecord,
|
|
3
|
+
CourseRegistration,
|
|
4
|
+
CourseRegistrationDoc,
|
|
5
|
+
ScheduledCard,
|
|
6
|
+
} from '@/core/types/user';
|
|
7
|
+
import { CourseElo, Status } from '@vue-skuilder/common';
|
|
8
|
+
import { Moment } from 'moment';
|
|
9
|
+
import { CardHistory, CardRecord } from '../types/types-legacy';
|
|
10
|
+
import { UserConfig } from '../types/user';
|
|
11
|
+
import { DocumentUpdater } from '@/study';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* User data and authentication
|
|
15
|
+
*/
|
|
16
|
+
export interface UserDBInterface extends DocumentUpdater {
|
|
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
|
+
|
|
47
|
+
getUsername(): string;
|
|
48
|
+
|
|
49
|
+
isLoggedIn(): boolean;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get user configuration
|
|
53
|
+
*/
|
|
54
|
+
getConfig(): Promise<UserConfig>;
|
|
55
|
+
|
|
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
|
+
/**
|
|
67
|
+
* Get cards that the user has seen
|
|
68
|
+
*/
|
|
69
|
+
getSeenCards(courseId?: string): Promise<string[]>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get cards that are actively scheduled for review
|
|
73
|
+
*/
|
|
74
|
+
getActiveCards(): Promise<string[]>;
|
|
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>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get user's course registrations
|
|
88
|
+
*/
|
|
89
|
+
getCourseRegistrationsDoc(): Promise<CourseRegistrationDoc>;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the registration doc for a specific course.
|
|
93
|
+
* @param courseId
|
|
94
|
+
*/
|
|
95
|
+
getCourseRegDoc(courseId: string): Promise<CourseRegistration>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get user's active courses
|
|
99
|
+
*/
|
|
100
|
+
getActiveCourses(): Promise<CourseRegistration[]>;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get user's pending reviews
|
|
104
|
+
*/
|
|
105
|
+
getPendingReviews(courseId?: string): Promise<ScheduledCard[]>;
|
|
106
|
+
|
|
107
|
+
getActivityRecords(): Promise<ActivityRecord[]>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Schedule a card for review
|
|
111
|
+
*/
|
|
112
|
+
scheduleCardReview(review: {
|
|
113
|
+
user: string;
|
|
114
|
+
course_id: string;
|
|
115
|
+
card_id: string;
|
|
116
|
+
time: Moment;
|
|
117
|
+
scheduledFor: 'course' | 'classroom';
|
|
118
|
+
schedulingAgentId: string;
|
|
119
|
+
}): Promise<void>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Remove a scheduled card review
|
|
123
|
+
*/
|
|
124
|
+
removeScheduledCardReview(reviewId: string): Promise<void>;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Register user for a classroom
|
|
128
|
+
*/
|
|
129
|
+
registerForClassroom(
|
|
130
|
+
classId: string,
|
|
131
|
+
registerAs: 'student' | 'teacher' | 'aide' | 'admin'
|
|
132
|
+
): Promise<PouchDB.Core.Response>;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Drop user from classroom
|
|
136
|
+
*/
|
|
137
|
+
dropFromClassroom(classId: string): Promise<PouchDB.Core.Response>;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get user's classroom registrations
|
|
141
|
+
*/
|
|
142
|
+
getUserClassrooms(): Promise<ClassroomRegistrationDoc>;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get user's active classes
|
|
146
|
+
*/
|
|
147
|
+
getActiveClasses(): Promise<string[]>;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Update user's ELO rating for a course
|
|
151
|
+
*/
|
|
152
|
+
updateUserElo(courseId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;
|
|
153
|
+
|
|
154
|
+
getCourseInterface(courseId: string): Promise<UsrCrsDataInterface>;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface UserCourseSettings {
|
|
158
|
+
[setting: string]: string | number | boolean;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface UserCourseSetting {
|
|
162
|
+
key: string;
|
|
163
|
+
value: string | number | boolean;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// [ ] reconsider here. Should maybe be generic type based on <T extends StudyContentSource> ?
|
|
167
|
+
export interface UsrCrsDataInterface {
|
|
168
|
+
getScheduledReviewCount(): Promise<number>;
|
|
169
|
+
getCourseSettings(): Promise<UserCourseSettings>;
|
|
170
|
+
updateCourseSettings(updates: UserCourseSetting[]): void; // [ ] return a result of some sort?
|
|
171
|
+
// getRegistrationDoc(): Promise<CourseRegistration>;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export type ClassroomRegistrationDesignation = 'student' | 'teacher' | 'aide' | 'admin';
|
|
175
|
+
|
|
176
|
+
export interface ClassroomRegistration {
|
|
177
|
+
classID: string;
|
|
178
|
+
registeredAs: ClassroomRegistrationDesignation;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface ClassroomRegistrationDoc {
|
|
182
|
+
registrations: ClassroomRegistration[];
|
|
183
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ScheduledCard } from '../types/user';
|
|
2
|
+
import { CourseDBInterface } from '../interfaces/courseDB';
|
|
3
|
+
import { UserDBInterface } from '../interfaces/userDB';
|
|
4
|
+
import { ContentNavigator } from './index';
|
|
5
|
+
import { CourseElo } from '@vue-skuilder/common';
|
|
6
|
+
import { StudySessionReviewItem, StudySessionNewItem } from '..';
|
|
7
|
+
|
|
8
|
+
export default class ELONavigator extends ContentNavigator {
|
|
9
|
+
user: UserDBInterface;
|
|
10
|
+
course: CourseDBInterface;
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
user: UserDBInterface,
|
|
14
|
+
course: CourseDBInterface
|
|
15
|
+
// The ELO strategy is non-parameterized.
|
|
16
|
+
//
|
|
17
|
+
// It instead relies on existing meta data from the course and user with respect to
|
|
18
|
+
//
|
|
19
|
+
//
|
|
20
|
+
// strategy?: ContentNavigationStrategyData
|
|
21
|
+
) {
|
|
22
|
+
super();
|
|
23
|
+
this.user = user;
|
|
24
|
+
this.course = course;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {
|
|
28
|
+
type ratedReview = ScheduledCard & CourseElo;
|
|
29
|
+
|
|
30
|
+
const reviews = await this.user.getPendingReviews(this.course.getCourseID()); // todo: this adds a db round trip - should be server side
|
|
31
|
+
const elo = await this.course.getCardEloData(reviews.map((r) => r.cardId));
|
|
32
|
+
|
|
33
|
+
const ratedReviews = reviews.map((r, i) => {
|
|
34
|
+
const ratedR: ratedReview = {
|
|
35
|
+
...r,
|
|
36
|
+
...elo[i],
|
|
37
|
+
};
|
|
38
|
+
return ratedR;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
ratedReviews.sort((a, b) => {
|
|
42
|
+
return a.global.score - b.global.score;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return ratedReviews.map((r) => {
|
|
46
|
+
return {
|
|
47
|
+
...r,
|
|
48
|
+
contentSourceType: 'course',
|
|
49
|
+
contentSourceID: this.course.getCourseID(),
|
|
50
|
+
cardID: r.cardId,
|
|
51
|
+
courseID: r.courseId,
|
|
52
|
+
qualifiedID: `${r.courseId}-${r.cardId}`,
|
|
53
|
+
reviewID: r._id,
|
|
54
|
+
status: 'review',
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {
|
|
60
|
+
const activeCards = await this.user.getActiveCards();
|
|
61
|
+
return (
|
|
62
|
+
await this.course.getCardsCenteredAtELO({ limit: limit, elo: 'user' }, (c: string) => {
|
|
63
|
+
if (activeCards.some((ac) => c.includes(ac))) {
|
|
64
|
+
return false;
|
|
65
|
+
} else {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
).map((c) => {
|
|
70
|
+
return {
|
|
71
|
+
...c,
|
|
72
|
+
status: 'new',
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
StudyContentSource,
|
|
3
|
+
UserDBInterface,
|
|
4
|
+
CourseDBInterface,
|
|
5
|
+
StudySessionReviewItem,
|
|
6
|
+
StudySessionNewItem,
|
|
7
|
+
} from '..';
|
|
8
|
+
import { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';
|
|
9
|
+
import { ScheduledCard } from '../types/user';
|
|
10
|
+
import { logger } from '../../util/logger';
|
|
11
|
+
|
|
12
|
+
export enum Navigators {
|
|
13
|
+
ELO = 'elo',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A content-navigator provides runtime steering of study sessions.
|
|
18
|
+
*/
|
|
19
|
+
export abstract class ContentNavigator implements StudyContentSource {
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @param user
|
|
23
|
+
* @param strategyData
|
|
24
|
+
* @returns the runtime object used to steer a study session.
|
|
25
|
+
*/
|
|
26
|
+
static async create(
|
|
27
|
+
user: UserDBInterface,
|
|
28
|
+
course: CourseDBInterface,
|
|
29
|
+
strategyData: ContentNavigationStrategyData
|
|
30
|
+
): Promise<ContentNavigator> {
|
|
31
|
+
const implementingClass = strategyData.implementingClass;
|
|
32
|
+
let NavigatorImpl;
|
|
33
|
+
|
|
34
|
+
// Try different extension variations
|
|
35
|
+
const variations = ['', '.js', '.ts'];
|
|
36
|
+
|
|
37
|
+
for (const ext of variations) {
|
|
38
|
+
try {
|
|
39
|
+
const module = await import(`./${implementingClass}${ext}`);
|
|
40
|
+
NavigatorImpl = module.default;
|
|
41
|
+
break; // Break the loop if loading succeeds
|
|
42
|
+
} catch (e) {
|
|
43
|
+
// Continue to next variation if this one fails
|
|
44
|
+
logger.debug(`Failed to load with extension ${ext}:`, e);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!NavigatorImpl) {
|
|
49
|
+
throw new Error(`Could not load navigator implementation for: ${implementingClass}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return new NavigatorImpl(user, course, strategyData);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
abstract getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;
|
|
56
|
+
abstract getNewCards(n?: number): Promise<StudySessionNewItem[]>;
|
|
57
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# `db/src/core`
|
|
2
|
+
|
|
3
|
+
Implementation-agnostic type definitions for skuilder data layer.
|
|
4
|
+
|
|
5
|
+
### Overview
|
|
6
|
+
|
|
7
|
+
- `types` - structured data types
|
|
8
|
+
- `interfaces` - behaviour contracts for reading and writing data
|
|
9
|
+
- `utils` - generic helper functions for working with these data types and interfaces
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DocType, SkuilderCourseData } from './types-legacy';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
export interface ContentNavigationStrategyData extends SkuilderCourseData {
|
|
7
|
+
id: string;
|
|
8
|
+
docType: DocType.NAVIGATION_STRATEGY;
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
/**
|
|
12
|
+
The name of the class that implements the navigation strategy at runtime.
|
|
13
|
+
*/
|
|
14
|
+
implementingClass: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
A representation of the strategy's parameterization - to be deserialized
|
|
18
|
+
by the implementing class's constructor at runtime.
|
|
19
|
+
*/
|
|
20
|
+
serializedData: string;
|
|
21
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { CourseElo, Answer, Evaluation } from '@vue-skuilder/common';
|
|
2
|
+
import { Moment } from 'moment';
|
|
3
|
+
import { logger } from '../../util/logger';
|
|
4
|
+
|
|
5
|
+
export const GuestUsername: string = 'Guest';
|
|
6
|
+
|
|
7
|
+
export const log = (message: string): void => {
|
|
8
|
+
logger.log(message);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export enum DocType {
|
|
12
|
+
DISPLAYABLE_DATA = 'DISPLAYABLE_DATA',
|
|
13
|
+
CARD = 'CARD',
|
|
14
|
+
DATASHAPE = 'DATASHAPE',
|
|
15
|
+
QUESTIONTYPE = 'QUESTION',
|
|
16
|
+
VIEW = 'VIEW',
|
|
17
|
+
PEDAGOGY = 'PEDAGOGY',
|
|
18
|
+
CARDRECORD = 'CARDRECORD',
|
|
19
|
+
SCHEDULED_CARD = 'SCHEDULED_CARD',
|
|
20
|
+
TAG = 'TAG',
|
|
21
|
+
NAVIGATION_STRATEGY = 'NAVIGATION_STRATEGY',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Interface for all data on course content and pedagogy stored
|
|
26
|
+
* in the c/pouch database.
|
|
27
|
+
*/
|
|
28
|
+
export interface SkuilderCourseData {
|
|
29
|
+
course: string;
|
|
30
|
+
docType: DocType;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Tag extends SkuilderCourseData {
|
|
34
|
+
docType: DocType.TAG;
|
|
35
|
+
name: string;
|
|
36
|
+
snippet: string; // 200 char description of the tag
|
|
37
|
+
wiki: string; // 3000 char md-friendly description
|
|
38
|
+
taggedCards: PouchDB.Core.DocumentId[];
|
|
39
|
+
}
|
|
40
|
+
export interface TagStub {
|
|
41
|
+
name: string;
|
|
42
|
+
snippet: string;
|
|
43
|
+
count: number; // the number of cards that have this tag applied
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface CardData extends SkuilderCourseData {
|
|
47
|
+
docType: DocType.CARD;
|
|
48
|
+
id_displayable_data: PouchDB.Core.DocumentId[];
|
|
49
|
+
id_view: PouchDB.Core.DocumentId;
|
|
50
|
+
elo: CourseElo;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A list of populated courses in the DB */
|
|
54
|
+
export interface CourseListData extends PouchDB.Core.Response {
|
|
55
|
+
courses: string[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The data used to hydrate viewable components (questions, info, etc)
|
|
60
|
+
*/
|
|
61
|
+
export interface DisplayableData extends SkuilderCourseData {
|
|
62
|
+
docType: DocType.DISPLAYABLE_DATA;
|
|
63
|
+
author?: string;
|
|
64
|
+
id_datashape: PouchDB.Core.DocumentId;
|
|
65
|
+
data: Field[];
|
|
66
|
+
_attachments?: { [index: string]: PouchDB.Core.FullAttachment };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface Field {
|
|
70
|
+
data: unknown;
|
|
71
|
+
name: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface DataShapeData extends SkuilderCourseData {
|
|
75
|
+
docType: DocType.DATASHAPE;
|
|
76
|
+
_id: PouchDB.Core.DocumentId;
|
|
77
|
+
questionTypes: PouchDB.Core.DocumentId[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface QuestionData extends SkuilderCourseData {
|
|
81
|
+
docType: DocType.QUESTIONTYPE;
|
|
82
|
+
_id: PouchDB.Core.DocumentId;
|
|
83
|
+
viewList: string[];
|
|
84
|
+
dataShapeList: PouchDB.Core.DocumentId[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const cardHistoryPrefix = 'cardH';
|
|
88
|
+
|
|
89
|
+
export interface CardHistory<T extends CardRecord> {
|
|
90
|
+
_id: PouchDB.Core.DocumentId;
|
|
91
|
+
/**
|
|
92
|
+
* The CouchDB id of the card
|
|
93
|
+
*/
|
|
94
|
+
cardID: PouchDB.Core.DocumentId;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The ID of the course
|
|
98
|
+
*/
|
|
99
|
+
courseID: string;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The to-date largest interval between successful
|
|
103
|
+
* card reviews. `0` indicates no successful reviews.
|
|
104
|
+
*/
|
|
105
|
+
bestInterval: number;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The number of times that a card has been
|
|
109
|
+
* failed in review
|
|
110
|
+
*/
|
|
111
|
+
lapses: number;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The number of consecutive successful impressions
|
|
115
|
+
* on this card
|
|
116
|
+
*/
|
|
117
|
+
streak: number;
|
|
118
|
+
|
|
119
|
+
records: T[];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface CardRecord {
|
|
123
|
+
/**
|
|
124
|
+
* The CouchDB id of the card
|
|
125
|
+
*/
|
|
126
|
+
cardID: string;
|
|
127
|
+
/**
|
|
128
|
+
* The ID of the course
|
|
129
|
+
*/
|
|
130
|
+
courseID: string;
|
|
131
|
+
/**
|
|
132
|
+
* Number of milliseconds that the user spent before dismissing
|
|
133
|
+
* the card (ie, "I've read this" or "here is my answer")
|
|
134
|
+
*
|
|
135
|
+
* //TODO: this (sometimes?) wants to be replaced with a rich
|
|
136
|
+
* recording of user activity in working the question
|
|
137
|
+
*/
|
|
138
|
+
timeSpent: number;
|
|
139
|
+
/**
|
|
140
|
+
* The date-time that the card was rendered. timeStamp + timeSpent will give the
|
|
141
|
+
* time of user submission.
|
|
142
|
+
*/
|
|
143
|
+
timeStamp: Moment;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface QuestionRecord extends CardRecord, Evaluation {
|
|
147
|
+
userAnswer: Answer;
|
|
148
|
+
/**
|
|
149
|
+
* The number of incorrect user submissions prededing this submisstion.
|
|
150
|
+
*
|
|
151
|
+
* eg, if a user is asked 7*6=__, submitting 46, 48, 42 will result in three
|
|
152
|
+
* records being created having 0, 1, and 2 as their recorded 'priorAttempts' values
|
|
153
|
+
*/
|
|
154
|
+
priorAttemps: number;
|
|
155
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { CourseElo } from '@vue-skuilder/common';
|
|
2
|
+
import { Moment } from 'moment';
|
|
3
|
+
|
|
4
|
+
export interface UserConfig {
|
|
5
|
+
darkMode: boolean;
|
|
6
|
+
likesConfetti: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ActivityRecord {
|
|
10
|
+
timeStamp: number | string;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CourseRegistration {
|
|
15
|
+
status?: 'active' | 'dropped' | 'maintenance-mode' | 'preview';
|
|
16
|
+
courseID: string;
|
|
17
|
+
admin: boolean;
|
|
18
|
+
moderator: boolean;
|
|
19
|
+
user: boolean;
|
|
20
|
+
settings?: {
|
|
21
|
+
[setting: string]: string | number | boolean;
|
|
22
|
+
};
|
|
23
|
+
elo: number | CourseElo;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface StudyWeights {
|
|
27
|
+
[courseID: string]: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CourseRegistrationDoc {
|
|
31
|
+
courses: CourseRegistration[];
|
|
32
|
+
studyWeight: StudyWeights;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ScheduledCard {
|
|
36
|
+
_id: PouchDB.Core.DocumentId;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The docID of the card to be reviewed
|
|
40
|
+
*/
|
|
41
|
+
cardId: PouchDB.Core.DocumentId;
|
|
42
|
+
/**
|
|
43
|
+
* The ID of the course
|
|
44
|
+
*/
|
|
45
|
+
courseId: string;
|
|
46
|
+
/**
|
|
47
|
+
* The time at which the card becomes eligible for review.
|
|
48
|
+
*
|
|
49
|
+
* (Should probably be UTC adjusted so that performance is
|
|
50
|
+
* not wonky across time zones)
|
|
51
|
+
*/
|
|
52
|
+
reviewTime: Moment;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The time at which this scheduled event was created.
|
|
56
|
+
*/
|
|
57
|
+
scheduledAt: Moment;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Classifying whether this card is scheduled on behalf of a
|
|
61
|
+
* user-registered course or by as assigned content from a
|
|
62
|
+
* user-registered classroom
|
|
63
|
+
*/
|
|
64
|
+
scheduledFor: 'course' | 'classroom';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The ID of the course or classroom that requested this card
|
|
68
|
+
*/
|
|
69
|
+
schedulingAgentId: string;
|
|
70
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { cardHistoryPrefix, CardHistory, CardRecord, QuestionRecord } from '../types/types-legacy';
|
|
2
|
+
|
|
3
|
+
export function areQuestionRecords(h: CardHistory<CardRecord>): h is CardHistory<QuestionRecord> {
|
|
4
|
+
return isQuestionRecord(h.records[0]);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function isQuestionRecord(c: CardRecord): c is QuestionRecord {
|
|
8
|
+
return (c as QuestionRecord).userAnswer !== undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getCardHistoryID(courseID: string, cardID: string): PouchDB.Core.DocumentId {
|
|
12
|
+
return `${cardHistoryPrefix}-${courseID}-${cardID}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function parseCardHistoryID(id: string): {
|
|
16
|
+
courseID: string;
|
|
17
|
+
cardID: string;
|
|
18
|
+
} {
|
|
19
|
+
const split = id.split('-');
|
|
20
|
+
let error: string = '';
|
|
21
|
+
error += split.length === 3 ? '' : `\n\tgiven ID has incorrect number of '-' characters`;
|
|
22
|
+
error +=
|
|
23
|
+
split[0] === cardHistoryPrefix ? '' : `\n\tgiven ID does not start with ${cardHistoryPrefix}`;
|
|
24
|
+
|
|
25
|
+
if (split.length === 3 && split[0] === cardHistoryPrefix) {
|
|
26
|
+
return {
|
|
27
|
+
courseID: split[1],
|
|
28
|
+
cardID: split[2],
|
|
29
|
+
};
|
|
30
|
+
} else {
|
|
31
|
+
throw new Error('parseCardHistory Error:' + error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface PouchDBError extends Error {
|
|
36
|
+
error?: string;
|
|
37
|
+
reason?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function docIsDeleted(e: PouchDBError): boolean {
|
|
41
|
+
return Boolean(e?.error === 'not_found' && e?.reason === 'deleted');
|
|
42
|
+
}
|