Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ead4e3b
Implement multi-cohort support
crthpl Mar 3, 2026
47953f9
Merge main into multi-cohort
crthpl Mar 3, 2026
7ec644d
Add per-member initial balance, cohort detail page, and python client…
crthpl Mar 5, 2026
94edddf
Copy global_migrations into Docker image
crthpl Mar 5, 2026
4043571
Add SQLX_OFFLINE and .dockerignore for Docker builds
crthpl Mar 5, 2026
1fee06a
Fix docs import path for cohort_name nesting, add .vercelignore
crthpl Mar 5, 2026
ea2048a
Fix cohort creation atomicity, add "use existing DB" option
crthpl Mar 5, 2026
4d2c155
Fix cohort DB path: don't strip leading slash from absolute path
crthpl Mar 5, 2026
18ec287
Add available databases list to admin cohort creation UI
crthpl Mar 5, 2026
efb52c6
Make read-only toggle take effect immediately via AtomicBool
crthpl Mar 5, 2026
e644b8a
Fix CORS preflight: use explicit CorsLayer config instead of permissi…
crthpl Mar 5, 2026
850b14d
Add email to global users, show in admin UI
crthpl Mar 5, 2026
30b608f
Admin UI improvements and account color coding
crthpl Mar 5, 2026
797654d
Use background color instead of dot for account colors in act-as drop…
crthpl Mar 5, 2026
00135db
Move vercel.json to repo root so rewrites apply (fixes 404 on reload)
crthpl Mar 5, 2026
6d7550d
Fix apiBase.ts to handle relative URLs in dev mode (/api)
crthpl Mar 5, 2026
8a3ff6e
Remove Airtable sync and harden cohort/admin access
crthpl Mar 6, 2026
87a1771
Handle missing data dir gracefully in list_available_dbs; add DEPLOY.md
crthpl Apr 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
backend/target
backend/data
backend/*.sqlite*
backend/remote-dbs
backend/uploads
frontend/node_modules
frontend/.svelte-kit
frontend/build
node_modules
.git
.claude
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ node_modules
backend/uploads
.dev-ports
.playwright-cli
.claude/worktrees/
.vercel
7 changes: 7 additions & 0 deletions .vercelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
backend/target
backend/data
backend/*.sqlite*
backend/remote-dbs
.git
.claude
node_modules
9 changes: 2 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ Copy the appropriate template to `frontend/.env` for your use case:

#### `backend/src/main.rs`
- **Entry point** for the backend server
- Initializes Axum router with WebSocket handler at `/api`, image upload/serving routes, and Airtable sync endpoint
- Initializes Axum router with per-cohort WebSocket handler at `/api/ws/:cohort_name`, admin/user REST endpoints, and image upload/serving routes
- Implements port binding with fallback logic (tries sequential ports if in use)
- Manages uploads directory and request body size limits
- Depends on `lib.rs` for `AppState`, `handle_socket.rs` for WebSocket handling
Expand All @@ -266,7 +266,7 @@ Copy the appropriate template to `frontend/.env` for your use case:
- Defines `AppState` struct containing DB connection pool, pub/sub subscriptions, and rate limiters
- Configures separate admin/user rate limit quotas for expensive queries and mutations
- Includes protobuf module generation via `build.rs`
- Declares modules: `websocket_api`, `auth`, `db`, `handle_socket`, `subscriptions`, `airtable_users`, `convert`, `seed`, `test_utils`
- Declares modules: `websocket_api`, `auth`, `db`, `global_db`, `handle_socket`, `subscriptions`, `convert`, `seed`, `test_utils`

#### `backend/src/handle_socket.rs`
- Core WebSocket request/response handler (~1150 lines)
Expand Down Expand Up @@ -301,11 +301,6 @@ Copy the appropriate template to `frontend/.env` for your use case:
- Implements `From` trait for all domain types (Portfolio, Market, Order, Trade, Transfer, Account, Auction)
- Converts Rust Decimal to protobuf floats, timestamps to protobuf Timestamp format

#### `backend/src/airtable_users.rs`
- Syncs Airtable student records to Kinde and database
- Creates Kinde accounts and DB entries, assigns initial balances based on product ID
- Caches Kinde API tokens, logs errors back to Airtable

#### `backend/src/seed.rs`
- Development seed data (feature-gated behind `dev-mode`)
- Seeds fresh databases with test accounts (Alice, Bob, Charlie, Admin), markets, orders, and trades
Expand Down
86 changes: 86 additions & 0 deletions DEPLOY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Deployment

## Architecture

- **Frontend**: Static SPA on Vercel (SvelteKit with `adapter-static`)
- **Backend**: Rust binary on Fly.io (Docker, SQLite with persistent volume)
- Frontend and backend are on different domains, so HTTP API calls use cross-origin requests with CORS.

## Staging

- **Frontend**: https://platform-staging-five-gamma.vercel.app (Vercel project `platform-staging` under `trading-bootcamp` team)
- **Backend**: https://trading-bootcamp-staging.fly.dev (Fly app `trading-bootcamp-staging`)

### Deploy staging backend

```bash
fly deploy --config backend/fly.staging.toml
```

### Deploy staging frontend

```bash
vercel --prod --scope trading-bootcamp
```

### Vercel env vars (Production environment)

| Variable | Value |
|----------|-------|
| `PUBLIC_KINDE_CLIENT_ID` | `a9869bb1225848b9ad5bad2a04b72b5f` |
| `PUBLIC_KINDE_DOMAIN` | `https://account.trading.camp` |
| `PUBLIC_KINDE_REDIRECT_URI` | `https://platform-staging-five-gamma.vercel.app` |
| `PUBLIC_SERVER_URL` | `wss://trading-bootcamp-staging.fly.dev/api` |
| `PUBLIC_TEST_AUTH` | `false` |

**Important**: When setting env vars via `vercel env add`, pipe with `printf` (not `echo`) to avoid embedding trailing newlines:
```bash
printf 'value' | vercel env add VAR_NAME production --scope trading-bootcamp
```

### Kinde setup

Add the frontend URL to "Allowed callback URLs" in the Kinde application settings for client ID `a9869bb1225848b9ad5bad2a04b72b5f`.

## Code changes required for deployment

### 1. `.dockerignore`

Excludes `backend/target`, `backend/data`, SQLite files, `node_modules`, `.git`, `.claude` etc. Without this, Docker context transfer is ~733MB instead of ~700KB.

### 2. `backend/Dockerfile` modifications

- **`ENV SQLX_OFFLINE=true`**: SQLx does compile-time query checking against a live database by default. In Docker there's no database, so offline mode uses the pre-generated `.sqlx/` cache instead.
- **`COPY ./backend/global_migrations /app/global_migrations`**: The multi-cohort feature added a `global_migrations/` directory that needs to be in the runtime image.

### 3. `.vercelignore`

Excludes the same heavy directories from Vercel uploads.

### 4. CORS: `backend/Cargo.toml` + `backend/src/main.rs`

Since frontend (Vercel) and backend (Fly.io) are on different domains, the browser blocks cross-origin API requests. The fix:

- Added `"cors"` feature to `tower-http` in `Cargo.toml`
- Replaced the manual `SetResponseHeaderLayer` for `Access-Control-Allow-Origin: *` with `CorsLayer::permissive()`, which properly handles preflight OPTIONS requests and allows the `Authorization` header

### 5. Cross-origin HTTP API calls: `frontend/src/lib/apiBase.ts`

In development, the Vite dev server proxies `/api/*` to the backend (see `frontend/vite.config.ts`). In production, there's no proxy — the frontend and backend are on different domains.

`apiBase.ts` derives the HTTP base URL from `PUBLIC_SERVER_URL`:
- `wss://host.fly.dev/api` → `https://host.fly.dev`
- `ws://localhost:8080` → `http://localhost:8080`

`cohortApi.ts` and `adminApi.ts` use `API_BASE` to make absolute URL requests instead of relative `/api/...` paths. This works in both dev (Vite proxy still intercepts) and production (direct cross-origin requests).

### 6. `frontend/src/routes/[cohort_name]/docs/[slug]/+page.svelte`

Fixed markdown import paths — the file moved one level deeper into `[cohort_name]/` so the relative imports needed an extra `../` (e.g. `../../../../../docs/` → `../../../../../../docs/`).

### 7. `backend/fly.staging.toml`

Fly.io config for the staging app. Key differences from production (`fly.toml`):
- `app = 'trading-bootcamp-staging'`
- Separate persistent volume mount (`trading_bootcamp_staging`)
- Staging database path
3 changes: 3 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ target/
db.sqlite
db.sqlite-shm
db.sqlite-wal
*.sqlite
*.sqlite-shm
*.sqlite-wal

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading