refactor: use rinja instead of askama

This commit is contained in:
Max Hohlfeld 2024-12-16 13:50:12 +01:00
parent 187f13a938
commit 4d04069f48
49 changed files with 405 additions and 345 deletions

112
Cargo.lock generated
View File

@ -379,60 +379,6 @@ dependencies = [
"password-hash", "password-hash",
] ]
[[package]]
name = "askama"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28"
dependencies = [
"askama_derive",
"askama_escape",
"humansize",
"num-traits",
"percent-encoding",
]
[[package]]
name = "askama_actix"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4b0dd17cfe203b00ba3853a89fba459ecf24c759b738b244133330607c78e55"
dependencies = [
"actix-web",
"askama",
]
[[package]]
name = "askama_derive"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83"
dependencies = [
"askama_parser",
"basic-toml",
"mime",
"mime_guess",
"proc-macro2",
"quote",
"serde",
"syn",
]
[[package]]
name = "askama_escape"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
[[package]]
name = "askama_parser"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0"
dependencies = [
"nom",
]
[[package]] [[package]]
name = "async-channel" name = "async-channel"
version = "1.9.0" version = "1.9.0"
@ -738,8 +684,6 @@ dependencies = [
"actix-web-static-files", "actix-web-static-files",
"anyhow", "anyhow",
"argon2", "argon2",
"askama",
"askama_actix",
"async-trait", "async-trait",
"brass-config", "brass-config",
"brass-macros", "brass-macros",
@ -754,6 +698,7 @@ dependencies = [
"quick-xml", "quick-xml",
"rand", "rand",
"regex", "regex",
"rinja",
"serde", "serde",
"serde_json", "serde_json",
"sqlx", "sqlx",
@ -818,9 +763,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.3" version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@ -2568,6 +2513,47 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "rinja"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dc4940d00595430b3d7d5a01f6222b5e5b51395d1120bdb28d854bb8abb17a5"
dependencies = [
"humansize",
"itoa",
"percent-encoding",
"rinja_derive",
]
[[package]]
name = "rinja_derive"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d9ed0146aef6e2825f1b1515f074510549efba38d71f4554eec32eb36ba18b"
dependencies = [
"basic-toml",
"memchr",
"mime",
"mime_guess",
"proc-macro2",
"quote",
"rinja_parser",
"rustc-hash",
"serde",
"syn",
]
[[package]]
name = "rinja_parser"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93f9a866e2e00a7a1fb27e46e9e324a6f7c0e7edc4543cae1d38f4e4a100c610"
dependencies = [
"memchr",
"nom",
"serde",
]
[[package]] [[package]]
name = "rsa" name = "rsa"
version = "0.9.7" version = "0.9.7"
@ -2594,6 +2580,12 @@ version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc-hash"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.1" version = "0.4.1"
@ -2716,9 +2708,9 @@ dependencies = [
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.23" version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
[[package]] [[package]]
name = "serde" name = "serde"

View File

@ -10,7 +10,8 @@ publish = false
[dependencies] [dependencies]
sqlx = { version = "^0.8", features = ["runtime-async-std-rustls", "postgres", "chrono"] } sqlx = { version = "^0.8", features = ["runtime-async-std-rustls", "postgres", "chrono"] }
actix-web = { version = "4" } actix-web = { version = "4" }
askama = { version = "0.12.0", features = ["with-actix-web"] } # askama = { version = "0.13.0", git = "https://github.com/rinja-rs/askama", branch = "main", features = ["with-actix-web"] }
# askama_actix = { git = "https://github.com/rinja-rs/askama", branch = "main" }
serde = { version = "1.0.164", features = ["derive"] } serde = { version = "1.0.164", features = ["derive"] }
argon2 = { version = "0.5.0", features = [ "std"]} argon2 = { version = "0.5.0", features = [ "std"]}
anyhow = "1.0.71" anyhow = "1.0.71"
@ -19,7 +20,6 @@ actix-session = { version = "0.10.1", features = ["cookie-session"] }
actix-identity = "0.8.0" actix-identity = "0.8.0"
chrono = { version = "0.4.33", features = ["serde", "now"] } chrono = { version = "0.4.33", features = ["serde", "now"] }
actix-files = "0.6.5" actix-files = "0.6.5"
askama_actix = "0.14.0"
futures-util = "0.3.30" futures-util = "0.3.30"
serde_json = "1.0.114" serde_json = "1.0.114"
pico-args = "0.5.0" pico-args = "0.5.0"
@ -36,6 +36,7 @@ regex = "1.11.1"
brass-macros = { path = "../macros" } brass-macros = { path = "../macros" }
brass-config = { path = "../config" } brass-config = { path = "../config" }
actix-http = "3.9.0" actix-http = "3.9.0"
rinja = "0.3.5"
[build-dependencies] [build-dependencies]
built = "0.7.4" built = "0.7.4"
@ -44,8 +45,5 @@ static-files = "0.2.1"
[dev-dependencies] [dev-dependencies]
insta = "1.41.1" insta = "1.41.1"
# [dev-dependencies]
# brass-web = { path = "." }
# [profile.dev.package.askama_derive] # [profile.dev.package.askama_derive]
# opt-level = 3 # opt-level = 3

View File

@ -0,0 +1,56 @@
---
source: web/src/endpoints/area/get_new.rs
expression: body
snapshot_kind: text
---
<section class="section">
<div class="container">
<form method="post" action="/area/new">
<h1 class="title">Neuen Bereich anlegen</h1>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Name</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" type="text" name="name" required placeholder="Leipzig Ost" />
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label"></div>
<div class="field-body">
<div class="field is-grouped">
<div class="control">
<button class="button is-success">
<svg class="icon">
<use href="/static/feather-sprite.svg#check-circle" />
</svg>
<span>
Erstellen
</span>
</button>
</div>
<div class="control">
<a class="button is-link is-light" hx-boost="true" href="/locations">
<svg class="icon">
<use href="/static/feather-sprite.svg#arrow-left" />
</svg>
<span>Zurück</span>
</a>
</div>
</div>
</div>
</div>
</form>
</div>
</section>

View File

@ -1,5 +1,5 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse; use rinja::Template;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
@ -24,7 +24,7 @@ async fn get(
area: Some(area_in_db), area: Some(area_in_db),
}; };
return Ok(template.to_response()); return Ok(HttpResponse::Ok().body(template.render()?))
} else { } else {
return Ok(HttpResponse::NotFound().finish()); return Ok(HttpResponse::NotFound().finish());
} }

View File

