feat: Sponsor model, migration, and CRUD endpoints #324

Closed
opened 2026-04-03 23:51:11 +00:00 by forgejo_admin · 0 comments

Type

Feature

Lineage

Child of basketball-api#316 (sponsor outreach system). Blocked by: nothing. Blocks: #324 (blast endpoint).

Repo

forgejo_admin/basketball-api

User Story

As an admin,
I want to manage sponsor contacts in the database,
So that I can track businesses through the outreach pipeline.

Context

This ticket creates the Sponsor data model and basic CRUD endpoints. The model represents a local business in the sponsorship outreach pipeline with status tracking (prospect → contacted → responded → negotiating → committed → declined).

This is the foundation ticket — the blast endpoint (#324) depends on this model and these routes existing.

The seed endpoint (POST /sponsors/seed) consumes the JSON fixture from #323 but can be built independently — it just needs the model schema.

File Targets

Files the agent should modify:

  • src/basketball_api/models.py — add SponsorStatus enum, SponsorCategory enum, Sponsor model, add sponsor_outreach to EmailType enum
  • src/basketball_api/app.py — register sponsor router

Files the agent should create:

  • src/basketball_api/services/sponsor_service.py — CRUD functions: get_sponsors, create_sponsor, update_sponsor, delete_sponsor, seed_sponsors
  • src/basketball_api/routes/sponsors.py — REST endpoints
  • alembic/versions/xxx_add_sponsors.py — migration for sponsors table + EmailType enum update
  • tests/test_sponsors.py — tests for model CRUD and endpoints

Files the agent should NOT touch:

  • src/basketball_api/services/email.py — email sending is in the blast ticket
  • src/basketball_api/routes/admin.py — sponsors get their own router

Acceptance Criteria

  • SponsorStatus enum: prospect, contacted, responded, negotiating, committed, declined
  • SponsorCategory enum: food, financial, retail, automotive, construction, fitness, dental, grocery, other
  • Sponsor model with: id, tenant_id (FK), business_name, email, category, status (default: prospect), custom_pitch (nullable), contact_name (nullable), phone (nullable), notes (nullable), last_contacted_at (nullable), created_at, updated_at
  • EmailType.sponsor_outreach added to enum
  • Alembic migration creates sponsors table and updates EmailType enum
  • GET /sponsors — list sponsors, filterable by ?category=food&status=prospect
  • POST /sponsors — create single sponsor
  • PATCH /sponsors/{id} — update sponsor fields (status, notes, custom_pitch, etc.)
  • DELETE /sponsors/{id} — delete sponsor
  • POST /sponsors/seed — bulk import from JSON array in request body
  • All endpoints are tenant-scoped (filter by tenant_id from auth context)
  • Router registered at /sponsors prefix in app.py

Test Expectations

  • Unit: Sponsor model creation, default status is prospect
  • Unit: seed_sponsors bulk import creates correct number of records
  • Integration: GET /sponsors returns filtered results
  • Integration: POST /sponsors creates and returns sponsor
  • Integration: PATCH /sponsors/{id} updates status
  • Integration: DELETE /sponsors/{id} removes record
  • Integration: POST /sponsors/seed bulk imports
  • Run command: pytest tests/test_sponsors.py -v

Constraints

  • Follow existing route patterns (see routes/jersey.py, routes/teams.py for style)
  • Use existing auth patterns for tenant scoping (see how other routes get current_tenant)
  • Migration must handle EmailType enum extension via ALTER TYPE ... ADD VALUE
  • Sponsor.email should not have a unique constraint — same business may have multiple contact emails

Checklist

  • PR opened
  • Tests pass
  • Migration runs cleanly
  • No unrelated changes
  • basketball-api#316 — parent epic
  • basketball-api#323 — seed data fixture (consumed by POST /sponsors/seed)
  • basketball-api#324 — blast endpoint (depends on this ticket)
### Type Feature ### Lineage Child of basketball-api#316 (sponsor outreach system). Blocked by: nothing. Blocks: #324 (blast endpoint). ### Repo `forgejo_admin/basketball-api` ### User Story As an admin, I want to manage sponsor contacts in the database, So that I can track businesses through the outreach pipeline. ### Context This ticket creates the Sponsor data model and basic CRUD endpoints. The model represents a local business in the sponsorship outreach pipeline with status tracking (prospect → contacted → responded → negotiating → committed → declined). This is the foundation ticket — the blast endpoint (#324) depends on this model and these routes existing. The seed endpoint (`POST /sponsors/seed`) consumes the JSON fixture from #323 but can be built independently — it just needs the model schema. ### File Targets Files the agent should modify: - `src/basketball_api/models.py` — add SponsorStatus enum, SponsorCategory enum, Sponsor model, add `sponsor_outreach` to EmailType enum - `src/basketball_api/app.py` — register sponsor router Files the agent should create: - `src/basketball_api/services/sponsor_service.py` — CRUD functions: get_sponsors, create_sponsor, update_sponsor, delete_sponsor, seed_sponsors - `src/basketball_api/routes/sponsors.py` — REST endpoints - `alembic/versions/xxx_add_sponsors.py` — migration for sponsors table + EmailType enum update - `tests/test_sponsors.py` — tests for model CRUD and endpoints Files the agent should NOT touch: - `src/basketball_api/services/email.py` — email sending is in the blast ticket - `src/basketball_api/routes/admin.py` — sponsors get their own router ### Acceptance Criteria - [ ] `SponsorStatus` enum: prospect, contacted, responded, negotiating, committed, declined - [ ] `SponsorCategory` enum: food, financial, retail, automotive, construction, fitness, dental, grocery, other - [ ] `Sponsor` model with: id, tenant_id (FK), business_name, email, category, status (default: prospect), custom_pitch (nullable), contact_name (nullable), phone (nullable), notes (nullable), last_contacted_at (nullable), created_at, updated_at - [ ] `EmailType.sponsor_outreach` added to enum - [ ] Alembic migration creates sponsors table and updates EmailType enum - [ ] `GET /sponsors` — list sponsors, filterable by `?category=food&status=prospect` - [ ] `POST /sponsors` — create single sponsor - [ ] `PATCH /sponsors/{id}` — update sponsor fields (status, notes, custom_pitch, etc.) - [ ] `DELETE /sponsors/{id}` — delete sponsor - [ ] `POST /sponsors/seed` — bulk import from JSON array in request body - [ ] All endpoints are tenant-scoped (filter by tenant_id from auth context) - [ ] Router registered at `/sponsors` prefix in app.py ### Test Expectations - [ ] Unit: Sponsor model creation, default status is prospect - [ ] Unit: seed_sponsors bulk import creates correct number of records - [ ] Integration: GET /sponsors returns filtered results - [ ] Integration: POST /sponsors creates and returns sponsor - [ ] Integration: PATCH /sponsors/{id} updates status - [ ] Integration: DELETE /sponsors/{id} removes record - [ ] Integration: POST /sponsors/seed bulk imports - Run command: `pytest tests/test_sponsors.py -v` ### Constraints - Follow existing route patterns (see `routes/jersey.py`, `routes/teams.py` for style) - Use existing auth patterns for tenant scoping (see how other routes get `current_tenant`) - Migration must handle EmailType enum extension via `ALTER TYPE ... ADD VALUE` - Sponsor.email should not have a unique constraint — same business may have multiple contact emails ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] Migration runs cleanly - [ ] No unrelated changes ### Related - basketball-api#316 — parent epic - basketball-api#323 — seed data fixture (consumed by POST /sponsors/seed) - basketball-api#324 — blast endpoint (depends on this ticket)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/basketball-api#324
No description provided.