Vite/React + shadcn/ui frontend with Superhuman-style keyboard nav (j/k/o/v/e/s/u, feed switching, localStorage caching for instant loads) backed by an Express API that polls the HN Firebase API into Postgres and serves cached feeds/comments. Instrumented with OpenTelemetry (traces/metrics/logs via OTLP/HTTP).
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
# HN Reader
|
||||
|
||||
A lightweight, keyboard-driven Hacker News reader. Navigate like
|
||||
Superhuman/Linear: `j`/`k` to move, `o`/`Enter` to read, `e` to
|
||||
archive, `s` to star, numbers to switch feeds. Feed data is cached in
|
||||
`localStorage` for instant loads, then revalidated in the background.
|
||||
|
||||
Press `?` in the app for the full shortcut list.
|
||||
|
||||
## Stack
|
||||
|
||||
- **Frontend**: Vite + React + TypeScript, [shadcn/ui](https://ui.shadcn.com) (Radix primitives, Tailwind v4, Nova preset)
|
||||
- **Backend**: Express (`server/`), polls the [HN Firebase API](https://github.com/HackerNews/API) every 5 minutes and caches stories in Postgres
|
||||
- **Database**: Postgres 16 (Docker), schema in `scripts/init.sql`
|
||||
- **Observability**: OpenTelemetry traces/metrics/logs shipped via OTLP/HTTP
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
src/ React SPA (feeds, keyboard nav, comments dialog)
|
||||
server/ Express API + HN sync job + OTel bootstrap
|
||||
scripts/ DB schema, backup script
|
||||
docker-compose.yml Postgres container for this app
|
||||
.gitea/workflows/ CI: lint, typecheck, build
|
||||
```
|
||||
|
||||
The API caches HN's top/new/best/ask/show/job feeds (100 items each)
|
||||
in Postgres so the frontend never talks to Hacker News directly.
|
||||
Per-story read/starred/archived state lives in `story_state` and is
|
||||
returned inline with each feed response.
|
||||
|
||||
## Local development
|
||||
|
||||
```bash
|
||||
cp .env.example .env # fill in POSTGRES_PASSWORD
|
||||
docker compose up -d # start Postgres
|
||||
npm install
|
||||
npm run dev:server # API on :3012 (loaded from .env)
|
||||
npm run dev # Vite dev server on :3012, proxies /api
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Runs as the `app-54d6877c` systemd unit: builds are not automated on
|
||||
deploy, so after pulling changes run `npm run build` then restart the
|
||||
service (`sudo systemctl restart app-54d6877c`). The compiled SPA is
|
||||
served as static files by the same Express process that serves `/api`,
|
||||
on port **3012**.
|
||||
|
||||
Nightly Postgres backups run via the `app-54d6877c-backup.timer`
|
||||
systemd timer (`scripts/backup.sh`), keeping 7 days of `pg_dump`
|
||||
snapshots in `~/backups`.
|
||||
|
||||
## Design system
|
||||
|
||||
Built with `shadcn/ui` (`components.json`, Radix base, Nova preset).
|
||||
Components live under `src/components/ui` and are added with
|
||||
`npx shadcn@latest add <component>` — nothing here is hand-rolled, so
|
||||
swapping in a custom registry later (e.g. a `privatecloud` registry)
|
||||
is a matter of updating `components.json`'s `registries` field and
|
||||
re-running `add` against it.
|
||||
|
||||
## Observability
|
||||
|
||||
The API sends traces, metrics, and logs to an OTLP/HTTP collector at
|
||||
`OTEL_EXPORTER_OTLP_ENDPOINT` (defaults to `http://localhost:4318`),
|
||||
instrumenting HTTP, Express, and Postgres calls automatically via
|
||||
`@opentelemetry/auto-instrumentations-node`. View it in Grafana at
|
||||
`localhost:3400`.
|
||||
Reference in New Issue
Block a user