You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Replace free-text competition_exp and volunteer_exp columns on User with structured relational tables. Add canonical Event table.
Background
Free-text experience fields can't be searched or filtered. TDs need to find volunteers by event experience. This replaces the blobs with structured entries that support filtering by event, expertise, and role.
Tasks
Backend — canonical Event table
Add Event model: id, name (unique), is_active (default True — retired events hidden from dropdowns but preserved for historical entries)
Admin-managed: GET/POST/PATCH /events/; DELETE /events/{id}/ soft-deletes via is_active=False
Register router, write tests
Backend — UserCompetitionExperience table
Model: id, user_id FK, event_id FK, school, notes
Routes (self-service): POST, PATCH /{id}/, DELETE /{id}/ under /users/me/competition-experience/
Permission check: user can only edit their own entries
Searchable/filterable by event_id
Backend — UserVolunteerExperience table
Model: id, user_id FK, tournament_name (nullable, free text), year (nullable), tournament_id FK (nullable), event_id FK (nullable), role (free text), notes, is_locked (default False)
Invariant: exactly one of tournament_id or tournament_name must be set — enforce via schema validator
Routes (self-service): POST, PATCH /{id}/, DELETE /{id}/ under /users/me/volunteer-experience/
PATCH and DELETE blocked if is_locked=True (return 403)
Pre-NEXUS entries only via these routes; NEXUS tournament entries are auto-populated when tournament is archived (# TODO(temp): implement when tournament verified status is built)
Replace free-text
competition_expandvolunteer_expcolumns onUserwith structured relational tables. Add canonicalEventtable.Background
Free-text experience fields can't be searched or filtered. TDs need to find volunteers by event experience. This replaces the blobs with structured entries that support filtering by event, expertise, and role.
Tasks
Backend — canonical
EventtableEventmodel:id,name(unique),is_active(defaultTrue— retired events hidden from dropdowns but preserved for historical entries)GET/POST/PATCH /events/;DELETE /events/{id}/soft-deletes viais_active=FalseBackend —
UserCompetitionExperiencetableid,user_idFK,event_idFK,school,notesPOST,PATCH /{id}/,DELETE /{id}/under/users/me/competition-experience/event_idBackend —
UserVolunteerExperiencetableid,user_idFK,tournament_name(nullable, free text),year(nullable),tournament_idFK (nullable),event_idFK (nullable),role(free text),notes,is_locked(defaultFalse)tournament_idortournament_namemust be set — enforce via schema validatorPOST,PATCH /{id}/,DELETE /{id}/under/users/me/volunteer-experience/PATCHandDELETEblocked ifis_locked=True(return 403)# TODO(temp): implement when tournament verified status is built)Backend —
Usermodel additionshas_competition_experience = Column(Boolean, nullable=True)—null=unanswered,False=explicitly none,True=claims entries existhas_volunteer_experience = Column(Boolean, nullable=True)— same semanticscompetition_exp(Text) andvolunteer_exp(Text) columns — confirm no real data before droppingprofile_incomplete_fieldslogic:has_*_experience is None→ incompletehas_*_experience is False→ skip (user said they have none)has_*_experience is Truebut no entries → incompleteBackend — read
GET /users/me/?full=truereturns nestedcompetition_experiencesandvolunteer_experienceslists (eagerly loaded)GET /users/{id}/(admin) returns sameNotes
event_idonUserVolunteerExperienceis nullable — some roles are tournament-wide staff not tied to a specific eventis_locked=Trueentries are auto-populated by NEXUS once a tournament is archived; users cannot edit or delete them