Backend Developer Handoff Summary
AI-Powered Photo Journaling iOS App - MVP Q1 2025Project: AI-Powered Photo Journaling iOS App Timeline: 12-week MVP (Q1 2025) Your Phase: Backend API Implementation Tech Stack: Next.js 14+ with TypeScript, Vercel Serverless Start Date: Week 7 (after database schema complete) Target Completion: Week 10 (Feb 25, 2025)
Executive Summary
You are responsible for implementing the backend API for an AI-powered photo journaling iOS app. The backend is built with Next.js 14+ API Routes deployed on Vercel Hobby Plan (10-second timeout constraint) using TypeScript. Core Responsibilities:- Journal entry CRUD API endpoints
- Photo upload to Supabase Storage
- AI prompt generation (OpenAI GPT-5 with async job queue)
- Apple In-App Purchase (IAP) receipt validation
- User data export (JSON + ZIP of photos)
- Search & filter API endpoints
Tech Stack
Framework: Next.js 14+ with TypeScript (App Router) Hosting: Vercel Hobby Plan (10-second timeout, 512MB memory) Database: Supabase PostgreSQL (via@supabase/supabase-js)
Storage: Supabase Storage (for photos)
AI: OpenAI GPT-5 (production) with GPT-4.1 fallback, Ollama (development)
Queue: Vercel KV (Redis) for async job queue
Auth: Supabase Auth (JWT validation in API routes)
Environment Variables:
API Endpoints to Implement
1. Journal Entries
POST /api/entries
Create a new journal entry. Request:- Validate JWT from
Authorization: Bearer <token> - Check photo limit (free users: 50/month, premium: unlimited)
- Insert
journal_entriesrow - Insert
entry_emotionsrows (junction table) - Increment
users.monthly_photo_count - Return created entry with emotions populated
- 401: Unauthorized (invalid/missing JWT)
- 403: Photo limit reached (free tier)
- 400: Validation errors (missing required fields)
- 500: Database error
GET /api/entries
List user’s journal entries (timeline view). Query Params:- Use
SELECTwithLIMITandOFFSETfor pagination - Truncate
journal_textto 100 chars (full text only in detail view) - Only include first 3 emotions (UI shows “+2 more” if >3)
GET /api/entries/:id
Get single journal entry (detail view). Response (200 OK):PATCH /api/entries/:id
Update journal entry. Request:- Update
journal_entriestable - If
emotion_idsprovided: DELETE existingentry_emotions, INSERT new ones - Update
updated_attimestamp (automatic via trigger)
DELETE /api/entries/:id
Delete journal entry. Response (204 No Content) Business Logic:- Delete from
journal_entries(cascades toentry_emotions,ai_prompts) - Decrement
users.monthly_photo_count - Delete photo from Supabase Storage (3 files: original, thumbnail, preview)
2. Photo Upload
POST /api/photos/upload
Upload photo to Supabase Storage (before creating entry). Request (multipart/form-data):- Validate file type (JPEG, PNG only)
- Validate file size (< 4MB)
- Generate unique filename:
{uuid}_original.jpg - Resize to 3 versions:
- Original: Max 2048×2048px (preserve aspect ratio)
- Thumbnail: 400×400px (square crop for timeline grid)
- Preview: 1024×1024px (preserve aspect ratio for detail view)
- Upload to Supabase Storage:
journal-photos/{user_id}/{uuid}_*.jpg - Return URLs
sharpfor image resizing/compressionuuidfor unique filenames
- 400: Invalid file type or size
- 413: File too large (>4MB)
- 500: Upload error
3. AI Prompt Generation
⚠️ CRITICAL: This endpoint takes 8-15 seconds, so it MUST use async job queue (Vercel 10s timeout).POST /api/ai/prompts
Generate 3 AI-powered journal prompts from photo. Request:- Create job in Vercel KV:
SET prompt_abc123 '{"status":"pending","photo_url":"..."}' - Return 202 immediately
- Background worker (separate API route or Vercel cron):
- Fetch job from KV
- Call AI provider (OpenAI GPT-5 or Ollama)
- Generate 3 prompts
- Update KV:
SET prompt_abc123 '{"status":"completed","prompts":[...]}' - Set TTL: 1 hour (auto-expire)
GET /api/ai/jobs/:job_id
Poll job status (client polls every 2 seconds). Response (200 OK):4. Search & Filter
GET /api/entries/search
Search journal entries by keyword, date range, and emotions. Query Params:- Use PostgreSQL
ts_rank()for relevance scoring - Highlight matching keywords in
journal_text(return with<mark>tags) - Debounce search on client side (300ms)
5. User Profile
GET /api/users/me
Get current user’s profile. Response (200 OK):PATCH /api/users/me
Update user profile. Request:POST /api/users/me/photo
Upload profile photo. Request (multipart/form-data):- Resize to 256×256px (circular crop)
- Upload to Supabase Storage:
profile-photos/{user_id}.jpg - Update
users.profile_photo_url
6. Subscriptions & IAP
POST /api/subscriptions/verify
Verify Apple IAP receipt and activate Premium subscription. Request:- Validate receipt with Apple’s verifyReceipt API:
- Production:
https://buy.itunes.apple.com/verifyReceipt - Sandbox:
https://sandbox.itunes.apple.com/verifyReceipt
- Production:
- Check
latest_receipt_infofor subscription details - Insert/update
subscriptionstable - Update
users.subscription_tierto ‘premium’ - Update
users.subscription_statusto ‘trial’ or ‘active’ - Set trial dates (14 days from now)
POST /api/subscriptions/webhook
Apple App Store Server Notifications webhook (subscription status changes). Request (from Apple):- Update
subscriptionstable based on notification type - Handle renewals, cancellations, expirations, grace periods
- Send email notifications to users (optional)
DID_RENEW: Subscription renewed successfully →status = 'active'DID_FAIL_TO_RENEW: Payment failed →status = 'grace_period'or'billing_retry'CANCEL: User cancelled →status = 'cancelled'(active until end date)EXPIRED: Subscription expired →status = 'expired',tier = 'free'
7. Data Export
POST /api/users/export
Request data export (sends email with download link). Request: (empty body, user from JWT) Response (202 Accepted):- Fetch all user’s journal entries
- Generate JSON file:
- Download all photos from Supabase Storage
- Create ZIP file:
{email}_data_export_{timestamp}.zip - Upload ZIP to Supabase Storage (temporary, 7-day expiration)
- Send email with download link (use Resend or SendGrid)
8. Account Deletion
DELETE /api/users/me
Soft delete user account (30-day recovery period). Request:- Verify password with Supabase Auth
- Set
users.deleted_at = NOW() - Invalidate all sessions (sign out)
- Schedule hard delete after 30 days (cron job)
- DO NOT delete data yet (soft delete allows recovery)
Authentication Middleware
All API routes (except public endpoints) require JWT authentication. Middleware (lib/auth/middleware.ts):
Error Handling
Standardized error responses:- 400: Bad Request (validation errors)
- 401: Unauthorized (missing/invalid JWT)
- 403: Forbidden (photo limit reached, permission denied)
- 404: Not Found (resource doesn’t exist)
- 413: Payload Too Large (file size limit)
- 429: Too Many Requests (rate limiting)
- 500: Internal Server Error
- 503: Service Unavailable (AI provider down)
Rate Limiting
Implement rate limiting to prevent abuse:- Photo upload: 5 uploads per minute
- AI prompt generation: 10 requests per minute
- Entry creation: 10 entries per minute
- Search: 20 searches per minute
Testing Checklist
API Endpoint Tests:
- POST /api/entries: Creates entry, increments photo count
- GET /api/entries: Returns paginated entries
- GET /api/entries/:id: Returns full entry details
- PATCH /api/entries/:id: Updates entry
- DELETE /api/entries/:id: Deletes entry, decrements photo count, deletes photos
- POST /api/photos/upload: Uploads 3 photo sizes, returns URLs
- POST /api/ai/prompts: Returns 202, creates job in KV
- GET /api/ai/jobs/:id: Returns job status (pending/completed/error)
- GET /api/entries/search: Full-text search works, filters apply correctly
- POST /api/subscriptions/verify: Validates Apple receipt, activates Premium
- POST /api/users/export: Sends email with ZIP download link
- DELETE /api/users/me: Soft deletes account
Auth & Security Tests:
- Unauthorized requests return 401
- Invalid JWT returns 401
- Users can only access their own data (RLS enforced)
- Photo limit enforced for free users
- Premium users have unlimited photos
- Rate limiting works (429 after threshold)
AI Integration Tests:
- OpenAI provider generates 3 prompts
- Ollama provider generates 3 prompts (dev environment)
- Fallback prompts used if AI fails
- Job queue pattern works (202 → polling → completed)
- AI timeout handled gracefully (>15s)
Performance Tests:
- Timeline query (20 entries): < 200ms
- Search query: < 500ms
- Photo upload (4MB): < 3s
- Entry creation: < 300ms
Deployment
Vercel Deployment:- Connect GitHub repo to Vercel
- Set environment variables in Vercel dashboard
- Deploy to production:
vercel --prod
Linear Ticket References
Create Linear tickets for each API endpoint:- OVE-XX: Journal Entries CRUD API
- OVE-XX: Photo Upload API
- OVE-XX: AI Prompt Generation (Async Job Queue)
- OVE-XX: Search & Filter API
- OVE-XX: User Profile API
- OVE-XX: Apple IAP Receipt Validation
- OVE-XX: Data Export API
- OVE-XX: Account Deletion API
- OVE-XX: Auth Middleware & RLS Testing
- OVE-XX: Rate Limiting Implementation
- OVE-XX: Vercel Deployment
micro-commit-workflow skill for atomic commits with LINEAR references.
Use linear-integration skill to update progress.
Handoff from DBA
You will receive from @dba:- ✅ Complete database schema (users, journal_entries, emotions, etc.)
- ✅ All migrations applied and tested
- ✅ Supabase project URL and anon key
- ✅ RLS policies verified
- ✅ Sample queries for common operations
- ✅ All API endpoints implemented and documented
- ✅ API base URL (Vercel deployment)
- ✅ Sample API requests/responses
- ✅ Error codes and messages
- ✅ Rate limits and quotas
Resources
- Next.js Docs: https://nextjs.org/docs
- Supabase JS Client: https://supabase.com/docs/reference/javascript
- OpenAI API: https://platform.openai.com/docs
- Apple IAP Docs: https://developer.apple.com/documentation/appstoreserverapi
- Vercel KV: https://vercel.com/docs/storage/vercel-kv
- PRD:
docs/product/prds/mvp-q1-2025-prd.md - Architecture:
docs/architecture/system-design.md,docs/architecture/async-processing.md,docs/architecture/ai-abstraction.md
Questions? Reach out to @software-architect or @dba for clarification. Ready to build! 🚀