5.7 KiB
Rezervovator
Booking system for sport/service facilities. Full-stack Rust: Leptos 0.8 (SSR + hydration) on Actix Web, PostgreSQL via sqlx. Crate name is rezervator, product name is "Rezervovator".
One crate, compiled twice by cargo-leptos:
- server binary — feature
ssr(Actix, sqlx, lettre, charts-rs) - browser WASM — feature
hydrate, targetwasm32-unknown-unknown
Every change must compile for both. Server-only code and imports go behind #[cfg(feature = "ssr")] / cfg_if!.
Commands
cargo leptos watch # dev server with reload (needs config.toml + running Postgres)
cargo leptos build --release # production build -> target/release/rezervator + target/site
cargo check --features ssr # type-check the server side
cargo check --lib --features hydrate --target wasm32-unknown-unknown # type-check the WASM side
cargo test --features ssr # unit tests
Run both cargo check commands and the tests after any change. They don't catch hydration errors, so after changing a view also load the page in a browser with a full reload (the browser caches the old WASM otherwise) and check the console; a plain cargo check / cargo test (no features) checks neither side properly.
Unit tests are #[cfg(test)] mod tests at the bottom of the file they test (#[cfg(all(test, feature = "ssr"))] for ssr-only code). They cover pure logic only — parsing, validation, paging, slot merging, mail templates, config. Nothing touches the database, so server functions, permissions and SQL are untested. Add tests for new pure logic; keep them free of DB and network.
end2end/ is still the untouched Leptos template (it asserts "Welcome to Leptos") — it does not test this app.
Running locally
config.toml(gitignored) is read from the working directory, or from-c <path>. Copyconfig_sample.toml. The bind address comes from[network]there, not fromLEPTOS_SITE_ADDR.- Migrations in
migrations/are embedded bysqlx::migrate!()and run on startup. - First start seeds an
admin/adminuser, a placeholder company, empty mail templates and an appearance row (create_admin,check_company,check_messages,check_appearanceinsrc/main.rs). - With
transport = "File"e-mails are written as.emlintoMaildir/instead of being sent. assets/is copied totarget/siteon build.assets/data/(banner.css, uploaded banner) is untracked but required: the page links/data/<css_name>and uploads are written totarget/site/data/at runtime, relative to the working directory. A rebuild wipestarget/site, so runtime uploads there are lost in dev.
Layout
src/main.rs— Actix setup: config, pool, migrations, seeding, session + auth middleware,/api/*server fns,/upload.src/app.rs—<App/>, router, shared contexts (MenuHelper,MenuOpener,DialogHelper).src/backend/— server functions per domain, plusdata.rs(all DTOs/entities shared by server and client) andauth_middleware.rs(redirects unauthenticated/admin*page requests to/login).src/pages/— page and dialog components;src/components/— reusable pieces (DataForm,QuestionDialog,ModalDialog,ServerErr,ValidationErr,AdminPortal).src/locales/—trl()and the translation catalogue.src/error.rs—AppError;src/config.rs—config.tomlstructs (ssr only);src/validator.rs— client-side form validation helper.assets/vendor/— third-party admin template (Bootstrap based). Don't edit or reformat it; app styles go tostyle/main.scss.
Routes: / public booking form, /login, /admin, /admin/settings, /admin/mail_settings, /admin/bookings, /admin/customers, /admin/appearance.
Domain model
property is a bookable thing with a price and slot length. A booking is one reservation_sum (date, customer, total price, state New → Approved / Canceled, public uuid) with one or more reservation rows (property + from/to time). opening_hour rows are per weekday, closing_time rows are date ranges when booking is closed. Customers are matched by e-mail. The public form is protected by a proof-of-work captcha (leptos-captcha).
Conventions
- Code, comments, identifiers, log messages and commit messages are in English. UI strings are English keys translated at runtime (see
.claude/rules/frontend.md). - Commit messages are always written in English, even when the conversation is in Czech: short past-tense sentences ending with a period, e.g. "Added setting for closing days."
- The project has no rustfmt config and the code is not rustfmt-formatted. Don't run
cargo fmtover existing files; match the surrounding style and keep diffs limited to what you changed. - Leptos is 0.8 — use the 0.8 API (
use leptos::prelude::*,signal(),RwSignal::new,Resource::new_blocking,ServerAction::<T>::new()), not the 0.6create_*functions. wasm-bindgenis pinned to an exact version inCargo.toml. Thewasm-bindgenCLI that cargo-leptos runs must be exactly the same version (cargo install -f wasm-bindgen-cli --version <same>), otherwise the front-end build fails with a schema version mismatch. Leptos 0.8 needs at least 0.2.108. Change the pin and the CLI together, never one of them.#![recursion_limit = "512"]at the top ofsrc/lib.rsandsrc/main.rsis required: without it the release build fails with "queries overflow the depth limit"..cargo/config.tomlselects thegetrandombackend for the WASM target (needed by the captcha's proof of work).getrandomanduuidhave theirjsfeatures enabled inCargo.tomlfor the same reason; don't remove them.- Version lives in
Cargo.tomland is shown in the page footer.
Detailed rules are in .claude/rules/: backend.md, frontend.md, database.md.