-- ============================================================================= -- Life OS - Release 1 Schema -- Self-hosted PostgreSQL on defiant-01 (Hetzner) -- ============================================================================= CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- ============================================================================= -- SYSTEM LEVEL: Context Types -- ============================================================================= CREATE TABLE context_types ( id SERIAL PRIMARY KEY, name TEXT NOT NULL UNIQUE, label TEXT NOT NULL, description TEXT, is_system BOOLEAN NOT NULL DEFAULT true, sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- ============================================================================= -- ORGANIZATIONAL HIERARCHY -- ============================================================================= CREATE TABLE domains ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT NOT NULL, color TEXT, sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE areas ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), domain_id UUID NOT NULL REFERENCES domains(id) ON DELETE CASCADE, name TEXT NOT NULL, description TEXT, status TEXT NOT NULL DEFAULT 'active', sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE projects ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), domain_id UUID NOT NULL REFERENCES domains(id) ON DELETE CASCADE, area_id UUID REFERENCES areas(id) ON DELETE SET NULL, name TEXT NOT NULL, description TEXT, status TEXT NOT NULL DEFAULT 'active', priority INTEGER NOT NULL DEFAULT 3, start_date DATE, target_date DATE, completed_at TIMESTAMPTZ, tags TEXT[], sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE tasks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), domain_id UUID NOT NULL REFERENCES domains(id) ON DELETE CASCADE, area_id UUID REFERENCES areas(id) ON DELETE SET NULL, project_id UUID REFERENCES projects(id) ON DELETE SET NULL, parent_id UUID REFERENCES tasks(id) ON DELETE SET NULL, title TEXT NOT NULL, description TEXT, priority INTEGER NOT NULL DEFAULT 3, status TEXT NOT NULL DEFAULT 'open', due_date DATE, deadline TIMESTAMPTZ, recurrence TEXT, tags TEXT[], context TEXT, is_custom_context BOOLEAN NOT NULL DEFAULT false, sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), completed_at TIMESTAMPTZ ); CREATE TABLE notes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), domain_id UUID NOT NULL REFERENCES domains(id) ON DELETE CASCADE, title TEXT NOT NULL, body TEXT, content_format TEXT NOT NULL DEFAULT 'rich', tags TEXT[], sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE lists ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), domain_id UUID NOT NULL REFERENCES domains(id) ON DELETE CASCADE, area_id UUID REFERENCES areas(id) ON DELETE SET NULL, project_id UUID REFERENCES projects(id) ON DELETE SET NULL, name TEXT NOT NULL, list_type TEXT NOT NULL DEFAULT 'checklist', description TEXT, tags TEXT[], sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE list_items ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), list_id UUID NOT NULL REFERENCES lists(id) ON DELETE CASCADE, parent_item_id UUID REFERENCES list_items(id) ON DELETE SET NULL, content TEXT NOT NULL, completed BOOLEAN NOT NULL DEFAULT false, completed_at TIMESTAMPTZ, sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE links ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), domain_id UUID NOT NULL REFERENCES domains(id) ON DELETE CASCADE, area_id UUID REFERENCES areas(id) ON DELETE SET NULL, project_id UUID REFERENCES projects(id) ON DELETE SET NULL, label TEXT NOT NULL, url TEXT NOT NULL, description TEXT, sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE files ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), filename TEXT NOT NULL, original_filename TEXT NOT NULL, storage_path TEXT NOT NULL, mime_type TEXT, size_bytes INTEGER, description TEXT, tags TEXT[], sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- ============================================================================= -- SYSTEM LEVEL: Contacts -- ============================================================================= CREATE TABLE contacts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT NOT NULL, company TEXT, role TEXT, email TEXT, phone TEXT, notes TEXT, tags TEXT[], sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- ============================================================================= -- SYSTEM LEVEL: Appointments -- ============================================================================= CREATE TABLE appointments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title TEXT NOT NULL, description TEXT, location TEXT, start_at TIMESTAMPTZ NOT NULL, end_at TIMESTAMPTZ, all_day BOOLEAN NOT NULL DEFAULT false, recurrence TEXT, tags TEXT[], sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- ============================================================================= -- SYSTEM LEVEL: Weblink Directory -- ============================================================================= CREATE TABLE weblink_folders ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), parent_id UUID REFERENCES weblink_folders(id) ON DELETE CASCADE, name TEXT NOT NULL, auto_generated BOOLEAN NOT NULL DEFAULT false, sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE weblinks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), label TEXT NOT NULL, url TEXT NOT NULL, description TEXT, tags TEXT[], sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- ============================================================================= -- SYSTEM LEVEL: Daily Focus -- ============================================================================= CREATE TABLE daily_focus ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), focus_date DATE NOT NULL, task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE, slot INTEGER, completed BOOLEAN NOT NULL DEFAULT false, note TEXT, sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- ============================================================================= -- SYSTEM LEVEL: Capture Queue -- ============================================================================= CREATE TABLE capture ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), raw_text TEXT NOT NULL, processed BOOLEAN NOT NULL DEFAULT false, converted_to_type TEXT, converted_to_id UUID, area_id UUID REFERENCES areas(id) ON DELETE SET NULL, project_id UUID REFERENCES projects(id) ON DELETE SET NULL, list_id UUID REFERENCES lists(id) ON DELETE SET NULL, sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- ============================================================================= -- SYSTEM LEVEL: Reminders -- ============================================================================= CREATE TABLE reminders ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE, remind_at TIMESTAMPTZ NOT NULL, delivered BOOLEAN NOT NULL DEFAULT false, channel TEXT NOT NULL DEFAULT 'web', created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- ============================================================================= -- JUNCTION TABLES -- ============================================================================= -- Notes <-> Projects (M2M) CREATE TABLE note_projects ( note_id UUID NOT NULL REFERENCES notes(id) ON DELETE CASCADE, project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE, is_primary BOOLEAN NOT NULL DEFAULT false, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (note_id, project_id) ); -- Files <-> any entity (polymorphic M2M) CREATE TABLE file_mappings ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), file_id UUID NOT NULL REFERENCES files(id) ON DELETE CASCADE, context_type TEXT NOT NULL, context_id UUID NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (file_id, context_type, context_id) ); -- Contacts <-> Tasks CREATE TABLE contact_tasks ( contact_id UUID NOT NULL REFERENCES contacts(id) ON DELETE CASCADE, task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE, role TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (contact_id, task_id) ); -- Contacts <-> Lists CREATE TABLE contact_lists ( contact_id UUID NOT NULL REFERENCES contacts(id) ON DELETE CASCADE, list_id UUID NOT NULL REFERENCES lists(id) ON DELETE CASCADE, role TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (contact_id, list_id) ); -- Contacts <-> List Items CREATE TABLE contact_list_items ( contact_id UUID NOT NULL REFERENCES contacts(id) ON DELETE CASCADE, list_item_id UUID NOT NULL REFERENCES list_items(id) ON DELETE CASCADE, role TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (contact_id, list_item_id) ); -- Contacts <-> Projects CREATE TABLE contact_projects ( contact_id UUID NOT NULL REFERENCES contacts(id) ON DELETE CASCADE, project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE, role TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (contact_id, project_id) ); -- Contacts <-> Appointments CREATE TABLE contact_appointments ( contact_id UUID NOT NULL REFERENCES contacts(id) ON DELETE CASCADE, appointment_id UUID NOT NULL REFERENCES appointments(id) ON DELETE CASCADE, role TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (contact_id, appointment_id) ); -- Weblinks <-> Folders (M2M) CREATE TABLE folder_weblinks ( folder_id UUID NOT NULL REFERENCES weblink_folders(id) ON DELETE CASCADE, weblink_id UUID NOT NULL REFERENCES weblinks(id) ON DELETE CASCADE, sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (folder_id, weblink_id) ); -- ============================================================================= -- INDEXES -- ============================================================================= -- Sort order indexes (used on every list render) CREATE INDEX idx_domains_sort ON domains(sort_order); CREATE INDEX idx_areas_sort ON areas(domain_id, sort_order); CREATE INDEX idx_projects_sort ON projects(domain_id, sort_order); CREATE INDEX idx_projects_area_sort ON projects(area_id, sort_order); CREATE INDEX idx_tasks_project_sort ON tasks(project_id, sort_order); CREATE INDEX idx_tasks_parent_sort ON tasks(parent_id, sort_order); CREATE INDEX idx_tasks_domain_sort ON tasks(domain_id, sort_order); CREATE INDEX idx_list_items_sort ON list_items(list_id, sort_order); CREATE INDEX idx_list_items_parent_sort ON list_items(parent_item_id, sort_order); CREATE INDEX idx_weblinks_sort ON weblink_folders(parent_id, sort_order); -- Lookup indexes CREATE INDEX idx_tasks_status ON tasks(status); CREATE INDEX idx_tasks_due_date ON tasks(due_date); CREATE INDEX idx_tasks_priority ON tasks(priority); CREATE INDEX idx_projects_status ON projects(status); CREATE INDEX idx_daily_focus_date ON daily_focus(focus_date); CREATE INDEX idx_appointments_start ON appointments(start_at); CREATE INDEX idx_capture_processed ON capture(processed); CREATE INDEX idx_file_mappings_context ON file_mappings(context_type, context_id);