Resume / backend /src /config /database.js
Resume Builder Dev
feat(export): switch resume export/import from PDF to DOCX
f4623fe
Raw
History Blame Contribute Delete
7.3 kB
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 };