Files
plant-disease-id/tasks/multi-image-user-feedback/08-feedback-api-endpoint.md

3.9 KiB

08. Feedback API Endpoint for Accuracy Ratings and Storage Consent

meta: id: multi-image-user-feedback-08 feature: multi-image-user-feedback priority: P1 depends_on: [multi-image-user-feedback-01] tags: [api, backend, database]

objective:

  • Create a POST endpoint at /api/feedback that accepts diagnosis feedback submissions (accuracy rating, notes, image storage consent) and persists them to the database.

deliverables:

  • src/app/api/feedback/route.ts — new API route
  • src/app/api/feedback/feedback.test.ts — test file

steps:

  1. Create src/app/api/feedback/route.ts:

    Route: POST /api/feedback

    Accepts JSON body matching FeedbackRequest:

    {
      sessionId: string;
      imageIds: string[];
      userSpecies?: string;
      predictedDiseaseId: string;
      accuracyRating: "correct" | "incorrect" | "unsure";
      consentToStoreImages: boolean;
      userCorrectedSpecies?: string;
      notes?: string;
    }
    

    Handler logic:

    • Parse and validate request body
    • Generate UUID for id
    • Get modelVersion from model loader's getStatus()
    • Set createdAt to current timestamp
    • Insert into diagnosisFeedback table via Drizzle
    • Return FeedbackResponse: { success: true, id: string }
    • Handle validation errors with 400 status
    • Handle DB errors with 500 status

    Validation rules:

    • sessionId — required, non-empty string
    • imageIds — required, array of non-empty strings, min length 1
    • accuracyRating — required, must be one of "correct", "incorrect", "unsure"
    • consentToStoreImages — required, boolean
    • userSpecies — optional string
    • userCorrectedSpecies — optional string, only meaningful when accuracy is not "correct"
    • notes — optional string, max 500 characters (with error message if exceeded)
  2. Create src/lib/api/feedback.ts — client-side helper:

    • submitFeedback(data: FeedbackRequest): Promise<FeedbackResponse>
    • POST to /api/feedback with JSON body
    • 15-second timeout
    • Handle network errors gracefully
  3. Handle edge cases:

    • Invalid JSON body → 400 with descriptive error
    • Missing required fields → 400 listing missing fields
    • Invalid accuracyRating value → 400 with allowed values
    • Database unreachable → 500 with error message
    • Duplicate sessionId → allowed (user can submit multiple times for different predictions)
  4. CORS and caching:

    • Add Cache-Control: no-store header
    • No authentication required (public endpoint for feedback)

tests:

  • Unit: valid feedback submission returns 200 with success
  • Unit: missing required fields return 400
  • Unit: invalid accuracyRating returns 400
  • Unit: notes over 500 chars returns 400
  • Unit: empty imageIds array returns 400
  • Unit: client helper submitFeedback() makes correct fetch call
  • Unit: client helper handles network error gracefully
  • Integration: submit feedback and verify it exists in database

acceptance_criteria:

  • POST /api/feedback accepts valid feedback and stores it
  • Invalid requests return appropriate 400 errors with descriptive messages
  • Database stores all fields correctly
  • Client helper function is usable from any feedback component
  • Endpoint returns { success: true, id } on success

validation:

  • npx tsc --noEmit passes
  • Unit tests pass: npx vitest run src/app/api/feedback/
  • Manual test: curl -X POST http://localhost:3000/api/feedback -H 'Content-Type: application/json' -d '{"sessionId":"test","imageIds":["img1"],"predictedDiseaseId":"early-blight","accuracyRating":"correct","consentToStoreImages":false}'
  • Verify stored data with direct DB query

notes:

  • No auth needed for MVP — feedback is public and anonymous
  • imageIds reference images in the uploads directory; no automatic cleanup
  • A future task could add a review/admin dashboard for browsing feedback entries
  • Rate limiting could be added later if needed (by sessionId or IP)