8081 is the alt-HTTP port — one of the crowded defaults every proxy/dev server grabs — so it's collision-prone on a shared host. Move to 22672, a registered- range port in the sparse 20000s band, derived deterministically from the service name and recorded in architecture port-allocations.md. Updated everywhere the number appears: api bind config + default, firewalld service XML, nginx upstream, vite dev proxy, deploy workflow API_PORT, infra-setup (SELinux semanage label), and docs. Operator note: re-run script/infra-setup.sh on slartibartfast to relabel the SELinux port (22672) and ship the updated firewalld XML before the next deploy restarts the api on the new port. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fKZzDpvjiJ9eYbPGgJvUP
3.7 KiB
CLAUDE.md — newsfeed
Agent-facing notes for working in this repo. Read the workspace-wide conventions in
~/git/architecture/*.md first; this file records only what's specific to newsfeed,
especially the deliberate deviations.
What this is
A self-hosted, user-controlled news feed. The core idea: the user owns the ranking. Never introduce an implicit/opaque signal that overrides the user's explicit weights. Signals (view/click/save/dismiss) may inform future ranking but must remain subordinate to the per-source and per-interest weights the user sets.
Layout & boundaries (strict)
newsfeed-entities— types/DTOs only, no I/O. Wire DTOs derivets_rs::TSwith#[ts(export)];cargo test -p newsfeed-entitiesregeneratesweb/src/api/bindings/(path set by.cargo/config.toml'sTS_RS_EXPORT_DIR). Don't hand-edit bindings.newsfeed-core— business logic + data-access ports (traits). Pure where possible (ranking, auth hashing). No DB or network calls.newsfeed-data— SQLite adapters implementing the core ports.newsfeed-api/newsfeed-worker— thin binaries; wire config/logging/signals and the concrete store. No business logic that could live in a library crate.
New types → entities. New logic → core. New I/O → data. Add a port to
core::ports, implement it in data::store.
Deliberate deviations from generic.md
- SQLite instead of Postgres (§5). Explicit project choice. Because SQLite is
single-file/single-host, api and worker co-locate and share
/var/lib/newsfeed/newsfeed.db. There is no central DB cluster, no mTLS DB auth, nopg_identmapping. The worker does in-process scheduling — theFOR UPDATE SKIP LOCKEDguidance does not apply. - Runtime sqlx queries, not
query!macros (§5). SQLite's dynamic typing makes compile-time checking low-value and forces a live DB or fiddly offline cache into CI. We usesqlx::query/query_aswithFromRowrow structs (seedata/src/rows.rs) and map to entities explicitly. No.sqlx/cache; CI builds need no database.
Both are documented at their site (data/src/lib.rs header, readme.md) — keep them in
sync if you change the approach.
Datastore conventions
TEXT UUIDs (hyphenated), RFC3339 timestamps, INTEGER booleans, JSON-in-TEXT for small
collections (tags, media). Migrations are immutable once committed (§5): add a new
crates/newsfeed-data/migrations/NNNN_*.sql, never edit a landed one. RETURNING and
ON CONFLICT are used, so the target needs SQLite ≥ 3.35.
Auth model
- Humans: Argon2id password hash + opaque session cookie (
nf_session), hash stored. - Producers: per-user bearer API tokens (
nf_<prefix>_<random>), SHA-256 hashed at rest, shown once. Ingest is attributed to the token's owner. Crypto/token logic lives incore::auth; the flow orchestration incore::service.
Deploy topology
api + worker → slartibartfast.kosherinata.internal; SPA → oolon.kosherinata.internal
(nginx serves + proxies /v1 to the API over the mesh; TLS terminates at oolon). API
port 22672 (registered in architecture port-allocations.md; deliberately not the crowded
8081), plain HTTP behind firewalld. See .gitea/workflows/deploy.yml (infra truth)
and script/infra-setup.sh (one-time host prep). Type=exec, not notify, because axum
doesn't sd_notify.
Before you commit
cargo fmt --all·cargo clippy --workspace --all-targets -- -D warnings·cargo test --workspacepnpm --dir web build(tsc typecheck + vite) ·pnpm --dir web lint- If you touched an entity DTO, regenerate bindings and include them in the commit.
- Conventional Commits; scope = crate/area (
feat(api):,fix(core):, …).