Spaces:
Sleeping
Sleeping
| # Deployment Guide β CourtMitra MissionOS | |
| > **Target:** Vercel (frontend) + Hugging Face Spaces (backend + worker) + Google OAuth (Clerk) + Neon (Postgres) | |
| --- | |
| ## Architecture Overview | |
| ``` | |
| βββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββββββ | |
| β Vercel (Next.js 15) ββββββββββΆβ Hugging Face Spaces (Docker) β | |
| β - Clerk auth β HTTPS β - NestJS API + Inngest Worker β | |
| β - Hero page β β - Port 7860 β | |
| β - Profile page β β β | |
| β - Mission dashboard β β /api/* β API routes β | |
| β - Static assets β β /api/inngest β Worker functions β | |
| βββββββββββββββββββββββββββ ββββββββββββββββββββββ¬ββββββββββββββββββββββ | |
| β | |
| β postgres://... | |
| βΌ | |
| ββββββββββββββββββββββββββββββ | |
| β Neon Postgres (Cloud) β | |
| β - 55 tables β | |
| β - pgvector + FTS β | |
| β - Legal chunks seeded β | |
| ββββββββββββββββββββββββββββββ | |
| ``` | |
| --- | |
| ## Env Files (Where Secrets Live) | |
| > **Crucial for your partner:** There are TWO separate env files. One is gitignored (real secrets), the other is a committed template. | |
| | File | In Git? | Where | Purpose | | |
| |------|---------|-------|---------| | |
| | `apps/web/.env.local` | β **NO** β gitignored | Your laptop / partner's laptop | **Frontend local dev** β Clerk keys + API URL. You MUST create this and fill in real keys. | | |
| | `apps/web/.env.local.example` | β YES | In repo as template | Shows exactly what `.env.local` should contain. | | |
| | `.env` | β **NO** β gitignored | Your laptop / partner's laptop | **Backend local dev** β DB, AI, Inngest, secrets. Copy from `.env.example` and fill in. | | |
| | `.env.example` | β YES | In repo as template | Comprehensive backend env template. | | |
| **What your partner needs to do before `pnpm dev`:** | |
| 1. Create `apps/web/.env.local` (copy from `apps/web/.env.local.example`) | |
| 2. Paste in `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` from Clerk dashboard | |
| 3. Create `.env` at repo root (copy from `.env.example`) | |
| 4. Fill in `DATABASE_URL`, `OPENROUTER_API_KEY`, etc. | |
| --- | |
| ## Prerequisites | |
| | Service | Purpose | Free Tier | | |
| |---------|---------|-----------| | |
| | [Vercel](https://vercel.com) | Frontend hosting | Hobby (unlimited) | | |
| | [Clerk](https://clerk.com) | Auth + Google OAuth | 10,000 MAU | | |
| | [Hugging Face Spaces](https://huggingface.co/spaces) | Backend API + Worker | CPU Basic (free) | | |
| | [Neon](https://neon.tech) | Postgres database | 500 MB, 200 hrs | | |
| | [Inngest](https://inngest.com) | Background workflows | 50k events/mo | | |
| | [OpenRouter](https://openrouter.ai) | LLM API | $5 free credits | | |
| --- | |
| ## Step 1: Clerk + Google OAuth (10 min) | |
| 1. Go to [dashboard.clerk.com](https://dashboard.clerk.com) β Create Application. | |
| 2. **Social Connections** β enable **Google**. | |
| - Development: toggle on (uses Clerk's shared OAuth credentials). | |
| - Production: add your own Google Client ID/Secret. | |
| 3. Copy keys from **API Keys** tab: | |
| - `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...` | |
| - `CLERK_SECRET_KEY=sk_test_...` | |
| 4. Add your Vercel domain to **URLs** β **Allowed Origins**: | |
| - `https://your-project.vercel.app` | |
| - `http://localhost:3000` (for local dev) | |
| --- | |
| ## Step 2: Neon Postgres (10 min) | |
| 1. [neon.tech](https://neon.tech) β New Project β copy **connection string**. | |
| 2. Run migrations from your local machine: | |
| ```bash | |
| export DATABASE_URL="postgres://courtmitra:password@host.neon.tech/courtmitra" | |
| export DIRECT_DATABASE_URL="$DATABASE_URL" | |
| pnpm db:migrate | |
| pnpm db:seed | |
| ``` | |
| 3. Verify: check that `users`, `missions`, `route_rules`, `legal_chunks` tables exist. | |
| --- | |
| ## Step 3: Frontend β Vercel (5 min) | |
| 1. Push repo to GitHub. | |
| 2. Vercel β **Add New Project** β import repo. | |
| 3. Set **Environment Variables**: | |
| | Variable | Value | Notes | | |
| |----------|-------|-------| | |
| | `NEXT_PUBLIC_API_BASE_URL` | `https://your-hf-space.hf.space/api` | Your HF Spaces URL | | |
| | `INTERNAL_API_BASE_URL` | `https://your-hf-space.hf.space/api` | Same as above | | |
| | `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` | `pk_test_...` | From Clerk dashboard | | |
| | `CLERK_SECRET_KEY` | `sk_test_...` | From Clerk dashboard | | |
| | `NEXT_PUBLIC_CLERK_SIGN_IN_URL` | `/en/sign-in` | Or `/hi/sign-in` | | |
| | `NEXT_PUBLIC_CLERK_SIGN_UP_URL` | `/en/sign-up` | Or `/hi/sign-up` | | |
| 4. Deploy. | |
| --- | |
| ## Step 4: Backend β Hugging Face Spaces (20 min) | |
| ### 4a. Create Space | |
| 1. [huggingface.co/spaces](https://huggingface.co/spaces) β **Create new Space**. | |
| 2. **Space name:** `courtmitra-api` | |
| 3. **SDK:** `Docker` | |
| 4. **Visibility:** Public | |
| 5. **Hardware:** Free CPU | |
| ### 4b. Link GitHub Repo | |
| In Space Settings β **Repository** β link your GitHub repo. | |
| The `Dockerfile.hf` at repo root builds the monorepo and runs API + Worker in one container. | |
| ### 4c. Set Environment Variables | |
| In Space β **Settings** β **Variables & Secrets**: | |
| #### Required (App won't start without these) | |
| ``` | |
| PORT=7860 | |
| API_PORT=7860 | |
| DATABASE_URL=postgres://courtmitra:password@host.neon.tech/courtmitra | |
| DIRECT_DATABASE_URL=postgres://courtmitra:password@host.neon.tech/courtmitra | |
| APP_BASE_URL=https://your-hf-space.hf.space | |
| API_BASE_URL=https://your-hf-space.hf.space | |
| WEBHOOK_BASE_URL=https://your-hf-space.hf.space | |
| JWT_SECRET=change-me-32-chars-minimum!! | |
| PII_HASH_SALT=change-me-salt | |
| TOKEN_ENCRYPTION_KEY=change-me-32-chars-minimum!! | |
| ``` | |
| #### Required for AI (Intake, route-plan, evidence parsing) | |
| ``` | |
| AI_PROVIDER=openrouter | |
| OPENROUTER_API_KEY=sk-or-v1-... | |
| AI_MODEL=google/gemini-2.0-flash-exp:free | |
| AI_VISION_MODEL=google/gemini-2.0-flash-exp:free | |
| ``` | |
| #### Clerk (for JWT verification) | |
| ``` | |
| CLERK_SECRET_KEY=sk_test_... | |
| # Optional: for networkless JWT verification (faster, no Clerk API call per request) | |
| CLERK_JWT_KEY=... | |
| # Optional: restrict which domains can use your API | |
| CLERK_AUTHORIZED_PARTIES=https://your-project.vercel.app,http://localhost:3000 | |
| ``` | |
| #### Optional (Email actions β Resend) | |
| ``` | |
| RESEND_API_KEY=re_... | |
| RESEND_FROM=noreply@yourdomain.com | |
| ``` | |
| #### Optional (Inngest background workflows) | |
| ``` | |
| INNGEST_EVENT_KEY=... | |
| INNGEST_SIGNING_KEY=... | |
| INNGEST_BASE_URL=https://api.inngest.com | |
| ``` | |
| #### Optional (Local dev helper β NOT for production) | |
| ``` | |
| DEMO_MODE=true | |
| ``` | |
| > β οΈ **Never set `DEMO_MODE=true` in production.** It bypasses all auth. | |
| #### Optional (Object storage β for evidence files) | |
| ``` | |
| OBJECT_STORAGE_ENDPOINT=https://... | |
| OBJECT_STORAGE_BUCKET=courtmitra | |
| OBJECT_STORAGE_ACCESS_KEY=... | |
| OBJECT_STORAGE_SECRET_KEY=... | |
| ``` | |
| > If not set, evidence files are stored locally in `/app/.storage` (lost on Space restart). | |
| #### Optional (Other channels) | |
| ``` | |
| WHATSAPP_VERIFY_TOKEN=... | |
| WHATSAPP_APP_SECRET=... | |
| WHATSAPP_ACCESS_TOKEN=... | |
| WHATSAPP_PHONE_NUMBER_ID=... | |
| TELEGRAM_BOT_TOKEN=... | |
| ``` | |
| #### Optional (Phase 2 features β not needed for hackathon) | |
| ``` | |
| QDRANT_URL=https://... | |
| QDRANT_API_KEY=... | |
| QDRANT_COLLECTION=courtmitra_legal | |
| OPENAI_API_KEY=... | |
| BROWSERBASE_API_KEY=... | |
| BROWSERBASE_PROJECT_ID=... | |
| SUREPASS_API_KEY=... | |
| ENABLE_PORTAL_AUTOMATION=1 | |
| ``` | |
| ### 4d. Verify | |
| Once built, test: | |
| ```bash | |
| curl https://your-space.hf.space/api/admin/health | |
| # Expected: {"status":"ok","ts":"..."} | |
| curl https://your-space.hf.space/api/capabilities | |
| # Expected: JSON with adapter capabilities | |
| curl https://your-space.hf.space/api/inngest | |
| # Expected: Inngest registration JSON | |
| ``` | |
| --- | |
| ## Step 5: Wire Frontend β Backend | |
| 1. In Vercel dashboard, update: | |
| ``` | |
| NEXT_PUBLIC_API_BASE_URL=https://your-hf-space.hf.space/api | |
| ``` | |
| 2. Redeploy frontend. | |
| --- | |
| ## Complete Environment Variable Reference | |
| ### Vercel (Frontend) | |
| | Variable | Required | Description | | |
| |----------|----------|-------------| | |
| | `NEXT_PUBLIC_API_BASE_URL` | β | HF Spaces API URL | | |
| | `INTERNAL_API_BASE_URL` | β | Same as above (for SSR) | | |
| | `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` | β | Clerk public key | | |
| | `CLERK_SECRET_KEY` | β | Clerk secret key | | |
| | `NEXT_PUBLIC_CLERK_SIGN_IN_URL` | β | `/en/sign-in` | | |
| | `NEXT_PUBLIC_CLERK_SIGN_UP_URL` | β | `/en/sign-up` | | |
| ### Hugging Face Spaces (Backend) | |
| | Variable | Required | Description | | |
| |----------|----------|-------------| | |
| | `PORT` | β | Must be `7860` | | |
| | `API_PORT` | β | Must be `7860` | | |
| | `DATABASE_URL` | β | Neon connection string | | |
| | `DIRECT_DATABASE_URL` | β | Same as `DATABASE_URL` | | |
| | `APP_BASE_URL` | β | HF Spaces URL | | |
| | `API_BASE_URL` | β | Same | | |
| | `WEBHOOK_BASE_URL` | β | Same | | |
| | `JWT_SECRET` | β | Min 32 chars | | |
| | `PII_HASH_SALT` | β | Any string | | |
| | `TOKEN_ENCRYPTION_KEY` | β | Min 32 chars | | |
| | `AI_PROVIDER` | β | `openrouter` or `opencode` | | |
| | `OPENROUTER_API_KEY` | β | If `AI_PROVIDER=openrouter` | | |
| | `AI_MODEL` | β | e.g. `google/gemini-2.0-flash-exp:free` | | |
| | `AI_VISION_MODEL` | β | e.g. `google/gemini-2.0-flash-exp:free` | | |
| | `CLERK_SECRET_KEY` | β | Same as Vercel | | |
| | `CLERK_JWT_KEY` | β¬ | Optional, for faster JWT verification | | |
| | `RESEND_API_KEY` | β¬ | For email actions | | |
| | `RESEND_FROM` | β¬ | Sender email | | |
| | `INNGEST_EVENT_KEY` | β¬ | For background workflows | | |
| | `INNGEST_SIGNING_KEY` | β¬ | For background workflows | | |
| | `LOCAL_STORAGE_DIR` | β¬ | Defaults to `/app/.storage` | | |
| | `DEMO_MODE` | β¬ | `true` = bypass auth (dev only!) | | |
| --- | |
| ## Quick Smoke Test | |
| After deploying both services: | |
| 1. Open Vercel URL β click **Sign In** β choose **Google**. | |
| 2. After signing in, you should see your avatar in the top-right. | |
| 3. On the hero page, click **Get Started** β create a mission. | |
| 4. Visit `/{locale}/profile` β you should see: | |
| - Your Google name + email + avatar | |
| - Editable phone number | |
| - Language preference dropdown | |
| - Your mission listed under "My Missions" | |
| 5. The API calls should show `200 OK` in browser DevTools Network tab with `Authorization: Bearer <token>` header. | |
| --- | |
| ## For Your Partner: Quick Local Dev Setup | |
| **Before they can run `pnpm dev`, they MUST create two env files:** | |
| ### 1. Frontend env: `apps/web/.env.local` | |
| Create this file (it's gitignored, so it won't exist after clone): | |
| ```bash | |
| cp apps/web/.env.local.example apps/web/.env.local | |
| ``` | |
| Then paste in your Clerk keys: | |
| ``` | |
| NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_... # from Clerk dashboard | |
| CLERK_SECRET_KEY=sk_test_... # from Clerk dashboard | |
| NEXT_PUBLIC_CLERK_SIGN_IN_URL=/en/sign-in | |
| NEXT_PUBLIC_CLERK_SIGN_UP_URL=/en/sign-up | |
| NEXT_PUBLIC_API_BASE_URL=http://localhost:3001/api | |
| INTERNAL_API_BASE_URL=http://localhost:3001/api | |
| ``` | |
| ### 2. Backend env: `.env` (at repo root) | |
| ```bash | |
| cp .env.example .env | |
| ``` | |
| Fill in at minimum: | |
| ``` | |
| DATABASE_URL=postgres://courtmitra:courtmitra@localhost:5432/courtmitra | |
| DIRECT_DATABASE_URL=postgres://courtmitra:courtmitra@localhost:5432/courtmitra | |
| OPENROUTER_API_KEY=sk-or-v1-... | |
| AI_PROVIDER=openrouter | |
| AI_MODEL=google/gemini-2.0-flash-exp:free | |
| AI_VISION_MODEL=google/gemini-2.0-flash-exp:free | |
| CLERK_SECRET_KEY=sk_test_... | |
| JWT_SECRET=change-me-32-chars-minimum!! | |
| PII_HASH_SALT=change-me-salt | |
| TOKEN_ENCRYPTION_KEY=change-me-32-chars-minimum!! | |
| ``` | |
| ### 3. Run it | |
| ```bash | |
| # Terminal 1: start Postgres + Inngest | |
| pnpm infra:up | |
| # Terminal 2: migrate and seed | |
| pnpm db:migrate | |
| pnpm db:seed | |
| # Terminal 3: run all apps | |
| pnpm dev | |
| ``` | |
| - Web: http://localhost:3000 | |
| - API: http://localhost:3001/api | |
| - Inngest Dev: http://localhost:8288 | |
| **No Clerk keys yet?** Add `DEMO_MODE=true` to `.env` to bypass auth for local testing. | |
| --- | |
| ## Local Development | |
| ```bash | |
| # 1. Start local Postgres + Inngest | |
| pnpm infra:up | |
| # 2. Copy env and fill in keys | |
| cp .env.example .env | |
| # Edit .env with your keys | |
| # 3. Run everything | |
| pnpm dev | |
| ``` | |
| - Web: http://localhost:3000 | |
| - API: http://localhost:3001/api | |
| - Inngest Dev Server: http://localhost:8288 | |
| **For local dev without Clerk keys**, add to `.env`: | |
| ``` | |
| DEMO_MODE=true | |
| ``` | |
| --- | |
| ## Troubleshooting | |
| ### HF Space build fails | |
| - Check **Factory Logs** in HF dashboard. | |
| - Common: `pnpm` not found β Dockerfile installs via `corepack`. | |
| - Common: out of disk β free tier has ~50GB. Dockerfile strips `next`/`react` packages. | |
| ### API returns 401 Unauthorized | |
| - Make sure `CLERK_SECRET_KEY` is set in HF Space. | |
| - Make sure frontend sends `Authorization: Bearer <token>` header (check DevTools). | |
| - If testing with curl, you need a valid Clerk JWT token. | |
| ### API returns 500 | |
| - Check HF **Application Logs**. | |
| - Common: `DATABASE_URL` not set β can't connect to Neon. | |
| - Common: `OPENROUTER_API_KEY` not set β AI endpoints fail. | |
| - Common: `JWT_SECRET` too short β must be β₯32 characters. | |
| ### CORS errors from frontend | |
| - API has `app.enableCors({ origin: true })` which reflects the request origin. | |
| - If still blocked, add your Vercel domain explicitly in `apps/api/src/main.ts`. | |
| ### Clerk sign-in fails | |
| - Verify `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` are set in Vercel. | |
| - In Clerk Dashboard β **Social Connections** β **Google** must be enabled. | |
| - Add your Vercel domain to Clerk Dashboard β **URLs** β **Allowed Origins**. | |
| ### Background workflows not running | |
| - HF free-tier Spaces **sleep after inactivity**. When the Space sleeps, Inngest can't invoke your worker. | |
| - **Fix:** Use a paid HF Space (Always On), or use Inngest's `serve` with a keep-alive ping. | |
| - Alternatively: trigger workflows manually via API for demo purposes. | |
| ### Database connection errors | |
| - Neon databases have a **connection limit**. If you see "too many clients", restart the HF Space. | |
| - Use Neon's **connection pooling** (add `?pgbouncer=true` to connection string). | |
| --- | |
| ## Known Limitations | |
| | Feature | Status | | |
| |---------|--------| | |
| | Google OAuth + user profiles | β Works | | |
| | Mission CRUD (user-scoped) | β Works | | |
| | Evidence upload | β Works (if storage configured) | | |
| | Background AI workflows | β οΈ Needs Inngest + always-on Space | | |
| | Email sending | β οΈ Needs Resend key | | |
| | WhatsApp/Telegram | β Needs Meta Business verification (days) | | |
| | Portal automation | β Phase 2 only | | |
| --- | |
| ## Security Checklist | |
| Before going live: | |
| - [ ] `DEMO_MODE` is NOT set (or set to `false`) | |
| - [ ] `JWT_SECRET` is β₯32 random characters | |
| - [ ] `TOKEN_ENCRYPTION_KEY` is β₯32 random characters | |
| - [ ] `CLERK_JWT_KEY` is set (optional but recommended for performance) | |
| - [ ] Database uses SSL (Neon does this by default) | |
| - [ ] HF Space is set to **private** if you don't want public API access | |
| - [ ] Vercel domain is in Clerk's **Allowed Origins** | |
| --- | |
| *Good luck at the hackathon! π* | |