fizalgo
A household chore tracker with a bit of gamification. People log task executions (who did what, where, with how much effort); tasks belong to divisions (rooms) and have a frequency; completed work earns XP. The /next page ranks tasks by urgency (a Postgres view), /stats charts cumulative XP and shows history.
Stack
- Next.js 16 (App Router) | React 19 | TypeScript
- Drizzle ORM + PostgreSQL
- HeroUI v3 components | Tailwind CSS 4
- i18next (server-first, runtime locale switch between PT and EN)
- Bun (package manager + script runner) | Biome (lint/format)
Architecture notes
- Two databases, one instance. A single Postgres server holds
fizalgo(real, authenticated data) andfizalgo_demo(a throwaway guest sandbox). Every request picks one viagetRequestDb()(src/db/request.ts): a valid session -> real, otherwise -> demo. - Auth is per-user, session-based (Lucia-guide pattern, no library): a random token in an httpOnly cookie, only its SHA-256 hash stored in
Session; passwords hashed withnode:cryptoscrypt. There is no open signup: users are provisioned withdb:create-user, each linked to aPerson. - Guest demo vs. real.
/is a public landing with "Try the demo" and "Login". Guests browse the whole app against the demo DB (/do-things, the random-data generator, is guest-only). Logging in switches every route to the real DB. - Rendering / revalidation. All data routes read the session cookie, so they're dynamic; with Next's default
staleTimes.dynamic = 0, navigation always re-renders fresh, so cross-routerevalidatePathis unnecessary; only in-place updates revalidate.
Local development
Postgres runs in Docker; the app runs on the host.
cp .env.example .env # set POSTGRES_PASSWORD (any value)
docker compose -f docker-compose.dev.yml up -d
bun install
bun db:migrate # creates both DBs + the TaskPeriod view
bun db:seed # seed the real DB
bun db:reset-demo # seed the demo sandbox
bun db:create-user --username you --password secret --person "Your Name"
bun dev # -> http://localhost:3000
Then: / -> Try the demo (demo DB, no login) or Login with the user you created (real DB). The language toggle (PT/EN) is in the navbar menu.
Scripts
| Script | What it does |
|---|---|
bun dev |
Dev server (HMR) |
bun run build / bun start |
Production build / serve |
bun run lint |
Biome check |
bun db:generate |
Generate a Drizzle migration from src/db/schema.ts |
bun db:migrate |
Apply migrations to both DBs (creates them if missing) |
bun db:seed |
Seed the real DB from seed/seed.json (falls back to seed/seed.example.json) |
bun db:reset-demo |
Wipe + reseed the demo DB |
bun db:create-user --username <u> --password <p> --person <name> |
Provision a real-DB user |
Environment
All connection URLs derive from these parts (.env, see .env.example):
| Var | Notes |
|---|---|
POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB |
Credentials + base DB name |
POSTGRES_HOST / POSTGRES_PORT |
localhost:5432 for local dev |
DATA_DIR |
Where Postgres stores data (compose bind-mount; deploy uses /opt/fizalgo/data) |
APP_PORT |
Host port the app listens on |
Real env files are never committed; seed/seed.json (real household data) is gitignored.
Internationalisation
Server-first: getDict() (src/i18n/server.ts) resolves the cookie's locale bundle and server components pass slices to client components as props. Client-side t() is available via I18nProvider but used sparingly (e.g. the language switcher). Translations live in src/i18n/{pt,en}.json; types are inferred from them (src/i18n/i18next.d.ts).
Deployment
Deploys to a Gitea-Actions server on push to master (.gitea/workflows/deploy.yaml), running the full docker-compose.yml stack (app + Postgres). The app is a Next standalone build; migrations run at container start against both DBs.
Host prep (once):
- Create
/opt/fizalgo/and/opt/fizalgo/data/. - Gitea repo -> Actions -> Variables:
POSTGRES_USER,POSTGRES_DB,APP_PORT; Secrets:POSTGRES_PASSWORD. - Point a Caddy vhost at the app's
APP_PORT. - Confirm the default branch is
master.
After the first deploy, seed the real DB once (docker compose exec app bun run db:seed) and create a user (... db:create-user ...). The demo DB can be reset on a schedule with db:reset-demo.