@ -1,23 +1,23 @@
use actix_web::{body::MessageBody, test::read_body, web, HttpResponse, Responder};
use askama_actix::TemplateToResponse;
use brass_macros::db_test;
use insta::assert_snapshot;
use crate::{ use crate::{
endpoints::area::NewOrEditAreaTemplate, endpoints::area::NewOrEditAreaTemplate,
models::{Role, User}, models::{Role, User},
utils::test_helper::DbTestContext, utils::ApplicationError,
}; };
use actix_web::{web, HttpResponse, Responder};
use brass_macros::db_test;
use rinja::Template;
#[cfg(test)] #[cfg(test)]
use crate::utils::test_helper::{test_get, RequestConfig}; use crate::utils::test_helper::{
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig,
};
#[cfg(test)] #[cfg(test)]
use actix_http::StatusCode; use actix_http::StatusCode;
#[actix_web::get("/area/new")] #[actix_web::get("/area/new")]
async fn get(user: web::ReqData<User>) -> impl Responder { async fn get(user: web::ReqData<User>) -> Result<impl Responder, ApplicationError> {
if user.role != Role::Admin { if user.role != Role::Admin {
return HttpResponse::Unauthorized().finish(); return Err(ApplicationError::Unauthorized);
} }
let template = NewOrEditAreaTemplate { let template = NewOrEditAreaTemplate {
@ -25,7 +25,7 @@ async fn get(user: web::ReqData<User>) -> impl Responder {
area: None, area: None,
}; };
template.to_response() Ok(HttpResponse::Ok().body(template.render()?))
} }
#[db_test] #[db_test]
@ -41,6 +41,6 @@ async fn produces_template(context: &DbTestContext) {
assert_eq!(StatusCode::OK, response.status()); assert_eq!(StatusCode::OK, response.status());
let body = String::from_utf8(read_body(response).await.to_vec()).unwrap(); let body = read_body(response).await;
assert_snapshot!(body); assert_snapshot!(body);
} }

View File

