Heetlog is a heating management app for people who heat their own house: register your boiler or stove, log every fuel purchase and burn, and get an honest picture of what a heating season costs — plus service reminders so the yearly inspection doesn't get forgotten. Built as my engineering thesis project.
- Multiple heaters per user (coal, wood, gas, oil, electric, pellet, biomass), each with its own history, and one marked as your main one
- Fuel records with per-unit tracking (kg / litres / kWh) and cost, aggregated into daily-usage series and season summaries
- Consumption charts and a stats page — see how usage tracks over the season instead of guessing
- Service records (cleaning, inspection, repair, maintenance) with statuses and reminders for what's due or already overdue
- Feature proposals board — users submit ideas, vote on them and comment; statuses are moved by admins
- Admin panel with a user list and per-user detail view
- Google OAuth sign-in, light/dark theme, installable as a PWA, Polish-language UI
A thesis project, but built like a real product: real auth, a containerised deployment story, and a test suite that runs against a real database rather than mocks.
Rust was the experiment here. A CRUD app like this would have been quicker in something I already knew — I picked Rust specifically to find out what it's like to build a normal web backend in it, ORM, migrations, OAuth and all.
| Rust + Actix Web | The experiment — a typed, fast HTTP layer with a controller → service → entity split that keeps business logic out of the handlers |
| SeaORM + Postgres | Async ORM with versioned migrations that run automatically on boot — no manual migration step in prod |
| utoipa + Swagger UI | The OpenAPI spec is derived from the handlers and DTOs themselves and served at /swagger, so the docs can't drift from the code |
| JWT in httpOnly cookies | Short-lived access token plus a refresh token, both httpOnly and SameSite-scoped, exchanged via a one-time code after the Google OAuth redirect |
| React + TypeScript + Tailwind | Responsive frontend with Zustand for client state and thin Axios service modules over the API |
| Docker Compose | One compose file per purpose — run the stack, run the tests — so CI and local runs are the same commands |
| GitHub Actions | Every push builds the images and runs the full backend + frontend suite through make test |
| Makefile | Single entry point for everything: make run, make test, make migration-up, make db-reset |
Backend
- Layered structure —
controllers/(HTTP + OpenAPI annotations),services/(business logic),entities/(SeaORM models),models/(request/response DTOs),middleware/(auth extraction). Domain rules like "is this service overdue?" live on the entity model, not in a handler. - Auth — Google OAuth 2.0 is the only login path. The callback hands the frontend a one-time code, which is exchanged for httpOnly access + refresh cookies; the refresh endpoint rotates the access token without another round trip to Google.
- Migrations — SeaORM migrations under
backend/migration/, applied byMigrator::upat startup, so a fresh container is always schema-current. - Config — a single
AppConfigloaded and validated once at boot (once_cell); missing env vars fail fast instead of surfacing later as runtime errors. - Testing — unit tests colocated in
src/unit_tests/for models and config, plus integration tests inbackend/tests/that hit a real Postgres started by Compose.
Frontend
- Two build modes —
npm run devtalks to a locally running backend; the production build is served by NGINX in its own container (frontend/Dockerfile), with the backend URL injected at build time. - State — Zustand stores for auth and theme, plain Axios service modules per resource; no data-fetching framework, which keeps the dependency surface small.
- Theme — light/dark is resolved before first paint by an inline script in
index.html, so there's no flash on load. - Testing — Vitest + Testing Library with MSW mocking the API, so component tests exercise real request handling instead of stubbed functions.
Deployment & CI
- Multi-stage images — the Rust backend compiles in a builder stage and ships as a slim runtime image; the frontend builds with Vite and ships as NGINX + static assets. The frontend Dockerfile also exposes a
testtarget that CI reuses. - Compose per purpose —
docker/docker-compose.ymlruns the stack,docker/docker-compose.test.ymlbrings up a throwaway Postgres, runs both suites and tears the volumes down. - CI — GitHub Actions runs
make teston every push, so CI and a local test run are literally the same command. - Hosting —
render.yamldeploys the backend as a Docker web service; the frontend deploys as a separate static build.
praca_dyplomowa/
├── backend/ # Rust + Actix Web API
│ ├── src/
│ │ ├── controllers/ # HTTP handlers + OpenAPI annotations
│ │ ├── services/ # business logic
│ │ ├── entities/ # SeaORM entities + domain rules
│ │ ├── models/ # request/response DTOs
│ │ └── middleware/ # auth
│ ├── migration/ # SeaORM migrations
│ └── tests/ # integration tests (real Postgres)
├── frontend/ # React + TypeScript + Vite + Tailwind
│ ├── src/
│ └── tests/ # Vitest + Testing Library + MSW
├── docker/ # compose files (run / test) and seed data
├── .github/workflows/ # CI
└── Makefile # dev/test/migration/deploy commands


