Skip to content

feat: structured competition and volunteer experience #46

Description

@ethnjs

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)

Backend — User model additions

  • Add has_competition_experience = Column(Boolean, nullable=True)null=unanswered, False=explicitly none, True=claims entries exist
  • Add has_volunteer_experience = Column(Boolean, nullable=True) — same semantics
  • Drop competition_exp (Text) and volunteer_exp (Text) columns — confirm no real data before dropping
  • Update profile_incomplete_fields logic:
    • has_*_experience is None → incomplete
    • has_*_experience is False → skip (user said they have none)
    • has_*_experience is True but no entries → incomplete
  • Migration covering all of the above

Backend — read

  • GET /users/me/?full=true returns nested competition_experiences and volunteer_experiences lists (eagerly loaded)
  • GET /users/{id}/ (admin) returns same

Notes

  • event_id on UserVolunteerExperience is nullable — some roles are tournament-wide staff not tied to a specific event
  • is_locked=True entries are auto-populated by NEXUS once a tournament is archived; users cannot edit or delete them
  • Expertise and role fields dropped from competition experience after design review — entries are event + school + notes only

Activity

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

Metadata

Metadata

Assignees

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions