Files
CalendarApi/sqlc/schema.sql
Michilis 2cb9d72a7f Add public/private calendars, full iCal support, and iCal URL import
- Public/private: toggle is_public via PUT /calendars/{id}; generate/clear
  public_token and return ical_url when public
- Public feed: GET /cal/{token}/feed.ics (no auth) for subscription in
  Google/Apple/Outlook calendars
- Full iCal export: use golang-ical; VALARM, ATTENDEE, all-day (VALUE=DATE),
  RRULE, DTSTAMP, CREATED, LAST-MODIFIED
- Full iCal import: parse TZID, VALUE=DATE, VALARM, ATTENDEE, RRULE
- Import from URL: POST /calendars/import-url with calendar_id + url
- Migration: unique index on public_token, calendar_subscriptions table
- Config: BASE_URL for ical_url; Calendar model + API: ical_url field
- Docs: OpenAPI, llms.txt, README, SKILL.md, about/overview

Made-with: Cursor
2026-02-28 04:48:53 +00:00

187 lines
6.0 KiB
SQL

-- 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,
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,
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,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_calendar_subscriptions_calendar_id ON calendar_subscriptions (calendar_id);