Files
moments/CLAUDE.md
rob thijssen 88ce993df3 feat(blog): add markdown blog sourced from a gitea repo
posts are markdown files with yaml frontmatter (title, slug, date;
optional draft/public) in the grenade/blog repo. the worker's new
BlogSource polls the repo — one branch-tip request when nothing
changed — and upserts posts into events with source='blog' and
occurred_at from the frontmatter date, so imported posts keep their
original publish dates and backfill the contribution graph.

- new /v1/blog and /v1/blog/{slug} endpoints over the existing
  EventReader port; drafts stay hidden via the public gate
- new /blog and /blog/:slug routes, nav link, activity-feed entry
  with post icon and filter toggle; relative image srcs resolve to
  gitea raw urls
- shared Markdown component extracted from ProjectPage
- vite proxy target overridable via API_PROXY_TARGET for local dev

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 22:44:56 +03:00

81 lines
3.7 KiB
Markdown

# 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), `/blog` + `/blog/:slug` (blog), `/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`, `blog`, `blog/{slug}`, `activity/daily`, `forge/{source}/*`, `og/contributions.png`.
Blog posts are markdown files with YAML frontmatter (`title`, `slug`, `date`; optional `draft`/`public`) in the `grenade/blog` Gitea repo. The worker's `BlogSource` polls the repo (branch-tip sha as change detection) and upserts posts into `events` with `source='blog'` and `occurred_at` from the frontmatter date, so imported posts keep their original publish dates. Publishing or editing a post = pushing to that repo.
## 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.