4.1 KiB
paths
| paths | ||||
|---|---|---|---|---|
|
Backend rules
Server functions
-
One file per domain in
src/backend/. Server-only helpers and their imports go in thecfg_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 localusestatements. -
Use plain
#[server]for new functions (a few older ones use#[server(name = Name, prefix = "/api", endpoint = "name")]; both end up under/api). The positional form with"Url"no longer compiles. Server functions are registered byleptos_routesinsrc/main.rs; there is no separate/apiroute. -
The current request is
use_context::<leptos_actix::Request>()(it derefs toHttpRequest); extractors go throughleptos_actix::extract().await?. -
Return
Result<ApiResponse<T>, ServerFnError>. Expected/business failures areOk(ApiResponse::Error("English message".to_string()))— the client passes that string throughtrl(), so add it to the catalogue.Err(ServerFnError)is for real failures (DB down etc.). -
A function used with
<DataForm>/<QuestionDialog>must returnApiResponse<()>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>, implementForValidationfor the generated struct (PascalCase of the fn name) right below the function: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)orperm_check!(is_admin); useuser_check!(login)when a non-admin may only touch their own record. The macros return early with 403 andApiResponse::Error, so they only work in functions returningResult<ApiResponse<_>, _>. - The auth middleware protects only
/admin*page requests./api/*is not covered by it — a server fn withoutperm_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
Useris stored in the cookie session under"user"; read it withlogged_in_user().
Database access
- Get the pool with
get_pool().await?(and the mailer withget_mailing().await?); they come fromAppDatain Actix app data. - Runtime-checked sqlx only:
query/query_as::<_, T>with$nbinds. Noquery!macros (there is noDATABASE_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 increate_reservation. - The users table is
"user"— always quoted.
Data types (data.rs)
- Types crossing the server/client boundary live in
src/backend/data.rsand must compile for WASM: sqlx derives only as#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]/sqlx::Type. idfields are private with anid()getter; other fields are mostlypub.- Validation uses
validator0.16 derive attributes; everymessage = "..."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 aFromimpl insrc/error.rsfor new error sources (ssr-only ones behind#[cfg(feature = "ssr")]). TheFromimpls already log the error. - Log with the
logmacros (info!,warn!,error!), notprintln!. - Send mail through
Mailer::send_mail(queued to a background sender thread), never directly from a request. Templates aremessagerows keyed byMessageTypewith#date#and#summary#placeholders. - A new
[section]/key inconfig.tomlneeds the struct insrc/config.rsand an entry inconfig_sample.toml.