← back to Ventura Corridor

db/migrations/022_appointments.sql

65 lines

-- 022_appointments.sql
-- Self-contained appointment-booking system.
-- All tables prefixed with appt_ to stay isolated from existing schema.

CREATE TABLE IF NOT EXISTS appt_appointments (
  id              BIGSERIAL PRIMARY KEY,
  name            TEXT NOT NULL,
  email           TEXT NOT NULL,
  phone           TEXT,
  business_id     BIGINT,                                   -- nullable; not FK'd so reversible
  owner_email     TEXT NOT NULL,                            -- which calendar owner this is for
  requested_at    TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  slot_start      TIMESTAMPTZ NOT NULL,
  slot_end        TIMESTAMPTZ NOT NULL,
  status          TEXT NOT NULL DEFAULT 'confirmed'         -- confirmed | cancelled | pending
                  CHECK (status IN ('confirmed','cancelled','pending')),
  notes           TEXT,
  google_event_id TEXT,                                     -- nullable; set if pushed to Google Calendar
  cancel_token    TEXT NOT NULL DEFAULT md5(random()::text || clock_timestamp()::text),
  created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS appt_appointments_owner_slot_idx
  ON appt_appointments (owner_email, slot_start);
CREATE INDEX IF NOT EXISTS appt_appointments_status_idx
  ON appt_appointments (status);

CREATE TABLE IF NOT EXISTS appt_availability (
  id                BIGSERIAL PRIMARY KEY,
  owner_email       TEXT NOT NULL,
  day_of_week       SMALLINT NOT NULL CHECK (day_of_week BETWEEN 0 AND 6), -- 0 = Sunday, 6 = Saturday
  start_min         INT NOT NULL CHECK (start_min BETWEEN 0 AND 1439),     -- minutes from midnight, local owner tz (America/Los_Angeles)
  end_min           INT NOT NULL CHECK (end_min BETWEEN 1 AND 1440),
  slot_duration_min INT NOT NULL DEFAULT 30 CHECK (slot_duration_min BETWEEN 5 AND 240),
  created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  UNIQUE (owner_email, day_of_week, start_min, end_min)
);

CREATE INDEX IF NOT EXISTS appt_availability_owner_idx
  ON appt_availability (owner_email);

CREATE TABLE IF NOT EXISTS appt_oauth_tokens (
  owner_email   TEXT PRIMARY KEY,
  access_token  TEXT NOT NULL,
  refresh_token TEXT,
  expires_at    TIMESTAMPTZ NOT NULL,
  scope         TEXT,
  token_type    TEXT DEFAULT 'Bearer',
  updated_at    TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- OAuth state nonce — short-lived row to bind ?state to a pending start request.
CREATE TABLE IF NOT EXISTS appt_oauth_state (
  state        TEXT PRIMARY KEY,
  owner_email  TEXT NOT NULL,
  created_at   TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Seed the test owner: info@designerwallcoverings.com, 9 AM – 5 PM weekdays, 30-min slots.
-- 9 AM = 540 min, 5 PM = 1020 min. Mon=1 ... Fri=5.
INSERT INTO appt_availability (owner_email, day_of_week, start_min, end_min, slot_duration_min)
SELECT 'info@designerwallcoverings.com', d, 540, 1020, 30
FROM generate_series(1, 5) AS d
ON CONFLICT (owner_email, day_of_week, start_min, end_min) DO NOTHING;