# Veola Self-hosted Go web app that tracks items across e-commerce platforms (eBay, Amazon family, Yahoo Auctions JP, Mercari JP) via the [Apify](https://apify.com) scraping API and pushes deal alerts to a self-hosted [ntfy](https://ntfy.sh) instance. Track. Watch. Notice. ## Features - Watch arbitrary items across multiple marketplaces with per-item search queries, target prices, and poll intervals - Active-listing, sold-listing, and price-comparison actors per item - Price-history chart and best-price badge once enough history accumulates - Deal alerts pushed to ntfy when current price falls at or below target - Single-binary deploy, SQLite storage, no CGO See [`veola-spec.md`](veola-spec.md) for the full specification. ## Requirements - Go 1.22+ (developed against 1.25) - An [Apify](https://apify.com) account + API key - A reachable [ntfy](https://ntfy.sh) instance (self-hosted or ntfy.sh) ## Build ```sh go build -o veola-bin . ``` The binary is named `veola-bin` rather than `veola` because the module is also `veola` — `go build` cannot write a binary with the same name as the module dir. If you change any `.templ` files, regenerate first: ```sh ~/go/bin/templ generate ``` ## Configure Copy the example and edit: ```sh cp config.toml.example config.toml ``` Both `session_secret` and `encryption_key` must be at least 32 bytes and different from each other. Generate with: ```sh openssl rand -hex 32 ``` `encryption_key` encrypts secrets at rest in SQLite (Apify keys, ntfy settings). Rotating it invalidates stored secrets — re-enter them through `/settings` after rotation. ## Run ```sh ./veola-bin -config config.toml ``` First-run flow: 1. Visit `http://localhost:8080/`. With no users, you are redirected to `/setup`. 2. Create the admin account. 3. Log in at `/login`. 4. Add items at `/items/new`. Optionally fill in your Apify key and ntfy URL via `/settings` if you didn't put them in `config.toml`. The scheduler starts with the server and polls each active item on its configured interval. The bottom-of-hour global poll runs every `scheduler.global_poll_interval_minutes`. ## Layout ``` main.go entry point: config, db open, scheduler, http server internal/ config/ TOML config loading and validation crypto/ AES-GCM encryption for secrets at rest db/ SQLite schema, migrations, store models/ domain types apify/ Apify API client ntfy/ ntfy push client auth/ session + CSRF scheduler/ poll loop, alert/dedup/badge logic handlers/ HTTP handlers templates/ templ components static/ CSS, vendored htmx ``` ## Test ```sh go test ./... ``` Unit tests cover crypto round-trip, db round-trip and dedup, and scheduler alert/badge logic. No handler-level tests yet. ## Operate - The SQLite file lives at `server.db_path` (default `./veola.db`). Back this up — it holds your watched items, history, encrypted secrets, and user accounts. - The process responds to `SIGINT` / `SIGTERM` with a graceful HTTP shutdown (30s timeout) followed by scheduler stop. - Logs go to stdout as structured `log/slog` records. ## Aesthetic Sega Genesis blue. Not dark mode, not light mode — blue mode. See the visual design section of `veola-spec.md` for the palette.