@vue-skuilder/db 0.1.13 → 0.1.14-2
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/core/index.js.map +1 -1
- package/dist/core/index.mjs.map +1 -1
- package/dist/impl/couch/index.js +0 -2
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +0 -2
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +0 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/impl/couch/courseLookupDB.ts +0 -1
package/dist/core/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/interfaces/adminDB.ts","../../src/core/interfaces/classroomDB.ts","../../src/impl/common/SyncStrategy.ts","../../src/util/logger.ts","../../src/core/types/types-legacy.ts","../../src/core/util/index.ts","../../src/impl/couch/pouchdb-setup.ts","../../src/util/tuiLogger.ts","../../src/util/dataDirectory.ts","../../src/impl/common/userDBHelpers.ts","../../src/util/Loggable.ts","../../src/impl/couch/updateQueue.ts","../../src/impl/couch/user-course-relDB.ts","../../src/impl/couch/clientCache.ts","../../src/impl/couch/courseAPI.ts","../../src/impl/couch/courseLookupDB.ts","../../src/core/navigators/elo.ts","../../src/core/navigators/hardcodedOrder.ts","../../src/core/navigators/index.ts","../../src/impl/couch/courseDB.ts","../../src/impl/couch/classroomDB.ts","../../src/impl/couch/adminDB.ts","../../src/impl/couch/auth.ts","../../src/impl/couch/CouchDBSyncStrategy.ts","../../src/impl/couch/index.ts","../../src/impl/common/BaseUserDB.ts","../../src/impl/common/index.ts","../../src/factory.ts","../../src/core/interfaces/contentSource.ts","../../src/core/interfaces/courseDB.ts","../../src/core/interfaces/dataLayerProvider.ts","../../src/core/interfaces/userDB.ts","../../src/core/interfaces/index.ts","../../src/core/types/user.ts","../../src/core/bulkImport/cardProcessor.ts","../../src/core/bulkImport/types.ts","../../src/core/bulkImport/index.ts","../../src/core/index.ts"],"sourcesContent":["import { ClassroomConfig, CourseConfig } from '@vue-skuilder/common';\n\n/**\n * Admin functionality\n */\nexport interface AdminDBInterface {\n /**\n * Get all users\n */\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n getUsers(): Promise<PouchDB.Core.Document<{}>[]>;\n\n /**\n * Get all courses\n */\n getCourses(): Promise<CourseConfig[]>;\n\n /**\n * Remove a course\n */\n removeCourse(id: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Get all classrooms\n */\n getClassrooms(): Promise<(ClassroomConfig & { _id: string })[]>;\n}\n","import { ClassroomConfig } from '@vue-skuilder/common';\nimport { ScheduledCard } from '../types/user';\nimport { StudySessionNewItem, StudySessionReviewItem } from './contentSource';\n\n/**\n * Classroom management\n */\nexport interface ClassroomDBInterface {\n /**\n * Get classroom config\n */\n getConfig(): ClassroomConfig;\n\n /**\n * Get assigned content\n */\n getAssignedContent(): Promise<AssignedContent[]>;\n}\n\nexport interface TeacherClassroomDBInterface extends ClassroomDBInterface {\n /**\n * For teacher interfaces: assign content\n */\n assignContent?(content: AssignedContent): Promise<boolean>;\n\n /**\n * For teacher interfaces: remove content\n */\n removeContent?(content: AssignedContent): Promise<void>;\n}\n\nexport interface StudentClassroomDBInterface extends ClassroomDBInterface {\n /**\n * For student interfaces: get pending reviews\n */\n getPendingReviews?(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;\n\n /**\n * For student interfaces: get new cards\n */\n getNewCards?(limit?: number): Promise<StudySessionNewItem[]>;\n}\n\nexport type AssignedContent = AssignedCourse | AssignedTag | AssignedCard;\n\nexport interface AssignedTag extends ContentBase {\n type: 'tag';\n tagID: string;\n}\nexport interface AssignedCourse extends ContentBase {\n type: 'course';\n}\nexport interface AssignedCard extends ContentBase {\n type: 'card';\n cardID: string;\n}\n\ninterface ContentBase {\n type: 'course' | 'tag' | 'card';\n /**\n * Username of the assigning teacher.\n */\n assignedBy: string;\n /**\n * Date the content was assigned.\n */\n assignedOn: moment.Moment;\n /**\n * A 'due' date for this assigned content, for scheduling content\n * in advance. Content will not be actively pushed to students until\n * this date.\n */\n activeOn: moment.Moment;\n courseID: string;\n}\n","// packages/db/src/impl/common/SyncStrategy.ts\n\nimport type { AccountCreationResult, AuthenticationResult } from './types';\n\n/**\n * Strategy interface for handling user data synchronization\n * Different implementations handle remote sync vs local-only storage\n */\nexport interface SyncStrategy {\n /**\n * Set up the remote database for a user\n * @param username The username to set up remote DB for\n * @returns PouchDB database instance (may be same as local for no-op)\n */\n setupRemoteDB(username: string): PouchDB.Database;\n\n /**\n * Get the database to use for write operations (local-first approach)\n * @param username The username to get write DB for\n * @returns PouchDB database instance for write operations\n */\n getWriteDB?(username: string): PouchDB.Database;\n\n /**\n * Start synchronization between local and remote databases\n * @param localDB The local PouchDB instance\n * @param remoteDB The remote PouchDB instance\n */\n startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;\n\n /**\n * Stop synchronization (optional - for cleanup)\n */\n stopSync?(): void;\n\n /**\n * Whether this strategy supports account creation\n */\n canCreateAccount(): boolean;\n\n /**\n * Whether this strategy supports authentication\n */\n canAuthenticate(): boolean;\n\n /**\n * Create a new user account (if supported)\n * @param username The username for the new account\n * @param password The password for the new account\n */\n createAccount?(username: string, password: string): Promise<AccountCreationResult>;\n\n /**\n * Authenticate a user (if supported)\n * @param username The username to authenticate\n * @param password The password to authenticate with\n */\n authenticate?(username: string, password: string): Promise<AuthenticationResult>;\n\n /**\n * Log out the current user (if supported)\n */\n logout?(): Promise<AuthenticationResult>;\n\n /**\n * Get the current logged-in username\n * Returns the username if logged in, or a default guest username\n */\n getCurrentUsername(): Promise<string>;\n}\n\n/**\n * Base class for sync strategies with common functionality\n */\nexport abstract class BaseSyncStrategy implements SyncStrategy {\n abstract setupRemoteDB(username: string): PouchDB.Database;\n abstract startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;\n abstract canCreateAccount(): boolean;\n abstract canAuthenticate(): boolean;\n abstract getCurrentUsername(): Promise<string>;\n\n stopSync?(): void {\n // Default no-op implementation\n }\n\n async createAccount(_username: string, _password: string): Promise<AccountCreationResult> {\n throw new Error('Account creation not supported by this sync strategy');\n }\n\n async authenticate(_username: string, _password: string): Promise<AuthenticationResult> {\n throw new Error('Authentication not supported by this sync strategy');\n }\n\n async logout(): Promise<AuthenticationResult> {\n throw new Error('Logout not supported by this sync strategy');\n }\n}\n","/**\n * Simple logging utility for @vue-skuilder/db package\n *\n * This utility provides environment-aware logging with ESLint suppressions\n * to resolve console statement violations while maintaining logging functionality.\n */\n\nconst isDevelopment = typeof process !== 'undefined' && process.env.NODE_ENV === 'development';\nconst _isBrowser = typeof window !== 'undefined';\n\nexport const logger = {\n /**\n * Debug-level logging - only shown in development\n */\n debug: (message: string, ...args: any[]): void => {\n if (isDevelopment) {\n // eslint-disable-next-line no-console\n console.debug(`[DB:DEBUG] ${message}`, ...args);\n }\n },\n\n /**\n * Info-level logging - general information\n */\n info: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.info(`[DB:INFO] ${message}`, ...args);\n },\n\n /**\n * Warning-level logging - potential issues\n */\n warn: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.warn(`[DB:WARN] ${message}`, ...args);\n },\n\n /**\n * Error-level logging - serious problems\n */\n error: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.error(`[DB:ERROR] ${message}`, ...args);\n },\n\n /**\n * Log function for backward compatibility with existing log() usage\n */\n log: (message: string, ...args: any[]): void => {\n if (isDevelopment) {\n // eslint-disable-next-line no-console\n console.log(`[DB:LOG] ${message}`, ...args);\n }\n },\n};\n","import { CourseElo, Answer, Evaluation } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\nimport { logger } from '../../util/logger';\n\nexport const GuestUsername: string = 'sk-guest-';\n\nexport const log = (message: string): void => {\n logger.log(message);\n};\n\nexport enum DocType {\n DISPLAYABLE_DATA = 'DISPLAYABLE_DATA',\n CARD = 'CARD',\n DATASHAPE = 'DATASHAPE',\n QUESTIONTYPE = 'QUESTION',\n VIEW = 'VIEW',\n PEDAGOGY = 'PEDAGOGY',\n CARDRECORD = 'CARDRECORD',\n SCHEDULED_CARD = 'SCHEDULED_CARD',\n TAG = 'TAG',\n NAVIGATION_STRATEGY = 'NAVIGATION_STRATEGY',\n}\n\nexport interface QualifiedCardID {\n courseID: string;\n cardID: string;\n}\n\n/**\n * Interface for all data on course content and pedagogy stored\n * in the c/pouch database.\n */\nexport interface SkuilderCourseData {\n course: string;\n docType: DocType;\n}\n\nexport interface Tag extends SkuilderCourseData {\n docType: DocType.TAG;\n name: string;\n snippet: string; // 200 char description of the tag\n wiki: string; // 3000 char md-friendly description\n taggedCards: PouchDB.Core.DocumentId[];\n author: string;\n}\nexport interface TagStub {\n name: string;\n snippet: string;\n count: number; // the number of cards that have this tag applied\n}\n\nexport interface CardData extends SkuilderCourseData {\n docType: DocType.CARD;\n id_displayable_data: PouchDB.Core.DocumentId[];\n id_view: PouchDB.Core.DocumentId;\n elo: CourseElo;\n author: string;\n}\n\n/** A list of populated courses in the DB */\nexport interface CourseListData extends PouchDB.Core.Response {\n courses: string[];\n}\n\n/**\n * The data used to hydrate viewable components (questions, info, etc)\n */\nexport interface DisplayableData extends SkuilderCourseData {\n docType: DocType.DISPLAYABLE_DATA;\n author?: string;\n id_datashape: PouchDB.Core.DocumentId;\n data: Field[];\n _attachments?: { [index: string]: PouchDB.Core.FullAttachment };\n}\n\nexport interface Field {\n data: unknown;\n name: string;\n}\n\nexport interface DataShapeData extends SkuilderCourseData {\n docType: DocType.DATASHAPE;\n _id: PouchDB.Core.DocumentId;\n questionTypes: PouchDB.Core.DocumentId[];\n}\n\nexport interface QuestionData extends SkuilderCourseData {\n docType: DocType.QUESTIONTYPE;\n _id: PouchDB.Core.DocumentId;\n viewList: string[];\n dataShapeList: PouchDB.Core.DocumentId[];\n}\n\nexport const DocTypePrefixes = {\n [DocType.CARD]: 'c',\n [DocType.DISPLAYABLE_DATA]: 'dd',\n [DocType.TAG]: 'TAG',\n [DocType.CARDRECORD]: 'cardH',\n [DocType.SCHEDULED_CARD]: 'card_review_',\n // Add other doctypes here as they get prefixed IDs\n [DocType.DATASHAPE]: 'DATASHAPE',\n [DocType.QUESTIONTYPE]: 'QUESTION',\n [DocType.VIEW]: 'VIEW',\n [DocType.PEDAGOGY]: 'PEDAGOGY',\n [DocType.NAVIGATION_STRATEGY]: 'NAVIGATION_STRATEGY',\n} as const;\n\nexport interface CardHistory<T extends CardRecord> {\n _id: PouchDB.Core.DocumentId;\n /**\n * The CouchDB id of the card\n */\n cardID: PouchDB.Core.DocumentId;\n\n /**\n * The ID of the course\n */\n courseID: string;\n\n /**\n * The to-date largest interval between successful\n * card reviews. `0` indicates no successful reviews.\n */\n bestInterval: number;\n\n /**\n * The number of times that a card has been\n * failed in review\n */\n lapses: number;\n\n /**\n * The number of consecutive successful impressions\n * on this card\n */\n streak: number;\n\n records: T[];\n}\n\nexport interface CardRecord {\n /**\n * The CouchDB id of the card\n */\n cardID: string;\n /**\n * The ID of the course\n */\n courseID: string;\n /**\n * Number of milliseconds that the user spent before dismissing\n * the card (ie, \"I've read this\" or \"here is my answer\")\n *\n * //TODO: this (sometimes?) wants to be replaced with a rich\n * recording of user activity in working the question\n */\n timeSpent: number;\n /**\n * The date-time that the card was rendered. timeStamp + timeSpent will give the\n * time of user submission.\n */\n timeStamp: Moment;\n}\n\nexport interface QuestionRecord extends CardRecord, Evaluation {\n userAnswer: Answer;\n /**\n * The number of incorrect user submissions prededing this submisstion.\n *\n * eg, if a user is asked 7*6=__, submitting 46, 48, 42 will result in three\n * records being created having 0, 1, and 2 as their recorded 'priorAttempts' values\n */\n priorAttemps: number;\n}\n","import { DocType, DocTypePrefixes, CardHistory, CardRecord, QuestionRecord } from '../types/types-legacy';\n\nexport function areQuestionRecords(h: CardHistory<CardRecord>): h is CardHistory<QuestionRecord> {\n return isQuestionRecord(h.records[0]);\n}\n\nexport function isQuestionRecord(c: CardRecord): c is QuestionRecord {\n return (c as QuestionRecord).userAnswer !== undefined;\n}\n\nexport function getCardHistoryID(courseID: string, cardID: string): PouchDB.Core.DocumentId {\n return `${DocTypePrefixes[DocType.CARDRECORD]}-${courseID}-${cardID}`;\n}\n\nexport function parseCardHistoryID(id: string): {\n courseID: string;\n cardID: string;\n} {\n const split = id.split('-');\n let error: string = '';\n error += split.length === 3 ? '' : `\\n\\tgiven ID has incorrect number of '-' characters`;\n error +=\n split[0] === DocTypePrefixes[DocType.CARDRECORD] ? '' : `\n\tgiven ID does not start with ${DocTypePrefixes[DocType.CARDRECORD]}`;\n\n if (split.length === 3 && split[0] === DocTypePrefixes[DocType.CARDRECORD]) {\n return {\n courseID: split[1],\n cardID: split[2],\n };\n } else {\n throw new Error('parseCardHistory Error:' + error);\n }\n}\n\ninterface PouchDBError extends Error {\n error?: string;\n reason?: string;\n}\n\nexport function docIsDeleted(e: PouchDBError): boolean {\n return Boolean(e?.error === 'not_found' && e?.reason === 'deleted');\n}\n","import PouchDB from 'pouchdb';\nimport PouchDBFind from 'pouchdb-find';\nimport PouchDBAuth from '@nilock2/pouchdb-authentication';\n\n// Register plugins\nPouchDB.plugin(PouchDBFind);\nPouchDB.plugin(PouchDBAuth);\n\n// Configure PouchDB globally\nPouchDB.defaults({\n // ajax: {\n // timeout: 60000,\n // },\n});\n\nexport default PouchDB;\n","// TUI-aware logging utility that redirects logs to file in Node.js\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { getAppDataDirectory } from './dataDirectory';\n\nlet logFile: string | null = null;\nlet isNodeEnvironment = false;\n\n/**\n * Initialize TUI logging - redirect console logs to file in Node.js\n */\nexport function initializeTuiLogging(): void {\n // Detect Node.js environment\n isNodeEnvironment = typeof window === 'undefined' && typeof process !== 'undefined';\n \n if (!isNodeEnvironment) {\n return; // Browser environment - keep normal console logging\n }\n\n try {\n // Set up log file path\n logFile = path.join(getAppDataDirectory(), 'lastrun.log');\n \n // Clear previous log file\n if (fs.existsSync(logFile)) {\n fs.unlinkSync(logFile);\n }\n \n // Create initial log entry\n const startTime = new Date().toISOString();\n fs.writeFileSync(logFile, `=== TUI Session Started: ${startTime} ===\\n`);\n \n // Redirect console methods to file\n const originalConsole = {\n // eslint-disable-next-line no-console\n log: console.log,\n // eslint-disable-next-line no-console\n error: console.error,\n // eslint-disable-next-line no-console\n warn: console.warn,\n // eslint-disable-next-line no-console\n info: console.info\n };\n \n const writeToLog = (level: string, args: any[]) => {\n const timestamp = new Date().toISOString();\n const message = args.map(arg => \n typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)\n ).join(' ');\n \n const logEntry = `[${timestamp}] ${level}: ${message}\\n`;\n \n try {\n fs.appendFileSync(logFile!, logEntry);\n } catch (err) {\n // Fallback to original console if file write fails\n originalConsole.error('Failed to write to log file:', err);\n originalConsole[level.toLowerCase() as keyof typeof originalConsole](...args);\n }\n };\n \n // Override console methods\n // eslint-disable-next-line no-console\n console.log = (...args) => writeToLog('INFO', args);\n // eslint-disable-next-line no-console\n console.info = (...args) => writeToLog('INFO', args);\n // eslint-disable-next-line no-console\n console.warn = (...args) => writeToLog('WARN', args);\n // eslint-disable-next-line no-console\n console.error = (...args) => writeToLog('ERROR', args);\n \n // Store original methods for potential restoration\n (console as any)._originalMethods = originalConsole;\n \n // eslint-disable-next-line no-console\n console.log('TUI logging initialized - logs redirected to', logFile);\n \n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('Failed to initialize TUI logging:', err);\n }\n}\n\n/**\n * Get the current log file path (for debugging)\n */\nexport function getLogFilePath(): string | null {\n return logFile;\n}\n\n/**\n * Show user-facing message (always visible in TUI)\n */\nexport function showUserMessage(message: string): void {\n if (isNodeEnvironment) {\n // In Node.js, write directly to stdout to bypass log redirection\n process.stdout.write(message + '\\n');\n } else {\n // In browser, use normal console\n // eslint-disable-next-line no-console\n console.log(message);\n }\n}\n\n/**\n * Show user-facing error (always visible in TUI)\n */\nexport function showUserError(message: string): void {\n if (isNodeEnvironment) {\n // In Node.js, write directly to stderr to bypass log redirection\n process.stderr.write('Error: ' + message + '\\n');\n } else {\n // In browser, use normal console\n // eslint-disable-next-line no-console\n console.error(message);\n }\n}\n\n/**\n * Logger object with standard log levels\n */\nexport const logger = {\n debug: (message: string, ...args: any[]) => {\n // eslint-disable-next-line no-console\n console.log(`[DEBUG] ${message}`, ...args);\n },\n info: (message: string, ...args: any[]) => {\n // eslint-disable-next-line no-console\n console.info(`[INFO] ${message}`, ...args);\n },\n warn: (message: string, ...args: any[]) => {\n // eslint-disable-next-line no-console\n console.warn(`[WARN] ${message}`, ...args);\n },\n error: (message: string, ...args: any[]) => {\n // eslint-disable-next-line no-console\n console.error(`[ERROR] ${message}`, ...args);\n },\n};","// Cross-platform data directory utilities for PouchDB\n// Provides OS-appropriate application data directories\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { logger } from './tuiLogger';\nimport { ENV } from '@db/factory';\n\n/**\n * Get the application data directory for the current platform\n * Uses ~/.tuilder as requested by user for simplicity\n */\nexport function getAppDataDirectory(): string {\n if (ENV.LOCAL_STORAGE_PREFIX) {\n return path.join(os.homedir(), `.tuilder`, ENV.LOCAL_STORAGE_PREFIX);\n } else {\n return path.join(os.homedir(), '.tuilder');\n }\n}\n\n/**\n * Ensure the application data directory exists\n * Creates directory recursively if it doesn't exist\n */\nexport async function ensureAppDataDirectory(): Promise<string> {\n const appDataDir = getAppDataDirectory();\n \n try {\n await fs.promises.mkdir(appDataDir, { recursive: true });\n } catch (err: any) {\n if (err.code !== 'EEXIST') {\n throw new Error(`Failed to create app data directory ${appDataDir}: ${err.message}`);\n }\n }\n \n return appDataDir;\n}\n\n/**\n * Get the full path for a PouchDB database file\n * @param dbName - The database name (e.g., 'userdb-Colin')\n */\nexport function getDbPath(dbName: string): string {\n return path.join(getAppDataDirectory(), dbName);\n}\n\n/**\n * Initialize data directory for PouchDB usage\n * Should be called once at application startup\n */\nexport async function initializeDataDirectory(): Promise<void> {\n await ensureAppDataDirectory();\n \n // Log initialization\n logger.info(`PouchDB data directory initialized: ${getAppDataDirectory()}`);\n}","// packages/db/src/impl/common/userDBHelpers.ts\n\nimport moment from 'moment';\nimport { DocType, DocTypePrefixes } from '@db/core';\nimport { logger } from '../../util/logger';\nimport { ScheduledCard } from '@db/core/types/user';\n\nexport const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';\n\nimport pouch from '../couch/pouchdb-setup';\nimport { getDbPath } from '../../util/dataDirectory';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\nexport function hexEncode(str: string): string {\n let hex: string;\n let returnStr: string = '';\n\n for (let i = 0; i < str.length; i++) {\n hex = str.charCodeAt(i).toString(16);\n returnStr += ('000' + hex).slice(3);\n }\n\n return returnStr;\n}\n\nexport function filterAllDocsByPrefix<T>(\n db: PouchDB.Database,\n prefix: string,\n opts?: PouchDB.Core.AllDocsOptions\n) {\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const options: PouchDB.Core.AllDocsWithinRangeOptions = {\n startkey: prefix,\n endkey: prefix + '\\ufff0',\n include_docs: true,\n };\n\n if (opts) {\n Object.assign(options, opts);\n }\n return db.allDocs<T>(options);\n}\n\nexport function getStartAndEndKeys(key: string): {\n startkey: string;\n endkey: string;\n} {\n return {\n startkey: key,\n endkey: key + '\\ufff0',\n };\n}\n\nexport function updateGuestAccountExpirationDate(guestDB: PouchDB.Database<object>) {\n const currentTime = moment.utc();\n const expirationDate: string = currentTime.add(2, 'months').toISOString();\n const expiryDocID: string = 'GuestAccountExpirationDate';\n\n void guestDB\n .get(expiryDocID)\n .then((doc) => {\n return guestDB.put({\n _id: expiryDocID,\n _rev: doc._rev,\n date: expirationDate,\n });\n })\n .catch(() => {\n return guestDB.put({\n _id: expiryDocID,\n date: expirationDate,\n });\n });\n}\n\n/**\n * Get local user database with appropriate adapter for environment\n */\nexport function getLocalUserDB(username: string): PouchDB.Database {\n // // Choose adapter based on environment\n //\n // Not certain of this is required. Let's let pouch's auto detection\n // handle it until we specifically know we need to intervene.\n //\n // let adapter: string;\n // if (typeof window !== 'undefined') {\n // // Browser environment - use IndexedDB\n // adapter = 'idb';\n // } else {\n // // Node.js environment (tests) - use memory adapter\n // adapter = 'memory';\n // }\n\n const dbName = `userdb-${username}`;\n \n // Use proper data directory in Node.js, browser will use IndexedDB\n if (typeof window === 'undefined') {\n // Node.js environment - use filesystem with proper app data directory\n return new pouch(getDbPath(dbName), {});\n } else {\n // Browser environment - use default (IndexedDB)\n return new pouch(dbName, {});\n }\n}\n\n/**\n * Schedule a card review (strategy-agnostic version)\n */\nexport function scheduleCardReviewLocal(\n userDB: PouchDB.Database,\n review: {\n card_id: PouchDB.Core.DocumentId;\n time: moment.Moment;\n course_id: string;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n }\n) {\n const now = moment.utc();\n logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);\n void userDB.put<ScheduledCard>({\n _id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),\n cardId: review.card_id,\n reviewTime: review.time.toISOString(),\n courseId: review.course_id,\n scheduledAt: now.toISOString(),\n scheduledFor: review.scheduledFor,\n schedulingAgentId: review.schedulingAgentId,\n });\n}\n\n/**\n * Remove a scheduled card review (strategy-agnostic version)\n */\nexport async function removeScheduledCardReviewLocal(\n userDB: PouchDB.Database,\n reviewDocID: string\n) {\n const reviewDoc = await userDB.get(reviewDocID);\n userDB\n .remove(reviewDoc)\n .then((res) => {\n if (res.ok) {\n log(`Removed Review Doc: ${reviewDocID}`);\n }\n })\n .catch((err) => {\n log(`Failed to remove Review Doc: ${reviewDocID},\\n${JSON.stringify(err)}`);\n });\n}\n","export abstract class Loggable {\n protected abstract readonly _className: string;\n protected log(...args: unknown[]): void {\n // eslint-disable-next-line no-console\n console.log(`LOG-${this._className}@${new Date()}:`, ...args);\n }\n protected error(...args: unknown[]): void {\n // eslint-disable-next-line no-console\n console.error(`ERROR-${this._className}@${new Date()}:`, ...args);\n }\n}\n","import { Loggable } from '../../util/Loggable';\nimport { logger } from '../../util/logger';\n\nexport type Update<T> = Partial<T> | ((x: T) => T);\n\nexport default class UpdateQueue extends Loggable {\n _className: string = 'UpdateQueue';\n private pendingUpdates: {\n [index: string]: Update<unknown>[];\n } = {};\n private inprogressUpdates: {\n [index: string]: boolean;\n } = {};\n\n private readDB: PouchDB.Database; // Database for read operations\n private writeDB: PouchDB.Database; // Database for write operations (local-first)\n\n public update<T extends PouchDB.Core.Document<object>>(\n id: PouchDB.Core.DocumentId,\n update: Update<T>\n ) {\n logger.debug(`Update requested on doc: ${id}`);\n if (this.pendingUpdates[id]) {\n this.pendingUpdates[id].push(update);\n } else {\n this.pendingUpdates[id] = [update];\n }\n return this.applyUpdates<T>(id);\n }\n\n constructor(readDB: PouchDB.Database, writeDB?: PouchDB.Database) {\n super();\n // PouchDB.debug.enable('*');\n this.readDB = readDB;\n this.writeDB = writeDB || readDB; // Default to readDB if writeDB not provided\n logger.debug(`UpdateQ initialized...`);\n void this.readDB.info().then((i) => {\n logger.debug(`db info: ${JSON.stringify(i)}`);\n });\n }\n\n private async applyUpdates<T extends PouchDB.Core.Document<object>>(\n id: string\n ): Promise<T & PouchDB.Core.GetMeta & PouchDB.Core.RevisionIdMeta> {\n logger.debug(`Applying updates on doc: ${id}`);\n if (this.inprogressUpdates[id]) {\n // Poll instead of recursing to avoid infinite recursion\n while (this.inprogressUpdates[id]) {\n await new Promise(resolve => setTimeout(resolve, Math.random() * 50));\n }\n return this.applyUpdates<T>(id);\n } else {\n if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {\n this.inprogressUpdates[id] = true;\n\n const MAX_RETRIES = 5;\n for (let i = 0; i < MAX_RETRIES; i++) {\n try {\n const doc = await this.readDB.get<T>(id);\n logger.debug(`Retrieved doc: ${id}`);\n\n // Create a new doc object to apply updates to for this attempt\n let updatedDoc = { ...doc };\n\n // Note: This loop is not fully safe if updates are functions that depend on a specific doc state\n // that might change between retries. But for simple object merges, it's okay.\n const updatesToApply = [...this.pendingUpdates[id]];\n for (const update of updatesToApply) {\n if (typeof update === 'function') {\n updatedDoc = { ...updatedDoc, ...update(updatedDoc) };\n } else {\n updatedDoc = {\n ...updatedDoc,\n ...update,\n };\n }\n }\n\n await this.writeDB.put<T>(updatedDoc);\n logger.debug(`Put doc: ${id}`);\n\n // Success! Remove the updates we just applied.\n this.pendingUpdates[id].splice(0, updatesToApply.length);\n\n if (this.pendingUpdates[id].length === 0) {\n this.inprogressUpdates[id] = false;\n delete this.inprogressUpdates[id];\n } else {\n // More updates came in, run again.\n return this.applyUpdates<T>(id);\n }\n return updatedDoc as any; // success, exit loop and function\n } catch (e: any) {\n if (e.name === 'conflict' && i < MAX_RETRIES - 1) {\n logger.warn(`Conflict on update for doc ${id}, retry #${i + 1}`);\n await new Promise((res) => setTimeout(res, 50 * Math.random()));\n // continue to next iteration of the loop\n } else if (e.name === 'not_found' && i === 0) {\n // Document not present - throw to caller for initialization\n logger.warn(`Update failed for ${id} - does not exist. Throwing to caller.`);\n throw e; // Let caller handle\n } else {\n // Max retries reached or a non-conflict error\n delete this.inprogressUpdates[id];\n if (this.pendingUpdates[id]) {\n delete this.pendingUpdates[id];\n }\n logger.error(`Error on attemped update (retry ${i}): ${JSON.stringify(e)}`);\n throw e; // Let caller handle\n }\n }\n }\n // This should be unreachable, but it satisfies the compiler that a value is always returned or an error thrown.\n throw new Error(`UpdateQueue failed for doc ${id} after ${MAX_RETRIES} retries.`);\n } else {\n throw new Error(`Empty Updates Queue Triggered`);\n }\n }\n }\n}\n","import {\n ScheduledCard,\n UserCourseSetting,\n UserCourseSettings,\n UsrCrsDataInterface,\n} from '@db/core';\n\nimport moment, { Moment } from 'moment';\n\nimport { UserDBInterface } from '@db/core';\nimport { logger } from '../../util/logger';\n\nexport class UsrCrsData implements UsrCrsDataInterface {\n private user: UserDBInterface;\n private _courseId: string;\n\n constructor(user: UserDBInterface, courseId: string) {\n this.user = user;\n this._courseId = courseId;\n }\n\n public async getReviewsForcast(daysCount: number) {\n const time = moment.utc().add(daysCount, 'days');\n return this.getReviewstoDate(time);\n }\n\n public async getPendingReviews() {\n const now = moment.utc();\n return this.getReviewstoDate(now);\n }\n\n public async getScheduledReviewCount(): Promise<number> {\n return (await this.getPendingReviews()).length;\n }\n\n public async getCourseSettings(): Promise<UserCourseSettings> {\n const regDoc = await this.user.getCourseRegistrationsDoc();\n const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);\n\n if (crsDoc && crsDoc.settings) {\n return crsDoc.settings;\n } else {\n logger.warn(`no settings found during lookup on course ${this._courseId}`);\n return {};\n }\n }\n public updateCourseSettings(updates: UserCourseSetting[]): void {\n // TODO: Add updateCourseSettings method to UserDBInterface\n // For now, we'll need to cast to access the concrete implementation\n if ('updateCourseSettings' in this.user) {\n void (this.user as any).updateCourseSettings(this._courseId, updates);\n }\n }\n\n private async getReviewstoDate(targetDate: Moment) {\n // Use the interface method instead of direct database access\n const allReviews = await this.user.getPendingReviews(this._courseId);\n\n logger.debug(\n `Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`\n );\n\n return allReviews.filter((review: ScheduledCard) => {\n const reviewTime = moment.utc(review.reviewTime);\n return targetDate.isAfter(reviewTime);\n });\n }\n}\n","// todo: something good here instead\n\nconst CLIENT_CACHE: {\n [k: string]: unknown;\n} = {};\n\nexport async function GET_CACHED<K>(k: string, f?: (x: string) => Promise<K>): Promise<K> {\n if (CLIENT_CACHE[k]) {\n // console.log('returning a cached item');\n return CLIENT_CACHE[k] as K;\n }\n\n CLIENT_CACHE[k] = f ? await f(k) : await GET_ITEM(k);\n return GET_CACHED(k);\n}\n\nasync function GET_ITEM(k: string): Promise<unknown> {\n throw new Error(`No implementation found for GET_CACHED(${k})`);\n}\n","import pouch from './pouchdb-setup';\nimport { createPouchDBConfig } from '.';\nimport { ENV } from '@db/factory';\n// import { DataShape } from '../..base-course/Interfaces/DataShape';\nimport { NameSpacer, ShapeDescriptor } from '@vue-skuilder/common';\nimport { CourseConfig, DataShape } from '@vue-skuilder/common';\nimport { CourseElo, blankCourseElo, toCourseElo } from '@vue-skuilder/common';\nimport { CourseDB, createTag } from './courseDB';\nimport { CardData, DisplayableData, DocType, Tag, DocTypePrefixes } from '../../core/types/types-legacy';\nimport { prepareNote55 } from '@vue-skuilder/common';\nimport { BaseUser } from '../common';\nimport { logger } from '@db/util/logger';\nimport { v4 as uuidv4 } from 'uuid';\n\n/**\n *\n * @param courseID id of the course (quilt) being added to\n * @param codeCourse\n * @param shape\n * @param data the datashape data - data required for this shape\n * @param author\n * @param uploads optional additional media uploads: img0, img1, ..., aud0, aud1,...\n * @returns\n */\nexport async function addNote55(\n courseID: string,\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [x: string]: PouchDB.Core.FullAttachment },\n elo: CourseElo = blankCourseElo()\n): Promise<PouchDB.Core.Response> {\n const db = getCourseDB(courseID);\n const payload = prepareNote55(courseID, codeCourse, shape, data, author, tags, uploads);\n const _id = `${DocTypePrefixes[DocType.DISPLAYABLE_DATA]}-${uuidv4()}`;\n const result = await db.put<DisplayableData>({ ...payload, _id });\n\n const dataShapeId = NameSpacer.getDataShapeString({\n course: codeCourse,\n dataShape: shape.name,\n });\n\n if (result.ok) {\n try {\n // create cards\n await createCards(courseID, dataShapeId, result.id, tags, elo, author);\n } catch (error) {\n // Handle CouchDB errors which often have a 'reason' property\n let errorMessage = 'Unknown error';\n if (error instanceof Error) {\n errorMessage = error.message;\n } else if (error && typeof error === 'object' && 'reason' in error) {\n errorMessage = error.reason as string;\n } else if (error && typeof error === 'object' && 'message' in error) {\n errorMessage = error.message as string;\n } else {\n errorMessage = String(error);\n }\n\n logger.error(`[addNote55] Failed to create cards for note ${result.id}: ${errorMessage}`);\n // Add info to result to indicate card creation failed\n (result as any).cardCreationFailed = true;\n (result as any).cardCreationError = errorMessage;\n }\n } else {\n logger.error(`[addNote55] Error adding note. Result: ${JSON.stringify(result)}`);\n }\n\n return result;\n}\n\nasync function createCards(\n courseID: string,\n datashapeID: PouchDB.Core.DocumentId,\n noteID: PouchDB.Core.DocumentId,\n tags: string[],\n elo: CourseElo = blankCourseElo(),\n author: string\n): Promise<void> {\n const cfg = await getCredentialledCourseConfig(courseID);\n const dsDescriptor = NameSpacer.getDataShapeDescriptor(datashapeID);\n let questionViewTypes: string[] = [];\n\n for (const ds of cfg.dataShapes) {\n if (ds.name === datashapeID) {\n questionViewTypes = ds.questionTypes;\n }\n }\n\n if (questionViewTypes.length === 0) {\n const errorMsg = `No questionViewTypes found for datashapeID: ${datashapeID} in course config. Cards cannot be created.`;\n logger.error(errorMsg);\n throw new Error(errorMsg);\n }\n\n for (const questionView of questionViewTypes) {\n await createCard(questionView, courseID, dsDescriptor, noteID, tags, elo, author);\n }\n}\n\nasync function createCard(\n questionViewName: string,\n courseID: string,\n dsDescriptor: ShapeDescriptor,\n noteID: string,\n tags: string[],\n elo: CourseElo = blankCourseElo(),\n author: string\n): Promise<void> {\n const qDescriptor = NameSpacer.getQuestionDescriptor(questionViewName);\n const cfg = await getCredentialledCourseConfig(courseID);\n\n for (const rQ of cfg.questionTypes) {\n if (rQ.name === questionViewName) {\n for (const view of rQ.viewList) {\n await addCard(\n courseID,\n dsDescriptor.course,\n [noteID],\n NameSpacer.getViewString({\n course: qDescriptor.course,\n questionType: qDescriptor.questionType,\n view,\n }),\n elo,\n tags,\n author\n );\n }\n }\n }\n}\n\n/**\n *\n * Adds a card to the DB. This function is called\n * as a side effect of adding either a View or\n * DisplayableData item.\n * @param course The name of the course that the card belongs to\n * @param id_displayable_data C/PouchDB ID of the data used to hydrate the view\n * @param id_view C/PouchDB ID of the view used to display the card\n *\n * @package\n */\nasync function addCard(\n courseID: string,\n course: string,\n id_displayable_data: PouchDB.Core.DocumentId[],\n id_view: PouchDB.Core.DocumentId,\n elo: CourseElo,\n tags: string[],\n author: string\n): Promise<PouchDB.Core.Response> {\n const db = getCourseDB(courseID);\n const _id = `${DocTypePrefixes[DocType.CARD]}-${uuidv4()}`;\n const card = await db.put<CardData>({\n _id,\n course,\n id_displayable_data,\n id_view,\n docType: DocType.CARD,\n elo: elo || toCourseElo(990 + Math.round(20 * Math.random())),\n author,\n });\n for (const tag of tags) {\n logger.info(`adding tag: ${tag} to card ${card.id}`);\n await addTagToCard(courseID, card.id, tag, author, false);\n }\n return card;\n}\n\nexport async function getCredentialledCourseConfig(courseID: string): Promise<CourseConfig> {\n try {\n const db = getCourseDB(courseID);\n const ret = await db.get<CourseConfig>('CourseConfig');\n ret.courseID = courseID;\n logger.info(`Returning course config: ${JSON.stringify(ret)}`);\n return ret;\n } catch (e) {\n logger.error(`Error fetching config for ${courseID}:`, e);\n throw e;\n }\n}\n\n/**\n Assciates a tag with a card.\n\n NB: DB stores tags as separate documents, with a list of card IDs.\n Consider renaming to `addCardToTag` to reflect this.\n\n NB: tags are created if they don't already exist\n\n @param updateELO whether to update the ELO of the card with the new tag. Default true.\n @package\n*/\nexport async function addTagToCard(\n courseID: string,\n cardID: string,\n tagID: string,\n author: string,\n updateELO: boolean = true\n): Promise<PouchDB.Core.Response> {\n // todo: possible future perf. hit if tags have large #s of taggedCards.\n // In this case, should be converted to a server-request\n const prefixedTagID = getTagID(tagID);\n const courseDB = getCourseDB(courseID);\n const courseApi = new CourseDB(courseID, async () => {\n const dummySyncStrategy = {\n setupRemoteDB: () => null as any,\n startSync: () => {},\n canCreateAccount: () => false,\n canAuthenticate: () => false,\n getCurrentUsername: async () => 'DummyUser',\n };\n return BaseUser.Dummy(dummySyncStrategy);\n });\n try {\n logger.info(`Applying tag ${tagID} to card ${courseID + '-' + cardID}...`);\n const tag = await courseDB.get<Tag>(prefixedTagID);\n if (!tag.taggedCards.includes(cardID)) {\n tag.taggedCards.push(cardID);\n\n if (updateELO) {\n try {\n const eloData = await courseApi.getCardEloData([cardID]);\n const elo = eloData[0];\n elo.tags[tagID] = {\n count: 0,\n score: elo.global.score, // todo: or 1000?\n };\n await updateCardElo(courseID, cardID, elo);\n } catch (error) {\n logger.error('Failed to update ELO data for card:', cardID, error);\n }\n }\n\n return courseDB.put<Tag>(tag);\n } else throw new AlreadyTaggedErr(`Card ${cardID} is already tagged with ${tagID}`);\n } catch (e) {\n if (e instanceof AlreadyTaggedErr) {\n throw e;\n }\n\n await createTag(courseID, tagID, author);\n return addTagToCard(courseID, cardID, tagID, author, updateELO);\n }\n}\n\nasync function updateCardElo(courseID: string, cardID: string, elo: CourseElo) {\n if (elo) {\n // checking against null, undefined, NaN\n const cDB = getCourseDB(courseID);\n const card = await cDB.get<CardData>(cardID);\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return cDB.put(card); // race conditions - is it important? probably not (net-zero effect)\n }\n}\n\nclass AlreadyTaggedErr extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AlreadyTaggedErr';\n }\n}\n\nexport function getTagID(tagName: string): string {\n const tagPrefix = DocType.TAG.valueOf() + '-';\n if (tagName.indexOf(tagPrefix) === 0) {\n return tagName;\n } else {\n return tagPrefix + tagName;\n }\n}\n\nexport function getCourseDB(courseID: string): PouchDB.Database {\n const dbName = `coursedb-${courseID}`;\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n","import pouch from './pouchdb-setup';\nimport { ENV } from '@db/factory';\nimport { logger } from '../../util/logger';\n\nconst courseLookupDBTitle = 'coursedb-lookup';\n\ninterface CourseLookupDoc {\n _id: string;\n _rev: string;\n name: string;\n disambiguator?: string;\n}\n\nlogger.debug(`COURSELOOKUP FILE RUNNING`);\n\n/**\n * A Lookup table of existant courses. Each docID in this DB correspondes to a\n * course database whose name is `coursedb-{docID}`\n */\nexport default class CourseLookup {\n // [ ] this db should be read only for public, admin-only for write\n // Cache for the PouchDB instance\n private static _dbInstance: PouchDB.Database | null = null;\n\n /**\n * Static getter for the PouchDB database instance.\n * Connects using ENV variables and caches the instance.\n * Throws an error if required ENV variables are not set.\n */\n private static get _db(): PouchDB.Database {\n // Return cached instance if available\n if (this._dbInstance) {\n return this._dbInstance;\n }\n\n // --- Check required environment variables ---\n if (ENV.COUCHDB_SERVER_URL === 'NOT_SET' || !ENV.COUCHDB_SERVER_URL) {\n throw new Error(\n 'CourseLookup.db: COUCHDB_SERVER_URL is not set. Ensure initializeDataLayer has been called with valid configuration.'\n );\n }\n if (ENV.COUCHDB_SERVER_PROTOCOL === 'NOT_SET' || !ENV.COUCHDB_SERVER_PROTOCOL) {\n throw new Error(\n 'CourseLookup.db: COUCHDB_SERVER_PROTOCOL is not set. Ensure initializeDataLayer has been called with valid configuration.'\n );\n }\n\n // --- Construct connection options ---\n const dbUrl = `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}/${courseLookupDBTitle}`;\n const options: PouchDB.Configuration.RemoteDatabaseConfiguration = {\n skip_setup: true, // Keep the original option\n // fetch: (url, opts) => { // Optional: Add for debugging network requests\n // console.log('PouchDB fetch:', url, opts);\n // return pouch.fetch(url, opts);\n // }\n };\n\n // Add authentication if both username and password are provided\n if (ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD) {\n options.auth = {\n username: ENV.COUCHDB_USERNAME,\n password: ENV.COUCHDB_PASSWORD,\n };\n logger.info(`CourseLookup: Connecting to ${dbUrl} with authentication.`);\n } else {\n logger.info(`CourseLookup: Connecting to ${dbUrl} without authentication.`);\n }\n\n // --- Create and cache the PouchDB instance ---\n try {\n this._dbInstance = new pouch(dbUrl, options);\n logger.info(`CourseLookup: Database instance created for ${courseLookupDBTitle}.`);\n return this._dbInstance;\n } catch (error) {\n logger.error(`CourseLookup: Failed to create PouchDB instance for ${dbUrl}`, error);\n // Reset cache attempt on failure\n this._dbInstance = null;\n // Re-throw the error to indicate connection failure\n throw new Error(\n `CourseLookup: Failed to initialize database connection: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n }\n\n /**\n * Adds a new course to the lookup database, and returns the courseID\n * @param courseName\n * @returns\n */\n static async add(courseName: string): Promise<string> {\n const resp = await CourseLookup._db.post({\n name: courseName,\n });\n\n return resp.id;\n }\n\n /**\n * Adds a new course to the lookup database with a specific courseID\n * @param courseId The specific course ID to use\n * @param courseName The course name\n * @param disambiguator Optional disambiguator\n * @returns Promise<void>\n */\n static async addWithId(\n courseId: string,\n courseName: string,\n disambiguator?: string\n ): Promise<void> {\n const doc: Omit<CourseLookupDoc, '_rev'> = {\n _id: courseId,\n name: courseName,\n };\n\n if (disambiguator) {\n doc.disambiguator = disambiguator;\n }\n\n await CourseLookup._db.put(doc);\n }\n\n /**\n * Removes a course from the index\n * @param courseID\n */\n static async delete(courseID: string): Promise<PouchDB.Core.Response> {\n const doc = await CourseLookup._db.get(courseID);\n return await CourseLookup._db.remove(doc);\n }\n\n // [ ] rename to allCourses()\n static async allCourseWare(): Promise<CourseLookupDoc[]> {\n const resp = await CourseLookup._db.allDocs<CourseLookupDoc>({\n include_docs: true,\n });\n\n return resp.rows.map((row) => row.doc!);\n }\n\n static async updateDisambiguator(\n courseID: string,\n disambiguator?: string\n ): Promise<PouchDB.Core.Response> {\n const doc = await CourseLookup._db.get<CourseLookupDoc>(courseID);\n doc.disambiguator = disambiguator;\n return await CourseLookup._db.put(doc);\n }\n\n static async isCourse(courseID: string): Promise<boolean> {\n try {\n await CourseLookup._db.get(courseID);\n return true;\n } catch (error) {\n logger.info(`Courselookup failed:`, error);\n return false;\n }\n }\n}\n","import { ScheduledCard } from '../types/user';\nimport { CourseDBInterface } from '../interfaces/courseDB';\nimport { UserDBInterface } from '../interfaces/userDB';\nimport { ContentNavigator } from './index';\nimport { CourseElo } from '@vue-skuilder/common';\nimport { StudySessionReviewItem, StudySessionNewItem, QualifiedCardID } from '..';\n\nexport default class ELONavigator extends ContentNavigator {\n user: UserDBInterface;\n course: CourseDBInterface;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface\n // The ELO strategy is non-parameterized.\n //\n // It instead relies on existing meta data from the course and user with respect to\n //\n //\n // strategy?: ContentNavigationStrategyData\n ) {\n super();\n this.user = user;\n this.course = course;\n }\n\n async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n type ratedReview = ScheduledCard & CourseElo;\n\n const reviews = await this.user.getPendingReviews(this.course.getCourseID()); // todo: this adds a db round trip - should be server side\n const elo = await this.course.getCardEloData(reviews.map((r) => r.cardId));\n\n const ratedReviews = reviews.map((r, i) => {\n const ratedR: ratedReview = {\n ...r,\n ...elo[i],\n };\n return ratedR;\n });\n\n ratedReviews.sort((a, b) => {\n return a.global.score - b.global.score;\n });\n\n return ratedReviews.map((r) => {\n return {\n ...r,\n contentSourceType: 'course',\n contentSourceID: this.course.getCourseID(),\n cardID: r.cardId,\n courseID: r.courseId,\n qualifiedID: `${r.courseId}-${r.cardId}`,\n reviewID: r._id,\n status: 'review',\n };\n });\n }\n\n async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {\n const activeCards = await this.user.getActiveCards();\n return (\n await this.course.getCardsCenteredAtELO(\n { limit: limit, elo: 'user' },\n (c: QualifiedCardID) => {\n if (activeCards.some((ac) => c.cardID === ac.cardID)) {\n return false;\n } else {\n return true;\n }\n }\n )\n ).map((c) => {\n return {\n ...c,\n status: 'new',\n };\n });\n }\n}\n","import { CourseDBInterface, QualifiedCardID, StudySessionNewItem, StudySessionReviewItem, UserDBInterface } from '..';\nimport { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport { ScheduledCard } from '../types/user';\nimport { ContentNavigator } from './index';\nimport { logger } from '../../util/logger';\n\nexport default class HardcodedOrderNavigator extends ContentNavigator {\n private orderedCardIds: string[] = [];\n private user: UserDBInterface;\n private course: CourseDBInterface;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ) {\n super();\n this.user = user;\n this.course = course;\n\n if (strategyData.serializedData) {\n try {\n this.orderedCardIds = JSON.parse(strategyData.serializedData);\n } catch (e) {\n logger.error('Failed to parse serializedData for HardcodedOrderNavigator', e);\n }\n }\n }\n\n async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n const reviews = await this.user.getPendingReviews(this.course.getCourseID());\n return reviews.map((r) => {\n return {\n ...r,\n contentSourceType: 'course',\n contentSourceID: this.course.getCourseID(),\n cardID: r.cardId,\n courseID: r.courseId,\n reviewID: r._id,\n status: 'review',\n };\n });\n }\n\n async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {\n const activeCardIds = (await this.user.getActiveCards()).map((c: QualifiedCardID) => c.cardID);\n\n const newCardIds = this.orderedCardIds.filter(\n (cardId) => !activeCardIds.includes(cardId)\n );\n\n const cardsToReturn = newCardIds.slice(0, limit);\n\n return cardsToReturn.map((cardId) => {\n return {\n cardID: cardId,\n courseID: this.course.getCourseID(),\n contentSourceType: 'course',\n contentSourceID: this.course.getCourseID(),\n status: 'new',\n };\n });\n }\n}\n","import {\n StudyContentSource,\n UserDBInterface,\n CourseDBInterface,\n StudySessionReviewItem,\n StudySessionNewItem,\n} from '..';\nimport { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport { ScheduledCard } from '../types/user';\nimport { logger } from '../../util/logger';\n\nexport enum Navigators {\n ELO = 'elo',\n HARDCODED = 'hardcodedOrder',\n}\n\n/**\n * A content-navigator provides runtime steering of study sessions.\n */\nexport abstract class ContentNavigator implements StudyContentSource {\n /**\n *\n * @param user\n * @param strategyData\n * @returns the runtime object used to steer a study session.\n */\n static async create(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ): Promise<ContentNavigator> {\n const implementingClass = strategyData.implementingClass;\n let NavigatorImpl;\n\n // Try different extension variations\n const variations = ['.ts', '.js', ''];\n\n for (const ext of variations) {\n try {\n const module = await import(`./${implementingClass}${ext}`);\n NavigatorImpl = module.default;\n break; // Break the loop if loading succeeds\n } catch (e) {\n // Continue to next variation if this one fails\n logger.debug(`Failed to load with extension ${ext}:`, e);\n }\n }\n\n if (!NavigatorImpl) {\n throw new Error(`Could not load navigator implementation for: ${implementingClass}`);\n }\n\n return new NavigatorImpl(user, course, strategyData);\n }\n\n abstract getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;\n abstract getNewCards(n?: number): Promise<StudySessionNewItem[]>;\n}\n","import { CourseDBInterface, CourseInfo, CoursesDBInterface, UserDBInterface } from '@db/core';\nimport { ScheduledCard } from '@db/core/types/user';\nimport {\n CourseConfig,\n CourseElo,\n DataShape,\n EloToNumber,\n Status,\n blankCourseElo,\n toCourseElo,\n} from '@vue-skuilder/common';\n\nimport { filterAllDocsByPrefix, getCourseDB, getCourseDoc, getCourseDocs } from '.';\nimport UpdateQueue from './updateQueue';\nimport {\n StudyContentSource,\n StudySessionItem,\n StudySessionNewItem,\n StudySessionReviewItem,\n} from '../../core/interfaces/contentSource';\nimport {\n CardData,\n DocType,\n QualifiedCardID,\n SkuilderCourseData,\n Tag,\n TagStub,\n DocTypePrefixes,\n} from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\nimport { GET_CACHED } from './clientCache';\nimport { addNote55, addTagToCard, getCredentialledCourseConfig, getTagID } from './courseAPI';\nimport { DataLayerResult } from '@db/core/types/db';\nimport { PouchError } from './types';\nimport CourseLookup from './courseLookupDB';\nimport { ContentNavigationStrategyData } from '@db/core/types/contentNavigationStrategy';\nimport { ContentNavigator, Navigators } from '@db/core/navigators';\n\nexport class CoursesDB implements CoursesDBInterface {\n _courseIDs: string[] | undefined;\n\n constructor(courseIDs?: string[]) {\n if (courseIDs && courseIDs.length > 0) {\n this._courseIDs = courseIDs;\n } else {\n this._courseIDs = undefined;\n }\n }\n\n public async getCourseList(): Promise<CourseConfig[]> {\n let crsList = await CourseLookup.allCourseWare();\n logger.debug(`AllCourses: ${crsList.map((c) => c.name + ', ' + c._id + '\\n\\t')}`);\n if (this._courseIDs) {\n crsList = crsList.filter((c) => this._courseIDs!.includes(c._id));\n }\n\n logger.debug(`AllCourses.filtered: ${crsList.map((c) => c.name + ', ' + c._id + '\\n\\t')}`);\n\n const cfgs = await Promise.all(\n crsList.map(async (c) => {\n try {\n const cfg = await getCredentialledCourseConfig(c._id);\n logger.debug(`Found cfg: ${JSON.stringify(cfg)}`);\n return cfg;\n } catch (e) {\n logger.warn(`Error fetching cfg for course ${c.name}, ${c._id}: ${e}`);\n return undefined;\n }\n })\n );\n return cfgs.filter((c) => !!c);\n }\n\n async getCourseConfig(courseId: string): Promise<CourseConfig> {\n if (this._courseIDs && this._courseIDs.length && !this._courseIDs.includes(courseId)) {\n throw new Error(`Course ${courseId} not in course list`);\n }\n\n const cfg = await getCredentialledCourseConfig(courseId);\n if (cfg === undefined) {\n throw new Error(`Error fetching cfg for course ${courseId}`);\n } else {\n return cfg;\n }\n }\n\n public async disambiguateCourse(courseId: string, disambiguator: string): Promise<void> {\n await CourseLookup.updateDisambiguator(courseId, disambiguator);\n }\n}\n\nfunction randIntWeightedTowardZero(n: number) {\n return Math.floor(Math.random() * Math.random() * Math.random() * n);\n}\n\nexport class CourseDB implements StudyContentSource, CourseDBInterface {\n // private log(msg: string): void {\n // log(`CourseLog: ${this.id}\\n ${msg}`);\n // }\n\n private db: PouchDB.Database;\n private id: string;\n private _getCurrentUser: () => Promise<UserDBInterface>;\n private updateQueue: UpdateQueue;\n\n constructor(id: string, userLookup: () => Promise<UserDBInterface>) {\n this.id = id;\n this.db = getCourseDB(this.id);\n this._getCurrentUser = userLookup;\n this.updateQueue = new UpdateQueue(this.db);\n }\n\n public getCourseID(): string {\n return this.id;\n }\n\n public async getCourseInfo(): Promise<CourseInfo> {\n const cardCount = (\n await this.db.find({\n selector: {\n docType: DocType.CARD,\n },\n limit: 1000,\n })\n ).docs.length;\n\n return {\n cardCount,\n registeredUsers: 0,\n };\n }\n\n public async getInexperiencedCards(limit: number = 2) {\n return (\n await this.db.query('cardsByInexperience', {\n limit,\n })\n ).rows.map((r) => {\n const ret = {\n courseId: this.id,\n cardId: r.id,\n count: r.key,\n elo: r.value,\n };\n return ret;\n });\n }\n\n public async getCardsByEloLimits(\n options: {\n low: number;\n high: number;\n limit: number;\n page: number;\n } = {\n low: 0,\n high: Number.MIN_SAFE_INTEGER,\n limit: 25,\n page: 0,\n }\n ) {\n return (\n await this.db.query('elo', {\n startkey: options.low,\n endkey: options.high,\n limit: options.limit,\n skip: options.limit * options.page,\n })\n ).rows.map((r) => {\n return `${this.id}-${r.id}-${r.key}`;\n });\n }\n public async getCardEloData(id: string[]): Promise<CourseElo[]> {\n const docs = await this.db.allDocs<CardData>({\n keys: id,\n include_docs: true,\n });\n const ret: CourseElo[] = [];\n docs.rows.forEach((r) => {\n // [ ] remove these ts-ignore directives.\n if (isSuccessRow(r)) {\n if (r.doc && r.doc.elo) {\n ret.push(toCourseElo(r.doc.elo));\n } else {\n logger.warn('no elo data for card: ' + r.id);\n ret.push(blankCourseElo());\n }\n } else {\n logger.warn('no elo data for card: ' + JSON.stringify(r));\n ret.push(blankCourseElo());\n }\n });\n return ret;\n }\n\n /**\n * Returns the lowest and highest `global` ELO ratings in the course\n */\n public async getELOBounds() {\n const [low, high] = await Promise.all([\n (\n await this.db.query('elo', {\n startkey: 0,\n limit: 1,\n include_docs: false,\n })\n ).rows[0].key,\n (\n await this.db.query('elo', {\n limit: 1,\n descending: true,\n startkey: 100_000,\n })\n ).rows[0].key,\n ]);\n\n return {\n low: low,\n high: high,\n };\n }\n\n public async removeCard(id: string) {\n const doc = await this.db.get<CardData>(id);\n if (!doc.docType || !(doc.docType === DocType.CARD)) {\n throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);\n }\n \n // Remove card from all associated tags before deleting the card\n try {\n const appliedTags = await this.getAppliedTags(id);\n const results = await Promise.allSettled(\n appliedTags.rows.map(async (tagRow) => {\n const tagId = tagRow.id;\n await this.removeTagFromCard(id, tagId);\n })\n );\n \n // Log any individual tag cleanup failures\n results.forEach((result, index) => {\n if (result.status === 'rejected') {\n const tagId = appliedTags.rows[index].id;\n logger.error(`Failed to remove card ${id} from tag ${tagId}: ${result.reason}`);\n }\n });\n } catch (error) {\n logger.error(`Error removing card ${id} from tags: ${error}`);\n // Continue with card deletion even if tag cleanup fails\n }\n \n return this.db.remove(doc);\n }\n\n public async getCardDisplayableDataIDs(id: string[]) {\n logger.debug(id.join(', '));\n const cards = await this.db.allDocs<CardData>({\n keys: id,\n include_docs: true,\n });\n const ret: { [card: string]: string[] } = {};\n cards.rows.forEach((r) => {\n if (isSuccessRow(r)) {\n ret[r.id] = r.doc!.id_displayable_data;\n }\n });\n\n await Promise.all(\n cards.rows.map((r) => {\n return async () => {\n if (isSuccessRow(r)) {\n ret[r.id] = r.doc!.id_displayable_data;\n }\n };\n })\n );\n\n return ret;\n }\n\n async getCardsByELO(elo: number, cardLimit?: number) {\n elo = parseInt(elo as any);\n const limit = cardLimit ? cardLimit : 25;\n\n const below: PouchDB.Query.Response<object> = await this.db.query('elo', {\n limit: Math.ceil(limit / 2),\n startkey: elo,\n descending: true,\n });\n\n const aboveLimit = limit - below.rows.length;\n\n const above: PouchDB.Query.Response<object> = await this.db.query('elo', {\n limit: aboveLimit,\n startkey: elo + 1,\n });\n // logger.log(JSON.stringify(below));\n\n let cards = below.rows;\n cards = cards.concat(above.rows);\n\n const ret = cards\n .sort((a, b) => {\n const s = Math.abs(a.key - elo) - Math.abs(b.key - elo);\n if (s === 0) {\n return Math.random() - 0.5;\n } else {\n return s;\n }\n })\n .map((c) => {\n return {\n courseID: this.id,\n cardID: c.id,\n elo: c.key,\n };\n });\n\n const str = `below:\\n${below.rows.map((r) => `\\t${r.id}-${r.key}\\n`)}\n\nabove:\\n${above.rows.map((r) => `\\t${r.id}-${r.key}\\n`)}`;\n\n logger.debug(`Getting ${limit} cards centered around elo: ${elo}:\\n\\n` + str);\n\n return ret;\n }\n\n async getCourseConfig(): Promise<CourseConfig> {\n const ret = await getCredentialledCourseConfig(this.id);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Course config not found for course ID: ${this.id}`);\n }\n }\n\n async updateCourseConfig(cfg: CourseConfig): Promise<PouchDB.Core.Response> {\n logger.debug(`Updating: ${JSON.stringify(cfg)}`);\n // write both to the course DB:\n try {\n return await updateCredentialledCourseConfig(this.id, cfg);\n } catch (error) {\n logger.error(`Error updating course config in course DB: ${error}`);\n throw error;\n }\n }\n\n async updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response> {\n if (!elo) {\n throw new Error(`Cannot update card elo with null or undefined value for card ID: ${cardId}`);\n }\n\n try {\n const result = await this.updateQueue.update<\n CardData & PouchDB.Core.GetMeta & PouchDB.Core.IdMeta\n >(cardId, (card) => {\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return card;\n });\n return { ok: true, id: cardId, rev: result._rev };\n } catch (error) {\n logger.error(`Failed to update card elo for card ID: ${cardId}`, error);\n throw new Error(`Failed to update card elo for card ID: ${cardId}`);\n }\n }\n\n async getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>> {\n const ret = await getAppliedTags(this.id, cardId);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Failed to find tags for card ${this.id}-${cardId}`);\n }\n }\n\n async addTagToCard(\n cardId: string,\n tagId: string,\n updateELO?: boolean\n ): Promise<PouchDB.Core.Response> {\n return await addTagToCard(\n this.id,\n cardId,\n tagId,\n (await this._getCurrentUser()).getUsername(),\n updateELO\n );\n }\n\n async removeTagFromCard(cardId: string, tagId: string): Promise<PouchDB.Core.Response> {\n return await removeTagFromCard(this.id, cardId, tagId);\n }\n\n async createTag(name: string, author: string): Promise<PouchDB.Core.Response> {\n return await createTag(this.id, name, author);\n }\n\n async getTag(tagId: string): Promise<PouchDB.Core.GetMeta & PouchDB.Core.Document<Tag>> {\n return await getTag(this.id, tagId);\n }\n\n async updateTag(tag: Tag): Promise<PouchDB.Core.Response> {\n if (tag.course !== this.id) {\n throw new Error(`Tag ${JSON.stringify(tag)} does not belong to course ${this.id}`);\n }\n\n return await updateTag(tag);\n }\n\n async getCourseTagStubs(): Promise<PouchDB.Core.AllDocsResponse<Tag>> {\n return getCourseTagStubs(this.id);\n }\n\n async addNote(\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [key: string]: PouchDB.Core.FullAttachment },\n elo: CourseElo = blankCourseElo()\n ): Promise<DataLayerResult> {\n try {\n const resp = await addNote55(this.id, codeCourse, shape, data, author, tags, uploads, elo);\n if (resp.ok) {\n // Check if card creation failed (property added by addNote55)\n if ((resp as any).cardCreationFailed) {\n logger.warn(\n `[courseDB.addNote] Note added but card creation failed: ${\n (resp as any).cardCreationError\n }`\n );\n return {\n status: Status.error,\n message: `Note was added but no cards were created: ${(resp as any).cardCreationError}`,\n id: resp.id,\n };\n }\n return {\n status: Status.ok,\n message: '',\n id: resp.id,\n };\n } else {\n return {\n status: Status.error,\n message: 'Unexpected error adding note',\n };\n }\n } catch (e) {\n const err = e as PouchDB.Core.Error;\n logger.error(\n `[addNote] error ${err.name}\\n\\treason: ${err.reason}\\n\\tmessage: ${err.message}`\n );\n return {\n status: Status.error,\n message: `Error adding note to course. ${(e as PouchError).reason || err.message}`,\n };\n }\n }\n\n async getCourseDoc<T extends SkuilderCourseData>(\n id: string,\n options?: PouchDB.Core.GetOptions\n ): Promise<PouchDB.Core.GetMeta & PouchDB.Core.Document<T>> {\n return await getCourseDoc(this.id, id, options);\n }\n\n async getCourseDocs<T extends SkuilderCourseData>(\n ids: string[],\n options: PouchDB.Core.AllDocsOptions = {}\n ): Promise<PouchDB.Core.AllDocsWithKeysResponse<{} & T>> {\n return await getCourseDocs(this.id, ids, options);\n }\n\n ////////////////////////////////////\n // NavigationStrategyManager implementation\n ////////////////////////////////////\n\n getNavigationStrategy(id: string): Promise<ContentNavigationStrategyData> {\n logger.debug(`[courseDB] Getting navigation strategy: ${id}`);\n\n if (id == '') {\n const strategy: ContentNavigationStrategyData = {\n _id: 'NAVIGATION_STRATEGY-ELO',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'ELO',\n description: 'ELO-based navigation strategy for ordering content by difficulty',\n implementingClass: Navigators.ELO,\n course: this.id,\n serializedData: '', // serde is a noop for ELO navigator.\n };\n return Promise.resolve(strategy);\n } else {\n return this.db.get(id);\n }\n }\n\n async getAllNavigationStrategies(): Promise<ContentNavigationStrategyData[]> {\n const prefix = DocTypePrefixes[DocType.NAVIGATION_STRATEGY];\n const result = await this.db.allDocs<ContentNavigationStrategyData>({\n startkey: prefix,\n endkey: `${prefix}\\ufff0`,\n include_docs: true,\n });\n return result.rows.map((row) => row.doc!);\n }\n\n async addNavigationStrategy(data: ContentNavigationStrategyData): Promise<void> {\n logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);\n // // For now, just log the data and return success\n // logger.debug(JSON.stringify(data));\n return this.db.put(data).then(() => {});\n }\n updateNavigationStrategy(id: string, data: ContentNavigationStrategyData): Promise<void> {\n logger.debug(`[courseDB] Updating navigation strategy: ${id}`);\n // For now, just log the data and return success\n logger.debug(JSON.stringify(data));\n return Promise.resolve();\n }\n\n async surfaceNavigationStrategy(): Promise<ContentNavigationStrategyData> {\n try {\n const config = await this.getCourseConfig();\n // @ts-expect-error tmp: defaultNavigationStrategyId property does not yet exist\n if (config.defaultNavigationStrategyId) {\n try {\n // @ts-expect-error tmp: defaultNavigationStrategyId property does not yet exist\n const strategy = await this.getNavigationStrategy(config.defaultNavigationStrategyId);\n if (strategy) {\n logger.debug(`Surfacing strategy ${strategy.name} from course config`);\n return strategy;\n }\n } catch (e) {\n logger.warn(\n // @ts-expect-error tmp: defaultNavigationStrategyId property does not yet exist\n `Failed to load strategy '${config.defaultNavigationStrategyId}' specified in course config. Falling back to ELO.`,\n e\n );\n }\n }\n } catch (e) {\n logger.warn(\n 'Could not retrieve course config to determine navigation strategy. Falling back to ELO.',\n e\n );\n }\n\n logger.warn(`Returning hard-coded default ELO navigator`);\n const ret: ContentNavigationStrategyData = {\n _id: 'NAVIGATION_STRATEGY-ELO',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'ELO',\n description: 'ELO-based navigation strategy',\n implementingClass: Navigators.ELO,\n course: this.id,\n serializedData: '', // serde is a noop for ELO navigator.\n };\n return Promise.resolve(ret);\n }\n\n ////////////////////////////////////\n // END NavigationStrategyManager implementation\n ////////////////////////////////////\n\n ////////////////////////////////////\n // StudyContentSource implementation\n ////////////////////////////////////\n\n public async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {\n const u = await this._getCurrentUser();\n\n try {\n const strategy = await this.surfaceNavigationStrategy();\n const navigator = await ContentNavigator.create(u, this, strategy);\n return navigator.getNewCards(limit);\n } catch (e) {\n logger.error(`[courseDB] Error surfacing a NavigationStrategy: ${e}`);\n throw e;\n }\n }\n\n public async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n const u = await this._getCurrentUser();\n\n try {\n const strategy = await this.surfaceNavigationStrategy();\n const navigator = await ContentNavigator.create(u, this, strategy);\n return navigator.getPendingReviews();\n } catch (e) {\n logger.error(`[courseDB] Error surfacing a NavigationStrategy: ${e}`);\n throw e;\n }\n }\n\n public async getCardsCenteredAtELO(\n options: {\n limit: number;\n elo: 'user' | 'random' | number;\n } = {\n limit: 99,\n elo: 'user',\n },\n filter?: (a: QualifiedCardID) => boolean\n ): Promise<StudySessionItem[]> {\n let targetElo: number;\n\n if (options.elo === 'user') {\n const u = await this._getCurrentUser();\n\n targetElo = -1;\n try {\n const courseDoc = (await u.getCourseRegistrationsDoc()).courses.find((c) => {\n return c.courseID === this.id;\n })!;\n targetElo = EloToNumber(courseDoc.elo);\n } catch {\n targetElo = 1000;\n }\n } else if (options.elo === 'random') {\n const bounds = await GET_CACHED(`elo-bounds-${this.id}`, () => this.getELOBounds());\n targetElo = Math.round(bounds.low + Math.random() * (bounds.high - bounds.low));\n // logger.log(`Picked ${targetElo} from [${bounds.low}, ${bounds.high}]`);\n } else {\n targetElo = options.elo;\n }\n\n let cards: (QualifiedCardID & { elo?: number })[] = [];\n let mult: number = 4;\n let previousCount: number = -1;\n let newCount: number = 0;\n\n while (cards.length < options.limit && newCount !== previousCount) {\n cards = await this.getCardsByELO(targetElo, mult * options.limit);\n previousCount = newCount;\n newCount = cards.length;\n\n logger.debug(`Found ${cards.length} elo neighbor cards...`);\n\n if (filter) {\n cards = cards.filter(filter);\n logger.debug(`Filtered to ${cards.length} cards...`);\n }\n\n mult *= 2;\n }\n\n const selectedCards: {\n courseID: string;\n cardID: string;\n elo?: number;\n }[] = [];\n\n while (selectedCards.length < options.limit && cards.length > 0) {\n const index = randIntWeightedTowardZero(cards.length);\n const card = cards.splice(index, 1)[0];\n selectedCards.push(card);\n }\n\n return selectedCards.map((c) => {\n return {\n courseID: this.id,\n cardID: c.cardID,\n contentSourceType: 'course',\n contentSourceID: this.id,\n elo: c.elo,\n status: 'new',\n };\n });\n }\n\n // Admin search methods\n public async searchCards(query: string): Promise<any[]> {\n logger.log(`[CourseDB ${this.id}] Searching for: \"${query}\"`);\n\n // Try multiple search approaches\n let displayableData;\n\n try {\n // Try regex search on the correct data structure: data[0].data\n displayableData = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n 'data.0.data': { $regex: `.*${query}.*` },\n },\n });\n logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`);\n } catch (regexError) {\n logger.log(\n `[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,\n regexError\n );\n\n // Fallback: get all displayable data and filter manually\n const allDisplayable = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n },\n });\n\n logger.log(\n `[CourseDB ${this.id}] Retrieved ${allDisplayable.docs.length} documents for manual filtering`\n );\n\n displayableData = {\n docs: allDisplayable.docs.filter((doc) => {\n // Search entire document as JSON string - inclusive approach for admin tool\n const docString = JSON.stringify(doc).toLowerCase();\n const match = docString.includes(query.toLowerCase());\n if (match) {\n logger.log(`[CourseDB ${this.id}] Manual match found in document: ${doc._id}`);\n }\n return match;\n }),\n };\n }\n\n logger.log(\n `[CourseDB ${this.id}] Found ${displayableData.docs.length} displayable data documents`\n );\n\n if (displayableData.docs.length === 0) {\n // Debug: Let's see what displayable data exists\n const allDisplayableData = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n },\n limit: 5, // Just sample a few\n });\n\n logger.log(\n `[CourseDB ${this.id}] Sample displayable data:`,\n allDisplayableData.docs.map((d) => ({\n id: d._id,\n docType: (d as any).docType,\n dataStructure: (d as any).data ? Object.keys((d as any).data) : 'no data field',\n dataContent: (d as any).data,\n fullDoc: d,\n }))\n );\n }\n\n const allResults: any[] = [];\n\n for (const dd of displayableData.docs) {\n const cards = await this.db.find({\n selector: {\n docType: 'CARD',\n id_displayable_data: { $in: [dd._id] },\n },\n });\n\n logger.log(\n `[CourseDB ${this.id}] Displayable data ${dd._id} linked to ${cards.docs.length} cards`\n );\n allResults.push(...cards.docs);\n }\n\n logger.log(`[CourseDB ${this.id}] Total cards found: ${allResults.length}`);\n return allResults;\n }\n\n public async find(\n request: PouchDB.Find.FindRequest<any>\n ): Promise<PouchDB.Find.FindResponse<any>> {\n return this.db.find(request);\n }\n}\n\n/**\n * Returns a list of registered datashapes for the specified\n * course.\n * @param courseID The ID of the course\n */\nexport async function getCourseDataShapes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n return cfg!.dataShapes;\n}\n\nexport async function getCredentialledDataShapes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n\n return cfg.dataShapes;\n}\n\nexport async function getCourseQuestionTypes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n return cfg!.questionTypes;\n}\n\n// todo: this is actually returning full tag docs now.\n// - performance issue when tags have lots of\n// applied docs\n// - will require a computed couch DB view\nexport async function getCourseTagStubs(\n courseID: string\n): Promise<PouchDB.Core.AllDocsResponse<Tag>> {\n logger.debug(`Getting tag stubs for course: ${courseID}`);\n const stubs = await filterAllDocsByPrefix<Tag>(\n getCourseDB(courseID),\n DocType.TAG.valueOf() + '-'\n );\n\n stubs.rows.forEach((row) => {\n logger.debug(`\\tTag stub for doc: ${row.id}`);\n });\n\n return stubs;\n}\n\nexport async function deleteTag(courseID: string, tagName: string) {\n tagName = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n const doc = await courseDB.get<Tag>(DocType.TAG.valueOf() + '-' + tagName);\n const resp = await courseDB.remove(doc);\n return resp;\n}\n\nexport async function createTag(courseID: string, tagName: string, author: string) {\n logger.debug(`Creating tag: ${tagName}...`);\n const tagID = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n const resp = await courseDB.put<Tag>({\n course: courseID,\n docType: DocType.TAG,\n name: tagName,\n snippet: '',\n taggedCards: [],\n wiki: '',\n author,\n _id: tagID,\n });\n return resp;\n}\n\nexport async function updateTag(tag: Tag) {\n const prior = await getTag(tag.course, tag.name);\n return await getCourseDB(tag.course).put<Tag>({\n ...tag,\n _rev: prior._rev,\n });\n}\n\nexport async function getTag(courseID: string, tagName: string) {\n const tagID = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n return courseDB.get<Tag>(tagID);\n}\n\nexport async function removeTagFromCard(courseID: string, cardID: string, tagID: string) {\n // todo: possible future perf. hit if tags have large #s of taggedCards.\n // In this case, should be converted to a server-request\n tagID = getTagID(tagID);\n const courseDB = getCourseDB(courseID);\n const tag = await courseDB.get<Tag>(tagID);\n tag.taggedCards = tag.taggedCards.filter((taggedID) => {\n return cardID !== taggedID;\n });\n return courseDB.put<Tag>(tag);\n}\n\n/**\n * Returns an array of ancestor tag IDs, where:\n * return[0] = parent,\n * return[1] = grandparent,\n * return[2] = great grandparent,\n * etc.\n *\n * If ret is empty, the tag itself is a root\n */\nexport function getAncestorTagIDs(courseID: string, tagID: string): string[] {\n tagID = getTagID(tagID);\n const split = tagID.split('>');\n if (split.length === 1) {\n return [];\n } else {\n split.pop();\n const parent = split.join('>');\n return [parent].concat(getAncestorTagIDs(courseID, parent));\n }\n}\n\nexport async function getChildTagStubs(courseID: string, tagID: string) {\n return await filterAllDocsByPrefix(getCourseDB(courseID), tagID + '>');\n}\n\nexport async function getAppliedTags(id_course: string, id_card: string) {\n const db = getCourseDB(id_course);\n\n const result = await db.query<TagStub>('getTags', {\n startkey: id_card,\n endkey: id_card,\n // include_docs: true\n });\n\n // log(`getAppliedTags looked up: ${id_card}`);\n // log(`getAppliedTags returning: ${JSON.stringify(result)}`);\n\n return result;\n}\n\nexport async function updateCardElo(courseID: string, cardID: string, elo: CourseElo) {\n if (elo) {\n // checking against null, undefined, NaN\n const cDB = getCourseDB(courseID);\n const card = await cDB.get<CardData>(cardID);\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return cDB.put(card); // race conditions - is it important? probably not (net-zero effect)\n }\n}\n\nexport async function updateCredentialledCourseConfig(courseID: string, config: CourseConfig) {\n logger.debug(`Updating course config:\n\n${JSON.stringify(config)}\n`);\n\n const db = getCourseDB(courseID);\n const old = await getCredentialledCourseConfig(courseID);\n\n return await db.put<CourseConfig>({\n ...config,\n _rev: (old as any)._rev,\n });\n}\n\nfunction isSuccessRow<T>(\n row:\n | {\n key: PouchDB.Core.DocumentKey;\n error: 'not_found';\n }\n | {\n doc?: PouchDB.Core.ExistingDocument<PouchDB.Core.AllDocsMeta & T> | null | undefined;\n id: PouchDB.Core.DocumentId;\n key: PouchDB.Core.DocumentKey;\n value: {\n rev: PouchDB.Core.RevisionId;\n deleted?: boolean | undefined;\n };\n }\n): row is {\n doc?: PouchDB.Core.ExistingDocument<PouchDB.Core.AllDocsMeta & T> | null | undefined;\n id: PouchDB.Core.DocumentId;\n key: PouchDB.Core.DocumentKey;\n value: {\n rev: PouchDB.Core.RevisionId;\n deleted?: boolean | undefined;\n };\n} {\n return 'doc' in row && row.doc !== null && row.doc !== undefined;\n}\n","import {\n StudyContentSource,\n StudySessionNewItem,\n StudySessionReviewItem,\n} from '@db/core/interfaces/contentSource';\nimport { ClassroomConfig } from '@vue-skuilder/common';\nimport { ENV } from '@db/factory';\nimport { logger } from '@db/util/logger';\nimport moment from 'moment';\nimport pouch from './pouchdb-setup';\nimport { getCourseDB, getStartAndEndKeys, createPouchDBConfig, REVIEW_TIME_FORMAT } from '.';\nimport { CourseDB, getTag } from './courseDB';\n\nimport { UserDBInterface } from '@db/core';\nimport {\n AssignedContent,\n AssignedCourse,\n AssignedTag,\n StudentClassroomDBInterface,\n TeacherClassroomDBInterface,\n} from '@db/core/interfaces/classroomDB';\nimport { ScheduledCard } from '@db/core/types/user';\n\nconst classroomLookupDBTitle = 'classdb-lookup';\nexport const CLASSROOM_CONFIG = 'ClassroomConfig';\n\nexport type ClassroomMessage = object;\n\nabstract class ClassroomDBBase {\n public _id!: string;\n protected _db!: PouchDB.Database;\n protected _cfg!: ClassroomConfig;\n protected _initComplete: boolean = false;\n\n protected readonly _content_prefix: string = 'content';\n protected get _content_searchkeys() {\n return getStartAndEndKeys(this._content_prefix);\n }\n\n protected abstract init(): Promise<void>;\n\n public async getAssignedContent(): Promise<AssignedContent[]> {\n logger.info(`Getting assigned content...`);\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const docRows = await this._db.allDocs<AssignedContent>({\n startkey: this._content_prefix,\n endkey: this._content_prefix + `\\ufff0`,\n include_docs: true,\n });\n\n const ret = docRows.rows.map((row) => {\n return row.doc!;\n });\n // logger.info(`Assigned content: ${JSON.stringify(ret)}`);\n\n return ret;\n }\n\n protected getContentId(content: AssignedContent): string {\n if (content.type === 'tag') {\n return `${this._content_prefix}-${content.courseID}-${content.tagID}`;\n } else {\n return `${this._content_prefix}-${content.courseID}`;\n }\n }\n\n public get ready(): boolean {\n return this._initComplete;\n }\n public getConfig(): ClassroomConfig {\n return this._cfg;\n }\n}\n\nexport class StudentClassroomDB\n extends ClassroomDBBase\n implements StudyContentSource, StudentClassroomDBInterface\n{\n // private readonly _prefix: string = 'content';\n private userMessages!: PouchDB.Core.Changes<object>;\n private _user: UserDBInterface;\n\n private constructor(classID: string, user: UserDBInterface) {\n super();\n this._id = classID;\n this._user = user;\n // init() is called explicitly in factory method, not in constructor\n }\n\n async init(): Promise<void> {\n const dbName = `classdb-student-${this._id}`;\n this._db = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n try {\n const cfg = await this._db.get<ClassroomConfig>(CLASSROOM_CONFIG);\n this._cfg = cfg;\n this.userMessages = this._db.changes({\n since: 'now',\n live: true,\n include_docs: true,\n });\n this._initComplete = true;\n return;\n } catch (e) {\n throw new Error(`Error in StudentClassroomDB constructor: ${JSON.stringify(e)}`);\n }\n }\n\n public static async factory(classID: string, user: UserDBInterface): Promise<StudentClassroomDB> {\n const ret = new StudentClassroomDB(classID, user);\n await ret.init();\n return ret;\n }\n\n public setChangeFcn(f: (value: unknown) => object): void {\n // todo: make this into a view request, w/ the user's name attached\n // todo: requires creating the view doc on classroom create in /express\n void this.userMessages.on('change', f);\n }\n\n public async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n const u = this._user;\n return (await u.getPendingReviews())\n .filter((r) => r.scheduledFor === 'classroom' && r.schedulingAgentId === this._id)\n .map((r) => {\n return {\n ...r,\n qualifiedID: `${r.courseId}-${r.cardId}`,\n courseID: r.courseId,\n cardID: r.cardId,\n contentSourceType: 'classroom',\n contentSourceID: this._id,\n reviewID: r._id,\n status: 'review',\n };\n });\n }\n\n public async getNewCards(): Promise<StudySessionNewItem[]> {\n const activeCards = await this._user.getActiveCards();\n const now = moment.utc();\n const assigned = await this.getAssignedContent();\n const due = assigned.filter((c) => now.isAfter(moment.utc(c.activeOn, REVIEW_TIME_FORMAT)));\n\n logger.info(`Due content: ${JSON.stringify(due)}`);\n\n let ret: StudySessionNewItem[] = [];\n\n for (let i = 0; i < due.length; i++) {\n const content = due[i];\n\n if (content.type === 'course') {\n const db = new CourseDB(content.courseID, async () => this._user);\n ret = ret.concat(await db.getNewCards());\n } else if (content.type === 'tag') {\n const tagDoc = await getTag(content.courseID, content.tagID);\n\n ret = ret.concat(\n tagDoc.taggedCards.map((c) => {\n return {\n courseID: content.courseID,\n cardID: c,\n qualifiedID: `${content.courseID}-${c}`,\n contentSourceType: 'classroom',\n contentSourceID: this._id,\n status: 'new',\n };\n })\n );\n } else if (content.type === 'card') {\n // returning card docs - not IDs\n ret.push(await getCourseDB(content.courseID).get(content.cardID));\n }\n }\n\n logger.info(\n `New Cards from classroom ${this._cfg.name}: ${ret.map((c) => `${c.courseID}-${c.cardID}`)}`\n );\n\n return ret.filter((c) => {\n if (activeCards.some((ac) => c.cardID === ac.cardID)) {\n // [ ] almost certainly broken after removing qualifiedID from StudySessionItem\n return false;\n } else {\n return true;\n }\n });\n }\n}\n\n/**\n * Interface for managing a classroom.\n */\nexport class TeacherClassroomDB extends ClassroomDBBase implements TeacherClassroomDBInterface {\n private _stuDb!: PouchDB.Database;\n\n private constructor(classID: string) {\n super();\n this._id = classID;\n }\n\n async init(): Promise<void> {\n const dbName = `classdb-teacher-${this._id}`;\n const stuDbName = `classdb-student-${this._id}`;\n this._db = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n this._stuDb = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + stuDbName,\n createPouchDBConfig()\n );\n try {\n return this._db\n .get<ClassroomConfig>(CLASSROOM_CONFIG)\n .then((cfg) => {\n this._cfg = cfg;\n this._initComplete = true;\n })\n .then(() => {\n return;\n });\n } catch (e) {\n throw new Error(`Error in TeacherClassroomDB constructor: ${JSON.stringify(e)}`);\n }\n }\n\n public static async factory(classID: string): Promise<TeacherClassroomDB> {\n const ret = new TeacherClassroomDB(classID);\n await ret.init();\n return ret;\n }\n\n public async removeContent(content: AssignedContent): Promise<void> {\n const contentID = this.getContentId(content);\n\n try {\n const doc = await this._db.get(contentID);\n await this._db.remove(doc);\n void this._db.replicate.to(this._stuDb, {\n doc_ids: [contentID],\n });\n } catch (error) {\n logger.error('Failed to remove content:', contentID, error);\n }\n }\n\n public async assignContent(content: AssignedContent): Promise<boolean> {\n let put: PouchDB.Core.Response;\n const id: string = this.getContentId(content);\n\n if (content.type === 'tag') {\n put = await this._db.put<AssignedTag>({\n courseID: content.courseID,\n tagID: content.tagID,\n type: 'tag',\n _id: id,\n assignedBy: content.assignedBy,\n assignedOn: moment.utc(),\n activeOn: content.activeOn || moment.utc(),\n });\n } else {\n put = await this._db.put<AssignedCourse>({\n courseID: content.courseID,\n type: 'course',\n _id: id,\n assignedBy: content.assignedBy,\n assignedOn: moment.utc(),\n activeOn: content.activeOn || moment.utc(),\n });\n }\n\n if (put.ok) {\n void this._db.replicate.to(this._stuDb, {\n doc_ids: [id],\n });\n return true;\n } else {\n return false;\n }\n }\n}\n\nexport const ClassroomLookupDB: () => PouchDB.Database = () =>\n new pouch(ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + classroomLookupDBTitle, {\n skip_setup: true,\n });\n\nexport function getClassroomDB(classID: string, version: 'student' | 'teacher'): PouchDB.Database {\n const dbName = `classdb-${version}-${classID}`;\n logger.info(`Retrieving classroom db: ${dbName}`);\n\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n\nexport async function getClassroomConfig(classID: string): Promise<ClassroomConfig> {\n return await getClassroomDB(classID, 'student').get<ClassroomConfig>(CLASSROOM_CONFIG);\n}\n","import pouch from './pouchdb-setup';\nimport { ENV } from '@db/factory';\nimport {\n createPouchDBConfig,\n getStartAndEndKeys,\n getCredentialledCourseConfig,\n updateCredentialledCourseConfig,\n} from '.';\nimport { TeacherClassroomDB, ClassroomLookupDB } from './classroomDB';\nimport { PouchError } from './types';\n\nimport { AdminDBInterface } from '@db/core';\nimport CourseLookup from './courseLookupDB';\nimport { logger } from '@db/util/logger';\n\nexport class AdminDB implements AdminDBInterface {\n private usersDB!: PouchDB.Database;\n\n constructor() {\n // [ ] execute a check here against credentials, and throw an error\n // if the user is not an admin\n this.usersDB = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + '_users',\n createPouchDBConfig()\n );\n }\n\n public async getUsers() {\n return (\n await this.usersDB.allDocs({\n include_docs: true,\n ...getStartAndEndKeys('org.couchdb.user:'),\n })\n ).rows.map((r) => r.doc!);\n }\n\n public async getCourses() {\n const list = await CourseLookup.allCourseWare();\n return await Promise.all(\n list.map((c) => {\n return getCredentialledCourseConfig(c._id);\n })\n );\n }\n public async removeCourse(id: string) {\n // remove the indexer\n const delResp = await CourseLookup.delete(id);\n\n // set the 'CourseConfig' to 'deleted'\n const cfg = await getCredentialledCourseConfig(id);\n cfg.deleted = true;\n const isDeletedResp = await updateCredentialledCourseConfig(id, cfg);\n\n return {\n ok: delResp.ok && isDeletedResp.ok,\n id: delResp.id,\n rev: delResp.rev,\n };\n }\n\n public async getClassrooms() {\n // const joincodes =\n const uuids = (\n await ClassroomLookupDB().allDocs<{ uuid: string }>({\n include_docs: true,\n })\n ).rows.map((r) => r.doc!.uuid);\n logger.debug(uuids.join(', '));\n\n const promisedCRDbs: TeacherClassroomDB[] = [];\n for (let i = 0; i < uuids.length; i++) {\n try {\n const db = await TeacherClassroomDB.factory(uuids[i]);\n promisedCRDbs.push(db);\n } catch (e) {\n const err = e as PouchError;\n if (err.error && err.error === 'not_found') {\n logger.warn(`db ${uuids[i]} not found`);\n }\n }\n }\n\n const dbs = await Promise.all(promisedCRDbs);\n return dbs.map((db) => {\n return {\n ...db.getConfig(),\n _id: db._id,\n };\n });\n }\n}\n","import { ENV, NOT_SET } from '@db/factory';\nimport { logger } from '@db/util/logger';\nimport fetch from 'cross-fetch';\n\ninterface SessionResponse {\n info: unknown;\n ok: boolean;\n userCtx: {\n name: string;\n roles: string[];\n };\n}\n\nexport async function getCurrentSession(): Promise<SessionResponse> {\n // Legacy XMLHttpRequest implementation\n // return new Promise((resolve, reject) => {\n // const authXML = new XMLHttpRequest();\n // authXML.withCredentials = true;\n //\n // authXML.onerror = (e): void => {\n // reject(new Error('Session check failed:', e));\n // };\n //\n // authXML.addEventListener('load', () => {\n // try {\n // const resp: SessionResponse = JSON.parse(authXML.responseText);\n // resolve(resp);\n // } catch (e) {\n // reject(e);\n // }\n // });\n //\n // const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}_session`;\n // authXML.open('GET', url);\n // authXML.send();\n // });\n \n try {\n // Handle case where ENV variables might not be properly set\n if (ENV.COUCHDB_SERVER_URL === NOT_SET || ENV.COUCHDB_SERVER_PROTOCOL === NOT_SET) {\n throw new Error(`CouchDB server configuration not properly initialized. Protocol: \"${ENV.COUCHDB_SERVER_PROTOCOL}\", URL: \"${ENV.COUCHDB_SERVER_URL}\"`);\n }\n \n // Ensure URL has proper slash before _session endpoint\n const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith('/') \n ? ENV.COUCHDB_SERVER_URL.slice(0, -1) \n : ENV.COUCHDB_SERVER_URL;\n const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;\n logger.debug(`Attempting session check at: ${url}`);\n \n const response = await fetch(url, {\n method: 'GET',\n credentials: 'include',\n });\n \n if (!response.ok) {\n throw new Error(`Session check failed: ${response.status}`);\n }\n \n const resp: SessionResponse = await response.json();\n return resp;\n } catch (error) {\n // Use same URL construction logic for error reporting\n const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith('/') \n ? ENV.COUCHDB_SERVER_URL.slice(0, -1) \n : ENV.COUCHDB_SERVER_URL;\n const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;\n logger.error(`Session check error attempting to connect to: ${url} - ${error}`);\n throw new Error(`Session check failed connecting to ${url}: ${error}`);\n }\n}\n\nexport async function getLoggedInUsername(): Promise<string> {\n const session = await getCurrentSession();\n if (session.userCtx.name && session.userCtx.name !== '') {\n return session.userCtx.name;\n }\n // Not logged in - throw so caller can handle guest account\n throw new Error('No logged in user');\n}\n","// packages/db/src/impl/couch/CouchDBSyncStrategy.ts\n\nimport { ENV } from '@db/factory';\nimport { GuestUsername } from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\nimport { Status } from '@vue-skuilder/common';\nimport type { SyncStrategy } from '../common/SyncStrategy';\nimport type { AccountCreationResult, AuthenticationResult } from '../common/types';\nimport { getLocalUserDB, hexEncode, updateGuestAccountExpirationDate, accomodateGuest } from '../common';\nimport pouch from './pouchdb-setup';\nimport { createPouchDBConfig } from './index';\nimport { getLoggedInUsername } from './auth';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\n/**\n * Sync strategy that implements full CouchDB remote synchronization\n * Handles account creation, authentication, and live sync with remote CouchDB server\n */\nexport class CouchDBSyncStrategy implements SyncStrategy {\n private syncHandle?: any; // Handle to cancel sync if needed\n\n setupRemoteDB(username: string): PouchDB.Database {\n if (username === GuestUsername || username.startsWith(GuestUsername)) {\n // For guest users, remote is same as local (no remote sync)\n return getLocalUserDB(username);\n } else {\n // For real users, connect to remote CouchDB\n return this.getUserDB(username);\n }\n }\n\n getWriteDB(username: string): PouchDB.Database {\n if (username === GuestUsername || username.startsWith(GuestUsername)) {\n // Guest users write to local database\n return getLocalUserDB(username);\n } else {\n // Authenticated users write to remote (which will sync to local)\n return this.getUserDB(username);\n }\n }\n\n startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void {\n // Only sync if local and remote are different instances\n if (localDB !== remoteDB) {\n this.syncHandle = pouch.sync(localDB, remoteDB, {\n live: true,\n retry: true,\n });\n }\n // If they're the same (guest mode), no sync needed\n }\n\n stopSync?(): void {\n if (this.syncHandle) {\n this.syncHandle.cancel();\n this.syncHandle = undefined;\n }\n }\n\n canCreateAccount(): boolean {\n return true;\n }\n\n canAuthenticate(): boolean {\n return true;\n }\n\n async createAccount(username: string, password: string): Promise<AccountCreationResult> {\n // IMPORTANT: Capture funnel username BEFORE any operations that might change session state\n const funnelUsername = await this.getCurrentUsername();\n const isFunnelAccount = funnelUsername.startsWith(GuestUsername);\n\n if (isFunnelAccount) {\n logger.info(`Creating account for funnel user ${funnelUsername} -> ${username}`);\n }\n\n try {\n const signupRequest = await this.getRemoteCouchRootDB().signUp(username, password);\n\n if (signupRequest.ok) {\n log(`CREATEACCOUNT: Successfully created account for ${username}`);\n\n // Log out any existing session\n try {\n const logoutResult = await this.getRemoteCouchRootDB().logOut();\n log(`CREATEACCOUNT: logged out: ${logoutResult.ok}`);\n } catch {\n // Ignore logout errors - might not be logged in\n }\n\n // Log in as the new user\n const loginResult = await this.getRemoteCouchRootDB().logIn(username, password);\n log(`CREATEACCOUNT: logged in as new user: ${loginResult.ok}`);\n\n if (loginResult.ok) {\n // Migrate funnel account data if applicable\n if (isFunnelAccount) {\n logger.info(`Migrating data from funnel account ${funnelUsername} to ${username}`);\n const migrationResult = await this.migrateFunnelData(funnelUsername, username);\n if (!migrationResult.success) {\n logger.warn(`Migration failed: ${migrationResult.error}`);\n // Continue anyway - don't block account creation\n }\n }\n\n return {\n status: Status.ok,\n error: undefined,\n };\n } else {\n return {\n status: Status.error,\n error: 'Failed to log in after account creation',\n };\n }\n } else {\n logger.warn(`Signup not OK: ${JSON.stringify(signupRequest)}`);\n return {\n status: Status.error,\n error: 'Account creation failed',\n };\n }\n } catch (e: any) {\n if (e.reason === 'Document update conflict.') {\n return {\n status: Status.error,\n error: 'This username is taken!',\n };\n }\n logger.error(`Error on signup: ${JSON.stringify(e)}`);\n return {\n status: Status.error,\n error: e.message || 'Unknown error during account creation',\n };\n }\n }\n\n async authenticate(username: string, password: string): Promise<AuthenticationResult> {\n try {\n const loginResult = await this.getRemoteCouchRootDB().logIn(username, password);\n\n if (loginResult.ok) {\n log(`Successfully logged in as ${username}`);\n return {\n ok: true,\n };\n } else {\n log(`Login failed for ${username}`);\n return {\n ok: false,\n error: 'Invalid username or password',\n };\n }\n } catch (error: any) {\n logger.error(`Authentication error for ${username}:`, error);\n return {\n ok: false,\n error: error.message || 'Authentication failed',\n };\n }\n }\n\n async logout(): Promise<AuthenticationResult> {\n try {\n const result = await this.getRemoteCouchRootDB().logOut();\n return {\n ok: result.ok,\n error: result.ok ? undefined : 'Logout failed',\n };\n } catch (error: any) {\n logger.error('Logout error:', error);\n return {\n ok: false,\n error: error.message || 'Logout failed',\n };\n }\n }\n\n async getCurrentUsername(): Promise<string> {\n logger.log('[funnel] CouchDBSyncStrategy.getCurrentUsername() called');\n try {\n const loggedInUsername = await getLoggedInUsername();\n logger.log('[funnel] getLoggedInUsername() returned:', loggedInUsername);\n return loggedInUsername;\n } catch (e) {\n // Not logged in - return unique guest account\n logger.log('[funnel] getLoggedInUsername() failed, calling accomodateGuest()');\n logger.log('[funnel] Error was:', e);\n const guestInfo = accomodateGuest();\n logger.log('[funnel] accomodateGuest() returned:', guestInfo);\n return guestInfo.username;\n }\n }\n\n /**\n * Migrate data from funnel account to real account\n */\n private async migrateFunnelData(\n oldUsername: string,\n newUsername: string\n ): Promise<{ success: boolean; error?: string }> {\n try {\n logger.info(`Starting data migration from ${oldUsername} to ${newUsername}`);\n\n const oldLocalDB = getLocalUserDB(oldUsername);\n const newLocalDB = getLocalUserDB(newUsername);\n\n // Get all docs from funnel account\n const allDocs = await oldLocalDB.allDocs({ include_docs: true });\n\n logger.info(`Found ${allDocs.rows.length} documents in funnel account`);\n\n // Filter out design docs and prepare for migration\n const docsToMigrate = allDocs.rows\n .filter(row => !row.id.startsWith('_design/'))\n .map(row => ({\n ...row.doc,\n _rev: undefined, // Remove rev to insert as new\n }));\n\n if (docsToMigrate.length > 0) {\n await newLocalDB.bulkDocs(docsToMigrate);\n logger.info(`Successfully migrated ${docsToMigrate.length} documents from ${oldUsername} to ${newUsername}`);\n } else {\n logger.info('No documents to migrate from funnel account');\n }\n\n return { success: true };\n } catch (error) {\n logger.error('Migration failed:', error);\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error'\n };\n }\n }\n\n /**\n * Get remote CouchDB root database for authentication operations\n */\n private getRemoteCouchRootDB(): PouchDB.Database {\n const remoteStr: string =\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + 'skuilder';\n\n try {\n return new pouch(remoteStr, {\n skip_setup: true,\n });\n } catch (error) {\n logger.error('Failed to initialize remote CouchDB connection:', error);\n throw new Error(`Failed to initialize CouchDB: ${JSON.stringify(error)}`);\n }\n }\n\n /**\n * Get remote user database for a specific user\n */\n private getUserDB(username: string): PouchDB.Database {\n const guestAccount: boolean = false;\n\n const hexName = hexEncode(username);\n const dbName = `userdb-${hexName}`;\n log(`Fetching user database: ${dbName} (${username})`);\n\n // Odd construction here the result of a bug in the\n // interaction between pouch, pouch-auth.\n // see: https://github.com/pouchdb-community/pouchdb-authentication/issues/239\n const ret = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n\n if (guestAccount) {\n updateGuestAccountExpirationDate(ret);\n }\n\n return ret;\n }\n}\n","import { ENV } from '@db/factory';\nimport {\n DocType,\n DocTypePrefixes,\n GuestUsername,\n log,\n SkuilderCourseData,\n} from '../../core/types/types-legacy';\nimport fetch from 'cross-fetch';\n// import { getCurrentUser } from '../../stores/useAuthStore';\nimport moment, { Moment } from 'moment';\nimport { logger } from '@db/util/logger';\n\nimport pouch from './pouchdb-setup';\n\nimport { ScheduledCard } from '@db/core/types/user';\nimport process from 'process';\n\nconst isBrowser = typeof window !== 'undefined';\n\nif (isBrowser) {\n (window as any).process = process; // required as a fix for pouchdb - see #18\n}\n\nconst expiryDocID: string = 'GuestAccountExpirationDate';\n\nconst GUEST_LOCAL_DB = `userdb-${GuestUsername}`;\nexport const localUserDB: PouchDB.Database = new pouch(GUEST_LOCAL_DB);\n\nexport function hexEncode(str: string): string {\n let hex: string;\n let returnStr: string = '';\n\n for (let i = 0; i < str.length; i++) {\n hex = str.charCodeAt(i).toString(16);\n returnStr += ('000' + hex).slice(3);\n }\n\n return returnStr;\n}\nconst pouchDBincludeCredentialsConfig: PouchDB.Configuration.RemoteDatabaseConfiguration = {\n fetch(url: string | Request, opts: RequestInit): Promise<Response> {\n opts.credentials = 'include';\n\n return (pouch as any).fetch(url, opts);\n },\n} as PouchDB.Configuration.RemoteDatabaseConfiguration;\n\n/**\n * Creates PouchDB configuration with appropriate authentication method\n * - Uses HTTP Basic Auth when credentials are available (Node.js/MCP)\n * - Falls back to cookie auth for browser environments\n */\nexport function createPouchDBConfig(): PouchDB.Configuration.RemoteDatabaseConfiguration {\n // Check if running in Node.js with explicit credentials\n const hasExplicitCredentials = ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD;\n const isNodeEnvironment = typeof window === 'undefined';\n \n if (hasExplicitCredentials && isNodeEnvironment) {\n // Use HTTP Basic Auth for Node.js environments (MCP server)\n return {\n fetch(url: string | Request, opts: RequestInit = {}): Promise<Response> {\n const basicAuth = btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`);\n const headers = new Headers(opts.headers || {});\n headers.set('Authorization', `Basic ${basicAuth}`);\n \n const newOpts = {\n ...opts,\n headers: headers\n };\n \n return (pouch as any).fetch(url, newOpts);\n }\n } as PouchDB.Configuration.RemoteDatabaseConfiguration;\n }\n \n // Use cookie-based auth for browser environments or when no explicit credentials\n return pouchDBincludeCredentialsConfig;\n}\n\nfunction getCouchDB(dbName: string): PouchDB.Database {\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n\nexport function getCourseDB(courseID: string): PouchDB.Database {\n // todo: keep a cache of opened courseDBs? need to benchmark this somehow\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + 'coursedb-' + courseID,\n createPouchDBConfig()\n );\n}\n\nexport async function getLatestVersion() {\n try {\n const docs = await getCouchDB('version').allDocs({\n descending: true,\n limit: 1,\n });\n if (docs && docs.rows && docs.rows[0]) {\n return docs.rows[0].id;\n } else {\n return '0.0.0';\n }\n } catch {\n return '-1';\n }\n}\n\n/**\n * Checks the remote couchdb to see if a given username is available\n * @param username The username to be checked\n */\nexport async function usernameIsAvailable(username: string): Promise<boolean> {\n log(`Checking availability of ${username}`);\n \n // Legacy XMLHttpRequest implementation (browser sync)\n // const req = new XMLHttpRequest();\n // const url = ENV.COUCHDB_SERVER_URL + 'userdb-' + hexEncode(username);\n // req.open('HEAD', url, false);\n // req.send();\n // return req.status === 404;\n \n try {\n const url = ENV.COUCHDB_SERVER_URL + 'userdb-' + hexEncode(username);\n const response = await fetch(url, { method: 'HEAD' });\n return response.status === 404;\n } catch (error) {\n log(`Error checking username availability: ${error}`);\n return false;\n }\n}\n\nexport function updateGuestAccountExpirationDate(guestDB: PouchDB.Database<object>) {\n const currentTime = moment.utc();\n const expirationDate: string = currentTime.add(2, 'months').toISOString();\n\n void guestDB\n .get(expiryDocID)\n .then((doc) => {\n return guestDB.put({\n _id: expiryDocID,\n _rev: doc._rev,\n date: expirationDate,\n });\n })\n .catch(() => {\n return guestDB.put({\n _id: expiryDocID,\n date: expirationDate,\n });\n });\n}\n\nexport function getCourseDocs<T extends SkuilderCourseData>(\n courseID: string,\n docIDs: string[],\n options: PouchDB.Core.AllDocsOptions = {}\n) {\n return getCourseDB(courseID).allDocs<T>({\n ...options,\n keys: docIDs,\n });\n}\n\nexport function getCourseDoc<T extends SkuilderCourseData>(\n courseID: string,\n docID: PouchDB.Core.DocumentId,\n options: PouchDB.Core.GetOptions = {}\n): Promise<T> {\n return getCourseDB(courseID).get<T>(docID, options);\n}\n\n/**\n * Returns *all* cards from the parameter courses, in\n * 'qualified' card format (\"courseid-cardid\")\n *\n * @param courseIDs A list of all course_ids to get cards from\n */\nexport async function getRandomCards(courseIDs: string[]) {\n if (courseIDs.length === 0) {\n throw new Error(`getRandomCards:\\n\\tAttempted to get all cards from no courses!`);\n } else {\n const courseResults = await Promise.all(\n courseIDs.map((course) => {\n return getCourseDB(course).find({\n selector: {\n docType: DocType.CARD,\n },\n limit: 1000,\n });\n })\n );\n\n const ret: string[] = [];\n courseResults.forEach((courseCards, index) => {\n courseCards.docs.forEach((doc) => {\n ret.push(`${courseIDs[index]}-${doc._id}`);\n });\n });\n\n return ret;\n }\n}\n\nexport const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';\n\nexport function getCouchUserDB(username: string): PouchDB.Database {\n const guestAccount: boolean = false;\n // console.log(`Getting user db: ${username}`);\n\n const hexName = hexEncode(username);\n const dbName = `userdb-${hexName}`;\n log(`Fetching user database: ${dbName} (${username})`);\n\n // odd construction here the result of a bug in the\n // interaction between pouch, pouch-auth.\n // see: https://github.com/pouchdb-community/pouchdb-authentication/issues/239\n const ret = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n if (guestAccount) {\n updateGuestAccountExpirationDate(ret);\n }\n\n return ret;\n}\n\nexport function scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: PouchDB.Core.DocumentId;\n time: Moment;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n}) {\n const now = moment.utc();\n logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);\n void getCouchUserDB(review.user).put<ScheduledCard>({\n _id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),\n cardId: review.card_id,\n reviewTime: review.time.toISOString(),\n courseId: review.course_id,\n scheduledAt: now.toISOString(),\n scheduledFor: review.scheduledFor,\n schedulingAgentId: review.schedulingAgentId,\n });\n}\n\nexport function filterAllDocsByPrefix<T>(\n db: PouchDB.Database,\n prefix: string,\n opts?: PouchDB.Core.AllDocsOptions\n) {\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const options: PouchDB.Core.AllDocsWithinRangeOptions = {\n startkey: prefix,\n endkey: prefix + '\\ufff0',\n include_docs: true,\n };\n\n if (opts) {\n Object.assign(options, opts);\n }\n return db.allDocs<T>(options);\n}\n\nexport function getStartAndEndKeys(key: string): {\n startkey: string;\n endkey: string;\n} {\n return {\n startkey: key,\n endkey: key + '\\ufff0',\n };\n}\n\n//////////////////////\n// Package exports\n//////////////////////\n\nexport * from '../../core/interfaces/contentSource';\nexport * from './adminDB';\nexport * from './classroomDB';\nexport * from './courseAPI';\nexport * from './courseDB';\nexport * from './CouchDBSyncStrategy';\n","import { DocType, DocTypePrefixes } from '@db/core';\nimport { getCardHistoryID } from '@db/core/util';\nimport { CourseElo, Status } from '@vue-skuilder/common';\nimport moment, { Moment } from 'moment';\nimport { GuestUsername } from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\n\nimport {\n ClassroomRegistrationDoc,\n UserCourseSetting,\n UserDBInterface,\n UsrCrsDataInterface,\n} from '@db/core';\nimport {\n ActivityRecord,\n CourseRegistration,\n CourseRegistrationDoc,\n ScheduledCard,\n UserConfig,\n} from '@db/core/types/user';\nimport { DocumentUpdater } from '@db/study';\nimport { CardHistory, CardRecord } from '../../core/types/types-legacy';\nimport type { SyncStrategy } from './SyncStrategy';\nimport {\n filterAllDocsByPrefix,\n getStartAndEndKeys,\n REVIEW_TIME_FORMAT,\n getLocalUserDB,\n scheduleCardReviewLocal,\n removeScheduledCardReviewLocal,\n} from './userDBHelpers';\nimport { PouchError } from '../couch/types';\nimport UpdateQueue, { Update } from '../couch/updateQueue';\nimport { UsrCrsData } from '../couch/user-course-relDB';\nimport { getCredentialledCourseConfig } from '../couch/index';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\n// logger.log(`Connecting to remote: ${remoteStr}`);\n\ninterface DesignDoc {\n _id: string;\n views: {\n [viewName: string]: {\n map: string; // String representation of the map function\n };\n };\n}\n\n/**\n * Base user database implementation that uses a pluggable sync strategy.\n * Handles local storage operations and delegates sync/remote operations to the strategy.\n */\nexport class BaseUser implements UserDBInterface, DocumentUpdater {\n private static _instance: BaseUser;\n private static _initialized: boolean = false;\n\n public static Dummy(syncStrategy: SyncStrategy): BaseUser {\n return new BaseUser('Me', syncStrategy);\n }\n\n static readonly DOC_IDS = {\n CONFIG: 'CONFIG',\n COURSE_REGISTRATIONS: 'CourseRegistrations',\n CLASSROOM_REGISTRATIONS: 'ClassroomRegistrations',\n };\n\n // private email: string;\n private _username: string;\n private syncStrategy: SyncStrategy;\n\n public getUsername(): string {\n return this._username;\n }\n\n public isLoggedIn(): boolean {\n return !this._username.startsWith(GuestUsername);\n }\n\n public remote(): PouchDB.Database {\n return this.remoteDB;\n }\n\n private localDB!: PouchDB.Database;\n private remoteDB!: PouchDB.Database;\n private writeDB!: PouchDB.Database; // Database to use for write operations (local-first approach)\n\n private updateQueue!: UpdateQueue;\n\n public async createAccount(\n username: string,\n password: string\n ): Promise<{\n status: Status;\n error: string;\n }> {\n if (!this.syncStrategy.canCreateAccount()) {\n throw new Error('Account creation not supported by current sync strategy');\n }\n\n if (!this._username.startsWith(GuestUsername)) {\n throw new Error(\n `Cannot create a new account while logged in:\nCurrently logged-in as ${this._username}.`\n );\n }\n\n const result = await this.syncStrategy.createAccount!(username, password);\n\n // If account creation was successful, update the username and reinitialize\n if (result.status === Status.ok) {\n log(`Account created successfully, updating username to ${username}`);\n this._username = username;\n try {\n localStorage.removeItem('sk-guest-uuid');\n } catch (e) {\n logger.warn('localStorage not available (Node.js environment):', e);\n }\n await this.init();\n }\n\n return {\n status: result.status,\n error: result.error || '',\n };\n }\n public async login(username: string, password: string) {\n if (!this.syncStrategy.canAuthenticate()) {\n throw new Error('Authentication not supported by current sync strategy');\n }\n\n if (!this._username.startsWith(GuestUsername) && this._username != username) {\n if (this._username != username) {\n throw new Error(`Cannot change accounts while logged in.\n Log out of account ${this.getUsername()} before logging in as ${username}.`);\n }\n logger.warn(`User ${this._username} is already logged in, but executing login again.`);\n }\n\n const loginResult = await this.syncStrategy.authenticate!(username, password);\n if (loginResult.ok) {\n log(`Logged in as ${username}`);\n this._username = username;\n try {\n localStorage.removeItem('sk-guest-uuid');\n } catch (e) {\n logger.warn('localStorage not available (Node.js environment):', e);\n }\n await this.init();\n }\n return loginResult;\n }\n\n public async resetUserData(): Promise<{ status: Status; error?: string }> {\n // Only allow reset for local-only sync strategies\n if (this.syncStrategy.canAuthenticate()) {\n return {\n status: Status.error,\n error:\n 'Reset user data is only available for local-only mode. Use logout instead for remote sync.',\n };\n }\n\n try {\n const localDB = getLocalUserDB(this._username);\n\n // Get all documents to identify user data to clear\n const allDocs = await localDB.allDocs({ include_docs: false });\n\n // Identify documents to delete (preserve authentication and user identity)\n const docsToDelete = allDocs.rows\n .filter((row) => {\n const id = row.id;\n // Delete user progress data but preserve core user documents\n return (\n id.startsWith(DocTypePrefixes[DocType.CARDRECORD]) || // Card interaction history\n id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD]) || // Scheduled reviews\n id === BaseUser.DOC_IDS.COURSE_REGISTRATIONS || // Course registrations\n id === BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS || // Classroom registrations\n id === BaseUser.DOC_IDS.CONFIG // User config\n );\n })\n .map((row) => ({ _id: row.id, _rev: row.value.rev, _deleted: true }));\n\n if (docsToDelete.length > 0) {\n await localDB.bulkDocs(docsToDelete);\n }\n\n // Reinitialize to create fresh default documents\n await this.init();\n\n return { status: Status.ok };\n } catch (error) {\n logger.error('Failed to reset user data:', error);\n return {\n status: Status.error,\n error: error instanceof Error ? error.message : 'Unknown error during reset',\n };\n }\n }\n\n public async logout() {\n if (!this.syncStrategy.canAuthenticate()) {\n // For strategies that don't support authentication, just switch to guest\n this._username = await this.syncStrategy.getCurrentUsername();\n await this.init();\n return { ok: true };\n }\n\n const ret = await this.syncStrategy.logout!();\n // return to 'guest' mode\n this._username = await this.syncStrategy.getCurrentUsername();\n await this.init();\n\n return ret;\n }\n\n public async get<T>(id: string): Promise<T & PouchDB.Core.RevisionIdMeta> {\n return this.localDB.get<T>(id);\n }\n\n public update<T extends PouchDB.Core.Document<object>>(id: string, update: Update<T>) {\n return this.updateQueue.update(id, update);\n }\n\n public async getCourseRegistrationsDoc(): Promise<\n CourseRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta\n > {\n logger.debug(`Fetching courseRegistrations for ${this.getUsername()}`);\n\n let ret;\n\n try {\n const regDoc = await this.localDB.get<CourseRegistrationDoc>(\n BaseUser.DOC_IDS.COURSE_REGISTRATIONS\n );\n return regDoc;\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n await this.localDB.put<CourseRegistrationDoc>({\n _id: BaseUser.DOC_IDS.COURSE_REGISTRATIONS,\n courses: [],\n studyWeight: {},\n });\n ret = await this.getCourseRegistrationsDoc();\n } else {\n throw new Error(\n `Unexpected error ${JSON.stringify(e)} in getOrCreateCourseRegistrationDoc...`\n );\n }\n }\n\n return ret;\n }\n\n public async getActiveCourses() {\n const reg = await this.getCourseRegistrationsDoc();\n return reg.courses.filter((c) => {\n return c.status === undefined || c.status === 'active';\n });\n }\n\n /**\n * Returns a promise of the card IDs that the user has\n * a scheduled review for.\n *\n */\n public async getActiveCards() {\n const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);\n\n const reviews = await this.remoteDB.allDocs<ScheduledCard>({\n startkey: keys.startkey,\n endkey: keys.endkey,\n include_docs: true,\n });\n\n return reviews.rows.map((r) => {\n return {\n courseID: r.doc!.courseId,\n cardID: r.doc!.cardId,\n };\n });\n }\n\n public async getActivityRecords(): Promise<ActivityRecord[]> {\n try {\n const hist = await this.getHistory();\n\n const allRecords: ActivityRecord[] = [];\n if (!Array.isArray(hist)) {\n logger.error('getHistory did not return an array:', hist);\n return allRecords;\n }\n\n // Sample the first few records to understand structure\n let sampleCount = 0;\n\n for (let i = 0; i < hist.length; i++) {\n try {\n if (hist[i] && Array.isArray(hist[i]!.records)) {\n hist[i]!.records.forEach((record: CardRecord) => {\n try {\n // Skip this record if timeStamp is missing\n if (!record.timeStamp) {\n return;\n }\n\n let timeStamp;\n\n // Handle different timestamp formats\n if (typeof record.timeStamp === 'object') {\n // It's likely a Moment object\n if (typeof record.timeStamp.toDate === 'function') {\n // It's definitely a Moment object\n timeStamp = record.timeStamp.toISOString();\n } else if (record.timeStamp instanceof Date) {\n // It's a Date object\n timeStamp = record.timeStamp.toISOString();\n } else {\n // Log a sample of unknown object types, but don't flood logger\n if (sampleCount < 3) {\n logger.warn('Unknown timestamp object type:', record.timeStamp);\n sampleCount++;\n }\n return;\n }\n } else if (typeof record.timeStamp === 'string') {\n // It's already a string, but make sure it's a valid date\n const date = new Date(record.timeStamp);\n if (isNaN(date.getTime())) {\n return; // Invalid date string\n }\n timeStamp = record.timeStamp;\n } else if (typeof record.timeStamp === 'number') {\n // Assume it's a Unix timestamp (milliseconds since epoch)\n timeStamp = new Date(record.timeStamp).toISOString();\n } else {\n // Unknown type, skip\n return;\n }\n\n allRecords.push({\n timeStamp,\n courseID: record.courseID || 'unknown',\n cardID: record.cardID || 'unknown',\n timeSpent: record.timeSpent || 0,\n type: 'card_view',\n });\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (err) {\n // Silently skip problematic records to avoid flooding logs\n }\n });\n }\n } catch (err) {\n logger.error('Error processing history item:', err);\n }\n }\n\n logger.debug(`Found ${allRecords.length} activity records`);\n return allRecords;\n } catch (err) {\n logger.error('Error in getActivityRecords:', err);\n return [];\n }\n }\n\n private async getReviewstoDate(targetDate: Moment, course_id?: string) {\n const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);\n\n const reviews = await this.remoteDB.allDocs<ScheduledCard>({\n startkey: keys.startkey,\n endkey: keys.endkey,\n include_docs: true,\n });\n\n log(\n `Fetching ${this._username}'s scheduled reviews${\n course_id ? ` for course ${course_id}` : ''\n }.`\n );\n return reviews.rows\n .filter((r) => {\n if (r.id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD])) {\n const date = moment.utc(\n r.id.substr(DocTypePrefixes[DocType.SCHEDULED_CARD].length),\n REVIEW_TIME_FORMAT\n );\n if (targetDate.isAfter(date)) {\n if (course_id === undefined || r.doc!.courseId === course_id) {\n return true;\n }\n }\n }\n })\n .map((r) => r.doc!);\n }\n\n public async getReviewsForcast(daysCount: number) {\n const time = moment.utc().add(daysCount, 'days');\n return this.getReviewstoDate(time);\n }\n\n public async getPendingReviews(course_id?: string) {\n const now = moment.utc();\n return this.getReviewstoDate(now, course_id);\n }\n\n public async getScheduledReviewCount(course_id: string): Promise<number> {\n return (await this.getPendingReviews(course_id)).length;\n }\n\n public async getRegisteredCourses() {\n const regDoc = await this.getCourseRegistrationsDoc();\n return regDoc.courses.filter((c) => {\n return !c.status || c.status === 'active' || c.status === 'maintenance-mode';\n });\n }\n\n public async getCourseRegDoc(courseID: string) {\n const regDocs = await this.getCourseRegistrationsDoc();\n const ret = regDocs.courses.find((c) => c.courseID === courseID);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Course registration not found for course ID: ${courseID}`);\n }\n }\n\n public async registerForCourse(course_id: string, previewMode: boolean = false) {\n return this.getCourseRegistrationsDoc()\n .then((doc: CourseRegistrationDoc) => {\n const status = previewMode ? 'preview' : 'active';\n logger.debug(`Registering for ${course_id} with status: ${status}`);\n\n const regItem: CourseRegistration = {\n status: status,\n courseID: course_id,\n user: true,\n admin: false,\n moderator: false,\n elo: {\n global: {\n score: 1000,\n count: 0,\n },\n tags: {},\n misc: {},\n },\n };\n\n if (\n doc.courses.filter((course) => {\n return course.courseID === regItem.courseID;\n }).length === 0\n ) {\n log(`It's a new course registration!`);\n doc.courses.push(regItem);\n doc.studyWeight[course_id] = 1;\n } else {\n doc.courses.forEach((c) => {\n log(`Found the previously registered course!`);\n if (c.courseID === course_id) {\n c.status = status;\n }\n });\n }\n\n return this.localDB.put<CourseRegistrationDoc>(doc);\n })\n .catch((e) => {\n log(`Registration failed because of: ${JSON.stringify(e)}`);\n throw e;\n });\n }\n public async dropCourse(course_id: string, dropStatus: CourseRegistration['status'] = 'dropped') {\n return this.getCourseRegistrationsDoc().then((doc) => {\n let index: number = -1;\n for (let i = 0; i < doc.courses.length; i++) {\n if (doc.courses[i].courseID === course_id) {\n index = i;\n }\n }\n\n if (index !== -1) {\n // remove from the relative-weighting of course study\n delete doc.studyWeight[course_id];\n // set drop status\n doc.courses[index].status = dropStatus;\n } else {\n throw new Error(\n `User ${this.getUsername()} is not currently registered for course ${course_id}`\n );\n }\n\n return this.localDB.put<CourseRegistrationDoc>(doc);\n });\n }\n\n public async getCourseInterface(courseId: string): Promise<UsrCrsDataInterface> {\n return new UsrCrsData(this, courseId);\n }\n\n public async getUserEditableCourses() {\n let courseIDs: string[] = [];\n\n const registeredCourses = await this.getCourseRegistrationsDoc();\n\n courseIDs = courseIDs.concat(\n registeredCourses.courses.map((course) => {\n return course.courseID;\n })\n );\n\n const cfgs = await Promise.all(\n courseIDs.map(async (id) => {\n return await getCredentialledCourseConfig(id);\n })\n );\n return cfgs;\n }\n\n public async getConfig(): Promise<UserConfig & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n const defaultConfig: PouchDB.Core.Document<UserConfig> = {\n _id: BaseUser.DOC_IDS.CONFIG,\n darkMode: false,\n likesConfetti: false,\n sessionTimeLimit: 5,\n };\n\n try {\n const cfg = await this.localDB.get<UserConfig>(BaseUser.DOC_IDS.CONFIG);\n logger.debug('Raw config from DB:', cfg);\n\n return cfg;\n } catch (e) {\n const err = e as PouchError;\n if (err.name && err.name === 'not_found') {\n await this.localDB.put<UserConfig>(defaultConfig);\n return this.getConfig();\n } else {\n logger.error(`Error setting user default config:`, e);\n throw new Error(`Error returning the user's configuration: ${JSON.stringify(e)}`);\n }\n }\n }\n\n public async setConfig(items: Partial<UserConfig>) {\n logger.debug(`Setting Config items ${JSON.stringify(items)}`);\n\n const c = await this.getConfig();\n const put = await this.localDB.put<UserConfig>({\n ...c,\n ...items,\n });\n\n if (put.ok) {\n logger.debug(`Config items set: ${JSON.stringify(items)}`);\n } else {\n logger.error(`Error setting config items: ${JSON.stringify(put)}`);\n }\n }\n\n /**\n *\n * This function should be called *only* by the pouchdb datalayer provider\n * auth store.\n *\n *\n * Anyone else seeking the current user should use the auth store's\n * exported `getCurrentUser` method.\n *\n */\n public static async instance(syncStrategy: SyncStrategy, username?: string): Promise<BaseUser> {\n if (username) {\n BaseUser._instance = new BaseUser(username, syncStrategy);\n await BaseUser._instance.init();\n return BaseUser._instance;\n } else if (BaseUser._instance && BaseUser._initialized) {\n // log(`USER.instance() returning user ${BaseUser._instance._username}`);\n return BaseUser._instance;\n } else if (BaseUser._instance) {\n return new Promise((resolve) => {\n (function waitForUser() {\n if (BaseUser._initialized) {\n return resolve(BaseUser._instance);\n } else {\n setTimeout(waitForUser, 50);\n }\n })();\n });\n } else {\n const guestUsername = await syncStrategy.getCurrentUsername();\n BaseUser._instance = new BaseUser(guestUsername, syncStrategy);\n await BaseUser._instance.init();\n return BaseUser._instance;\n }\n }\n\n private constructor(username: string, syncStrategy: SyncStrategy) {\n BaseUser._initialized = false;\n this._username = username;\n this.syncStrategy = syncStrategy;\n this.setDBandQ();\n }\n\n private setDBandQ() {\n this.localDB = getLocalUserDB(this._username);\n this.remoteDB = this.syncStrategy.setupRemoteDB(this._username);\n // writeDB follows local-first pattern: static mode writes to local, CouchDB writes to remote/local as appropriate\n this.writeDB = this.syncStrategy.getWriteDB\n ? this.syncStrategy.getWriteDB(this._username)\n : this.localDB;\n this.updateQueue = new UpdateQueue(this.localDB, this.writeDB);\n }\n\n private async init() {\n BaseUser._initialized = false;\n\n // Skip admin user\n if (this._username === 'admin') {\n BaseUser._initialized = true;\n return;\n }\n\n this.setDBandQ();\n\n this.syncStrategy.startSync(this.localDB, this.remoteDB);\n this.applyDesignDocs().catch((error) => {\n log(`Error in applyDesignDocs background task: ${error}`);\n if (error && typeof error === 'object') {\n log(`Full error details in applyDesignDocs: ${JSON.stringify(error)}`);\n }\n });\n this.deduplicateReviews().catch((error) => {\n log(`Error in deduplicateReviews background task: ${error}`);\n if (error && typeof error === 'object') {\n log(`Full error details in background task: ${JSON.stringify(error)}`);\n }\n });\n BaseUser._initialized = true;\n }\n\n private static designDocs: DesignDoc[] = [\n {\n _id: '_design/reviewCards',\n views: {\n reviewCards: {\n map: `function (doc) {\n if (doc._id && doc._id.indexOf('card_review') === 0 && doc.courseId && doc.cardId) {\n emit(doc._id, doc.courseId + '-' + doc.cardId);\n }\n }`,\n },\n },\n },\n ];\n\n private async applyDesignDocs() {\n log(`Starting applyDesignDocs for user: ${this._username}`);\n log(`Remote DB name: ${this.remoteDB.name || 'unknown'}`);\n\n if (this._username === 'admin') {\n // Skip admin user\n log('Skipping design docs for admin user');\n return;\n }\n\n log(`Applying ${BaseUser.designDocs.length} design docs`);\n for (const doc of BaseUser.designDocs) {\n log(`Applying design doc: ${doc._id}`);\n try {\n // Try to get existing doc\n try {\n const existingDoc = await this.remoteDB.get(doc._id);\n // Update existing doc\n await this.remoteDB.put({\n ...doc,\n _rev: existingDoc._rev,\n });\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'not_found') {\n // Create new doc\n await this.remoteDB.put(doc);\n } else {\n throw e; // Re-throw unexpected errors\n }\n }\n } catch (error: unknown) {\n if ((error as any).name && (error as any).name === 'conflict') {\n logger.warn(`Design doc ${doc._id} update conflict - will retry`);\n // Wait a bit and try again\n await new Promise((resolve) => setTimeout(resolve, 1000));\n await this.applyDesignDoc(doc); // Recursive retry\n } else {\n logger.error(`Failed to apply design doc ${doc._id}:`, error);\n throw error;\n }\n }\n }\n }\n\n // Helper method for single doc update with retry\n private async applyDesignDoc(doc: DesignDoc, retries = 3): Promise<void> {\n try {\n const existingDoc = await this.remoteDB.get(doc._id);\n await this.remoteDB.put({\n ...doc,\n _rev: existingDoc._rev,\n });\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'conflict' && retries > 0) {\n await new Promise((resolve) => setTimeout(resolve, 1000));\n return this.applyDesignDoc(doc, retries - 1);\n }\n throw e;\n }\n }\n\n /**\n * Logs a record of the user's interaction with the card and returns the card's\n * up-to-date history\n *\n * // [ ] #db-refactor extract to a smaller scope - eg, UserStudySession\n *\n * @param record the recent recorded interaction between user and card\n * @returns The updated state of the card's CardHistory data\n */\n\n public async putCardRecord<T extends CardRecord>(\n record: T\n ): Promise<CardHistory<CardRecord> & PouchDB.Core.RevisionIdMeta> {\n const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);\n // stringify the current record to make it writable to couchdb\n record.timeStamp = moment.utc(record.timeStamp).toString() as unknown as Moment;\n\n try {\n const cardHistory = await this.update<CardHistory<T>>(\n cardHistoryID,\n function (h: CardHistory<T>) {\n h.records.push(record);\n h.bestInterval = h.bestInterval || 0;\n h.lapses = h.lapses || 0;\n h.streak = h.streak || 0;\n return h;\n }\n );\n\n // Convert timestamps to moment objects\n cardHistory.records = cardHistory.records.map<T>((record) => {\n const ret: T = {\n ...(record as object),\n } as T;\n ret.timeStamp = moment.utc(record.timeStamp);\n return ret;\n });\n return cardHistory;\n } catch (e) {\n const reason = e as PouchError;\n if (reason.status === 404) {\n try {\n const initCardHistory: CardHistory<T> = {\n _id: cardHistoryID,\n cardID: record.cardID,\n courseID: record.courseID,\n records: [record],\n lapses: 0,\n streak: 0,\n bestInterval: 0,\n };\n const putResult = await this.writeDB.put<CardHistory<T>>(initCardHistory);\n return { ...initCardHistory, _rev: putResult.rev };\n } catch (creationError) {\n throw new Error(\n `Failed to create CardHistory for ${cardHistoryID}. Reason: ${creationError}`\n );\n }\n } else {\n throw new Error(`putCardRecord failed because of:\n name:${reason.name}\n error: ${reason.error}\n message: ${reason.message}`);\n }\n }\n }\n\n private async deduplicateReviews() {\n try {\n log('Starting deduplication of scheduled reviews...');\n log(`Remote DB name: ${this.remoteDB.name || 'unknown'}`);\n log(`Write DB name: ${this.writeDB.name || 'unknown'}`);\n /**\n * Maps the qualified-id of a scheduled review card to\n * the docId of the same scheduled review.\n *\n * EG: {\n * courseId-cardId: 'card_review_2021-06--17:12:165'\n * }\n */\n const reviewsMap: { [index: string]: string } = {};\n const duplicateDocIds: string[] = [];\n\n log(\n `Attempting to query remoteDB for reviewCards/reviewCards. Database: ${this.remoteDB.name || 'unknown'}`\n );\n const scheduledReviews = await this.remoteDB.query<{\n id: string;\n value: string;\n }>('reviewCards/reviewCards');\n\n log(`Found ${scheduledReviews.rows.length} scheduled reviews to process`);\n\n // First pass: identify duplicates\n scheduledReviews.rows.forEach((r) => {\n const qualifiedCardId = r.value; // courseId-cardId\n const docId = r.key; // card_review_2021-06--17:12:165\n\n if (reviewsMap[qualifiedCardId]) {\n // this card is scheduled more than once! mark the earlier one for deletion\n log(`Found duplicate scheduled review for card: ${qualifiedCardId}`);\n log(\n `Marking earlier review ${reviewsMap[qualifiedCardId]} for deletion, keeping ${docId}`\n );\n duplicateDocIds.push(reviewsMap[qualifiedCardId]);\n // replace with the later-dated scheduled review\n reviewsMap[qualifiedCardId] = docId;\n } else {\n // note that this card is scheduled for review\n reviewsMap[qualifiedCardId] = docId;\n }\n });\n\n // Second pass: remove duplicates\n if (duplicateDocIds.length > 0) {\n log(`Removing ${duplicateDocIds.length} duplicate reviews...`);\n const deletePromises = duplicateDocIds.map(async (docId) => {\n try {\n const doc = await this.remoteDB.get(docId);\n await this.writeDB.remove(doc);\n log(`Successfully removed duplicate review: ${docId}`);\n } catch (error) {\n log(`Failed to remove duplicate review ${docId}: ${error}`);\n }\n });\n\n await Promise.all(deletePromises);\n log(`Deduplication complete. Processed ${duplicateDocIds.length} duplicates`);\n } else {\n log('No duplicate reviews found');\n }\n } catch (error) {\n log(`Error during review deduplication: ${error}`);\n if (error && typeof error === 'object' && 'status' in error && error.status === 404) {\n log(\n `Database not found (404) during review deduplication. Database: ${this.remoteDB.name || 'unknown'}`\n );\n log(\n `This might indicate the user database doesn't exist or the reviewCards view isn't available`\n );\n }\n // Log full error details for debugging\n if (error && typeof error === 'object') {\n log(`Full error details: ${JSON.stringify(error)}`);\n }\n }\n }\n\n /**\n * Returns a promise of the card IDs that the user has\n * encountered in the past.\n *\n * @param course_id optional specification of individual course\n */\n async getSeenCards(course_id?: string) {\n let prefix = DocTypePrefixes[DocType.CARDRECORD];\n if (course_id) {\n prefix += course_id;\n }\n const docs = await filterAllDocsByPrefix(this.localDB, prefix, {\n include_docs: false,\n });\n // const docs = await this.localDB.allDocs({});\n const ret: PouchDB.Core.DocumentId[] = [];\n docs.rows.forEach((row) => {\n if (row.id.startsWith(DocTypePrefixes[DocType.CARDRECORD])) {\n ret.push(row.id.substr(DocTypePrefixes[DocType.CARDRECORD].length));\n }\n });\n return ret;\n }\n\n /**\n *\n * @returns A promise of the cards that the user has seen in the past.\n */\n async getHistory() {\n const cards = await filterAllDocsByPrefix<CardHistory<CardRecord>>(\n this.remoteDB,\n DocTypePrefixes[DocType.CARDRECORD],\n {\n include_docs: true,\n attachments: false,\n }\n );\n return cards.rows.map((r) => r.doc);\n }\n\n async updateCourseSettings(course_id: string, settings: UserCourseSetting[]) {\n void this.getCourseRegistrationsDoc().then((doc) => {\n const crs = doc.courses.find((c) => c.courseID === course_id);\n if (crs) {\n if (crs.settings === null || crs.settings === undefined) {\n crs.settings = {};\n }\n settings.forEach((setting) => {\n crs!.settings![setting.key] = setting.value;\n });\n }\n\n return this.localDB.put(doc);\n });\n }\n async getCourseSettings(course_id: string) {\n const regDoc = await this.getCourseRegistrationsDoc();\n const crsDoc = regDoc.courses.find((c) => c.courseID === course_id);\n\n if (crsDoc) {\n return crsDoc.settings;\n } else {\n throw new Error(`getCourseSettings Failed:\n User is not registered for course ${course_id}`);\n }\n }\n\n private async getOrCreateClassroomRegistrationsDoc(): Promise<\n ClassroomRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta\n > {\n let ret;\n\n try {\n ret = await this.remoteDB.get<ClassroomRegistrationDoc>(\n BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS\n );\n } catch (e) {\n const err = e as PouchError;\n\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await this.writeDB.put<ClassroomRegistrationDoc>({\n _id: BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS,\n registrations: [],\n });\n ret = await this.getOrCreateClassroomRegistrationsDoc();\n } else {\n // Properly serialize error information\n const errorDetails = {\n name: err.name,\n status: err.status,\n message: err.message,\n reason: err.reason,\n error: err.error,\n };\n\n logger.error(\n 'Database error in getOrCreateClassroomRegistrationsDoc (private method):',\n errorDetails\n );\n\n throw new Error(\n `Database error accessing classroom registrations: ${err.message || err.name || 'Unknown error'} (status: ${err.status})`\n );\n }\n }\n\n logger.debug(`Returning classroom registrations doc: ${JSON.stringify(ret)}`);\n return ret;\n }\n\n /**\n * Retrieves the list of active classroom IDs where the user is registered as a student.\n *\n * @returns Promise<string[]> - Array of classroom IDs, or empty array if classroom\n * registration document is unavailable due to database errors\n *\n * @description This method gracefully handles database connectivity issues by returning\n * an empty array when the classroom registrations document cannot be accessed.\n * This ensures that users can still access other application features even\n * when classroom functionality is temporarily unavailable.\n */\n public async getActiveClasses(): Promise<string[]> {\n try {\n return (await this.getOrCreateClassroomRegistrationsDoc()).registrations\n .filter((c) => c.registeredAs === 'student')\n .map((c) => c.classID);\n } catch (error) {\n logger.warn(\n 'Failed to load classroom registrations, continuing without classroom data:',\n error\n );\n // Return empty array so user can still access other features\n return [];\n }\n }\n\n public async scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: PouchDB.Core.DocumentId;\n time: Moment;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n }) {\n return scheduleCardReviewLocal(this.writeDB, review);\n }\n public async removeScheduledCardReview(reviewId: string): Promise<void> {\n return removeScheduledCardReviewLocal(this.writeDB, reviewId);\n }\n\n public async registerForClassroom(\n _classId: string,\n _registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n ): Promise<PouchDB.Core.Response> {\n return registerUserForClassroom(this._username, _classId, _registerAs);\n }\n\n public async dropFromClassroom(classId: string): Promise<PouchDB.Core.Response> {\n return dropUserFromClassroom(this._username, classId);\n }\n public async getUserClassrooms(): Promise<ClassroomRegistrationDoc> {\n return getUserClassrooms(this._username);\n }\n\n public async updateUserElo(courseId: string, elo: CourseElo): Promise<PouchDB.Core.Response> {\n return updateUserElo(this._username, courseId, elo);\n }\n}\n\nexport function accomodateGuest(): {\n username: string;\n firstVisit: boolean;\n} {\n logger.log('[funnel] accomodateGuest() called');\n\n // Check if localStorage is available (browser environment)\n if (typeof localStorage === 'undefined') {\n logger.log('[funnel] localStorage not available (Node.js environment), returning default guest');\n return {\n username: GuestUsername + 'nodejs-test',\n firstVisit: true,\n };\n }\n\n const dbUUID = 'sk-guest-uuid';\n let firstVisit: boolean;\n\n const existingUUID = localStorage.getItem(dbUUID);\n logger.log('[funnel] Checking localStorage for key:', dbUUID);\n logger.log('[funnel] Existing UUID value:', existingUUID);\n logger.log('[funnel] existingUUID !== null:', existingUUID !== null);\n\n if (existingUUID !== null) {\n firstVisit = false;\n logger.log(`[funnel] Returning guest ${existingUUID} \"logging in\".`);\n } else {\n firstVisit = true;\n logger.log('[funnel] No existing UUID, generating new one...');\n const uuid = generateUUID();\n logger.log('[funnel] Generated UUID:', uuid);\n logger.log('[funnel] UUID length:', uuid.length);\n\n try {\n localStorage.setItem(dbUUID, uuid);\n logger.log('[funnel] Successfully stored UUID in localStorage');\n const verification = localStorage.getItem(dbUUID);\n logger.log('[funnel] Verification read from localStorage:', verification);\n } catch (e) {\n logger.error('[funnel] ERROR storing UUID:', e);\n }\n\n logger.log(`[funnel] Accommodating a new guest with account: ${uuid}`);\n }\n\n const finalUUID = localStorage.getItem(dbUUID);\n const finalUsername = GuestUsername + finalUUID;\n logger.log('[funnel] Final UUID from localStorage:', finalUUID);\n logger.log('[funnel] GuestUsername constant:', GuestUsername);\n logger.log('[funnel] Final username to return:', finalUsername);\n\n return {\n username: finalUsername,\n firstVisit: firstVisit,\n };\n\n // Use cryptographically secure UUID generation\n function generateUUID() {\n logger.log('[funnel] Inside generateUUID()');\n\n // Use crypto.randomUUID() if available (Node 14.17+ / modern browsers)\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n const uuid = crypto.randomUUID();\n logger.log('[funnel] Generated UUID using crypto.randomUUID():', uuid);\n return uuid;\n }\n\n // Fallback for older environments: use crypto.getRandomValues()\n if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n\n // Set version (4) and variant bits according to RFC 4122\n bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4\n bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10\n\n const uuid = [\n Array.from(bytes.slice(0, 4)).map(b => b.toString(16).padStart(2, '0')).join(''),\n Array.from(bytes.slice(4, 6)).map(b => b.toString(16).padStart(2, '0')).join(''),\n Array.from(bytes.slice(6, 8)).map(b => b.toString(16).padStart(2, '0')).join(''),\n Array.from(bytes.slice(8, 10)).map(b => b.toString(16).padStart(2, '0')).join(''),\n Array.from(bytes.slice(10, 16)).map(b => b.toString(16).padStart(2, '0')).join(''),\n ].join('-');\n\n logger.log('[funnel] Generated UUID using crypto.getRandomValues():', uuid);\n return uuid;\n }\n\n // Last resort fallback (should never happen in modern environments)\n logger.warn('[funnel] crypto API not available, using timestamp-based UUID (NOT SECURE)');\n let d = new Date().getTime();\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n d += performance.now();\n }\n const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (d + Math.random() * 16) % 16 | 0;\n d = Math.floor(d / 16);\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n logger.log('[funnel] Generated UUID (fallback):', uuid);\n return uuid;\n }\n}\n\nconst userCoursesDoc = 'CourseRegistrations';\nconst userClassroomsDoc = 'ClassroomRegistrations';\n\nasync function getOrCreateClassroomRegistrationsDoc(\n user: string\n): Promise<ClassroomRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n let ret;\n\n try {\n ret = await getLocalUserDB(user).get<ClassroomRegistrationDoc>(userClassroomsDoc);\n } catch (e) {\n const err = e as PouchError;\n\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await getLocalUserDB(user).put<ClassroomRegistrationDoc>({\n _id: userClassroomsDoc,\n registrations: [],\n });\n ret = await getOrCreateClassroomRegistrationsDoc(user);\n } else {\n // Properly serialize error information\n const errorDetails = {\n name: err.name,\n status: err.status,\n message: err.message,\n reason: err.reason,\n error: err.error,\n };\n\n logger.error(\n 'Database error in getOrCreateClassroomRegistrationsDoc (standalone function):',\n errorDetails\n );\n\n throw new Error(\n `Database error accessing classroom registrations: ${err.message || err.name || 'Unknown error'} (status: ${err.status})`\n );\n }\n }\n\n return ret;\n}\n\nasync function getOrCreateCourseRegistrationsDoc(\n user: string\n): Promise<CourseRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n let ret;\n\n try {\n ret = await getLocalUserDB(user).get<CourseRegistrationDoc>(userCoursesDoc);\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await getLocalUserDB(user).put<CourseRegistrationDoc>({\n _id: userCoursesDoc,\n courses: [],\n studyWeight: {},\n });\n ret = await getOrCreateCourseRegistrationsDoc(user);\n } else {\n throw new Error(\n `Unexpected error ${JSON.stringify(e)} in getOrCreateCourseRegistrationDoc...`\n );\n }\n }\n\n return ret;\n}\n\nexport async function updateUserElo(user: string, course_id: string, elo: CourseElo) {\n const regDoc = await getOrCreateCourseRegistrationsDoc(user);\n const course = regDoc.courses.find((c) => c.courseID === course_id)!;\n course.elo = elo;\n return getLocalUserDB(user).put(regDoc);\n}\n\nexport async function registerUserForClassroom(\n user: string,\n classID: string,\n registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n) {\n log(`Registering user: ${user} in course: ${classID}`);\n return getOrCreateClassroomRegistrationsDoc(user).then((doc) => {\n const regItem = {\n classID: classID,\n registeredAs: registerAs,\n };\n\n if (\n doc.registrations.filter((reg) => {\n return reg.classID === regItem.classID && reg.registeredAs === regItem.registeredAs;\n }).length === 0\n ) {\n doc.registrations.push(regItem);\n } else {\n log(`User ${user} is already registered for class ${classID}`);\n }\n\n return getLocalUserDB(user).put(doc);\n });\n}\n\nexport async function dropUserFromClassroom(user: string, classID: string) {\n return getOrCreateClassroomRegistrationsDoc(user).then((doc) => {\n let index: number = -1;\n\n for (let i = 0; i < doc.registrations.length; i++) {\n if (doc.registrations[i].classID === classID) {\n index = i;\n }\n }\n\n if (index !== -1) {\n doc.registrations.splice(index, 1);\n }\n return getLocalUserDB(user).put(doc);\n });\n}\n\nexport async function getUserClassrooms(user: string) {\n return getOrCreateClassroomRegistrationsDoc(user);\n}\n","// packages/db/src/impl/common/index.ts\n\nexport type { SyncStrategy } from './SyncStrategy';\nexport { BaseSyncStrategy } from './SyncStrategy';\nexport type {\n AccountCreationResult,\n AuthenticationResult,\n UserSession,\n SyncConfig,\n SyncStatus,\n} from './types';\nexport { BaseUser, accomodateGuest } from './BaseUserDB';\nexport {\n REVIEW_TIME_FORMAT,\n hexEncode,\n filterAllDocsByPrefix,\n getStartAndEndKeys,\n updateGuestAccountExpirationDate,\n getLocalUserDB,\n scheduleCardReviewLocal,\n removeScheduledCardReviewLocal,\n} from './userDBHelpers';\n","// db/src/factory.ts\n\nimport { DataLayerProvider } from './core/interfaces';\nimport { BaseUser } from './impl/common';\nimport { logger } from './util/logger';\nimport { StaticCourseManifest } from './util/packer/types';\n\nconst NOT_SET = 'NOT_SET' as const;\n\ninterface DBEnv {\n COUCHDB_SERVER_URL: string; // URL of CouchDB server\n COUCHDB_SERVER_PROTOCOL: string; // Protocol of CouchDB server (http or https)\n COUCHDB_USERNAME?: string;\n COUCHDB_PASSWORD?: string;\n LOCAL_STORAGE_PREFIX: string; // Prefix for IndexedDB storage names\n}\n\nexport const ENV: DBEnv = {\n COUCHDB_SERVER_PROTOCOL: NOT_SET,\n COUCHDB_SERVER_URL: NOT_SET,\n LOCAL_STORAGE_PREFIX: '',\n};\n\nexport { NOT_SET };\n\n// Configuration type for data layer initialization\nexport interface DataLayerConfig {\n type: 'couch' | 'static';\n options: {\n staticContentPath?: string; // Path to static content JSON files\n localStoragePrefix?: string; // Prefix for IndexedDB storage names\n manifests?: Record<string, StaticCourseManifest>; // Course manifests for static mode\n COUCHDB_SERVER_URL?: string;\n COUCHDB_SERVER_PROTOCOL?: string;\n COUCHDB_USERNAME?: string;\n COUCHDB_PASSWORD?: string;\n\n COURSE_IDS?: string[];\n };\n}\n\n// Singleton instance\nlet dataLayerInstance: DataLayerProvider | null = null;\n\n/**\n * Initialize the data layer with the specified configuration\n */\nexport async function initializeDataLayer(config: DataLayerConfig): Promise<DataLayerProvider> {\n if (dataLayerInstance) {\n logger.warn('Data layer already initialized. Returning existing instance.');\n return dataLayerInstance;\n }\n if (config.options.localStoragePrefix) {\n ENV.LOCAL_STORAGE_PREFIX = config.options.localStoragePrefix;\n }\n\n if (config.type === 'couch') {\n if (!config.options.COUCHDB_SERVER_URL || !config.options.COUCHDB_SERVER_PROTOCOL) {\n throw new Error('Missing CouchDB server URL or protocol');\n }\n ENV.COUCHDB_SERVER_PROTOCOL = config.options.COUCHDB_SERVER_PROTOCOL;\n ENV.COUCHDB_SERVER_URL = config.options.COUCHDB_SERVER_URL;\n ENV.COUCHDB_USERNAME = config.options.COUCHDB_USERNAME;\n ENV.COUCHDB_PASSWORD = config.options.COUCHDB_PASSWORD;\n\n if (\n config.options.COUCHDB_PASSWORD &&\n config.options.COUCHDB_USERNAME &&\n typeof window !== 'undefined'\n ) {\n // Dynamic import to avoid loading both implementations when only one is needed\n const { CouchDBSyncStrategy } = await import('./impl/couch/CouchDBSyncStrategy');\n\n // Create a sync strategy instance and authenticate\n const syncStrategy = new CouchDBSyncStrategy();\n\n const user = await BaseUser.instance(syncStrategy, config.options.COUCHDB_USERNAME);\n const authResult = await user.login(\n config.options.COUCHDB_USERNAME,\n config.options.COUCHDB_PASSWORD\n );\n\n if (authResult.ok) {\n logger.info(`Successfully authenticated as ${config.options.COUCHDB_USERNAME}`);\n } else {\n logger.warn(`Authentication failed: ${authResult.error}`);\n }\n }\n\n // Dynamic import to avoid loading both implementations when only one is needed\n const { CouchDataLayerProvider } = await import('./impl/couch/PouchDataLayerProvider');\n dataLayerInstance = new CouchDataLayerProvider(config.options.COURSE_IDS);\n } else if (config.type === 'static') {\n const { StaticDataLayerProvider } = await import('./impl/static/StaticDataLayerProvider');\n dataLayerInstance = new StaticDataLayerProvider(config.options);\n } else {\n throw new Error(`Unknown data layer type: ${config.type}`);\n }\n\n await dataLayerInstance.initialize();\n return dataLayerInstance;\n}\n\n/**\n * Get the initialized data layer instance\n * @throws Error if not initialized\n */\nexport function getDataLayer(): DataLayerProvider {\n if (!dataLayerInstance) {\n throw new Error('Data layer not initialized. Call initializeDataLayer first.');\n }\n return dataLayerInstance;\n}\n\n/**\n * Reset the data layer (primarily for testing)\n */\nexport async function _resetDataLayer(): Promise<void> {\n if (dataLayerInstance) {\n await dataLayerInstance.teardown();\n }\n dataLayerInstance = null;\n}\n","import { getDataLayer } from '@db/factory';\nimport { UserDBInterface } from '..';\nimport { StudentClassroomDB } from '../../impl/couch/classroomDB';\nimport { ScheduledCard } from '@db/core/types/user';\n\nexport type StudySessionFailedItem = StudySessionFailedNewItem | StudySessionFailedReviewItem;\n\nexport interface StudySessionFailedNewItem extends StudySessionItem {\n status: 'failed-new';\n}\nexport interface StudySessionFailedReviewItem extends StudySessionReviewItem {\n status: 'failed-review';\n}\n\nexport interface StudySessionNewItem extends StudySessionItem {\n status: 'new';\n}\n\nexport interface StudySessionReviewItem extends StudySessionItem {\n reviewID: string;\n status: 'review' | 'failed-review';\n}\nexport function isReview(item: StudySessionItem): item is StudySessionReviewItem {\n const ret = item.status === 'review' || item.status === 'failed-review' || 'reviewID' in item;\n\n // console.log(`itemIsReview: ${ret}\n // \\t${JSON.stringify(item)}`);\n\n return ret;\n}\n\nexport interface StudySessionItem {\n status: 'new' | 'review' | 'failed-new' | 'failed-review';\n contentSourceType: 'course' | 'classroom';\n contentSourceID: string;\n // qualifiedID: `${string}-${string}` | `${string}-${string}-${number}`;\n cardID: string;\n courseID: string;\n elo?: number;\n // reviewID?: string;\n}\n\nexport interface ContentSourceID {\n type: 'course' | 'classroom';\n id: string;\n}\n\n// #region docs_StudyContentSource\nexport interface StudyContentSource {\n getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;\n getNewCards(n?: number): Promise<StudySessionNewItem[]>;\n}\n// #endregion docs_StudyContentSource\n\nexport async function getStudySource(\n source: ContentSourceID,\n user: UserDBInterface\n): Promise<StudyContentSource> {\n if (source.type === 'classroom') {\n return await StudentClassroomDB.factory(source.id, user);\n } else {\n // if (source.type === 'course') - removed so tsc is certain something returns\n // return new CourseDB(source.id, async () => {\n // return user;\n // });\n\n return getDataLayer().getCourseDB(source.id) as unknown as StudyContentSource;\n }\n}\n","import { CourseConfig, CourseElo, DataShape, SkuilderCourseData } from '@vue-skuilder/common';\nimport { StudySessionNewItem, StudySessionItem } from './contentSource';\nimport { TagStub, Tag, QualifiedCardID } from '../types/types-legacy';\nimport { DataLayerResult } from '../types/db';\nimport { NavigationStrategyManager } from './navigationStrategyManager';\n\n/**\n * Course content and management\n */\nexport interface CoursesDBInterface {\n /**\n * Get course config\n */\n getCourseConfig(courseId: string): Promise<CourseConfig>;\n\n /**\n * Get a list of all courses\n */\n getCourseList(): Promise<CourseConfig[]>;\n\n disambiguateCourse(courseId: string, disambiguator: string): Promise<void>;\n}\n\nexport interface CourseInfo {\n cardCount: number;\n registeredUsers: number;\n}\n\nexport interface CourseDBInterface extends NavigationStrategyManager {\n /**\n * Get course config\n */\n getCourseConfig(): Promise<CourseConfig>;\n\n getCourseID(): string;\n\n /**\n * Set course config\n */\n updateCourseConfig(cfg: CourseConfig): Promise<PouchDB.Core.Response>;\n\n getCourseInfo(): Promise<CourseInfo>;\n\n getCourseDoc<T extends SkuilderCourseData>(\n id: string,\n options?: PouchDB.Core.GetOptions\n ): Promise<T>;\n getCourseDocs<T extends SkuilderCourseData>(\n ids: string[],\n options?: PouchDB.Core.AllDocsOptions\n ): Promise<PouchDB.Core.AllDocsWithKeysResponse<{} & T>>;\n\n /**\n * Get cards sorted by ELO rating\n */\n getCardsByELO(\n elo: number,\n limit?: number\n ): Promise<\n {\n courseID: string;\n cardID: string;\n elo?: number;\n }[]\n >;\n\n /**\n * Get ELO data for specific cards\n */\n getCardEloData(cardIds: string[]): Promise<CourseElo[]>;\n\n /**\n * Update card ELO rating\n */\n updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;\n\n /**\n * Get new cards for study\n */\n getNewCards(limit?: number): Promise<StudySessionNewItem[]>;\n\n /**\n * Get cards centered at a particular ELO rating\n */\n getCardsCenteredAtELO(\n options: { limit: number; elo: 'user' | 'random' | number },\n filter?: (card: QualifiedCardID) => boolean\n ): Promise<StudySessionItem[]>;\n\n /**\n * Get tags for a card\n */\n getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>>;\n\n /**\n * Add a tag to a card\n */\n addTagToCard(cardId: string, tagId: string, updateELO?: boolean): Promise<PouchDB.Core.Response>;\n\n /**\n * Remove a tag from a card\n */\n removeTagFromCard(cardId: string, tagId: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Create a new tag\n */\n createTag(tagName: string, author: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Get a tag by name\n */\n getTag(tagName: string): Promise<Tag>;\n\n /**\n * Update a tag\n */\n updateTag(tag: Tag): Promise<PouchDB.Core.Response>;\n\n /**\n * Get all tag stubs for a course\n */\n getCourseTagStubs(): Promise<PouchDB.Core.AllDocsResponse<Tag>>;\n\n /**\n * Add a note to the course\n */\n addNote(\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [key: string]: PouchDB.Core.FullAttachment },\n elo?: CourseElo\n ): Promise<DataLayerResult>;\n\n removeCard(cardId: string): Promise<PouchDB.Core.Response>;\n\n getInexperiencedCards(): Promise<\n {\n courseId: string;\n cardId: string;\n count: number;\n elo: CourseElo;\n }[]\n >;\n\n /**\n * Search for cards by text content\n * @param query Text to search for\n * @returns Array of matching card data\n */\n searchCards(query: string): Promise<any[]>;\n\n /**\n * Find documents using PouchDB query syntax\n * @param request PouchDB find request\n * @returns Query response\n */\n find(request: PouchDB.Find.FindRequest<any>): Promise<PouchDB.Find.FindResponse<any>>;\n}\n","// db/src/core/interfaces.ts\n\nimport { UserDBInterface, UserDBReader } from './userDB';\nimport { CourseDBInterface, CoursesDBInterface } from './courseDB';\nimport { ClassroomDBInterface } from './classroomDB';\nimport { AdminDBInterface } from './adminDB';\n\n/**\n * Main factory interface for data access\n */\nexport interface DataLayerProvider {\n /**\n * Get the user database interface\n */\n getUserDB(): UserDBInterface;\n\n /**\n * Create a UserDBReader for a specific user (admin access required)\n * Uses session authentication to verify requesting user is admin\n * @param targetUsername - The username to create a reader for\n * @throws Error if requesting user is not 'admin'\n */\n createUserReaderForUser(targetUsername: string): Promise<UserDBReader>;\n\n /**\n * Get a course database interface\n */\n getCourseDB(courseId: string): CourseDBInterface;\n\n /**\n * Get the courses-lookup interface\n */\n getCoursesDB(): CoursesDBInterface;\n\n /**\n * Get a classroom database interface\n */\n getClassroomDB(classId: string, type: 'student' | 'teacher'): Promise<ClassroomDBInterface>;\n\n /**\n * Get the admin database interface\n */\n getAdminDB(): AdminDBInterface;\n\n /**\n * Initialize the data layer\n */\n initialize(): Promise<void>;\n\n /**\n * Teardown the data layer\n */\n teardown(): Promise<void>;\n\n /**\n * Check if this data layer is read-only\n */\n isReadOnly(): boolean;\n}\n","import {\n ActivityRecord,\n CourseRegistration,\n CourseRegistrationDoc,\n ScheduledCard,\n} from '@db/core/types/user';\nimport { CourseElo, Status } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\nimport { CardHistory, CardRecord, QualifiedCardID } from '../types/types-legacy';\nimport { UserConfig } from '../types/user';\nimport { DocumentUpdater } from '@db/study';\n\n/**\n * Read-only user data operations\n */\nexport interface UserDBReader {\n get<T>(id: string): Promise<T & PouchDB.Core.RevisionIdMeta>;\n getUsername(): string;\n isLoggedIn(): boolean;\n\n /**\n * Get user configuration\n */\n getConfig(): Promise<UserConfig>;\n\n /**\n * Get cards that the user has seen\n */\n getSeenCards(courseId?: string): Promise<string[]>;\n\n /**\n * Get cards that are actively scheduled for review\n */\n getActiveCards(): Promise<QualifiedCardID[]>;\n\n /**\n * Get user's course registrations\n */\n getCourseRegistrationsDoc(): Promise<CourseRegistrationDoc>;\n\n /**\n * Get the registration doc for a specific course.\n * @param courseId\n */\n getCourseRegDoc(courseId: string): Promise<CourseRegistration>;\n\n /**\n * Get user's active courses\n */\n getActiveCourses(): Promise<CourseRegistration[]>;\n\n /**\n * Get user's pending reviews\n */\n getPendingReviews(courseId?: string): Promise<ScheduledCard[]>;\n\n getActivityRecords(): Promise<ActivityRecord[]>;\n\n /**\n * Get user's classroom registrations\n */\n getUserClassrooms(): Promise<ClassroomRegistrationDoc>;\n\n /**\n * Get user's active classes\n */\n getActiveClasses(): Promise<string[]>;\n\n getCourseInterface(courseId: string): Promise<UsrCrsDataInterface>;\n}\n\n/**\n * User data mutation operations\n */\nexport interface UserDBWriter extends DocumentUpdater {\n /**\n * Update user configuration\n */\n setConfig(config: Partial<UserConfig>): Promise<void>;\n\n /**\n * Record a user's interaction with a card\n */\n putCardRecord<T extends CardRecord>(record: T): Promise<CardHistory<CardRecord>>;\n\n /**\n * Register user for a course\n */\n registerForCourse(courseId: string, previewMode?: boolean): Promise<PouchDB.Core.Response>;\n\n /**\n * Drop a course registration\n */\n dropCourse(courseId: string, dropStatus?: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Schedule a card for review\n */\n scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: string;\n time: Moment;\n scheduledFor: 'course' | 'classroom';\n schedulingAgentId: string;\n }): Promise<void>;\n\n /**\n * Remove a scheduled card review\n */\n removeScheduledCardReview(reviewId: string): Promise<void>;\n\n /**\n * Register user for a classroom\n */\n registerForClassroom(\n classId: string,\n registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n ): Promise<PouchDB.Core.Response>;\n\n /**\n * Drop user from classroom\n */\n dropFromClassroom(classId: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Update user's ELO rating for a course\n */\n updateUserElo(courseId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;\n\n /**\n * Reset all user data (progress, registrations, etc.) while preserving authentication\n */\n resetUserData(): Promise<{ status: Status; error?: string }>;\n}\n\n/**\n * Authentication and account management operations\n */\nexport interface UserDBAuthenticator {\n /**\n * Create a new user account\n */\n createAccount(\n username: string,\n password: string\n ): Promise<{\n status: Status;\n error: string;\n }>;\n\n /**\n * Log in as a user\n */\n login(\n username: string,\n password: string\n ): Promise<{\n ok: boolean;\n name?: string;\n roles?: string[];\n }>;\n\n /**\n * Log out the current user\n */\n logout(): Promise<{\n ok: boolean;\n }>;\n}\n\n/**\n * Complete user database interface - combines all user operations\n * This maintains backward compatibility with existing code\n */\nexport interface UserDBInterface extends UserDBReader, UserDBWriter, UserDBAuthenticator {}\n\nexport interface UserCourseSettings {\n [setting: string]: string | number | boolean;\n}\n\nexport interface UserCourseSetting {\n key: string;\n value: string | number | boolean;\n}\n\n// [ ] reconsider here. Should maybe be generic type based on <T extends StudyContentSource> ?\nexport interface UsrCrsDataInterface {\n getScheduledReviewCount(): Promise<number>;\n getCourseSettings(): Promise<UserCourseSettings>;\n updateCourseSettings(updates: UserCourseSetting[]): void; // [ ] return a result of some sort?\n // getRegistrationDoc(): Promise<CourseRegistration>;\n}\n\nexport type ClassroomRegistrationDesignation = 'student' | 'teacher' | 'aide' | 'admin';\n\nexport interface ClassroomRegistration {\n classID: string;\n registeredAs: ClassroomRegistrationDesignation;\n}\n\nexport interface ClassroomRegistrationDoc {\n registrations: ClassroomRegistration[];\n}\n","export * from './adminDB';\nexport * from './classroomDB';\nexport * from './contentSource';\nexport * from './courseDB';\nexport * from './dataLayerProvider';\n\nexport * from './userDB';\n","import { CourseElo } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\n\nexport interface SessionTrackingData {\n peekSessionCount: number;\n studySessionCount: number;\n sessionCount: number; // total\n firstSessionDate: string;\n lastSessionDate: string;\n signupPrompted: boolean;\n promptDismissalCount: number;\n studyModeAcknowledged: boolean;\n}\n\nexport interface UserConfig {\n darkMode: boolean;\n likesConfetti: boolean;\n sessionTimeLimit: number; // Session time limit in minutes\n email?: string; // Optional email for verification flows (added for enhanced auth)\n\n // Session tracking for trial enforcement (per-course)\n // Key is courseId (e.g., 'letterspractice-basic')\n sessionTracking?: Record<string, SessionTrackingData>;\n}\n\nexport interface ActivityRecord {\n timeStamp: number | string;\n [key: string]: any;\n}\n\nexport interface CourseRegistration {\n status?: 'active' | 'dropped' | 'maintenance-mode' | 'preview';\n courseID: string;\n admin: boolean;\n moderator: boolean;\n user: boolean;\n settings?: {\n [setting: string]: string | number | boolean;\n };\n elo: number | CourseElo;\n}\n\ninterface StudyWeights {\n [courseID: string]: number;\n}\n\nexport interface CourseRegistrationDoc {\n courses: CourseRegistration[];\n studyWeight: StudyWeights;\n}\n\nexport interface ScheduledCard {\n _id: PouchDB.Core.DocumentId;\n\n /**\n * The docID of the card to be reviewed\n */\n cardId: PouchDB.Core.DocumentId;\n /**\n * The ID of the course\n */\n courseId: string;\n /**\n * The time at which the card becomes eligible for review.\n *\n * (Should probably be UTC adjusted so that performance is\n * not wonky across time zones)\n * \n * Note: Stored as ISO string for PouchDB serialization compatibility,\n * but can be consumed as Moment objects via moment.utc(reviewTime)\n */\n reviewTime: string | Moment;\n\n /**\n * The time at which this scheduled event was created.\n * \n * Note: Stored as ISO string for PouchDB serialization compatibility,\n * but can be consumed as Moment objects via moment.utc(scheduledAt)\n */\n scheduledAt: string | Moment;\n\n /**\n * Classifying whether this card is scheduled on behalf of a\n * user-registered course or by as assigned content from a\n * user-registered classroom\n */\n scheduledFor: 'course' | 'classroom';\n\n /**\n * The ID of the course or classroom that requested this card\n */\n schedulingAgentId: string;\n}\n","import { CourseElo, Status, ParsedCard, BulkImportCardData } from '@vue-skuilder/common';\nimport { CourseDBInterface } from '../../core/interfaces/courseDB';\nimport { ImportResult, BulkCardProcessorConfig } from './types';\nimport { logger } from '../../util/logger';\n\n/**\n * Processes multiple cards from bulk text input\n *\n * @param parsedCards - Array of parsed cards to import\n * @param courseDB - Course database interface\n * @param config - Configuration for the card processor\n * @returns Array of import results\n */\nexport async function importParsedCards(\n parsedCards: ParsedCard[],\n courseDB: CourseDBInterface,\n config: BulkCardProcessorConfig\n): Promise<ImportResult[]> {\n const results: ImportResult[] = [];\n\n for (const parsedCard of parsedCards) {\n try {\n // processCard takes a ParsedCard and returns an ImportResult\n const result = await processCard(parsedCard, courseDB, config);\n results.push(result);\n } catch (error) {\n logger.error('Error processing card:', error);\n // Reconstruct originalText from parsedCard for this specific catch block\n // This is a fallback if processCard itself throws an unhandled error.\n // Normally, processCard should return an ImportResult with status 'error'.\n let errorOriginalText = parsedCard.markdown;\n if (parsedCard.tags && parsedCard.tags.length > 0) {\n errorOriginalText += `\\ntags: ${parsedCard.tags.join(', ')}`;\n }\n if (parsedCard.elo !== undefined) {\n errorOriginalText += `\\nelo: ${parsedCard.elo}`;\n }\n results.push({\n originalText: errorOriginalText,\n status: 'error',\n message: `Error processing card: ${\n error instanceof Error ? error.message : 'Unknown error'\n }`,\n });\n }\n }\n\n return results;\n}\n\n/**\n * Processes a single parsed card\n *\n * @param parsedCard - Parsed card data\n * @param courseDB - Course database interface\n * @param config - Configuration for the card processor\n * @returns Import result for the card\n */\nasync function processCard(\n parsedCard: ParsedCard,\n courseDB: CourseDBInterface,\n config: BulkCardProcessorConfig\n): Promise<ImportResult> {\n const { markdown, tags, elo } = parsedCard;\n\n // Build the original text representation including metadata\n let originalText = markdown;\n if (tags.length > 0) {\n originalText += `\\ntags: ${tags.join(', ')}`;\n }\n if (elo !== undefined) {\n originalText += `\\nelo: ${elo}`;\n }\n\n // Create card data object\n const cardData: BulkImportCardData = {\n Input: markdown,\n Uploads: [], // No uploads for bulk import\n };\n\n const tagsElo: CourseElo['tags'] = {};\n for (const tag of tags) {\n tagsElo[tag] = {\n score: elo || 0,\n count: 1,\n };\n }\n\n try {\n const result = await courseDB.addNote(\n config.courseCode,\n config.dataShape,\n cardData,\n config.userName,\n tags,\n undefined, // attachments\n elo\n ? {\n global: {\n score: elo,\n count: 1,\n },\n tags: tagsElo,\n misc: {},\n }\n : undefined\n );\n\n if (result.status === Status.ok) {\n return {\n originalText,\n status: 'success',\n message: 'Card added successfully.',\n cardId: result.id ? result.id : '(unknown)',\n };\n } else {\n return {\n originalText,\n status: 'error',\n message: result.message || 'Failed to add card to database. Unknown error.',\n };\n }\n } catch (error) {\n logger.error('Error adding note:', error);\n return {\n originalText,\n status: 'error',\n message: `Error adding card: ${error instanceof Error ? error.message : 'Unknown error'}`,\n };\n }\n}\n\n/**\n * Validates the configuration for bulk card processing\n *\n * @param config - Configuration to validate\n * @returns Object with validation result and error message if any\n */\nexport function validateProcessorConfig(config: Partial<BulkCardProcessorConfig>): {\n isValid: boolean;\n errorMessage?: string;\n} {\n if (!config.dataShape) {\n return {\n isValid: false,\n errorMessage: 'No data shape provided for card processing',\n };\n }\n\n if (!config.courseCode) {\n return {\n isValid: false,\n errorMessage: 'No course code provided for card processing',\n };\n }\n\n if (!config.userName) {\n return {\n isValid: false,\n errorMessage: 'No user name provided for card processing',\n };\n }\n\n return { isValid: true };\n}\n","import { DataShape } from '@vue-skuilder/common';\n\n/**\n * Interface representing the result of a bulk import operation for a single card\n */\nexport interface ImportResult {\n /** The original text input for the card */\n originalText: string;\n /** Status of the import operation */\n status: 'success' | 'error';\n /** Message describing the result or error */\n message: string;\n /** ID of the newly created card (only for success) */\n cardId?: string;\n}\n\n/**\n * Configuration for the bulk card processor\n */\nexport interface BulkCardProcessorConfig {\n /** The data shape to use for the cards */\n dataShape: DataShape;\n /** The course code used for adding notes */\n courseCode: string;\n /** The username of the current user */\n userName: string;\n}\n","export * from './cardProcessor.js';\nexport * from './types.js';","// Export all core interfaces and types\n\nexport * from './interfaces';\nexport * from './types/types-legacy';\nexport * from './types/user';\nexport * from '../util/Loggable';\nexport * from './util';\nexport * from './navigators';\nexport * from './bulkImport';\n"],"mappings":"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","names":["DocType","PouchDB","PouchDBFind","PouchDBAuth","moment","log","import_moment","moment","uuidv4","import_common","Navigators","module","filterAllDocsByPrefix","getCourseDB","import_common","import_moment","init_classroomDB","getStartAndEndKeys","moment","REVIEW_TIME_FORMAT","getCourseDB","init_adminDB","init_classroomDB","import_common","getCourseDB","filterAllDocsByPrefix","getStartAndEndKeys","import_cross_fetch","import_moment","REVIEW_TIME_FORMAT","init_adminDB","init_classroomDB","process","log","import_common","import_moment","moment","record","init_classroomDB","init_courseDB","init_courseDB","import_common"]}
|
|
1
|
+
{"version":3,"sources":["../../src/core/interfaces/adminDB.ts","../../src/core/interfaces/classroomDB.ts","../../src/impl/common/SyncStrategy.ts","../../src/util/logger.ts","../../src/core/types/types-legacy.ts","../../src/core/util/index.ts","../../src/impl/couch/pouchdb-setup.ts","../../src/util/tuiLogger.ts","../../src/util/dataDirectory.ts","../../src/impl/common/userDBHelpers.ts","../../src/util/Loggable.ts","../../src/impl/couch/updateQueue.ts","../../src/impl/couch/user-course-relDB.ts","../../src/impl/couch/clientCache.ts","../../src/impl/couch/courseAPI.ts","../../src/impl/couch/courseLookupDB.ts","../../src/core/navigators/elo.ts","../../src/core/navigators/hardcodedOrder.ts","../../src/core/navigators/index.ts","../../src/impl/couch/courseDB.ts","../../src/impl/couch/classroomDB.ts","../../src/impl/couch/adminDB.ts","../../src/impl/couch/auth.ts","../../src/impl/couch/CouchDBSyncStrategy.ts","../../src/impl/couch/index.ts","../../src/impl/common/BaseUserDB.ts","../../src/impl/common/index.ts","../../src/factory.ts","../../src/core/interfaces/contentSource.ts","../../src/core/interfaces/courseDB.ts","../../src/core/interfaces/dataLayerProvider.ts","../../src/core/interfaces/userDB.ts","../../src/core/interfaces/index.ts","../../src/core/types/user.ts","../../src/core/bulkImport/cardProcessor.ts","../../src/core/bulkImport/types.ts","../../src/core/bulkImport/index.ts","../../src/core/index.ts"],"sourcesContent":["import { ClassroomConfig, CourseConfig } from '@vue-skuilder/common';\n\n/**\n * Admin functionality\n */\nexport interface AdminDBInterface {\n /**\n * Get all users\n */\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n getUsers(): Promise<PouchDB.Core.Document<{}>[]>;\n\n /**\n * Get all courses\n */\n getCourses(): Promise<CourseConfig[]>;\n\n /**\n * Remove a course\n */\n removeCourse(id: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Get all classrooms\n */\n getClassrooms(): Promise<(ClassroomConfig & { _id: string })[]>;\n}\n","import { ClassroomConfig } from '@vue-skuilder/common';\nimport { ScheduledCard } from '../types/user';\nimport { StudySessionNewItem, StudySessionReviewItem } from './contentSource';\n\n/**\n * Classroom management\n */\nexport interface ClassroomDBInterface {\n /**\n * Get classroom config\n */\n getConfig(): ClassroomConfig;\n\n /**\n * Get assigned content\n */\n getAssignedContent(): Promise<AssignedContent[]>;\n}\n\nexport interface TeacherClassroomDBInterface extends ClassroomDBInterface {\n /**\n * For teacher interfaces: assign content\n */\n assignContent?(content: AssignedContent): Promise<boolean>;\n\n /**\n * For teacher interfaces: remove content\n */\n removeContent?(content: AssignedContent): Promise<void>;\n}\n\nexport interface StudentClassroomDBInterface extends ClassroomDBInterface {\n /**\n * For student interfaces: get pending reviews\n */\n getPendingReviews?(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;\n\n /**\n * For student interfaces: get new cards\n */\n getNewCards?(limit?: number): Promise<StudySessionNewItem[]>;\n}\n\nexport type AssignedContent = AssignedCourse | AssignedTag | AssignedCard;\n\nexport interface AssignedTag extends ContentBase {\n type: 'tag';\n tagID: string;\n}\nexport interface AssignedCourse extends ContentBase {\n type: 'course';\n}\nexport interface AssignedCard extends ContentBase {\n type: 'card';\n cardID: string;\n}\n\ninterface ContentBase {\n type: 'course' | 'tag' | 'card';\n /**\n * Username of the assigning teacher.\n */\n assignedBy: string;\n /**\n * Date the content was assigned.\n */\n assignedOn: moment.Moment;\n /**\n * A 'due' date for this assigned content, for scheduling content\n * in advance. Content will not be actively pushed to students until\n * this date.\n */\n activeOn: moment.Moment;\n courseID: string;\n}\n","// packages/db/src/impl/common/SyncStrategy.ts\n\nimport type { AccountCreationResult, AuthenticationResult } from './types';\n\n/**\n * Strategy interface for handling user data synchronization\n * Different implementations handle remote sync vs local-only storage\n */\nexport interface SyncStrategy {\n /**\n * Set up the remote database for a user\n * @param username The username to set up remote DB for\n * @returns PouchDB database instance (may be same as local for no-op)\n */\n setupRemoteDB(username: string): PouchDB.Database;\n\n /**\n * Get the database to use for write operations (local-first approach)\n * @param username The username to get write DB for\n * @returns PouchDB database instance for write operations\n */\n getWriteDB?(username: string): PouchDB.Database;\n\n /**\n * Start synchronization between local and remote databases\n * @param localDB The local PouchDB instance\n * @param remoteDB The remote PouchDB instance\n */\n startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;\n\n /**\n * Stop synchronization (optional - for cleanup)\n */\n stopSync?(): void;\n\n /**\n * Whether this strategy supports account creation\n */\n canCreateAccount(): boolean;\n\n /**\n * Whether this strategy supports authentication\n */\n canAuthenticate(): boolean;\n\n /**\n * Create a new user account (if supported)\n * @param username The username for the new account\n * @param password The password for the new account\n */\n createAccount?(username: string, password: string): Promise<AccountCreationResult>;\n\n /**\n * Authenticate a user (if supported)\n * @param username The username to authenticate\n * @param password The password to authenticate with\n */\n authenticate?(username: string, password: string): Promise<AuthenticationResult>;\n\n /**\n * Log out the current user (if supported)\n */\n logout?(): Promise<AuthenticationResult>;\n\n /**\n * Get the current logged-in username\n * Returns the username if logged in, or a default guest username\n */\n getCurrentUsername(): Promise<string>;\n}\n\n/**\n * Base class for sync strategies with common functionality\n */\nexport abstract class BaseSyncStrategy implements SyncStrategy {\n abstract setupRemoteDB(username: string): PouchDB.Database;\n abstract startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;\n abstract canCreateAccount(): boolean;\n abstract canAuthenticate(): boolean;\n abstract getCurrentUsername(): Promise<string>;\n\n stopSync?(): void {\n // Default no-op implementation\n }\n\n async createAccount(_username: string, _password: string): Promise<AccountCreationResult> {\n throw new Error('Account creation not supported by this sync strategy');\n }\n\n async authenticate(_username: string, _password: string): Promise<AuthenticationResult> {\n throw new Error('Authentication not supported by this sync strategy');\n }\n\n async logout(): Promise<AuthenticationResult> {\n throw new Error('Logout not supported by this sync strategy');\n }\n}\n","/**\n * Simple logging utility for @vue-skuilder/db package\n *\n * This utility provides environment-aware logging with ESLint suppressions\n * to resolve console statement violations while maintaining logging functionality.\n */\n\nconst isDevelopment = typeof process !== 'undefined' && process.env.NODE_ENV === 'development';\nconst _isBrowser = typeof window !== 'undefined';\n\nexport const logger = {\n /**\n * Debug-level logging - only shown in development\n */\n debug: (message: string, ...args: any[]): void => {\n if (isDevelopment) {\n // eslint-disable-next-line no-console\n console.debug(`[DB:DEBUG] ${message}`, ...args);\n }\n },\n\n /**\n * Info-level logging - general information\n */\n info: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.info(`[DB:INFO] ${message}`, ...args);\n },\n\n /**\n * Warning-level logging - potential issues\n */\n warn: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.warn(`[DB:WARN] ${message}`, ...args);\n },\n\n /**\n * Error-level logging - serious problems\n */\n error: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.error(`[DB:ERROR] ${message}`, ...args);\n },\n\n /**\n * Log function for backward compatibility with existing log() usage\n */\n log: (message: string, ...args: any[]): void => {\n if (isDevelopment) {\n // eslint-disable-next-line no-console\n console.log(`[DB:LOG] ${message}`, ...args);\n }\n },\n};\n","import { CourseElo, Answer, Evaluation } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\nimport { logger } from '../../util/logger';\n\nexport const GuestUsername: string = 'sk-guest-';\n\nexport const log = (message: string): void => {\n logger.log(message);\n};\n\nexport enum DocType {\n DISPLAYABLE_DATA = 'DISPLAYABLE_DATA',\n CARD = 'CARD',\n DATASHAPE = 'DATASHAPE',\n QUESTIONTYPE = 'QUESTION',\n VIEW = 'VIEW',\n PEDAGOGY = 'PEDAGOGY',\n CARDRECORD = 'CARDRECORD',\n SCHEDULED_CARD = 'SCHEDULED_CARD',\n TAG = 'TAG',\n NAVIGATION_STRATEGY = 'NAVIGATION_STRATEGY',\n}\n\nexport interface QualifiedCardID {\n courseID: string;\n cardID: string;\n}\n\n/**\n * Interface for all data on course content and pedagogy stored\n * in the c/pouch database.\n */\nexport interface SkuilderCourseData {\n course: string;\n docType: DocType;\n}\n\nexport interface Tag extends SkuilderCourseData {\n docType: DocType.TAG;\n name: string;\n snippet: string; // 200 char description of the tag\n wiki: string; // 3000 char md-friendly description\n taggedCards: PouchDB.Core.DocumentId[];\n author: string;\n}\nexport interface TagStub {\n name: string;\n snippet: string;\n count: number; // the number of cards that have this tag applied\n}\n\nexport interface CardData extends SkuilderCourseData {\n docType: DocType.CARD;\n id_displayable_data: PouchDB.Core.DocumentId[];\n id_view: PouchDB.Core.DocumentId;\n elo: CourseElo;\n author: string;\n}\n\n/** A list of populated courses in the DB */\nexport interface CourseListData extends PouchDB.Core.Response {\n courses: string[];\n}\n\n/**\n * The data used to hydrate viewable components (questions, info, etc)\n */\nexport interface DisplayableData extends SkuilderCourseData {\n docType: DocType.DISPLAYABLE_DATA;\n author?: string;\n id_datashape: PouchDB.Core.DocumentId;\n data: Field[];\n _attachments?: { [index: string]: PouchDB.Core.FullAttachment };\n}\n\nexport interface Field {\n data: unknown;\n name: string;\n}\n\nexport interface DataShapeData extends SkuilderCourseData {\n docType: DocType.DATASHAPE;\n _id: PouchDB.Core.DocumentId;\n questionTypes: PouchDB.Core.DocumentId[];\n}\n\nexport interface QuestionData extends SkuilderCourseData {\n docType: DocType.QUESTIONTYPE;\n _id: PouchDB.Core.DocumentId;\n viewList: string[];\n dataShapeList: PouchDB.Core.DocumentId[];\n}\n\nexport const DocTypePrefixes = {\n [DocType.CARD]: 'c',\n [DocType.DISPLAYABLE_DATA]: 'dd',\n [DocType.TAG]: 'TAG',\n [DocType.CARDRECORD]: 'cardH',\n [DocType.SCHEDULED_CARD]: 'card_review_',\n // Add other doctypes here as they get prefixed IDs\n [DocType.DATASHAPE]: 'DATASHAPE',\n [DocType.QUESTIONTYPE]: 'QUESTION',\n [DocType.VIEW]: 'VIEW',\n [DocType.PEDAGOGY]: 'PEDAGOGY',\n [DocType.NAVIGATION_STRATEGY]: 'NAVIGATION_STRATEGY',\n} as const;\n\nexport interface CardHistory<T extends CardRecord> {\n _id: PouchDB.Core.DocumentId;\n /**\n * The CouchDB id of the card\n */\n cardID: PouchDB.Core.DocumentId;\n\n /**\n * The ID of the course\n */\n courseID: string;\n\n /**\n * The to-date largest interval between successful\n * card reviews. `0` indicates no successful reviews.\n */\n bestInterval: number;\n\n /**\n * The number of times that a card has been\n * failed in review\n */\n lapses: number;\n\n /**\n * The number of consecutive successful impressions\n * on this card\n */\n streak: number;\n\n records: T[];\n}\n\nexport interface CardRecord {\n /**\n * The CouchDB id of the card\n */\n cardID: string;\n /**\n * The ID of the course\n */\n courseID: string;\n /**\n * Number of milliseconds that the user spent before dismissing\n * the card (ie, \"I've read this\" or \"here is my answer\")\n *\n * //TODO: this (sometimes?) wants to be replaced with a rich\n * recording of user activity in working the question\n */\n timeSpent: number;\n /**\n * The date-time that the card was rendered. timeStamp + timeSpent will give the\n * time of user submission.\n */\n timeStamp: Moment;\n}\n\nexport interface QuestionRecord extends CardRecord, Evaluation {\n userAnswer: Answer;\n /**\n * The number of incorrect user submissions prededing this submisstion.\n *\n * eg, if a user is asked 7*6=__, submitting 46, 48, 42 will result in three\n * records being created having 0, 1, and 2 as their recorded 'priorAttempts' values\n */\n priorAttemps: number;\n}\n","import { DocType, DocTypePrefixes, CardHistory, CardRecord, QuestionRecord } from '../types/types-legacy';\n\nexport function areQuestionRecords(h: CardHistory<CardRecord>): h is CardHistory<QuestionRecord> {\n return isQuestionRecord(h.records[0]);\n}\n\nexport function isQuestionRecord(c: CardRecord): c is QuestionRecord {\n return (c as QuestionRecord).userAnswer !== undefined;\n}\n\nexport function getCardHistoryID(courseID: string, cardID: string): PouchDB.Core.DocumentId {\n return `${DocTypePrefixes[DocType.CARDRECORD]}-${courseID}-${cardID}`;\n}\n\nexport function parseCardHistoryID(id: string): {\n courseID: string;\n cardID: string;\n} {\n const split = id.split('-');\n let error: string = '';\n error += split.length === 3 ? '' : `\\n\\tgiven ID has incorrect number of '-' characters`;\n error +=\n split[0] === DocTypePrefixes[DocType.CARDRECORD] ? '' : `\n\tgiven ID does not start with ${DocTypePrefixes[DocType.CARDRECORD]}`;\n\n if (split.length === 3 && split[0] === DocTypePrefixes[DocType.CARDRECORD]) {\n return {\n courseID: split[1],\n cardID: split[2],\n };\n } else {\n throw new Error('parseCardHistory Error:' + error);\n }\n}\n\ninterface PouchDBError extends Error {\n error?: string;\n reason?: string;\n}\n\nexport function docIsDeleted(e: PouchDBError): boolean {\n return Boolean(e?.error === 'not_found' && e?.reason === 'deleted');\n}\n","import PouchDB from 'pouchdb';\nimport PouchDBFind from 'pouchdb-find';\nimport PouchDBAuth from '@nilock2/pouchdb-authentication';\n\n// Register plugins\nPouchDB.plugin(PouchDBFind);\nPouchDB.plugin(PouchDBAuth);\n\n// Configure PouchDB globally\nPouchDB.defaults({\n // ajax: {\n // timeout: 60000,\n // },\n});\n\nexport default PouchDB;\n","// TUI-aware logging utility that redirects logs to file in Node.js\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { getAppDataDirectory } from './dataDirectory';\n\nlet logFile: string | null = null;\nlet isNodeEnvironment = false;\n\n/**\n * Initialize TUI logging - redirect console logs to file in Node.js\n */\nexport function initializeTuiLogging(): void {\n // Detect Node.js environment\n isNodeEnvironment = typeof window === 'undefined' && typeof process !== 'undefined';\n \n if (!isNodeEnvironment) {\n return; // Browser environment - keep normal console logging\n }\n\n try {\n // Set up log file path\n logFile = path.join(getAppDataDirectory(), 'lastrun.log');\n \n // Clear previous log file\n if (fs.existsSync(logFile)) {\n fs.unlinkSync(logFile);\n }\n \n // Create initial log entry\n const startTime = new Date().toISOString();\n fs.writeFileSync(logFile, `=== TUI Session Started: ${startTime} ===\\n`);\n \n // Redirect console methods to file\n const originalConsole = {\n // eslint-disable-next-line no-console\n log: console.log,\n // eslint-disable-next-line no-console\n error: console.error,\n // eslint-disable-next-line no-console\n warn: console.warn,\n // eslint-disable-next-line no-console\n info: console.info\n };\n \n const writeToLog = (level: string, args: any[]) => {\n const timestamp = new Date().toISOString();\n const message = args.map(arg => \n typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)\n ).join(' ');\n \n const logEntry = `[${timestamp}] ${level}: ${message}\\n`;\n \n try {\n fs.appendFileSync(logFile!, logEntry);\n } catch (err) {\n // Fallback to original console if file write fails\n originalConsole.error('Failed to write to log file:', err);\n originalConsole[level.toLowerCase() as keyof typeof originalConsole](...args);\n }\n };\n \n // Override console methods\n // eslint-disable-next-line no-console\n console.log = (...args) => writeToLog('INFO', args);\n // eslint-disable-next-line no-console\n console.info = (...args) => writeToLog('INFO', args);\n // eslint-disable-next-line no-console\n console.warn = (...args) => writeToLog('WARN', args);\n // eslint-disable-next-line no-console\n console.error = (...args) => writeToLog('ERROR', args);\n \n // Store original methods for potential restoration\n (console as any)._originalMethods = originalConsole;\n \n // eslint-disable-next-line no-console\n console.log('TUI logging initialized - logs redirected to', logFile);\n \n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('Failed to initialize TUI logging:', err);\n }\n}\n\n/**\n * Get the current log file path (for debugging)\n */\nexport function getLogFilePath(): string | null {\n return logFile;\n}\n\n/**\n * Show user-facing message (always visible in TUI)\n */\nexport function showUserMessage(message: string): void {\n if (isNodeEnvironment) {\n // In Node.js, write directly to stdout to bypass log redirection\n process.stdout.write(message + '\\n');\n } else {\n // In browser, use normal console\n // eslint-disable-next-line no-console\n console.log(message);\n }\n}\n\n/**\n * Show user-facing error (always visible in TUI)\n */\nexport function showUserError(message: string): void {\n if (isNodeEnvironment) {\n // In Node.js, write directly to stderr to bypass log redirection\n process.stderr.write('Error: ' + message + '\\n');\n } else {\n // In browser, use normal console\n // eslint-disable-next-line no-console\n console.error(message);\n }\n}\n\n/**\n * Logger object with standard log levels\n */\nexport const logger = {\n debug: (message: string, ...args: any[]) => {\n // eslint-disable-next-line no-console\n console.log(`[DEBUG] ${message}`, ...args);\n },\n info: (message: string, ...args: any[]) => {\n // eslint-disable-next-line no-console\n console.info(`[INFO] ${message}`, ...args);\n },\n warn: (message: string, ...args: any[]) => {\n // eslint-disable-next-line no-console\n console.warn(`[WARN] ${message}`, ...args);\n },\n error: (message: string, ...args: any[]) => {\n // eslint-disable-next-line no-console\n console.error(`[ERROR] ${message}`, ...args);\n },\n};","// Cross-platform data directory utilities for PouchDB\n// Provides OS-appropriate application data directories\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { logger } from './tuiLogger';\nimport { ENV } from '@db/factory';\n\n/**\n * Get the application data directory for the current platform\n * Uses ~/.tuilder as requested by user for simplicity\n */\nexport function getAppDataDirectory(): string {\n if (ENV.LOCAL_STORAGE_PREFIX) {\n return path.join(os.homedir(), `.tuilder`, ENV.LOCAL_STORAGE_PREFIX);\n } else {\n return path.join(os.homedir(), '.tuilder');\n }\n}\n\n/**\n * Ensure the application data directory exists\n * Creates directory recursively if it doesn't exist\n */\nexport async function ensureAppDataDirectory(): Promise<string> {\n const appDataDir = getAppDataDirectory();\n \n try {\n await fs.promises.mkdir(appDataDir, { recursive: true });\n } catch (err: any) {\n if (err.code !== 'EEXIST') {\n throw new Error(`Failed to create app data directory ${appDataDir}: ${err.message}`);\n }\n }\n \n return appDataDir;\n}\n\n/**\n * Get the full path for a PouchDB database file\n * @param dbName - The database name (e.g., 'userdb-Colin')\n */\nexport function getDbPath(dbName: string): string {\n return path.join(getAppDataDirectory(), dbName);\n}\n\n/**\n * Initialize data directory for PouchDB usage\n * Should be called once at application startup\n */\nexport async function initializeDataDirectory(): Promise<void> {\n await ensureAppDataDirectory();\n \n // Log initialization\n logger.info(`PouchDB data directory initialized: ${getAppDataDirectory()}`);\n}","// packages/db/src/impl/common/userDBHelpers.ts\n\nimport moment from 'moment';\nimport { DocType, DocTypePrefixes } from '@db/core';\nimport { logger } from '../../util/logger';\nimport { ScheduledCard } from '@db/core/types/user';\n\nexport const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';\n\nimport pouch from '../couch/pouchdb-setup';\nimport { getDbPath } from '../../util/dataDirectory';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\nexport function hexEncode(str: string): string {\n let hex: string;\n let returnStr: string = '';\n\n for (let i = 0; i < str.length; i++) {\n hex = str.charCodeAt(i).toString(16);\n returnStr += ('000' + hex).slice(3);\n }\n\n return returnStr;\n}\n\nexport function filterAllDocsByPrefix<T>(\n db: PouchDB.Database,\n prefix: string,\n opts?: PouchDB.Core.AllDocsOptions\n) {\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const options: PouchDB.Core.AllDocsWithinRangeOptions = {\n startkey: prefix,\n endkey: prefix + '\\ufff0',\n include_docs: true,\n };\n\n if (opts) {\n Object.assign(options, opts);\n }\n return db.allDocs<T>(options);\n}\n\nexport function getStartAndEndKeys(key: string): {\n startkey: string;\n endkey: string;\n} {\n return {\n startkey: key,\n endkey: key + '\\ufff0',\n };\n}\n\nexport function updateGuestAccountExpirationDate(guestDB: PouchDB.Database<object>) {\n const currentTime = moment.utc();\n const expirationDate: string = currentTime.add(2, 'months').toISOString();\n const expiryDocID: string = 'GuestAccountExpirationDate';\n\n void guestDB\n .get(expiryDocID)\n .then((doc) => {\n return guestDB.put({\n _id: expiryDocID,\n _rev: doc._rev,\n date: expirationDate,\n });\n })\n .catch(() => {\n return guestDB.put({\n _id: expiryDocID,\n date: expirationDate,\n });\n });\n}\n\n/**\n * Get local user database with appropriate adapter for environment\n */\nexport function getLocalUserDB(username: string): PouchDB.Database {\n // // Choose adapter based on environment\n //\n // Not certain of this is required. Let's let pouch's auto detection\n // handle it until we specifically know we need to intervene.\n //\n // let adapter: string;\n // if (typeof window !== 'undefined') {\n // // Browser environment - use IndexedDB\n // adapter = 'idb';\n // } else {\n // // Node.js environment (tests) - use memory adapter\n // adapter = 'memory';\n // }\n\n const dbName = `userdb-${username}`;\n \n // Use proper data directory in Node.js, browser will use IndexedDB\n if (typeof window === 'undefined') {\n // Node.js environment - use filesystem with proper app data directory\n return new pouch(getDbPath(dbName), {});\n } else {\n // Browser environment - use default (IndexedDB)\n return new pouch(dbName, {});\n }\n}\n\n/**\n * Schedule a card review (strategy-agnostic version)\n */\nexport function scheduleCardReviewLocal(\n userDB: PouchDB.Database,\n review: {\n card_id: PouchDB.Core.DocumentId;\n time: moment.Moment;\n course_id: string;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n }\n) {\n const now = moment.utc();\n logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);\n void userDB.put<ScheduledCard>({\n _id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),\n cardId: review.card_id,\n reviewTime: review.time.toISOString(),\n courseId: review.course_id,\n scheduledAt: now.toISOString(),\n scheduledFor: review.scheduledFor,\n schedulingAgentId: review.schedulingAgentId,\n });\n}\n\n/**\n * Remove a scheduled card review (strategy-agnostic version)\n */\nexport async function removeScheduledCardReviewLocal(\n userDB: PouchDB.Database,\n reviewDocID: string\n) {\n const reviewDoc = await userDB.get(reviewDocID);\n userDB\n .remove(reviewDoc)\n .then((res) => {\n if (res.ok) {\n log(`Removed Review Doc: ${reviewDocID}`);\n }\n })\n .catch((err) => {\n log(`Failed to remove Review Doc: ${reviewDocID},\\n${JSON.stringify(err)}`);\n });\n}\n","export abstract class Loggable {\n protected abstract readonly _className: string;\n protected log(...args: unknown[]): void {\n // eslint-disable-next-line no-console\n console.log(`LOG-${this._className}@${new Date()}:`, ...args);\n }\n protected error(...args: unknown[]): void {\n // eslint-disable-next-line no-console\n console.error(`ERROR-${this._className}@${new Date()}:`, ...args);\n }\n}\n","import { Loggable } from '../../util/Loggable';\nimport { logger } from '../../util/logger';\n\nexport type Update<T> = Partial<T> | ((x: T) => T);\n\nexport default class UpdateQueue extends Loggable {\n _className: string = 'UpdateQueue';\n private pendingUpdates: {\n [index: string]: Update<unknown>[];\n } = {};\n private inprogressUpdates: {\n [index: string]: boolean;\n } = {};\n\n private readDB: PouchDB.Database; // Database for read operations\n private writeDB: PouchDB.Database; // Database for write operations (local-first)\n\n public update<T extends PouchDB.Core.Document<object>>(\n id: PouchDB.Core.DocumentId,\n update: Update<T>\n ) {\n logger.debug(`Update requested on doc: ${id}`);\n if (this.pendingUpdates[id]) {\n this.pendingUpdates[id].push(update);\n } else {\n this.pendingUpdates[id] = [update];\n }\n return this.applyUpdates<T>(id);\n }\n\n constructor(readDB: PouchDB.Database, writeDB?: PouchDB.Database) {\n super();\n // PouchDB.debug.enable('*');\n this.readDB = readDB;\n this.writeDB = writeDB || readDB; // Default to readDB if writeDB not provided\n logger.debug(`UpdateQ initialized...`);\n void this.readDB.info().then((i) => {\n logger.debug(`db info: ${JSON.stringify(i)}`);\n });\n }\n\n private async applyUpdates<T extends PouchDB.Core.Document<object>>(\n id: string\n ): Promise<T & PouchDB.Core.GetMeta & PouchDB.Core.RevisionIdMeta> {\n logger.debug(`Applying updates on doc: ${id}`);\n if (this.inprogressUpdates[id]) {\n // Poll instead of recursing to avoid infinite recursion\n while (this.inprogressUpdates[id]) {\n await new Promise(resolve => setTimeout(resolve, Math.random() * 50));\n }\n return this.applyUpdates<T>(id);\n } else {\n if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {\n this.inprogressUpdates[id] = true;\n\n const MAX_RETRIES = 5;\n for (let i = 0; i < MAX_RETRIES; i++) {\n try {\n const doc = await this.readDB.get<T>(id);\n logger.debug(`Retrieved doc: ${id}`);\n\n // Create a new doc object to apply updates to for this attempt\n let updatedDoc = { ...doc };\n\n // Note: This loop is not fully safe if updates are functions that depend on a specific doc state\n // that might change between retries. But for simple object merges, it's okay.\n const updatesToApply = [...this.pendingUpdates[id]];\n for (const update of updatesToApply) {\n if (typeof update === 'function') {\n updatedDoc = { ...updatedDoc, ...update(updatedDoc) };\n } else {\n updatedDoc = {\n ...updatedDoc,\n ...update,\n };\n }\n }\n\n await this.writeDB.put<T>(updatedDoc);\n logger.debug(`Put doc: ${id}`);\n\n // Success! Remove the updates we just applied.\n this.pendingUpdates[id].splice(0, updatesToApply.length);\n\n if (this.pendingUpdates[id].length === 0) {\n this.inprogressUpdates[id] = false;\n delete this.inprogressUpdates[id];\n } else {\n // More updates came in, run again.\n return this.applyUpdates<T>(id);\n }\n return updatedDoc as any; // success, exit loop and function\n } catch (e: any) {\n if (e.name === 'conflict' && i < MAX_RETRIES - 1) {\n logger.warn(`Conflict on update for doc ${id}, retry #${i + 1}`);\n await new Promise((res) => setTimeout(res, 50 * Math.random()));\n // continue to next iteration of the loop\n } else if (e.name === 'not_found' && i === 0) {\n // Document not present - throw to caller for initialization\n logger.warn(`Update failed for ${id} - does not exist. Throwing to caller.`);\n throw e; // Let caller handle\n } else {\n // Max retries reached or a non-conflict error\n delete this.inprogressUpdates[id];\n if (this.pendingUpdates[id]) {\n delete this.pendingUpdates[id];\n }\n logger.error(`Error on attemped update (retry ${i}): ${JSON.stringify(e)}`);\n throw e; // Let caller handle\n }\n }\n }\n // This should be unreachable, but it satisfies the compiler that a value is always returned or an error thrown.\n throw new Error(`UpdateQueue failed for doc ${id} after ${MAX_RETRIES} retries.`);\n } else {\n throw new Error(`Empty Updates Queue Triggered`);\n }\n }\n }\n}\n","import {\n ScheduledCard,\n UserCourseSetting,\n UserCourseSettings,\n UsrCrsDataInterface,\n} from '@db/core';\n\nimport moment, { Moment } from 'moment';\n\nimport { UserDBInterface } from '@db/core';\nimport { logger } from '../../util/logger';\n\nexport class UsrCrsData implements UsrCrsDataInterface {\n private user: UserDBInterface;\n private _courseId: string;\n\n constructor(user: UserDBInterface, courseId: string) {\n this.user = user;\n this._courseId = courseId;\n }\n\n public async getReviewsForcast(daysCount: number) {\n const time = moment.utc().add(daysCount, 'days');\n return this.getReviewstoDate(time);\n }\n\n public async getPendingReviews() {\n const now = moment.utc();\n return this.getReviewstoDate(now);\n }\n\n public async getScheduledReviewCount(): Promise<number> {\n return (await this.getPendingReviews()).length;\n }\n\n public async getCourseSettings(): Promise<UserCourseSettings> {\n const regDoc = await this.user.getCourseRegistrationsDoc();\n const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);\n\n if (crsDoc && crsDoc.settings) {\n return crsDoc.settings;\n } else {\n logger.warn(`no settings found during lookup on course ${this._courseId}`);\n return {};\n }\n }\n public updateCourseSettings(updates: UserCourseSetting[]): void {\n // TODO: Add updateCourseSettings method to UserDBInterface\n // For now, we'll need to cast to access the concrete implementation\n if ('updateCourseSettings' in this.user) {\n void (this.user as any).updateCourseSettings(this._courseId, updates);\n }\n }\n\n private async getReviewstoDate(targetDate: Moment) {\n // Use the interface method instead of direct database access\n const allReviews = await this.user.getPendingReviews(this._courseId);\n\n logger.debug(\n `Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`\n );\n\n return allReviews.filter((review: ScheduledCard) => {\n const reviewTime = moment.utc(review.reviewTime);\n return targetDate.isAfter(reviewTime);\n });\n }\n}\n","// todo: something good here instead\n\nconst CLIENT_CACHE: {\n [k: string]: unknown;\n} = {};\n\nexport async function GET_CACHED<K>(k: string, f?: (x: string) => Promise<K>): Promise<K> {\n if (CLIENT_CACHE[k]) {\n // console.log('returning a cached item');\n return CLIENT_CACHE[k] as K;\n }\n\n CLIENT_CACHE[k] = f ? await f(k) : await GET_ITEM(k);\n return GET_CACHED(k);\n}\n\nasync function GET_ITEM(k: string): Promise<unknown> {\n throw new Error(`No implementation found for GET_CACHED(${k})`);\n}\n","import pouch from './pouchdb-setup';\nimport { createPouchDBConfig } from '.';\nimport { ENV } from '@db/factory';\n// import { DataShape } from '../..base-course/Interfaces/DataShape';\nimport { NameSpacer, ShapeDescriptor } from '@vue-skuilder/common';\nimport { CourseConfig, DataShape } from '@vue-skuilder/common';\nimport { CourseElo, blankCourseElo, toCourseElo } from '@vue-skuilder/common';\nimport { CourseDB, createTag } from './courseDB';\nimport { CardData, DisplayableData, DocType, Tag, DocTypePrefixes } from '../../core/types/types-legacy';\nimport { prepareNote55 } from '@vue-skuilder/common';\nimport { BaseUser } from '../common';\nimport { logger } from '@db/util/logger';\nimport { v4 as uuidv4 } from 'uuid';\n\n/**\n *\n * @param courseID id of the course (quilt) being added to\n * @param codeCourse\n * @param shape\n * @param data the datashape data - data required for this shape\n * @param author\n * @param uploads optional additional media uploads: img0, img1, ..., aud0, aud1,...\n * @returns\n */\nexport async function addNote55(\n courseID: string,\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [x: string]: PouchDB.Core.FullAttachment },\n elo: CourseElo = blankCourseElo()\n): Promise<PouchDB.Core.Response> {\n const db = getCourseDB(courseID);\n const payload = prepareNote55(courseID, codeCourse, shape, data, author, tags, uploads);\n const _id = `${DocTypePrefixes[DocType.DISPLAYABLE_DATA]}-${uuidv4()}`;\n const result = await db.put<DisplayableData>({ ...payload, _id });\n\n const dataShapeId = NameSpacer.getDataShapeString({\n course: codeCourse,\n dataShape: shape.name,\n });\n\n if (result.ok) {\n try {\n // create cards\n await createCards(courseID, dataShapeId, result.id, tags, elo, author);\n } catch (error) {\n // Handle CouchDB errors which often have a 'reason' property\n let errorMessage = 'Unknown error';\n if (error instanceof Error) {\n errorMessage = error.message;\n } else if (error && typeof error === 'object' && 'reason' in error) {\n errorMessage = error.reason as string;\n } else if (error && typeof error === 'object' && 'message' in error) {\n errorMessage = error.message as string;\n } else {\n errorMessage = String(error);\n }\n\n logger.error(`[addNote55] Failed to create cards for note ${result.id}: ${errorMessage}`);\n // Add info to result to indicate card creation failed\n (result as any).cardCreationFailed = true;\n (result as any).cardCreationError = errorMessage;\n }\n } else {\n logger.error(`[addNote55] Error adding note. Result: ${JSON.stringify(result)}`);\n }\n\n return result;\n}\n\nasync function createCards(\n courseID: string,\n datashapeID: PouchDB.Core.DocumentId,\n noteID: PouchDB.Core.DocumentId,\n tags: string[],\n elo: CourseElo = blankCourseElo(),\n author: string\n): Promise<void> {\n const cfg = await getCredentialledCourseConfig(courseID);\n const dsDescriptor = NameSpacer.getDataShapeDescriptor(datashapeID);\n let questionViewTypes: string[] = [];\n\n for (const ds of cfg.dataShapes) {\n if (ds.name === datashapeID) {\n questionViewTypes = ds.questionTypes;\n }\n }\n\n if (questionViewTypes.length === 0) {\n const errorMsg = `No questionViewTypes found for datashapeID: ${datashapeID} in course config. Cards cannot be created.`;\n logger.error(errorMsg);\n throw new Error(errorMsg);\n }\n\n for (const questionView of questionViewTypes) {\n await createCard(questionView, courseID, dsDescriptor, noteID, tags, elo, author);\n }\n}\n\nasync function createCard(\n questionViewName: string,\n courseID: string,\n dsDescriptor: ShapeDescriptor,\n noteID: string,\n tags: string[],\n elo: CourseElo = blankCourseElo(),\n author: string\n): Promise<void> {\n const qDescriptor = NameSpacer.getQuestionDescriptor(questionViewName);\n const cfg = await getCredentialledCourseConfig(courseID);\n\n for (const rQ of cfg.questionTypes) {\n if (rQ.name === questionViewName) {\n for (const view of rQ.viewList) {\n await addCard(\n courseID,\n dsDescriptor.course,\n [noteID],\n NameSpacer.getViewString({\n course: qDescriptor.course,\n questionType: qDescriptor.questionType,\n view,\n }),\n elo,\n tags,\n author\n );\n }\n }\n }\n}\n\n/**\n *\n * Adds a card to the DB. This function is called\n * as a side effect of adding either a View or\n * DisplayableData item.\n * @param course The name of the course that the card belongs to\n * @param id_displayable_data C/PouchDB ID of the data used to hydrate the view\n * @param id_view C/PouchDB ID of the view used to display the card\n *\n * @package\n */\nasync function addCard(\n courseID: string,\n course: string,\n id_displayable_data: PouchDB.Core.DocumentId[],\n id_view: PouchDB.Core.DocumentId,\n elo: CourseElo,\n tags: string[],\n author: string\n): Promise<PouchDB.Core.Response> {\n const db = getCourseDB(courseID);\n const _id = `${DocTypePrefixes[DocType.CARD]}-${uuidv4()}`;\n const card = await db.put<CardData>({\n _id,\n course,\n id_displayable_data,\n id_view,\n docType: DocType.CARD,\n elo: elo || toCourseElo(990 + Math.round(20 * Math.random())),\n author,\n });\n for (const tag of tags) {\n logger.info(`adding tag: ${tag} to card ${card.id}`);\n await addTagToCard(courseID, card.id, tag, author, false);\n }\n return card;\n}\n\nexport async function getCredentialledCourseConfig(courseID: string): Promise<CourseConfig> {\n try {\n const db = getCourseDB(courseID);\n const ret = await db.get<CourseConfig>('CourseConfig');\n ret.courseID = courseID;\n logger.info(`Returning course config: ${JSON.stringify(ret)}`);\n return ret;\n } catch (e) {\n logger.error(`Error fetching config for ${courseID}:`, e);\n throw e;\n }\n}\n\n/**\n Assciates a tag with a card.\n\n NB: DB stores tags as separate documents, with a list of card IDs.\n Consider renaming to `addCardToTag` to reflect this.\n\n NB: tags are created if they don't already exist\n\n @param updateELO whether to update the ELO of the card with the new tag. Default true.\n @package\n*/\nexport async function addTagToCard(\n courseID: string,\n cardID: string,\n tagID: string,\n author: string,\n updateELO: boolean = true\n): Promise<PouchDB.Core.Response> {\n // todo: possible future perf. hit if tags have large #s of taggedCards.\n // In this case, should be converted to a server-request\n const prefixedTagID = getTagID(tagID);\n const courseDB = getCourseDB(courseID);\n const courseApi = new CourseDB(courseID, async () => {\n const dummySyncStrategy = {\n setupRemoteDB: () => null as any,\n startSync: () => {},\n canCreateAccount: () => false,\n canAuthenticate: () => false,\n getCurrentUsername: async () => 'DummyUser',\n };\n return BaseUser.Dummy(dummySyncStrategy);\n });\n try {\n logger.info(`Applying tag ${tagID} to card ${courseID + '-' + cardID}...`);\n const tag = await courseDB.get<Tag>(prefixedTagID);\n if (!tag.taggedCards.includes(cardID)) {\n tag.taggedCards.push(cardID);\n\n if (updateELO) {\n try {\n const eloData = await courseApi.getCardEloData([cardID]);\n const elo = eloData[0];\n elo.tags[tagID] = {\n count: 0,\n score: elo.global.score, // todo: or 1000?\n };\n await updateCardElo(courseID, cardID, elo);\n } catch (error) {\n logger.error('Failed to update ELO data for card:', cardID, error);\n }\n }\n\n return courseDB.put<Tag>(tag);\n } else throw new AlreadyTaggedErr(`Card ${cardID} is already tagged with ${tagID}`);\n } catch (e) {\n if (e instanceof AlreadyTaggedErr) {\n throw e;\n }\n\n await createTag(courseID, tagID, author);\n return addTagToCard(courseID, cardID, tagID, author, updateELO);\n }\n}\n\nasync function updateCardElo(courseID: string, cardID: string, elo: CourseElo) {\n if (elo) {\n // checking against null, undefined, NaN\n const cDB = getCourseDB(courseID);\n const card = await cDB.get<CardData>(cardID);\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return cDB.put(card); // race conditions - is it important? probably not (net-zero effect)\n }\n}\n\nclass AlreadyTaggedErr extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AlreadyTaggedErr';\n }\n}\n\nexport function getTagID(tagName: string): string {\n const tagPrefix = DocType.TAG.valueOf() + '-';\n if (tagName.indexOf(tagPrefix) === 0) {\n return tagName;\n } else {\n return tagPrefix + tagName;\n }\n}\n\nexport function getCourseDB(courseID: string): PouchDB.Database {\n const dbName = `coursedb-${courseID}`;\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n","import pouch from './pouchdb-setup';\nimport { ENV } from '@db/factory';\nimport { logger } from '../../util/logger';\n\nconst courseLookupDBTitle = 'coursedb-lookup';\n\ninterface CourseLookupDoc {\n _id: string;\n _rev: string;\n name: string;\n disambiguator?: string;\n}\n\nlogger.debug(`COURSELOOKUP FILE RUNNING`);\n\n/**\n * A Lookup table of existant courses. Each docID in this DB correspondes to a\n * course database whose name is `coursedb-{docID}`\n */\nexport default class CourseLookup {\n // [ ] this db should be read only for public, admin-only for write\n // Cache for the PouchDB instance\n private static _dbInstance: PouchDB.Database | null = null;\n\n /**\n * Static getter for the PouchDB database instance.\n * Connects using ENV variables and caches the instance.\n * Throws an error if required ENV variables are not set.\n */\n private static get _db(): PouchDB.Database {\n // Return cached instance if available\n if (this._dbInstance) {\n return this._dbInstance;\n }\n\n // --- Check required environment variables ---\n if (ENV.COUCHDB_SERVER_URL === 'NOT_SET' || !ENV.COUCHDB_SERVER_URL) {\n throw new Error(\n 'CourseLookup.db: COUCHDB_SERVER_URL is not set. Ensure initializeDataLayer has been called with valid configuration.'\n );\n }\n if (ENV.COUCHDB_SERVER_PROTOCOL === 'NOT_SET' || !ENV.COUCHDB_SERVER_PROTOCOL) {\n throw new Error(\n 'CourseLookup.db: COUCHDB_SERVER_PROTOCOL is not set. Ensure initializeDataLayer has been called with valid configuration.'\n );\n }\n\n // --- Construct connection options ---\n const dbUrl = `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}/${courseLookupDBTitle}`;\n const options: PouchDB.Configuration.RemoteDatabaseConfiguration = {\n // fetch: (url, opts) => { // Optional: Add for debugging network requests\n // console.log('PouchDB fetch:', url, opts);\n // return pouch.fetch(url, opts);\n // }\n };\n\n // Add authentication if both username and password are provided\n if (ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD) {\n options.auth = {\n username: ENV.COUCHDB_USERNAME,\n password: ENV.COUCHDB_PASSWORD,\n };\n logger.info(`CourseLookup: Connecting to ${dbUrl} with authentication.`);\n } else {\n logger.info(`CourseLookup: Connecting to ${dbUrl} without authentication.`);\n }\n\n // --- Create and cache the PouchDB instance ---\n try {\n this._dbInstance = new pouch(dbUrl, options);\n logger.info(`CourseLookup: Database instance created for ${courseLookupDBTitle}.`);\n return this._dbInstance;\n } catch (error) {\n logger.error(`CourseLookup: Failed to create PouchDB instance for ${dbUrl}`, error);\n // Reset cache attempt on failure\n this._dbInstance = null;\n // Re-throw the error to indicate connection failure\n throw new Error(\n `CourseLookup: Failed to initialize database connection: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n }\n\n /**\n * Adds a new course to the lookup database, and returns the courseID\n * @param courseName\n * @returns\n */\n static async add(courseName: string): Promise<string> {\n const resp = await CourseLookup._db.post({\n name: courseName,\n });\n\n return resp.id;\n }\n\n /**\n * Adds a new course to the lookup database with a specific courseID\n * @param courseId The specific course ID to use\n * @param courseName The course name\n * @param disambiguator Optional disambiguator\n * @returns Promise<void>\n */\n static async addWithId(\n courseId: string,\n courseName: string,\n disambiguator?: string\n ): Promise<void> {\n const doc: Omit<CourseLookupDoc, '_rev'> = {\n _id: courseId,\n name: courseName,\n };\n\n if (disambiguator) {\n doc.disambiguator = disambiguator;\n }\n\n await CourseLookup._db.put(doc);\n }\n\n /**\n * Removes a course from the index\n * @param courseID\n */\n static async delete(courseID: string): Promise<PouchDB.Core.Response> {\n const doc = await CourseLookup._db.get(courseID);\n return await CourseLookup._db.remove(doc);\n }\n\n // [ ] rename to allCourses()\n static async allCourseWare(): Promise<CourseLookupDoc[]> {\n const resp = await CourseLookup._db.allDocs<CourseLookupDoc>({\n include_docs: true,\n });\n\n return resp.rows.map((row) => row.doc!);\n }\n\n static async updateDisambiguator(\n courseID: string,\n disambiguator?: string\n ): Promise<PouchDB.Core.Response> {\n const doc = await CourseLookup._db.get<CourseLookupDoc>(courseID);\n doc.disambiguator = disambiguator;\n return await CourseLookup._db.put(doc);\n }\n\n static async isCourse(courseID: string): Promise<boolean> {\n try {\n await CourseLookup._db.get(courseID);\n return true;\n } catch (error) {\n logger.info(`Courselookup failed:`, error);\n return false;\n }\n }\n}\n","import { ScheduledCard } from '../types/user';\nimport { CourseDBInterface } from '../interfaces/courseDB';\nimport { UserDBInterface } from '../interfaces/userDB';\nimport { ContentNavigator } from './index';\nimport { CourseElo } from '@vue-skuilder/common';\nimport { StudySessionReviewItem, StudySessionNewItem, QualifiedCardID } from '..';\n\nexport default class ELONavigator extends ContentNavigator {\n user: UserDBInterface;\n course: CourseDBInterface;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface\n // The ELO strategy is non-parameterized.\n //\n // It instead relies on existing meta data from the course and user with respect to\n //\n //\n // strategy?: ContentNavigationStrategyData\n ) {\n super();\n this.user = user;\n this.course = course;\n }\n\n async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n type ratedReview = ScheduledCard & CourseElo;\n\n const reviews = await this.user.getPendingReviews(this.course.getCourseID()); // todo: this adds a db round trip - should be server side\n const elo = await this.course.getCardEloData(reviews.map((r) => r.cardId));\n\n const ratedReviews = reviews.map((r, i) => {\n const ratedR: ratedReview = {\n ...r,\n ...elo[i],\n };\n return ratedR;\n });\n\n ratedReviews.sort((a, b) => {\n return a.global.score - b.global.score;\n });\n\n return ratedReviews.map((r) => {\n return {\n ...r,\n contentSourceType: 'course',\n contentSourceID: this.course.getCourseID(),\n cardID: r.cardId,\n courseID: r.courseId,\n qualifiedID: `${r.courseId}-${r.cardId}`,\n reviewID: r._id,\n status: 'review',\n };\n });\n }\n\n async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {\n const activeCards = await this.user.getActiveCards();\n return (\n await this.course.getCardsCenteredAtELO(\n { limit: limit, elo: 'user' },\n (c: QualifiedCardID) => {\n if (activeCards.some((ac) => c.cardID === ac.cardID)) {\n return false;\n } else {\n return true;\n }\n }\n )\n ).map((c) => {\n return {\n ...c,\n status: 'new',\n };\n });\n }\n}\n","import { CourseDBInterface, QualifiedCardID, StudySessionNewItem, StudySessionReviewItem, UserDBInterface } from '..';\nimport { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport { ScheduledCard } from '../types/user';\nimport { ContentNavigator } from './index';\nimport { logger } from '../../util/logger';\n\nexport default class HardcodedOrderNavigator extends ContentNavigator {\n private orderedCardIds: string[] = [];\n private user: UserDBInterface;\n private course: CourseDBInterface;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ) {\n super();\n this.user = user;\n this.course = course;\n\n if (strategyData.serializedData) {\n try {\n this.orderedCardIds = JSON.parse(strategyData.serializedData);\n } catch (e) {\n logger.error('Failed to parse serializedData for HardcodedOrderNavigator', e);\n }\n }\n }\n\n async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n const reviews = await this.user.getPendingReviews(this.course.getCourseID());\n return reviews.map((r) => {\n return {\n ...r,\n contentSourceType: 'course',\n contentSourceID: this.course.getCourseID(),\n cardID: r.cardId,\n courseID: r.courseId,\n reviewID: r._id,\n status: 'review',\n };\n });\n }\n\n async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {\n const activeCardIds = (await this.user.getActiveCards()).map((c: QualifiedCardID) => c.cardID);\n\n const newCardIds = this.orderedCardIds.filter(\n (cardId) => !activeCardIds.includes(cardId)\n );\n\n const cardsToReturn = newCardIds.slice(0, limit);\n\n return cardsToReturn.map((cardId) => {\n return {\n cardID: cardId,\n courseID: this.course.getCourseID(),\n contentSourceType: 'course',\n contentSourceID: this.course.getCourseID(),\n status: 'new',\n };\n });\n }\n}\n","import {\n StudyContentSource,\n UserDBInterface,\n CourseDBInterface,\n StudySessionReviewItem,\n StudySessionNewItem,\n} from '..';\nimport { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport { ScheduledCard } from '../types/user';\nimport { logger } from '../../util/logger';\n\nexport enum Navigators {\n ELO = 'elo',\n HARDCODED = 'hardcodedOrder',\n}\n\n/**\n * A content-navigator provides runtime steering of study sessions.\n */\nexport abstract class ContentNavigator implements StudyContentSource {\n /**\n *\n * @param user\n * @param strategyData\n * @returns the runtime object used to steer a study session.\n */\n static async create(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ): Promise<ContentNavigator> {\n const implementingClass = strategyData.implementingClass;\n let NavigatorImpl;\n\n // Try different extension variations\n const variations = ['.ts', '.js', ''];\n\n for (const ext of variations) {\n try {\n const module = await import(`./${implementingClass}${ext}`);\n NavigatorImpl = module.default;\n break; // Break the loop if loading succeeds\n } catch (e) {\n // Continue to next variation if this one fails\n logger.debug(`Failed to load with extension ${ext}:`, e);\n }\n }\n\n if (!NavigatorImpl) {\n throw new Error(`Could not load navigator implementation for: ${implementingClass}`);\n }\n\n return new NavigatorImpl(user, course, strategyData);\n }\n\n abstract getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;\n abstract getNewCards(n?: number): Promise<StudySessionNewItem[]>;\n}\n","import { CourseDBInterface, CourseInfo, CoursesDBInterface, UserDBInterface } from '@db/core';\nimport { ScheduledCard } from '@db/core/types/user';\nimport {\n CourseConfig,\n CourseElo,\n DataShape,\n EloToNumber,\n Status,\n blankCourseElo,\n toCourseElo,\n} from '@vue-skuilder/common';\n\nimport { filterAllDocsByPrefix, getCourseDB, getCourseDoc, getCourseDocs } from '.';\nimport UpdateQueue from './updateQueue';\nimport {\n StudyContentSource,\n StudySessionItem,\n StudySessionNewItem,\n StudySessionReviewItem,\n} from '../../core/interfaces/contentSource';\nimport {\n CardData,\n DocType,\n QualifiedCardID,\n SkuilderCourseData,\n Tag,\n TagStub,\n DocTypePrefixes,\n} from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\nimport { GET_CACHED } from './clientCache';\nimport { addNote55, addTagToCard, getCredentialledCourseConfig, getTagID } from './courseAPI';\nimport { DataLayerResult } from '@db/core/types/db';\nimport { PouchError } from './types';\nimport CourseLookup from './courseLookupDB';\nimport { ContentNavigationStrategyData } from '@db/core/types/contentNavigationStrategy';\nimport { ContentNavigator, Navigators } from '@db/core/navigators';\n\nexport class CoursesDB implements CoursesDBInterface {\n _courseIDs: string[] | undefined;\n\n constructor(courseIDs?: string[]) {\n if (courseIDs && courseIDs.length > 0) {\n this._courseIDs = courseIDs;\n } else {\n this._courseIDs = undefined;\n }\n }\n\n public async getCourseList(): Promise<CourseConfig[]> {\n let crsList = await CourseLookup.allCourseWare();\n logger.debug(`AllCourses: ${crsList.map((c) => c.name + ', ' + c._id + '\\n\\t')}`);\n if (this._courseIDs) {\n crsList = crsList.filter((c) => this._courseIDs!.includes(c._id));\n }\n\n logger.debug(`AllCourses.filtered: ${crsList.map((c) => c.name + ', ' + c._id + '\\n\\t')}`);\n\n const cfgs = await Promise.all(\n crsList.map(async (c) => {\n try {\n const cfg = await getCredentialledCourseConfig(c._id);\n logger.debug(`Found cfg: ${JSON.stringify(cfg)}`);\n return cfg;\n } catch (e) {\n logger.warn(`Error fetching cfg for course ${c.name}, ${c._id}: ${e}`);\n return undefined;\n }\n })\n );\n return cfgs.filter((c) => !!c);\n }\n\n async getCourseConfig(courseId: string): Promise<CourseConfig> {\n if (this._courseIDs && this._courseIDs.length && !this._courseIDs.includes(courseId)) {\n throw new Error(`Course ${courseId} not in course list`);\n }\n\n const cfg = await getCredentialledCourseConfig(courseId);\n if (cfg === undefined) {\n throw new Error(`Error fetching cfg for course ${courseId}`);\n } else {\n return cfg;\n }\n }\n\n public async disambiguateCourse(courseId: string, disambiguator: string): Promise<void> {\n await CourseLookup.updateDisambiguator(courseId, disambiguator);\n }\n}\n\nfunction randIntWeightedTowardZero(n: number) {\n return Math.floor(Math.random() * Math.random() * Math.random() * n);\n}\n\nexport class CourseDB implements StudyContentSource, CourseDBInterface {\n // private log(msg: string): void {\n // log(`CourseLog: ${this.id}\\n ${msg}`);\n // }\n\n private db: PouchDB.Database;\n private id: string;\n private _getCurrentUser: () => Promise<UserDBInterface>;\n private updateQueue: UpdateQueue;\n\n constructor(id: string, userLookup: () => Promise<UserDBInterface>) {\n this.id = id;\n this.db = getCourseDB(this.id);\n this._getCurrentUser = userLookup;\n this.updateQueue = new UpdateQueue(this.db);\n }\n\n public getCourseID(): string {\n return this.id;\n }\n\n public async getCourseInfo(): Promise<CourseInfo> {\n const cardCount = (\n await this.db.find({\n selector: {\n docType: DocType.CARD,\n },\n limit: 1000,\n })\n ).docs.length;\n\n return {\n cardCount,\n registeredUsers: 0,\n };\n }\n\n public async getInexperiencedCards(limit: number = 2) {\n return (\n await this.db.query('cardsByInexperience', {\n limit,\n })\n ).rows.map((r) => {\n const ret = {\n courseId: this.id,\n cardId: r.id,\n count: r.key,\n elo: r.value,\n };\n return ret;\n });\n }\n\n public async getCardsByEloLimits(\n options: {\n low: number;\n high: number;\n limit: number;\n page: number;\n } = {\n low: 0,\n high: Number.MIN_SAFE_INTEGER,\n limit: 25,\n page: 0,\n }\n ) {\n return (\n await this.db.query('elo', {\n startkey: options.low,\n endkey: options.high,\n limit: options.limit,\n skip: options.limit * options.page,\n })\n ).rows.map((r) => {\n return `${this.id}-${r.id}-${r.key}`;\n });\n }\n public async getCardEloData(id: string[]): Promise<CourseElo[]> {\n const docs = await this.db.allDocs<CardData>({\n keys: id,\n include_docs: true,\n });\n const ret: CourseElo[] = [];\n docs.rows.forEach((r) => {\n // [ ] remove these ts-ignore directives.\n if (isSuccessRow(r)) {\n if (r.doc && r.doc.elo) {\n ret.push(toCourseElo(r.doc.elo));\n } else {\n logger.warn('no elo data for card: ' + r.id);\n ret.push(blankCourseElo());\n }\n } else {\n logger.warn('no elo data for card: ' + JSON.stringify(r));\n ret.push(blankCourseElo());\n }\n });\n return ret;\n }\n\n /**\n * Returns the lowest and highest `global` ELO ratings in the course\n */\n public async getELOBounds() {\n const [low, high] = await Promise.all([\n (\n await this.db.query('elo', {\n startkey: 0,\n limit: 1,\n include_docs: false,\n })\n ).rows[0].key,\n (\n await this.db.query('elo', {\n limit: 1,\n descending: true,\n startkey: 100_000,\n })\n ).rows[0].key,\n ]);\n\n return {\n low: low,\n high: high,\n };\n }\n\n public async removeCard(id: string) {\n const doc = await this.db.get<CardData>(id);\n if (!doc.docType || !(doc.docType === DocType.CARD)) {\n throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);\n }\n \n // Remove card from all associated tags before deleting the card\n try {\n const appliedTags = await this.getAppliedTags(id);\n const results = await Promise.allSettled(\n appliedTags.rows.map(async (tagRow) => {\n const tagId = tagRow.id;\n await this.removeTagFromCard(id, tagId);\n })\n );\n \n // Log any individual tag cleanup failures\n results.forEach((result, index) => {\n if (result.status === 'rejected') {\n const tagId = appliedTags.rows[index].id;\n logger.error(`Failed to remove card ${id} from tag ${tagId}: ${result.reason}`);\n }\n });\n } catch (error) {\n logger.error(`Error removing card ${id} from tags: ${error}`);\n // Continue with card deletion even if tag cleanup fails\n }\n \n return this.db.remove(doc);\n }\n\n public async getCardDisplayableDataIDs(id: string[]) {\n logger.debug(id.join(', '));\n const cards = await this.db.allDocs<CardData>({\n keys: id,\n include_docs: true,\n });\n const ret: { [card: string]: string[] } = {};\n cards.rows.forEach((r) => {\n if (isSuccessRow(r)) {\n ret[r.id] = r.doc!.id_displayable_data;\n }\n });\n\n await Promise.all(\n cards.rows.map((r) => {\n return async () => {\n if (isSuccessRow(r)) {\n ret[r.id] = r.doc!.id_displayable_data;\n }\n };\n })\n );\n\n return ret;\n }\n\n async getCardsByELO(elo: number, cardLimit?: number) {\n elo = parseInt(elo as any);\n const limit = cardLimit ? cardLimit : 25;\n\n const below: PouchDB.Query.Response<object> = await this.db.query('elo', {\n limit: Math.ceil(limit / 2),\n startkey: elo,\n descending: true,\n });\n\n const aboveLimit = limit - below.rows.length;\n\n const above: PouchDB.Query.Response<object> = await this.db.query('elo', {\n limit: aboveLimit,\n startkey: elo + 1,\n });\n // logger.log(JSON.stringify(below));\n\n let cards = below.rows;\n cards = cards.concat(above.rows);\n\n const ret = cards\n .sort((a, b) => {\n const s = Math.abs(a.key - elo) - Math.abs(b.key - elo);\n if (s === 0) {\n return Math.random() - 0.5;\n } else {\n return s;\n }\n })\n .map((c) => {\n return {\n courseID: this.id,\n cardID: c.id,\n elo: c.key,\n };\n });\n\n const str = `below:\\n${below.rows.map((r) => `\\t${r.id}-${r.key}\\n`)}\n\nabove:\\n${above.rows.map((r) => `\\t${r.id}-${r.key}\\n`)}`;\n\n logger.debug(`Getting ${limit} cards centered around elo: ${elo}:\\n\\n` + str);\n\n return ret;\n }\n\n async getCourseConfig(): Promise<CourseConfig> {\n const ret = await getCredentialledCourseConfig(this.id);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Course config not found for course ID: ${this.id}`);\n }\n }\n\n async updateCourseConfig(cfg: CourseConfig): Promise<PouchDB.Core.Response> {\n logger.debug(`Updating: ${JSON.stringify(cfg)}`);\n // write both to the course DB:\n try {\n return await updateCredentialledCourseConfig(this.id, cfg);\n } catch (error) {\n logger.error(`Error updating course config in course DB: ${error}`);\n throw error;\n }\n }\n\n async updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response> {\n if (!elo) {\n throw new Error(`Cannot update card elo with null or undefined value for card ID: ${cardId}`);\n }\n\n try {\n const result = await this.updateQueue.update<\n CardData & PouchDB.Core.GetMeta & PouchDB.Core.IdMeta\n >(cardId, (card) => {\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return card;\n });\n return { ok: true, id: cardId, rev: result._rev };\n } catch (error) {\n logger.error(`Failed to update card elo for card ID: ${cardId}`, error);\n throw new Error(`Failed to update card elo for card ID: ${cardId}`);\n }\n }\n\n async getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>> {\n const ret = await getAppliedTags(this.id, cardId);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Failed to find tags for card ${this.id}-${cardId}`);\n }\n }\n\n async addTagToCard(\n cardId: string,\n tagId: string,\n updateELO?: boolean\n ): Promise<PouchDB.Core.Response> {\n return await addTagToCard(\n this.id,\n cardId,\n tagId,\n (await this._getCurrentUser()).getUsername(),\n updateELO\n );\n }\n\n async removeTagFromCard(cardId: string, tagId: string): Promise<PouchDB.Core.Response> {\n return await removeTagFromCard(this.id, cardId, tagId);\n }\n\n async createTag(name: string, author: string): Promise<PouchDB.Core.Response> {\n return await createTag(this.id, name, author);\n }\n\n async getTag(tagId: string): Promise<PouchDB.Core.GetMeta & PouchDB.Core.Document<Tag>> {\n return await getTag(this.id, tagId);\n }\n\n async updateTag(tag: Tag): Promise<PouchDB.Core.Response> {\n if (tag.course !== this.id) {\n throw new Error(`Tag ${JSON.stringify(tag)} does not belong to course ${this.id}`);\n }\n\n return await updateTag(tag);\n }\n\n async getCourseTagStubs(): Promise<PouchDB.Core.AllDocsResponse<Tag>> {\n return getCourseTagStubs(this.id);\n }\n\n async addNote(\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [key: string]: PouchDB.Core.FullAttachment },\n elo: CourseElo = blankCourseElo()\n ): Promise<DataLayerResult> {\n try {\n const resp = await addNote55(this.id, codeCourse, shape, data, author, tags, uploads, elo);\n if (resp.ok) {\n // Check if card creation failed (property added by addNote55)\n if ((resp as any).cardCreationFailed) {\n logger.warn(\n `[courseDB.addNote] Note added but card creation failed: ${\n (resp as any).cardCreationError\n }`\n );\n return {\n status: Status.error,\n message: `Note was added but no cards were created: ${(resp as any).cardCreationError}`,\n id: resp.id,\n };\n }\n return {\n status: Status.ok,\n message: '',\n id: resp.id,\n };\n } else {\n return {\n status: Status.error,\n message: 'Unexpected error adding note',\n };\n }\n } catch (e) {\n const err = e as PouchDB.Core.Error;\n logger.error(\n `[addNote] error ${err.name}\\n\\treason: ${err.reason}\\n\\tmessage: ${err.message}`\n );\n return {\n status: Status.error,\n message: `Error adding note to course. ${(e as PouchError).reason || err.message}`,\n };\n }\n }\n\n async getCourseDoc<T extends SkuilderCourseData>(\n id: string,\n options?: PouchDB.Core.GetOptions\n ): Promise<PouchDB.Core.GetMeta & PouchDB.Core.Document<T>> {\n return await getCourseDoc(this.id, id, options);\n }\n\n async getCourseDocs<T extends SkuilderCourseData>(\n ids: string[],\n options: PouchDB.Core.AllDocsOptions = {}\n ): Promise<PouchDB.Core.AllDocsWithKeysResponse<{} & T>> {\n return await getCourseDocs(this.id, ids, options);\n }\n\n ////////////////////////////////////\n // NavigationStrategyManager implementation\n ////////////////////////////////////\n\n getNavigationStrategy(id: string): Promise<ContentNavigationStrategyData> {\n logger.debug(`[courseDB] Getting navigation strategy: ${id}`);\n\n if (id == '') {\n const strategy: ContentNavigationStrategyData = {\n _id: 'NAVIGATION_STRATEGY-ELO',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'ELO',\n description: 'ELO-based navigation strategy for ordering content by difficulty',\n implementingClass: Navigators.ELO,\n course: this.id,\n serializedData: '', // serde is a noop for ELO navigator.\n };\n return Promise.resolve(strategy);\n } else {\n return this.db.get(id);\n }\n }\n\n async getAllNavigationStrategies(): Promise<ContentNavigationStrategyData[]> {\n const prefix = DocTypePrefixes[DocType.NAVIGATION_STRATEGY];\n const result = await this.db.allDocs<ContentNavigationStrategyData>({\n startkey: prefix,\n endkey: `${prefix}\\ufff0`,\n include_docs: true,\n });\n return result.rows.map((row) => row.doc!);\n }\n\n async addNavigationStrategy(data: ContentNavigationStrategyData): Promise<void> {\n logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);\n // // For now, just log the data and return success\n // logger.debug(JSON.stringify(data));\n return this.db.put(data).then(() => {});\n }\n updateNavigationStrategy(id: string, data: ContentNavigationStrategyData): Promise<void> {\n logger.debug(`[courseDB] Updating navigation strategy: ${id}`);\n // For now, just log the data and return success\n logger.debug(JSON.stringify(data));\n return Promise.resolve();\n }\n\n async surfaceNavigationStrategy(): Promise<ContentNavigationStrategyData> {\n try {\n const config = await this.getCourseConfig();\n // @ts-expect-error tmp: defaultNavigationStrategyId property does not yet exist\n if (config.defaultNavigationStrategyId) {\n try {\n // @ts-expect-error tmp: defaultNavigationStrategyId property does not yet exist\n const strategy = await this.getNavigationStrategy(config.defaultNavigationStrategyId);\n if (strategy) {\n logger.debug(`Surfacing strategy ${strategy.name} from course config`);\n return strategy;\n }\n } catch (e) {\n logger.warn(\n // @ts-expect-error tmp: defaultNavigationStrategyId property does not yet exist\n `Failed to load strategy '${config.defaultNavigationStrategyId}' specified in course config. Falling back to ELO.`,\n e\n );\n }\n }\n } catch (e) {\n logger.warn(\n 'Could not retrieve course config to determine navigation strategy. Falling back to ELO.',\n e\n );\n }\n\n logger.warn(`Returning hard-coded default ELO navigator`);\n const ret: ContentNavigationStrategyData = {\n _id: 'NAVIGATION_STRATEGY-ELO',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'ELO',\n description: 'ELO-based navigation strategy',\n implementingClass: Navigators.ELO,\n course: this.id,\n serializedData: '', // serde is a noop for ELO navigator.\n };\n return Promise.resolve(ret);\n }\n\n ////////////////////////////////////\n // END NavigationStrategyManager implementation\n ////////////////////////////////////\n\n ////////////////////////////////////\n // StudyContentSource implementation\n ////////////////////////////////////\n\n public async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {\n const u = await this._getCurrentUser();\n\n try {\n const strategy = await this.surfaceNavigationStrategy();\n const navigator = await ContentNavigator.create(u, this, strategy);\n return navigator.getNewCards(limit);\n } catch (e) {\n logger.error(`[courseDB] Error surfacing a NavigationStrategy: ${e}`);\n throw e;\n }\n }\n\n public async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n const u = await this._getCurrentUser();\n\n try {\n const strategy = await this.surfaceNavigationStrategy();\n const navigator = await ContentNavigator.create(u, this, strategy);\n return navigator.getPendingReviews();\n } catch (e) {\n logger.error(`[courseDB] Error surfacing a NavigationStrategy: ${e}`);\n throw e;\n }\n }\n\n public async getCardsCenteredAtELO(\n options: {\n limit: number;\n elo: 'user' | 'random' | number;\n } = {\n limit: 99,\n elo: 'user',\n },\n filter?: (a: QualifiedCardID) => boolean\n ): Promise<StudySessionItem[]> {\n let targetElo: number;\n\n if (options.elo === 'user') {\n const u = await this._getCurrentUser();\n\n targetElo = -1;\n try {\n const courseDoc = (await u.getCourseRegistrationsDoc()).courses.find((c) => {\n return c.courseID === this.id;\n })!;\n targetElo = EloToNumber(courseDoc.elo);\n } catch {\n targetElo = 1000;\n }\n } else if (options.elo === 'random') {\n const bounds = await GET_CACHED(`elo-bounds-${this.id}`, () => this.getELOBounds());\n targetElo = Math.round(bounds.low + Math.random() * (bounds.high - bounds.low));\n // logger.log(`Picked ${targetElo} from [${bounds.low}, ${bounds.high}]`);\n } else {\n targetElo = options.elo;\n }\n\n let cards: (QualifiedCardID & { elo?: number })[] = [];\n let mult: number = 4;\n let previousCount: number = -1;\n let newCount: number = 0;\n\n while (cards.length < options.limit && newCount !== previousCount) {\n cards = await this.getCardsByELO(targetElo, mult * options.limit);\n previousCount = newCount;\n newCount = cards.length;\n\n logger.debug(`Found ${cards.length} elo neighbor cards...`);\n\n if (filter) {\n cards = cards.filter(filter);\n logger.debug(`Filtered to ${cards.length} cards...`);\n }\n\n mult *= 2;\n }\n\n const selectedCards: {\n courseID: string;\n cardID: string;\n elo?: number;\n }[] = [];\n\n while (selectedCards.length < options.limit && cards.length > 0) {\n const index = randIntWeightedTowardZero(cards.length);\n const card = cards.splice(index, 1)[0];\n selectedCards.push(card);\n }\n\n return selectedCards.map((c) => {\n return {\n courseID: this.id,\n cardID: c.cardID,\n contentSourceType: 'course',\n contentSourceID: this.id,\n elo: c.elo,\n status: 'new',\n };\n });\n }\n\n // Admin search methods\n public async searchCards(query: string): Promise<any[]> {\n logger.log(`[CourseDB ${this.id}] Searching for: \"${query}\"`);\n\n // Try multiple search approaches\n let displayableData;\n\n try {\n // Try regex search on the correct data structure: data[0].data\n displayableData = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n 'data.0.data': { $regex: `.*${query}.*` },\n },\n });\n logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`);\n } catch (regexError) {\n logger.log(\n `[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,\n regexError\n );\n\n // Fallback: get all displayable data and filter manually\n const allDisplayable = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n },\n });\n\n logger.log(\n `[CourseDB ${this.id}] Retrieved ${allDisplayable.docs.length} documents for manual filtering`\n );\n\n displayableData = {\n docs: allDisplayable.docs.filter((doc) => {\n // Search entire document as JSON string - inclusive approach for admin tool\n const docString = JSON.stringify(doc).toLowerCase();\n const match = docString.includes(query.toLowerCase());\n if (match) {\n logger.log(`[CourseDB ${this.id}] Manual match found in document: ${doc._id}`);\n }\n return match;\n }),\n };\n }\n\n logger.log(\n `[CourseDB ${this.id}] Found ${displayableData.docs.length} displayable data documents`\n );\n\n if (displayableData.docs.length === 0) {\n // Debug: Let's see what displayable data exists\n const allDisplayableData = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n },\n limit: 5, // Just sample a few\n });\n\n logger.log(\n `[CourseDB ${this.id}] Sample displayable data:`,\n allDisplayableData.docs.map((d) => ({\n id: d._id,\n docType: (d as any).docType,\n dataStructure: (d as any).data ? Object.keys((d as any).data) : 'no data field',\n dataContent: (d as any).data,\n fullDoc: d,\n }))\n );\n }\n\n const allResults: any[] = [];\n\n for (const dd of displayableData.docs) {\n const cards = await this.db.find({\n selector: {\n docType: 'CARD',\n id_displayable_data: { $in: [dd._id] },\n },\n });\n\n logger.log(\n `[CourseDB ${this.id}] Displayable data ${dd._id} linked to ${cards.docs.length} cards`\n );\n allResults.push(...cards.docs);\n }\n\n logger.log(`[CourseDB ${this.id}] Total cards found: ${allResults.length}`);\n return allResults;\n }\n\n public async find(\n request: PouchDB.Find.FindRequest<any>\n ): Promise<PouchDB.Find.FindResponse<any>> {\n return this.db.find(request);\n }\n}\n\n/**\n * Returns a list of registered datashapes for the specified\n * course.\n * @param courseID The ID of the course\n */\nexport async function getCourseDataShapes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n return cfg!.dataShapes;\n}\n\nexport async function getCredentialledDataShapes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n\n return cfg.dataShapes;\n}\n\nexport async function getCourseQuestionTypes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n return cfg!.questionTypes;\n}\n\n// todo: this is actually returning full tag docs now.\n// - performance issue when tags have lots of\n// applied docs\n// - will require a computed couch DB view\nexport async function getCourseTagStubs(\n courseID: string\n): Promise<PouchDB.Core.AllDocsResponse<Tag>> {\n logger.debug(`Getting tag stubs for course: ${courseID}`);\n const stubs = await filterAllDocsByPrefix<Tag>(\n getCourseDB(courseID),\n DocType.TAG.valueOf() + '-'\n );\n\n stubs.rows.forEach((row) => {\n logger.debug(`\\tTag stub for doc: ${row.id}`);\n });\n\n return stubs;\n}\n\nexport async function deleteTag(courseID: string, tagName: string) {\n tagName = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n const doc = await courseDB.get<Tag>(DocType.TAG.valueOf() + '-' + tagName);\n const resp = await courseDB.remove(doc);\n return resp;\n}\n\nexport async function createTag(courseID: string, tagName: string, author: string) {\n logger.debug(`Creating tag: ${tagName}...`);\n const tagID = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n const resp = await courseDB.put<Tag>({\n course: courseID,\n docType: DocType.TAG,\n name: tagName,\n snippet: '',\n taggedCards: [],\n wiki: '',\n author,\n _id: tagID,\n });\n return resp;\n}\n\nexport async function updateTag(tag: Tag) {\n const prior = await getTag(tag.course, tag.name);\n return await getCourseDB(tag.course).put<Tag>({\n ...tag,\n _rev: prior._rev,\n });\n}\n\nexport async function getTag(courseID: string, tagName: string) {\n const tagID = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n return courseDB.get<Tag>(tagID);\n}\n\nexport async function removeTagFromCard(courseID: string, cardID: string, tagID: string) {\n // todo: possible future perf. hit if tags have large #s of taggedCards.\n // In this case, should be converted to a server-request\n tagID = getTagID(tagID);\n const courseDB = getCourseDB(courseID);\n const tag = await courseDB.get<Tag>(tagID);\n tag.taggedCards = tag.taggedCards.filter((taggedID) => {\n return cardID !== taggedID;\n });\n return courseDB.put<Tag>(tag);\n}\n\n/**\n * Returns an array of ancestor tag IDs, where:\n * return[0] = parent,\n * return[1] = grandparent,\n * return[2] = great grandparent,\n * etc.\n *\n * If ret is empty, the tag itself is a root\n */\nexport function getAncestorTagIDs(courseID: string, tagID: string): string[] {\n tagID = getTagID(tagID);\n const split = tagID.split('>');\n if (split.length === 1) {\n return [];\n } else {\n split.pop();\n const parent = split.join('>');\n return [parent].concat(getAncestorTagIDs(courseID, parent));\n }\n}\n\nexport async function getChildTagStubs(courseID: string, tagID: string) {\n return await filterAllDocsByPrefix(getCourseDB(courseID), tagID + '>');\n}\n\nexport async function getAppliedTags(id_course: string, id_card: string) {\n const db = getCourseDB(id_course);\n\n const result = await db.query<TagStub>('getTags', {\n startkey: id_card,\n endkey: id_card,\n // include_docs: true\n });\n\n // log(`getAppliedTags looked up: ${id_card}`);\n // log(`getAppliedTags returning: ${JSON.stringify(result)}`);\n\n return result;\n}\n\nexport async function updateCardElo(courseID: string, cardID: string, elo: CourseElo) {\n if (elo) {\n // checking against null, undefined, NaN\n const cDB = getCourseDB(courseID);\n const card = await cDB.get<CardData>(cardID);\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return cDB.put(card); // race conditions - is it important? probably not (net-zero effect)\n }\n}\n\nexport async function updateCredentialledCourseConfig(courseID: string, config: CourseConfig) {\n logger.debug(`Updating course config:\n\n${JSON.stringify(config)}\n`);\n\n const db = getCourseDB(courseID);\n const old = await getCredentialledCourseConfig(courseID);\n\n return await db.put<CourseConfig>({\n ...config,\n _rev: (old as any)._rev,\n });\n}\n\nfunction isSuccessRow<T>(\n row:\n | {\n key: PouchDB.Core.DocumentKey;\n error: 'not_found';\n }\n | {\n doc?: PouchDB.Core.ExistingDocument<PouchDB.Core.AllDocsMeta & T> | null | undefined;\n id: PouchDB.Core.DocumentId;\n key: PouchDB.Core.DocumentKey;\n value: {\n rev: PouchDB.Core.RevisionId;\n deleted?: boolean | undefined;\n };\n }\n): row is {\n doc?: PouchDB.Core.ExistingDocument<PouchDB.Core.AllDocsMeta & T> | null | undefined;\n id: PouchDB.Core.DocumentId;\n key: PouchDB.Core.DocumentKey;\n value: {\n rev: PouchDB.Core.RevisionId;\n deleted?: boolean | undefined;\n };\n} {\n return 'doc' in row && row.doc !== null && row.doc !== undefined;\n}\n","import {\n StudyContentSource,\n StudySessionNewItem,\n StudySessionReviewItem,\n} from '@db/core/interfaces/contentSource';\nimport { ClassroomConfig } from '@vue-skuilder/common';\nimport { ENV } from '@db/factory';\nimport { logger } from '@db/util/logger';\nimport moment from 'moment';\nimport pouch from './pouchdb-setup';\nimport { getCourseDB, getStartAndEndKeys, createPouchDBConfig, REVIEW_TIME_FORMAT } from '.';\nimport { CourseDB, getTag } from './courseDB';\n\nimport { UserDBInterface } from '@db/core';\nimport {\n AssignedContent,\n AssignedCourse,\n AssignedTag,\n StudentClassroomDBInterface,\n TeacherClassroomDBInterface,\n} from '@db/core/interfaces/classroomDB';\nimport { ScheduledCard } from '@db/core/types/user';\n\nconst classroomLookupDBTitle = 'classdb-lookup';\nexport const CLASSROOM_CONFIG = 'ClassroomConfig';\n\nexport type ClassroomMessage = object;\n\nabstract class ClassroomDBBase {\n public _id!: string;\n protected _db!: PouchDB.Database;\n protected _cfg!: ClassroomConfig;\n protected _initComplete: boolean = false;\n\n protected readonly _content_prefix: string = 'content';\n protected get _content_searchkeys() {\n return getStartAndEndKeys(this._content_prefix);\n }\n\n protected abstract init(): Promise<void>;\n\n public async getAssignedContent(): Promise<AssignedContent[]> {\n logger.info(`Getting assigned content...`);\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const docRows = await this._db.allDocs<AssignedContent>({\n startkey: this._content_prefix,\n endkey: this._content_prefix + `\\ufff0`,\n include_docs: true,\n });\n\n const ret = docRows.rows.map((row) => {\n return row.doc!;\n });\n // logger.info(`Assigned content: ${JSON.stringify(ret)}`);\n\n return ret;\n }\n\n protected getContentId(content: AssignedContent): string {\n if (content.type === 'tag') {\n return `${this._content_prefix}-${content.courseID}-${content.tagID}`;\n } else {\n return `${this._content_prefix}-${content.courseID}`;\n }\n }\n\n public get ready(): boolean {\n return this._initComplete;\n }\n public getConfig(): ClassroomConfig {\n return this._cfg;\n }\n}\n\nexport class StudentClassroomDB\n extends ClassroomDBBase\n implements StudyContentSource, StudentClassroomDBInterface\n{\n // private readonly _prefix: string = 'content';\n private userMessages!: PouchDB.Core.Changes<object>;\n private _user: UserDBInterface;\n\n private constructor(classID: string, user: UserDBInterface) {\n super();\n this._id = classID;\n this._user = user;\n // init() is called explicitly in factory method, not in constructor\n }\n\n async init(): Promise<void> {\n const dbName = `classdb-student-${this._id}`;\n this._db = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n try {\n const cfg = await this._db.get<ClassroomConfig>(CLASSROOM_CONFIG);\n this._cfg = cfg;\n this.userMessages = this._db.changes({\n since: 'now',\n live: true,\n include_docs: true,\n });\n this._initComplete = true;\n return;\n } catch (e) {\n throw new Error(`Error in StudentClassroomDB constructor: ${JSON.stringify(e)}`);\n }\n }\n\n public static async factory(classID: string, user: UserDBInterface): Promise<StudentClassroomDB> {\n const ret = new StudentClassroomDB(classID, user);\n await ret.init();\n return ret;\n }\n\n public setChangeFcn(f: (value: unknown) => object): void {\n // todo: make this into a view request, w/ the user's name attached\n // todo: requires creating the view doc on classroom create in /express\n void this.userMessages.on('change', f);\n }\n\n public async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n const u = this._user;\n return (await u.getPendingReviews())\n .filter((r) => r.scheduledFor === 'classroom' && r.schedulingAgentId === this._id)\n .map((r) => {\n return {\n ...r,\n qualifiedID: `${r.courseId}-${r.cardId}`,\n courseID: r.courseId,\n cardID: r.cardId,\n contentSourceType: 'classroom',\n contentSourceID: this._id,\n reviewID: r._id,\n status: 'review',\n };\n });\n }\n\n public async getNewCards(): Promise<StudySessionNewItem[]> {\n const activeCards = await this._user.getActiveCards();\n const now = moment.utc();\n const assigned = await this.getAssignedContent();\n const due = assigned.filter((c) => now.isAfter(moment.utc(c.activeOn, REVIEW_TIME_FORMAT)));\n\n logger.info(`Due content: ${JSON.stringify(due)}`);\n\n let ret: StudySessionNewItem[] = [];\n\n for (let i = 0; i < due.length; i++) {\n const content = due[i];\n\n if (content.type === 'course') {\n const db = new CourseDB(content.courseID, async () => this._user);\n ret = ret.concat(await db.getNewCards());\n } else if (content.type === 'tag') {\n const tagDoc = await getTag(content.courseID, content.tagID);\n\n ret = ret.concat(\n tagDoc.taggedCards.map((c) => {\n return {\n courseID: content.courseID,\n cardID: c,\n qualifiedID: `${content.courseID}-${c}`,\n contentSourceType: 'classroom',\n contentSourceID: this._id,\n status: 'new',\n };\n })\n );\n } else if (content.type === 'card') {\n // returning card docs - not IDs\n ret.push(await getCourseDB(content.courseID).get(content.cardID));\n }\n }\n\n logger.info(\n `New Cards from classroom ${this._cfg.name}: ${ret.map((c) => `${c.courseID}-${c.cardID}`)}`\n );\n\n return ret.filter((c) => {\n if (activeCards.some((ac) => c.cardID === ac.cardID)) {\n // [ ] almost certainly broken after removing qualifiedID from StudySessionItem\n return false;\n } else {\n return true;\n }\n });\n }\n}\n\n/**\n * Interface for managing a classroom.\n */\nexport class TeacherClassroomDB extends ClassroomDBBase implements TeacherClassroomDBInterface {\n private _stuDb!: PouchDB.Database;\n\n private constructor(classID: string) {\n super();\n this._id = classID;\n }\n\n async init(): Promise<void> {\n const dbName = `classdb-teacher-${this._id}`;\n const stuDbName = `classdb-student-${this._id}`;\n this._db = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n this._stuDb = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + stuDbName,\n createPouchDBConfig()\n );\n try {\n return this._db\n .get<ClassroomConfig>(CLASSROOM_CONFIG)\n .then((cfg) => {\n this._cfg = cfg;\n this._initComplete = true;\n })\n .then(() => {\n return;\n });\n } catch (e) {\n throw new Error(`Error in TeacherClassroomDB constructor: ${JSON.stringify(e)}`);\n }\n }\n\n public static async factory(classID: string): Promise<TeacherClassroomDB> {\n const ret = new TeacherClassroomDB(classID);\n await ret.init();\n return ret;\n }\n\n public async removeContent(content: AssignedContent): Promise<void> {\n const contentID = this.getContentId(content);\n\n try {\n const doc = await this._db.get(contentID);\n await this._db.remove(doc);\n void this._db.replicate.to(this._stuDb, {\n doc_ids: [contentID],\n });\n } catch (error) {\n logger.error('Failed to remove content:', contentID, error);\n }\n }\n\n public async assignContent(content: AssignedContent): Promise<boolean> {\n let put: PouchDB.Core.Response;\n const id: string = this.getContentId(content);\n\n if (content.type === 'tag') {\n put = await this._db.put<AssignedTag>({\n courseID: content.courseID,\n tagID: content.tagID,\n type: 'tag',\n _id: id,\n assignedBy: content.assignedBy,\n assignedOn: moment.utc(),\n activeOn: content.activeOn || moment.utc(),\n });\n } else {\n put = await this._db.put<AssignedCourse>({\n courseID: content.courseID,\n type: 'course',\n _id: id,\n assignedBy: content.assignedBy,\n assignedOn: moment.utc(),\n activeOn: content.activeOn || moment.utc(),\n });\n }\n\n if (put.ok) {\n void this._db.replicate.to(this._stuDb, {\n doc_ids: [id],\n });\n return true;\n } else {\n return false;\n }\n }\n}\n\nexport const ClassroomLookupDB: () => PouchDB.Database = () =>\n new pouch(ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + classroomLookupDBTitle, {\n skip_setup: true,\n });\n\nexport function getClassroomDB(classID: string, version: 'student' | 'teacher'): PouchDB.Database {\n const dbName = `classdb-${version}-${classID}`;\n logger.info(`Retrieving classroom db: ${dbName}`);\n\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n\nexport async function getClassroomConfig(classID: string): Promise<ClassroomConfig> {\n return await getClassroomDB(classID, 'student').get<ClassroomConfig>(CLASSROOM_CONFIG);\n}\n","import pouch from './pouchdb-setup';\nimport { ENV } from '@db/factory';\nimport {\n createPouchDBConfig,\n getStartAndEndKeys,\n getCredentialledCourseConfig,\n updateCredentialledCourseConfig,\n} from '.';\nimport { TeacherClassroomDB, ClassroomLookupDB } from './classroomDB';\nimport { PouchError } from './types';\n\nimport { AdminDBInterface } from '@db/core';\nimport CourseLookup from './courseLookupDB';\nimport { logger } from '@db/util/logger';\n\nexport class AdminDB implements AdminDBInterface {\n private usersDB!: PouchDB.Database;\n\n constructor() {\n // [ ] execute a check here against credentials, and throw an error\n // if the user is not an admin\n this.usersDB = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + '_users',\n createPouchDBConfig()\n );\n }\n\n public async getUsers() {\n return (\n await this.usersDB.allDocs({\n include_docs: true,\n ...getStartAndEndKeys('org.couchdb.user:'),\n })\n ).rows.map((r) => r.doc!);\n }\n\n public async getCourses() {\n const list = await CourseLookup.allCourseWare();\n return await Promise.all(\n list.map((c) => {\n return getCredentialledCourseConfig(c._id);\n })\n );\n }\n public async removeCourse(id: string) {\n // remove the indexer\n const delResp = await CourseLookup.delete(id);\n\n // set the 'CourseConfig' to 'deleted'\n const cfg = await getCredentialledCourseConfig(id);\n cfg.deleted = true;\n const isDeletedResp = await updateCredentialledCourseConfig(id, cfg);\n\n return {\n ok: delResp.ok && isDeletedResp.ok,\n id: delResp.id,\n rev: delResp.rev,\n };\n }\n\n public async getClassrooms() {\n // const joincodes =\n const uuids = (\n await ClassroomLookupDB().allDocs<{ uuid: string }>({\n include_docs: true,\n })\n ).rows.map((r) => r.doc!.uuid);\n logger.debug(uuids.join(', '));\n\n const promisedCRDbs: TeacherClassroomDB[] = [];\n for (let i = 0; i < uuids.length; i++) {\n try {\n const db = await TeacherClassroomDB.factory(uuids[i]);\n promisedCRDbs.push(db);\n } catch (e) {\n const err = e as PouchError;\n if (err.error && err.error === 'not_found') {\n logger.warn(`db ${uuids[i]} not found`);\n }\n }\n }\n\n const dbs = await Promise.all(promisedCRDbs);\n return dbs.map((db) => {\n return {\n ...db.getConfig(),\n _id: db._id,\n };\n });\n }\n}\n","import { ENV, NOT_SET } from '@db/factory';\nimport { logger } from '@db/util/logger';\nimport fetch from 'cross-fetch';\n\ninterface SessionResponse {\n info: unknown;\n ok: boolean;\n userCtx: {\n name: string;\n roles: string[];\n };\n}\n\nexport async function getCurrentSession(): Promise<SessionResponse> {\n // Legacy XMLHttpRequest implementation\n // return new Promise((resolve, reject) => {\n // const authXML = new XMLHttpRequest();\n // authXML.withCredentials = true;\n //\n // authXML.onerror = (e): void => {\n // reject(new Error('Session check failed:', e));\n // };\n //\n // authXML.addEventListener('load', () => {\n // try {\n // const resp: SessionResponse = JSON.parse(authXML.responseText);\n // resolve(resp);\n // } catch (e) {\n // reject(e);\n // }\n // });\n //\n // const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}_session`;\n // authXML.open('GET', url);\n // authXML.send();\n // });\n \n try {\n // Handle case where ENV variables might not be properly set\n if (ENV.COUCHDB_SERVER_URL === NOT_SET || ENV.COUCHDB_SERVER_PROTOCOL === NOT_SET) {\n throw new Error(`CouchDB server configuration not properly initialized. Protocol: \"${ENV.COUCHDB_SERVER_PROTOCOL}\", URL: \"${ENV.COUCHDB_SERVER_URL}\"`);\n }\n \n // Ensure URL has proper slash before _session endpoint\n const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith('/') \n ? ENV.COUCHDB_SERVER_URL.slice(0, -1) \n : ENV.COUCHDB_SERVER_URL;\n const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;\n logger.debug(`Attempting session check at: ${url}`);\n \n const response = await fetch(url, {\n method: 'GET',\n credentials: 'include',\n });\n \n if (!response.ok) {\n throw new Error(`Session check failed: ${response.status}`);\n }\n \n const resp: SessionResponse = await response.json();\n return resp;\n } catch (error) {\n // Use same URL construction logic for error reporting\n const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith('/') \n ? ENV.COUCHDB_SERVER_URL.slice(0, -1) \n : ENV.COUCHDB_SERVER_URL;\n const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;\n logger.error(`Session check error attempting to connect to: ${url} - ${error}`);\n throw new Error(`Session check failed connecting to ${url}: ${error}`);\n }\n}\n\nexport async function getLoggedInUsername(): Promise<string> {\n const session = await getCurrentSession();\n if (session.userCtx.name && session.userCtx.name !== '') {\n return session.userCtx.name;\n }\n // Not logged in - throw so caller can handle guest account\n throw new Error('No logged in user');\n}\n","// packages/db/src/impl/couch/CouchDBSyncStrategy.ts\n\nimport { ENV } from '@db/factory';\nimport { GuestUsername } from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\nimport { Status } from '@vue-skuilder/common';\nimport type { SyncStrategy } from '../common/SyncStrategy';\nimport type { AccountCreationResult, AuthenticationResult } from '../common/types';\nimport { getLocalUserDB, hexEncode, updateGuestAccountExpirationDate, accomodateGuest } from '../common';\nimport pouch from './pouchdb-setup';\nimport { createPouchDBConfig } from './index';\nimport { getLoggedInUsername } from './auth';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\n/**\n * Sync strategy that implements full CouchDB remote synchronization\n * Handles account creation, authentication, and live sync with remote CouchDB server\n */\nexport class CouchDBSyncStrategy implements SyncStrategy {\n private syncHandle?: any; // Handle to cancel sync if needed\n\n setupRemoteDB(username: string): PouchDB.Database {\n if (username === GuestUsername || username.startsWith(GuestUsername)) {\n // For guest users, remote is same as local (no remote sync)\n return getLocalUserDB(username);\n } else {\n // For real users, connect to remote CouchDB\n return this.getUserDB(username);\n }\n }\n\n getWriteDB(username: string): PouchDB.Database {\n if (username === GuestUsername || username.startsWith(GuestUsername)) {\n // Guest users write to local database\n return getLocalUserDB(username);\n } else {\n // Authenticated users write to remote (which will sync to local)\n return this.getUserDB(username);\n }\n }\n\n startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void {\n // Only sync if local and remote are different instances\n if (localDB !== remoteDB) {\n this.syncHandle = pouch.sync(localDB, remoteDB, {\n live: true,\n retry: true,\n });\n }\n // If they're the same (guest mode), no sync needed\n }\n\n stopSync?(): void {\n if (this.syncHandle) {\n this.syncHandle.cancel();\n this.syncHandle = undefined;\n }\n }\n\n canCreateAccount(): boolean {\n return true;\n }\n\n canAuthenticate(): boolean {\n return true;\n }\n\n async createAccount(username: string, password: string): Promise<AccountCreationResult> {\n // IMPORTANT: Capture funnel username BEFORE any operations that might change session state\n const funnelUsername = await this.getCurrentUsername();\n const isFunnelAccount = funnelUsername.startsWith(GuestUsername);\n\n if (isFunnelAccount) {\n logger.info(`Creating account for funnel user ${funnelUsername} -> ${username}`);\n }\n\n try {\n const signupRequest = await this.getRemoteCouchRootDB().signUp(username, password);\n\n if (signupRequest.ok) {\n log(`CREATEACCOUNT: Successfully created account for ${username}`);\n\n // Log out any existing session\n try {\n const logoutResult = await this.getRemoteCouchRootDB().logOut();\n log(`CREATEACCOUNT: logged out: ${logoutResult.ok}`);\n } catch {\n // Ignore logout errors - might not be logged in\n }\n\n // Log in as the new user\n const loginResult = await this.getRemoteCouchRootDB().logIn(username, password);\n log(`CREATEACCOUNT: logged in as new user: ${loginResult.ok}`);\n\n if (loginResult.ok) {\n // Migrate funnel account data if applicable\n if (isFunnelAccount) {\n logger.info(`Migrating data from funnel account ${funnelUsername} to ${username}`);\n const migrationResult = await this.migrateFunnelData(funnelUsername, username);\n if (!migrationResult.success) {\n logger.warn(`Migration failed: ${migrationResult.error}`);\n // Continue anyway - don't block account creation\n }\n }\n\n return {\n status: Status.ok,\n error: undefined,\n };\n } else {\n return {\n status: Status.error,\n error: 'Failed to log in after account creation',\n };\n }\n } else {\n logger.warn(`Signup not OK: ${JSON.stringify(signupRequest)}`);\n return {\n status: Status.error,\n error: 'Account creation failed',\n };\n }\n } catch (e: any) {\n if (e.reason === 'Document update conflict.') {\n return {\n status: Status.error,\n error: 'This username is taken!',\n };\n }\n logger.error(`Error on signup: ${JSON.stringify(e)}`);\n return {\n status: Status.error,\n error: e.message || 'Unknown error during account creation',\n };\n }\n }\n\n async authenticate(username: string, password: string): Promise<AuthenticationResult> {\n try {\n const loginResult = await this.getRemoteCouchRootDB().logIn(username, password);\n\n if (loginResult.ok) {\n log(`Successfully logged in as ${username}`);\n return {\n ok: true,\n };\n } else {\n log(`Login failed for ${username}`);\n return {\n ok: false,\n error: 'Invalid username or password',\n };\n }\n } catch (error: any) {\n logger.error(`Authentication error for ${username}:`, error);\n return {\n ok: false,\n error: error.message || 'Authentication failed',\n };\n }\n }\n\n async logout(): Promise<AuthenticationResult> {\n try {\n const result = await this.getRemoteCouchRootDB().logOut();\n return {\n ok: result.ok,\n error: result.ok ? undefined : 'Logout failed',\n };\n } catch (error: any) {\n logger.error('Logout error:', error);\n return {\n ok: false,\n error: error.message || 'Logout failed',\n };\n }\n }\n\n async getCurrentUsername(): Promise<string> {\n logger.log('[funnel] CouchDBSyncStrategy.getCurrentUsername() called');\n try {\n const loggedInUsername = await getLoggedInUsername();\n logger.log('[funnel] getLoggedInUsername() returned:', loggedInUsername);\n return loggedInUsername;\n } catch (e) {\n // Not logged in - return unique guest account\n logger.log('[funnel] getLoggedInUsername() failed, calling accomodateGuest()');\n logger.log('[funnel] Error was:', e);\n const guestInfo = accomodateGuest();\n logger.log('[funnel] accomodateGuest() returned:', guestInfo);\n return guestInfo.username;\n }\n }\n\n /**\n * Migrate data from funnel account to real account\n */\n private async migrateFunnelData(\n oldUsername: string,\n newUsername: string\n ): Promise<{ success: boolean; error?: string }> {\n try {\n logger.info(`Starting data migration from ${oldUsername} to ${newUsername}`);\n\n const oldLocalDB = getLocalUserDB(oldUsername);\n const newLocalDB = getLocalUserDB(newUsername);\n\n // Get all docs from funnel account\n const allDocs = await oldLocalDB.allDocs({ include_docs: true });\n\n logger.info(`Found ${allDocs.rows.length} documents in funnel account`);\n\n // Filter out design docs and prepare for migration\n const docsToMigrate = allDocs.rows\n .filter(row => !row.id.startsWith('_design/'))\n .map(row => ({\n ...row.doc,\n _rev: undefined, // Remove rev to insert as new\n }));\n\n if (docsToMigrate.length > 0) {\n await newLocalDB.bulkDocs(docsToMigrate);\n logger.info(`Successfully migrated ${docsToMigrate.length} documents from ${oldUsername} to ${newUsername}`);\n } else {\n logger.info('No documents to migrate from funnel account');\n }\n\n return { success: true };\n } catch (error) {\n logger.error('Migration failed:', error);\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error'\n };\n }\n }\n\n /**\n * Get remote CouchDB root database for authentication operations\n */\n private getRemoteCouchRootDB(): PouchDB.Database {\n const remoteStr: string =\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + 'skuilder';\n\n try {\n return new pouch(remoteStr, {\n skip_setup: true,\n });\n } catch (error) {\n logger.error('Failed to initialize remote CouchDB connection:', error);\n throw new Error(`Failed to initialize CouchDB: ${JSON.stringify(error)}`);\n }\n }\n\n /**\n * Get remote user database for a specific user\n */\n private getUserDB(username: string): PouchDB.Database {\n const guestAccount: boolean = false;\n\n const hexName = hexEncode(username);\n const dbName = `userdb-${hexName}`;\n log(`Fetching user database: ${dbName} (${username})`);\n\n // Odd construction here the result of a bug in the\n // interaction between pouch, pouch-auth.\n // see: https://github.com/pouchdb-community/pouchdb-authentication/issues/239\n const ret = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n\n if (guestAccount) {\n updateGuestAccountExpirationDate(ret);\n }\n\n return ret;\n }\n}\n","import { ENV } from '@db/factory';\nimport {\n DocType,\n DocTypePrefixes,\n GuestUsername,\n log,\n SkuilderCourseData,\n} from '../../core/types/types-legacy';\nimport fetch from 'cross-fetch';\n// import { getCurrentUser } from '../../stores/useAuthStore';\nimport moment, { Moment } from 'moment';\nimport { logger } from '@db/util/logger';\n\nimport pouch from './pouchdb-setup';\n\nimport { ScheduledCard } from '@db/core/types/user';\nimport process from 'process';\n\nconst isBrowser = typeof window !== 'undefined';\n\nif (isBrowser) {\n (window as any).process = process; // required as a fix for pouchdb - see #18\n}\n\nconst expiryDocID: string = 'GuestAccountExpirationDate';\n\nconst GUEST_LOCAL_DB = `userdb-${GuestUsername}`;\nexport const localUserDB: PouchDB.Database = new pouch(GUEST_LOCAL_DB);\n\nexport function hexEncode(str: string): string {\n let hex: string;\n let returnStr: string = '';\n\n for (let i = 0; i < str.length; i++) {\n hex = str.charCodeAt(i).toString(16);\n returnStr += ('000' + hex).slice(3);\n }\n\n return returnStr;\n}\nconst pouchDBincludeCredentialsConfig: PouchDB.Configuration.RemoteDatabaseConfiguration = {\n fetch(url: string | Request, opts: RequestInit): Promise<Response> {\n opts.credentials = 'include';\n\n return (pouch as any).fetch(url, opts);\n },\n} as PouchDB.Configuration.RemoteDatabaseConfiguration;\n\n/**\n * Creates PouchDB configuration with appropriate authentication method\n * - Uses HTTP Basic Auth when credentials are available (Node.js/MCP)\n * - Falls back to cookie auth for browser environments\n */\nexport function createPouchDBConfig(): PouchDB.Configuration.RemoteDatabaseConfiguration {\n // Check if running in Node.js with explicit credentials\n const hasExplicitCredentials = ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD;\n const isNodeEnvironment = typeof window === 'undefined';\n \n if (hasExplicitCredentials && isNodeEnvironment) {\n // Use HTTP Basic Auth for Node.js environments (MCP server)\n return {\n fetch(url: string | Request, opts: RequestInit = {}): Promise<Response> {\n const basicAuth = btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`);\n const headers = new Headers(opts.headers || {});\n headers.set('Authorization', `Basic ${basicAuth}`);\n \n const newOpts = {\n ...opts,\n headers: headers\n };\n \n return (pouch as any).fetch(url, newOpts);\n }\n } as PouchDB.Configuration.RemoteDatabaseConfiguration;\n }\n \n // Use cookie-based auth for browser environments or when no explicit credentials\n return pouchDBincludeCredentialsConfig;\n}\n\nfunction getCouchDB(dbName: string): PouchDB.Database {\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n\nexport function getCourseDB(courseID: string): PouchDB.Database {\n // todo: keep a cache of opened courseDBs? need to benchmark this somehow\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + 'coursedb-' + courseID,\n createPouchDBConfig()\n );\n}\n\nexport async function getLatestVersion() {\n try {\n const docs = await getCouchDB('version').allDocs({\n descending: true,\n limit: 1,\n });\n if (docs && docs.rows && docs.rows[0]) {\n return docs.rows[0].id;\n } else {\n return '0.0.0';\n }\n } catch {\n return '-1';\n }\n}\n\n/**\n * Checks the remote couchdb to see if a given username is available\n * @param username The username to be checked\n */\nexport async function usernameIsAvailable(username: string): Promise<boolean> {\n log(`Checking availability of ${username}`);\n \n // Legacy XMLHttpRequest implementation (browser sync)\n // const req = new XMLHttpRequest();\n // const url = ENV.COUCHDB_SERVER_URL + 'userdb-' + hexEncode(username);\n // req.open('HEAD', url, false);\n // req.send();\n // return req.status === 404;\n \n try {\n const url = ENV.COUCHDB_SERVER_URL + 'userdb-' + hexEncode(username);\n const response = await fetch(url, { method: 'HEAD' });\n return response.status === 404;\n } catch (error) {\n log(`Error checking username availability: ${error}`);\n return false;\n }\n}\n\nexport function updateGuestAccountExpirationDate(guestDB: PouchDB.Database<object>) {\n const currentTime = moment.utc();\n const expirationDate: string = currentTime.add(2, 'months').toISOString();\n\n void guestDB\n .get(expiryDocID)\n .then((doc) => {\n return guestDB.put({\n _id: expiryDocID,\n _rev: doc._rev,\n date: expirationDate,\n });\n })\n .catch(() => {\n return guestDB.put({\n _id: expiryDocID,\n date: expirationDate,\n });\n });\n}\n\nexport function getCourseDocs<T extends SkuilderCourseData>(\n courseID: string,\n docIDs: string[],\n options: PouchDB.Core.AllDocsOptions = {}\n) {\n return getCourseDB(courseID).allDocs<T>({\n ...options,\n keys: docIDs,\n });\n}\n\nexport function getCourseDoc<T extends SkuilderCourseData>(\n courseID: string,\n docID: PouchDB.Core.DocumentId,\n options: PouchDB.Core.GetOptions = {}\n): Promise<T> {\n return getCourseDB(courseID).get<T>(docID, options);\n}\n\n/**\n * Returns *all* cards from the parameter courses, in\n * 'qualified' card format (\"courseid-cardid\")\n *\n * @param courseIDs A list of all course_ids to get cards from\n */\nexport async function getRandomCards(courseIDs: string[]) {\n if (courseIDs.length === 0) {\n throw new Error(`getRandomCards:\\n\\tAttempted to get all cards from no courses!`);\n } else {\n const courseResults = await Promise.all(\n courseIDs.map((course) => {\n return getCourseDB(course).find({\n selector: {\n docType: DocType.CARD,\n },\n limit: 1000,\n });\n })\n );\n\n const ret: string[] = [];\n courseResults.forEach((courseCards, index) => {\n courseCards.docs.forEach((doc) => {\n ret.push(`${courseIDs[index]}-${doc._id}`);\n });\n });\n\n return ret;\n }\n}\n\nexport const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';\n\nexport function getCouchUserDB(username: string): PouchDB.Database {\n const guestAccount: boolean = false;\n // console.log(`Getting user db: ${username}`);\n\n const hexName = hexEncode(username);\n const dbName = `userdb-${hexName}`;\n log(`Fetching user database: ${dbName} (${username})`);\n\n // odd construction here the result of a bug in the\n // interaction between pouch, pouch-auth.\n // see: https://github.com/pouchdb-community/pouchdb-authentication/issues/239\n const ret = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n if (guestAccount) {\n updateGuestAccountExpirationDate(ret);\n }\n\n return ret;\n}\n\nexport function scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: PouchDB.Core.DocumentId;\n time: Moment;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n}) {\n const now = moment.utc();\n logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);\n void getCouchUserDB(review.user).put<ScheduledCard>({\n _id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),\n cardId: review.card_id,\n reviewTime: review.time.toISOString(),\n courseId: review.course_id,\n scheduledAt: now.toISOString(),\n scheduledFor: review.scheduledFor,\n schedulingAgentId: review.schedulingAgentId,\n });\n}\n\nexport function filterAllDocsByPrefix<T>(\n db: PouchDB.Database,\n prefix: string,\n opts?: PouchDB.Core.AllDocsOptions\n) {\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const options: PouchDB.Core.AllDocsWithinRangeOptions = {\n startkey: prefix,\n endkey: prefix + '\\ufff0',\n include_docs: true,\n };\n\n if (opts) {\n Object.assign(options, opts);\n }\n return db.allDocs<T>(options);\n}\n\nexport function getStartAndEndKeys(key: string): {\n startkey: string;\n endkey: string;\n} {\n return {\n startkey: key,\n endkey: key + '\\ufff0',\n };\n}\n\n//////////////////////\n// Package exports\n//////////////////////\n\nexport * from '../../core/interfaces/contentSource';\nexport * from './adminDB';\nexport * from './classroomDB';\nexport * from './courseAPI';\nexport * from './courseDB';\nexport * from './CouchDBSyncStrategy';\n","import { DocType, DocTypePrefixes } from '@db/core';\nimport { getCardHistoryID } from '@db/core/util';\nimport { CourseElo, Status } from '@vue-skuilder/common';\nimport moment, { Moment } from 'moment';\nimport { GuestUsername } from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\n\nimport {\n ClassroomRegistrationDoc,\n UserCourseSetting,\n UserDBInterface,\n UsrCrsDataInterface,\n} from '@db/core';\nimport {\n ActivityRecord,\n CourseRegistration,\n CourseRegistrationDoc,\n ScheduledCard,\n UserConfig,\n} from '@db/core/types/user';\nimport { DocumentUpdater } from '@db/study';\nimport { CardHistory, CardRecord } from '../../core/types/types-legacy';\nimport type { SyncStrategy } from './SyncStrategy';\nimport {\n filterAllDocsByPrefix,\n getStartAndEndKeys,\n REVIEW_TIME_FORMAT,\n getLocalUserDB,\n scheduleCardReviewLocal,\n removeScheduledCardReviewLocal,\n} from './userDBHelpers';\nimport { PouchError } from '../couch/types';\nimport UpdateQueue, { Update } from '../couch/updateQueue';\nimport { UsrCrsData } from '../couch/user-course-relDB';\nimport { getCredentialledCourseConfig } from '../couch/index';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\n// logger.log(`Connecting to remote: ${remoteStr}`);\n\ninterface DesignDoc {\n _id: string;\n views: {\n [viewName: string]: {\n map: string; // String representation of the map function\n };\n };\n}\n\n/**\n * Base user database implementation that uses a pluggable sync strategy.\n * Handles local storage operations and delegates sync/remote operations to the strategy.\n */\nexport class BaseUser implements UserDBInterface, DocumentUpdater {\n private static _instance: BaseUser;\n private static _initialized: boolean = false;\n\n public static Dummy(syncStrategy: SyncStrategy): BaseUser {\n return new BaseUser('Me', syncStrategy);\n }\n\n static readonly DOC_IDS = {\n CONFIG: 'CONFIG',\n COURSE_REGISTRATIONS: 'CourseRegistrations',\n CLASSROOM_REGISTRATIONS: 'ClassroomRegistrations',\n };\n\n // private email: string;\n private _username: string;\n private syncStrategy: SyncStrategy;\n\n public getUsername(): string {\n return this._username;\n }\n\n public isLoggedIn(): boolean {\n return !this._username.startsWith(GuestUsername);\n }\n\n public remote(): PouchDB.Database {\n return this.remoteDB;\n }\n\n private localDB!: PouchDB.Database;\n private remoteDB!: PouchDB.Database;\n private writeDB!: PouchDB.Database; // Database to use for write operations (local-first approach)\n\n private updateQueue!: UpdateQueue;\n\n public async createAccount(\n username: string,\n password: string\n ): Promise<{\n status: Status;\n error: string;\n }> {\n if (!this.syncStrategy.canCreateAccount()) {\n throw new Error('Account creation not supported by current sync strategy');\n }\n\n if (!this._username.startsWith(GuestUsername)) {\n throw new Error(\n `Cannot create a new account while logged in:\nCurrently logged-in as ${this._username}.`\n );\n }\n\n const result = await this.syncStrategy.createAccount!(username, password);\n\n // If account creation was successful, update the username and reinitialize\n if (result.status === Status.ok) {\n log(`Account created successfully, updating username to ${username}`);\n this._username = username;\n try {\n localStorage.removeItem('sk-guest-uuid');\n } catch (e) {\n logger.warn('localStorage not available (Node.js environment):', e);\n }\n await this.init();\n }\n\n return {\n status: result.status,\n error: result.error || '',\n };\n }\n public async login(username: string, password: string) {\n if (!this.syncStrategy.canAuthenticate()) {\n throw new Error('Authentication not supported by current sync strategy');\n }\n\n if (!this._username.startsWith(GuestUsername) && this._username != username) {\n if (this._username != username) {\n throw new Error(`Cannot change accounts while logged in.\n Log out of account ${this.getUsername()} before logging in as ${username}.`);\n }\n logger.warn(`User ${this._username} is already logged in, but executing login again.`);\n }\n\n const loginResult = await this.syncStrategy.authenticate!(username, password);\n if (loginResult.ok) {\n log(`Logged in as ${username}`);\n this._username = username;\n try {\n localStorage.removeItem('sk-guest-uuid');\n } catch (e) {\n logger.warn('localStorage not available (Node.js environment):', e);\n }\n await this.init();\n }\n return loginResult;\n }\n\n public async resetUserData(): Promise<{ status: Status; error?: string }> {\n // Only allow reset for local-only sync strategies\n if (this.syncStrategy.canAuthenticate()) {\n return {\n status: Status.error,\n error:\n 'Reset user data is only available for local-only mode. Use logout instead for remote sync.',\n };\n }\n\n try {\n const localDB = getLocalUserDB(this._username);\n\n // Get all documents to identify user data to clear\n const allDocs = await localDB.allDocs({ include_docs: false });\n\n // Identify documents to delete (preserve authentication and user identity)\n const docsToDelete = allDocs.rows\n .filter((row) => {\n const id = row.id;\n // Delete user progress data but preserve core user documents\n return (\n id.startsWith(DocTypePrefixes[DocType.CARDRECORD]) || // Card interaction history\n id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD]) || // Scheduled reviews\n id === BaseUser.DOC_IDS.COURSE_REGISTRATIONS || // Course registrations\n id === BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS || // Classroom registrations\n id === BaseUser.DOC_IDS.CONFIG // User config\n );\n })\n .map((row) => ({ _id: row.id, _rev: row.value.rev, _deleted: true }));\n\n if (docsToDelete.length > 0) {\n await localDB.bulkDocs(docsToDelete);\n }\n\n // Reinitialize to create fresh default documents\n await this.init();\n\n return { status: Status.ok };\n } catch (error) {\n logger.error('Failed to reset user data:', error);\n return {\n status: Status.error,\n error: error instanceof Error ? error.message : 'Unknown error during reset',\n };\n }\n }\n\n public async logout() {\n if (!this.syncStrategy.canAuthenticate()) {\n // For strategies that don't support authentication, just switch to guest\n this._username = await this.syncStrategy.getCurrentUsername();\n await this.init();\n return { ok: true };\n }\n\n const ret = await this.syncStrategy.logout!();\n // return to 'guest' mode\n this._username = await this.syncStrategy.getCurrentUsername();\n await this.init();\n\n return ret;\n }\n\n public async get<T>(id: string): Promise<T & PouchDB.Core.RevisionIdMeta> {\n return this.localDB.get<T>(id);\n }\n\n public update<T extends PouchDB.Core.Document<object>>(id: string, update: Update<T>) {\n return this.updateQueue.update(id, update);\n }\n\n public async getCourseRegistrationsDoc(): Promise<\n CourseRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta\n > {\n logger.debug(`Fetching courseRegistrations for ${this.getUsername()}`);\n\n let ret;\n\n try {\n const regDoc = await this.localDB.get<CourseRegistrationDoc>(\n BaseUser.DOC_IDS.COURSE_REGISTRATIONS\n );\n return regDoc;\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n await this.localDB.put<CourseRegistrationDoc>({\n _id: BaseUser.DOC_IDS.COURSE_REGISTRATIONS,\n courses: [],\n studyWeight: {},\n });\n ret = await this.getCourseRegistrationsDoc();\n } else {\n throw new Error(\n `Unexpected error ${JSON.stringify(e)} in getOrCreateCourseRegistrationDoc...`\n );\n }\n }\n\n return ret;\n }\n\n public async getActiveCourses() {\n const reg = await this.getCourseRegistrationsDoc();\n return reg.courses.filter((c) => {\n return c.status === undefined || c.status === 'active';\n });\n }\n\n /**\n * Returns a promise of the card IDs that the user has\n * a scheduled review for.\n *\n */\n public async getActiveCards() {\n const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);\n\n const reviews = await this.remoteDB.allDocs<ScheduledCard>({\n startkey: keys.startkey,\n endkey: keys.endkey,\n include_docs: true,\n });\n\n return reviews.rows.map((r) => {\n return {\n courseID: r.doc!.courseId,\n cardID: r.doc!.cardId,\n };\n });\n }\n\n public async getActivityRecords(): Promise<ActivityRecord[]> {\n try {\n const hist = await this.getHistory();\n\n const allRecords: ActivityRecord[] = [];\n if (!Array.isArray(hist)) {\n logger.error('getHistory did not return an array:', hist);\n return allRecords;\n }\n\n // Sample the first few records to understand structure\n let sampleCount = 0;\n\n for (let i = 0; i < hist.length; i++) {\n try {\n if (hist[i] && Array.isArray(hist[i]!.records)) {\n hist[i]!.records.forEach((record: CardRecord) => {\n try {\n // Skip this record if timeStamp is missing\n if (!record.timeStamp) {\n return;\n }\n\n let timeStamp;\n\n // Handle different timestamp formats\n if (typeof record.timeStamp === 'object') {\n // It's likely a Moment object\n if (typeof record.timeStamp.toDate === 'function') {\n // It's definitely a Moment object\n timeStamp = record.timeStamp.toISOString();\n } else if (record.timeStamp instanceof Date) {\n // It's a Date object\n timeStamp = record.timeStamp.toISOString();\n } else {\n // Log a sample of unknown object types, but don't flood logger\n if (sampleCount < 3) {\n logger.warn('Unknown timestamp object type:', record.timeStamp);\n sampleCount++;\n }\n return;\n }\n } else if (typeof record.timeStamp === 'string') {\n // It's already a string, but make sure it's a valid date\n const date = new Date(record.timeStamp);\n if (isNaN(date.getTime())) {\n return; // Invalid date string\n }\n timeStamp = record.timeStamp;\n } else if (typeof record.timeStamp === 'number') {\n // Assume it's a Unix timestamp (milliseconds since epoch)\n timeStamp = new Date(record.timeStamp).toISOString();\n } else {\n // Unknown type, skip\n return;\n }\n\n allRecords.push({\n timeStamp,\n courseID: record.courseID || 'unknown',\n cardID: record.cardID || 'unknown',\n timeSpent: record.timeSpent || 0,\n type: 'card_view',\n });\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (err) {\n // Silently skip problematic records to avoid flooding logs\n }\n });\n }\n } catch (err) {\n logger.error('Error processing history item:', err);\n }\n }\n\n logger.debug(`Found ${allRecords.length} activity records`);\n return allRecords;\n } catch (err) {\n logger.error('Error in getActivityRecords:', err);\n return [];\n }\n }\n\n private async getReviewstoDate(targetDate: Moment, course_id?: string) {\n const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);\n\n const reviews = await this.remoteDB.allDocs<ScheduledCard>({\n startkey: keys.startkey,\n endkey: keys.endkey,\n include_docs: true,\n });\n\n log(\n `Fetching ${this._username}'s scheduled reviews${\n course_id ? ` for course ${course_id}` : ''\n }.`\n );\n return reviews.rows\n .filter((r) => {\n if (r.id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD])) {\n const date = moment.utc(\n r.id.substr(DocTypePrefixes[DocType.SCHEDULED_CARD].length),\n REVIEW_TIME_FORMAT\n );\n if (targetDate.isAfter(date)) {\n if (course_id === undefined || r.doc!.courseId === course_id) {\n return true;\n }\n }\n }\n })\n .map((r) => r.doc!);\n }\n\n public async getReviewsForcast(daysCount: number) {\n const time = moment.utc().add(daysCount, 'days');\n return this.getReviewstoDate(time);\n }\n\n public async getPendingReviews(course_id?: string) {\n const now = moment.utc();\n return this.getReviewstoDate(now, course_id);\n }\n\n public async getScheduledReviewCount(course_id: string): Promise<number> {\n return (await this.getPendingReviews(course_id)).length;\n }\n\n public async getRegisteredCourses() {\n const regDoc = await this.getCourseRegistrationsDoc();\n return regDoc.courses.filter((c) => {\n return !c.status || c.status === 'active' || c.status === 'maintenance-mode';\n });\n }\n\n public async getCourseRegDoc(courseID: string) {\n const regDocs = await this.getCourseRegistrationsDoc();\n const ret = regDocs.courses.find((c) => c.courseID === courseID);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Course registration not found for course ID: ${courseID}`);\n }\n }\n\n public async registerForCourse(course_id: string, previewMode: boolean = false) {\n return this.getCourseRegistrationsDoc()\n .then((doc: CourseRegistrationDoc) => {\n const status = previewMode ? 'preview' : 'active';\n logger.debug(`Registering for ${course_id} with status: ${status}`);\n\n const regItem: CourseRegistration = {\n status: status,\n courseID: course_id,\n user: true,\n admin: false,\n moderator: false,\n elo: {\n global: {\n score: 1000,\n count: 0,\n },\n tags: {},\n misc: {},\n },\n };\n\n if (\n doc.courses.filter((course) => {\n return course.courseID === regItem.courseID;\n }).length === 0\n ) {\n log(`It's a new course registration!`);\n doc.courses.push(regItem);\n doc.studyWeight[course_id] = 1;\n } else {\n doc.courses.forEach((c) => {\n log(`Found the previously registered course!`);\n if (c.courseID === course_id) {\n c.status = status;\n }\n });\n }\n\n return this.localDB.put<CourseRegistrationDoc>(doc);\n })\n .catch((e) => {\n log(`Registration failed because of: ${JSON.stringify(e)}`);\n throw e;\n });\n }\n public async dropCourse(course_id: string, dropStatus: CourseRegistration['status'] = 'dropped') {\n return this.getCourseRegistrationsDoc().then((doc) => {\n let index: number = -1;\n for (let i = 0; i < doc.courses.length; i++) {\n if (doc.courses[i].courseID === course_id) {\n index = i;\n }\n }\n\n if (index !== -1) {\n // remove from the relative-weighting of course study\n delete doc.studyWeight[course_id];\n // set drop status\n doc.courses[index].status = dropStatus;\n } else {\n throw new Error(\n `User ${this.getUsername()} is not currently registered for course ${course_id}`\n );\n }\n\n return this.localDB.put<CourseRegistrationDoc>(doc);\n });\n }\n\n public async getCourseInterface(courseId: string): Promise<UsrCrsDataInterface> {\n return new UsrCrsData(this, courseId);\n }\n\n public async getUserEditableCourses() {\n let courseIDs: string[] = [];\n\n const registeredCourses = await this.getCourseRegistrationsDoc();\n\n courseIDs = courseIDs.concat(\n registeredCourses.courses.map((course) => {\n return course.courseID;\n })\n );\n\n const cfgs = await Promise.all(\n courseIDs.map(async (id) => {\n return await getCredentialledCourseConfig(id);\n })\n );\n return cfgs;\n }\n\n public async getConfig(): Promise<UserConfig & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n const defaultConfig: PouchDB.Core.Document<UserConfig> = {\n _id: BaseUser.DOC_IDS.CONFIG,\n darkMode: false,\n likesConfetti: false,\n sessionTimeLimit: 5,\n };\n\n try {\n const cfg = await this.localDB.get<UserConfig>(BaseUser.DOC_IDS.CONFIG);\n logger.debug('Raw config from DB:', cfg);\n\n return cfg;\n } catch (e) {\n const err = e as PouchError;\n if (err.name && err.name === 'not_found') {\n await this.localDB.put<UserConfig>(defaultConfig);\n return this.getConfig();\n } else {\n logger.error(`Error setting user default config:`, e);\n throw new Error(`Error returning the user's configuration: ${JSON.stringify(e)}`);\n }\n }\n }\n\n public async setConfig(items: Partial<UserConfig>) {\n logger.debug(`Setting Config items ${JSON.stringify(items)}`);\n\n const c = await this.getConfig();\n const put = await this.localDB.put<UserConfig>({\n ...c,\n ...items,\n });\n\n if (put.ok) {\n logger.debug(`Config items set: ${JSON.stringify(items)}`);\n } else {\n logger.error(`Error setting config items: ${JSON.stringify(put)}`);\n }\n }\n\n /**\n *\n * This function should be called *only* by the pouchdb datalayer provider\n * auth store.\n *\n *\n * Anyone else seeking the current user should use the auth store's\n * exported `getCurrentUser` method.\n *\n */\n public static async instance(syncStrategy: SyncStrategy, username?: string): Promise<BaseUser> {\n if (username) {\n BaseUser._instance = new BaseUser(username, syncStrategy);\n await BaseUser._instance.init();\n return BaseUser._instance;\n } else if (BaseUser._instance && BaseUser._initialized) {\n // log(`USER.instance() returning user ${BaseUser._instance._username}`);\n return BaseUser._instance;\n } else if (BaseUser._instance) {\n return new Promise((resolve) => {\n (function waitForUser() {\n if (BaseUser._initialized) {\n return resolve(BaseUser._instance);\n } else {\n setTimeout(waitForUser, 50);\n }\n })();\n });\n } else {\n const guestUsername = await syncStrategy.getCurrentUsername();\n BaseUser._instance = new BaseUser(guestUsername, syncStrategy);\n await BaseUser._instance.init();\n return BaseUser._instance;\n }\n }\n\n private constructor(username: string, syncStrategy: SyncStrategy) {\n BaseUser._initialized = false;\n this._username = username;\n this.syncStrategy = syncStrategy;\n this.setDBandQ();\n }\n\n private setDBandQ() {\n this.localDB = getLocalUserDB(this._username);\n this.remoteDB = this.syncStrategy.setupRemoteDB(this._username);\n // writeDB follows local-first pattern: static mode writes to local, CouchDB writes to remote/local as appropriate\n this.writeDB = this.syncStrategy.getWriteDB\n ? this.syncStrategy.getWriteDB(this._username)\n : this.localDB;\n this.updateQueue = new UpdateQueue(this.localDB, this.writeDB);\n }\n\n private async init() {\n BaseUser._initialized = false;\n\n // Skip admin user\n if (this._username === 'admin') {\n BaseUser._initialized = true;\n return;\n }\n\n this.setDBandQ();\n\n this.syncStrategy.startSync(this.localDB, this.remoteDB);\n this.applyDesignDocs().catch((error) => {\n log(`Error in applyDesignDocs background task: ${error}`);\n if (error && typeof error === 'object') {\n log(`Full error details in applyDesignDocs: ${JSON.stringify(error)}`);\n }\n });\n this.deduplicateReviews().catch((error) => {\n log(`Error in deduplicateReviews background task: ${error}`);\n if (error && typeof error === 'object') {\n log(`Full error details in background task: ${JSON.stringify(error)}`);\n }\n });\n BaseUser._initialized = true;\n }\n\n private static designDocs: DesignDoc[] = [\n {\n _id: '_design/reviewCards',\n views: {\n reviewCards: {\n map: `function (doc) {\n if (doc._id && doc._id.indexOf('card_review') === 0 && doc.courseId && doc.cardId) {\n emit(doc._id, doc.courseId + '-' + doc.cardId);\n }\n }`,\n },\n },\n },\n ];\n\n private async applyDesignDocs() {\n log(`Starting applyDesignDocs for user: ${this._username}`);\n log(`Remote DB name: ${this.remoteDB.name || 'unknown'}`);\n\n if (this._username === 'admin') {\n // Skip admin user\n log('Skipping design docs for admin user');\n return;\n }\n\n log(`Applying ${BaseUser.designDocs.length} design docs`);\n for (const doc of BaseUser.designDocs) {\n log(`Applying design doc: ${doc._id}`);\n try {\n // Try to get existing doc\n try {\n const existingDoc = await this.remoteDB.get(doc._id);\n // Update existing doc\n await this.remoteDB.put({\n ...doc,\n _rev: existingDoc._rev,\n });\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'not_found') {\n // Create new doc\n await this.remoteDB.put(doc);\n } else {\n throw e; // Re-throw unexpected errors\n }\n }\n } catch (error: unknown) {\n if ((error as any).name && (error as any).name === 'conflict') {\n logger.warn(`Design doc ${doc._id} update conflict - will retry`);\n // Wait a bit and try again\n await new Promise((resolve) => setTimeout(resolve, 1000));\n await this.applyDesignDoc(doc); // Recursive retry\n } else {\n logger.error(`Failed to apply design doc ${doc._id}:`, error);\n throw error;\n }\n }\n }\n }\n\n // Helper method for single doc update with retry\n private async applyDesignDoc(doc: DesignDoc, retries = 3): Promise<void> {\n try {\n const existingDoc = await this.remoteDB.get(doc._id);\n await this.remoteDB.put({\n ...doc,\n _rev: existingDoc._rev,\n });\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'conflict' && retries > 0) {\n await new Promise((resolve) => setTimeout(resolve, 1000));\n return this.applyDesignDoc(doc, retries - 1);\n }\n throw e;\n }\n }\n\n /**\n * Logs a record of the user's interaction with the card and returns the card's\n * up-to-date history\n *\n * // [ ] #db-refactor extract to a smaller scope - eg, UserStudySession\n *\n * @param record the recent recorded interaction between user and card\n * @returns The updated state of the card's CardHistory data\n */\n\n public async putCardRecord<T extends CardRecord>(\n record: T\n ): Promise<CardHistory<CardRecord> & PouchDB.Core.RevisionIdMeta> {\n const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);\n // stringify the current record to make it writable to couchdb\n record.timeStamp = moment.utc(record.timeStamp).toString() as unknown as Moment;\n\n try {\n const cardHistory = await this.update<CardHistory<T>>(\n cardHistoryID,\n function (h: CardHistory<T>) {\n h.records.push(record);\n h.bestInterval = h.bestInterval || 0;\n h.lapses = h.lapses || 0;\n h.streak = h.streak || 0;\n return h;\n }\n );\n\n // Convert timestamps to moment objects\n cardHistory.records = cardHistory.records.map<T>((record) => {\n const ret: T = {\n ...(record as object),\n } as T;\n ret.timeStamp = moment.utc(record.timeStamp);\n return ret;\n });\n return cardHistory;\n } catch (e) {\n const reason = e as PouchError;\n if (reason.status === 404) {\n try {\n const initCardHistory: CardHistory<T> = {\n _id: cardHistoryID,\n cardID: record.cardID,\n courseID: record.courseID,\n records: [record],\n lapses: 0,\n streak: 0,\n bestInterval: 0,\n };\n const putResult = await this.writeDB.put<CardHistory<T>>(initCardHistory);\n return { ...initCardHistory, _rev: putResult.rev };\n } catch (creationError) {\n throw new Error(\n `Failed to create CardHistory for ${cardHistoryID}. Reason: ${creationError}`\n );\n }\n } else {\n throw new Error(`putCardRecord failed because of:\n name:${reason.name}\n error: ${reason.error}\n message: ${reason.message}`);\n }\n }\n }\n\n private async deduplicateReviews() {\n try {\n log('Starting deduplication of scheduled reviews...');\n log(`Remote DB name: ${this.remoteDB.name || 'unknown'}`);\n log(`Write DB name: ${this.writeDB.name || 'unknown'}`);\n /**\n * Maps the qualified-id of a scheduled review card to\n * the docId of the same scheduled review.\n *\n * EG: {\n * courseId-cardId: 'card_review_2021-06--17:12:165'\n * }\n */\n const reviewsMap: { [index: string]: string } = {};\n const duplicateDocIds: string[] = [];\n\n log(\n `Attempting to query remoteDB for reviewCards/reviewCards. Database: ${this.remoteDB.name || 'unknown'}`\n );\n const scheduledReviews = await this.remoteDB.query<{\n id: string;\n value: string;\n }>('reviewCards/reviewCards');\n\n log(`Found ${scheduledReviews.rows.length} scheduled reviews to process`);\n\n // First pass: identify duplicates\n scheduledReviews.rows.forEach((r) => {\n const qualifiedCardId = r.value; // courseId-cardId\n const docId = r.key; // card_review_2021-06--17:12:165\n\n if (reviewsMap[qualifiedCardId]) {\n // this card is scheduled more than once! mark the earlier one for deletion\n log(`Found duplicate scheduled review for card: ${qualifiedCardId}`);\n log(\n `Marking earlier review ${reviewsMap[qualifiedCardId]} for deletion, keeping ${docId}`\n );\n duplicateDocIds.push(reviewsMap[qualifiedCardId]);\n // replace with the later-dated scheduled review\n reviewsMap[qualifiedCardId] = docId;\n } else {\n // note that this card is scheduled for review\n reviewsMap[qualifiedCardId] = docId;\n }\n });\n\n // Second pass: remove duplicates\n if (duplicateDocIds.length > 0) {\n log(`Removing ${duplicateDocIds.length} duplicate reviews...`);\n const deletePromises = duplicateDocIds.map(async (docId) => {\n try {\n const doc = await this.remoteDB.get(docId);\n await this.writeDB.remove(doc);\n log(`Successfully removed duplicate review: ${docId}`);\n } catch (error) {\n log(`Failed to remove duplicate review ${docId}: ${error}`);\n }\n });\n\n await Promise.all(deletePromises);\n log(`Deduplication complete. Processed ${duplicateDocIds.length} duplicates`);\n } else {\n log('No duplicate reviews found');\n }\n } catch (error) {\n log(`Error during review deduplication: ${error}`);\n if (error && typeof error === 'object' && 'status' in error && error.status === 404) {\n log(\n `Database not found (404) during review deduplication. Database: ${this.remoteDB.name || 'unknown'}`\n );\n log(\n `This might indicate the user database doesn't exist or the reviewCards view isn't available`\n );\n }\n // Log full error details for debugging\n if (error && typeof error === 'object') {\n log(`Full error details: ${JSON.stringify(error)}`);\n }\n }\n }\n\n /**\n * Returns a promise of the card IDs that the user has\n * encountered in the past.\n *\n * @param course_id optional specification of individual course\n */\n async getSeenCards(course_id?: string) {\n let prefix = DocTypePrefixes[DocType.CARDRECORD];\n if (course_id) {\n prefix += course_id;\n }\n const docs = await filterAllDocsByPrefix(this.localDB, prefix, {\n include_docs: false,\n });\n // const docs = await this.localDB.allDocs({});\n const ret: PouchDB.Core.DocumentId[] = [];\n docs.rows.forEach((row) => {\n if (row.id.startsWith(DocTypePrefixes[DocType.CARDRECORD])) {\n ret.push(row.id.substr(DocTypePrefixes[DocType.CARDRECORD].length));\n }\n });\n return ret;\n }\n\n /**\n *\n * @returns A promise of the cards that the user has seen in the past.\n */\n async getHistory() {\n const cards = await filterAllDocsByPrefix<CardHistory<CardRecord>>(\n this.remoteDB,\n DocTypePrefixes[DocType.CARDRECORD],\n {\n include_docs: true,\n attachments: false,\n }\n );\n return cards.rows.map((r) => r.doc);\n }\n\n async updateCourseSettings(course_id: string, settings: UserCourseSetting[]) {\n void this.getCourseRegistrationsDoc().then((doc) => {\n const crs = doc.courses.find((c) => c.courseID === course_id);\n if (crs) {\n if (crs.settings === null || crs.settings === undefined) {\n crs.settings = {};\n }\n settings.forEach((setting) => {\n crs!.settings![setting.key] = setting.value;\n });\n }\n\n return this.localDB.put(doc);\n });\n }\n async getCourseSettings(course_id: string) {\n const regDoc = await this.getCourseRegistrationsDoc();\n const crsDoc = regDoc.courses.find((c) => c.courseID === course_id);\n\n if (crsDoc) {\n return crsDoc.settings;\n } else {\n throw new Error(`getCourseSettings Failed:\n User is not registered for course ${course_id}`);\n }\n }\n\n private async getOrCreateClassroomRegistrationsDoc(): Promise<\n ClassroomRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta\n > {\n let ret;\n\n try {\n ret = await this.remoteDB.get<ClassroomRegistrationDoc>(\n BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS\n );\n } catch (e) {\n const err = e as PouchError;\n\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await this.writeDB.put<ClassroomRegistrationDoc>({\n _id: BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS,\n registrations: [],\n });\n ret = await this.getOrCreateClassroomRegistrationsDoc();\n } else {\n // Properly serialize error information\n const errorDetails = {\n name: err.name,\n status: err.status,\n message: err.message,\n reason: err.reason,\n error: err.error,\n };\n\n logger.error(\n 'Database error in getOrCreateClassroomRegistrationsDoc (private method):',\n errorDetails\n );\n\n throw new Error(\n `Database error accessing classroom registrations: ${err.message || err.name || 'Unknown error'} (status: ${err.status})`\n );\n }\n }\n\n logger.debug(`Returning classroom registrations doc: ${JSON.stringify(ret)}`);\n return ret;\n }\n\n /**\n * Retrieves the list of active classroom IDs where the user is registered as a student.\n *\n * @returns Promise<string[]> - Array of classroom IDs, or empty array if classroom\n * registration document is unavailable due to database errors\n *\n * @description This method gracefully handles database connectivity issues by returning\n * an empty array when the classroom registrations document cannot be accessed.\n * This ensures that users can still access other application features even\n * when classroom functionality is temporarily unavailable.\n */\n public async getActiveClasses(): Promise<string[]> {\n try {\n return (await this.getOrCreateClassroomRegistrationsDoc()).registrations\n .filter((c) => c.registeredAs === 'student')\n .map((c) => c.classID);\n } catch (error) {\n logger.warn(\n 'Failed to load classroom registrations, continuing without classroom data:',\n error\n );\n // Return empty array so user can still access other features\n return [];\n }\n }\n\n public async scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: PouchDB.Core.DocumentId;\n time: Moment;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n }) {\n return scheduleCardReviewLocal(this.writeDB, review);\n }\n public async removeScheduledCardReview(reviewId: string): Promise<void> {\n return removeScheduledCardReviewLocal(this.writeDB, reviewId);\n }\n\n public async registerForClassroom(\n _classId: string,\n _registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n ): Promise<PouchDB.Core.Response> {\n return registerUserForClassroom(this._username, _classId, _registerAs);\n }\n\n public async dropFromClassroom(classId: string): Promise<PouchDB.Core.Response> {\n return dropUserFromClassroom(this._username, classId);\n }\n public async getUserClassrooms(): Promise<ClassroomRegistrationDoc> {\n return getUserClassrooms(this._username);\n }\n\n public async updateUserElo(courseId: string, elo: CourseElo): Promise<PouchDB.Core.Response> {\n return updateUserElo(this._username, courseId, elo);\n }\n}\n\nexport function accomodateGuest(): {\n username: string;\n firstVisit: boolean;\n} {\n logger.log('[funnel] accomodateGuest() called');\n\n // Check if localStorage is available (browser environment)\n if (typeof localStorage === 'undefined') {\n logger.log('[funnel] localStorage not available (Node.js environment), returning default guest');\n return {\n username: GuestUsername + 'nodejs-test',\n firstVisit: true,\n };\n }\n\n const dbUUID = 'sk-guest-uuid';\n let firstVisit: boolean;\n\n const existingUUID = localStorage.getItem(dbUUID);\n logger.log('[funnel] Checking localStorage for key:', dbUUID);\n logger.log('[funnel] Existing UUID value:', existingUUID);\n logger.log('[funnel] existingUUID !== null:', existingUUID !== null);\n\n if (existingUUID !== null) {\n firstVisit = false;\n logger.log(`[funnel] Returning guest ${existingUUID} \"logging in\".`);\n } else {\n firstVisit = true;\n logger.log('[funnel] No existing UUID, generating new one...');\n const uuid = generateUUID();\n logger.log('[funnel] Generated UUID:', uuid);\n logger.log('[funnel] UUID length:', uuid.length);\n\n try {\n localStorage.setItem(dbUUID, uuid);\n logger.log('[funnel] Successfully stored UUID in localStorage');\n const verification = localStorage.getItem(dbUUID);\n logger.log('[funnel] Verification read from localStorage:', verification);\n } catch (e) {\n logger.error('[funnel] ERROR storing UUID:', e);\n }\n\n logger.log(`[funnel] Accommodating a new guest with account: ${uuid}`);\n }\n\n const finalUUID = localStorage.getItem(dbUUID);\n const finalUsername = GuestUsername + finalUUID;\n logger.log('[funnel] Final UUID from localStorage:', finalUUID);\n logger.log('[funnel] GuestUsername constant:', GuestUsername);\n logger.log('[funnel] Final username to return:', finalUsername);\n\n return {\n username: finalUsername,\n firstVisit: firstVisit,\n };\n\n // Use cryptographically secure UUID generation\n function generateUUID() {\n logger.log('[funnel] Inside generateUUID()');\n\n // Use crypto.randomUUID() if available (Node 14.17+ / modern browsers)\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n const uuid = crypto.randomUUID();\n logger.log('[funnel] Generated UUID using crypto.randomUUID():', uuid);\n return uuid;\n }\n\n // Fallback for older environments: use crypto.getRandomValues()\n if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n\n // Set version (4) and variant bits according to RFC 4122\n bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4\n bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10\n\n const uuid = [\n Array.from(bytes.slice(0, 4)).map(b => b.toString(16).padStart(2, '0')).join(''),\n Array.from(bytes.slice(4, 6)).map(b => b.toString(16).padStart(2, '0')).join(''),\n Array.from(bytes.slice(6, 8)).map(b => b.toString(16).padStart(2, '0')).join(''),\n Array.from(bytes.slice(8, 10)).map(b => b.toString(16).padStart(2, '0')).join(''),\n Array.from(bytes.slice(10, 16)).map(b => b.toString(16).padStart(2, '0')).join(''),\n ].join('-');\n\n logger.log('[funnel] Generated UUID using crypto.getRandomValues():', uuid);\n return uuid;\n }\n\n // Last resort fallback (should never happen in modern environments)\n logger.warn('[funnel] crypto API not available, using timestamp-based UUID (NOT SECURE)');\n let d = new Date().getTime();\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n d += performance.now();\n }\n const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (d + Math.random() * 16) % 16 | 0;\n d = Math.floor(d / 16);\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n logger.log('[funnel] Generated UUID (fallback):', uuid);\n return uuid;\n }\n}\n\nconst userCoursesDoc = 'CourseRegistrations';\nconst userClassroomsDoc = 'ClassroomRegistrations';\n\nasync function getOrCreateClassroomRegistrationsDoc(\n user: string\n): Promise<ClassroomRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n let ret;\n\n try {\n ret = await getLocalUserDB(user).get<ClassroomRegistrationDoc>(userClassroomsDoc);\n } catch (e) {\n const err = e as PouchError;\n\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await getLocalUserDB(user).put<ClassroomRegistrationDoc>({\n _id: userClassroomsDoc,\n registrations: [],\n });\n ret = await getOrCreateClassroomRegistrationsDoc(user);\n } else {\n // Properly serialize error information\n const errorDetails = {\n name: err.name,\n status: err.status,\n message: err.message,\n reason: err.reason,\n error: err.error,\n };\n\n logger.error(\n 'Database error in getOrCreateClassroomRegistrationsDoc (standalone function):',\n errorDetails\n );\n\n throw new Error(\n `Database error accessing classroom registrations: ${err.message || err.name || 'Unknown error'} (status: ${err.status})`\n );\n }\n }\n\n return ret;\n}\n\nasync function getOrCreateCourseRegistrationsDoc(\n user: string\n): Promise<CourseRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n let ret;\n\n try {\n ret = await getLocalUserDB(user).get<CourseRegistrationDoc>(userCoursesDoc);\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await getLocalUserDB(user).put<CourseRegistrationDoc>({\n _id: userCoursesDoc,\n courses: [],\n studyWeight: {},\n });\n ret = await getOrCreateCourseRegistrationsDoc(user);\n } else {\n throw new Error(\n `Unexpected error ${JSON.stringify(e)} in getOrCreateCourseRegistrationDoc...`\n );\n }\n }\n\n return ret;\n}\n\nexport async function updateUserElo(user: string, course_id: string, elo: CourseElo) {\n const regDoc = await getOrCreateCourseRegistrationsDoc(user);\n const course = regDoc.courses.find((c) => c.courseID === course_id)!;\n course.elo = elo;\n return getLocalUserDB(user).put(regDoc);\n}\n\nexport async function registerUserForClassroom(\n user: string,\n classID: string,\n registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n) {\n log(`Registering user: ${user} in course: ${classID}`);\n return getOrCreateClassroomRegistrationsDoc(user).then((doc) => {\n const regItem = {\n classID: classID,\n registeredAs: registerAs,\n };\n\n if (\n doc.registrations.filter((reg) => {\n return reg.classID === regItem.classID && reg.registeredAs === regItem.registeredAs;\n }).length === 0\n ) {\n doc.registrations.push(regItem);\n } else {\n log(`User ${user} is already registered for class ${classID}`);\n }\n\n return getLocalUserDB(user).put(doc);\n });\n}\n\nexport async function dropUserFromClassroom(user: string, classID: string) {\n return getOrCreateClassroomRegistrationsDoc(user).then((doc) => {\n let index: number = -1;\n\n for (let i = 0; i < doc.registrations.length; i++) {\n if (doc.registrations[i].classID === classID) {\n index = i;\n }\n }\n\n if (index !== -1) {\n doc.registrations.splice(index, 1);\n }\n return getLocalUserDB(user).put(doc);\n });\n}\n\nexport async function getUserClassrooms(user: string) {\n return getOrCreateClassroomRegistrationsDoc(user);\n}\n","// packages/db/src/impl/common/index.ts\n\nexport type { SyncStrategy } from './SyncStrategy';\nexport { BaseSyncStrategy } from './SyncStrategy';\nexport type {\n AccountCreationResult,\n AuthenticationResult,\n UserSession,\n SyncConfig,\n SyncStatus,\n} from './types';\nexport { BaseUser, accomodateGuest } from './BaseUserDB';\nexport {\n REVIEW_TIME_FORMAT,\n hexEncode,\n filterAllDocsByPrefix,\n getStartAndEndKeys,\n updateGuestAccountExpirationDate,\n getLocalUserDB,\n scheduleCardReviewLocal,\n removeScheduledCardReviewLocal,\n} from './userDBHelpers';\n","// db/src/factory.ts\n\nimport { DataLayerProvider } from './core/interfaces';\nimport { BaseUser } from './impl/common';\nimport { logger } from './util/logger';\nimport { StaticCourseManifest } from './util/packer/types';\n\nconst NOT_SET = 'NOT_SET' as const;\n\ninterface DBEnv {\n COUCHDB_SERVER_URL: string; // URL of CouchDB server\n COUCHDB_SERVER_PROTOCOL: string; // Protocol of CouchDB server (http or https)\n COUCHDB_USERNAME?: string;\n COUCHDB_PASSWORD?: string;\n LOCAL_STORAGE_PREFIX: string; // Prefix for IndexedDB storage names\n}\n\nexport const ENV: DBEnv = {\n COUCHDB_SERVER_PROTOCOL: NOT_SET,\n COUCHDB_SERVER_URL: NOT_SET,\n LOCAL_STORAGE_PREFIX: '',\n};\n\nexport { NOT_SET };\n\n// Configuration type for data layer initialization\nexport interface DataLayerConfig {\n type: 'couch' | 'static';\n options: {\n staticContentPath?: string; // Path to static content JSON files\n localStoragePrefix?: string; // Prefix for IndexedDB storage names\n manifests?: Record<string, StaticCourseManifest>; // Course manifests for static mode\n COUCHDB_SERVER_URL?: string;\n COUCHDB_SERVER_PROTOCOL?: string;\n COUCHDB_USERNAME?: string;\n COUCHDB_PASSWORD?: string;\n\n COURSE_IDS?: string[];\n };\n}\n\n// Singleton instance\nlet dataLayerInstance: DataLayerProvider | null = null;\n\n/**\n * Initialize the data layer with the specified configuration\n */\nexport async function initializeDataLayer(config: DataLayerConfig): Promise<DataLayerProvider> {\n if (dataLayerInstance) {\n logger.warn('Data layer already initialized. Returning existing instance.');\n return dataLayerInstance;\n }\n if (config.options.localStoragePrefix) {\n ENV.LOCAL_STORAGE_PREFIX = config.options.localStoragePrefix;\n }\n\n if (config.type === 'couch') {\n if (!config.options.COUCHDB_SERVER_URL || !config.options.COUCHDB_SERVER_PROTOCOL) {\n throw new Error('Missing CouchDB server URL or protocol');\n }\n ENV.COUCHDB_SERVER_PROTOCOL = config.options.COUCHDB_SERVER_PROTOCOL;\n ENV.COUCHDB_SERVER_URL = config.options.COUCHDB_SERVER_URL;\n ENV.COUCHDB_USERNAME = config.options.COUCHDB_USERNAME;\n ENV.COUCHDB_PASSWORD = config.options.COUCHDB_PASSWORD;\n\n if (\n config.options.COUCHDB_PASSWORD &&\n config.options.COUCHDB_USERNAME &&\n typeof window !== 'undefined'\n ) {\n // Dynamic import to avoid loading both implementations when only one is needed\n const { CouchDBSyncStrategy } = await import('./impl/couch/CouchDBSyncStrategy');\n\n // Create a sync strategy instance and authenticate\n const syncStrategy = new CouchDBSyncStrategy();\n\n const user = await BaseUser.instance(syncStrategy, config.options.COUCHDB_USERNAME);\n const authResult = await user.login(\n config.options.COUCHDB_USERNAME,\n config.options.COUCHDB_PASSWORD\n );\n\n if (authResult.ok) {\n logger.info(`Successfully authenticated as ${config.options.COUCHDB_USERNAME}`);\n } else {\n logger.warn(`Authentication failed: ${authResult.error}`);\n }\n }\n\n // Dynamic import to avoid loading both implementations when only one is needed\n const { CouchDataLayerProvider } = await import('./impl/couch/PouchDataLayerProvider');\n dataLayerInstance = new CouchDataLayerProvider(config.options.COURSE_IDS);\n } else if (config.type === 'static') {\n const { StaticDataLayerProvider } = await import('./impl/static/StaticDataLayerProvider');\n dataLayerInstance = new StaticDataLayerProvider(config.options);\n } else {\n throw new Error(`Unknown data layer type: ${config.type}`);\n }\n\n await dataLayerInstance.initialize();\n return dataLayerInstance;\n}\n\n/**\n * Get the initialized data layer instance\n * @throws Error if not initialized\n */\nexport function getDataLayer(): DataLayerProvider {\n if (!dataLayerInstance) {\n throw new Error('Data layer not initialized. Call initializeDataLayer first.');\n }\n return dataLayerInstance;\n}\n\n/**\n * Reset the data layer (primarily for testing)\n */\nexport async function _resetDataLayer(): Promise<void> {\n if (dataLayerInstance) {\n await dataLayerInstance.teardown();\n }\n dataLayerInstance = null;\n}\n","import { getDataLayer } from '@db/factory';\nimport { UserDBInterface } from '..';\nimport { StudentClassroomDB } from '../../impl/couch/classroomDB';\nimport { ScheduledCard } from '@db/core/types/user';\n\nexport type StudySessionFailedItem = StudySessionFailedNewItem | StudySessionFailedReviewItem;\n\nexport interface StudySessionFailedNewItem extends StudySessionItem {\n status: 'failed-new';\n}\nexport interface StudySessionFailedReviewItem extends StudySessionReviewItem {\n status: 'failed-review';\n}\n\nexport interface StudySessionNewItem extends StudySessionItem {\n status: 'new';\n}\n\nexport interface StudySessionReviewItem extends StudySessionItem {\n reviewID: string;\n status: 'review' | 'failed-review';\n}\nexport function isReview(item: StudySessionItem): item is StudySessionReviewItem {\n const ret = item.status === 'review' || item.status === 'failed-review' || 'reviewID' in item;\n\n // console.log(`itemIsReview: ${ret}\n // \\t${JSON.stringify(item)}`);\n\n return ret;\n}\n\nexport interface StudySessionItem {\n status: 'new' | 'review' | 'failed-new' | 'failed-review';\n contentSourceType: 'course' | 'classroom';\n contentSourceID: string;\n // qualifiedID: `${string}-${string}` | `${string}-${string}-${number}`;\n cardID: string;\n courseID: string;\n elo?: number;\n // reviewID?: string;\n}\n\nexport interface ContentSourceID {\n type: 'course' | 'classroom';\n id: string;\n}\n\n// #region docs_StudyContentSource\nexport interface StudyContentSource {\n getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;\n getNewCards(n?: number): Promise<StudySessionNewItem[]>;\n}\n// #endregion docs_StudyContentSource\n\nexport async function getStudySource(\n source: ContentSourceID,\n user: UserDBInterface\n): Promise<StudyContentSource> {\n if (source.type === 'classroom') {\n return await StudentClassroomDB.factory(source.id, user);\n } else {\n // if (source.type === 'course') - removed so tsc is certain something returns\n // return new CourseDB(source.id, async () => {\n // return user;\n // });\n\n return getDataLayer().getCourseDB(source.id) as unknown as StudyContentSource;\n }\n}\n","import { CourseConfig, CourseElo, DataShape, SkuilderCourseData } from '@vue-skuilder/common';\nimport { StudySessionNewItem, StudySessionItem } from './contentSource';\nimport { TagStub, Tag, QualifiedCardID } from '../types/types-legacy';\nimport { DataLayerResult } from '../types/db';\nimport { NavigationStrategyManager } from './navigationStrategyManager';\n\n/**\n * Course content and management\n */\nexport interface CoursesDBInterface {\n /**\n * Get course config\n */\n getCourseConfig(courseId: string): Promise<CourseConfig>;\n\n /**\n * Get a list of all courses\n */\n getCourseList(): Promise<CourseConfig[]>;\n\n disambiguateCourse(courseId: string, disambiguator: string): Promise<void>;\n}\n\nexport interface CourseInfo {\n cardCount: number;\n registeredUsers: number;\n}\n\nexport interface CourseDBInterface extends NavigationStrategyManager {\n /**\n * Get course config\n */\n getCourseConfig(): Promise<CourseConfig>;\n\n getCourseID(): string;\n\n /**\n * Set course config\n */\n updateCourseConfig(cfg: CourseConfig): Promise<PouchDB.Core.Response>;\n\n getCourseInfo(): Promise<CourseInfo>;\n\n getCourseDoc<T extends SkuilderCourseData>(\n id: string,\n options?: PouchDB.Core.GetOptions\n ): Promise<T>;\n getCourseDocs<T extends SkuilderCourseData>(\n ids: string[],\n options?: PouchDB.Core.AllDocsOptions\n ): Promise<PouchDB.Core.AllDocsWithKeysResponse<{} & T>>;\n\n /**\n * Get cards sorted by ELO rating\n */\n getCardsByELO(\n elo: number,\n limit?: number\n ): Promise<\n {\n courseID: string;\n cardID: string;\n elo?: number;\n }[]\n >;\n\n /**\n * Get ELO data for specific cards\n */\n getCardEloData(cardIds: string[]): Promise<CourseElo[]>;\n\n /**\n * Update card ELO rating\n */\n updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;\n\n /**\n * Get new cards for study\n */\n getNewCards(limit?: number): Promise<StudySessionNewItem[]>;\n\n /**\n * Get cards centered at a particular ELO rating\n */\n getCardsCenteredAtELO(\n options: { limit: number; elo: 'user' | 'random' | number },\n filter?: (card: QualifiedCardID) => boolean\n ): Promise<StudySessionItem[]>;\n\n /**\n * Get tags for a card\n */\n getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>>;\n\n /**\n * Add a tag to a card\n */\n addTagToCard(cardId: string, tagId: string, updateELO?: boolean): Promise<PouchDB.Core.Response>;\n\n /**\n * Remove a tag from a card\n */\n removeTagFromCard(cardId: string, tagId: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Create a new tag\n */\n createTag(tagName: string, author: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Get a tag by name\n */\n getTag(tagName: string): Promise<Tag>;\n\n /**\n * Update a tag\n */\n updateTag(tag: Tag): Promise<PouchDB.Core.Response>;\n\n /**\n * Get all tag stubs for a course\n */\n getCourseTagStubs(): Promise<PouchDB.Core.AllDocsResponse<Tag>>;\n\n /**\n * Add a note to the course\n */\n addNote(\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [key: string]: PouchDB.Core.FullAttachment },\n elo?: CourseElo\n ): Promise<DataLayerResult>;\n\n removeCard(cardId: string): Promise<PouchDB.Core.Response>;\n\n getInexperiencedCards(): Promise<\n {\n courseId: string;\n cardId: string;\n count: number;\n elo: CourseElo;\n }[]\n >;\n\n /**\n * Search for cards by text content\n * @param query Text to search for\n * @returns Array of matching card data\n */\n searchCards(query: string): Promise<any[]>;\n\n /**\n * Find documents using PouchDB query syntax\n * @param request PouchDB find request\n * @returns Query response\n */\n find(request: PouchDB.Find.FindRequest<any>): Promise<PouchDB.Find.FindResponse<any>>;\n}\n","// db/src/core/interfaces.ts\n\nimport { UserDBInterface, UserDBReader } from './userDB';\nimport { CourseDBInterface, CoursesDBInterface } from './courseDB';\nimport { ClassroomDBInterface } from './classroomDB';\nimport { AdminDBInterface } from './adminDB';\n\n/**\n * Main factory interface for data access\n */\nexport interface DataLayerProvider {\n /**\n * Get the user database interface\n */\n getUserDB(): UserDBInterface;\n\n /**\n * Create a UserDBReader for a specific user (admin access required)\n * Uses session authentication to verify requesting user is admin\n * @param targetUsername - The username to create a reader for\n * @throws Error if requesting user is not 'admin'\n */\n createUserReaderForUser(targetUsername: string): Promise<UserDBReader>;\n\n /**\n * Get a course database interface\n */\n getCourseDB(courseId: string): CourseDBInterface;\n\n /**\n * Get the courses-lookup interface\n */\n getCoursesDB(): CoursesDBInterface;\n\n /**\n * Get a classroom database interface\n */\n getClassroomDB(classId: string, type: 'student' | 'teacher'): Promise<ClassroomDBInterface>;\n\n /**\n * Get the admin database interface\n */\n getAdminDB(): AdminDBInterface;\n\n /**\n * Initialize the data layer\n */\n initialize(): Promise<void>;\n\n /**\n * Teardown the data layer\n */\n teardown(): Promise<void>;\n\n /**\n * Check if this data layer is read-only\n */\n isReadOnly(): boolean;\n}\n","import {\n ActivityRecord,\n CourseRegistration,\n CourseRegistrationDoc,\n ScheduledCard,\n} from '@db/core/types/user';\nimport { CourseElo, Status } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\nimport { CardHistory, CardRecord, QualifiedCardID } from '../types/types-legacy';\nimport { UserConfig } from '../types/user';\nimport { DocumentUpdater } from '@db/study';\n\n/**\n * Read-only user data operations\n */\nexport interface UserDBReader {\n get<T>(id: string): Promise<T & PouchDB.Core.RevisionIdMeta>;\n getUsername(): string;\n isLoggedIn(): boolean;\n\n /**\n * Get user configuration\n */\n getConfig(): Promise<UserConfig>;\n\n /**\n * Get cards that the user has seen\n */\n getSeenCards(courseId?: string): Promise<string[]>;\n\n /**\n * Get cards that are actively scheduled for review\n */\n getActiveCards(): Promise<QualifiedCardID[]>;\n\n /**\n * Get user's course registrations\n */\n getCourseRegistrationsDoc(): Promise<CourseRegistrationDoc>;\n\n /**\n * Get the registration doc for a specific course.\n * @param courseId\n */\n getCourseRegDoc(courseId: string): Promise<CourseRegistration>;\n\n /**\n * Get user's active courses\n */\n getActiveCourses(): Promise<CourseRegistration[]>;\n\n /**\n * Get user's pending reviews\n */\n getPendingReviews(courseId?: string): Promise<ScheduledCard[]>;\n\n getActivityRecords(): Promise<ActivityRecord[]>;\n\n /**\n * Get user's classroom registrations\n */\n getUserClassrooms(): Promise<ClassroomRegistrationDoc>;\n\n /**\n * Get user's active classes\n */\n getActiveClasses(): Promise<string[]>;\n\n getCourseInterface(courseId: string): Promise<UsrCrsDataInterface>;\n}\n\n/**\n * User data mutation operations\n */\nexport interface UserDBWriter extends DocumentUpdater {\n /**\n * Update user configuration\n */\n setConfig(config: Partial<UserConfig>): Promise<void>;\n\n /**\n * Record a user's interaction with a card\n */\n putCardRecord<T extends CardRecord>(record: T): Promise<CardHistory<CardRecord>>;\n\n /**\n * Register user for a course\n */\n registerForCourse(courseId: string, previewMode?: boolean): Promise<PouchDB.Core.Response>;\n\n /**\n * Drop a course registration\n */\n dropCourse(courseId: string, dropStatus?: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Schedule a card for review\n */\n scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: string;\n time: Moment;\n scheduledFor: 'course' | 'classroom';\n schedulingAgentId: string;\n }): Promise<void>;\n\n /**\n * Remove a scheduled card review\n */\n removeScheduledCardReview(reviewId: string): Promise<void>;\n\n /**\n * Register user for a classroom\n */\n registerForClassroom(\n classId: string,\n registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n ): Promise<PouchDB.Core.Response>;\n\n /**\n * Drop user from classroom\n */\n dropFromClassroom(classId: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Update user's ELO rating for a course\n */\n updateUserElo(courseId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;\n\n /**\n * Reset all user data (progress, registrations, etc.) while preserving authentication\n */\n resetUserData(): Promise<{ status: Status; error?: string }>;\n}\n\n/**\n * Authentication and account management operations\n */\nexport interface UserDBAuthenticator {\n /**\n * Create a new user account\n */\n createAccount(\n username: string,\n password: string\n ): Promise<{\n status: Status;\n error: string;\n }>;\n\n /**\n * Log in as a user\n */\n login(\n username: string,\n password: string\n ): Promise<{\n ok: boolean;\n name?: string;\n roles?: string[];\n }>;\n\n /**\n * Log out the current user\n */\n logout(): Promise<{\n ok: boolean;\n }>;\n}\n\n/**\n * Complete user database interface - combines all user operations\n * This maintains backward compatibility with existing code\n */\nexport interface UserDBInterface extends UserDBReader, UserDBWriter, UserDBAuthenticator {}\n\nexport interface UserCourseSettings {\n [setting: string]: string | number | boolean;\n}\n\nexport interface UserCourseSetting {\n key: string;\n value: string | number | boolean;\n}\n\n// [ ] reconsider here. Should maybe be generic type based on <T extends StudyContentSource> ?\nexport interface UsrCrsDataInterface {\n getScheduledReviewCount(): Promise<number>;\n getCourseSettings(): Promise<UserCourseSettings>;\n updateCourseSettings(updates: UserCourseSetting[]): void; // [ ] return a result of some sort?\n // getRegistrationDoc(): Promise<CourseRegistration>;\n}\n\nexport type ClassroomRegistrationDesignation = 'student' | 'teacher' | 'aide' | 'admin';\n\nexport interface ClassroomRegistration {\n classID: string;\n registeredAs: ClassroomRegistrationDesignation;\n}\n\nexport interface ClassroomRegistrationDoc {\n registrations: ClassroomRegistration[];\n}\n","export * from './adminDB';\nexport * from './classroomDB';\nexport * from './contentSource';\nexport * from './courseDB';\nexport * from './dataLayerProvider';\n\nexport * from './userDB';\n","import { CourseElo } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\n\nexport interface SessionTrackingData {\n peekSessionCount: number;\n studySessionCount: number;\n sessionCount: number; // total\n firstSessionDate: string;\n lastSessionDate: string;\n signupPrompted: boolean;\n promptDismissalCount: number;\n studyModeAcknowledged: boolean;\n}\n\nexport interface UserConfig {\n darkMode: boolean;\n likesConfetti: boolean;\n sessionTimeLimit: number; // Session time limit in minutes\n email?: string; // Optional email for verification flows (added for enhanced auth)\n\n // Session tracking for trial enforcement (per-course)\n // Key is courseId (e.g., 'letterspractice-basic')\n sessionTracking?: Record<string, SessionTrackingData>;\n}\n\nexport interface ActivityRecord {\n timeStamp: number | string;\n [key: string]: any;\n}\n\nexport interface CourseRegistration {\n status?: 'active' | 'dropped' | 'maintenance-mode' | 'preview';\n courseID: string;\n admin: boolean;\n moderator: boolean;\n user: boolean;\n settings?: {\n [setting: string]: string | number | boolean;\n };\n elo: number | CourseElo;\n}\n\ninterface StudyWeights {\n [courseID: string]: number;\n}\n\nexport interface CourseRegistrationDoc {\n courses: CourseRegistration[];\n studyWeight: StudyWeights;\n}\n\nexport interface ScheduledCard {\n _id: PouchDB.Core.DocumentId;\n\n /**\n * The docID of the card to be reviewed\n */\n cardId: PouchDB.Core.DocumentId;\n /**\n * The ID of the course\n */\n courseId: string;\n /**\n * The time at which the card becomes eligible for review.\n *\n * (Should probably be UTC adjusted so that performance is\n * not wonky across time zones)\n * \n * Note: Stored as ISO string for PouchDB serialization compatibility,\n * but can be consumed as Moment objects via moment.utc(reviewTime)\n */\n reviewTime: string | Moment;\n\n /**\n * The time at which this scheduled event was created.\n * \n * Note: Stored as ISO string for PouchDB serialization compatibility,\n * but can be consumed as Moment objects via moment.utc(scheduledAt)\n */\n scheduledAt: string | Moment;\n\n /**\n * Classifying whether this card is scheduled on behalf of a\n * user-registered course or by as assigned content from a\n * user-registered classroom\n */\n scheduledFor: 'course' | 'classroom';\n\n /**\n * The ID of the course or classroom that requested this card\n */\n schedulingAgentId: string;\n}\n","import { CourseElo, Status, ParsedCard, BulkImportCardData } from '@vue-skuilder/common';\nimport { CourseDBInterface } from '../../core/interfaces/courseDB';\nimport { ImportResult, BulkCardProcessorConfig } from './types';\nimport { logger } from '../../util/logger';\n\n/**\n * Processes multiple cards from bulk text input\n *\n * @param parsedCards - Array of parsed cards to import\n * @param courseDB - Course database interface\n * @param config - Configuration for the card processor\n * @returns Array of import results\n */\nexport async function importParsedCards(\n parsedCards: ParsedCard[],\n courseDB: CourseDBInterface,\n config: BulkCardProcessorConfig\n): Promise<ImportResult[]> {\n const results: ImportResult[] = [];\n\n for (const parsedCard of parsedCards) {\n try {\n // processCard takes a ParsedCard and returns an ImportResult\n const result = await processCard(parsedCard, courseDB, config);\n results.push(result);\n } catch (error) {\n logger.error('Error processing card:', error);\n // Reconstruct originalText from parsedCard for this specific catch block\n // This is a fallback if processCard itself throws an unhandled error.\n // Normally, processCard should return an ImportResult with status 'error'.\n let errorOriginalText = parsedCard.markdown;\n if (parsedCard.tags && parsedCard.tags.length > 0) {\n errorOriginalText += `\\ntags: ${parsedCard.tags.join(', ')}`;\n }\n if (parsedCard.elo !== undefined) {\n errorOriginalText += `\\nelo: ${parsedCard.elo}`;\n }\n results.push({\n originalText: errorOriginalText,\n status: 'error',\n message: `Error processing card: ${\n error instanceof Error ? error.message : 'Unknown error'\n }`,\n });\n }\n }\n\n return results;\n}\n\n/**\n * Processes a single parsed card\n *\n * @param parsedCard - Parsed card data\n * @param courseDB - Course database interface\n * @param config - Configuration for the card processor\n * @returns Import result for the card\n */\nasync function processCard(\n parsedCard: ParsedCard,\n courseDB: CourseDBInterface,\n config: BulkCardProcessorConfig\n): Promise<ImportResult> {\n const { markdown, tags, elo } = parsedCard;\n\n // Build the original text representation including metadata\n let originalText = markdown;\n if (tags.length > 0) {\n originalText += `\\ntags: ${tags.join(', ')}`;\n }\n if (elo !== undefined) {\n originalText += `\\nelo: ${elo}`;\n }\n\n // Create card data object\n const cardData: BulkImportCardData = {\n Input: markdown,\n Uploads: [], // No uploads for bulk import\n };\n\n const tagsElo: CourseElo['tags'] = {};\n for (const tag of tags) {\n tagsElo[tag] = {\n score: elo || 0,\n count: 1,\n };\n }\n\n try {\n const result = await courseDB.addNote(\n config.courseCode,\n config.dataShape,\n cardData,\n config.userName,\n tags,\n undefined, // attachments\n elo\n ? {\n global: {\n score: elo,\n count: 1,\n },\n tags: tagsElo,\n misc: {},\n }\n : undefined\n );\n\n if (result.status === Status.ok) {\n return {\n originalText,\n status: 'success',\n message: 'Card added successfully.',\n cardId: result.id ? result.id : '(unknown)',\n };\n } else {\n return {\n originalText,\n status: 'error',\n message: result.message || 'Failed to add card to database. Unknown error.',\n };\n }\n } catch (error) {\n logger.error('Error adding note:', error);\n return {\n originalText,\n status: 'error',\n message: `Error adding card: ${error instanceof Error ? error.message : 'Unknown error'}`,\n };\n }\n}\n\n/**\n * Validates the configuration for bulk card processing\n *\n * @param config - Configuration to validate\n * @returns Object with validation result and error message if any\n */\nexport function validateProcessorConfig(config: Partial<BulkCardProcessorConfig>): {\n isValid: boolean;\n errorMessage?: string;\n} {\n if (!config.dataShape) {\n return {\n isValid: false,\n errorMessage: 'No data shape provided for card processing',\n };\n }\n\n if (!config.courseCode) {\n return {\n isValid: false,\n errorMessage: 'No course code provided for card processing',\n };\n }\n\n if (!config.userName) {\n return {\n isValid: false,\n errorMessage: 'No user name provided for card processing',\n };\n }\n\n return { isValid: true };\n}\n","import { DataShape } from '@vue-skuilder/common';\n\n/**\n * Interface representing the result of a bulk import operation for a single card\n */\nexport interface ImportResult {\n /** The original text input for the card */\n originalText: string;\n /** Status of the import operation */\n status: 'success' | 'error';\n /** Message describing the result or error */\n message: string;\n /** ID of the newly created card (only for success) */\n cardId?: string;\n}\n\n/**\n * Configuration for the bulk card processor\n */\nexport interface BulkCardProcessorConfig {\n /** The data shape to use for the cards */\n dataShape: DataShape;\n /** The course code used for adding notes */\n courseCode: string;\n /** The username of the current user */\n userName: string;\n}\n","export * from './cardProcessor.js';\nexport * from './types.js';","// Export all core interfaces and types\n\nexport * from './interfaces';\nexport * from './types/types-legacy';\nexport * from './types/user';\nexport * from '../util/Loggable';\nexport * from './util';\nexport * from './navigators';\nexport * from './bulkImport';\n"],"mappings":"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","names":["DocType","PouchDB","PouchDBFind","PouchDBAuth","moment","log","import_moment","moment","uuidv4","import_common","Navigators","module","filterAllDocsByPrefix","getCourseDB","import_common","import_moment","init_classroomDB","getStartAndEndKeys","moment","REVIEW_TIME_FORMAT","getCourseDB","init_adminDB","init_classroomDB","import_common","getCourseDB","filterAllDocsByPrefix","getStartAndEndKeys","import_cross_fetch","import_moment","REVIEW_TIME_FORMAT","init_adminDB","init_classroomDB","process","log","import_common","import_moment","moment","record","init_classroomDB","init_courseDB","init_courseDB","import_common"]}
|