Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/db/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"pouchdb-find": "^9.0.0"
},
"devDependencies": {
"@types/uuid": "^10.0.0",
"tsup": "^8.0.2",
"typescript": "~5.7.2"
}
Expand Down
14 changes: 13 additions & 1 deletion packages/db/src/core/types/types-legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,19 @@ export interface QuestionData extends SkuilderCourseData {
dataShapeList: PouchDB.Core.DocumentId[];
}

export const cardHistoryPrefix = 'cardH';
export const DocTypePrefixes: Record<string, string> = {
[DocType.CARD]: 'c',
[DocType.DISPLAYABLE_DATA]: 'dd',
[DocType.TAG]: 'TAG',
[DocType.CARDRECORD]: 'cardH',
[DocType.SCHEDULED_CARD]: 'card_review_',
// Add other doctypes here as they get prefixed IDs
[DocType.DATASHAPE]: 'DATASHAPE',
[DocType.QUESTIONTYPE]: 'QUESTION',
[DocType.VIEW]: 'VIEW',
[DocType.PEDAGOGY]: 'PEDAGOGY',
[DocType.NAVIGATION_STRATEGY]: 'NAVIGATION_STRATEGY',
};

export interface CardHistory<T extends CardRecord> {
_id: PouchDB.Core.DocumentId;
Expand Down
9 changes: 5 additions & 4 deletions packages/db/src/core/util/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cardHistoryPrefix, CardHistory, CardRecord, QuestionRecord } from '../types/types-legacy';
import { DocType, DocTypePrefixes, CardHistory, CardRecord, QuestionRecord } from '../types/types-legacy';

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

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

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

