File size: 15,394 Bytes
f69e867
 
 
 
 
 
f89cf0c
f69e867
f89cf0c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f69e867
 
 
d2d8e95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f69e867
 
f89cf0c
 
 
 
 
 
 
 
f69e867
 
 
 
 
 
f89cf0c
 
 
 
 
 
 
 
 
f69e867
 
 
 
 
f89cf0c
 
f69e867
f89cf0c
 
f69e867
 
 
f89cf0c
f69e867
 
 
 
 
f89cf0c
 
 
f69e867
f89cf0c
 
 
 
 
 
 
 
f69e867
f89cf0c
f69e867
f89cf0c
f69e867
f89cf0c
f69e867
f89cf0c
 
f69e867
 
f89cf0c
f69e867
 
f89cf0c
 
f69e867
f89cf0c
f69e867
 
 
f89cf0c
f69e867
f89cf0c
f69e867
 
 
f89cf0c
 
 
 
 
 
 
 
 
 
 
 
f69e867
 
 
 
f89cf0c
 
 
 
f69e867
f89cf0c
 
 
 
f69e867
 
f89cf0c
f69e867
 
 
 
 
f89cf0c
f69e867
f89cf0c
 
f69e867
 
 
f89cf0c
f69e867
 
 
f89cf0c
f69e867
f89cf0c
f69e867
f89cf0c
 
 
 
f69e867
f89cf0c
f69e867
f89cf0c
 
 
 
 
 
 
 
f69e867
f89cf0c
f69e867
f89cf0c
 
 
 
 
 
 
 
f69e867
 
f89cf0c
f69e867
f89cf0c
 
 
 
f69e867
f89cf0c
 
f69e867
f89cf0c
 
 
f69e867
 
 
f89cf0c
f69e867
f89cf0c
 
 
 
 
f69e867
 
 
f89cf0c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f69e867
 
 
f89cf0c
f69e867
f89cf0c
f69e867
f89cf0c
 
 
 
 
 
 
 
 
f69e867
 
 
d2d8e95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f89cf0c
f69e867
 
 
 
 
 
d2d8e95
f89cf0c
f69e867
 
 
 
 
 
 
 
 
f89cf0c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f69e867
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# 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! πŸš€*