import Database from "better-sqlite3" import path from "path" // Database file stored in project root const DB_PATH = path.join(process.cwd(), "data", "quotes.db") // Ensure data directory exists import fs from "fs" const dataDir = path.dirname(DB_PATH) if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir, { recursive: true }) } const db = new Database(DB_PATH) // Initialize database schema db.exec(` CREATE TABLE IF NOT EXISTS quotes ( id INTEGER PRIMARY KEY AUTOINCREMENT, postal_code TEXT NOT NULL, address TEXT, area REAL NOT NULL, height REAL NOT NULL, include_floor_heating INTEGER NOT NULL DEFAULT 1, flooring_type TEXT NOT NULL DEFAULT 'STANDARD', customer_name TEXT NOT NULL, customer_email TEXT NOT NULL, customer_phone TEXT NOT NULL, remarks TEXT, total_excl_vat REAL, total_incl_vat REAL, status TEXT NOT NULL DEFAULT 'new', created_at TEXT DEFAULT CURRENT_TIMESTAMP ) `) // Migration: Add status column if it doesn't exist try { db.exec("ALTER TABLE quotes ADD COLUMN status TEXT NOT NULL DEFAULT 'new'") } catch { // Column already exists } // Migration: Add email_opened_at column for tracking try { db.exec("ALTER TABLE quotes ADD COLUMN email_opened_at TEXT") } catch { // Column already exists } // Users table for authentication db.exec(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, name TEXT NOT NULL, created_at TEXT DEFAULT CURRENT_TIMESTAMP ) `) // Sessions table for login sessions db.exec(` CREATE TABLE IF NOT EXISTS sessions ( id TEXT PRIMARY KEY, user_id INTEGER NOT NULL, expires_at TEXT NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ) `) // Start IDs at 1000 const countResult = db.prepare("SELECT COUNT(*) as count FROM quotes").get() as { count: number } if (countResult.count === 0) { db.exec( "INSERT INTO quotes (id, postal_code, area, height, customer_name, customer_email, customer_phone) VALUES (999, '0000', 0, 0, 'init', 'init', 'init')" ) db.exec("DELETE FROM quotes WHERE id = 999") // Reset autoincrement to start at 1000 db.exec("UPDATE sqlite_sequence SET seq = 999 WHERE name = 'quotes'") } export interface QuoteInput { postalCode: string address?: string area: number height: number includeFloorHeating: boolean flooringType: string customerName: string customerEmail: string customerPhone: string remarks?: string totalExclVat?: number totalInclVat?: number } export type QuoteStatus = "new" | "contacted" | "accepted" | "rejected" export interface StoredQuote extends QuoteInput { id: number status: QuoteStatus createdAt: string emailOpenedAt: string | null } export function saveQuote(quote: QuoteInput): { id: number; slug: string } { const stmt = db.prepare(` INSERT INTO quotes ( postal_code, address, area, height, include_floor_heating, flooring_type, customer_name, customer_email, customer_phone, remarks, total_excl_vat, total_incl_vat ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `) const result = stmt.run( quote.postalCode, quote.address || null, quote.area, quote.height, quote.includeFloorHeating ? 1 : 0, quote.flooringType, quote.customerName, quote.customerEmail, quote.customerPhone, quote.remarks || null, quote.totalExclVat || null, quote.totalInclVat || null ) const id = result.lastInsertRowid as number const slug = `${quote.postalCode}-${id}` return { id, slug } } export function getQuoteBySlug(slug: string): StoredQuote | null { const match = slug.match(/^(\d{4})-(\d+)$/) if (!match) return null const [, postalCode, idStr] = match const id = parseInt(idStr, 10) const stmt = db.prepare("SELECT * FROM quotes WHERE id = ? AND postal_code = ?") const row = stmt.get(id, postalCode) as any if (!row) return null return rowToQuote(row) } export function getQuoteById(id: number): StoredQuote | null { const stmt = db.prepare("SELECT * FROM quotes WHERE id = ?") const row = stmt.get(id) as any if (!row) return null return rowToQuote(row) } export function getAllQuotes(): StoredQuote[] { const stmt = db.prepare("SELECT * FROM quotes ORDER BY id DESC") const rows = stmt.all() as any[] return rows.map(rowToQuote) } export function updateQuoteStatus(id: number, status: QuoteStatus): boolean { const stmt = db.prepare("UPDATE quotes SET status = ? WHERE id = ?") const result = stmt.run(status, id) return result.changes > 0 } export function markEmailOpened(id: number): boolean { // Only update if not already set (first open) const stmt = db.prepare( "UPDATE quotes SET email_opened_at = ? WHERE id = ? AND email_opened_at IS NULL" ) const result = stmt.run(new Date().toISOString(), id) return result.changes > 0 } function rowToQuote(row: any): StoredQuote { return { id: row.id, postalCode: row.postal_code, address: row.address, area: row.area, height: row.height, includeFloorHeating: row.include_floor_heating === 1, flooringType: row.flooring_type, customerName: row.customer_name, customerEmail: row.customer_email, customerPhone: row.customer_phone, remarks: row.remarks, totalExclVat: row.total_excl_vat, totalInclVat: row.total_incl_vat, status: row.status || "new", createdAt: row.created_at, emailOpenedAt: row.email_opened_at || null, } } // User management export interface User { id: number email: string name: string createdAt: string } export interface UserWithPassword extends User { passwordHash: string } export function createUser(email: string, passwordHash: string, name: string): User { const stmt = db.prepare("INSERT INTO users (email, password_hash, name) VALUES (?, ?, ?)") const result = stmt.run(email, passwordHash, name) return { id: result.lastInsertRowid as number, email, name, createdAt: new Date().toISOString(), } } export function getUserByEmail(email: string): UserWithPassword | null { const stmt = db.prepare("SELECT * FROM users WHERE email = ?") const row = stmt.get(email) as any if (!row) return null return { id: row.id, email: row.email, name: row.name, passwordHash: row.password_hash, createdAt: row.created_at, } } export function getUserById(id: number): User | null { const stmt = db.prepare("SELECT id, email, name, created_at FROM users WHERE id = ?") const row = stmt.get(id) as any if (!row) return null return { id: row.id, email: row.email, name: row.name, createdAt: row.created_at, } } // Session management export function createSession(sessionId: string, userId: number, expiresAt: Date): void { const stmt = db.prepare("INSERT INTO sessions (id, user_id, expires_at) VALUES (?, ?, ?)") stmt.run(sessionId, userId, expiresAt.toISOString()) } export function getSession(sessionId: string): { userId: number; expiresAt: Date } | null { const stmt = db.prepare("SELECT user_id, expires_at FROM sessions WHERE id = ?") const row = stmt.get(sessionId) as any if (!row) return null return { userId: row.user_id, expiresAt: new Date(row.expires_at), } } export function deleteSession(sessionId: string): void { const stmt = db.prepare("DELETE FROM sessions WHERE id = ?") stmt.run(sessionId) } export function cleanExpiredSessions(): void { const stmt = db.prepare("DELETE FROM sessions WHERE expires_at < ?") stmt.run(new Date().toISOString()) }