Files
rezervovator/.claude/rules/backend.md
T
2026-10-09 18:18:35 +02:00

55 lines
3.8 KiB
Markdown

---
paths:
- "src/backend/**"
- "src/main.rs"
- "src/error.rs"
- "src/config.rs"
---
# Backend rules
## Server functions
- One file per domain in `src/backend/`. Server-only helpers and their imports go in the `cfg_if! { if #[cfg(feature = "ssr")] { ... }}` block at the top; `#[server]` functions follow below it. Extra server-only imports inside a server fn body are written as local `use` statements.
- Use plain `#[server]` for new functions (older ones spell out `#[server(Name, "/api", "Url", "name")]`; both end up under `/api`).
- Return `Result<ApiResponse<T>, ServerFnError>`. Expected/business failures are `Ok(ApiResponse::Error("English message".to_string()))` — the client passes that string through `trl()`, so add it to the catalogue. `Err(ServerFnError)` is for real failures (DB down etc.).
- A function used with `<DataForm>` / `<QuestionDialog>` must return `ApiResponse<()>` and take the form entity as a single struct argument; its name is the prefix of the form field names (`company: Company` ↔ `name="company[name]"`).
- For `<DataForm>`, implement `ForValidation` for the generated struct (PascalCase of the fn name) right below the function:
```rust
impl ForValidation for UpdateCompany {
fn entity(&self) -> &dyn Validate { &self.company }
}
```
That only validates in the browser. Anything that matters must be checked again inside the server fn.
## Authorization
- Every server fn that reads or changes non-public data starts with `perm_check!(is_logged_in)` or `perm_check!(is_admin)`; use `user_check!(login)` when a non-admin may only touch their own record. The macros return early with 403 and `ApiResponse::Error`, so they only work in functions returning `Result<ApiResponse<_>, _>`.
- The auth middleware protects only `/admin*` page requests. `/api/*` is not covered by it — a server fn without `perm_check!` is public. Only the public booking form endpoints should be.
- Plain Actix handlers (like `appearance::upload`) must check the session themselves.
- The logged-in `User` is stored in the cookie session under `"user"`; read it with `logged_in_user()`.
## Database access
- Get the pool with `get_pool().await?` (and the mailer with `get_mailing().await?`); they come from `AppData` in Actix app data.
- Runtime-checked sqlx only: `query` / `query_as::<_, T>` with `$n` binds. No `query!` macros (there is no `DATABASE_URL`/offline data at build time).
- Always bind values; never format user input into SQL.
- Multi-statement writes go through a transaction (`pool.begin()`, helpers take `&mut Transaction<'_, Postgres>`), as in `create_reservation`.
- The users table is `"user"` — always quoted.
## Data types (`data.rs`)
- Types crossing the server/client boundary live in `src/backend/data.rs` and must compile for WASM: sqlx derives only as `#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]` / `sqlx::Type`.
- `id` fields are private with an `id()` getter; other fields are mostly `pub`.
- Validation uses `validator` 0.16 derive attributes; every `message = "..."` is an English translation key.
- Rust enums mapped to Postgres enums (`SlotType`, `ReservationState`, `MessageType`) must keep variant names identical to the SQL enum labels.
## Errors, logging, mail
- Server-side helpers return `AppError`; add a `From` impl in `src/error.rs` for new error sources (ssr-only ones behind `#[cfg(feature = "ssr")]`). The `From` impls already log the error.
- Log with the `log` macros (`info!`, `warn!`, `error!`), not `println!`.
- Send mail through `Mailer::send_mail` (queued to a background sender thread), never directly from a request. Templates are `message` rows keyed by `MessageType` with `#date#` and `#summary#` placeholders.
- A new `[section]`/key in `config.toml` needs the struct in `src/config.rs` and an entry in `config_sample.toml`.