Spaces:
Sleeping
Sleeping
Hetansh Waghela commited on
Commit Β·
f89cf0c
1
Parent(s): 76b46a3
fix: Add /profile to protected routes, comprehensive DEPLOY.md
Browse files- Add /:locale/profile to clerkMiddleware protected routes
- Update DEPLOY.md with complete env var reference for Vercel + HF Spaces
- Add architecture diagram, smoke test steps, troubleshooting section
- Security checklist for production deployment
- DEPLOY.md +250 -171
- apps/web/middleware.ts +1 -0
DEPLOY.md
CHANGED
|
@@ -4,264 +4,274 @@
|
|
| 4 |
|
| 5 |
---
|
| 6 |
|
| 7 |
-
##
|
| 8 |
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
---
|
| 19 |
|
| 20 |
## Prerequisites
|
| 21 |
|
| 22 |
-
|
| 23 |
-
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
|
| 31 |
---
|
| 32 |
|
| 33 |
## Step 1: Clerk + Google OAuth (10 min)
|
| 34 |
|
| 35 |
1. Go to [dashboard.clerk.com](https://dashboard.clerk.com) β Create Application.
|
| 36 |
-
2.
|
| 37 |
-
-
|
| 38 |
-
-
|
| 39 |
-
3. Copy
|
| 40 |
-
|
| 41 |
-
``
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/en/sign-up
|
| 46 |
-
```
|
| 47 |
-
5. That's it β the frontend has Clerk wired with `SignInButton`, `UserButton`, and `<Show when="signed-in">` components.
|
| 48 |
-
6. **Backend Clerk verification:** The API verifies Clerk JWTs from the `Authorization: Bearer <token>` header. On first API call, it auto-creates a user in the DB and links their Google identity.
|
| 49 |
|
| 50 |
---
|
| 51 |
|
| 52 |
## Step 2: Neon Postgres (10 min)
|
| 53 |
|
| 54 |
-
1.
|
| 55 |
-
2.
|
| 56 |
-
3. **Run migrations** against the Neon DB:
|
| 57 |
```bash
|
| 58 |
-
|
| 59 |
-
export
|
| 60 |
-
export DIRECT_DATABASE_URL="postgres://..."
|
| 61 |
pnpm db:migrate
|
| 62 |
pnpm db:seed
|
| 63 |
```
|
|
|
|
| 64 |
|
| 65 |
---
|
| 66 |
|
| 67 |
## Step 3: Frontend β Vercel (5 min)
|
| 68 |
|
| 69 |
-
1. Push
|
| 70 |
-
2.
|
| 71 |
-
3. Set
|
| 72 |
-
```
|
| 73 |
-
NEXT_PUBLIC_API_BASE_URL=https://your-hf-space.hf.space/api
|
| 74 |
-
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
|
| 75 |
-
CLERK_SECRET_KEY=sk_test_...
|
| 76 |
-
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/en/sign-in
|
| 77 |
-
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/en/sign-up
|
| 78 |
-
```
|
| 79 |
-
4. Deploy. Vercel will auto-detect Next.js in `apps/web`.
|
| 80 |
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
-
|
| 84 |
|
| 85 |
-
|
| 86 |
|
| 87 |
-
##
|
| 88 |
|
| 89 |
-
|
|
|
|
| 90 |
2. **Space name:** `courtmitra-api`
|
| 91 |
3. **SDK:** `Docker`
|
| 92 |
-
4. **Visibility:** Public
|
| 93 |
5. **Hardware:** Free CPU
|
| 94 |
|
| 95 |
-
### 4b.
|
| 96 |
-
|
| 97 |
-
The repo root has `Dockerfile.hf` which builds the monorepo and runs the NestJS API + Inngest Worker in ONE container.
|
| 98 |
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
```bash
|
| 102 |
-
git add Dockerfile.hf
|
| 103 |
-
git commit -m "Add HF Spaces Dockerfile (API + Worker combined)"
|
| 104 |
-
git push origin main
|
| 105 |
-
```
|
| 106 |
-
|
| 107 |
-
Then in your HF Space β **Settings** β **Repository** β link your GitHub repo.
|
| 108 |
|
| 109 |
### 4c. Set Environment Variables
|
| 110 |
|
| 111 |
-
In
|
| 112 |
|
|
|
|
| 113 |
```
|
| 114 |
PORT=7860
|
| 115 |
API_PORT=7860
|
| 116 |
-
DATABASE_URL=postgres://..
|
| 117 |
-
DIRECT_DATABASE_URL=postgres://..
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
AI_PROVIDER=openrouter
|
| 119 |
OPENROUTER_API_KEY=sk-or-v1-...
|
| 120 |
AI_MODEL=google/gemini-2.0-flash-exp:free
|
| 121 |
AI_VISION_MODEL=google/gemini-2.0-flash-exp:free
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
JWT_SECRET=hackathon-jwt-secret-change-me
|
| 127 |
-
PII_HASH_SALT=hackathon-salt
|
| 128 |
-
TOKEN_ENCRYPTION_KEY=hackathon-enc-key-min-32-chars!!
|
| 129 |
CLERK_SECRET_KEY=sk_test_...
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
| 131 |
```
|
| 132 |
|
| 133 |
-
Optional (
|
| 134 |
```
|
| 135 |
RESEND_API_KEY=re_...
|
| 136 |
RESEND_FROM=noreply@yourdomain.com
|
| 137 |
```
|
| 138 |
|
| 139 |
-
Optional (
|
| 140 |
```
|
| 141 |
-
INNGEST_EVENT_KEY=...
|
| 142 |
-
INNGEST_SIGNING_KEY=...
|
| 143 |
INNGEST_BASE_URL=https://api.inngest.com
|
| 144 |
```
|
| 145 |
|
| 146 |
-
Optional (
|
| 147 |
```
|
| 148 |
DEMO_MODE=true
|
| 149 |
```
|
|
|
|
| 150 |
|
| 151 |
-
###
|
| 152 |
-
|
| 153 |
-
Once the Space builds, visit:
|
| 154 |
-
- `https://your-space.hf.space/api/admin/health` β should return `{"status":"ok"}`
|
| 155 |
-
- `https://your-space.hf.space/api/inngest` β should return the Inngest registration JSON
|
| 156 |
-
|
| 157 |
-
---
|
| 158 |
-
|
| 159 |
-
## Step 5: Wire Frontend β Backend
|
| 160 |
-
|
| 161 |
-
Update your Vercel environment variable:
|
| 162 |
```
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
| 164 |
```
|
| 165 |
-
|
| 166 |
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
|
|
|
|
| 171 |
```
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
β β β - missions β β - intake β β β
|
| 181 |
-
β β β - actions β β - evidence β β β
|
| 182 |
-
β β β - users β β - route-plan β β β
|
| 183 |
-
β β β - evidence β β - execute β β β
|
| 184 |
-
β β βββββββββββββββ ββββββββββββββββββ β β
|
| 185 |
-
β βββββββββββββββββββββββββββββββββββββββββββ β
|
| 186 |
-
β β β
|
| 187 |
-
β Neon Postgres (Cloud) β
|
| 188 |
-
βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 189 |
```
|
| 190 |
|
| 191 |
-
|
| 192 |
|
| 193 |
-
|
|
|
|
|
|
|
|
|
|
| 194 |
|
| 195 |
-
|
|
|
|
| 196 |
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
4. The API call should reach HF Spaces, create a user in the DB, and create a mission.
|
| 201 |
-
5. Visit `/{locale}/profile` β you should see your profile and the mission you just created.
|
| 202 |
-
6. Check `https://your-hf-space.hf.space/api/admin/health` to confirm backend is alive.
|
| 203 |
|
| 204 |
---
|
| 205 |
|
| 206 |
-
##
|
| 207 |
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
| My Missions list on profile | β
Lists user's own missions |
|
| 214 |
-
| Mission ownership | β
Users only see their own missions |
|
| 215 |
-
| Combined API + Worker container | β
One HF Space serves both |
|
| 216 |
-
| Demo mode | β
`DEMO_MODE=true` bypasses auth for local dev |
|
| 217 |
|
| 218 |
---
|
| 219 |
|
| 220 |
-
##
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
|
| 225 |
-
|
|
| 226 |
-
|
|
| 227 |
-
|
|
| 228 |
-
|
|
| 229 |
-
|
|
| 230 |
-
|
|
| 231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
|
| 233 |
---
|
| 234 |
|
| 235 |
-
##
|
| 236 |
-
|
| 237 |
-
**HF Space build fails:**
|
| 238 |
-
- Check **Factory Logs** in HF. If `pnpm` is missing, the Dockerfile installs it via `corepack`.
|
| 239 |
-
- If out of disk: HF free tier has ~50GB disk. The Dockerfile removes frontend packages (`next`, `react`) from the API deploy to save space.
|
| 240 |
-
|
| 241 |
-
**API returns 401 Unauthorized:**
|
| 242 |
-
- Make sure `CLERK_SECRET_KEY` is set in HF Space env vars.
|
| 243 |
-
- Make sure the frontend passes `Authorization: Bearer <token>` header. Check browser DevTools Network tab.
|
| 244 |
-
|
| 245 |
-
**API returns 500:**
|
| 246 |
-
- Verify `DATABASE_URL` is set and migrations ran.
|
| 247 |
-
- Verify `OPENROUTER_API_KEY` is set (some endpoints call LLMs).
|
| 248 |
|
| 249 |
-
|
| 250 |
-
- The API has `app.enableCors({ origin: true })` which reflects the request origin. If blocked, add your Vercel domain to `origin: ["https://your-vercel.app"]` in `apps/api/src/main.ts`.
|
| 251 |
|
| 252 |
-
**
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
-
|
| 259 |
-
-
|
| 260 |
-
|
| 261 |
|
| 262 |
---
|
| 263 |
|
| 264 |
-
## Local
|
| 265 |
|
| 266 |
```bash
|
| 267 |
# 1. Start local Postgres + Inngest
|
|
@@ -269,6 +279,7 @@ pnpm infra:up
|
|
| 269 |
|
| 270 |
# 2. Copy env and fill in keys
|
| 271 |
cp infra/.env.example .env
|
|
|
|
| 272 |
|
| 273 |
# 3. Run everything
|
| 274 |
pnpm dev
|
|
@@ -278,7 +289,75 @@ pnpm dev
|
|
| 278 |
- API: http://localhost:3001/api
|
| 279 |
- Inngest Dev Server: http://localhost:8288
|
| 280 |
|
| 281 |
-
**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
|
| 283 |
---
|
| 284 |
|
|
|
|
| 4 |
|
| 5 |
---
|
| 6 |
|
| 7 |
+
## Architecture Overview
|
| 8 |
|
| 9 |
+
```
|
| 10 |
+
βββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββββββ
|
| 11 |
+
β Vercel (Next.js 15) ββββββββββΆβ Hugging Face Spaces (Docker) β
|
| 12 |
+
β - Clerk auth β HTTPS β - NestJS API + Inngest Worker β
|
| 13 |
+
β - Hero page β β - Port 7860 β
|
| 14 |
+
β - Profile page β β β
|
| 15 |
+
β - Mission dashboard β β /api/* β API routes β
|
| 16 |
+
β - Static assets β β /api/inngest β Worker functions β
|
| 17 |
+
βββββββββββββββββββββββββββ ββββββββββββββββββββββ¬ββββββββββββββββββββββ
|
| 18 |
+
β
|
| 19 |
+
β postgres://...
|
| 20 |
+
βΌ
|
| 21 |
+
ββββββββββββββββββββββββββββββ
|
| 22 |
+
β Neon Postgres (Cloud) β
|
| 23 |
+
β - 55 tables β
|
| 24 |
+
β - pgvector + FTS β
|
| 25 |
+
β - Legal chunks seeded β
|
| 26 |
+
ββββββββββββββββββββββββββββββ
|
| 27 |
+
```
|
| 28 |
|
| 29 |
---
|
| 30 |
|
| 31 |
## Prerequisites
|
| 32 |
|
| 33 |
+
| Service | Purpose | Free Tier |
|
| 34 |
+
|---------|---------|-----------|
|
| 35 |
+
| [Vercel](https://vercel.com) | Frontend hosting | Hobby (unlimited) |
|
| 36 |
+
| [Clerk](https://clerk.com) | Auth + Google OAuth | 10,000 MAU |
|
| 37 |
+
| [Hugging Face Spaces](https://huggingface.co/spaces) | Backend API + Worker | CPU Basic (free) |
|
| 38 |
+
| [Neon](https://neon.tech) | Postgres database | 500 MB, 200 hrs |
|
| 39 |
+
| [Inngest](https://inngest.com) | Background workflows | 50k events/mo |
|
| 40 |
+
| [OpenRouter](https://openrouter.ai) | LLM API | $5 free credits |
|
| 41 |
|
| 42 |
---
|
| 43 |
|
| 44 |
## Step 1: Clerk + Google OAuth (10 min)
|
| 45 |
|
| 46 |
1. Go to [dashboard.clerk.com](https://dashboard.clerk.com) β Create Application.
|
| 47 |
+
2. **Social Connections** β enable **Google**.
|
| 48 |
+
- Development: toggle on (uses Clerk's shared OAuth credentials).
|
| 49 |
+
- Production: add your own Google Client ID/Secret.
|
| 50 |
+
3. Copy keys from **API Keys** tab:
|
| 51 |
+
- `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...`
|
| 52 |
+
- `CLERK_SECRET_KEY=sk_test_...`
|
| 53 |
+
4. Add your Vercel domain to **URLs** β **Allowed Origins**:
|
| 54 |
+
- `https://your-project.vercel.app`
|
| 55 |
+
- `http://localhost:3000` (for local dev)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
---
|
| 58 |
|
| 59 |
## Step 2: Neon Postgres (10 min)
|
| 60 |
|
| 61 |
+
1. [neon.tech](https://neon.tech) β New Project β copy **connection string**.
|
| 62 |
+
2. Run migrations from your local machine:
|
|
|
|
| 63 |
```bash
|
| 64 |
+
export DATABASE_URL="postgres://courtmitra:password@host.neon.tech/courtmitra"
|
| 65 |
+
export DIRECT_DATABASE_URL="$DATABASE_URL"
|
|
|
|
| 66 |
pnpm db:migrate
|
| 67 |
pnpm db:seed
|
| 68 |
```
|
| 69 |
+
3. Verify: check that `users`, `missions`, `route_rules`, `legal_chunks` tables exist.
|
| 70 |
|
| 71 |
---
|
| 72 |
|
| 73 |
## Step 3: Frontend β Vercel (5 min)
|
| 74 |
|
| 75 |
+
1. Push repo to GitHub.
|
| 76 |
+
2. Vercel β **Add New Project** β import repo.
|
| 77 |
+
3. Set **Environment Variables**:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
+
| Variable | Value | Notes |
|
| 80 |
+
|----------|-------|-------|
|
| 81 |
+
| `NEXT_PUBLIC_API_BASE_URL` | `https://your-hf-space.hf.space/api` | Your HF Spaces URL |
|
| 82 |
+
| `INTERNAL_API_BASE_URL` | `https://your-hf-space.hf.space/api` | Same as above |
|
| 83 |
+
| `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` | `pk_test_...` | From Clerk dashboard |
|
| 84 |
+
| `CLERK_SECRET_KEY` | `sk_test_...` | From Clerk dashboard |
|
| 85 |
+
| `NEXT_PUBLIC_CLERK_SIGN_IN_URL` | `/en/sign-in` | Or `/hi/sign-in` |
|
| 86 |
+
| `NEXT_PUBLIC_CLERK_SIGN_UP_URL` | `/en/sign-up` | Or `/hi/sign-up` |
|
| 87 |
|
| 88 |
+
4. Deploy.
|
| 89 |
|
| 90 |
+
---
|
| 91 |
|
| 92 |
+
## Step 4: Backend β Hugging Face Spaces (20 min)
|
| 93 |
|
| 94 |
+
### 4a. Create Space
|
| 95 |
+
1. [huggingface.co/spaces](https://huggingface.co/spaces) β **Create new Space**.
|
| 96 |
2. **Space name:** `courtmitra-api`
|
| 97 |
3. **SDK:** `Docker`
|
| 98 |
+
4. **Visibility:** Public
|
| 99 |
5. **Hardware:** Free CPU
|
| 100 |
|
| 101 |
+
### 4b. Link GitHub Repo
|
| 102 |
+
In Space Settings β **Repository** β link your GitHub repo.
|
|
|
|
| 103 |
|
| 104 |
+
The `Dockerfile.hf` at repo root builds the monorepo and runs API + Worker in one container.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
### 4c. Set Environment Variables
|
| 107 |
|
| 108 |
+
In Space β **Settings** β **Variables & Secrets**:
|
| 109 |
|
| 110 |
+
#### Required (App won't start without these)
|
| 111 |
```
|
| 112 |
PORT=7860
|
| 113 |
API_PORT=7860
|
| 114 |
+
DATABASE_URL=postgres://courtmitra:password@host.neon.tech/courtmitra
|
| 115 |
+
DIRECT_DATABASE_URL=postgres://courtmitra:password@host.neon.tech/courtmitra
|
| 116 |
+
APP_BASE_URL=https://your-hf-space.hf.space
|
| 117 |
+
API_BASE_URL=https://your-hf-space.hf.space
|
| 118 |
+
WEBHOOK_BASE_URL=https://your-hf-space.hf.space
|
| 119 |
+
JWT_SECRET=change-me-32-chars-minimum!!
|
| 120 |
+
PII_HASH_SALT=change-me-salt
|
| 121 |
+
TOKEN_ENCRYPTION_KEY=change-me-32-chars-minimum!!
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
#### Required for AI (Intake, route-plan, evidence parsing)
|
| 125 |
+
```
|
| 126 |
AI_PROVIDER=openrouter
|
| 127 |
OPENROUTER_API_KEY=sk-or-v1-...
|
| 128 |
AI_MODEL=google/gemini-2.0-flash-exp:free
|
| 129 |
AI_VISION_MODEL=google/gemini-2.0-flash-exp:free
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
#### Clerk (for JWT verification)
|
| 133 |
+
```
|
|
|
|
|
|
|
|
|
|
| 134 |
CLERK_SECRET_KEY=sk_test_...
|
| 135 |
+
# Optional: for networkless JWT verification (faster, no Clerk API call per request)
|
| 136 |
+
CLERK_JWT_KEY=...
|
| 137 |
+
# Optional: restrict which domains can use your API
|
| 138 |
+
CLERK_AUTHORIZED_PARTIES=https://your-project.vercel.app,http://localhost:3000
|
| 139 |
```
|
| 140 |
|
| 141 |
+
#### Optional (Email actions β Resend)
|
| 142 |
```
|
| 143 |
RESEND_API_KEY=re_...
|
| 144 |
RESEND_FROM=noreply@yourdomain.com
|
| 145 |
```
|
| 146 |
|
| 147 |
+
#### Optional (Inngest background workflows)
|
| 148 |
```
|
| 149 |
+
INNGEST_EVENT_KEY=...
|
| 150 |
+
INNGEST_SIGNING_KEY=...
|
| 151 |
INNGEST_BASE_URL=https://api.inngest.com
|
| 152 |
```
|
| 153 |
|
| 154 |
+
#### Optional (Local dev helper β NOT for production)
|
| 155 |
```
|
| 156 |
DEMO_MODE=true
|
| 157 |
```
|
| 158 |
+
> β οΈ **Never set `DEMO_MODE=true` in production.** It bypasses all auth.
|
| 159 |
|
| 160 |
+
#### Optional (Object storage β for evidence files)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
```
|
| 162 |
+
OBJECT_STORAGE_ENDPOINT=https://...
|
| 163 |
+
OBJECT_STORAGE_BUCKET=courtmitra
|
| 164 |
+
OBJECT_STORAGE_ACCESS_KEY=...
|
| 165 |
+
OBJECT_STORAGE_SECRET_KEY=...
|
| 166 |
```
|
| 167 |
+
> If not set, evidence files are stored locally in `/app/.storage` (lost on Space restart).
|
| 168 |
|
| 169 |
+
#### Optional (Other channels)
|
| 170 |
+
```
|
| 171 |
+
WHATSAPP_VERIFY_TOKEN=...
|
| 172 |
+
WHATSAPP_APP_SECRET=...
|
| 173 |
+
WHATSAPP_ACCESS_TOKEN=...
|
| 174 |
+
WHATSAPP_PHONE_NUMBER_ID=...
|
| 175 |
+
TELEGRAM_BOT_TOKEN=...
|
| 176 |
+
```
|
| 177 |
|
| 178 |
+
#### Optional (Phase 2 features β not needed for hackathon)
|
| 179 |
```
|
| 180 |
+
QDRANT_URL=https://...
|
| 181 |
+
QDRANT_API_KEY=...
|
| 182 |
+
QDRANT_COLLECTION=courtmitra_legal
|
| 183 |
+
OPENAI_API_KEY=...
|
| 184 |
+
BROWSERBASE_API_KEY=...
|
| 185 |
+
BROWSERBASE_PROJECT_ID=...
|
| 186 |
+
SUREPASS_API_KEY=...
|
| 187 |
+
ENABLE_PORTAL_AUTOMATION=1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
```
|
| 189 |
|
| 190 |
+
### 4d. Verify
|
| 191 |
|
| 192 |
+
Once built, test:
|
| 193 |
+
```bash
|
| 194 |
+
curl https://your-space.hf.space/api/admin/health
|
| 195 |
+
# Expected: {"status":"ok","ts":"..."}
|
| 196 |
|
| 197 |
+
curl https://your-space.hf.space/api/capabilities
|
| 198 |
+
# Expected: JSON with adapter capabilities
|
| 199 |
|
| 200 |
+
curl https://your-space.hf.space/api/inngest
|
| 201 |
+
# Expected: Inngest registration JSON
|
| 202 |
+
```
|
|
|
|
|
|
|
|
|
|
| 203 |
|
| 204 |
---
|
| 205 |
|
| 206 |
+
## Step 5: Wire Frontend β Backend
|
| 207 |
|
| 208 |
+
1. In Vercel dashboard, update:
|
| 209 |
+
```
|
| 210 |
+
NEXT_PUBLIC_API_BASE_URL=https://your-hf-space.hf.space/api
|
| 211 |
+
```
|
| 212 |
+
2. Redeploy frontend.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
|
| 214 |
---
|
| 215 |
|
| 216 |
+
## Complete Environment Variable Reference
|
| 217 |
+
|
| 218 |
+
### Vercel (Frontend)
|
| 219 |
+
|
| 220 |
+
| Variable | Required | Description |
|
| 221 |
+
|----------|----------|-------------|
|
| 222 |
+
| `NEXT_PUBLIC_API_BASE_URL` | β
| HF Spaces API URL |
|
| 223 |
+
| `INTERNAL_API_BASE_URL` | β
| Same as above (for SSR) |
|
| 224 |
+
| `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` | β
| Clerk public key |
|
| 225 |
+
| `CLERK_SECRET_KEY` | β
| Clerk secret key |
|
| 226 |
+
| `NEXT_PUBLIC_CLERK_SIGN_IN_URL` | β
| `/en/sign-in` |
|
| 227 |
+
| `NEXT_PUBLIC_CLERK_SIGN_UP_URL` | β
| `/en/sign-up` |
|
| 228 |
+
|
| 229 |
+
### Hugging Face Spaces (Backend)
|
| 230 |
+
|
| 231 |
+
| Variable | Required | Description |
|
| 232 |
+
|----------|----------|-------------|
|
| 233 |
+
| `PORT` | β
| Must be `7860` |
|
| 234 |
+
| `API_PORT` | β
| Must be `7860` |
|
| 235 |
+
| `DATABASE_URL` | β
| Neon connection string |
|
| 236 |
+
| `DIRECT_DATABASE_URL` | β
| Same as `DATABASE_URL` |
|
| 237 |
+
| `APP_BASE_URL` | β
| HF Spaces URL |
|
| 238 |
+
| `API_BASE_URL` | β
| Same |
|
| 239 |
+
| `WEBHOOK_BASE_URL` | β
| Same |
|
| 240 |
+
| `JWT_SECRET` | β
| Min 32 chars |
|
| 241 |
+
| `PII_HASH_SALT` | β
| Any string |
|
| 242 |
+
| `TOKEN_ENCRYPTION_KEY` | β
| Min 32 chars |
|
| 243 |
+
| `AI_PROVIDER` | β
| `openrouter` or `opencode` |
|
| 244 |
+
| `OPENROUTER_API_KEY` | β
| If `AI_PROVIDER=openrouter` |
|
| 245 |
+
| `AI_MODEL` | β
| e.g. `google/gemini-2.0-flash-exp:free` |
|
| 246 |
+
| `AI_VISION_MODEL` | β
| e.g. `google/gemini-2.0-flash-exp:free` |
|
| 247 |
+
| `CLERK_SECRET_KEY` | β
| Same as Vercel |
|
| 248 |
+
| `CLERK_JWT_KEY` | β¬ | Optional, for faster JWT verification |
|
| 249 |
+
| `RESEND_API_KEY` | β¬ | For email actions |
|
| 250 |
+
| `RESEND_FROM` | β¬ | Sender email |
|
| 251 |
+
| `INNGEST_EVENT_KEY` | β¬ | For background workflows |
|
| 252 |
+
| `INNGEST_SIGNING_KEY` | β¬ | For background workflows |
|
| 253 |
+
| `LOCAL_STORAGE_DIR` | β¬ | Defaults to `/app/.storage` |
|
| 254 |
+
| `DEMO_MODE` | β¬ | `true` = bypass auth (dev only!) |
|
| 255 |
|
| 256 |
---
|
| 257 |
|
| 258 |
+
## Quick Smoke Test
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
|
| 260 |
+
After deploying both services:
|
|
|
|
| 261 |
|
| 262 |
+
1. Open Vercel URL β click **Sign In** β choose **Google**.
|
| 263 |
+
2. After signing in, you should see your avatar in the top-right.
|
| 264 |
+
3. On the hero page, click **Get Started** β create a mission.
|
| 265 |
+
4. Visit `/{locale}/profile` β you should see:
|
| 266 |
+
- Your Google name + email + avatar
|
| 267 |
+
- Editable phone number
|
| 268 |
+
- Language preference dropdown
|
| 269 |
+
- Your mission listed under "My Missions"
|
| 270 |
+
5. The API calls should show `200 OK` in browser DevTools Network tab with `Authorization: Bearer <token>` header.
|
| 271 |
|
| 272 |
---
|
| 273 |
|
| 274 |
+
## Local Development
|
| 275 |
|
| 276 |
```bash
|
| 277 |
# 1. Start local Postgres + Inngest
|
|
|
|
| 279 |
|
| 280 |
# 2. Copy env and fill in keys
|
| 281 |
cp infra/.env.example .env
|
| 282 |
+
# Edit .env with your keys
|
| 283 |
|
| 284 |
# 3. Run everything
|
| 285 |
pnpm dev
|
|
|
|
| 289 |
- API: http://localhost:3001/api
|
| 290 |
- Inngest Dev Server: http://localhost:8288
|
| 291 |
|
| 292 |
+
**For local dev without Clerk keys**, add to `.env`:
|
| 293 |
+
```
|
| 294 |
+
DEMO_MODE=true
|
| 295 |
+
```
|
| 296 |
+
|
| 297 |
+
---
|
| 298 |
+
|
| 299 |
+
## Troubleshooting
|
| 300 |
+
|
| 301 |
+
### HF Space build fails
|
| 302 |
+
- Check **Factory Logs** in HF dashboard.
|
| 303 |
+
- Common: `pnpm` not found β Dockerfile installs via `corepack`.
|
| 304 |
+
- Common: out of disk β free tier has ~50GB. Dockerfile strips `next`/`react` packages.
|
| 305 |
+
|
| 306 |
+
### API returns 401 Unauthorized
|
| 307 |
+
- Make sure `CLERK_SECRET_KEY` is set in HF Space.
|
| 308 |
+
- Make sure frontend sends `Authorization: Bearer <token>` header (check DevTools).
|
| 309 |
+
- If testing with curl, you need a valid Clerk JWT token.
|
| 310 |
+
|
| 311 |
+
### API returns 500
|
| 312 |
+
- Check HF **Application Logs**.
|
| 313 |
+
- Common: `DATABASE_URL` not set β can't connect to Neon.
|
| 314 |
+
- Common: `OPENROUTER_API_KEY` not set β AI endpoints fail.
|
| 315 |
+
- Common: `JWT_SECRET` too short β must be β₯32 characters.
|
| 316 |
+
|
| 317 |
+
### CORS errors from frontend
|
| 318 |
+
- API has `app.enableCors({ origin: true })` which reflects the request origin.
|
| 319 |
+
- If still blocked, add your Vercel domain explicitly in `apps/api/src/main.ts`.
|
| 320 |
+
|
| 321 |
+
### Clerk sign-in fails
|
| 322 |
+
- Verify `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` are set in Vercel.
|
| 323 |
+
- In Clerk Dashboard β **Social Connections** β **Google** must be enabled.
|
| 324 |
+
- Add your Vercel domain to Clerk Dashboard β **URLs** β **Allowed Origins**.
|
| 325 |
+
|
| 326 |
+
### Background workflows not running
|
| 327 |
+
- HF free-tier Spaces **sleep after inactivity**. When the Space sleeps, Inngest can't invoke your worker.
|
| 328 |
+
- **Fix:** Use a paid HF Space (Always On), or use Inngest's `serve` with a keep-alive ping.
|
| 329 |
+
- Alternatively: trigger workflows manually via API for demo purposes.
|
| 330 |
+
|
| 331 |
+
### Database connection errors
|
| 332 |
+
- Neon databases have a **connection limit**. If you see "too many clients", restart the HF Space.
|
| 333 |
+
- Use Neon's **connection pooling** (add `?pgbouncer=true` to connection string).
|
| 334 |
+
|
| 335 |
+
---
|
| 336 |
+
|
| 337 |
+
## Known Limitations
|
| 338 |
+
|
| 339 |
+
| Feature | Status |
|
| 340 |
+
|---------|--------|
|
| 341 |
+
| Google OAuth + user profiles | β
Works |
|
| 342 |
+
| Mission CRUD (user-scoped) | β
Works |
|
| 343 |
+
| Evidence upload | β
Works (if storage configured) |
|
| 344 |
+
| Background AI workflows | β οΈ Needs Inngest + always-on Space |
|
| 345 |
+
| Email sending | β οΈ Needs Resend key |
|
| 346 |
+
| WhatsApp/Telegram | β Needs Meta Business verification (days) |
|
| 347 |
+
| Portal automation | β Phase 2 only |
|
| 348 |
+
|
| 349 |
+
---
|
| 350 |
+
|
| 351 |
+
## Security Checklist
|
| 352 |
+
|
| 353 |
+
Before going live:
|
| 354 |
+
- [ ] `DEMO_MODE` is NOT set (or set to `false`)
|
| 355 |
+
- [ ] `JWT_SECRET` is β₯32 random characters
|
| 356 |
+
- [ ] `TOKEN_ENCRYPTION_KEY` is β₯32 random characters
|
| 357 |
+
- [ ] `CLERK_JWT_KEY` is set (optional but recommended for performance)
|
| 358 |
+
- [ ] Database uses SSL (Neon does this by default)
|
| 359 |
+
- [ ] HF Space is set to **private** if you don't want public API access
|
| 360 |
+
- [ ] Vercel domain is in Clerk's **Allowed Origins**
|
| 361 |
|
| 362 |
---
|
| 363 |
|
apps/web/middleware.ts
CHANGED
|
@@ -7,6 +7,7 @@ const intlMiddleware = createMiddleware(routing);
|
|
| 7 |
const isProtectedRoute = createRouteMatcher([
|
| 8 |
"/:locale/ledger",
|
| 9 |
"/:locale/missions(.*)",
|
|
|
|
| 10 |
]);
|
| 11 |
|
| 12 |
export default clerkMiddleware(async (auth, req) => {
|
|
|
|
| 7 |
const isProtectedRoute = createRouteMatcher([
|
| 8 |
"/:locale/ledger",
|
| 9 |
"/:locale/missions(.*)",
|
| 10 |
+
"/:locale/profile",
|
| 11 |
]);
|
| 12 |
|
| 13 |
export default clerkMiddleware(async (auth, req) => {
|