Add PostgreSQL support with SQLite/Postgres database compatibility layer

- Add dbGet/dbAll helper functions for database-agnostic queries
- Add toDbBool/convertBooleansForDb for boolean type conversion
- Add toDbDate/getNow for timestamp type handling
- Add generateId that returns UUID for Postgres, nanoid for SQLite
- Update all routes to use compatibility helpers
- Add normalizeEvent to return clean number types from Postgres decimal
- Add formatPrice utility for consistent price display
- Add legal pages admin interface with RichTextEditor
- Update carousel images
- Add drizzle migration files for PostgreSQL
This commit is contained in:
Michilis
2026-02-02 03:46:35 +00:00
parent 9410e83b89
commit bafd1425c4
61 changed files with 5015 additions and 881 deletions

View File

@@ -1,3 +1,4 @@
import 'dotenv/config';
import { drizzle as drizzleSqlite } from 'drizzle-orm/better-sqlite3';
import { drizzle as drizzlePg } from 'drizzle-orm/node-postgres';
import Database from 'better-sqlite3';
@@ -29,5 +30,51 @@ if (dbType === 'postgres') {
db = drizzleSqlite(sqlite, { schema });
}
export { db };
// ==================== Database Compatibility Helpers ====================
// These functions abstract the differences between SQLite and PostgreSQL Drizzle drivers:
// - SQLite uses .get() for single result, .all() for multiple
// - PostgreSQL returns arrays directly (no .get()/.all() methods)
/**
* Get a single result from a query (works with both SQLite and PostgreSQL)
* @param query - A Drizzle query builder (e.g., db.select().from(table).where(...))
* @returns The first result or null
*/
export async function dbGet<T>(query: any): Promise<T | null> {
if (dbType === 'postgres') {
const results = await query;
return results[0] || null;
}
// SQLite - use .get()
return query.get() || null;
}
/**
* Get all results from a query (works with both SQLite and PostgreSQL)
* @param query - A Drizzle query builder (e.g., db.select().from(table).where(...))
* @returns Array of results
*/
export async function dbAll<T>(query: any): Promise<T[]> {
if (dbType === 'postgres') {
return await query;
}
// SQLite - use .all()
return query.all();
}
/**
* Check if using PostgreSQL
*/
export function isPostgres(): boolean {
return dbType === 'postgres';
}
/**
* Check if using SQLite
*/
export function isSqlite(): boolean {
return dbType === 'sqlite';
}
export { db, dbType };
export * from './schema.js';

View File

