From f06d907b25b2b6d4b0ad446f33862cdd65439480 Mon Sep 17 00:00:00 2001 From: Max Hohlfeld Date: Sun, 2 Feb 2025 18:01:48 +0100 Subject: [PATCH] refactor: bugfixes and code cleanup --- ...f8802090ce15f811d1b2506ff8bc12fe8fad9.json | 137 ++++++++++++++++++ web/src/endpoints/availability/delete.rs | 2 +- web/src/endpoints/availability/get_update.rs | 23 ++- web/src/endpoints/availability/post_update.rs | 27 +++- web/src/endpoints/export/get_availability.rs | 14 +- .../endpoints/export/get_availability_data.rs | 23 ++- web/src/filters.rs | 24 +-- web/src/models/availabillity.rs | 137 +++++++++++++----- web/src/utils/event_planning_template.rs | 6 +- web/templates/availability/new_or_edit.html | 2 +- web/templates/export/availability.html | 68 ++++----- 11 files changed, 357 insertions(+), 106 deletions(-) create mode 100644 .sqlx/query-e5c0fdebea352e78f5d34fd0774f8802090ce15f811d1b2506ff8bc12fe8fad9.json diff --git a/.sqlx/query-e5c0fdebea352e78f5d34fd0774f8802090ce15f811d1b2506ff8bc12fe8fad9.json b/.sqlx/query-e5c0fdebea352e78f5d34fd0774f8802090ce15f811d1b2506ff8bc12fe8fad9.json new file mode 100644 index 00000000..964cab1b --- /dev/null +++ b/.sqlx/query-e5c0fdebea352e78f5d34fd0774f8802090ce15f811d1b2506ff8bc12fe8fad9.json @@ -0,0 +1,137 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n availabillity.id,\n availabillity.userId,\n availabillity.date,\n availabillity.startTime,\n availabillity.endTime,\n availabillity.comment,\n user_.name,\n user_.email,\n user_.password,\n user_.salt,\n user_.role AS \"role: Role\",\n user_.function AS \"function: Function\",\n user_.areaId,\n user_.locked,\n user_.lastLogin,\n user_.receiveNotifications\n FROM availabillity\n JOIN user_ ON availabillity.userId = user_.id\n WHERE availabillity.date = $1\n AND user_.areaId = $2\n AND ((availabillity.startTime IS NULL AND availabillity.endTime IS NULL)\n OR (availabillity.startTime <= $3 AND availabillity.endTime >= $4));\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "userid", + "type_info": "Int4" + }, + { + "ordinal": 2, + "name": "date", + "type_info": "Date" + }, + { + "ordinal": 3, + "name": "starttime", + "type_info": "Time" + }, + { + "ordinal": 4, + "name": "endtime", + "type_info": "Time" + }, + { + "ordinal": 5, + "name": "comment", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "password", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "salt", + "type_info": "Text" + }, + { + "ordinal": 10, + "name": "role: Role", + "type_info": { + "Custom": { + "name": "role", + "kind": { + "Enum": [ + "staff", + "areamanager", + "admin" + ] + } + } + } + }, + { + "ordinal": 11, + "name": "function: Function", + "type_info": { + "Custom": { + "name": "function", + "kind": { + "Enum": [ + "posten", + "fuehrungsassistent", + "wachhabender" + ] + } + } + } + }, + { + "ordinal": 12, + "name": "areaid", + "type_info": "Int4" + }, + { + "ordinal": 13, + "name": "locked", + "type_info": "Bool" + }, + { + "ordinal": 14, + "name": "lastlogin", + "type_info": "Timestamptz" + }, + { + "ordinal": 15, + "name": "receivenotifications", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Date", + "Int4", + "Time", + "Time" + ] + }, + "nullable": [ + false, + false, + false, + true, + true, + true, + false, + false, + true, + true, + false, + false, + false, + false, + true, + false + ] + }, + "hash": "e5c0fdebea352e78f5d34fd0774f8802090ce15f811d1b2506ff8bc12fe8fad9" +} diff --git a/web/src/endpoints/availability/delete.rs b/web/src/endpoints/availability/delete.rs index d1c9c86b..295a01ce 100644 --- a/web/src/endpoints/availability/delete.rs +++ b/web/src/endpoints/availability/delete.rs @@ -16,7 +16,7 @@ pub async fn delete( return Ok(HttpResponse::NotFound().finish()); }; - if availabillity.user_id == user.id { + if availabillity.user_id != user.id { return Err(ApplicationError::Unauthorized); } diff --git a/web/src/endpoints/availability/get_update.rs b/web/src/endpoints/availability/get_update.rs index 18016a3a..ce5411d0 100644 --- a/web/src/endpoints/availability/get_update.rs +++ b/web/src/endpoints/availability/get_update.rs @@ -4,12 +4,10 @@ use serde::Deserialize; use sqlx::PgPool; use crate::{ - endpoints::{ - availability::NewOrEditAvailabilityTemplate, - IdPath, - }, + endpoints::{availability::NewOrEditAvailabilityTemplate, IdPath}, models::{find_free_time_slots, Availability, AvailabilityTime, User}, utils::ApplicationError, + END_OF_DAY, START_OF_DAY, }; #[derive(Deserialize)] @@ -34,12 +32,9 @@ pub async fn get( } let suggestions = if let AvailabilityTime::Temporarily(start, end) = availability.time { - let availabilities = Availability::read_by_user_and_date( - pool.get_ref(), - user.id, - &availability.date, - ) - .await?; + let availabilities = + Availability::read_by_user_and_date(pool.get_ref(), user.id, &availability.date) + .await?; find_free_time_slots(&availabilities) .into_iter() @@ -49,8 +44,12 @@ pub async fn get( Vec::new() }; - let time_selection = if query.whole_day.unwrap_or(availability.time == AvailabilityTime::WholeDay) { - AvailabilityTime::WholeDay + let time_selection = if let Some(whole_day) = query.whole_day { + if whole_day { + AvailabilityTime::WholeDay + } else { + AvailabilityTime::Temporarily(START_OF_DAY, END_OF_DAY) + } } else { availability.time.clone() }; diff --git a/web/src/endpoints/availability/post_update.rs b/web/src/endpoints/availability/post_update.rs index ea1ab9d2..c34de2bc 100644 --- a/web/src/endpoints/availability/post_update.rs +++ b/web/src/endpoints/availability/post_update.rs @@ -1,11 +1,16 @@ use actix_web::{http::header::LOCATION, web, HttpResponse, Responder}; +use chrono::NaiveTime; use garde::Validate; use sqlx::PgPool; use crate::{ - endpoints::{availability::{find_adjacend_availability, post_new::AvailabillityForm}, IdPath}, + endpoints::{ + availability::{find_adjacend_availability, post_new::AvailabillityForm}, + IdPath, + }, models::{Availability, AvailabilityChangeset, AvailabilityContext, AvailabilityTime, User}, utils::{self, ApplicationError}, + END_OF_DAY, }; #[actix_web::post("/availabillity/edit/{id}")] @@ -23,14 +28,24 @@ pub async fn post( return Err(ApplicationError::Unauthorized); } - let existing_availabilities = - Availability::read_by_user_and_date(pool.get_ref(), user.id, &availability.date).await?; + let existing_availabilities: Vec = + Availability::read_by_user_and_date(pool.get_ref(), user.id, &availability.date) + .await? + .into_iter() + .filter(|a| a.id != availability.id) + .collect(); + let context = AvailabilityContext { existing_availabilities: existing_availabilities.clone(), }; let time = if form.from.is_some() && form.till.is_some() { - AvailabilityTime::Temporarily(form.from.unwrap(), form.till.unwrap()) + let end_time = if form.till.unwrap() == NaiveTime::from_hms_opt(23, 59, 0).unwrap() { + END_OF_DAY + } else { + form.till.unwrap() + }; + AvailabilityTime::Temporarily(form.from.unwrap(), end_time) } else { AvailabilityTime::WholeDay }; @@ -44,7 +59,9 @@ pub async fn post( return Ok(HttpResponse::BadRequest().body(e.to_string())); }; - if let Some(a) = find_adjacend_availability(&changeset, Some(availability.id), &existing_availabilities) { + if let Some(a) = + find_adjacend_availability(&changeset, Some(availability.id), &existing_availabilities) + { let (changeset_start, changeset_end) = changeset.time.time_tuple_opt().unwrap(); let (adjacent_start, adjacent_end) = changeset.time.time_tuple_opt().unwrap(); diff --git a/web/src/endpoints/export/get_availability.rs b/web/src/endpoints/export/get_availability.rs index f7648982..acfc0eea 100644 --- a/web/src/endpoints/export/get_availability.rs +++ b/web/src/endpoints/export/get_availability.rs @@ -1,20 +1,25 @@ use actix_web::{web, HttpResponse, Responder}; +use chrono::{NaiveDate, Utc}; use rinja::Template; use sqlx::PgPool; -use crate::{models::{Area, Role, User}, utils::ApplicationError}; +use crate::{ + models::{Area, Role, User}, + utils::ApplicationError, +}; #[derive(Template)] #[template(path = "export/availability.html")] struct AvailabilityExportTemplate { user: User, - areas: Option> + areas: Option>, + today: NaiveDate, } #[actix_web::get("/export/availability")] pub async fn get( user: web::ReqData, - pool: web::Data + pool: web::Data, ) -> Result { if user.role != Role::Admin && user.role != Role::AreaManager { return Err(ApplicationError::Unauthorized); @@ -28,7 +33,8 @@ pub async fn get( let template = AvailabilityExportTemplate { user: user.into_inner(), - areas + areas, + today: Utc::now().date_naive(), }; Ok(HttpResponse::Ok().body(template.render()?)) diff --git a/web/src/endpoints/export/get_availability_data.rs b/web/src/endpoints/export/get_availability_data.rs index bbc51391..b63ae89d 100644 --- a/web/src/endpoints/export/get_availability_data.rs +++ b/web/src/endpoints/export/get_availability_data.rs @@ -4,11 +4,12 @@ use actix_web::{ web, HttpResponse, Responder, }; use chrono::{Months, NaiveDate, NaiveTime, Utc}; +use quick_xml::se::Serializer; use serde::{Deserialize, Serialize}; use sqlx::PgPool; use crate::{ - models::{Availability, AvailabilityTime, Function, Role, User}, + models::{Area, Availability, AvailabilityTime, Function, Role, User}, END_OF_DAY, START_OF_DAY, }; @@ -97,16 +98,30 @@ pub async fn get( }) .collect(); + let area = Area::read_by_id(pool.get_ref(), area_id) + .await + .unwrap() + .unwrap() + .name; + let export = Export { year: query.year, month: query.month, - area: area_id.to_string(), + area, availabillities: export_availabillities, }; let out = match query.format.as_str() { - "xml" => quick_xml::se::to_string(&export).unwrap_or_default(), - "json" => serde_json::to_string(&export).unwrap_or_default(), + "xml" => { + let mut buffer = String::new(); + let mut ser = Serializer::new(&mut buffer); + ser.indent(' ', 2); + + export.serialize(ser).unwrap(); + + buffer + } + "json" => serde_json::to_string_pretty(&export).unwrap_or_default(), _ => return HttpResponse::BadRequest().finish(), }; diff --git a/web/src/filters.rs b/web/src/filters.rs index 80aff6b6..4c41b1d7 100644 --- a/web/src/filters.rs +++ b/web/src/filters.rs @@ -1,3 +1,5 @@ +use maud::html; + use crate::models::Function; pub fn show_area_query(a: &Option, first: bool) -> rinja::Result { @@ -39,15 +41,17 @@ pub fn invert(b: &bool) -> rinja::Result { } pub fn show_tree(f: &Function) -> rinja::Result { - let mut tags = String::from(r#"Posten"#); + let html = html! { + div class="tags" { + span class="tag is-primary is-light" { "Posten"} + @if *f == Function::Fuehrungsassistent || *f == Function::Wachhabender { + span class="tag is-primary has-background-primary-85 has-text-primary-85-invert" { "Führungsassistent"} + } + @if *f == Function::Wachhabender { + span class="tag is-primary" { "Wachhabender"} + } + } + }; - if f == &Function::Fuehrungsassistent || f == &Function::Wachhabender { - tags.push_str(r#"Führungsassistent"#); - } - - if f == &Function::Wachhabender { - tags.push_str(r#"Wachhabender"#); - } - - Ok(format!(r#"
{tags}
"#)) + Ok(html.into_string()) } diff --git a/web/src/models/availabillity.rs b/web/src/models/availabillity.rs index a371ba39..4589862a 100644 --- a/web/src/models/availabillity.rs +++ b/web/src/models/availabillity.rs @@ -134,6 +134,77 @@ impl Availability { Ok(availabillities) } + pub async fn read_by_date_time_area_including_user( + pool: &PgPool, + date: NaiveDate, + time: (NaiveTime, NaiveTime), + area_id: i32, + ) -> Result> { + let records = query!( + r##" + SELECT + availabillity.id, + availabillity.userId, + availabillity.date, + availabillity.startTime, + availabillity.endTime, + availabillity.comment, + user_.name, + user_.email, + user_.password, + user_.salt, + user_.role AS "role: Role", + user_.function AS "function: Function", + user_.areaId, + user_.locked, + user_.lastLogin, + user_.receiveNotifications + FROM availabillity + JOIN user_ ON availabillity.userId = user_.id + WHERE availabillity.date = $1 + AND user_.areaId = $2 + AND ((availabillity.startTime IS NULL AND availabillity.endTime IS NULL) + OR (availabillity.startTime <= $3 AND availabillity.endTime >= $4)); + "##, + date, + area_id, + time.0, + time.1 + ) + .fetch_all(pool) + .await?; + + let availabillities = records + .iter() + .map(|r| Availability { + id: r.id, + user_id: r.userid, + user: Some(User { + id: r.userid, + name: r.name.clone(), + email: r.email.clone(), + password: r.password.clone(), + salt: r.salt.clone(), + role: r.role, + function: r.function, + area_id: r.areaid, + area: None, + locked: r.locked, + last_login: r.lastlogin, + receive_notifications: r.receivenotifications, + }), + date: r.date, + time: match (r.starttime, r.endtime) { + (Some(start), Some(end)) => AvailabilityTime::Temporarily(start, end), + (_, _) => AvailabilityTime::WholeDay, + }, + comment: r.comment.clone(), + }) + .collect(); + + Ok(availabillities) + } + pub async fn read_by_id_including_user(pool: &PgPool, id: i32) -> Result> { let record = query!( r##" @@ -165,29 +236,29 @@ impl Availability { .await?; let availabillity = record.map(|r| Availability { - id: r.id, - user_id: r.userid, - user: Some(User { - id: r.userid, - name: r.name.clone(), - email: r.email.clone(), - password: r.password.clone(), - salt: r.salt.clone(), - role: r.role, - function: r.function, - area_id: r.areaid, - area: None, - locked: r.locked, - last_login: r.lastlogin, - receive_notifications: r.receivenotifications, - }), - date: r.date, - time: match (r.starttime, r.endtime) { - (Some(start), Some(end)) => AvailabilityTime::Temporarily(start, end), - (_, _) => AvailabilityTime::WholeDay, - }, - comment: r.comment.clone(), - }); + id: r.id, + user_id: r.userid, + user: Some(User { + id: r.userid, + name: r.name.clone(), + email: r.email.clone(), + password: r.password.clone(), + salt: r.salt.clone(), + role: r.role, + function: r.function, + area_id: r.areaid, + area: None, + locked: r.locked, + last_login: r.lastlogin, + receive_notifications: r.receivenotifications, + }), + date: r.date, + time: match (r.starttime, r.endtime) { + (Some(start), Some(end)) => AvailabilityTime::Temporarily(start, end), + (_, _) => AvailabilityTime::WholeDay, + }, + comment: r.comment.clone(), + }); Ok(availabillity) } @@ -198,16 +269,16 @@ impl Availability { .await?; let availabillity = record.map(|record| Availability { - id: record.id, - user_id: record.userid, - user: None, - date: record.date, - time: match (record.starttime, record.endtime) { - (Some(start), Some(end)) => AvailabilityTime::Temporarily(start, end), - (_, _) => AvailabilityTime::WholeDay, - }, - comment: record.comment.clone(), - }); + id: record.id, + user_id: record.userid, + user: None, + date: record.date, + time: match (record.starttime, record.endtime) { + (Some(start), Some(end)) => AvailabilityTime::Temporarily(start, end), + (_, _) => AvailabilityTime::WholeDay, + }, + comment: record.comment.clone(), + }); Ok(availabillity) } diff --git a/web/src/utils/event_planning_template.rs b/web/src/utils/event_planning_template.rs index 29f7acbf..203f1d90 100644 --- a/web/src/utils/event_planning_template.rs +++ b/web/src/utils/event_planning_template.rs @@ -1,7 +1,8 @@ use sqlx::PgPool; use crate::models::{ - Assignment, Availability, AvailabillityAssignmentState, Event, Function, Vehicle, VehicleAssignement, + Assignment, Availability, AvailabillityAssignmentState, Event, Function, Vehicle, + VehicleAssignement, }; use super::ApplicationError; @@ -10,9 +11,10 @@ pub async fn generate_availabillity_assignment_list( pool: &PgPool, event: &Event, ) -> Result, ApplicationError> { - let availabillities_in_db = Availability::read_by_date_and_area_including_user( + let availabillities_in_db = Availability::read_by_date_time_area_including_user( pool, event.date, + (event.start_time, event.end_time), event.location.as_ref().unwrap().area_id, ) .await?; diff --git a/web/templates/availability/new_or_edit.html b/web/templates/availability/new_or_edit.html index 4c3d7f32..2c0ddc9a 100644 --- a/web/templates/availability/new_or_edit.html +++ b/web/templates/availability/new_or_edit.html @@ -55,7 +55,7 @@

noch mögliche Zeiträume:

{% for (s, e) in slot_suggestions %} - {{ s }} - {{ e }} + {{ s.format("%R") }} - {{ e.format("%R") }} {% endfor %}
{% endif %} diff --git a/web/templates/export/availability.html b/web/templates/export/availability.html index 0d9c6223..bcd88fea 100644 --- a/web/templates/export/availability.html +++ b/web/templates/export/availability.html @@ -6,49 +6,49 @@

Verfügbarkeiten nach XML exportieren

-
-
- -
- -
+ +
+ +
+
+
-
- -
- -
+
+ +
+
+
-
- -
-
- -
+
+ +
+
+
+
- {% if user.role == Role::Admin %} -
- -
-
- -
+ {% if user.role == Role::Admin %} +
+ +
+
+
- {% endif %} - - +
+ {% endif %} + +
{% endblock %}