Skip to content

refactor(api): split booking into services, guests and typed responses - #302

Merged
harshtandiya merged 2 commits into
developfrom
refactor/api-booking
Jul 29, 2026
Merged

refactor(api): split booking into services, guests and typed responses#302
harshtandiya merged 2 commits into
developfrom
refactor/api-booking

Conversation

@harshtandiya

Copy link
Copy Markdown
Collaborator

Stacked on #301 — read that first. Last domain PR; PR 7 (the require_type_annotated_api_methods flip) is next.

booking/__init__.py 676 lines → 49, over services.py, guests.py, event_data.py, details.py, coupons.py, schemas.py, exceptions.py. process_booking (200 lines, 16 flat params) becomes BookingRequest + BookingService.

Second commit fixes an auth hole found while splitting: get_booking_details had no permission check — any logged-in user could read any booking by id, attendee emails and ticket QR payloads included. Reproduced against a real booking, then blocked. Guard mirrors create_cancellation_request; message and exception are the ones get_booking_confirmation already threw.

Things the diff won't tell you:

  • APIResponse.__json__ no longer calls model_dump() — shared plumbing, so flag it if you'd rather it went back into refactor: split buzz/api into domain packages #295. pydantic 2.13 raises 'None' is not an instance of 'SchemaSerializer' for any frappe._dict in an Any field: _dict.__getattr__ is dict.get, so it fakes a __pydantic_serializer__. No model_dump flag avoids it. Earlier domains re-wrapped their rows in typed models and never hit it.
  • process_booking reshapes the wire payload. Client now posts {"booking": {...}}; BookingForm.vue:1293 nests it. Plain/OTP/offline all funnel through submitBooking, so it's one call site.
  • Only four named errors. The OTP messages are a frontend contract — BookingForm.vue:1325 closes the modal on the "Too many" and "expired" fragments — so those keep exact text. Everything else stays a generic 417 with its existing message.
  • Attendee rows stay list[dict]: ticket type names arrive as ints, and typing them makes frappe's arg validation reject the payload.
  • create_add_on_doc moved out of tickets/ (only caller was here). send_guest_booking_otp and process_booking pinned to POST.
  • Only deliberate response change: get_booking_details.venue is now always present as nullBookingEventInfo.vue already v-if-guards it.
  • ~30 flat call sites in three doctype test modules kept working with a three-line shim each rather than a rewrite.

Checks: 234 Python tests green across 12 modules (19 new in test_booking.py); all reads byte-identical vs a pre-change snapshot over 8 events × 2 users, a booking and 6 coupons; ruff + semgrep clean; yarn typecheck and unit tests pass; E2E 17/17 headed (event-booking, offline-payment). Not run: the two guest OTP specs pass in CI but not locally — they read the OTP off the response, which needs frappe.in_test on the server, and they fail identically on the base branch.

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

Base automatically changed from refactor/api-tickets to developJuly 29, 2026 17:35
harshtandiyaand others added 2 commits July 29, 2026 23:42
booking/__init__.py held 676 lines: six endpoints, a 200-line
process_booking taking 16 flat parameters, guest OTP delivery and
verification, HMAC access tokens, and three read payloads assembled inline.
process_booking becomes BookingRequest + BookingService, with the body split
across resolve_user, verify_guest, append_utm_parameters, append_attendees
and the three finalize branches. Because a BaseModel parameter reshapes the
wire payload, BookingForm.vue nests its payload under "booking" — all three
paths (plain, OTP, offline) already funnel through submitBooking, so that is
one call site. Attendee rows stay list[dict]: ticket type names arrive as
ints and typing them would make frappe's arg validation reject the payload.
Only four named errors, not one per throw. The OTP failures carry text the
dashboard branches on — BookingForm.vue closes the modal on the "Too many"
and "expired" fragments — so those three keep their exact messages, and
RegistrationsClosed is a 409. Every other throw stays a generic 417 with the
message it already had.
APIResponse.__json__ no longer calls model_dump(). pydantic 2.13's
polymorphic serialization raises "'None' is not an instance of
SchemaSerializer" for any frappe._dict sitting in an Any field, because
_dict.__getattr__ is dict.get and so fakes a __pydantic_serializer__; no
model_dump flag avoids it. __json__ now returns the raw field values and
deep-converts only nested APIResponse models, leaving rows and documents to
json_handler as before. Earlier domains never hit this — their query rows
were re-wrapped in typed models.
create_add_on_doc moves from tickets/ to booking/, its only caller, and
OFFLINE_PAYMENT_METHOD now lives beside the service that writes it.
send_guest_booking_otp and process_booking are methods=["POST"], continuing
the sweep started in forms.
The three doctype test modules keep their ~30 flat call sites; a three-line
shim in each wraps the endpoint, taking kwargs in and returning __json__()
out. test_buzz_event's patches follow are_registrations_closed into
services.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
get_booking_details had no permission check at all: any logged-in user could
read any booking by id, including every attendee's email and the ticket QR
payloads. Its sibling get_booking_confirmation has always checked.
The guard sits in build_booking_details, which every caller routes through.
The owner-or-permission shape matches create_cancellation_request, and the
exception and message are the ones get_booking_confirmation already throws,
so no new error class and no dashboard change.
Nothing legitimate is tightened out: the only routes into the page are
BookingsList, which filters by user: session.user, and the post-booking
redirect for the booking just made.
Three tests cover it — owner reads, another user gets a PermissionError, a
System Manager reads any booking. Verified the middle one fails with the
guard removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@harshtandiya
harshtandiya merged commit 6de3cd0 into developJul 29, 2026
7 checks passed
@harshtandiya
harshtandiya deleted the refactor/api-booking branch July 29, 2026 18:17
@github-actions

Copy link
Copy Markdown
Contributor

Backport failed for main, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin main
git worktree add -d .worktree/backport-302-to-main origin/main
cd .worktree/backport-302-to-main
git switch --create backport-302-to-main
git cherry-pick -x 6de3cd0a3b5b7c484d18d26e912ac0f2c22900d3

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

Labels

backport mainbackport to main branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@harshtandiya