Spaces:
Running
Running
| const sqlite3 = require('sqlite3'); | |
| const { open } = require('sqlite'); | |
| const path = require('path'); | |
| const fs = require('fs'); | |
| const logger = require('./logger'); | |
| const DB_PATH = process.env.DB_PATH || path.join(__dirname, '../../data/resume_builder.db'); | |
| // Ensure data directory exists | |
| const dataDir = path.dirname(DB_PATH); | |
| if (!fs.existsSync(dataDir)) { | |
| fs.mkdirSync(dataDir, { recursive: true }); | |
| } | |
| let db; | |
| async function getDb() { | |
| if (!db) { | |
| db = await open({ | |
| filename: DB_PATH, | |
| driver: sqlite3.Database | |
| }); | |
| // Enable WAL mode and foreign keys | |
| await db.run('PRAGMA journal_mode = WAL'); | |
| await db.run('PRAGMA foreign_keys = ON'); | |
| await db.run('PRAGMA synchronous = NORMAL'); | |
| logger.info(`SQLite connected: ${DB_PATH}`); | |
| await initSchema(db); | |
| } | |
| return db; | |
| } | |
| async function initSchema(db) { | |
| await db.exec(` | |
| -- βββ Users βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| CREATE TABLE IF NOT EXISTS users ( | |
| id TEXT PRIMARY KEY, | |
| email TEXT UNIQUE NOT NULL, | |
| password TEXT NOT NULL, | |
| name TEXT NOT NULL, | |
| phone TEXT, | |
| avatar_url TEXT, | |
| plan TEXT NOT NULL DEFAULT 'free', | |
| created_at TEXT NOT NULL DEFAULT (datetime('now')), | |
| updated_at TEXT NOT NULL DEFAULT (datetime('now')) | |
| ); | |
| -- βββ Templates ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| CREATE TABLE IF NOT EXISTS templates ( | |
| id TEXT PRIMARY KEY, | |
| name TEXT NOT NULL, | |
| description TEXT, | |
| category TEXT NOT NULL DEFAULT 'modern', | |
| layout TEXT NOT NULL DEFAULT 'single', | |
| thumbnail_url TEXT, | |
| html_content TEXT NOT NULL, | |
| css_content TEXT NOT NULL, | |
| is_ats_safe INTEGER NOT NULL DEFAULT 1, | |
| is_premium INTEGER NOT NULL DEFAULT 0, | |
| tags TEXT NOT NULL DEFAULT '[]', | |
| sort_order INTEGER NOT NULL DEFAULT 0, | |
| created_at TEXT NOT NULL DEFAULT (datetime('now')) | |
| ); | |
| -- βββ Resumes ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| CREATE TABLE IF NOT EXISTS resumes ( | |
| id TEXT PRIMARY KEY, | |
| user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, | |
| template_id TEXT REFERENCES templates(id), | |
| title TEXT NOT NULL DEFAULT 'Untitled Resume', | |
| slug TEXT, | |
| content TEXT NOT NULL DEFAULT '{}', | |
| design TEXT NOT NULL DEFAULT '{}', | |
| section_order TEXT NOT NULL DEFAULT '[]', | |
| ats_score INTEGER DEFAULT 0, | |
| ats_feedback TEXT DEFAULT '[]', | |
| is_public INTEGER NOT NULL DEFAULT 0, | |
| last_exported TEXT, | |
| created_at TEXT NOT NULL DEFAULT (datetime('now')), | |
| updated_at TEXT NOT NULL DEFAULT (datetime('now')) | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_resumes_user ON resumes(user_id); | |
| -- βββ Resume Versions ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| CREATE TABLE IF NOT EXISTS resume_versions ( | |
| id TEXT PRIMARY KEY, | |
| resume_id TEXT NOT NULL REFERENCES resumes(id) ON DELETE CASCADE, | |
| version_num INTEGER NOT NULL, | |
| snapshot TEXT NOT NULL, | |
| label TEXT, | |
| created_at TEXT NOT NULL DEFAULT (datetime('now')) | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_versions_resume ON resume_versions(resume_id); | |
| -- βββ Exports ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| CREATE TABLE IF NOT EXISTS exports ( | |
| id TEXT PRIMARY KEY, | |
| resume_id TEXT NOT NULL REFERENCES resumes(id) ON DELETE CASCADE, | |
| user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, | |
| format TEXT NOT NULL DEFAULT 'docx', | |
| file_path TEXT NOT NULL, | |
| file_size INTEGER, | |
| created_at TEXT NOT NULL DEFAULT (datetime('now')) | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_exports_resume ON exports(resume_id); | |
| -- βββ Import Jobs ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| CREATE TABLE IF NOT EXISTS import_jobs ( | |
| id TEXT PRIMARY KEY, | |
| user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, | |
| resume_id TEXT REFERENCES resumes(id) ON DELETE SET NULL, | |
| source_type TEXT NOT NULL, | |
| status TEXT NOT NULL DEFAULT 'pending', | |
| extracted TEXT DEFAULT '{}', | |
| error TEXT, | |
| created_at TEXT NOT NULL DEFAULT (datetime('now')), | |
| updated_at TEXT NOT NULL DEFAULT (datetime('now')) | |
| ); | |
| -- βββ Writing Tips βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| CREATE TABLE IF NOT EXISTS writing_tips ( | |
| id TEXT PRIMARY KEY, | |
| section TEXT NOT NULL, | |
| tip TEXT NOT NULL, | |
| example TEXT, | |
| tags TEXT DEFAULT '[]' | |
| ); | |
| `); | |
| await db.run(` | |
| INSERT OR IGNORE INTO users (id, email, password, name, plan) | |
| VALUES ('guest_user', 'guest@resumepro.local', 'none', 'Guest User', 'free'); | |
| `); | |
| try { | |
| await db.exec('ALTER TABLE users ADD COLUMN phone TEXT'); | |
| } catch (err) { | |
| // Column likely already exists | |
| } | |
| logger.info('Database schema initialized and guest user ensured'); | |
| // Auto-seed if empty | |
| const templateCount = await db.get('SELECT COUNT(*) as count FROM templates'); | |
| if (templateCount.count === 0) { | |
| logger.info('Templates table is empty. Triggering auto-seed...'); | |
| try { | |
| // We require it here to avoid circular dependency if any | |
| const { templates, writingTips } = require('../scripts/seedData'); | |
| for (const tmpl of templates) { | |
| await db.run( | |
| `INSERT INTO templates (id, name, description, category, layout, thumbnail_url, html_content, css_content, is_ats_safe, is_premium, tags) | |
| VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, | |
| tmpl.id, tmpl.name, tmpl.description, tmpl.category, tmpl.layout, tmpl.thumbnail_url, tmpl.html_content, tmpl.css_content, tmpl.is_ats_safe, tmpl.is_premium, tmpl.tags | |
| ); | |
| } | |
| for (const tip of writingTips) { | |
| await db.run('INSERT INTO writing_tips (id, section, tip, example) VALUES (?, ?, ?, ?)', tip.id, tip.section, tip.tip, tip.example); | |
| } | |
| logger.info(`Successfully auto-seeded ${templates.length} templates.`); | |
| } catch (seedErr) { | |
| logger.error('Auto-seed failed:', seedErr); | |
| } | |
| } | |
| } | |
| module.exports = { getDb }; | |