feat: dockerfile (node runtime), startup migration, README, runtime env config
This commit is contained in:
117
README.md
Normal file
117
README.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# ClaudeDo Online Inbox
|
||||
|
||||
Optional online mirror of the [ClaudeDo](https://github.com/) desktop app's **Idle** task
|
||||
backlog. Lets the single owner view their lists and jot new tasks from a phone or browser.
|
||||
The desktop app (local, .NET/SQLite) remains the source of truth; this service only
|
||||
stores/serves data — it never executes tasks.
|
||||
|
||||
**Live:** https://claudedo.kuns.dev · **API base:** `https://claudedo.kuns.dev/api`
|
||||
|
||||
## Governing rule
|
||||
|
||||
The online store mirrors **exactly** the desktop's Idle tasks. A task exists online only
|
||||
while it is Idle; queuing it on the desktop deletes it here. Running/Done/Failed/review
|
||||
tasks never appear online.
|
||||
|
||||
- **Lists** — desktop → online only (full-replace catalog).
|
||||
- **Idle tasks** — two-way creation: the desktop pushes its Idle tasks (PUT) and pulls
|
||||
web-created ones (`consumed=false` → import → consume).
|
||||
|
||||
## Stack
|
||||
|
||||
Nuxt 3 (Vue 3, TypeScript, Bun) running as an SPA (`ssr: false`) with Nitro server routes as
|
||||
the API, on shared PostgreSQL, behind Zitadel auth, deployed via Coolify.
|
||||
|
||||
- Web client login: [`@kuns/zitadel-auth`](../kuns-zitadel) (vendored under `vendor/`).
|
||||
- API token validation: `jose` against Zitadel JWKS.
|
||||
- DB access: `postgres` (postgres.js), parameterized queries only.
|
||||
|
||||
## API
|
||||
|
||||
Every `/api/**` route requires a valid Zitadel **access token** (`Authorization: Bearer …`)
|
||||
belonging to the owner. Missing/invalid/expired → `401`. No anonymous access.
|
||||
|
||||
| Method & path | Caller | Behaviour |
|
||||
|---|---|---|
|
||||
| `PUT /api/lists` | desktop | Body `[{id,name}]` = full catalog. Upserts all, deletes lists not in payload (cascades tasks). → 200 |
|
||||
| `GET /api/lists` | web | → 200 `[{id,name}]` |
|
||||
| `GET /api/lists/{id}/tasks` | web | → 200 tasks for the list; 404 if unknown |
|
||||
| `POST /api/tasks` | web | Body `{title, description?, listId}`. Server-generated GUID, `source=web`, `consumed=false`. 404 if listId unknown. → 201 with the created task |
|
||||
| `PUT /api/tasks/{id}` | desktop | Body `{listId, title, description?}`. Idempotent upsert (`source=desktop` on insert). → 201 (new) / 200 (existing) |
|
||||
| `DELETE /api/tasks/{id}` | desktop | Idempotent. → 204 (even if absent) |
|
||||
| `GET /api/tasks?consumed=false` | desktop | → 200 `[{id, listId, title, description, createdAt}]` web tasks not yet imported |
|
||||
| `POST /api/tasks/{id}/consume` | desktop | Sets `consumed=true`. Idempotent. → 200; 404 if unknown |
|
||||
|
||||
Task ids are a **shared GUID space**: web-created ids are reused verbatim by the desktop on
|
||||
import; all task writes are idempotent upserts keyed on `id`.
|
||||
|
||||
## Zitadel configuration (for the desktop client)
|
||||
|
||||
Both the desktop and the web client authenticate as the **same Zitadel user** (the owner).
|
||||
Project **ClaudeDo** has two PKCE apps (both issue JWT access tokens):
|
||||
|
||||
| | Web | Desktop |
|
||||
|---|---|---|
|
||||
| Client id | `376787352019861775` | `376787352137302287` |
|
||||
| App type | User-Agent (SPA) | Native |
|
||||
| Auth method | PKCE (none) | PKCE (none) |
|
||||
| Grants | authorization_code | authorization_code, refresh_token |
|
||||
| Redirect | `https://claudedo.kuns.dev/auth/callback` | `http://localhost:8765/callback`, `http://127.0.0.1:8765/callback` |
|
||||
|
||||
**Desktop OnlineInbox settings:**
|
||||
|
||||
- Issuer: `https://auth.kuns.dev`
|
||||
- Client id: `376787352137302287`
|
||||
- Scopes: `openid profile email offline_access urn:zitadel:iam:org:project:id:376787351902355727:aud`
|
||||
- `offline_access` → refresh token for headless re-auth.
|
||||
- the `…:aud` scope puts the project id into the token's `aud` so the API validates it.
|
||||
- Flow: Authorization Code + PKCE on a loopback redirect for the initial interactive login,
|
||||
then refresh-token grant headlessly. Store the refresh token securely.
|
||||
- API base URL: `https://claudedo.kuns.dev/api`
|
||||
|
||||
Project id: `376787351902355727`. Owner user id (`sub`): `365090688972947729`
|
||||
(`mika@kuns.dev`).
|
||||
|
||||
## Environment variables
|
||||
|
||||
Server-only values are read from `process.env` at runtime (set them in Coolify):
|
||||
|
||||
| Var | Purpose |
|
||||
|---|---|
|
||||
| `DATABASE_URL` | `postgres://mika:…@l8kogcggsc80sgcgk8kswww4:5432/claudedo` (shared PG, internal host) |
|
||||
| `ZITADEL_ISSUER` | `https://auth.kuns.dev` |
|
||||
| `ZITADEL_AUDIENCE` | accepted audiences (CSV): web id, desktop id, project id |
|
||||
| `ALLOWED_USER_IDS` | owner `sub` allowlist (CSV) |
|
||||
| `WEB_ORIGIN` | CORS allowed origin (`https://claudedo.kuns.dev`) |
|
||||
|
||||
Public web-client config is **baked at build time** (non-secret) via Dockerfile build args
|
||||
(`NUXT_PUBLIC_ZITADEL_ISSUER`, `NUXT_PUBLIC_ZITADEL_CLIENT_ID`,
|
||||
`NUXT_PUBLIC_ZITADEL_PROJECT_ID`); override with `--build-arg` if the ids change.
|
||||
|
||||
See `.env.example` for local development.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
bun install
|
||||
# Point at a local/tunnelled Postgres and apply the schema:
|
||||
DATABASE_URL=postgres://… bun run migrate
|
||||
DATABASE_URL=postgres://… bun run dev # http://localhost:3000
|
||||
|
||||
# Tests (need a Postgres test DB):
|
||||
DATABASE_URL=postgres://…/claudedo_test bun run test
|
||||
```
|
||||
|
||||
Local API smoke without a Zitadel token: set `AUTH_DEV_BYPASS=1` (dev mode only — it is
|
||||
dead-code-eliminated from production builds).
|
||||
|
||||
## Database
|
||||
|
||||
DB `claudedo` on the shared PostgreSQL instance. Schema (`server/utils/schema.ts`) is applied
|
||||
idempotently on server startup by `server/plugins/migrate.ts` (advisory-locked) and via
|
||||
`bun run migrate`.
|
||||
|
||||
## Deployment
|
||||
|
||||
Coolify builds the `Dockerfile` (Bun build → Node runtime) on push to Gitea; Traefik routes
|
||||
`claudedo.kuns.dev` with automatic SSL.
|
||||
Reference in New Issue
Block a user