-- +goose Up -- Tenant memberships keyed by the auth subject (Neon Auth `sub`). -- The auth middleware resolves JWT subjects to memberships through users.neon_subject. CREATE TABLE IF NOT EXISTS tenant_memberships ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, user_neon_subject text NOT NULL, role text NOT NULL DEFAULT 'staff', joined_at timestamptz NOT NULL DEFAULT now(), UNIQUE (tenant_id, user_neon_subject) ); CREATE INDEX IF NOT EXISTS idx_tenant_memberships_subject ON tenant_memberships (user_neon_subject); -- Unguessable per-booking management token used by /v1/public/bookings/:reference endpoints. ALTER TABLE bookings ADD COLUMN IF NOT EXISTS manage_token text; CREATE UNIQUE INDEX IF NOT EXISTS idx_bookings_manage_token ON bookings (manage_token) WHERE manage_token IS NOT NULL; -- Backfill tokens for pre-existing bookings. UPDATE bookings SET manage_token = replace(gen_random_uuid()::text, '-', '') || replace(gen_random_uuid()::text, '-', '') WHERE manage_token IS NULL; -- locations.updated_at is written by the repository update path. ALTER TABLE locations ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(); -- Demo tenant membership so demo-owner resolves to Studio Atelier. INSERT INTO tenant_memberships (tenant_id, user_neon_subject, role) SELECT t.id, u.neon_subject, 'owner' FROM tenants t JOIN users u ON u.neon_subject = 'demo-owner' WHERE t.slug = 'studio-atelier' ON CONFLICT (tenant_id, user_neon_subject) DO NOTHING; -- +goose Down ALTER TABLE locations DROP COLUMN IF EXISTS updated_at; DROP INDEX IF EXISTS idx_bookings_manage_token; ALTER TABLE bookings DROP COLUMN IF EXISTS manage_token; DROP TABLE IF EXISTS tenant_memberships;