diff --git a/src/app/api/settings/hourly_rate/route.ts b/src/app/api/settings/hourly_rate/route.ts index ac8d4a1..674e5e3 100644 --- a/src/app/api/settings/hourly_rate/route.ts +++ b/src/app/api/settings/hourly_rate/route.ts @@ -1,8 +1,9 @@ import { NextRequest, NextResponse } from 'next/server'; -import db from '@/lib/db'; +import getDb from '@/lib/db'; export async function GET(req: NextRequest) { + const db = getDb(); try { const stmt = db.prepare('SELECT value FROM settings WHERE key = ?'); const setting = stmt.get('hourly_rate') as { value: string } | undefined; @@ -20,6 +21,7 @@ export async function GET(req: NextRequest) { } export async function POST(req: NextRequest) { + const db = getDb(); try { const { value } = await req.json(); diff --git a/src/app/api/settings/smtp/route.ts b/src/app/api/settings/smtp/route.ts index ac25aa4..f928d8d 100644 --- a/src/app/api/settings/smtp/route.ts +++ b/src/app/api/settings/smtp/route.ts @@ -1,8 +1,9 @@ import { NextRequest, NextResponse } from 'next/server'; -import db from '@/lib/db'; +import getDb from '@/lib/db'; export async function GET(req: NextRequest) { + const db = getDb(); try { const stmt = db.prepare("SELECT key, value FROM settings WHERE key LIKE 'smtp_%'"); const settings = stmt.all() as { key: string, value: string }[]; @@ -18,6 +19,7 @@ export async function GET(req: NextRequest) { } export async function POST(req: NextRequest) { + const db = getDb(); try { const settings = await req.json(); diff --git a/src/lib/actions/email.ts b/src/lib/actions/email.ts index 674a4e4..80228b1 100644 --- a/src/lib/actions/email.ts +++ b/src/lib/actions/email.ts @@ -2,7 +2,7 @@ 'use server'; import { z } from 'zod'; -import db from '@/lib/db'; +import getDb from '@/lib/db'; import { revalidatePath } from 'next/cache'; const emailTemplateSchema = z.object({ @@ -59,6 +59,7 @@ const defaultBody = ` * If no template is found, it creates and returns a default one. */ export async function getEmailTemplate(): Promise<{ subject: string; body: string }> { + const db = getDb(); try { const stmt = db.prepare('SELECT subject, body FROM email_templates WHERE id = ?'); let template = stmt.get(1) as { subject: string; body: string } | undefined; @@ -90,6 +91,7 @@ export async function updateEmailTemplate(data: { subject: string; body: string } const { subject, body } = validation.data; + const db = getDb(); try { const stmt = db.prepare('UPDATE email_templates SET subject = ?, body = ? WHERE id = ?'); diff --git a/src/lib/actions/flows.ts b/src/lib/actions/flows.ts index 392e96b..ccff413 100644 --- a/src/lib/actions/flows.ts +++ b/src/lib/actions/flows.ts @@ -1,7 +1,7 @@ 'use server'; -import db from '@/lib/db'; +import getDb from '@/lib/db'; import { z } from 'zod'; import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; @@ -29,6 +29,7 @@ type State = { } export async function getFlows(): Promise { + const db = getDb(); try { const stmt = db.prepare( 'SELECT id, name, description, path, createdAt, updatedAt FROM flows ORDER BY createdAt DESC' @@ -42,6 +43,7 @@ export async function getFlows(): Promise { } export async function getFlow(id: number): Promise { + const db = getDb(); try { const stmt = db.prepare('SELECT * FROM flows WHERE id = ?'); const flow = stmt.get(id) as Flow | undefined; @@ -64,6 +66,7 @@ export async function saveFlow(prevState: State, formData: FormData): Promise { + const db = getDb(); try { // Prevent deletion of the default flow (ID 1) if (id === 1) { diff --git a/src/lib/actions/leads.ts b/src/lib/actions/leads.ts index 755db23..70f0a61 100644 --- a/src/lib/actions/leads.ts +++ b/src/lib/actions/leads.ts @@ -1,7 +1,7 @@ 'use server'; -import db from '@/lib/db'; +import getDb from '@/lib/db'; export type Lead = { id: number; @@ -12,6 +12,7 @@ export type Lead = { }; export async function getLeads(): Promise { + const db = getDb(); try { const stmt = db.prepare( 'SELECT id, name, email, phone, createdAt FROM leads ORDER BY createdAt DESC' diff --git a/src/lib/actions/send-estimate.ts b/src/lib/actions/send-estimate.ts index f01141f..b2a1815 100644 --- a/src/lib/actions/send-estimate.ts +++ b/src/lib/actions/send-estimate.ts @@ -4,7 +4,7 @@ import { z } from 'zod'; import nodemailer from 'nodemailer'; import { PDFDocument, rgb, StandardFonts } from 'pdf-lib'; -import db from '@/lib/db'; +import getDb from '@/lib/db'; import { getEmailTemplate } from './email'; import type { FormData } from '@/components/cost-estimator/cost-estimator-form'; @@ -21,6 +21,7 @@ type InputType = z.infer; // Helper to get SMTP settings from the database async function getSmtpSettings() { + const db = getDb(); const stmt = db.prepare("SELECT key, value FROM settings WHERE key LIKE 'smtp_%'"); const settings = stmt.all() as { key: string, value: string }[]; const config = settings.reduce((acc, setting) => { @@ -117,6 +118,7 @@ async function createEstimatePdf(data: InputType): Promise { } async function saveLead(name: string, email: string, phone?: string) { + const db = getDb(); try { const stmt = db.prepare('INSERT INTO leads (name, email, phone) VALUES (?, ?, ?)'); stmt.run(name, email, phone || null); diff --git a/src/lib/actions/user.ts b/src/lib/actions/user.ts index 02e6f24..ffc1530 100644 --- a/src/lib/actions/user.ts +++ b/src/lib/actions/user.ts @@ -1,7 +1,8 @@ + 'use server'; import { z } from 'zod'; -import db from '@/lib/db'; +import getDb from '@/lib/db'; import { revalidatePath } from 'next/cache'; import { auth } from '@/auth'; @@ -21,6 +22,7 @@ type User = { }; export async function getUserByEmail(email: string): Promise { + const db = getDb(); try { const stmt = db.prepare('SELECT id, name, email, password FROM users WHERE email = ?'); const user = stmt.get(email) as User | undefined; @@ -64,6 +66,7 @@ export async function updateUser(data: UserFormValues): Promise<{ success: boole const { name, email, password } = validation.data; try { + const db = getDb(); const userId = session.user.id; // Check if the new email is already taken by another user diff --git a/src/lib/db.ts b/src/lib/db.ts index b18dae1..363000a 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -1,127 +1,134 @@ import Database from 'better-sqlite3'; -// Use a file-based database in development -const db = new Database('local.db'); -db.pragma('journal_mode = WAL'); +let db: Database.Database; -// --- SCHEMA CREATION --- +function initializeDb() { + // Use a file-based database + const newDb = new Database('local.db'); + newDb.pragma('journal_mode = WAL'); -// Drop the users table to ensure a clean slate on every start, avoiding schema conflicts. -db.exec('DROP TABLE IF EXISTS users'); + // --- SCHEMA CREATION --- -// Auth.js tables -db.exec(` - CREATE TABLE IF NOT EXISTS users ( - id TEXT NOT NULL PRIMARY KEY, - name TEXT, - email TEXT NOT NULL UNIQUE, - emailVerified INTEGER, - image TEXT, - password TEXT - ) -`); + // Auth.js tables + newDb.exec(` + CREATE TABLE IF NOT EXISTS users ( + id TEXT NOT NULL PRIMARY KEY, + name TEXT, + email TEXT NOT NULL UNIQUE, + emailVerified INTEGER, + image TEXT, + password TEXT + ) + `); -db.exec(` - CREATE TABLE IF NOT EXISTS accounts ( - userId TEXT NOT NULL, - type TEXT NOT NULL, - provider TEXT NOT NULL, - providerAccountId TEXT NOT NULL, - refresh_token TEXT, - access_token TEXT, - expires_at INTEGER, - token_type TEXT, - scope TEXT, - id_token TEXT, - session_state TEXT, - PRIMARY KEY (provider, providerAccountId), - FOREIGN KEY (userId) REFERENCES users (id) ON DELETE CASCADE - ) -`); + newDb.exec(` + CREATE TABLE IF NOT EXISTS accounts ( + userId TEXT NOT NULL, + type TEXT NOT NULL, + provider TEXT NOT NULL, + providerAccountId TEXT NOT NULL, + refresh_token TEXT, + access_token TEXT, + expires_at INTEGER, + token_type TEXT, + scope TEXT, + id_token TEXT, + session_state TEXT, + PRIMARY KEY (provider, providerAccountId), + FOREIGN KEY (userId) REFERENCES users (id) ON DELETE CASCADE + ) + `); -db.exec(` - CREATE TABLE IF NOT EXISTS sessions ( - sessionToken TEXT NOT NULL PRIMARY KEY, - userId TEXT NOT NULL, - expires INTEGER NOT NULL, - FOREIGN KEY (userId) REFERENCES users (id) ON DELETE CASCADE - ) -`); + newDb.exec(` + CREATE TABLE IF NOT EXISTS sessions ( + sessionToken TEXT NOT NULL PRIMARY KEY, + userId TEXT NOT NULL, + expires INTEGER NOT NULL, + FOREIGN KEY (userId) REFERENCES users (id) ON DELETE CASCADE + ) + `); -db.exec(` - CREATE TABLE IF NOT EXISTS verification_tokens ( - identifier TEXT NOT NULL, - token TEXT NOT NULL, - expires INTEGER NOT NULL, - PRIMARY KEY (identifier, token) - ) -`); + newDb.exec(` + CREATE TABLE IF NOT EXISTS verification_tokens ( + identifier TEXT NOT NULL, + token TEXT NOT NULL, + expires INTEGER NOT NULL, + PRIMARY KEY (identifier, token) + ) + `); + newDb.exec(` + CREATE TABLE IF NOT EXISTS settings ( + key TEXT PRIMARY KEY, + value TEXT + ) + `); -db.exec(` - CREATE TABLE IF NOT EXISTS settings ( - key TEXT PRIMARY KEY, - value TEXT - ) -`); + newDb.exec(` + CREATE TABLE IF NOT EXISTS email_templates ( + id INTEGER PRIMARY KEY, + subject TEXT, + body TEXT + ) + `); -db.exec(` - CREATE TABLE IF NOT EXISTS email_templates ( - id INTEGER PRIMARY KEY, - subject TEXT, - body TEXT - ) -`); + newDb.exec(` + CREATE TABLE IF NOT EXISTS leads ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + email TEXT NOT NULL, + phone TEXT, + createdAt DATETIME DEFAULT CURRENT_TIMESTAMP + ) + `); -db.exec(` - CREATE TABLE IF NOT EXISTS leads ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - email TEXT NOT NULL, - phone TEXT, - createdAt DATETIME DEFAULT CURRENT_TIMESTAMP - ) -`); + newDb.exec(` + CREATE TABLE IF NOT EXISTS flows ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + description TEXT, + path TEXT NOT NULL UNIQUE, + createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, + updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP + ) + `); -db.exec(` - CREATE TABLE IF NOT EXISTS flows ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - description TEXT, - path TEXT NOT NULL UNIQUE, - createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, - updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP - ) -`); + // --- SEEDING LOGIC --- + console.log('Running database checks and seeding if necessary...'); + // Seed default user + const userStmt = newDb.prepare('SELECT id FROM users WHERE email = ?'); + const defaultUser = userStmt.get('admin@example.com'); + if (!defaultUser) { + const insertUser = newDb.prepare( + "INSERT INTO users (id, email, password, name) VALUES (?, ?, ?, ?)" + ); + // Note: In a real app, hash the password! + insertUser.run('cl-admin-user-id', 'admin@example.com', 'password', 'Admin User'); + console.log('Default user created.'); + } -// --- SEEDING LOGIC --- -console.log('Running database checks and seeding if necessary...'); + // Seed default flow + const flowStmt = newDb.prepare("SELECT id FROM flows WHERE path = ?"); + const defaultFlow = flowStmt.get('/'); + if (!defaultFlow) { + const insertFlow = newDb.prepare( + "INSERT INTO flows (name, description, path) VALUES (?, ?, ?)" + ); + insertFlow.run('Cost Estimator', 'The main cost estimation tool for clients.', '/'); + console.log('Default flow created.'); + } -// Seed default user -const userStmt = db.prepare('SELECT id FROM users WHERE email = ?'); -const defaultUser = userStmt.get('admin@example.com'); -if (!defaultUser) { - const insertUser = db.prepare( - "INSERT INTO users (id, email, password, name) VALUES (?, ?, ?, ?)" - ); - // Note: In a real app, hash the password! - insertUser.run('cl-admin-user-id', 'admin@example.com', 'password', 'Admin User'); - console.log('Default user created.'); + console.log('Database setup complete.'); + return newDb; } -// Seed default flow -const flowStmt = db.prepare("SELECT id FROM flows WHERE path = ?"); -const defaultFlow = flowStmt.get('/'); -if (!defaultFlow) { - const insertFlow = db.prepare( - "INSERT INTO flows (name, description, path) VALUES (?, ?, ?)" - ); - insertFlow.run('Cost Estimator', 'The main cost estimation tool for clients.', '/'); - console.log('Default flow created.'); +function getDb() { + if (!db) { + db = initializeDb(); + } + return db; } -console.log('Database setup complete.'); - -export default db; +export default getDb;