Skip to main content

Backend Developer Handoff Summary

AI-Powered Photo Journaling iOS App - MVP Q1 2025
Project: 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
Critical Constraint: Vercel Hobby has a 10-second timeout for serverless functions. AI prompt generation takes 8-15 seconds, so you must use an async job queue pattern (202 Accepted + polling).

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:
Response (201 Created):
Business Logic:
  1. Validate JWT from Authorization: Bearer <token>
  2. Check photo limit (free users: 50/month, premium: unlimited)
  3. Insert journal_entries row
  4. Insert entry_emotions rows (junction table)
  5. Increment users.monthly_photo_count
  6. Return created entry with emotions populated
Error Cases:
  • 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:
Response (200 OK):
Optimizations:
  • Use SELECT with LIMIT and OFFSET for pagination
  • Truncate journal_text to 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:
Response (200 OK): Same as GET /api/entries/:id Business Logic:
  • Update journal_entries table
  • If emotion_ids provided: DELETE existing entry_emotions, INSERT new ones
  • Update updated_at timestamp (automatic via trigger)

DELETE /api/entries/:id

Delete journal entry. Response (204 No Content) Business Logic:
  • Delete from journal_entries (cascades to entry_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):
Response (200 OK):
Business Logic:
  1. Validate file type (JPEG, PNG only)
  2. Validate file size (< 4MB)
  3. Generate unique filename: {uuid}_original.jpg
  4. 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)
  5. Upload to Supabase Storage: journal-photos/{user_id}/{uuid}_*.jpg
  6. Return URLs
Libraries:
  • sharp for image resizing/compression
  • uuid for unique filenames
Error Cases:
  • 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:
Response (202 Accepted):
Business Logic (Async):
  1. Create job in Vercel KV: SET prompt_abc123 '{"status":"pending","photo_url":"..."}'
  2. Return 202 immediately
  3. 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)
AI Prompt Example (to GPT-5):
AI Provider Abstraction (from architecture):

GET /api/ai/jobs/:job_id

Poll job status (client polls every 2 seconds). Response (200 OK):
Fallback Prompts (if AI fails):

4. Search & Filter

GET /api/entries/search

Search journal entries by keyword, date range, and emotions. Query Params:
Response (200 OK):
SQL Query (Full-text search + filters):
Business Logic:
  • 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:
Response (200 OK): Same as GET /api/users/me

POST /api/users/me/photo

Upload profile photo. Request (multipart/form-data):
Response (200 OK):
Business Logic:
  • 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:
Response (200 OK):
Business Logic:
  1. Validate receipt with Apple’s verifyReceipt API:
    • Production: https://buy.itunes.apple.com/verifyReceipt
    • Sandbox: https://sandbox.itunes.apple.com/verifyReceipt
  2. Check latest_receipt_info for subscription details
  3. Insert/update subscriptions table
  4. Update users.subscription_tier to ‘premium’
  5. Update users.subscription_status to ‘trial’ or ‘active’
  6. Set trial dates (14 days from now)
Apple IAP Verification Example:

POST /api/subscriptions/webhook

Apple App Store Server Notifications webhook (subscription status changes). Request (from Apple):
Business Logic:
  • Update subscriptions table based on notification type
  • Handle renewals, cancellations, expirations, grace periods
  • Send email notifications to users (optional)
Notification Types:
  • 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):
Business Logic (Async):
  1. Fetch all user’s journal entries
  2. Generate JSON file:
  1. Download all photos from Supabase Storage
  2. Create ZIP file: {email}_data_export_{timestamp}.zip
  3. Upload ZIP to Supabase Storage (temporary, 7-day expiration)
  4. Send email with download link (use Resend or SendGrid)
Email Template:

8. Account Deletion

DELETE /api/users/me

Soft delete user account (30-day recovery period). Request:
Response (204 No Content) Business Logic:
  1. Verify password with Supabase Auth
  2. Set users.deleted_at = NOW()
  3. Invalidate all sessions (sign out)
  4. Schedule hard delete after 30 days (cron job)
  5. DO NOT delete data yet (soft delete allows recovery)
Hard Delete (after 30 days):

Authentication Middleware

All API routes (except public endpoints) require JWT authentication. Middleware (lib/auth/middleware.ts):
Usage:

Error Handling

Standardized error responses:
Common Errors:
  • 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:
Rate Limits:
  • 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:
  1. Connect GitHub repo to Vercel
  2. Set environment variables in Vercel dashboard
  3. Deploy to production: vercel --prod
Environment Setup:
Serverless Function Configuration:

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
Use 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
You will hand off to @frontend-developer:
  • ✅ All API endpoints implemented and documented
  • ✅ API base URL (Vercel deployment)
  • ✅ Sample API requests/responses
  • ✅ Error codes and messages
  • ✅ Rate limits and quotas

Resources


Questions? Reach out to @software-architect or @dba for clarification. Ready to build! 🚀