-- Calendar & Contacts API Schema CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- Users CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email TEXT NOT NULL, password_hash TEXT NOT NULL, timezone TEXT NOT NULL DEFAULT 'UTC', is_active BOOLEAN NOT NULL DEFAULT true, week_start_day SMALLINT NOT NULL DEFAULT 0, date_format TEXT NOT NULL DEFAULT 'MM/dd/yyyy', time_format TEXT NOT NULL DEFAULT '12h', default_event_duration_minutes INTEGER NOT NULL DEFAULT 60, default_reminder_minutes INTEGER NOT NULL DEFAULT 10, show_weekends BOOLEAN NOT NULL DEFAULT true, working_hours_start TEXT NOT NULL DEFAULT '09:00', working_hours_end TEXT NOT NULL DEFAULT '17:00', notifications_email BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ ); CREATE UNIQUE INDEX idx_users_email ON users (email) WHERE deleted_at IS NULL; -- Refresh Tokens CREATE TABLE refresh_tokens ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id), token_hash TEXT NOT NULL UNIQUE, expires_at TIMESTAMPTZ NOT NULL, revoked_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_refresh_tokens_user_id ON refresh_tokens (user_id); -- API Keys CREATE TABLE api_keys ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id), name TEXT NOT NULL, key_hash TEXT NOT NULL UNIQUE, scopes JSONB NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), revoked_at TIMESTAMPTZ ); CREATE INDEX idx_api_keys_user_id ON api_keys (user_id); -- Calendars CREATE TABLE calendars ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), owner_id UUID NOT NULL REFERENCES users(id), name TEXT NOT NULL, color TEXT NOT NULL DEFAULT '#3B82F6', is_public BOOLEAN NOT NULL DEFAULT false, public_token TEXT, count_for_availability BOOLEAN NOT NULL DEFAULT true, default_reminder_minutes INTEGER, sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ ); CREATE INDEX idx_calendars_owner_id ON calendars (owner_id); CREATE UNIQUE INDEX idx_calendars_public_token ON calendars (public_token) WHERE public_token IS NOT NULL; -- Calendar Members CREATE TABLE calendar_members ( calendar_id UUID NOT NULL REFERENCES calendars(id), user_id UUID NOT NULL REFERENCES users(id), role TEXT NOT NULL CHECK (role IN ('owner', 'editor', 'viewer')), PRIMARY KEY (calendar_id, user_id) ); -- Events CREATE TABLE events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), calendar_id UUID NOT NULL REFERENCES calendars(id), title TEXT NOT NULL, description TEXT, location TEXT, start_time TIMESTAMPTZ NOT NULL, end_time TIMESTAMPTZ NOT NULL, timezone TEXT NOT NULL DEFAULT 'UTC', all_day BOOLEAN NOT NULL DEFAULT false, recurrence_rule TEXT, tags TEXT[] NOT NULL DEFAULT '{}', created_by UUID NOT NULL REFERENCES users(id), updated_by UUID NOT NULL REFERENCES users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ ); CREATE INDEX idx_events_calendar_start ON events (calendar_id, start_time); CREATE INDEX idx_events_start_time ON events (start_time); CREATE INDEX idx_events_tags ON events USING GIN (tags); -- Event Reminders CREATE TABLE event_reminders ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), event_id UUID NOT NULL REFERENCES events(id), minutes_before INTEGER NOT NULL CHECK (minutes_before >= 0 AND minutes_before <= 10080) ); CREATE INDEX idx_event_reminders_event_id ON event_reminders (event_id); -- Event Attendees CREATE TABLE event_attendees ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), event_id UUID NOT NULL REFERENCES events(id), user_id UUID REFERENCES users(id), email TEXT, status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'accepted', 'declined', 'tentative')) ); CREATE INDEX idx_event_attendees_event_id ON event_attendees (event_id); -- Event Exceptions (for recurrence) CREATE TABLE event_exceptions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), event_id UUID NOT NULL REFERENCES events(id), exception_date DATE NOT NULL, action TEXT NOT NULL DEFAULT 'skip' CHECK (action IN ('skip')) ); CREATE INDEX idx_event_exceptions_event_id ON event_exceptions (event_id); -- Event Attachments CREATE TABLE event_attachments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), event_id UUID NOT NULL REFERENCES events(id), file_url TEXT NOT NULL ); CREATE INDEX idx_event_attachments_event_id ON event_attachments (event_id); -- Contacts CREATE TABLE contacts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), owner_id UUID NOT NULL REFERENCES users(id), first_name TEXT, last_name TEXT, email TEXT, phone TEXT, company TEXT, notes TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ ); CREATE INDEX idx_contacts_owner_id ON contacts (owner_id); CREATE INDEX idx_contacts_search ON contacts (owner_id, first_name, last_name, email, company); -- Booking Links CREATE TABLE booking_links ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), calendar_id UUID NOT NULL REFERENCES calendars(id), token TEXT NOT NULL UNIQUE, duration_minutes INTEGER NOT NULL, buffer_minutes INTEGER NOT NULL DEFAULT 0, timezone TEXT NOT NULL DEFAULT 'UTC', working_hours JSONB NOT NULL DEFAULT '{}', active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_booking_links_token ON booking_links (token); -- Audit Logs CREATE TABLE audit_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), entity_type TEXT NOT NULL, entity_id UUID NOT NULL, action TEXT NOT NULL, user_id UUID NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_audit_logs_entity ON audit_logs (entity_type, entity_id); -- Calendar Subscriptions (external iCal URL sources) CREATE TABLE calendar_subscriptions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), calendar_id UUID NOT NULL REFERENCES calendars(id), source_url TEXT NOT NULL, last_synced_at TIMESTAMPTZ, sync_interval_minutes INTEGER, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_calendar_subscriptions_calendar_id ON calendar_subscriptions (calendar_id); -- Projects (Layer 2) CREATE TABLE projects ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), owner_id UUID NOT NULL REFERENCES users(id), name TEXT NOT NULL, color TEXT NOT NULL DEFAULT '#3B82F6', is_shared BOOLEAN NOT NULL DEFAULT false, deadline TIMESTAMPTZ, sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ ); CREATE INDEX idx_projects_owner_id ON projects (owner_id); -- Project Members (Layer 2 shared projects) CREATE TABLE project_members ( project_id UUID NOT NULL REFERENCES projects(id), user_id UUID NOT NULL REFERENCES users(id), role TEXT NOT NULL CHECK (role IN ('owner', 'editor', 'viewer')), PRIMARY KEY (project_id, user_id) ); CREATE INDEX idx_project_members_project_id ON project_members (project_id); -- Tags (Layer 2) CREATE TABLE tags ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), owner_id UUID NOT NULL REFERENCES users(id), name TEXT NOT NULL, color TEXT NOT NULL DEFAULT '#6B7280', created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_tags_owner_id ON tags (owner_id); -- Tasks (Layer 1 core) CREATE TABLE tasks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), owner_id UUID NOT NULL REFERENCES users(id), title TEXT NOT NULL, description TEXT, status TEXT NOT NULL DEFAULT 'todo' CHECK (status IN ('todo', 'in_progress', 'done', 'archived')), priority TEXT NOT NULL DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high', 'critical')), due_date TIMESTAMPTZ, completed_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ, project_id UUID REFERENCES projects(id), parent_id UUID REFERENCES tasks(id), sort_order INTEGER NOT NULL DEFAULT 0, recurrence_rule TEXT ); CREATE INDEX idx_tasks_owner_id ON tasks (owner_id); CREATE INDEX idx_tasks_status ON tasks (owner_id, status) WHERE deleted_at IS NULL; CREATE INDEX idx_tasks_priority ON tasks (owner_id, priority) WHERE deleted_at IS NULL; CREATE INDEX idx_tasks_due_date ON tasks (owner_id, due_date) WHERE deleted_at IS NULL; CREATE INDEX idx_tasks_project_id ON tasks (project_id) WHERE deleted_at IS NULL; CREATE INDEX idx_tasks_parent_id ON tasks (parent_id) WHERE deleted_at IS NULL; -- Task Tags (many-to-many) CREATE TABLE task_tags ( task_id UUID NOT NULL REFERENCES tasks(id), tag_id UUID NOT NULL REFERENCES tags(id), PRIMARY KEY (task_id, tag_id) ); CREATE INDEX idx_task_tags_task_id ON task_tags (task_id); CREATE INDEX idx_task_tags_tag_id ON task_tags (tag_id); -- Task Dependencies (Layer 3) CREATE TABLE task_dependencies ( task_id UUID NOT NULL REFERENCES tasks(id), blocks_task_id UUID NOT NULL REFERENCES tasks(id), PRIMARY KEY (task_id, blocks_task_id), CHECK (task_id != blocks_task_id) ); CREATE INDEX idx_task_dependencies_task_id ON task_dependencies (task_id); CREATE INDEX idx_task_dependencies_blocks ON task_dependencies (blocks_task_id); -- Task Reminders (Layer 2) CREATE TABLE task_reminders ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), task_id UUID NOT NULL REFERENCES tasks(id), type TEXT NOT NULL CHECK (type IN ('push', 'email', 'webhook', 'telegram', 'nostr')), config JSONB NOT NULL DEFAULT '{}', scheduled_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_task_reminders_task_id ON task_reminders (task_id); CREATE INDEX idx_task_reminders_scheduled ON task_reminders (scheduled_at); -- Task Webhooks (Layer 3) CREATE TABLE task_webhooks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), owner_id UUID NOT NULL REFERENCES users(id), url TEXT NOT NULL, events JSONB NOT NULL DEFAULT '[]', secret TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_task_webhooks_owner_id ON task_webhooks (owner_id);