Skip to content

Commit 15336e8

Browse files
authored
Improve db namespacing of documents (#811)
Closes #772 - **add DocTypePrefixes lookup** - **use indexed PREFIX rather than local const** - **centralize doctype prefixes defintion...** - **migrate usage of floating prefix definitions** - **migrate usage of floating prefix definitions...**
2 parents 51f4535 + 8f8735f commit 15336e8

File tree

11 files changed

+93
-93
lines changed

11 files changed

+93
-93
lines changed

packages/db/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"pouchdb-find": "^9.0.0"
5252
},
5353
"devDependencies": {
54+
"@types/uuid": "^10.0.0",
5455
"tsup": "^8.0.2",
5556
"typescript": "~5.7.2"
5657
}

packages/db/src/core/types/types-legacy.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,19 @@ export interface QuestionData extends SkuilderCourseData {
8686
dataShapeList: PouchDB.Core.DocumentId[];
8787
}
8888

89-
export const cardHistoryPrefix = 'cardH';
89+
export const DocTypePrefixes: Record<string, string> = {
90+
[DocType.CARD]: 'c',
91+
[DocType.DISPLAYABLE_DATA]: 'dd',
92+
[DocType.TAG]: 'TAG',
93+
[DocType.CARDRECORD]: 'cardH',
94+
[DocType.SCHEDULED_CARD]: 'card_review_',
95+
// Add other doctypes here as they get prefixed IDs
96+
[DocType.DATASHAPE]: 'DATASHAPE',
97+
[DocType.QUESTIONTYPE]: 'QUESTION',
98+
[DocType.VIEW]: 'VIEW',
99+
[DocType.PEDAGOGY]: 'PEDAGOGY',
100+
[DocType.NAVIGATION_STRATEGY]: 'NAVIGATION_STRATEGY',
101+
};
90102

