← back to Designer Portfolio Pages
src/db.ts
136 lines
import fs from "node:fs";
import path from "node:path";
import Database from "better-sqlite3";
import type { PatternSnapshot } from "./schemas.js";
export type PortfolioRow = {
id: number;
slug: string;
title: string | null;
created_at: string;
updated_at: string;
contact_name: string;
business_name: string | null;
email: string;
phone: string | null;
website: string | null;
password_hash: string | null;
patterns_json: string;
ai_intro: string | null;
};
export type Portfolio = {
id: number;
slug: string;
title?: string;
createdAt: string;
updatedAt: string;
contact: {
name: string;
business?: string;
email: string;
phone?: string;
website?: string;
};
passwordHash?: string;
patterns: PatternSnapshot[];
aiIntro?: string;
};
export function openDb(sqlitePath: string) {
const dir = path.dirname(sqlitePath);
fs.mkdirSync(dir, { recursive: true });
const db = new Database(sqlitePath);
db.pragma("journal_mode = WAL");
db.exec(`
CREATE TABLE IF NOT EXISTS portfolios (
id INTEGER PRIMARY KEY AUTOINCREMENT,
slug TEXT NOT NULL UNIQUE,
title TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
contact_name TEXT NOT NULL,
business_name TEXT,
email TEXT NOT NULL,
phone TEXT,
website TEXT,
password_hash TEXT,
patterns_json TEXT NOT NULL,
ai_intro TEXT
);
CREATE INDEX IF NOT EXISTS idx_portfolios_slug ON portfolios(slug);
`);
return db;
}
export function insertPortfolio(db: Database.Database, args: {
slug: string;
title?: string;
createdAtIso: string;
updatedAtIso: string;
contact: { name: string; business?: string; email: string; phone?: string; website?: string };
passwordHash: string | null;
patterns: PatternSnapshot[];
aiIntro: string | null;
}): number {
const stmt = db.prepare(`
INSERT INTO portfolios (
slug, title, created_at, updated_at,
contact_name, business_name, email, phone, website,
password_hash,
patterns_json,
ai_intro
) VALUES (
@slug, @title, @created_at, @updated_at,
@contact_name, @business_name, @email, @phone, @website,
@password_hash,
@patterns_json,
@ai_intro
)
`);
const info = stmt.run({
slug: args.slug,
title: args.title?.trim() || null,
created_at: args.createdAtIso,
updated_at: args.updatedAtIso,
contact_name: args.contact.name,
business_name: (args.contact.business ?? "").trim() || null,
email: args.contact.email,
phone: (args.contact.phone ?? "").trim() || null,
website: (args.contact.website ?? "").trim() || null,
password_hash: args.passwordHash,
patterns_json: JSON.stringify(args.patterns),
ai_intro: args.aiIntro
});
return Number(info.lastInsertRowid);
}
export function getPortfolioBySlug(db: Database.Database, slug: string): Portfolio | null {
const row = db.prepare(`SELECT * FROM portfolios WHERE slug = ?`).get(slug) as PortfolioRow | undefined;
if (!row) return null;
return {
id: row.id,
slug: row.slug,
title: row.title ?? undefined,
createdAt: row.created_at,
updatedAt: row.updated_at,
contact: {
name: row.contact_name,
business: row.business_name ?? undefined,
email: row.email,
phone: row.phone ?? undefined,
website: row.website ?? undefined
},
passwordHash: row.password_hash ?? undefined,
patterns: JSON.parse(row.patterns_json) as PatternSnapshot[],
aiIntro: row.ai_intro ?? undefined
};
}