Improved error messages. Added README.md

main
Josef Rokos 2 months ago
parent a5cfc96814
commit 5d80c4951e

1
.gitignore vendored

@ -18,3 +18,4 @@ playwright/.cache/
/.settings/ /.settings/
/.vscode/ /.vscode/
/config.toml /config.toml
/html/

1264
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -20,7 +20,7 @@ server_fn = { version = "0.6.13", features = ["multipart"] }
leptos_router = { version = "0.6.13" } leptos_router = { version = "0.6.13" }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1.0.117" serde_json = "1.0.117"
wasm-bindgen = "=0.2.93" wasm-bindgen = "=0.2.100"
web-sys = { version = "0.3.69", features = ["Navigator"] } web-sys = { version = "0.3.69", features = ["Navigator"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
chrono = { version = "0.4.38", features = ["serde"]} chrono = { version = "0.4.38", features = ["serde"]}
@ -35,7 +35,7 @@ toml = "0.8.12"
log = "0.4.21" log = "0.4.21"
env_logger = "0.11.3" env_logger = "0.11.3"
getopts = "0.2.21" getopts = "0.2.21"
leptos-use = "0.12.0" leptos-use = "0.13.13"
lettre = {version = "0.11.6", features = ["tokio1-native-tls", "smtp-transport", "file-transport"], optional = true} lettre = {version = "0.11.6", features = ["tokio1-native-tls", "smtp-transport", "file-transport"], optional = true}
leptos-captcha = "0.2.0" leptos-captcha = "0.2.0"
charts-rs = { version = "0.3.5", optional = true} charts-rs = { version = "0.3.5", optional = true}

@ -0,0 +1,46 @@
<picture>
<source srcset="https://demo.rezervovator.cz/rezervovator_l.svg">
<img src="https://demo.rezervovator.cz/rezervovator_l.svg" alt="Rezervovator Logo">
</picture>
# Rezervovator
Simple application for booking sport or service facilities.
## Building project
You will need cargo-leptos tool:
`cargo install cargo-leptos`
## Running project
`cargo leptos watch`
## Executing a Server on a Remote Machine Without the Toolchain
After running a `cargo leptos build --release` the minimum files needed are:
1. The server binary located in `target/server/release`
2. The `site` directory and all files within located in `target/site`
Copy these files to your remote server. The directory structure should be:
```text
leptos_start
site/
```
Set the following environment variables (updating for your project as needed):
```sh
export LEPTOS_OUTPUT_NAME="leptos_start"
export LEPTOS_SITE_ROOT="site"
export LEPTOS_SITE_PKG_DIR="pkg"
export LEPTOS_SITE_ADDR="127.0.0.1:3000"
export LEPTOS_RELOAD_PORT="3001"
```
Finally, run the server binary.
## Notes about SSG and Trunk:
Although it is not recommended, you can also run your project without server integration using the feature `csr` and `trunk serve`:
`trunk serve --open --features csr`
This may be useful for integrating external tools which require a static site, e.g. `tauri`.

@ -68,7 +68,7 @@ fn app_footer() -> impl IntoView {
view! { view! {
<footer class="content-footer footer bg-footer-theme" style={move || if loc.pathname.get().starts_with("/admin") {"display: none;"} else {"display: block;"}}> <footer class="content-footer footer bg-footer-theme" style={move || if loc.pathname.get().starts_with("/admin") {"display: none;"} else {"display: block;"}}>
<div class="mb-2 mb-md-0" > <div class="mb-2 mb-md-0" align="center">
<a href="https://rezervovator.cz" target="_blank"><img src="/rezervovator_l.svg" width="110"/></a> {format!(" v {}", env!("CARGO_PKG_VERSION"))} <a href="https://rezervovator.cz" target="_blank"><img src="/rezervovator_l.svg" width="110"/></a> {format!(" v {}", env!("CARGO_PKG_VERSION"))}
</div> </div>
</footer> </footer>

@ -105,9 +105,10 @@ cfg_if! { if #[cfg(feature = "ssr")] {
pub async fn login(username: String, password: String) -> Result<ApiResponse<()>, ServerFnError> { pub async fn login(username: String, password: String) -> Result<ApiResponse<()>, ServerFnError> {
use actix_session::*; use actix_session::*;
use leptos_actix::extract; use leptos_actix::extract;
use actix_web::http::StatusCode; //use actix_web::http::StatusCode;
use leptos_actix::ResponseOptions; //use leptos_actix::ResponseOptions;
use crate::backend::get_pool; use crate::backend::get_pool;
use crate::locales::trl;
let pool = get_pool().await?; let pool = get_pool().await?;
let user = user_from_login(&pool, &username).await.unwrap_or(User::default()); let user = user_from_login(&pool, &username).await.unwrap_or(User::default());
@ -123,10 +124,10 @@ pub async fn login(username: String, password: String) -> Result<ApiResponse<()>
} }
warn!("Login failed for user {}", username); warn!("Login failed for user {}", username);
let response = expect_context::<ResponseOptions>(); //let response = expect_context::<ResponseOptions>();
response.set_status(StatusCode::UNAUTHORIZED); //response.set_status(StatusCode::UNAUTHORIZED);
return Ok(ApiResponse::Error("Bad username or password".to_string())) Ok(ApiResponse::Error(trl("Bad username or password")()))
} }
#[server] #[server]
@ -173,15 +174,16 @@ pub async fn get_users() -> Result<ApiResponse<Vec<User>>, ServerFnError> {
pub async fn update_profile(user: UserProfile) -> Result<ApiResponse<()>, ServerFnError> { pub async fn update_profile(user: UserProfile) -> Result<ApiResponse<()>, ServerFnError> {
use crate::user_check; use crate::user_check;
use crate::backend::get_pool; use crate::backend::get_pool;
use crate::locales::trl;
user_check!(user.login()); user_check!(user.login());
let usr = logged_in_user().await.unwrap_or(User::default()); let usr = logged_in_user().await.unwrap_or(User::default());
if !usr.admin && user.admin() { if !usr.admin && user.admin() {
let response = expect_context::<ResponseOptions>(); //let response = expect_context::<ResponseOptions>();
response.set_status(StatusCode::FORBIDDEN); //response.set_status(StatusCode::FORBIDDEN);
return Ok(ApiResponse::Error("You can't escalate your privileges".to_string())) return Ok(ApiResponse::Error(trl("You can't escalate your privileges")()))
} }
let pool = get_pool().await?; let pool = get_pool().await?;
@ -213,6 +215,7 @@ impl ForValidation for UpdateProfile {
pub async fn change_pwd(new_pw: PwdChange) -> Result<ApiResponse<()>, ServerFnError> { pub async fn change_pwd(new_pw: PwdChange) -> Result<ApiResponse<()>, ServerFnError> {
use crate::user_check; use crate::user_check;
use crate::backend::get_pool; use crate::backend::get_pool;
use crate::locales::trl;
user_check!(new_pw.login()); user_check!(new_pw.login());
@ -222,10 +225,10 @@ pub async fn change_pwd(new_pw: PwdChange) -> Result<ApiResponse<()>, ServerFnEr
if (!user.admin || user.login == new_pw.login()) if (!user.admin || user.login == new_pw.login())
&& !pwhash::bcrypt::verify(new_pw.old_password(), &usr.password) { && !pwhash::bcrypt::verify(new_pw.old_password(), &usr.password) {
let response = expect_context::<ResponseOptions>(); //let response = expect_context::<ResponseOptions>();
response.set_status(StatusCode::UNAUTHORIZED); //response.set_status(StatusCode::UNAUTHORIZED);
return Ok(ApiResponse::Error("Invalid old password".to_string())) return Ok(ApiResponse::Error(trl("Invalid old password")()))
} }
sqlx::query(r#"UPDATE "user" SET password = $1 WHERE login = $2"#) sqlx::query(r#"UPDATE "user" SET password = $1 WHERE login = $2"#)
@ -249,6 +252,7 @@ impl ForValidation for ChangePwd {
pub async fn create_user(user: UserProfile) -> Result<ApiResponse<()>, ServerFnError> { pub async fn create_user(user: UserProfile) -> Result<ApiResponse<()>, ServerFnError> {
use crate::perm_check; use crate::perm_check;
use crate::backend::get_pool; use crate::backend::get_pool;
use crate::locales::trl;
perm_check!(is_admin); perm_check!(is_admin);
@ -259,10 +263,10 @@ pub async fn create_user(user: UserProfile) -> Result<ApiResponse<()>, ServerFnE
.await?; .await?;
if count.0 != 0 { if count.0 != 0 {
let response = expect_context::<ResponseOptions>(); //let response = expect_context::<ResponseOptions>();
response.set_status(StatusCode::CONFLICT); //response.set_status(StatusCode::CONFLICT);
return Ok(ApiResponse::Error("Username already exists".to_string())); return Ok(ApiResponse::Error(trl("Username already exists")()));
} }
let usr_pw = user.password().clone(); let usr_pw = user.password().clone();
@ -292,15 +296,16 @@ impl ForValidation for CreateUser {
pub async fn delete_user(id: i32) -> Result<ApiResponse<()>, ServerFnError> { pub async fn delete_user(id: i32) -> Result<ApiResponse<()>, ServerFnError> {
use crate::perm_check; use crate::perm_check;
use crate::backend::get_pool; use crate::backend::get_pool;
use crate::locales::trl;
perm_check!(is_admin); perm_check!(is_admin);
let user = logged_in_user().await.unwrap_or_default(); let user = logged_in_user().await.unwrap_or_default();
if user.id() == id { if user.id() == id {
let response = expect_context::<ResponseOptions>(); //let response = expect_context::<ResponseOptions>();
response.set_status(StatusCode::NOT_ACCEPTABLE); //response.set_status(StatusCode::NOT_ACCEPTABLE);
return Ok(ApiResponse::Error("You can't delete yourself".to_string())) return Ok(ApiResponse::Error(trl("You can't delete yourself")()))
} }
sqlx::query(r#"DELETE FROM "user" WHERE id=$1"#) sqlx::query(r#"DELETE FROM "user" WHERE id=$1"#)
@ -317,5 +322,9 @@ pub async fn delete_user(id: i32) -> Result<ApiResponse<()>, ServerFnError> {
pub async fn get_pow() -> Result<String, ServerFnError> { pub async fn get_pow() -> Result<String, ServerFnError> {
use leptos_captcha::spow::pow::Pow; use leptos_captcha::spow::pow::Pow;
Ok(Pow::new(10)?.to_string()) if !cfg!(debug_assertions) {
Ok(Pow::with_difficulty(10, 10)?.to_string())
} else {
Ok(Pow::new(10)?.to_string())
}
} }

@ -13,11 +13,12 @@ fn about(opener: DialogOpener) -> impl IntoView {
<ModalBody> <ModalBody>
<img src="/rezervovator_l.svg" width="180"/> <br /><br/> <img src="/rezervovator_l.svg" width="180"/> <br /><br/>
<p> <p>
{trl("Online booking application for sports facilities and service providers.")}<br/><br/> {trl("Online booking application for sports facilities and service providers.")}<br/>
{format!(" v {}", env!("CARGO_PKG_VERSION"))}<br/><br/>
<div align="center"> <div align="center">
<a href="https://www.rust-lang.org" target="_blank"><img src="/rust.png" height="40"/></a>" " <a href="https://www.rust-lang.org" target="_blank"><img src="/rust.png" height="40"/></a>" "
<a href="https://leptos.dev" target="_blank"><img src="/Leptos_logo.png" height="40"/></a> <br/><br/> <a href="https://leptos.dev" target="_blank"><img src="/Leptos_logo.png" height="40"/></a> <br/><br/>
"(c) 2023 - 2024" "(c) 2023 - 2025"
</div> </div>
</p> </p>
</ModalBody> </ModalBody>

@ -161,7 +161,13 @@ lazy_static! {
("Closing days: ", "Zavírací dny: "), ("Closing days: ", "Zavírací dny: "),
("Closing days", "Zavírací dny"), ("Closing days", "Zavírací dny"),
("From", "Od"), ("From", "Od"),
("To", "do") ("To", "do"),
("Delete closing days", "Smazat zavírací dny"),
("Are you sure you want to delete closing days?", "Opravdu chcete smazat zavírací dny?"),
("Bad username or password", "Špatné uživatelské jméno nebo heslo"),
("You can't escalate your privileges", "Nemůžete povýšit práva sami sobě"),
("Username already exists", "Uživatel již existuje"),
("You can't delete yourself", "Nemůžete smazat sami sebe")
])), ])),
("sk", HashMap::from( [ ("sk", HashMap::from( [
("Dashboard", "Prehlad"), ("Dashboard", "Prehlad"),

Loading…
Cancel
Save