Skip to content

Admin Applicants — detail, inline edits, CSV exports #503

Description

@adityapat24

Context

Last sprint shipped the list endpoint with filters/sort/pagination, plus seeding and indexes. What's missing is the detail view, the ability to change decisions and RSVP status inline, and CSV exports for admin data analysis.

Tasks

Detail endpoint

  • Implement getApplicant(id) in src/lib/applicants/service.ts. Returns the full applicant document from applicant_data with every field — email, all applicationResponses, applicationStatus, decisionStatus, rsvpStatus, postAcceptanceResponses, timestamps, and any linked upload records.
    • What this accomplishes: The applicant detail page needs everything about one applicant on one screen. Unlike the list which summarizes, this returns the full record for review.
  • For upload references in responses, resolve them to displayable info: filename and a link that calls /api/v1/uploads/[id] (which returns a signed download URL). Alternatively, return the upload record inline and let the frontend request the download URL when needed.
    • What this accomplishes: Admins reviewing an application can see what files the applicant uploaded and download them (resumes, etc.). Without this, the responses just show opaque upload IDs.
  • Wire GET /api/v1/applicants/[id]/route.ts to call getApplicant. Returns 404 for missing IDs.

Update endpoint

  • Implement updateApplicant(id, patch, updatedBy) in the service. Accepts a partial patch of { decisionStatus?, rsvpStatus? }. Validates each field is a valid enum value. Writes with $set to the applicant document, plus updatedBy and updatedAt audit fields.
    • What this accomplishes: The single function admins use to change decisions and RSVP statuses. Audit fields record who made the change and when.
  • Reject patches that try to change other fields (email, responses, etc.). Only decision and RSVP are admin-editable through this endpoint.
    • What this accomplishes: Prevents an admin from accidentally overwriting an applicant's responses. Application content is applicant-owned; only status fields are admin-owned.
  • Wire POST /api/v1/applicants/[id]/route.ts to call updateApplicant. Returns the updated applicant on success, 400 on invalid patch, 404 if missing.

CSV exports

  • Implement toCsv(rows, columns) streaming helper in src/lib/applicants/csv.ts. Uses ReadableStream to send row-by-row instead of buffering everything.
    • What this accomplishes: Exports with thousands of applicants don't blow up memory. Streaming lets the server hand off each row as it's serialized instead of holding the whole file at once.
  • Properly escape CSV fields: wrap in double quotes if the value contains commas, newlines, or double quotes; escape internal double quotes by doubling them.
    • What this accomplishes: Applicant answers contain commas, quotes, and newlines constantly. Naive CSV generation would produce malformed files that break Excel.
  • Implement GET /api/v1/export/applications/route.ts. Streams a CSV with one row per applicant containing email, name, status, decision, RSVP, and one column per application question response. Set Content-Type: text/csv and Content-Disposition: attachment; filename="applications-YYYY-MM-DD.csv".
    • What this accomplishes: Clicking "Export applications CSV" on the admin page downloads a file that opens directly in Excel. The date in the filename helps admins keep multiple exports organized.
  • Implement GET /api/v1/export/post-acceptance/route.ts with the same pattern but for postAcceptanceResponses.
    • What this accomplishes: Separate export for RSVP data — dietary restrictions, t-shirt sizes, etc. — that event ops uses.

Auth gating

  • Wire requireAdmin() into GET /api/v1/applicants, GET /api/v1/applicants/[id], POST /api/v1/applicants/[id], GET /api/v1/export/applications, and GET /api/v1/export/post-acceptance.
    • What this accomplishes: Applicant data never leaves the system to anyone but admins. Non-admins get 403 on all six endpoints.

End-to-end verification

  • Sign in as admin, open /admin/applicants, click a row, verify the detail page shows all responses and any uploaded files are downloadable.
  • Change a decision inline, refresh, verify it stuck.
  • Click both export buttons, open the resulting CSVs in Excel or a text editor, verify structure is sensible and special characters are escaped.

Definition of done

  • Admins can view a full applicant detail page including downloadable uploads.
  • Admins can change decision and RSVP status inline from the detail page and it persists.
  • Both CSV endpoints download well-formed files with all applicant data.
  • All five endpoints are gated by requireAdmin().

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions