Public Access
feat: add collections management and study coach features
- Implemented collection repository with CRUD operations for user collections. - Created routes for managing collections, including listing, creating, updating, and deleting collections. - Added sharing functionality for exams, allowing users to share and clone exams. - Developed a study coach service that analyzes exam results and provides study tips based on weak areas. - Introduced frontend components for managing collections, sharing exams, and displaying study coach insights. - Enhanced user experience with loading states and error handling in shared components.
This commit is contained in:
1 parent
7755e77039
commit
1ec1633dd9
26 files changed
+2557
-127
No files matched your search
@@ -53,6 +53,19 @@ export function runMigrations(db: Database.Database): void {
|
||||
CREATE INDEX IF NOT EXISTS idx_exam_results_user ON exam_results(user_id);
|
||||
`);
|
||||
|
||||
// ── Add share_token and is_public columns if missing ──
|
||||
const examCols = db.prepare(`PRAGMA table_info(saved_exams)`).all() as { name: string }[];
|
||||
if (!examCols.some((c) => c.name === 'share_token')) {
|
||||
db.exec(`ALTER TABLE saved_exams ADD COLUMN share_token TEXT`);
|
||||
}
|
||||
if (!examCols.some((c) => c.name === 'is_public')) {
|
||||
db.exec(`ALTER TABLE saved_exams ADD COLUMN is_public INTEGER DEFAULT 0`);
|
||||
}
|
||||
|
||||
db.exec(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_saved_exams_share ON saved_exams(share_token);
|
||||
`);
|
||||
|
||||
// ── Add role column if missing (existing databases) ──
|
||||
const cols = db.prepare(`PRAGMA table_info(users)`).all() as {
|
||||
name: string;
|
||||
@@ -61,15 +74,53 @@ export function runMigrations(db: Database.Database): void {
|
||||
db.exec(`ALTER TABLE users ADD COLUMN role TEXT NOT NULL DEFAULT 'user'`);
|
||||
}
|
||||
|
||||
// ── Add storage_quota_bytes if missing ──
|
||||
if (!cols.some((c) => c.name === "storage_quota_bytes")) {
|
||||
db.exec(`ALTER TABLE users ADD COLUMN storage_quota_bytes INTEGER NOT NULL DEFAULT 1073741824`);
|
||||
}
|
||||
// ── Add storage_quota_bytes if missing ──
|
||||
if (!cols.some((c) => c.name === "storage_quota_bytes")) {
|
||||
db.exec(`ALTER TABLE users ADD COLUMN storage_quota_bytes INTEGER NOT NULL DEFAULT 1073741824`);
|
||||
}
|
||||
|
||||
// ── Add plan if missing ──
|
||||
if (!cols.some((c) => c.name === "plan")) {
|
||||
db.exec(`ALTER TABLE users ADD COLUMN plan TEXT NOT NULL DEFAULT 'free'`);
|
||||
}
|
||||
// ── Add plan if missing ──
|
||||
if (!cols.some((c) => c.name === "plan")) {
|
||||
db.exec(`ALTER TABLE users ADD COLUMN plan TEXT NOT NULL DEFAULT 'free'`);
|
||||
}
|
||||
|
||||
// ── Add is_favorite to saved_exams if missing ──
|
||||
const examSavedCols = db.prepare(`PRAGMA table_info(saved_exams)`).all() as { name: string }[];
|
||||
if (!examSavedCols.some((c) => c.name === 'is_favorite')) {
|
||||
db.exec(`ALTER TABLE saved_exams ADD COLUMN is_favorite INTEGER DEFAULT 0`);
|
||||
}
|
||||
|
||||
// ── Add tags to saved_exams if missing ──
|
||||
if (!examSavedCols.some((c) => c.name === 'tags')) {
|
||||
db.exec(`ALTER TABLE saved_exams ADD COLUMN tags TEXT DEFAULT '[]'`);
|
||||
}
|
||||
|
||||
// ── Add collection_id to saved_exams if missing ──
|
||||
if (!examSavedCols.some((c) => c.name === 'collection_id')) {
|
||||
db.exec(`ALTER TABLE saved_exams ADD COLUMN collection_id INTEGER REFERENCES collections(id) ON DELETE SET NULL`);
|
||||
}
|
||||
|
||||
// ── Add share_mode to saved_exams if missing ──
|
||||
if (!examSavedCols.some((c) => c.name === 'share_mode')) {
|
||||
db.exec(`ALTER TABLE saved_exams ADD COLUMN share_mode TEXT DEFAULT 'clone'`);
|
||||
}
|
||||
|
||||
// ── Add cloned_from_share_token to saved_exams if missing ──
|
||||
if (!examSavedCols.some((c) => c.name === 'cloned_from_share_token')) {
|
||||
db.exec(`ALTER TABLE saved_exams ADD COLUMN cloned_from_share_token TEXT`);
|
||||
}
|
||||
|
||||
// ── Collections table ──
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS collections (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
created_at INTEGER DEFAULT (unixepoch()),
|
||||
updated_at INTEGER DEFAULT (unixepoch())
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_collections_user ON collections(user_id);
|
||||
`);
|
||||
|
||||
// ── Promote first user to admin if no admins exist ──
|
||||
const adminCount = db
|
||||
|
||||
Reference in new issue
Block a user