Skip to content

Add Eventlite exporter#2

Merged
nbudin merged 13 commits into
mainfrom
eventlite-support
Jun 22, 2026
Merged

Add Eventlite exporter#2
nbudin merged 13 commits into
mainfrom
eventlite-support

Conversation

@nbudin

@nbudin nbudin commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Purpose

Adds support for exporting Eventlite sites to the convention JSON import format. Eventlite is a lightweight Rails ticketing CMS, and this exporter maps its data model onto Intercode's import schema — one export file per Eventlite event (each becoming an Intercode `single_event` convention).

Changes

💻 New exporter

  • `rake export:eventlite` — connects to an Eventlite PostgreSQL database and writes one `convention-export-{slug}.json` per event
  • Exports users (Devise bcrypt passthroughs; names inferred from ticket records), user_con_profiles (name + phone from tickets), active tickets, CMS layouts/pages/files/navigation items
  • Ticket type pricing and payment history are exported as `store_items` / `store_orders`, with each product carrying a `provides_ticket_type_name` back-reference
  • CMS files are fetched from S3 via `FILE_BASE_URL` and base64-encoded inline; omitted gracefully if the env var isn't set
  • Canceled tickets are excluded from the `tickets` array but appear as `status: "cancelled"` store orders so payment history is preserved

📐 Schema extensions

  • Top-level `cms_layouts` collection + `cms_layout_name` on `cms_page` (model and columns already exist in Intercode — a follow-up PR will wire up the importer)
  • `provides_ticket_type_name` on `store_item` (importer follow-up needed here too)
  • `"eventlite"` added to the `source_system` enum

🔧 Infrastructure

  • Adds `pg` gem for PostgreSQL connectivity
  • New `EventliteDbTestHelper` using `EVENTLITE_TEST_DATABASE_URL` (separate from the MySQL `TEST_DATABASE_URL`)
  • CI gains a PostgreSQL 16 service alongside the existing MySQL 8 one

Risks

The `cms_layouts` and `provides_ticket_type_name` fields are new schema additions that Intercode's importer doesn't yet consume — they'll be silently ignored until the follow-up Intercode PR lands. Everything else maps to fields the importer already handles.

🚢

🤖 Generated with Claude Code

Adds support for exporting Eventlite sites to the convention JSON
format, one export file per Eventlite event.

- New `rake export:eventlite` task (requires EVENTLITE_DB_URL;
  optional DOMAIN_SUFFIX, TIMEZONE, FILE_BASE_URL)
- Exports users (Devise bcrypt passthrough, names inferred from
  tickets), user_con_profiles, tickets (active only), store_items,
  store_orders, CMS layouts/pages/files/navigation
- Schema extensions: cms_layouts top-level collection,
  cms_layout_name on cms_page, provides_ticket_type_name on
  store_item, eventlite source_system value
- PostgreSQL support via pg gem
- Integration test suite (skipped without EVENTLITE_TEST_DATABASE_URL)
- CI: adds PostgreSQL 16 service alongside existing MySQL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
nbudin and others added 10 commits June 22, 2026 10:46
In some Eventlite databases (including production), tickets were created
without a linked user account — the attendee name and email are stored
directly on the ticket (tickets.email, tickets.name) and user_id is NULL.

The previous exporter assumed all tickets had a user_id FK and silently
skipped any that didn't, producing empty exports against real data.

- Tables::Tickets: fall back to row[:email] when user_id is nil
- Exporter#build_store_orders: same fallback
- Exporter#build_profiles: same fallback
- Tables::Users: emit synthetic user records for ticket emails that have
  no user_id and no matching user account; deduplicates against the
  users-table pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each exported event now includes a single Run (as expected for a
single_event convention) and a confirmed signup for every ticket holder.

The event also gets a registration_policy with one unlimited 'attendees'
bucket so signups have a valid bucket_key to reference on import.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When an event has multiple ticket types (e.g. male/female character
splits), map each ticket type to a registration_policy bucket instead of
the generic single 'attendees' bucket.

Bucket slots come from ticket_type.number_available when set.

For attendees who bought multiple ticket types (e.g. a character ticket
and a cabin add-on), one signup is created per person using the ticket
type with the most available slots — so a cabin ticket (1 slot) never
wins over a character ticket (15-25 slots).

Single-ticket-type events continue to use a single unlimited 'attendees'
bucket as before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
required_for_signup only applies to convention-mode sites. Single-event
sites must use ticket_per_event instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The single_event content set provides the 'Regular event form' that
EventCategory requires. Without it the import fails with a validation
error on event_form.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pages using {% ticket_form %} (Eventlite's registration form tag) can't
be imported into Intercode since that tag doesn't exist there. These
pages serve Eventlite-specific functionality that Intercode handles
natively, so skipping them is the right behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pages skipped due to Eventlite-specific tags (e.g. ticket_form) now
also have their navigation links removed. Both Pages and NavigationItems
share the tag-detection logic via a new eventlite_only_content? helper
on the base Table class.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TicketType.name must be a word-character identifier. Eventlite names
like "Female character" and "2-occupancy cabin" fail that validation.

- slug_for() converts names to lowercase underscored identifiers
- Ticket types export with name: slug and description: original_name
- ticket.ticket_type_name, store_item.provides_ticket_type_name, and
  signup.bucket_key all use the slug
- store_item.name and store_order.store_item_name keep the human-
  readable original name since they're display text, not identifiers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Users with no name data now fall back to their email prefix as first_name,
satisfying Intercode's Name presence validation. For multi-ticket-type events,
tickets are deduplicated to one per user (best by available slots), preventing
the unique user_con_profile constraint violation. Store items no longer carry
provides_ticket_type_name since tickets are handled explicitly via the tickets
array; losing ticket purchases remain as store orders only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…name

- Export default_layout_name and root_page_slug instead of raw layout
  content so the import service can wire up convention.default_layout and
  convention.root_page after CMS content is imported
- Use event name directly as the convention name (was falling back to
  site_settings.site_title which gives the org name, not the event name)
- NavigationItems: use navigation_section_id for proper section/link
  grouping instead of flattening everything into one "Navigation" section;
  standalone top-level links each become their own single-link section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nbudin nbudin marked this pull request as ready for review June 22, 2026 20:25
nbudin and others added 2 commits June 22, 2026 13:26
Reflects the fix that uses the email local part as first_name when both
names are blank, so Intercode's name presence validation doesn't fail.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nbudin nbudin merged commit 418c8b1 into main Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant