Files
rezervovator/CLAUDE.md
T
2026-10-12 10:45:50 +02:00

5.9 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, target wasm32-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>. Copy config_sample.toml. The bind address comes from [network] there, not from LEPTOS_SITE_ADDR.
  • Migrations in migrations/ are embedded by sqlx::migrate!() and run on startup.
  • First start seeds an admin / admin user, a placeholder company, empty mail templates and an appearance row (create_admin, check_company, check_messages, check_appearance in src/main.rs).
  • With transport = "File" e-mails are written as .eml into Maildir/ instead of being sent.
  • assets/ is copied to target/site on build. assets/data/ (banner.css, uploaded banner) is untracked but required: the page links /data/<css_name> and uploads are written to target/site/data/ at runtime, relative to the working directory. A rebuild wipes target/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, plus data.rs (all DTOs/entities shared by server and client) and auth_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.toml structs (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 are plain CSS files in assets/css/, linked in src/components/header.rs.
  • style/main.scss is compiled to /pkg/rezervator.css, but nothing links that file, so styles written there have no effect.

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 fmt over 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.6 create_* functions.
  • wasm-bindgen is pinned to an exact version in Cargo.toml. The wasm-bindgen CLI 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 of src/lib.rs and src/main.rs is required: without it the release build fails with "queries overflow the depth limit".
  • .cargo/config.toml selects the getrandom backend for the WASM target (needed by the captcha's proof of work). getrandom and uuid have their js features enabled in Cargo.toml for the same reason; don't remove them.
  • Version lives in Cargo.toml and is shown in the page footer.

Detailed rules are in .claude/rules/: backend.md, frontend.md, database.md.