Codex 5.3 Refactor Note: Canonical refactor plan: docs/CODEX-5.3-REFACTOR-PLAN.md. This document is retained for historical and implementation context during the refactor.
W6: HR Upload Resume - Implementation Complete ✅
Quick Start
What is W6?
W6 is the entry point to the hiring system. HR users upload a resume (PDF/DOC/DOCX), enter a position, and the system creates a Candidate record (status=NEW) + Document record, then redirects to the candidate detail page (W7).
Files Summary
- 2 API endpoints (presign + create candidate)
- 2 UI components (form + dropzone)
- 2 utility libraries (S3 + code generator)
- Updated database schema (Candidate + CandidateDocument models)
- 4 comprehensive docs (spec + setup + guide + code structure)
Deployment (5 steps)
-
Database Migration
cd /Users/rezafahmi/projectweb-nextjs export $(cat .env.local | xargs) npx prisma migrate dev --name add_candidates_and_documents -
Install AWS SDK
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner -
Configure AWS Add to
.env.local:AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=your-key AWS_SECRET_ACCESS_KEY=your-secret S3_BUCKET_NAME=offers-review -
Start Dev Server
npm run dev -
Test
- Login as HR user at http://localhost:3000/login
- Navigate to http://localhost:3000/upload-resume
- Upload PDF resume
- Verify redirect and database records
Key Files
| File | Purpose | Lines |
|---|---|---|
src/app/api/uploads/presign/route.ts | S3 presign endpoint | 54 |
src/app/api/candidates/route.ts | Create candidate endpoint | 104 |
src/app/(app)/upload-resume/page.tsx | Main form | 312 |
src/app/(app)/upload-resume/_components/Dropzone.tsx | Drag-drop component | 124 |
src/lib/storage/s3.ts | S3 helper | 42 |
src/lib/candidates/code.ts | Code generator | 7 |
prisma/schema.prisma | Database models | +60 |
How It Works
User selects PDF + position
↓
POST /api/uploads/presign
↓
Get presigned S3 URL + storageKey
↓
PUT file directly to S3
↓
POST /api/candidates with storageKey
↓
Create Candidate (NEW) + Document (RESUME) + audit logs
↓
Auto-redirect to /candidates/{id}
RBAC
- Only HR and Admin can access
/upload-resume - Only HR and Admin can call
/api/candidates - Other roles get 403 Forbidden
Validation
- File types: PDF, DOC, DOCX only
- File size: Max 10MB
- Position: Required
- Name: Optional
Audit Events
- CANDIDATE_CREATED – Logs candidateCode, status, position
- RESUME_UPLOADED – Logs filename, size, storage location
Documentation
docs/W6-UPLOAD-RESUME-IMPLEMENTATION.md– Full specificationdocs/W6-IMPLEMENTATION-SUMMARY.md– Overview + integration pointsdocs/W6-SETUP-GUIDE.md– Migration + deployment + testingdocs/W6-CODE-STRUCTURE.md– File tree + code snippetsW6-DELIVERY-SUMMARY.md– This project delivery summary
Testing Checklist
- Migration applied
- AWS credentials configured
- Dev server running
- HR can upload PDF
- Redirect to
/candidates/{id}works - Database records created
- Audit logs exist
- File in S3
- RBAC works (403 for other roles)
- Error handling (invalid type, >10MB)
Common Issues
"DATABASE_URL not found"
export $(cat .env.local | xargs)
"Cannot find module @aws-sdk/..."
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
"Can't reach database server" Ensure PostgreSQL is running:
psql projectweb -U postgres -c "SELECT 1"
API Endpoints
POST /api/uploads/presign
Request: { "filename": "resume.pdf", "mimeType": "application/pdf", "sizeBytes": 524288 }
Response: { "uploadUrl": "https://...", "storageKey": "candidates/...", "expiresIn": 3600 }
Auth: HR/Admin only
POST /api/candidates
Request: {
"applyingFor": "Software Engineer",
"fullName": "John Doe",
"resume": { "filename": "...", "mimeType": "...", "sizeBytes": 524288, "storageKey": "..." }
}
Response: { "candidateId": "cuid...", "candidateCode": "CAND-2026-45678" }
Auth: HR/Admin only
Database Schema
Candidate
- id (PK)
- candidateCode (UNIQUE, CAND-YYYY-#####)
- fullName (nullable)
- applyingFor (required)
- status (NEW by default)
- createdByUserId (FK to User)
- createdAt, updatedAt
CandidateDocument
- id (PK)
- candidateId (FK)
- category (RESUME)
- filename, mimeType, sizeBytes
- storageKey (S3 path)
- version (1)
- uploadedByUserId (FK to User)
- createdAt
Integration
Upstream: W5 (Dashboard) – Can link to /upload-resume for HR
Downstream: W7 (Candidate Intake) – Auto-redirects from W6, loads candidate detail + resume
Same Pattern: W3 (Access Requests) – Same RBAC (HR/Admin only)
Performance
- Upload: 10MB max, presigned URL expires in 1 hour
- DB: Indexed on candidateCode, status, createdByUserId
- Audit: Non-blocking (failure doesn't break operation)
Security
- JWT authentication (httpOnly cookie)
- Role-based access control (403 if not HR/Admin)
- File validation (type + size, client + server)
- S3 presigned URLs (1-hour expiry)
- Audit trail for compliance
What's Next
- Apply migration
- Configure AWS
- Test end-to-end
- Deploy to staging
- Implement W7 (Candidate Intake)
Support
See documentation in docs/ folder:
- Stuck? Check
W6-SETUP-GUIDE.md - Need code? Check
W6-CODE-STRUCTURE.md - Full spec? Check
W6-UPLOAD-RESUME-IMPLEMENTATION.md
W6 is ready for deployment! 🚀
For questions, refer to the comprehensive documentation provided.