assessment-model/bulk-address-upload.md
2026-04-16 17:47:53 +00:00

3.2 KiB

Bulk Address Upload — Implementation Tracker

Overview

Upload CSV/XLSX to S3 (browser-direct via XHR with progress bar) → confirm in DB → redirect to upload list. Portfolio shows all uploads ordered by date. User picks which to continue.


DB Migration (manual — do this first)

CREATE TABLE bulk_address_uploads (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  portfolio_id TEXT NOT NULL,
  user_id TEXT NOT NULL,
  s3_bucket TEXT NOT NULL,
  s3_key TEXT NOT NULL,
  filename TEXT NOT NULL,
  status TEXT NOT NULL DEFAULT 'ready_for_processing',
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Status values: ready_for_processing | processing | complete | failed

  • Migration applied to dev
  • Migration applied to staging
  • Migration applied to prod

Tasks

1. Drizzle Schema

  • Create src/app/db/schema/bulk_address_uploads.ts
  • Import + spread into src/app/db/db.ts

2. Confirm API Route

  • Create src/app/api/upload/bulk-addresses/confirm/route.ts
    • POST { fileKey, filename, portfolioId, userId }
    • Inserts into bulk_address_uploads, s3Bucket from RETROFIT_PLAN_INPUT_BUCKET_NAME env
    • Returns { id, s3Key, s3Bucket, status }

3. List API Route

  • Create src/app/api/portfolio/[portfolioId]/bulk-uploads/route.ts
    • GET → all uploads for portfolio ordered by created_at DESC
    • Returns array of upload records

4. Modal — XHR Upload + Progress + Redirect to List

  • Replace fetch PUT → XMLHttpRequest in handleUpload
  • Add progress: number | null state
  • Show progress bar in dropzone while uploading
  • After XHR load: POST confirm → router.push(/portfolio/[id]/bulk-upload)

5. Upload List Page

  • Create src/app/portfolio/[portfolioId]/bulk-upload/page.tsx
    • Server component
    • List all uploads: filename, status badge, created date, "Continue →" link
    • Empty state if none
    • Each row links to /bulk-upload/[uploadId]

6. Upload Detail Page

  • Create src/app/portfolio/[portfolioId]/bulk-upload/[uploadId]/page.tsx
    • Server component
    • Shows: filename, s3://bucket/key, status, created date
    • For now: "Your file is queued for processing"

Flow

User drops/clicks file
  → validate (size, extension, headers)
  → GET presigned URL     (/api/upload/bulk-addresses)
  → XHR PUT to S3         (progress bar shown)
  → POST confirm          (/api/upload/bulk-addresses/confirm)
  → redirect to list      (/portfolio/[id]/bulk-upload)
  → list page             (all uploads, status badges, click to continue)
  → detail page           (/portfolio/[id]/bulk-upload/[uploadId])
  → shows s3_uri + status

Files Touched

File Status
src/app/db/schema/bulk_address_uploads.ts not started
src/app/db/db.ts not started
src/app/api/upload/bulk-addresses/confirm/route.ts not started
src/app/api/portfolio/[portfolioId]/bulk-uploads/route.ts not started
src/app/components/portfolio/BulkUploadComingSoonModal.tsx not started
src/app/portfolio/[portfolioId]/bulk-upload/page.tsx not started
src/app/portfolio/[portfolioId]/bulk-upload/[uploadId]/page.tsx not started