Skip to content

Duplicate an event type #17

Closed
Closed

Description

@shockalotti

Requested in #16.

Let hosts duplicate an existing event type from the Event types list, so building several variants that share configuration (location, duration, buffers, booking window) doesn't mean re-entering everything by hand.

The thing to get right

An event type is not just its row in event_types. It owns:

What Where
Intake questions questions.event_type_id
Hosts, with role + routing priority event_type_hosts
Event-scoped availability availability_rules.event_type_id (NULL means the global default)
Per-event email / reminder copy event_types.msg_*, event_type_reminders

A row-only copy produces a clone that looks correct in the editor while having no questions, no hosts, and silently falling back to global availability. That is worse than not shipping the feature, because the failure is invisible until a booker hits the page or a booking lands on the wrong person.

Scope

  • POST /v1/event-types/{slug}/duplicate (or equivalent) copying the row plus all four child sets above
  • Slug uniquified (intro-call to intro-call-copy, then -2 etc. on collision; the column is UNIQUE)
  • Copy starts with is_active = 0 so a half-configured clone isn't live at a guessable URL
  • Copy price_cents / currency faithfully rather than zeroing them, so nobody accidentally publishes a paid event type at zero
  • Do not copy bookings (bookings.event_type_id is ON DELETE RESTRICT)
  • Duplicate action in the event-types list UI, consistent with the existing row actions (ghost icon button + Tooltip)
  • Tests asserting each child set came across, not just that the new row exists

Notes for whoever picks this up

  • The whole copy must be one transaction. The DB pool is MaxOpenConns(1); never query inside an open rows cursor, materialise to a slice first.
  • UI conventions are in CLAUDE.md (shadcn-svelte, no hand-rolled buttons/modals).

Activity

  1. pullfrog commented on Sep 1, 2026

    @pullfrog
    Contributor
  2. shockalotti commented on Sep 2, 2026

    @shockalotti
    ContributorAuthor

    @MinosChatzidakis you offered in #16 to open a PR under guidance. This one is yours if you'd like it, and it's a good fit: self-contained, no new architecture, but with a real trap in it that makes it worth doing carefully rather than mechanically.

    The scope is in the issue body above. A few things that aren't:

    Where to start reading. internal/handler/event_type.go has the create path, which is the closest thing to a specification of what a complete event type write looks like. Duplication is essentially that, sourced from an existing row instead of a request body, plus the four child sets.

    Two rules that will bite you if you don't know them. The whole copy needs to be one transaction, and the DB pool is deliberately capped at a single connection, so you must never run a query while an earlier result cursor is still open. Read the rows into a slice first, then write. docs/ARCHITECTURE.md §17 covers this and the other cross-cutting gotchas; it's worth ten minutes before you start.

    On the UI side, follow the existing row actions on the event types list (ghost icon button plus a Tooltip). CLAUDE.md has the component conventions. Please don't hand-roll buttons or use window.confirm.

    Tests are the part I care most about. The failure mode this feature has is a clone that looks perfect in the editor while missing its questions, its hosts, or its custom availability. A test that only checks the new row exists would pass against exactly the broken version. Assert each child set came across. internal/handler/slot_interval_test.go is a reasonable shape to copy.

    CONTRIBUTING.md covers the rest: branch off main, go test ./... green, make build succeeds. The CLA bot will ask you to sign with a one-line comment on your first PR. That's expected and it's a one-time thing; it exists so the project can relicense future versions if a hosted edition ever happens, and you keep the copyright to your own work either way. I verified the bot is working properly earlier today, so it should be painless.

    No deadline and no obligation. If you'd rather talk through the approach before writing anything, or you get partway and want a look at it, say so here and I'll respond. If you'd prefer a smaller first contribution instead, #20 is a gentler one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions