File size: 7,303 Bytes
92a138e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f4623fe
92a138e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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 };