fix: resolve per-recipient placeholders in /email/blast data values #301
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/basketball-api!301
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "295-fix-data-placeholder-merge"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
The
/email/blastendpoint mergedbody.dataover recipient data but never resolved{{key}}placeholders within data string values. Subject placeholders were resolved correctly, but data values like"Hey {{parent_name}}, ..."were passed through raw to the template engine.Changes
src/basketball_api/routes/admin.py-- Added a resolution loop after internal key removal (lines 1131-1137) that substitutes recipient fields into data string values before subject resolution and beforesend_templated_emailis called.tests/test_email_blast.py-- Addedtest_data_placeholder_resolutionverifying thatbody.datacontaining{{parent_name}}and{{player_name}}resolves to actual recipient values.Test Plan
test_email_blast.pypass, including the new test.Review Checklist
Related Notes
None.
Related
The /email/blast endpoint merged body.data over recipient data but never resolved {{key}} placeholders within data string values. Subject placeholders were resolved, but data values like "Hey {{parent_name}}, ..." were passed through raw. Add a resolution loop after internal key removal that substitutes recipient fields into data values before template rendering. Closes #295 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>PR #301 Review
DOMAIN REVIEW
Tech stack: Python / FastAPI / SQLAlchemy (basketball-api)
The fix adds a per-recipient placeholder resolution loop over
template_datastring values at lines 1131-1137 ofadmin.py. This usesstr.replace(f"{{{{rkey}}}}", rval)-- identical to the existing subject resolution pattern andload_email_template's own placeholder engine. The fix is placed correctly: after internal keys (parent_id,players) are popped, before subject resolution, and beforesend_templated_emailis called.The resolution only applies to
strvalues (isinstanceguard on both data_val and rval), so nested dicts, ints, etc. pass through safely.Security: The
/email/blastendpoint is admin-only. Recipient field values come from the database, not from the request body. No injection vector introduced.BLOCKERS
None.
NITS
Repeated placeholder resolution pattern -- The
str.replace(f"{{{{key}}}}", value)loop now appears in three places: data value resolution (new code), subject resolution (existing), andload_email_template. Consider extracting aresolve_placeholders(template: str, context: dict) -> strhelper inemail.py. Not blocking -- the blast endpoint is admin-facing and the pattern is simple.Test
call_argsextraction is fragile (lines 548-553) -- The fallback logiccall_args[0][1] if len(call_args[0]) > 1 else call_args[1].get("data", call_args[0][1])tries to handle both positional and keyword calling conventions. If the function signature changes, this will silently grab the wrong arg. Consider asserting the exact call form, e.g.mock_load_template.assert_called_once_with("contract-offer", ANY)then checking the second positional arg directly.Non-string rval silently skipped -- If a recipient field is an
int(e.g., a hypothetical numeric field),isinstance(rval, str)skips it. This is fine today sinceparent_name/player_nameare strings, but worth documenting the assumption.SOP COMPLIANCE
295-fix-data-placeholder-mergereferences #295)PROCESS OBSERVATIONS
Clean, minimal fix. 8 lines of production code, 43 lines of test. The 9 pre-existing failures in jersey/checkout tests (tracked in #300/#274) are unrelated and should not block this merge. Change failure risk is low -- the new code only adds resolution; it cannot break data values that contain no placeholders.
VERDICT: APPROVED