91103
export interface CardHistory<T extends CardRecord> {
92104
_id: PouchDB.Core.DocumentId;

packages/db/src/core/util/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { cardHistoryPrefix, CardHistory, CardRecord, QuestionRecord } from '../types/types-legacy';
1+
import { DocType, DocTypePrefixes, CardHistory, CardRecord, QuestionRecord } from '../types/types-legacy';
22

33
export function areQuestionRecords(h: CardHistory<CardRecord>): h is CardHistory<QuestionRecord> {
44
return isQuestionRecord(h.records[0]);
@@ -9,7 +9,7 @@ export function isQuestionRecord(c: CardRecord): c is QuestionRecord {
99
}
1010

1111
export function getCardHistoryID(courseID: string, cardID: string): PouchDB.Core.DocumentId {
12-
return `${cardHistoryPrefix}-${courseID}-${cardID}`;
12+
return `${DocTypePrefixes[DocType.CARDRECORD]}-${courseID}-${cardID}`;
1313
}
1414

1515
export function parseCardHistoryID(id: string): {
@@ -20,9 +20,10 @@ export function parseCardHistoryID(id: string): {
2020
let error: string = '';
2121
error += split.length === 3 ? '' : `\n\tgiven ID has incorrect number of '-' characters`;
2222
error +=
23-
split[0] === cardHistoryPrefix ? '' : `\n\tgiven ID does not start with ${cardHistoryPrefix}`;
23+
split[0] === DocTypePrefixes[DocType.CARDRECORD] ? '' : `
24+
given ID does not start with ${DocTypePrefixes[DocType.CARDRECORD]}`;
2425

25-
if (split.length === 3 && split[0] === cardHistoryPrefix) {
26+
if (split.length === 3 && split[0] === DocTypePrefixes[DocType.CARDRECORD]) {
2627
return {
2728
courseID: split[1],
2829
cardID: split[2],

packages/db/src/impl/common/BaseUserDB.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { DocType, DocTypePrefixes } from '@db/core';
12
import { getCardHistoryID } from '@db/core/util';
23
import { CourseElo, Status } from '@vue-skuilder/common';
34
import moment, { Moment } from 'moment';
@@ -23,7 +24,6 @@ import type { SyncStrategy } from './SyncStrategy';
2324
import {
2425
filterAllDocsByPrefix,
2526
getStartAndEndKeys,
26-
REVIEW_PREFIX,
2727
REVIEW_TIME_FORMAT,
2828
getLocalUserDB,
2929
scheduleCardReviewLocal,
@@ -38,8 +38,6 @@ const log = (s: any) => {
3838
logger.info(s);
3939
};
4040

41-
const cardHistoryPrefix = 'cardH-';
42-
4341
// console.log(`Connecting to remote: ${remoteStr}`);
4442

4543
interface DesignDoc {
@@ -174,8 +172,8 @@ Currently logged-in as ${this._username}.`
174172
const id = row.id;
175173
// Delete user progress data but preserve core user documents
176174
return (
177-
id.startsWith(cardHistoryPrefix) || // Card interaction history
178-
id.startsWith(REVIEW_PREFIX) || // Scheduled reviews
175+
id.startsWith(DocTypePrefixes[DocType.CARDRECORD]) || // Card interaction history
176+
id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD]) || // Scheduled reviews
179177
id === BaseUser.DOC_IDS.COURSE_REGISTRATIONS || // Course registrations
180178
id === BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS || // Classroom registrations
181179
id === BaseUser.DOC_IDS.CONFIG // User config
@@ -264,7 +262,7 @@ Currently logged-in as ${this._username}.`
264262
*
265263
*/
266264
public async getActiveCards() {
267-
const keys = getStartAndEndKeys(REVIEW_PREFIX);
265+
const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);
268266

269267
const reviews = await this.remoteDB.allDocs<ScheduledCard>({
270268
startkey: keys.startkey,
@@ -359,7 +357,7 @@ Currently logged-in as ${this._username}.`
359357
}
360358

361359
private async getReviewstoDate(targetDate: Moment, course_id?: string) {
362-
const keys = getStartAndEndKeys(REVIEW_PREFIX);
360+
const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);
363361

364362
const reviews = await this.remoteDB.allDocs<ScheduledCard>({
365363
startkey: keys.startkey,
@@ -374,8 +372,11 @@ Currently logged-in as ${this._username}.`
374372
);
375373
return reviews.rows
376374
.filter((r) => {
377-
if (r.id.startsWith(REVIEW_PREFIX)) {
378-
const date = moment.utc(r.id.substr(REVIEW_PREFIX.length), REVIEW_TIME_FORMAT);
375+
if (r.id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD])) {
376+
const date = moment.utc(
377+
r.id.substr(DocTypePrefixes[DocType.SCHEDULED_CARD].length),
378+
REVIEW_TIME_FORMAT
379+
);
379380
if (targetDate.isAfter(date)) {
380381
if (course_id === undefined || r.doc!.courseId === course_id) {
381382
return true;
@@ -816,7 +817,7 @@ Currently logged-in as ${this._username}.`
816817
* @param course_id optional specification of individual course
817818
*/
818819
async getSeenCards(course_id?: string) {
819-
let prefix = cardHistoryPrefix;
820+
let prefix = DocTypePrefixes[DocType.CARDRECORD];
820821
if (course_id) {
821822
prefix += course_id;
822823
}
@@ -826,8 +827,8 @@ Currently logged-in as ${this._username}.`
826827
// const docs = await this.localDB.allDocs({});
827828
const ret: PouchDB.Core.DocumentId[] = [];
828829
docs.rows.forEach((row) => {
829-
if (row.id.startsWith(cardHistoryPrefix)) {
830-
ret.push(row.id.substr(cardHistoryPrefix.length));
830+
if (row.id.startsWith(DocTypePrefixes[DocType.CARDRECORD])) {
831+
ret.push(row.id.substr(DocTypePrefixes[DocType.CARDRECORD].length));
831832
}
832833
});
833834
return ret;
@@ -840,7 +841,7 @@ Currently logged-in as ${this._username}.`
840841
async getHistory() {
841842
const cards = await filterAllDocsByPrefix<CardHistory<CardRecord>>(
842843
this.remoteDB,
843-
cardHistoryPrefix,
844+
DocTypePrefixes[DocType.CARDRECORD],
844845
{
845846
include_docs: true,
846847
attachments: false,

packages/db/src/impl/common/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ export type {
1111
} from './types';
1212
export { BaseUser } from './BaseUserDB';
1313
export {
14-
REVIEW_PREFIX,
1514
REVIEW_TIME_FORMAT,
1615
hexEncode,
1716
filterAllDocsByPrefix,

packages/db/src/impl/common/userDBHelpers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// packages/db/src/impl/common/userDBHelpers.ts
22

33
import moment from 'moment';
4+
import { DocType, DocTypePrefixes } from '@db/core';
45
import { logger } from '../../util/logger';
56
import { ScheduledCard } from '@db/core/types/user';
67

7-
export const REVIEW_PREFIX: string = 'card_review_';
88
export const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';
99

1010
import pouch from '../couch/pouchdb-setup';
@@ -123,7 +123,7 @@ export function scheduleCardReviewLocal(
123123
const now = moment.utc();
124124
logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);
125125
void userDB.put<ScheduledCard>({
126-
_id: REVIEW_PREFIX + review.time.format(REVIEW_TIME_FORMAT),
126+
_id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),
127127
cardId: review.card_id,
128128
reviewTime: review.time,
129129
courseId: review.course_id,

packages/db/src/impl/couch/courseAPI.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ import { NameSpacer, ShapeDescriptor } from '@vue-skuilder/common';
66
import { CourseConfig, DataShape } from '@vue-skuilder/common';
77
import { CourseElo, blankCourseElo, toCourseElo } from '@vue-skuilder/common';
88
import { CourseDB, createTag } from './courseDB';
9-
import { CardData, DisplayableData, DocType, Tag } from '../../core/types/types-legacy';
9+
import { CardData, DisplayableData, DocType, Tag, DocTypePrefixes } from '../../core/types/types-legacy';
1010
import { prepareNote55 } from '@vue-skuilder/common';
1111
import { BaseUser } from '../common';
1212
import { logger } from '@db/util/logger';
13+
import { v4 as uuidv4 } from 'uuid';
1314

1415
/**
1516
*
@@ -33,9 +34,8 @@ export async function addNote55(
3334
): Promise<PouchDB.Core.Response> {
3435
const db = getCourseDB(courseID);
3536
const payload = prepareNote55(courseID, codeCourse, shape, data, author, tags, uploads);
36-
// [ ] NAMESPACING: consider put( _id: "displayable_data-uuid")
37-
// consider also semantic hashing
38-
const result = await db.post<DisplayableData>(payload);
37+
const _id = `${DocTypePrefixes[DocType.DISPLAYABLE_DATA]}-${uuidv4()}`;
38+
const result = await db.put<DisplayableData>({ ...payload, _id });
3939

4040
const dataShapeId = NameSpacer.getDataShapeString({
4141
course: codeCourse,
@@ -153,9 +153,10 @@ async function addCard(
153153
tags: string[],
154154
author: string
155155
): Promise<PouchDB.Core.Response> {
156-
// [ ] NAMESPACING: consider put( _id: "card-uuid")
157156
const db = getCourseDB(courseID);
158-
const card = await db.post<CardData>({
157+
const _id = `${DocTypePrefixes[DocType.CARD]}-${uuidv4()}`;
158+
const card = await db.put<CardData>({
159+
_id,
159160
course,
160161
id_displayable_data,
161162
id_view,

packages/db/src/impl/couch/index.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { ENV } from '@db/factory';
2-
import { DocType, GuestUsername, log, SkuilderCourseData } from '../../core/types/types-legacy';
2+
import {
3+
DocType,
4+
DocTypePrefixes,
5+
GuestUsername,
6+
log,
7+
SkuilderCourseData,
8+
} from '../../core/types/types-legacy';
39
// import { getCurrentUser } from '../../stores/useAuthStore';
410
import moment, { Moment } from 'moment';
511
import { logger } from '@db/util/logger';
@@ -155,7 +161,6 @@ export async function getRandomCards(courseIDs: string[]) {
155161
}
156162
}
157163

158-
export const REVIEW_PREFIX: string = 'card_review_';
159164
export const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';
160165

161166
export function getCouchUserDB(username: string): PouchDB.Database {
@@ -191,7 +196,7 @@ export function scheduleCardReview(review: {
191196
const now = moment.utc();
192197
logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);
193198
void getCouchUserDB(review.user).put<ScheduledCard>({
194-
_id: REVIEW_PREFIX + review.time.format(REVIEW_TIME_FORMAT),
199+
_id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),
195200
cardId: review.card_id,
196201
reviewTime: review.time,
197202
courseId: review.course_id,

packages/db/src/impl/couch/user-course-relDB.ts

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,18 @@ import {
44
UserCourseSettings,
55
UsrCrsDataInterface,
66
} from '@db/core';
7+
78
import moment, { Moment } from 'moment';
8-
import { getStartAndEndKeys, REVIEW_PREFIX, REVIEW_TIME_FORMAT } from '.';
9-
import { CourseDB } from './courseDB';
10-
import { User } from './userDB';
9+
10+
import { UserDBInterface } from '@db/core';
1111
import { logger } from '../../util/logger';
1212

1313
export class UsrCrsData implements UsrCrsDataInterface {
14-
private user: User;
15-
private course: CourseDB;
14+
private user: UserDBInterface;
1615
private _courseId: string;
1716

18-
constructor(user: User, courseId: string) {
17+
constructor(user: UserDBInterface, courseId: string) {
1918
this.user = user;
20-
this.course = new CourseDB(courseId, async () => this.user);
2119
this._courseId = courseId;
2220
}
2321

@@ -47,32 +45,24 @@ export class UsrCrsData implements UsrCrsDataInterface {
4745
}
4846
}
4947
public updateCourseSettings(updates: UserCourseSetting[]): void {
50-
void this.user.updateCourseSettings(this._courseId, updates);
48+
// TODO: Add updateCourseSettings method to UserDBInterface
49+
// For now, we'll need to cast to access the concrete implementation
50+
if ('updateCourseSettings' in this.user) {
51+
void (this.user as any).updateCourseSettings(this._courseId, updates);
52+
}
5153
}
5254

5355
private async getReviewstoDate(targetDate: Moment) {
54-
const keys = getStartAndEndKeys(REVIEW_PREFIX);
55-
56-
const reviews = await this.user.remote().allDocs<ScheduledCard>({
57-
startkey: keys.startkey,
58-
endkey: keys.endkey,
59-
include_docs: true,
60-
});
56+
// Use the interface method instead of direct database access
57+
const allReviews = await this.user.getPendingReviews(this._courseId);
6158

6259
logger.debug(
6360
`Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`
6461
);
65-
return reviews.rows
66-
.filter((r) => {
67-
if (r.id.startsWith(REVIEW_PREFIX)) {
68-
const date = moment.utc(r.id.substr(REVIEW_PREFIX.length), REVIEW_TIME_FORMAT);
69-
if (targetDate.isAfter(date)) {
70-
if (this._courseId === undefined || r.doc!.courseId === this._courseId) {
71-
return true;
72-
}
73-
}
74-
}
75-
})
76-
.map((r) => r.doc!);
62+
63+
return allReviews.filter((review: ScheduledCard) => {
64+
const reviewTime = moment.utc(review.reviewTime);
65+
return targetDate.isAfter(reviewTime);
66+
});
7767
}
7868
}

0 commit comments

Comments
 (0)