if (split.length === 3 && split[0] === cardHistoryPrefix) {
if (split.length === 3 && split[0] === DocTypePrefixes[DocType.CARDRECORD]) {
return {
courseID: split[1],
cardID: split[2],
Expand Down
27 changes: 14 additions & 13 deletions packages/db/src/impl/common/BaseUserDB.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DocType, DocTypePrefixes } from '@db/core';
import { getCardHistoryID } from '@db/core/util';
import { CourseElo, Status } from '@vue-skuilder/common';
import moment, { Moment } from 'moment';
Expand All @@ -23,7 +24,6 @@ import type { SyncStrategy } from './SyncStrategy';
import {
filterAllDocsByPrefix,
getStartAndEndKeys,
REVIEW_PREFIX,
REVIEW_TIME_FORMAT,
getLocalUserDB,
scheduleCardReviewLocal,
Expand All @@ -38,8 +38,6 @@ const log = (s: any) => {
logger.info(s);
};

const cardHistoryPrefix = 'cardH-';

// console.log(`Connecting to remote: ${remoteStr}`);

interface DesignDoc {
Expand Down Expand Up @@ -174,8 +172,8 @@ Currently logged-in as ${this._username}.`
const id = row.id;
// Delete user progress data but preserve core user documents
return (
id.startsWith(cardHistoryPrefix) || // Card interaction history
id.startsWith(REVIEW_PREFIX) || // Scheduled reviews
id.startsWith(DocTypePrefixes[DocType.CARDRECORD]) || // Card interaction history
id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD]) || // Scheduled reviews
id === BaseUser.DOC_IDS.COURSE_REGISTRATIONS || // Course registrations
id === BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS || // Classroom registrations
id === BaseUser.DOC_IDS.CONFIG // User config
Expand Down Expand Up @@ -264,7 +262,7 @@ Currently logged-in as ${this._username}.`
*
*/
public async getActiveCards() {
const keys = getStartAndEndKeys(REVIEW_PREFIX);
const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);

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

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

const reviews = await this.remoteDB.allDocs<ScheduledCard>({
startkey: keys.startkey,
Expand All @@ -374,8 +372,11 @@ Currently logged-in as ${this._username}.`
);
return reviews.rows
.filter((r) => {
if (r.id.startsWith(REVIEW_PREFIX)) {
const date = moment.utc(r.id.substr(REVIEW_PREFIX.length), REVIEW_TIME_FORMAT);
if (r.id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD])) {
const date = moment.utc(
r.id.substr(DocTypePrefixes[DocType.SCHEDULED_CARD].length),
REVIEW_TIME_FORMAT
);
if (targetDate.isAfter(date)) {
if (course_id === undefined || r.doc!.courseId === course_id) {
return true;
Expand Down Expand Up @@ -816,7 +817,7 @@ Currently logged-in as ${this._username}.`
* @param course_id optional specification of individual course
*/
async getSeenCards(course_id?: string) {
let prefix = cardHistoryPrefix;
let prefix = DocTypePrefixes[DocType.CARDRECORD];
if (course_id) {
prefix += course_id;
}
Expand All @@ -826,8 +827,8 @@ Currently logged-in as ${this._username}.`
// const docs = await this.localDB.allDocs({});
const ret: PouchDB.Core.DocumentId[] = [];
docs.rows.forEach((row) => {
if (row.id.startsWith(cardHistoryPrefix)) {
ret.push(row.id.substr(cardHistoryPrefix.length));
if (row.id.startsWith(DocTypePrefixes[DocType.CARDRECORD])) {
ret.push(row.id.substr(DocTypePrefixes[DocType.CARDRECORD].length));
}
});
return ret;
Expand All @@ -840,7 +841,7 @@ Currently logged-in as ${this._username}.`
async getHistory() {
const cards = await filterAllDocsByPrefix<CardHistory<CardRecord>>(
this.remoteDB,
cardHistoryPrefix,
DocTypePrefixes[DocType.CARDRECORD],
{
include_docs: true,
attachments: false,
Expand Down
1 change: 0 additions & 1 deletion packages/db/src/impl/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ export type {
} from './types';
export { BaseUser } from './BaseUserDB';
export {
REVIEW_PREFIX,
REVIEW_TIME_FORMAT,
hexEncode,
filterAllDocsByPrefix,
Expand Down
4 changes: 2 additions & 2 deletions packages/db/src/impl/common/userDBHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// packages/db/src/impl/common/userDBHelpers.ts

import moment from 'moment';
import { DocType, DocTypePrefixes } from '@db/core';
import { logger } from '../../util/logger';
import { ScheduledCard } from '@db/core/types/user';

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

import pouch from '../couch/pouchdb-setup';
Expand Down Expand Up @@ -123,7 +123,7 @@ export function scheduleCardReviewLocal(
const now = moment.utc();
logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);
void userDB.put<ScheduledCard>({
_id: REVIEW_PREFIX + review.time.format(REVIEW_TIME_FORMAT),
_id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),
cardId: review.card_id,
reviewTime: review.time,
courseId: review.course_id,
Expand Down
13 changes: 7 additions & 6 deletions packages/db/src/impl/couch/courseAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { NameSpacer, ShapeDescriptor } from '@vue-skuilder/common';
import { CourseConfig, DataShape } from '@vue-skuilder/common';
import { CourseElo, blankCourseElo, toCourseElo } from '@vue-skuilder/common';
import { CourseDB, createTag } from './courseDB';
import { CardData, DisplayableData, DocType, Tag } from '../../core/types/types-legacy';
import { CardData, DisplayableData, DocType, Tag, DocTypePrefixes } from '../../core/types/types-legacy';
import { prepareNote55 } from '@vue-skuilder/common';
import { BaseUser } from '../common';
import { logger } from '@db/util/logger';
import { v4 as uuidv4 } from 'uuid';

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

const dataShapeId = NameSpacer.getDataShapeString({
course: codeCourse,
Expand Down Expand Up @@ -153,9 +153,10 @@ async function addCard(
tags: string[],
author: string
): Promise<PouchDB.Core.Response> {
// [ ] NAMESPACING: consider put( _id: "card-uuid")
const db = getCourseDB(courseID);
const card = await db.post<CardData>({
const _id = `${DocTypePrefixes[DocType.CARD]}-${uuidv4()}`;
const card = await db.put<CardData>({
_id,
course,
id_displayable_data,
id_view,
Expand Down
11 changes: 8 additions & 3 deletions packages/db/src/impl/couch/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { ENV } from '@db/factory';
import { DocType, GuestUsername, log, SkuilderCourseData } from '../../core/types/types-legacy';
import {
DocType,
DocTypePrefixes,
GuestUsername,
log,
SkuilderCourseData,
} from '../../core/types/types-legacy';
// import { getCurrentUser } from '../../stores/useAuthStore';
import moment, { Moment } from 'moment';
import { logger } from '@db/util/logger';
Expand Down Expand Up @@ -155,7 +161,6 @@ export async function getRandomCards(courseIDs: string[]) {
}
}

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

export function getCouchUserDB(username: string): PouchDB.Database {
Expand Down Expand Up @@ -191,7 +196,7 @@ export function scheduleCardReview(review: {
const now = moment.utc();
logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);
void getCouchUserDB(review.user).put<ScheduledCard>({
_id: REVIEW_PREFIX + review.time.format(REVIEW_TIME_FORMAT),
_id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),
cardId: review.card_id,
reviewTime: review.time,
courseId: review.course_id,
Expand Down
44 changes: 17 additions & 27 deletions packages/db/src/impl/couch/user-course-relDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,18 @@ import {
UserCourseSettings,
UsrCrsDataInterface,
} from '@db/core';

import moment, { Moment } from 'moment';
import { getStartAndEndKeys, REVIEW_PREFIX, REVIEW_TIME_FORMAT } from '.';
import { CourseDB } from './courseDB';
import { User } from './userDB';

import { UserDBInterface } from '@db/core';
import { logger } from '../../util/logger';

export class UsrCrsData implements UsrCrsDataInterface {
private user: User;
private course: CourseDB;
private user: UserDBInterface;
private _courseId: string;

constructor(user: User, courseId: string) {
constructor(user: UserDBInterface, courseId: string) {
this.user = user;
this.course = new CourseDB(courseId, async () => this.user);
this._courseId = courseId;
}

Expand Down Expand Up @@ -47,32 +45,24 @@ export class UsrCrsData implements UsrCrsDataInterface {
}
}
public updateCourseSettings(updates: UserCourseSetting[]): void {
void this.user.updateCourseSettings(this._courseId, updates);
// TODO: Add updateCourseSettings method to UserDBInterface
// For now, we'll need to cast to access the concrete implementation
if ('updateCourseSettings' in this.user) {
void (this.user as any).updateCourseSettings(this._courseId, updates);
}
}

private async getReviewstoDate(targetDate: Moment) {
const keys = getStartAndEndKeys(REVIEW_PREFIX);

const reviews = await this.user.remote().allDocs<ScheduledCard>({
startkey: keys.startkey,
endkey: keys.endkey,
include_docs: true,
});
// Use the interface method instead of direct database access
const allReviews = await this.user.getPendingReviews(this._courseId);

logger.debug(
`Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`
);
return reviews.rows
.filter((r) => {
if (r.id.startsWith(REVIEW_PREFIX)) {
const date = moment.utc(r.id.substr(REVIEW_PREFIX.length), REVIEW_TIME_FORMAT);
if (targetDate.isAfter(date)) {
if (this._courseId === undefined || r.doc!.courseId === this._courseId) {
return true;
}
}
}
})
.map((r) => r.doc!);

return allReviews.filter((review: ScheduledCard) => {
const reviewTime = moment.utc(review.reviewTime);
return targetDate.isAfter(reviewTime);
});
}
}
Loading