Skip to content
TARGET-DATA

Target data model

This is the schema that ADR-001 to ADR-011 add up to. It keeps the stack (Nuxt 4, tRPC, Drizzle, Neon Postgres, Vercel) and every row of production data. The path from today's schema to this one is the migration plan; the decisions behind each shape are linked per context.

Conventions used in every table (ADR-002): uuid primary keys (gen_random_uuid()); created_at/updated_at are timestamptz NOT NULL DEFAULT now() (updated_at maintained by one trigger); every FK has an explicit ON DELETE and an index; closed value sets are text + CHECK; scalar lists are text[] (GIN index where filtered); documents are jsonb; emails and handles are citext (extension citext). Columns listed below omit created_at/updated_at unless they matter.

Overview

Rendering diagram…

Diagram source
erDiagram
  dancers ||--o{ sessions : ""
  dancers ||--o{ auth_tokens : ""
  handles |o--o| dancers : ""
  handles |o--o| profiles : ""
  dancers ||--o{ dancer_styles : ""
  dance_styles ||--o{ dancer_styles : ""
  cities ||--o{ dancers : ""
  cities ||--o{ profiles : ""
  cities ||--o{ events : ""
  profiles ||--o{ profile_members : ""
  dancers ||--o{ profile_members : ""
  profiles ||--o{ profile_claims : ""
  profiles ||--o{ profile_affiliations : ""
  profiles ||--o{ follows : ""
  dancers ||--o{ follows : ""
  event_series ||--o{ events : ""
  profiles |o--o{ events : "venue / organiser"
  events ||--o{ event_artists : ""
  profiles |o--o{ event_artists : ""
  events ||--o| festival_details : ""
  events ||--o{ programme_sessions : ""
  programme_sessions ||--o{ session_artists : ""
  events ||--o{ ticket_types : ""
  events ||--o{ orders : ""
  orders ||--|{ order_items : ""
  ticket_types ||--o{ order_items : ""
  dancers |o--o{ orders : ""
  orders |o--o| referrals : ""
  events ||--o{ attendances : ""
  dancers |o--o{ attendances : ""
  orders |o--o| attendances : ""
  events ||--o{ waves : ""
  events ||--o{ ride_shares : ""
  events ||--o{ roommate_requests : ""
  events ||--o{ dinners : ""
  dinners ||--o{ dinner_signups : ""
  dinners ||--o{ dinner_groups : ""
  dinner_groups ||--o{ dinner_group_members : ""
  dancers ||--o{ session_picks : ""
  programme_sessions ||--o{ session_picks : ""
  dancers ||--o{ series_enrolments : ""
  event_series ||--o{ series_enrolments : ""
  dancers ||--o{ dancer_goals : ""
  profiles ||--o{ bookable_spaces : ""
  bookable_spaces ||--o{ availability_slots : ""
  bookable_spaces ||--o{ booking_requests : ""
  booking_requests |o--o| events : ""
  profiles ||--o{ moderator_elections : ""
  moderator_elections ||--o{ election_candidates : ""
  election_candidates ||--o{ election_votes : ""
  moderator_elections ||--o{ election_vote_history : ""
  profiles ||--o{ guideline_versions : ""
  cities ||--o{ city_videos : ""
  city_videos ||--o{ video_votes : ""
  cities ||--o{ city_battle_votes : ""
  cities ||--o{ giveaways : ""
  giveaways ||--o{ giveaway_entries : ""
  events |o--o{ reviews : ""
  profiles |o--o{ reviews : ""
  cities ||--o{ recommendation_requests : ""
  cities ||--o{ community_groups : ""
  community_groups ||--o{ community_group_reports : ""
  profiles |o--o{ gigs : ""
  cities |o--o{ gigs : ""
  cities ||--o{ hangouts : ""
  hangouts ||--o{ hangout_rsvps : ""
  festival_submissions |o--o| events : ""
  inbound_requests ||--o{ deliveries : ""
  gigs |o--o{ inbound_requests : ""

Identity — ADR-003, ADR-004

dancers (kept, changed)

ColumnTypeConstraints / notes
iduuidPK
emailcitextNOT NULL, UNIQUE
email_verified_attimestamptznew; set when a login/verify token is consumed
nametextNOT NULL
photo_urltextrenamed from photo
city_iduuidFK cities ON DELETE SET NULL; replaces city (ADR-005)
partner_roletextCHECK IN (lead,follow,both); renamed from role
bio, instagram, youtube, websitetextlengths checked
profile_publicbooleanNOT NULL DEFAULT true
password_salt, password_hashtextNULL = no password (no '' sentinel)
intenttextCHECK against the onboarding persona list
onboarded_attimestamptz
is_adminbooleanNOT NULL DEFAULT false
neon_auth_idtextUNIQUE; kept for a future provider (ADR-003)
source_refjsonbadded to schema.ts (exists in prod, 00-schema.mjs:5)

Removed: username (→ handles), magic_token* (→ auth_tokens), dance_styles/dance_levels (→ dancer_styles), city, role, photo.

dancer_styles (new)

ColumnTypeConstraints / notes
dancer_iduuidPK, FK dancers ON DELETE CASCADE
style_keytextPK, FK dance_styles ON DELETE RESTRICT
leveltextCHECK IN (Beginner,Intermediate,Advanced) or NULL

Index: (style_key, level) for the organiser style-and-level mix (RAZ-79).

sessions (kept, changed)

ColumnTypeConstraints / notes
iduuidPK
dancer_iduuidNOT NULL, FK dancers ON DELETE CASCADE, indexed
token_hashbyteaNOT NULL, UNIQUE (SHA-256); replaces plaintext token
expires_attimestamptzNOT NULL
last_seen_at, revoked_attimestamptz
user_agenttext

auth_tokens (new)

ColumnTypeConstraints / notes
iduuidPK
dancer_iduuidFK dancers ON DELETE CASCADE; NULL for a sign-up not yet consumed
emailcitextNOT NULL
purposetextCHECK IN (login,reset_password,verify_email)
token_hashbyteaNOT NULL, UNIQUE
payloadjsonbsign-up fields, applied on consume
expires_attimestamptzNOT NULL
used_attimestamptzsingle use

Index: (email, purpose); expired rows purged daily.

handles (new)

ColumnTypeConstraints / notes
handlecitextPK; CHECK format ^[a-z0-9][a-z0-9._-]{1,39}$, not in reserved list
dancer_iduuidUNIQUE, FK dancers ON DELETE CASCADE
profile_iduuidUNIQUE, FK profiles ON DELETE CASCADE
CHECK num_nonnulls(dancer_id, profile_id) = 1

rate_limits (new, platform)

ColumnTypeConstraints / notes
keytextPK part — procedure + IP (+ email for auth)
window_starttimestamptzPK part
hitsintegerNOT NULL DEFAULT 0

Profiles and places — ADR-004, ADR-005

profiles (kept, changed)

ColumnTypeConstraints / notes
iduuidPK
typetextCHECK IN (venue,artist,organizer)
rolestext[]NOT NULL DEFAULT '{}'; CHECK ⊆ role vocabulary; GIN
nametextNOT NULL
city_iduuidFK cities ON DELETE SET NULL; replaces city, city_slug
photo_url, bio, map_url, guidelinestext
styles, languages, amenitiestext[]NOT NULL DEFAULT '{}'; GIN on styles, amenities
address_line, postal_codetextvenues
country_codechar(2)
lat, lngdouble precision
google_place_idtextUNIQUE where not null
floor_type, venue_typetextCHECK vocabularies
booking_modeltextCHECK IN (free,commercial) NOT NULL DEFAULT commercial
socialsjsonblist of {platform, url}
statustextCHECK IN (visible,hidden,stub) NOT NULL DEFAULT visible
claimed_attimestamptzreplaces claimed boolean
source_refjsonbprovenance (v4/v3)

Removed: username (→ handles), moderator_name/handle/since (→ profile_members role moderator), address (after reads move to the structured columns), claimed, city, city_slug. Indexes: (type, city_id) WHERE status = 'visible'.

profile_members (new)

ColumnTypeConstraints / notes
profile_iduuidPK, FK profiles ON DELETE CASCADE
dancer_iduuidPK, FK dancers ON DELETE CASCADE, indexed
roletextPK; CHECK IN (owner,editor,moderator)
term_start, term_enddatemoderator terms
granted_byuuidFK dancers ON DELETE SET NULL
granted_attimestamptzNOT NULL DEFAULT now()

profile_claims (new)

ColumnTypeConstraints / notes
iduuidPK
profile_iduuidNOT NULL, FK profiles ON DELETE CASCADE
dancer_iduuidNOT NULL, FK dancers ON DELETE CASCADE
statustextCHECK IN (pending,approved,rejected)
methodtextCHECK IN (admin_review) (more later)
evidencetext
decided_byuuidFK dancers ON DELETE SET NULL
decided_attimestamptz

Unique: one pending claim per (profile_id, dancer_id).

profile_affiliations (new)

ColumnTypeConstraints / notes
artist_profile_iduuidPK, FK profiles ON DELETE CASCADE
organizer_profile_iduuidPK, FK profiles ON DELETE CASCADE
kindtextPK; CHECK IN (teaches_at,resident,member)

follows (renamed from artist_follows, merged with legacy follows)

ColumnTypeConstraints / notes
dancer_iduuidPK, FK dancers ON DELETE CASCADE
profile_iduuidPK, FK profiles ON DELETE CASCADE, indexed
source_refjsonbv4 provenance for merged edges

legacy_follows keeps unresolved v4 edges (dancer → dancer handle) until the product decides on dancer-to-dancer follows.

cities (kept; applied to production in step 11)

As in schema.ts:739-759 (Google place id, slug, English and local name, country, region, lat/lng, alt_names), with alt_names as text[] (GIN) instead of jsonb.

dance_styles (new)

ColumnTypeConstraints / notes
keytextPK (e.g. salsa-cubana)
labeltextNOT NULL
familytexte.g. salsa, bachata, kizomba
sortinteger

Seeded from docs/research/2026-10-12-dance-styles.md and the v3 STYLE_MAP. text[] style columns on events and profiles are validated against it in the write path.

Events and festivals — ADR-006, ADR-007

events (kept, reshaped)

ColumnTypeConstraints / notes
iduuidPK
slugtextNOT NULL, UNIQUE
titletextNOT NULL (renamed from name)
kindtextCHECK IN (social,party,class,course,workshop,festival,congress,weekender,concert,show,private)
statustextCHECK IN (draft,published,cancelled,archived); replaces archived + published
descriptiontext
cover_urltext
starts_at, ends_attimestamptzCHECK ends_at >= starts_at
timezonetextNOT NULL when status = 'published' (CHECK)
series_iduuidFK event_series ON DELETE SET NULL
city_iduuidFK cities ON DELETE SET NULL
venue_profile_iduuidFK profiles ON DELETE SET NULL
space_iduuidFK bookable_spaces ON DELETE SET NULL
venue_name, venue_addresstextsnapshot when no venue profile
venue_lat, venue_lngdouble precision
organizer_profile_iduuidFK profiles ON DELETE SET NULL
organizer_nametextsnapshot
stylestext[]NOT NULL DEFAULT '{}', GIN
price_texttextdisplay text (was price)
price_from_centsintegerCHECK ≥ 0
currencychar(3)
ticket_url, linktext
sourcetextCHECK IN (wedance,wedance-v3,wedance-v4,booking) NOT NULL
source_idtextUNIQUE with source
source_refjsonb
sync_locked_attimestamptzsync stops overwriting once set
synced_attimestamptz
created_byuuidFK dancers ON DELETE SET NULL

Removed: is_festival (= kind), archived, published, city, city_slug, country, venue_username, organizer_username, artists, type, text series_id. Indexes: (city_id, starts_at) WHERE status = 'published', (venue_profile_id, starts_at), (organizer_profile_id, starts_at), (series_id), (kind, starts_at).

event_series (new)

ColumnTypeConstraints / notes
iduuidPK
titletextNOT NULL
kindtextsame CHECK as events.kind
rruletextRFC 5545; NULL for irregular series
timezonetextNOT NULL
city_id, venue_profile_id, organizer_profile_iduuidFKs ON DELETE SET NULL
statustextCHECK IN (active,paused,ended)
source, source_idtextUNIQUE pair (v3 seriesId)

event_artists (new)

ColumnTypeConstraints / notes
iduuidPK
event_iduuidNOT NULL, FK events ON DELETE CASCADE
profile_iduuidFK profiles ON DELETE SET NULL, indexed
display_nametextNOT NULL (name as listed upstream)
roletextCHECK IN (teacher,dj,performer,mc,band,host)
sortintegerNOT NULL DEFAULT 0

Unique (event_id, profile_id) WHERE profile_id IS NOT NULL.

festival_details (new, replaces festivals)

ColumnTypeConstraints / notes
event_iduuidPK, FK events ON DELETE CASCADE
legacy_festival_iduuidUNIQUE (old festivals.id, for backfills)
logo_urltext
accent_colortextCHECK hex colour
early_bird_deadlinedatedisplay; prices live in ticket_types
early_bird_offer_nametext
tickettailor_event_idtextUNIQUE; replaces the hardcoded map

programme_sessions (new)

ColumnTypeConstraints / notes
iduuidPK
event_iduuidNOT NULL, FK events ON DELETE CASCADE
titletextNOT NULL
kindtextCHECK IN (workshop,class,bootcamp,show,party,talk)
style_keytextFK dance_styles ON DELETE SET NULL
leveltextCHECK IN (Beginner,Intermediate,Advanced,Open)
starts_at, ends_attimestamptzNOT NULL
roomtext
capacityintegerCHECK > 0
descriptiontext
sortinteger
source_refjsonbmock file + id for the seed

Index: (event_id, starts_at).

session_artists (new)

ColumnTypeConstraints / notes
iduuidPK
session_iduuidNOT NULL, FK programme_sessions ON DELETE CASCADE
profile_iduuidFK profiles ON DELETE SET NULL
display_nametextNOT NULL
sortinteger

festival_submissions (kept, changed)

ColumnTypeConstraints / notes
iduuidPK
slug, nametext
submitted_byuuidFK dancers ON DELETE SET NULL
submitted_by_emailcitext
payloadjsonbwas json
statustextCHECK IN (pending,reviewing,onboarded,declined)
event_iduuidFK events ON DELETE SET NULL, UNIQUE
reviewed_byuuidFK dancers ON DELETE SET NULL
reviewed_attimestamptz

Ticketing — ADR-007

ticket_types (new)

ColumnTypeConstraints / notes
iduuidPK
event_iduuidNOT NULL, FK events ON DELETE CASCADE
kindtextCHECK IN (admission,social_pass)
nametextNOT NULL; UNIQUE with event_id
descriptiontext
price_centsintegerNOT NULL, CHECK ≥ 0
currencychar(3)NOT NULL DEFAULT EUR
capacityintegerCHECK > 0; NULL = unlimited
sale_starts_at, sale_ends_attimestamptz
statustextCHECK IN (on_sale,sold_out,hidden)
sortinteger
provider, provider_reftexte.g. TicketTailor ticket type id

orders (new)

ColumnTypeConstraints / notes
iduuidPK
dancer_iduuidFK dancers ON DELETE SET NULL (+ scrub buyer_email)
event_iduuidNOT NULL, FK events ON DELETE RESTRICT
statustextCHECK IN (pending,paid,expired,refunded,cancelled)
providertextCHECK IN (stripe,tickettailor,free)
provider_reftextStripe session id / TT order id; UNIQUE with provider
idempotency_keytextUNIQUE
buyer_emailcitext
subtotal_cents, discount_cents, total_centsintegerNOT NULL, CHECK ≥ 0
currencychar(3)NOT NULL
referral_iduuidFK referrals ON DELETE SET NULL
paid_at, expires_attimestamptz

Indexes: (dancer_id), (event_id, status).

order_items (new)

ColumnTypeConstraints / notes
iduuidPK
order_iduuidNOT NULL, FK orders ON DELETE CASCADE
ticket_type_iduuidNOT NULL, FK ticket_types ON DELETE RESTRICT, indexed
quantityintegerNOT NULL, CHECK > 0
unit_price_centsintegerNOT NULL, CHECK ≥ 0

referrals (kept, changed)

ColumnTypeConstraints / notes
iduuidPK
event_iduuidNOT NULL, FK events (was festival_id)
referrer_iduuidNOT NULL, FK dancers ON DELETE CASCADE, indexed
referee_iduuidNOT NULL, FK dancers ON DELETE CASCADE, indexed
order_iduuidFK orders ON DELETE SET NULL; replaces stripe_session_id
discount_cents, credit_centsintegerNOT NULL DEFAULT 0
credit_order_iduuidFK orders; set only when that order is paid
statustextCHECK IN (pending,completed,expired)
completed_attimestamptz

Unique (event_id, referee_id); CHECK referrer_id <> referee_id.

webhook_events (new)

ColumnTypeConstraints / notes
providertextPK
event_idtextPK (Stripe evt_…, TicketTailor order id + event)
typetext
received_at, processed_attimestamptz

Attendance and festival social — ADR-008

attendances (new; replaces festival_signups and Going rows of plan_items)

ColumnTypeConstraints / notes
iduuidPK
event_iduuidNOT NULL, FK events ON DELETE CASCADE
dancer_iduuidFK dancers ON DELETE CASCADE; NULL for unclaimed ticket stubs
buyer_emailcitextstubs only
statustextCHECK IN (interested,going,ticket_holder)
sourcetextCHECK IN (click,free_pass,social_pass,stripe,tickettailor,claim)
order_iduuidFK orders ON DELETE SET NULL
visibilitytextCHECK IN (hidden,public_minimal,public_full) NOT NULL
partner_statustextCHECK IN (looking,found)
travel_booked_at, stay_booked_at, vacation_booked_attimestamptzwere localStorage
checked_in_at, verified_attimestamptz

Unique (event_id, dancer_id); unique (event_id, buyer_email) WHERE dancer_id IS NULL; CHECK dancer_id IS NOT NULL OR buyer_email IS NOT NULL. Indexes: (event_id, status) WHERE visibility <> 'hidden', (dancer_id).

waves (renamed from festival_waves)

ColumnTypeConstraints / notes
iduuidPK
event_iduuidNOT NULL, FK events ON DELETE CASCADE (was festival_slug text)
from_dancer_iduuidNOT NULL, FK dancers ON DELETE CASCADE
to_dancer_iduuidNOT NULL, FK dancers ON DELETE CASCADE

Unique (event_id, from_dancer_id, to_dancer_id); CHECK from_dancer_id <> to_dancer_id; index (event_id, to_dancer_id).

ride_shares (renamed from festival_ride_shares)

ColumnTypeConstraints / notes
iduuidPK
event_iduuidNOT NULL, FK events ON DELETE CASCADE
dancer_iduuidNOT NULL, FK dancers ON DELETE CASCADE
typetextCHECK IN (offering,looking)
origin_city_iduuidFK cities; origin_city text until step 11 contracts
departs_ondateNOT NULL
seats_availableintegerCHECK ≥ 0
chat_urltextnew (map:meetup-3.3)

Unique (event_id, dancer_id).

roommate_requests (renamed from festival_roommate_lookups)

ColumnTypeConstraints / notes
iduuidPK
event_iduuidNOT NULL, FK events ON DELETE CASCADE
dancer_iduuidNOT NULL, FK dancers ON DELETE CASCADE
check_in, check_outdatenew (RAZ-121 AC3)
budget_centsintegernew
notestextnew

Unique (event_id, dancer_id).

dinners, dinner_signups, dinner_groups, dinner_group_members (kept, changed)

  • dinners: event_id (was festival_id), starts_at timestamptz + label (replaces day/date/time_slot text), restaurant, restaurant_address, reveal_at timestamptz, max_size integer CHECK BETWEEN 2 AND 12; unique (event_id, starts_at).
  • dinner_signups: PK (dinner_id, dancer_id), both CASCADE; index dancer_id.
  • dinner_groups: dinner_id CASCADE, chat_url.
  • dinner_group_members: PK (group_id, dancer_id), plus dinner_id with unique (dinner_id, dancer_id) so a dancer is in one group per dinner.

Plans — ADR-009

TableColumnsConstraints
session_picks (new)dancer_id, session_id, created_atPK (dancer_id, session_id); FKs CASCADE
series_enrolments (new)dancer_id, series_id, status, enrolled_at, ended_atPK (dancer_id, series_id); status CHECK IN (active,paused,ended)
dancer_goals (new)id, dancer_id, kind, title, target, period_year, metadata jsonbFK CASCADE; index dancer_id

Booking and governance — ADR-004, ADR-006

bookable_spaces (kept, changed)

profile_id FK CASCADE; name NOT NULL; capacity CHECK > 0; floor_type CHECK vocabulary; amenities text[]; price_info; sort_order integer NOT NULL DEFAULT 0.

availability_slots (kept, changed)

ColumnTypeConstraints / notes
iduuidPK
space_iduuidNOT NULL, FK bookable_spaces ON DELETE CASCADE, indexed
day_of_weeksmallintCHECK BETWEEN 1 AND 7 (ISO)
start_time, end_timetimewere 'HH:MM' text; CHECK end_time > start_time
is_activebooleanNOT NULL DEFAULT true

booking_requests (kept, changed)

ColumnTypeConstraints / notes
iduuidPK
space_iduuidNOT NULL, FK bookable_spaces ON DELETE CASCADE, indexed
profile_iduuidNOT NULL, FK profiles ON DELETE CASCADE, indexed
organizer_profile_iduuidFK profiles ON DELETE SET NULL (was organizer_id)
requester_iduuidFK dancers ON DELETE SET NULL (+ scrub email/name)
requester_emailcitextNOT NULL
requester_name, title, messagetextprivate to venue members
event_kindtextCHECK vocabulary (was event_type)
stylestext[]
artist_namestext[]resolved to event_artists on acceptance
starts_at, ends_attimestamptzwere event_date + 'HH:MM' text
headcountintegerCHECK > 0
ticket_urltext
terms_accepted_attimestamptzNOT NULL
statustextCHECK IN (pending,accepted,declined,withdrawn)
decided_byuuidFK dancers ON DELETE SET NULL (was moderated_by_id)
decided_at, notified_attimestamptz
decision_notetext
event_iduuidFK events ON DELETE SET NULL, UNIQUE — set on acceptance

Index: (profile_id, status, starts_at).

moderator_elections and children (kept, references fixed)

  • moderator_elections: profile_id CASCADE; status CHECK; winner_candidate_id FK election_candidates ON DELETE SET NULL (cycle is fine: the column is nullable); partial unique index (profile_id) WHERE status <> 'closed' (one running election per space, now enforced by the database).
  • election_candidates: unique (election_id, dancer_id) and unique (election_id, id) (target of composite FKs).
  • election_votes: composite FK (election_id, candidate_id) → election_candidates(election_id, id) so a vote can only go to a candidate of the same election; unique (election_id, voter_dancer_id).
  • election_vote_history: composite FKs for from_candidate_id and to_candidate_id; append-only (UPDATE/DELETE revoked from the app role, rows removed only by cascade).
  • guideline_versions: unchanged apart from delete rules.

Community — ADR-005

TableChange
city_videoscity_id FK replaces city_slug; competition_month date (first of month) replaces '2026-07' text; vote_count derived; elo_score updated atomically
video_votescity_id; FKs to city_videos CASCADE; voter FK SET NULL
city_battle_voteswinner_city_id, loser_city_id FKs; video ids FK SET NULL (provenance survives deletion)
giveawayscity_id; status CHECK; starts_at/ends_at timestamptz
giveaway_entriesvoter FK SET NULL; unique (giveaway_id, email); index dancer_id
reviewsexclusive arc event_id / profile_id (CHECK one non-null) replaces target_type/target_slug; reviewer_name/reviewer_username snapshots dropped (join); recommendations of unknown places create a profiles row with status = 'stub'; unique (dancer_id, event_id) and (dancer_id, profile_id)
recommendation_requestscity_id; status CHECK
community_groupscity_id; styles text[]; report_count derived from reports (or trigger)
community_group_reportsunchanged apart from delete rules

Opportunities — ADR-010

TableChange
gigsposter_profile_id FK (nullable) next to poster_name; city_id + is_remote replace location; starts_on/ends_on date + when_note replace free-text when; contact_email private (never in public DTOs); category, poster_type CHECK vocabularies
hangoutscity_id; starts_at timestamptz replaces 'HH:MM' text; people_count dropped (derived from RSVPs); closes_at
hangout_rsvpsPK (hangout_id, dancer_id), both CASCADE

inbound_requests (new)

ColumnTypeConstraints / notes
iduuidPK
kindtextCHECK IN (event_inquiry,problem_report,gig_application,contact)
statustextCHECK IN (new,triaged,in_progress,answered,closed,spam)
dancer_iduuidFK dancers ON DELETE SET NULL
emailcitext
nametext
event_id, profile_id, gig_iduuidFKs ON DELETE SET NULL; gig_id required when kind = 'gig_application'
payloadjsonbform fields, context, screenshot URL

deliveries (new)

ColumnTypeConstraints / notes
iduuidPK
inbound_request_iduuidFK CASCADE (exclusive arc with booking_request_id)
booking_request_iduuidFK CASCADE
channeltextCHECK IN (linear,email)
targettextLinear team or email address
statustextCHECK IN (pending,sent,failed)
attemptsintegerNOT NULL DEFAULT 0
next_attempt_at, delivered_attimestamptz
last_error, external_reftext

Index: (status, next_attempt_at).

Current → target mapping (all 38 tables)

Current tableTargetActionStep
dancersdancers (+ dancer_styles, handles, auth_tokens)kept; split out handles, tokens, styles3, 8, 11, 12
sessionssessionskept; token → token_hash2
festivalsevents + festival_detailsmerged into events, extension split out19
festival_signupsorders + order_items + attendancessplit, then dropped22, 24, 27
dinnersdinnerskept; festival_id → event_id, times typed19
dinner_signupsdinner_signupskept; PK changed8
dinner_groupsdinner_groupskept8
dinner_group_membersdinner_group_memberskept; dinner_id added8
city_videoscity_videoskept; city_id, typed month7, 11
video_votesvideo_voteskept; city_id11
city_battle_votescity_battle_voteskept; city FKs11
giveawaysgiveawayskept; city_id11
giveaway_entriesgiveaway_entrieskept8
reviewsreviewskept; exclusive arc replaces type/slug19
community_groupscommunity_groupskept; city_id, derived count7, 11
community_group_reportscommunity_group_reportskept5
recommendation_requestsrecommendation_requestskept; city_id11
profilesprofiles (+ profile_members, profile_claims, profile_affiliations, handles)kept; ownership and handles split out12, 13, 14
bookable_spacesbookable_spaceskept8, 14
booking_requestsbooking_requestskept; typed times, event_id on acceptance8, 20
availability_slotsavailability_slotskept; time columns8
festival_submissionsfestival_submissionskept; event_id, jsonb8, 23
moderator_electionsmoderator_electionskept; FK + one-open index9
election_candidateselection_candidateskept9
election_voteselection_voteskept; composite FK9
election_vote_historyelection_vote_historykept; composite FKs, append-only9
guideline_versionsguideline_versionskept5
gigsgigskept; poster profile, typed place/time14, 16
hangoutshangoutskept; typed time, derived count7, 11
plan_itemsattendances + session_picks + series_enrolments + dancer_goalssplit, then dropped24, 25, 27
hangout_rsvpshangout_rsvpskept5
eventsevents (+ event_series, event_artists)kept; reshaped17, 18
festival_ride_sharesride_sharesrenamed; event_id24
festival_roommate_lookupsroommate_requestsrenamed; event_id, preferences24
referralsreferralskept; event_id, order references22
artist_followsfollowsrenamed; legacy edges merged15
festival_waveswavesrenamed; slug → event_id24
citiescitieskept; applied to production11
(prod only) followsfollows / legacy_followsmerged, then dropped15

New tables: auth_tokens, handles, dancer_styles, dance_styles, rate_limits, profile_members, profile_claims, profile_affiliations, legacy_follows, event_series, event_artists, festival_details, programme_sessions, session_artists, ticket_types, orders, order_items, webhook_events, attendances, session_picks, series_enrolments, dancer_goals, inbound_requests, deliveries.

Dropped (after their contract step): festivals, festival_signups, plan_items, the production-only follows.