diff --git a/.gitignore b/.gitignore index b1f7192..53ad8dc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ **/*.rs.bk .env .env.local +.zed/ # frontend /ui/node_modules diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8b74e8d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,78 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**moments** is a personal activity timeline and portfolio site. It ingests developer activity from multiple forges (GitHub, Gitea, Mercurial, Bugzilla), stores raw JSON payloads in PostgreSQL, and serves a React frontend showing contribution graphs, a ranked project dashboard, and a filterable activity timeline. + +## Architecture + +Hexagonal (ports & adapters) Rust backend with a React/TypeScript frontend. + +### Crate Dependency Graph + +``` +moments-entities — pure types/DTOs, no DB or HTTP deps + ^ +moments-core — port traits (EventReader, EventWriter, EventSource, PollerStateStore) + + presentation reshape + poller loop + ^ +moments-data — sole adapter: PgStore implements all core traits + + EventSource impls (github, gitea, hg, bugzilla) + + SQL migrations + ^ +moments-api — axum HTTP API binary (read-only, connects as moments_ro) +moments-worker — ingestion daemon binary (runs migrations, connects as moments_rw) +``` + +### Key Design Decisions + +- **Raw payload storage**: upstream JSON is stored verbatim in `events.payload` (JSONB). The `reshape()` function in `moments-core/src/presentation.rs` transforms payloads into `TimelineItem` at request time — no re-ingestion needed to change presentation. +- **Public/private gate**: `events.public` boolean controls API visibility. Only `public = true` rows are served. +- **Wire types are hand-maintained**: `ui/src/api/client.ts` mirrors Rust entity types manually. +- **Migrations**: run automatically on worker startup via `sqlx::migrate!`. The API binary never runs migrations. + +### Frontend + +React 19 + Vite 6 (SWC) + TypeScript + Bootstrap 5. State/data via `@tanstack/react-query`. Package manager is **pnpm**. + +Routes: `/` (dashboard), `/activity` (timeline), `/project/:source/*` (project detail), `/cv` (resume). + +## Build & Dev Commands + +### Rust + +```sh +cargo build --workspace # build all crates +cargo build --workspace --release # release build +cargo clippy --workspace # lint +cargo fmt --check # format check +cargo test --workspace # run tests + +# Run binaries (need DATABASE_URL) +DATABASE_URL=postgres://localhost/moments cargo run -p moments-api +DATABASE_URL=postgres://localhost/moments cargo run -p moments-worker +``` + +### Frontend + +```sh +cd ui +pnpm install # install deps +pnpm dev # dev server on :5173 (proxies /api/* to localhost:8080) +pnpm lint # tsc --noEmit type-check +pnpm build # production build (tsc -b && vite build) +``` + +## Database + +PostgreSQL with three migrations in `crates/moments-data/migrations/`. Two roles: `moments_rw` (worker, full access) and `moments_ro` (API, SELECT-only). + +## API Endpoints + +All under `/v1/`: `healthz`, `events`, `sources`, `projects`, `activity/daily`, `forge/{source}/*`, `og/contributions.png`. + +## Deployment + +Production uses `./script/deploy.sh`. Services run under systemd with hardened units. Secrets resolved from `pass` store via template substitution. Nginx reverse-proxies `/api/` to the API host.