import pg from 'pg' const { Pool } = pg export const pool = new Pool({ connectionString: process.env.DATABASE_URL }) export async function initDb() { await pool.query(` CREATE TABLE IF NOT EXISTS calendars ( id SERIAL PRIMARY KEY, slug TEXT NOT NULL UNIQUE, name TEXT NOT NULL, color TEXT NOT NULL, is_system BOOLEAN NOT NULL DEFAULT FALSE, deleted_at TIMESTAMPTZ, created_by TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS events ( id SERIAL PRIMARY KEY, calendar_id INT NOT NULL REFERENCES calendars(id), uid TEXT NOT NULL UNIQUE, -- iCalendar UID, e.g. '@calendar.hotelnumberfour.com' title TEXT NOT NULL, description TEXT, location TEXT, start_at TIMESTAMPTZ NOT NULL, end_at TIMESTAMPTZ NOT NULL, all_day BOOLEAN NOT NULL DEFAULT FALSE, sequence INT NOT NULL DEFAULT 0, created_by TEXT, -- email created_by_name TEXT, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ ); CREATE INDEX IF NOT EXISTS events_calendar_idx ON events (calendar_id); CREATE INDEX IF NOT EXISTS events_start_idx ON events (start_at); CREATE INDEX IF NOT EXISTS events_deleted_idx ON events (deleted_at); CREATE TABLE IF NOT EXISTS event_departments ( event_id INT NOT NULL REFERENCES events(id) ON DELETE CASCADE, department_id TEXT NOT NULL, department_name TEXT NOT NULL, PRIMARY KEY (event_id, department_id) ); CREATE TABLE IF NOT EXISTS event_assignees ( event_id INT NOT NULL REFERENCES events(id) ON DELETE CASCADE, user_email TEXT NOT NULL, user_name TEXT, PRIMARY KEY (event_id, user_email) ); CREATE TABLE IF NOT EXISTS event_attachments ( id SERIAL PRIMARY KEY, event_id INT NOT NULL REFERENCES events(id) ON DELETE CASCADE, filename TEXT NOT NULL, stored_path TEXT NOT NULL, -- relative path under uploads dir mime_type TEXT, size_bytes INT, uploaded_by TEXT, -- email created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS caldav_credentials ( id SERIAL PRIMARY KEY, user_email TEXT NOT NULL, username TEXT NOT NULL UNIQUE, -- generated, e.g. '-' password_hash TEXT NOT NULL, -- bcrypt label TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), last_used_at TIMESTAMPTZ ); CREATE TABLE IF NOT EXISTS activity_log ( id SERIAL PRIMARY KEY, actor_email TEXT, actor_name TEXT, action TEXT NOT NULL, -- created | updated | deleted entity_type TEXT NOT NULL, -- event | calendar | attachment | caldav_credential entity_id INT, calendar_id INT, summary TEXT NOT NULL, details JSONB, source TEXT NOT NULL DEFAULT 'web', -- web | caldav created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS activity_log_calendar_idx ON activity_log (calendar_id, created_at DESC); CREATE INDEX IF NOT EXISTS activity_log_entity_idx ON activity_log (entity_type, entity_id); `) await seedDefaults() } async function seedDefaults() { await pool.query( `INSERT INTO calendars (slug, name, color, is_system) VALUES ('bank-holidays', 'Bank Holidays', '#64748b', TRUE) ON CONFLICT (slug) DO NOTHING` ) await pool.query( `INSERT INTO calendars (slug, name, color, is_system) VALUES ('general', 'All Staff', '#c9a84c', FALSE) ON CONFLICT (slug) DO NOTHING` ) }