@ -4,13 +4,14 @@ pub mod get_edit;
pub mod post_edit; pub mod post_edit;
pub mod delete; pub mod delete;
use askama::Template; use rinja::Template;
use serde::Deserialize; use serde::Deserialize;
use crate::models::{Area, Role, User}; use crate::models::{Area, Role, User};
#[derive(Template)] #[derive(Template)]
#[template(path = "area/new_or_edit.html")] #[cfg_attr(not(test), template(path = "area/new_or_edit.html"))]
#[cfg_attr(test, template(path = "area/new_or_edit.html", block = "content"))]
struct NewOrEditAreaTemplate { struct NewOrEditAreaTemplate {
user: User, user: User,
area: Option<Area>, area: Option<Area>,

View File

@ -1,5 +1,5 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse; use rinja::Template;
use serde::Deserialize; use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
@ -61,5 +61,5 @@ pub async fn delete(
further_wachhabender_required, further_wachhabender_required,
}; };
Ok(template.to_response()) Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,4 +1,4 @@
use askama::Template; use rinja::Template;
use crate::{ use crate::{
filters, filters,

View File

@ -1,7 +1,5 @@
use std::ops::AddAssign;
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse; use rinja::Template;
use serde::Deserialize; use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
@ -108,5 +106,5 @@ pub async fn post(
further_wachhabender_required, further_wachhabender_required,
}; };
Ok(template.to_response()) Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,10 +1,11 @@
use actix_web::{web, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse;
use chrono::NaiveDate; use chrono::NaiveDate;
use rinja::Template;
use serde::Deserialize; use serde::Deserialize;
use crate::endpoints::availability::NewOrEditAvailabilityTemplate; use crate::endpoints::availability::NewOrEditAvailabilityTemplate;
use crate::models::User; use crate::models::User;
use crate::utils::ApplicationError;
#[derive(Deserialize)] #[derive(Deserialize)]
struct AvailabilityNewQuery { struct AvailabilityNewQuery {
@ -17,7 +18,7 @@ struct AvailabilityNewQuery {
pub async fn get( pub async fn get(
user: web::ReqData<User>, user: web::ReqData<User>,
query: web::Query<AvailabilityNewQuery>, query: web::Query<AvailabilityNewQuery>,
) -> impl Responder { ) -> Result<impl Responder, ApplicationError> {
let template = NewOrEditAvailabilityTemplate { let template = NewOrEditAvailabilityTemplate {
user: user.into_inner(), user: user.into_inner(),
date: query.date, date: query.date,
@ -28,5 +29,5 @@ pub async fn get(
comment: None, comment: None,
}; };
template.to_response() Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,8 +1,7 @@
use crate::filters; use crate::{filters, utils::ApplicationError};
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama::Template;
use askama_actix::TemplateToResponse;
use chrono::{NaiveDate, Utc}; use chrono::{NaiveDate, Utc};
use rinja::Template;
use serde::Deserialize; use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
@ -30,18 +29,18 @@ async fn get(
user: web::ReqData<User>, user: web::ReqData<User>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
query: web::Query<CalendarQuery>, query: web::Query<CalendarQuery>,
) -> impl Responder { ) -> Result<impl Responder, ApplicationError> {
let date = match query.date { let date = match query.date {
Some(given_date) => given_date, Some(given_date) => given_date,
None => Utc::now().date_naive(), None => Utc::now().date_naive(),
}; };
let areas = Area::read_all(pool.get_ref()).await.unwrap(); let areas = Area::read_all(pool.get_ref()).await?;
let selected_area = match query.area { let selected_area = match query.area {
Some(id) => { Some(id) => {
if !areas.iter().any(|a| a.id == id) { if !areas.iter().any(|a| a.id == id) {
return HttpResponse::BadRequest().finish(); return Ok(HttpResponse::BadRequest().finish());
} }
Some(id) Some(id)
@ -54,16 +53,14 @@ async fn get(
date, date,
query.area.unwrap_or(user.area_id), query.area.unwrap_or(user.area_id),
) )
.await .await?;
.unwrap();
let availabillities = Availabillity::read_by_date_and_area_including_user( let availabillities = Availabillity::read_by_date_and_area_including_user(
pool.get_ref(), pool.get_ref(),
date, date,
query.area.unwrap_or(user.area_id), query.area.unwrap_or(user.area_id),
) )
.await .await?;
.unwrap();
let template = CalendarTemplate { let template = CalendarTemplate {
user: user.into_inner(), user: user.into_inner(),
@ -74,5 +71,5 @@ async fn get(
availabillities, availabillities,
}; };
template.to_response() Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,5 +1,5 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse; use rinja::Template;
use serde::Deserialize; use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
@ -50,5 +50,5 @@ pub async fn get(
comment: availabillity.comment.as_deref(), comment: availabillity.comment.as_deref(),
}; };
Ok(template.to_response()) Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,4 +1,4 @@
use askama::Template; use rinja::Template;
use chrono::NaiveDate; use chrono::NaiveDate;
use crate::filters; use crate::filters;

View File

@ -1,7 +1,6 @@
use actix_web::{web, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama::Template;
use askama_actix::TemplateToResponse;
use chrono::NaiveDate; use chrono::NaiveDate;
use rinja::Template;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
@ -40,5 +39,5 @@ pub async fn get(
locations, locations,
}; };
Ok(template.to_response()) Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,6 +1,5 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama::Template; use rinja::Template;
use askama_actix::TemplateToResponse;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
@ -61,5 +60,5 @@ pub async fn get(
further_wachhabender_required, further_wachhabender_required,
}; };
Ok(template.to_response()) Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,9 +1,8 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama::Template; use rinja::Template;
use askama_actix::TemplateToResponse;
use sqlx::PgPool; use sqlx::PgPool;
use crate::models::{Area, User, Role}; use crate::{models::{Area, Role, User}, utils::ApplicationError};
#[derive(Template)] #[derive(Template)]
#[template(path = "export/availability.html")] #[template(path = "export/availability.html")]
@ -16,13 +15,13 @@ struct AvailabilityExportTemplate {
pub async fn get( pub async fn get(
user: web::ReqData<User>, user: web::ReqData<User>,
pool: web::Data<PgPool> pool: web::Data<PgPool>
) -> impl Responder { ) -> Result<impl Responder, ApplicationError> {
if user.role != Role::Admin && user.role != Role::AreaManager { if user.role != Role::Admin && user.role != Role::AreaManager {
return HttpResponse::Unauthorized().finish(); return Err(ApplicationError::Unauthorized);
} }
let areas = if user.role == Role::Admin { let areas = if user.role == Role::Admin {
Some(Area::read_all(pool.get_ref()).await.unwrap()) Some(Area::read_all(pool.get_ref()).await?)
} else { } else {
None None
}; };
@ -32,5 +31,5 @@ pub async fn get(
areas areas
}; };
return template.to_response(); Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -40,9 +40,10 @@ pub async fn get(
user: Identity, user: Identity,
query: web::Query<ExportQuery>, query: web::Query<ExportQuery>,
) -> impl Responder { ) -> impl Responder {
// TODO: rerwrite
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap()) let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap())
.await .await
.unwrap(); .unwrap().unwrap();
if current_user.role != Role::Admin && current_user.role != Role::AreaManager { if current_user.role != Role::Admin && current_user.role != Role::AreaManager {
return HttpResponse::Unauthorized().finish(); return HttpResponse::Unauthorized().finish();

View File

@ -1,14 +1,15 @@
use actix_web::Responder; use actix_web::{HttpResponse, Responder};
use askama::Template; use rinja::Template;
use askama_actix::TemplateToResponse;
use crate::utils::ApplicationError;
#[derive(Template)] #[derive(Template)]
#[template(path = "imprint.html")] #[template(path = "imprint.html")]
struct ImprintTemplate {} struct ImprintTemplate {}
#[actix_web::get("/imprint")] #[actix_web::get("/imprint")]
pub async fn get_imprint() -> impl Responder { pub async fn get_imprint() -> Result<impl Responder, ApplicationError> {
let template = ImprintTemplate {}; let template = ImprintTemplate {};
template.to_response() Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,5 +1,5 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse; use rinja::Template;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
@ -38,5 +38,5 @@ pub async fn get(
location: Some(location), location: Some(location),
}; };
Ok(template.to_response()) Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,26 +1,33 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse; use rinja::Template;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{endpoints::location::LocationTemplate, models::{Area, Role, User}}; use crate::{
endpoints::location::LocationTemplate,
models::{Area, Role, User},
utils::ApplicationError,
};
#[actix_web::get("/locations/new")] #[actix_web::get("/locations/new")]
pub async fn get(user: web::ReqData<User>, pool: web::Data<PgPool>) -> impl Responder { pub async fn get(
if user.role == Role::AreaManager || user.role == Role::Admin { user: web::ReqData<User>,
pool: web::Data<PgPool>,
) -> Result<impl Responder, ApplicationError> {
if user.role != Role::AreaManager && user.role != Role::Admin {
return Err(ApplicationError::Unauthorized);
}
let mut areas = None; let mut areas = None;
if user.role == Role::Admin { if user.role == Role::Admin {
areas = Some(Area::read_all(pool.get_ref()).await.unwrap()); areas = Some(Area::read_all(pool.get_ref()).await?);
} }
let template = LocationTemplate { let template = LocationTemplate {
user: user.into_inner(), user: user.into_inner(),
areas, areas,
location: None location: None,
}; };
return template.to_response(); Ok(HttpResponse::Ok().body(template.render()?))
}
return HttpResponse::Unauthorized().finish();
} }

View File

@ -1,9 +1,11 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama::Template; use rinja::Template;
use askama_actix::TemplateToResponse;
use sqlx::PgPool; use sqlx::PgPool;
use crate::models::{Area, Location, Role, User}; use crate::{
models::{Area, Location, Role, User},
utils::ApplicationError,
};
#[derive(Template)] #[derive(Template)]
#[template(path = "location/overview.html")] #[template(path = "location/overview.html")]
@ -13,26 +15,29 @@ pub struct LocationsTemplate {
} }
#[actix_web::get("/locations")] #[actix_web::get("/locations")]
pub async fn get(user: web::ReqData<User>, pool: web::Data<PgPool>) -> impl Responder { pub async fn get(
if user.role == Role::AreaManager || user.role == Role::Admin { user: web::ReqData<User>,
pool: web::Data<PgPool>,
) -> Result<impl Responder, ApplicationError> {
if user.role != Role::AreaManager && user.role != Role::Admin {
return Err(ApplicationError::Unauthorized);
}
let mut locations; let mut locations;
let mut grouped_locations: Vec<(Area, Vec<Location>)>; let mut grouped_locations: Vec<(Area, Vec<Location>)>;
if user.role == Role::Admin { if user.role == Role::Admin {
locations = Location::read_all(pool.get_ref()).await.unwrap(); locations = Location::read_all(pool.get_ref()).await.unwrap();
grouped_locations = Area::read_all(pool.get_ref()) grouped_locations = Area::read_all(pool.get_ref())
.await .await?
.unwrap()
.into_iter() .into_iter()
.map(|a| (a, Vec::new())) .map(|a| (a, Vec::new()))
.collect(); .collect();
} else { } else {
locations = Location::read_by_area(pool.get_ref(), user.area_id) locations = Location::read_by_area(pool.get_ref(), user.area_id).await?;
.await
.unwrap();
let area = Area::read_by_id(pool.get_ref(), user.area_id) let area = Area::read_by_id(pool.get_ref(), user.area_id)
.await .await?
.unwrap().unwrap(); .unwrap();
grouped_locations = vec![(area, Vec::new())]; grouped_locations = vec![(area, Vec::new())];
} }
@ -50,8 +55,5 @@ pub async fn get(user: web::ReqData<User>, pool: web::Data<PgPool>) -> impl Resp
grouped_locations, grouped_locations,
}; };
return template.to_response(); Ok(HttpResponse::Ok().body(template.render()?))
}
return HttpResponse::Unauthorized().finish();
} }

View File

@ -1,4 +1,4 @@
use askama::Template; use rinja::Template;
use serde::Deserialize; use serde::Deserialize;
use crate::models::{Area, Location, Role, User}; use crate::models::{Area, Location, Role, User};

View File

@ -16,7 +16,9 @@ pub async fn delete(
return Err(ApplicationError::Unauthorized); return Err(ApplicationError::Unauthorized);
} }
let user_in_db = User::read_by_id(pool.get_ref(), path.id).await?; let Some(user_in_db) = User::read_by_id(pool.get_ref(), path.id).await? else {
return Ok(HttpResponse::NotFound().finish());
};
if user.role == Role::AreaManager && user.area_id != user_in_db.area_id { if user.role == Role::AreaManager && user.area_id != user_in_db.area_id {
return Err(ApplicationError::Unauthorized); return Err(ApplicationError::Unauthorized);

View File

@ -1,16 +1,15 @@
use actix_web::{web, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama::Template; use rinja::Template;
use askama_actix::TemplateToResponse;
use crate::models::User; use crate::{models::User, utils::ApplicationError};
#[derive(Template)] #[derive(Template)]
#[template(path = "user/profile_change_password.html")] #[template(path = "user/profile_change_password.html")]
struct ProfileChangePasswordTemplate {} struct ProfileChangePasswordTemplate {}
#[actix_web::get("/users/changepassword")] #[actix_web::get("/users/changepassword")]
pub async fn get(_user: web::ReqData<User>) -> impl Responder { pub async fn get(_user: web::ReqData<User>) -> Result<impl Responder, ApplicationError> {
let template = ProfileChangePasswordTemplate {}; let template = ProfileChangePasswordTemplate {};
template.to_response() Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,10 +1,11 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse; use rinja::Template;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
endpoints::{user::NewOrEditUserTemplate, IdPath}, endpoints::{user::NewOrEditUserTemplate, IdPath},
models::{Area, Role, User}, models::{Area, Role, User},
utils::ApplicationError,
}; };
#[actix_web::get("/users/edit/{id}")] #[actix_web::get("/users/edit/{id}")]
@ -12,18 +13,21 @@ pub async fn get_edit(
user: web::ReqData<User>, user: web::ReqData<User>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
path: web::Path<IdPath>, path: web::Path<IdPath>,
) -> impl Responder { ) -> Result<impl Responder, ApplicationError> {
let mut areas = None; let mut areas = None;
if user.role != Role::AreaManager && user.role != Role::Admin { if user.role != Role::AreaManager && user.role != Role::Admin {
return HttpResponse::Unauthorized().finish(); return Err(ApplicationError::Unauthorized);
} }
if user.role == Role::Admin { if user.role == Role::Admin {
areas = Some(Area::read_all(pool.get_ref()).await.unwrap()); areas = Some(Area::read_all(pool.get_ref()).await?);
} }
if let Ok(user_in_db) = User::read_by_id(pool.get_ref(), path.id).await { let Some(user_in_db) = User::read_by_id(pool.get_ref(), path.id).await? else {
return Ok(HttpResponse::NotFound().finish());
};
let template = NewOrEditUserTemplate { let template = NewOrEditUserTemplate {
user: user.into_inner(), user: user.into_inner(),
id: Some(user_in_db.id), id: Some(user_in_db.id),
@ -35,8 +39,5 @@ pub async fn get_edit(
area_id: Some(user_in_db.area_id), area_id: Some(user_in_db.area_id),
}; };
return template.to_response(); Ok(HttpResponse::Ok().body(template.render()?))
}
HttpResponse::BadRequest().body("Fehler beim Bearbeiten des Nutzers")
} }

View File

@ -1,21 +1,22 @@
use actix_identity::Identity; use actix_identity::Identity;
use actix_web::{http::header::LOCATION, HttpResponse, Responder}; use actix_web::{http::header::LOCATION, HttpResponse, Responder};
use askama::Template; use rinja::Template;
use askama_actix::TemplateToResponse;
use crate::utils::ApplicationError;
#[derive(Template)] #[derive(Template)]
#[template(path = "user/login.html")] #[template(path = "user/login.html")]
struct LoginTemplate {} struct LoginTemplate {}
#[actix_web::get("/login")] #[actix_web::get("/login")]
async fn get(user: Option<Identity>) -> impl Responder { async fn get(user: Option<Identity>) -> Result<impl Responder, ApplicationError> {
if let Some(_) = user { if let Some(_) = user {
return HttpResponse::Found() Ok(HttpResponse::Found()
.insert_header((LOCATION, "/")) .insert_header((LOCATION, "/"))
.finish(); .finish())
} else { } else {
let template = LoginTemplate {}; let template = LoginTemplate {};
return template.to_response(); Ok(HttpResponse::Ok().body(template.render()?))
} }
} }

View File

@ -1,18 +1,22 @@
use actix_web::{web, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse; use rinja::Template;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
endpoints::user::NewOrEditUserTemplate, endpoints::user::NewOrEditUserTemplate,
models::{Area, Role, User}, models::{Area, Role, User},
utils::ApplicationError,
}; };
#[actix_web::get("/users/new")] #[actix_web::get("/users/new")]
pub async fn get_new(user: web::ReqData<User>, pool: web::Data<PgPool>) -> impl Responder { pub async fn get_new(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
) -> Result<impl Responder, ApplicationError> {
let mut areas: Option<Vec<Area>> = None; let mut areas: Option<Vec<Area>> = None;
if user.role == Role::Admin { if user.role == Role::Admin {
areas = Some(Area::read_all(pool.get_ref()).await.unwrap()) areas = Some(Area::read_all(pool.get_ref()).await?)
} }
let template = NewOrEditUserTemplate { let template = NewOrEditUserTemplate {
@ -26,5 +30,5 @@ pub async fn get_new(user: web::ReqData<User>, pool: web::Data<PgPool>) -> impl
area_id: None, area_id: None,
}; };
template.to_response() Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,8 +1,10 @@
use crate::models::{Area, Role, User, Function}; use crate::{
use actix_identity::Identity; models::{Area, Function, Role, User},
utils::ApplicationError,
};
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama::Template; use rinja::Template;
use askama_actix::TemplateToResponse;
use sqlx::PgPool; use sqlx::PgPool;
#[derive(Template)] #[derive(Template)]
@ -14,36 +16,33 @@ pub struct UsersTemplate {
} }
#[actix_web::get("/users")] #[actix_web::get("/users")]
pub async fn get_overview(user: Identity, pool: web::Data<PgPool>) -> impl Responder { pub async fn get_overview(
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap()) user: web::ReqData<User>,
.await pool: web::Data<PgPool>,
.unwrap(); ) -> Result<impl Responder, ApplicationError> {
if user.role != Role::AreaManager && user.role != Role::Admin {
return Err(ApplicationError::Unauthorized);
}
if current_user.role == Role::AreaManager || current_user.role == Role::Admin {
let mut area = None; let mut area = None;
let users; let users;
if current_user.role == Role::AreaManager { if user.role == Role::AreaManager {
area = Some( area = Some(
Area::read_by_id(pool.get_ref(), current_user.area_id) Area::read_by_id(pool.get_ref(), user.area_id)
.await .await?
.unwrap().unwrap(), .unwrap(),
); );
users = User::read_all_by_area(pool.get_ref(), current_user.area_id) users = User::read_all_by_area(pool.get_ref(), user.area_id).await?;
.await
.unwrap();
} else { } else {
users = User::read_all_including_area(pool.get_ref()).await.unwrap(); users = User::read_all_including_area(pool.get_ref()).await?;
} }
let template = UsersTemplate { let template = UsersTemplate {
user: current_user, user: user.into_inner(),
area, area,
users, users,
}; };
return template.to_response(); Ok(HttpResponse::Ok().body(template.render()?))
}
return HttpResponse::BadRequest().body("Fehler beim Zugriff auf die Nutzerverwaltung!");
} }

View File

@ -1,11 +1,11 @@
use actix_web::{web, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama::Template; use rinja::Template;
use askama_actix::TemplateToResponse;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
filters, filters,
models::{Area, Role, User}, models::{Area, Role, User},
utils::ApplicationError,
}; };
#[derive(Template)] #[derive(Template)]
@ -15,15 +15,16 @@ struct ProfileTemplate {
} }
#[actix_web::get("/profile")] #[actix_web::get("/profile")]
pub async fn get(user: web::ReqData<User>, pool: web::Data<PgPool>) -> impl Responder { pub async fn get(
let area = Area::read_by_id(pool.get_ref(), user.area_id) user: web::ReqData<User>,
.await pool: web::Data<PgPool>,
.unwrap(); ) -> Result<impl Responder, ApplicationError> {
let area = Area::read_by_id(pool.get_ref(), user.area_id).await?;
let mut user = user.into_inner(); let mut user = user.into_inner();
user.area = area; user.area = area;
let template = ProfileTemplate { user }; let template = ProfileTemplate { user };
return template.to_response(); Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,6 +1,6 @@
use actix_identity::Identity; use actix_identity::Identity;
use actix_web::{get, http::header::LOCATION, web, HttpResponse, Responder}; use actix_web::{get, http::header::LOCATION, web, HttpResponse, Responder};
use askama_actix::TemplateToResponse; use rinja::Template;
use serde::Deserialize; use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
@ -25,7 +25,10 @@ pub async fn get(
.finish()); .finish());
} }
if let Some(_) = Registration::does_token_exist(pool.get_ref(), &query.token).await? { let Some(_) = Registration::does_token_exist(pool.get_ref(), &query.token).await? else {
return Ok(HttpResponse::NotFound().finish());
};
let template = ResetPasswordTemplate { let template = ResetPasswordTemplate {
token: &query.token, token: &query.token,
title: "Brass - Registrierung", title: "Brass - Registrierung",
@ -35,8 +38,5 @@ pub async fn get(
submit_button_label: "Registrieren", submit_button_label: "Registrieren",
}; };
return Ok(template.to_response()); Ok(HttpResponse::Ok().body(template.render()?))
}
Ok(HttpResponse::NotFound().finish())
} }

View File

@ -1,11 +1,10 @@
use actix_identity::Identity; use actix_identity::Identity;
use actix_web::{get, http::header::LOCATION, web, HttpResponse, Responder}; use actix_web::{get, http::header::LOCATION, web, HttpResponse, Responder};
use askama::Template; use rinja::Template;
use askama_actix::TemplateToResponse;
use serde::Deserialize; use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
use crate::models::PasswordReset; use crate::{models::PasswordReset, utils::ApplicationError};
use super::ResetPasswordTemplate; use super::ResetPasswordTemplate;
@ -23,13 +22,13 @@ pub async fn get(
user: Option<Identity>, user: Option<Identity>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
query: web::Query<TokenQuery>, query: web::Query<TokenQuery>,
) -> impl Responder { ) -> Result<impl Responder, ApplicationError> {
if let Some(_) = user { if let Some(_) = user {
return HttpResponse::Found() return Ok(HttpResponse::Found()
.insert_header((LOCATION, "/")) .insert_header((LOCATION, "/"))
.finish(); .finish());
} else if let Some(token) = &query.token { } else if let Some(token) = &query.token {
if let Ok(_) = PasswordReset::does_token_exist(pool.get_ref(), token).await { if let Some(_) = PasswordReset::does_token_exist(pool.get_ref(), token).await? {
let template = ResetPasswordTemplate { let template = ResetPasswordTemplate {
token, token,
title: "Brass - Passwort zurücksetzen", title: "Brass - Passwort zurücksetzen",
@ -39,11 +38,10 @@ pub async fn get(
submit_button_label: "Passwort zurücksetzen", submit_button_label: "Passwort zurücksetzen",
}; };
return template.to_response(); return Ok(HttpResponse::Ok().body(template.render()?))
} }
} }
let template = ForgotPasswordTemplate {}; let template = ForgotPasswordTemplate {};
Ok(HttpResponse::Ok().body(template.render()?))
return template.to_response();
} }

View File

@ -3,7 +3,7 @@ use crate::models::{Area, Role, Token, User};
use crate::utils::{password_help, ApplicationError}; use crate::utils::{password_help, ApplicationError};
use crate::{auth, filters}; use crate::{auth, filters};
use actix_web::HttpResponse; use actix_web::HttpResponse;
use askama::Template; use rinja::Template;
use sqlx::PgPool; use sqlx::PgPool;
use zxcvbn::{zxcvbn, Score}; use zxcvbn::{zxcvbn, Score};
@ -73,7 +73,8 @@ async fn handle_password_change_request(
return Ok(no_message); return Ok(no_message);
} }
let user = User::read_by_id(pool, user_id).await?; // TODO: make sure this unwrap is safe
let user = User::read_by_id(pool, user_id).await?.unwrap();
let mut split_names: Vec<&str> = user.name.as_str().split_whitespace().collect(); let mut split_names: Vec<&str> = user.name.as_str().split_whitespace().collect();
let mut user_inputs = vec![user.email.as_str()]; let mut user_inputs = vec![user.email.as_str()];
user_inputs.append(&mut split_names); user_inputs.append(&mut split_names);

View File

@ -24,15 +24,16 @@ pub async fn post_edit(
path: web::Path<IdPath>, path: web::Path<IdPath>,
form: web::Form<EditUserForm>, form: web::Form<EditUserForm>,
) -> impl Responder { ) -> impl Responder {
// TODO: rewrite
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap()) let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap())
.await .await
.unwrap(); .unwrap().unwrap();
if current_user.role != Role::AreaManager && current_user.role != Role::Admin { if current_user.role != Role::AreaManager && current_user.role != Role::Admin {
return HttpResponse::Unauthorized().finish(); return HttpResponse::Unauthorized().finish();
} }
if let Ok(user_in_db) = User::read_by_id(pool.get_ref(), path.id).await { if let Some(user_in_db) = User::read_by_id(pool.get_ref(), path.id).await.unwrap() {
if current_user.role == Role::AreaManager && current_user.area_id != user_in_db.area_id { if current_user.role == Role::AreaManager && current_user.area_id != user_in_db.area_id {
return HttpResponse::Unauthorized().finish(); return HttpResponse::Unauthorized().finish();
} }

View File

@ -48,7 +48,7 @@ pub async fn post_new(
.await?; .await?;
let registration = Registration::insert_new_for_user(pool.get_ref(), id).await?; let registration = Registration::insert_new_for_user(pool.get_ref(), id).await?;
let new_user = User::read_by_id(pool.get_ref(), id).await?; let new_user = User::read_by_id(pool.get_ref(), id).await?.unwrap();
let message = email::build_registration_message(&new_user, &registration.token)?; let message = email::build_registration_message(&new_user, &registration.token)?;
mailer.send(&message)?; mailer.send(&message)?;

View File

@ -19,12 +19,13 @@ pub async fn post(
path: web::Path<IdPath>, path: web::Path<IdPath>,
query: web::Query<ToggleQuery>, query: web::Query<ToggleQuery>,
) -> impl Responder { ) -> impl Responder {
// Todo: rewrite
if user.id != path.id && user.role != Role::Admin && user.role != Role::AreaManager { if user.id != path.id && user.role != Role::Admin && user.role != Role::AreaManager {
return HttpResponse::Unauthorized().finish(); return HttpResponse::Unauthorized().finish();
} }
let user = if user.id != path.id { let user = if user.id != path.id {
User::read_by_id(pool.get_ref(), path.id).await.unwrap() User::read_by_id(pool.get_ref(), path.id).await.unwrap().unwrap()
} else { } else {
user.into_inner() user.into_inner()
}; };

View File

@ -1,5 +1,5 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse; use rinja::Template;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
@ -27,5 +27,5 @@ pub async fn get(
vehicle: Some(vehicle), vehicle: Some(vehicle),
}; };
Ok(template.to_response()) Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,5 +1,5 @@
use actix_web::{web, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse; use rinja::Template;
use crate::{ use crate::{
endpoints::vehicle::VehicleNewOrEditTemplate, endpoints::vehicle::VehicleNewOrEditTemplate,
@ -18,5 +18,5 @@ pub async fn get(user: web::ReqData<User>) -> Result<impl Responder, Application
vehicle: None, vehicle: None,
}; };
Ok(template.to_response()) Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,6 +1,5 @@
use actix_web::{web, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama::Template; use rinja::Template;
use askama_actix::TemplateToResponse;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{models::{User, Vehicle, Role}, utils::ApplicationError}; use crate::{models::{User, Vehicle, Role}, utils::ApplicationError};
@ -25,5 +24,5 @@ pub async fn get(user: web::ReqData<User>, pool: web::Data<PgPool>) -> Result<im
vehicles vehicles
}; };
Ok(template.to_response()) Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,4 +1,4 @@
use askama::Template; use rinja::Template;
use serde::Deserialize; use serde::Deserialize;
use crate::models::{Role, User, Vehicle}; use crate::models::{Role, User, Vehicle};

View File

@ -1,6 +1,6 @@
use crate::models::Function; use crate::models::Function;
pub fn show_area_query(a: &Option<i32>, first: bool) -> ::askama::Result<String> { pub fn show_area_query(a: &Option<i32>, first: bool) -> rinja::Result<String> {
let char = if first { '?' } else { '&' }; let char = if first { '?' } else { '&' };
if let Some(a) = a { if let Some(a) = a {
@ -10,7 +10,7 @@ pub fn show_area_query(a: &Option<i32>, first: bool) -> ::askama::Result<String>
} }
} }
pub fn cond_show(show: &bool, text: &str) -> askama::Result<String> { pub fn cond_show(show: &bool, text: &str) -> rinja::Result<String> {
return if *show { return if *show {
Ok(String::from(text)) Ok(String::from(text))
} else { } else {
@ -18,7 +18,7 @@ pub fn cond_show(show: &bool, text: &str) -> askama::Result<String> {
}; };
} }
pub fn insert_value(option: &Option<String>) -> askama::Result<String> { pub fn insert_value(option: &Option<String>) -> rinja::Result<String> {
if let Some(val) = option { if let Some(val) = option {
let s = format!(r#"value="{val}""#); let s = format!(r#"value="{val}""#);
return Ok(s); return Ok(s);
@ -27,18 +27,18 @@ pub fn insert_value(option: &Option<String>) -> askama::Result<String> {
Ok(String::new()) Ok(String::new())
} }
pub fn is_some_and_eq<T>(option: &Option<T>, other: &T) -> askama::Result<bool> pub fn is_some_and_eq<T>(option: &Option<T>, other: &T) -> rinja::Result<bool>
where where
T: Eq, T: Eq,
{ {
Ok(option.as_ref().is_some_and(|x| x == other)) Ok(option.as_ref().is_some_and(|x| x == other))
} }
pub fn invert(b: &bool) -> askama::Result<bool> { pub fn invert(b: &bool) -> rinja::Result<bool> {
return Ok(!b); return Ok(!b);
} }
pub fn show_tree(f: &Function) -> askama::Result<String> { pub fn show_tree(f: &Function) -> rinja::Result<String> {
let mut tags = String::from(r#"<span class="tag is-info is-light">Posten</span>"#); let mut tags = String::from(r#"<span class="tag is-info is-light">Posten</span>"#);
if f == &Function::Fuehrungsassistent || f == &Function::Wachhabender { if f == &Function::Fuehrungsassistent || f == &Function::Wachhabender {

View File

@ -56,6 +56,7 @@ where
let user = User::read_by_id(pool.get_ref(), id.parse().unwrap()) let user = User::read_by_id(pool.get_ref(), id.parse().unwrap())
.await .await
.unwrap()
.unwrap(); .unwrap();
req.extensions_mut().insert::<User>(user); req.extensions_mut().insert::<User>(user);

View File

@ -76,7 +76,7 @@ impl User {
b b
} }
pub async fn read_by_id(pool: &PgPool, id: i32) -> Result<User> { pub async fn read_by_id(pool: &PgPool, id: i32) -> Result<Option<User>> {
let record = sqlx::query!( let record = sqlx::query!(
r#" r#"
SELECT id, SELECT id,
@ -95,23 +95,23 @@ impl User {
"#, "#,
id, id,
) )
.fetch_one(pool) .fetch_optional(pool)
.await?; .await?;
let user = User { let user = record.and_then(|u| Some(User {
id: record.id, id: u.id,
name: record.name, name: u.name,
email: record.email, email: u.email,
password: record.password, password: u.password,
salt: record.salt, salt: u.salt,
role: record.role, role: u.role,
function: record.function, function: u.function,
area_id: record.areaid, area_id: u.areaid,
area: None, area: None,
locked: record.locked, locked: u.locked,
last_login: record.lastlogin, last_login: u.lastlogin,
receive_notifications: record.receivenotifications, receive_notifications: u.receivenotifications,
}; }));
Ok(user) Ok(user)
} }
@ -197,7 +197,7 @@ impl User {
Ok(result) Ok(result)
} }
pub async fn read_all_including_area(pool: &PgPool) -> anyhow::Result<Vec<User>> { pub async fn read_all_including_area(pool: &PgPool) -> Result<Vec<User>> {
let records = sqlx::query!( let records = sqlx::query!(
r#" r#"
SELECT SELECT
@ -245,7 +245,7 @@ impl User {
Ok(results) Ok(results)
} }
pub async fn read_all_by_area(pool: &PgPool, area_id: i32) -> anyhow::Result<Vec<User>> { pub async fn read_all_by_area(pool: &PgPool, area_id: i32) -> Result<Vec<User>> {
let records = sqlx::query!( let records = sqlx::query!(
r#" r#"
SELECT id, SELECT id,

View File

@ -18,7 +18,9 @@ pub enum ApplicationError {
#[error("email transport not working")] #[error("email transport not working")]
EmailTransport(#[from] lettre::transport::smtp::Error), EmailTransport(#[from] lettre::transport::smtp::Error),
#[error("hashfunction failed")] #[error("hashfunction failed")]
Hash(#[from] argon2::password_hash::Error) Hash(#[from] argon2::password_hash::Error),
#[error("templating failed")]
Template(#[from] rinja::Error),
} }
impl actix_web::error::ResponseError for ApplicationError { impl actix_web::error::ResponseError for ApplicationError {
@ -32,6 +34,7 @@ impl actix_web::error::ResponseError for ApplicationError {
ApplicationError::Email(_) => StatusCode::INTERNAL_SERVER_ERROR, ApplicationError::Email(_) => StatusCode::INTERNAL_SERVER_ERROR,
ApplicationError::EmailTransport(_) => StatusCode::INTERNAL_SERVER_ERROR, ApplicationError::EmailTransport(_) => StatusCode::INTERNAL_SERVER_ERROR,
ApplicationError::Hash(_) => StatusCode::INTERNAL_SERVER_ERROR, ApplicationError::Hash(_) => StatusCode::INTERNAL_SERVER_ERROR,
ApplicationError::Template(_) => StatusCode::INTERNAL_SERVER_ERROR,
} }
} }
@ -43,6 +46,7 @@ impl actix_web::error::ResponseError for ApplicationError {
ApplicationError::EmailAdress(e) => response.body(format!("{self} - {e}")), ApplicationError::EmailAdress(e) => response.body(format!("{self} - {e}")),
ApplicationError::Email(e) => response.body(format!("{self} - {e}")), ApplicationError::Email(e) => response.body(format!("{self} - {e}")),
ApplicationError::EmailTransport(e) => response.body(format!("{self} - {e}")), ApplicationError::EmailTransport(e) => response.body(format!("{self} - {e}")),
ApplicationError::Template(e) => response.body(format!("{self} - {e}")),
_ => response.body(self.to_string()), _ => response.body(self.to_string()),
} }
} }

View File

@ -58,7 +58,4 @@
</form> </form>
</div> </div>
</section> </section>
<script>
</script>
{% endblock %} {% endblock %}

View File

@ -32,10 +32,10 @@
<label class="radio"> <label class="radio">
{% if id.is_some() %} {% if id.is_some() %}
<input type="radio" name="hasTime" hx-get="/availabillity/edit/{{ id.unwrap() }}?wholeday=false" <input type="radio" name="hasTime" hx-get="/availabillity/edit/{{ id.unwrap() }}?wholeday=false"
hx-target="closest body" {{ whole_day|invert|cond_show("checked") }} /> hx-target="closest body" {{ whole_day|invert|ref|cond_show("checked") }} />
{% else %} {% else %}
<input type="radio" name="hasTime" hx-get="/availabillity/new?date={{ date }}&wholeday=false" <input type="radio" name="hasTime" hx-get="/availabillity/new?date={{ date }}&wholeday=false"
hx-target="closest body" {{ whole_day|invert|cond_show("checked") }} /> hx-target="closest body" {{ whole_day|invert|ref|cond_show("checked") }} />
{% endif %} {% endif %}
zeitweise zeitweise
</label> </label>
@ -51,11 +51,11 @@
<div class="field-body"> <div class="field-body">
<div class="field"> <div class="field">
<input class="input" type="time" id="from" name="from" value='{{ start_time.unwrap_or("00:00") }}' {{ <input class="input" type="time" id="from" name="from" value='{{ start_time.unwrap_or("00:00") }}' {{
whole_day|cond_show("disabled") }} {{ whole_day|invert|cond_show("required") }}> whole_day|cond_show("disabled") }} {{ whole_day|invert|ref|cond_show("required") }}>
</div> </div>
<div class="field"> <div class="field">
<input class="input" type="time" id="till" name="till" value='{{ end_time.unwrap_or("23:59") }}' {{ <input class="input" type="time" id="till" name="till" value='{{ end_time.unwrap_or("23:59") }}' {{
whole_day|cond_show("disabled") }} {{ whole_day|invert|cond_show("required") }}> whole_day|cond_show("disabled") }} {{ whole_day|invert|ref|cond_show("required") }}>
</div> </div>
</div> </div>
</div> </div>

View File

@ -43,7 +43,7 @@
</div> </div>
<div class="cell is-col-span-2"> <div class="cell is-col-span-2">
<p><b>Anmerkungen:</b> {{ event.note.as_ref().unwrap_or(String::new()|as_ref) }}</p> <p><b>Anmerkungen:</b> {{ event.note.as_ref().unwrap_or(String::new()|ref) }}</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -45,23 +45,23 @@
<div class="buttons has-addons"> <div class="buttons has-addons">
<button hx-post="/assignments/new?event={{ event.id }}&availabillity={{ availabillity.id }}&function=1" <button hx-post="/assignments/new?event={{ event.id }}&availabillity={{ availabillity.id }}&function=1"
hx-target="closest table" hx-swap="outerHTML" class="button is-small" {% if !further_posten_required || hx-target="closest table" hx-swap="outerHTML" class="button is-small" {% if !further_posten_required ||
status !=AvailabillityAssignmentState::Unassigned|as_ref %}disabled{% endif %}>Posten</button> status !=AvailabillityAssignmentState::Unassigned|ref %}disabled{% endif %}>Posten</button>
{% if u.function == Function::Wachhabender || u.function == Function::Fuehrungsassistent %} {% if u.function == Function::Wachhabender || u.function == Function::Fuehrungsassistent %}
<button hx-post="/assignments/new?event={{ event.id }}&availabillity={{ availabillity.id }}&function=5" <button hx-post="/assignments/new?event={{ event.id }}&availabillity={{ availabillity.id }}&function=5"
hx-target="closest table" hx-swap="outerHTML" class="button is-small" {% if hx-target="closest table" hx-swap="outerHTML" class="button is-small" {% if
!further_fuehrungsassistent_required || status !=AvailabillityAssignmentState::Unassigned|as_ref !further_fuehrungsassistent_required || status !=AvailabillityAssignmentState::Unassigned|ref
%}disabled{% endif %}>Führungsassistent</button> %}disabled{% endif %}>Führungsassistent</button>
{% endif %} {% endif %}
{% if u.function == Function::Wachhabender %} {% if u.function == Function::Wachhabender %}
<button hx-post="/assignments/new?event={{ event.id }}&availabillity={{ availabillity.id }}&function=10" <button hx-post="/assignments/new?event={{ event.id }}&availabillity={{ availabillity.id }}&function=10"
hx-target="closest table" hx-swap="outerHTML" class="button is-small" {% if !further_wachhabender_required hx-target="closest table" hx-swap="outerHTML" class="button is-small" {% if !further_wachhabender_required
|| status !=AvailabillityAssignmentState::Unassigned|as_ref %}disabled{% endif %}>Wachhabender</button> || status !=AvailabillityAssignmentState::Unassigned|ref %}disabled{% endif %}>Wachhabender</button>
{% endif %} {% endif %}
</div> </div>
</td> </td>
<td> <td>
{% if status != AvailabillityAssignmentState::Unassigned|as_ref && status != {% if status != AvailabillityAssignmentState::Unassigned|ref && status !=
AvailabillityAssignmentState::Conflicting|as_ref %} AvailabillityAssignmentState::Conflicting|ref %}
<button hx-delete="/assignments/delete?event={{ event.id }}&availabillity={{ availabillity.id }}" <button hx-delete="/assignments/delete?event={{ event.id }}&availabillity={{ availabillity.id }}"
hx-target="closest table" hx-swap="outerHTML" class="button is-small">Entplanen</button> hx-target="closest table" hx-swap="outerHTML" class="button is-small">Entplanen</button>
{% endif %} {% endif %}

View File

@ -40,7 +40,7 @@
{% let areaid = loc.area_id %} {% let areaid = loc.area_id %}
{% endif -%} {% endif -%}
{% for area in areas.as_ref().unwrap() %} {% for area in areas.as_ref().unwrap() %}
<option {{ areaid|is_some_and_eq(area.id)|cond_show("selected") }} value="{{ area.id }}">{{ <option {{ areaid|is_some_and_eq(area.id)|ref|cond_show("selected") }} value="{{ area.id }}">{{
area.name }}</option> area.name }}</option>
{% endfor %} {% endfor %}
</select> </select>

View File

@ -48,10 +48,10 @@
<div class="control"> <div class="control">
<div class="select is-fullwidth"> <div class="select is-fullwidth">
<select name="role"> <select name="role">
<option value="1" {{ role|is_some_and_eq(1|as_ref)|cond_show("selected") }}>Personal</option> <option value="1" {{ role|is_some_and_eq(1|ref)|ref|cond_show("selected") }}>Personal</option>
<option value="10" {{ role|is_some_and_eq(10|as_ref)|cond_show("selected") }}>Bereichsleiter <option value="10" {{ role|is_some_and_eq(10|ref)|ref|cond_show("selected") }}>Bereichsleiter
</option> </option>
<option value="100" {{ role|is_some_and_eq(100|as_ref)|cond_show("selected") }}>Admin</option> <option value="100" {{ role|is_some_and_eq(100|ref)|ref|cond_show("selected") }}>Admin</option>
</select> </select>
</div> </div>
</div> </div>
@ -68,10 +68,10 @@
<div class="control"> <div class="control">
<div class="select is-fullwidth"> <div class="select is-fullwidth">
<select name="function"> <select name="function">
<option value="1" {{ function|is_some_and_eq(1|as_ref)|cond_show("selected") }}>Posten</option> <option value="1" {{ function|is_some_and_eq(1|ref)|ref|cond_show("selected") }}>Posten</option>
<option value="1" {{ function|is_some_and_eq(5|as_ref)|cond_show("selected") }}>Führungsassistent <option value="1" {{ function|is_some_and_eq(5|ref)|ref|cond_show("selected") }}>Führungsassistent
</option> </option>
<option value="10" {{ function|is_some_and_eq(10|as_ref)|cond_show("selected") }}>Wachhabender <option value="10" {{ function|is_some_and_eq(10|ref)|ref|cond_show("selected") }}>Wachhabender
</option> </option>
</select> </select>
</div> </div>
@ -91,7 +91,7 @@
<div class="select is-fullwidth"> <div class="select is-fullwidth">
<select name="area"> <select name="area">
{% for area in areas.as_ref().unwrap() %} {% for area in areas.as_ref().unwrap() %}
<option value="{{ area.id }}" {{ area_id|is_some_and_eq(area.id)|cond_show("selected") }}>{{ <option value="{{ area.id }}" {{ area_id|is_some_and_eq(area.id)|ref|cond_show("selected") }}>{{
area.name }}</option> area.name }}</option>
{% endfor %} {% endfor %}
</select> </select>