Skip to content

refactor(api): give sponsorships a service class and clean up payments - #298

Open
harshtandiya wants to merge 3 commits into
refactor/api-checkinfrom
refactor/api-sponsorships
Open

refactor(api): give sponsorships a service class and clean up payments#298
harshtandiya wants to merge 3 commits into
refactor/api-checkinfrom
refactor/api-sponsorships

Conversation

@harshtandiya

@harshtandiya harshtandiya commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

PR 3 of the buzz/api/ refactor, stacked on #297. Review the incremental diff.

Sponsorships. Four endpoints that hand-built dicts and threw bare, so every refusal was a generic 417. The three per-enquiry ones now delegate to SponsorshipService — enquiry and event as plain properties over get_cached_doc, permission checks in one place. The list endpoint stays a module-level function; a list query isn't one enquiry, and a one-method class is ceremony. Five response models matching the wire byte for byte, five named errors (3× 403, 2× 409). DoesNotExistError left alone on an unknown enquiry — right status, well-understood exc_type.

Payments. get_payment_link_for_booking and get_payment_link_for_sponsorship lose their @frappe.whitelist(); only callers were inside buzz/api. Functions stay put — doctype controllers import them. Two things there were dead, not unused: get_payment_gateway_for_event had no caller at all, and the "legacy field" fallback read Buzz Event.payment_gateway, a field the doctype no longer has, so it always produced []. No services.py, no schemas.py — one endpoint returning a list of strings.

Gotcha. Buzz Event autonames to integers, Link fields arrive as strings. Batching the list endpoint's per-row title lookups into maps silently missed every row until the keys were str()-cast; the get_value calls it replaced were coercing in SQL. Caught by a test, not by reading the diff.

Not changed. No endpoint renamed, no payload reshaped — dashboard untouched. The two No payment gateway configured throws stay 417s: retyping needs the class importable from buzz/payments.py, and buzz/api/payments/exceptions.py can't be — importing it runs __init__.py, which imports buzz.payments back mid-initialisation.

Verified. 17 new tests; test_sponsorship_enquiry, test_event_sponsor, test_event_booking (39) still green. Both read endpoints byte-identical against a pre-change copy, then re-checked over HTTP: key order, creation as "2026-07-29 01:36:38.702894", start_date as "2026-07-29", has_sponsor a bool, second withdraw 409 with the message in _server_messages. ruff clean.

🤖 Generated with Claude Code

harshtandiya and others added 2 commits July 29, 2026 01:39
get_sponsorship_details, get_user_sponsorship_inquiries,
create_sponsorship_payment_link and withdraw_sponsorship_enquiry each
hand-built a dict and raised a bare frappe.throw, so every refusal came
back as a generic 417 with the copy inline at the call site.

The three per-enquiry endpoints now go through SponsorshipService, which
reads the enquiry and its event as plain properties over get_cached_doc
and owns the permission checks. The list endpoint stays a module-level
function — a list query is not one enquiry, and a class with a single
method is ceremony.

Responses are pydantic models declared to match the wire byte for byte:
same keys, same order, same date rendering. Errors are five named classes
carrying their own status and copy — three 403s (view, pay, withdraw) and
two 409s (already paid, already withdrawn). frappe.DoesNotExistError is
left alone on an unknown enquiry; it already says the right thing with the
right status.

The two per-row title lookups in the list endpoint are batched into maps.
Buzz Event autonames to integers on some sites while Link fields arrive as
strings, so those map keys need an explicit str() cast — the get_value
calls this replaces were coercing in SQL.

No endpoint renamed and no payload reshaped, so the dashboard is untouched.
Both read endpoints were diffed against a copy of the pre-change module and
came out identical, then re-checked over HTTP for key order, date format
and the 409 reaching err.messages[0].

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
get_payment_link_for_booking and get_payment_link_for_sponsorship were
whitelisted, but their only callers live inside buzz/api — they are service
functions, not endpoints, and being remotely callable let a client name its
own amount source. The decorators go; the functions stay where they are,
since buzz/payments.py already is the payments service layer and doctype
controllers import from it.

Two more things in the module were dead rather than merely unused.
get_payment_gateway_for_event had no caller anywhere. And the "fallback to
legacy field" branch in get_payment_gateways_for_event read
Buzz Event.payment_gateway, a field the doctype no longer has, so
get_cached_value returned None and the branch always produced []. Deleting
both leaves behaviour unchanged.

test_payments covers the one endpoint the domain has. It is thin enough
that a service layer would be an empty hop, so payments/ gets no
services.py and no schemas.py — get_event_payment_gateways returns a list
of strings, and modelling that would be fiction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

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.

setUpClass picked the first existing Payment Gateway record, which works
on a bench that has one configured and raises IndexError on CI, where
none exist. The test now creates its own gateway per test, inside the
transaction that tearDown rolls back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

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.

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