@@ -1,7 +1,10 @@
import 'dotenv/config';
import { db } from './index.js';
import { sql } from 'drizzle-orm';
const dbType = process.env.DB_TYPE || 'sqlite';
console.log(`Database type: ${dbType}`);
console.log(`Database URL: ${process.env.DATABASE_URL?.substring(0, 30)}...`);
async function migrate() {
console.log('Running migrations...');
@@ -384,6 +387,23 @@ async function migrate() {
updated_by TEXT REFERENCES users(id)
)
`);
// Legal pages table for admin-editable legal content
await (db as any).run(sql`
CREATE TABLE IF NOT EXISTS legal_pages (
id TEXT PRIMARY KEY,
slug TEXT NOT NULL UNIQUE,
title TEXT NOT NULL,
title_es TEXT,
content_text TEXT NOT NULL,
content_text_es TEXT,
content_markdown TEXT NOT NULL,
content_markdown_es TEXT,
updated_at TEXT NOT NULL,
updated_by TEXT REFERENCES users(id),
created_at TEXT NOT NULL
)
`);
} else {
// PostgreSQL migrations
await (db as any).execute(sql`
@@ -716,6 +736,23 @@ async function migrate() {
updated_by UUID REFERENCES users(id)
)
`);
// Legal pages table for admin-editable legal content
await (db as any).execute(sql`
CREATE TABLE IF NOT EXISTS legal_pages (
id UUID PRIMARY KEY,
slug VARCHAR(100) NOT NULL UNIQUE,
title VARCHAR(255) NOT NULL,
title_es VARCHAR(255),
content_text TEXT NOT NULL,
content_text_es TEXT,
content_markdown TEXT NOT NULL,
content_markdown_es TEXT,
updated_at TIMESTAMP NOT NULL,
updated_by UUID REFERENCES users(id),
created_at TIMESTAMP NOT NULL
)
`);
}
console.log('Migrations completed successfully!');

View File

@@ -249,6 +249,21 @@ export const sqliteEmailSettings = sqliteTable('email_settings', {
updatedAt: text('updated_at').notNull(),
});
// Legal Pages table for admin-editable legal content
export const sqliteLegalPages = sqliteTable('legal_pages', {
id: text('id').primaryKey(),
slug: text('slug').notNull().unique(),
title: text('title').notNull(), // English title
titleEs: text('title_es'), // Spanish title
contentText: text('content_text').notNull(), // Plain text edited by admin (English)
contentTextEs: text('content_text_es'), // Plain text edited by admin (Spanish)
contentMarkdown: text('content_markdown').notNull(), // Generated markdown for public display (English)
contentMarkdownEs: text('content_markdown_es'), // Generated markdown for public display (Spanish)
updatedAt: text('updated_at').notNull(),
updatedBy: text('updated_by').references(() => sqliteUsers.id),
createdAt: text('created_at').notNull(),
});
// Site Settings table for global website configuration
export const sqliteSiteSettings = sqliteTable('site_settings', {
id: text('id').primaryKey(),
@@ -512,6 +527,21 @@ export const pgEmailSettings = pgTable('email_settings', {
updatedAt: timestamp('updated_at').notNull(),
});
// Legal Pages table for admin-editable legal content
export const pgLegalPages = pgTable('legal_pages', {
id: uuid('id').primaryKey(),
slug: varchar('slug', { length: 100 }).notNull().unique(),
title: varchar('title', { length: 255 }).notNull(), // English title
titleEs: varchar('title_es', { length: 255 }), // Spanish title
contentText: pgText('content_text').notNull(), // Plain text edited by admin (English)
contentTextEs: pgText('content_text_es'), // Plain text edited by admin (Spanish)
contentMarkdown: pgText('content_markdown').notNull(), // Generated markdown for public display (English)
contentMarkdownEs: pgText('content_markdown_es'), // Generated markdown for public display (Spanish)
updatedAt: timestamp('updated_at').notNull(),
updatedBy: uuid('updated_by').references(() => pgUsers.id),
createdAt: timestamp('created_at').notNull(),
});
// Site Settings table for global website configuration
export const pgSiteSettings = pgTable('site_settings', {
id: uuid('id').primaryKey(),
@@ -556,6 +586,7 @@ export const magicLinkTokens = dbType === 'postgres' ? pgMagicLinkTokens : sqlit
export const userSessions = dbType === 'postgres' ? pgUserSessions : sqliteUserSessions;
export const invoices = dbType === 'postgres' ? pgInvoices : sqliteInvoices;
export const siteSettings = dbType === 'postgres' ? pgSiteSettings : sqliteSiteSettings;
export const legalPages = dbType === 'postgres' ? pgLegalPages : sqliteLegalPages;
// Type exports
export type User = typeof sqliteUsers.$inferSelect;
@@ -584,3 +615,5 @@ export type Invoice = typeof sqliteInvoices.$inferSelect;
export type NewInvoice = typeof sqliteInvoices.$inferInsert;
export type SiteSettings = typeof sqliteSiteSettings.$inferSelect;
export type NewSiteSettings = typeof sqliteSiteSettings.$inferInsert;
export type LegalPage = typeof sqliteLegalPages.$inferSelect;
export type NewLegalPage = typeof sqliteLegalPages.$inferInsert;