GroupMe data model + create groups + auto-invite on contract signing #156
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#156
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Type
Feature
Lineage
project-groupme-westside→ Ticket 2 of 3 (depends on Ticket 1: groupme-sdk)Repo
forgejo_admin/basketball-apiUser Story
As an admin (story:GM-1), I want players auto-added to their team's GroupMe when they sign their contract, so I don't manage membership manually.
As a platform operator (story:GM-6), I want GroupMe group IDs stored per team in the database.
As a platform operator (story:GM-7), I want an audit trail of GroupMe invites.
Context
The basketball-api needs to know which GroupMe group belongs to which team, and automatically invite parents when contracts are signed. Requires:
groupme_group_idto teams table + new GroupMe tracking tablesKnown behavior: Adding by phone via API may not match existing GroupMe accounts. Don't mix manual and API adds. Discovered during Marcus onboarding 2026-03-24.
File Targets
alembic/versions/xxx_add_groupme.py— migration: addgroupme_group_idto teams, creategroupme_groupsandgroupme_memberstablessrc/basketball_api/models/groupme.py— SQLAlchemy models for GroupMe tablessrc/basketball_api/routes/players.py— add GroupMe invite after contract signingsrc/basketball_api/services/groupme.py— service layer wrapping SDK callsscripts/create_groupme_groups.py— one-time script: create 11 groups, store IDspyproject.toml— add groupme-sdk dependencyFiles NOT to touch:
src/basketball_api/routes/teams.py— no API changes needed, just DB columnsrc/basketball_api/auth.py— no auth changesAcceptance Criteria
groupme_group_idVARCHAR(50) to teams tablePOST /api/players/{id}/contract) triggers GroupMe inviteTest Expectations
pytest tests/ -k groupmeConstraints
30257234("Coach Marcus Draney"), NOT the phone-matched accountChecklist
Related
project-groupme-westside— project pageArchitecture Simplification (2026-03-24)
Major scope reduction after brainstorming. Parents self-join via share link in welcome email instead of being API-added by phone number.
Simplified migration:
groupme_group_id VARCHAR(50)to teams tablegroupme_share_url VARCHAR(500)to teams tableContract signing flow (simplified):
contract_status = 'signed'team.groupme_share_urlemail_logtable (no new tracking needed)Removed from scope:
add_membercall on contract signing (replaced by share link in email)groupme_groupstablegroupme_memberstableUpdated AC:
groupme_group_id+groupme_share_urlto teams tableCode Exploration Findings (2026-03-24)
Contract signing endpoint
POST /api/players/{player_id}/contractinsrc/basketball_api/routes/players.py:249-320Email architecture
basketball-api uses gmail-sdk directly (NOT pal-e-mail). All emails built inline in
src/basketball_api/services/email.py:_brand_wrapper()for Westside red/black stylingemail_logtableEmailType enum (models.py:47-51)
Current values:
registration,reminder,roster_export,announcementMissing:
contract_signedRevised File Targets
src/basketball_api/models.py— addcontract_signedto EmailType enum, addgroupme_group_id+groupme_share_urlto Team modelalembic/versions/xxx_add_groupme.py— migration for new Team columnssrc/basketball_api/services/email.py— addsend_contract_signed_email()following existing patternsrc/basketball_api/routes/players.py— callsend_contract_signed_email()after successful contract signingscripts/create_groupme_groups.py— one-time: create 11 groups via SDK, store IDs + share URLsRevised AC
contract_signedadded to EmailType enumgroupme_group_id+groupme_share_urlcolumns on teams table (Alembic migration)send_contract_signed_email()function in services/email.py — includes team name + GroupMe share linkScope Review: NEEDS_REFINEMENT
Review note:
review-304-2026-03-24Two file target and acceptance criteria issues found before this ticket is agent-ready.
src/basketball_api/models/groupme.pydoes not exist — repo uses a singlemodels.pyfile, not amodels/package. Fix tosrc/basketball_api/models.pyor scope a package refactor.parent.phoneis nullable andplayer.team_idis nullable. Contract signing can happen before team assignment or without a phone number. Acceptance criteria must specify behavior for both cases (skip invite? queue? log warning?).Review Fixes (2026-03-24)
Addressing review-304-2026-03-24 findings:
1. Wrong file path
Corrected: the repo uses a single
src/basketball_api/models.py, not amodels/package. GroupMe columns get added to the existingTeamclass inmodels.py. No newmodels/groupme.pyfile.Revised file targets:
src/basketball_api/models.py— addgroupme_group_id+groupme_share_urlto Team model, addcontract_signedto EmailType enumalembic/versions/xxx_add_groupme.py— migration for new columnssrc/basketball_api/services/email.py— addsend_contract_signed_email()src/basketball_api/routes/players.py— call email after contract signingscripts/create_groupme_groups.py— one-time group creation + DB population2. Nullable edge cases
parent.phoneandplayer.team_idare both nullable. Contract signing with missing data:team_id IS NULL): skip GroupMe invite, log warning. Email still sent but without GroupMe link — just "Welcome to Westside, team assignment coming soon."These are best-effort: contract signing always succeeds. GroupMe invite is a bonus, not a gate.
Architecture Update: Outbox Pattern (2026-03-24)
Problem
If GroupMe API or email service is down when a parent signs their contract, the inline call fails and the parent gets no welcome email. Contract signing should never fail because of an external service.
Solution: Event Outbox
Contract signing writes an event to an outbox table (same DB transaction). A worker processes events asynchronously.
Revised File Targets
src/basketball_api/models.py— addgroupme_group_id+groupme_share_urlto Team, addcontract_signedto EmailType enum, addOutboxmodel (id, event_type, payload JSON, status, created_at, processed_at)alembic/versions/xxx_add_groupme_and_outbox.py— migration for Team columns + outbox tablesrc/basketball_api/services/email.py— addsend_contract_signed_email()src/basketball_api/services/outbox.py— worker logic: poll pending events, process, mark donesrc/basketball_api/routes/players.py— contract signing writes outbox event (NOT inline email call)scripts/create_groupme_groups.py— reconciliation script: query GroupMe for existing groups, match to teams by name, store IDs. Idempotent.Reconciliation Script Logic
Why Outbox > Inline Call
Revised AC
groupme_group_id+groupme_share_urlon teams,outboxtablecontract_signedevent to outbox (same transaction)pendingand retry next cycleArchitecture Diagram Updated (2026-03-24)
Outbox pattern + admin trigger endpoint now documented in the data flow diagram:
https://pal-e-docs.tail5b443a.ts.net/notes/arch-dataflow-westside-basketball
Shows: contract signing → outbox (same transaction) → admin/cron triggers worker → welcome email with GroupMe link → parent self-joins.