From d156cbfa98e25faf0465cabf77cd1bad83a5e5d2 Mon Sep 17 00:00:00 2001 From: Chris Jean-Marie Date: Fri, 20 Jun 2025 14:48:37 +0000 Subject: [PATCH] Allow celendar event editing --- backend/src/calendar.rs | 126 +++++++++++++++++++++++++++++--- backend/src/main.rs | 3 + backend/src/rbac.rs | 2 +- backend/templates/calendar.html | 85 ++++++++++++++++++++- 4 files changed, 202 insertions(+), 14 deletions(-) diff --git a/backend/src/calendar.rs b/backend/src/calendar.rs index d782529..52a55bf 100644 --- a/backend/src/calendar.rs +++ b/backend/src/calendar.rs @@ -4,15 +4,18 @@ use axum::{ response::{Html, IntoResponse, Redirect, Response}, Extension, Form, }; -use chrono::Days; +use chrono::{Days, TimeDelta}; use http::StatusCode; +use rbac::RbacService; use serde::{Deserialize, Serialize}; +use serde_json::Value; use sqlx::{postgres::PgRow, Error, FromRow, PgPool, Row}; use uuid::Uuid; -use rbac::RbacService; use crate::{ - middlewares::is_authorized, rbac, user::{get_user_roles_display, AccountData} + middlewares::is_authorized, + rbac, + user::{get_user_roles_display, AccountData}, }; struct HtmlTemplate(T); @@ -116,7 +119,7 @@ async fn get_event(event_id: Uuid, db_pool: &PgPool) -> String { // Set default events let mut eventstring: String = "[]".to_string(); - let event: Result = Ok(sqlx::query( + let event: Result = Ok(sqlx::query( r#"select to_json(json_build_object( 'title', ce.title, 'start', ce.start_time, @@ -162,6 +165,10 @@ pub async fn get_events( // Extract the user data. let _user = user_data.as_ref().unwrap().clone(); let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default(); + let personid = user_data + .as_ref() + .map(|s| s.person_id.clone()) + .unwrap_or_default(); // Set empty query string let mut query = r#""#; @@ -172,6 +179,7 @@ pub async fn get_events( } else { query = r#"select to_json(json_agg(jbo.val)) from (select json_build_object( + 'id', ce.id, 'title', ce.title, 'start', ce.start_time, 'end', ce.end_time, @@ -187,6 +195,7 @@ pub async fn get_events( and ce.created_by = $3 union all select json_build_object( + 'id', ce.id, 'title', 'In use', 'start', ce.start_time, 'end', ce.end_time, @@ -203,6 +212,7 @@ pub async fn get_events( } } else { query = r#"select to_json(json_agg(json_build_object( + 'id', ce.id, 'title', ce.title, 'start', ce.start_time, 'end', ce.end_time, @@ -218,6 +228,7 @@ pub async fn get_events( } } else { query = r#"select to_json(json_agg(json_build_object( + 'id', ce.id, 'title', ce.title, 'start', ce.start_time, 'end', ce.end_time, @@ -237,12 +248,12 @@ pub async fn get_events( //println!("User is authorized"); // Get requested calendar events from database - let events = sqlx::query(query,) - .bind(chrono::DateTime::parse_from_rfc3339(¶ms.start).unwrap()) - .bind(chrono::DateTime::parse_from_rfc3339(¶ms.end).unwrap()) - .bind(userid) - .fetch_one(&db_pool) - .await; + let events = sqlx::query(query) + .bind(chrono::DateTime::parse_from_rfc3339(¶ms.start).unwrap()) + .bind(chrono::DateTime::parse_from_rfc3339(¶ms.end).unwrap()) + .bind(personid) + .fetch_one(&db_pool) + .await; //println!("Events: {:?}", events); @@ -297,7 +308,10 @@ pub async fn create_event( if is_authorized("/calendar", user_data.clone(), db_pool.clone()).await { let fmt = "%Y-%m-%d"; let start_date = chrono::NaiveDate::parse_from_str(&event.start_time, fmt).unwrap(); - let end_date = chrono::NaiveDate::parse_from_str(&event.end_time, fmt).unwrap().checked_sub_days(Days::new(1)).unwrap(); + let end_date = chrono::NaiveDate::parse_from_str(&event.end_time, fmt) + .unwrap() + .checked_sub_days(Days::new(1)) + .unwrap(); let start_datetime = start_date.and_hms_opt(14, 0, 0).unwrap(); let end_datetime = end_date.and_hms_opt(10, 0, 0).unwrap(); @@ -378,7 +392,10 @@ pub async fn new_request( let fmt = "%Y-%m-%d"; let start_date = chrono::NaiveDate::parse_from_str(¶ms.start, fmt).unwrap(); - let end_date = chrono::NaiveDate::parse_from_str(¶ms.end, fmt).unwrap().checked_sub_days(Days::new(1)).unwrap(); + let end_date = chrono::NaiveDate::parse_from_str(¶ms.end, fmt) + .unwrap() + .checked_sub_days(Days::new(1)) + .unwrap(); let start_datetime = start_date.and_hms_opt(14, 0, 0).unwrap(); let end_datetime = end_date.and_hms_opt(10, 0, 0).unwrap(); @@ -423,6 +440,91 @@ pub async fn new_request( eventstring } +pub async fn update_event( + State(db_pool): State, + Extension(user_data): Extension>, + Extension(rbac): Extension, + request: axum::http::Request, +) -> impl IntoResponse { + // Is the user logged in? + let logged_in = user_data.is_some(); + + // Set default events + let mut eventstring: String = "[]".to_string(); + + if logged_in { + // Extract the user data. + let _user = user_data.as_ref().unwrap().clone(); + let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default(); + let personid = user_data + .as_ref() + .map(|s| s.person_id.clone()) + .unwrap_or_default(); + + if rbac.has_permission(userid, "calendar:*:*").await { + let (_parts, body) = request.into_parts(); + let bytes = axum::body::to_bytes(body, usize::MAX).await.unwrap(); + let body_str = String::from_utf8(bytes.to_vec()).unwrap(); + // println!("Body: {}", body_str); + + let v: Value = serde_json::from_str(&body_str).unwrap(); + + let fmt = "%Y-%m-%d"; + + let start_date = chrono::NaiveDate::parse_from_str(v["start"].as_str().unwrap(), fmt).unwrap(); + let end_date = chrono::NaiveDate::parse_from_str(v["end"].as_str().unwrap(), fmt).unwrap(); + let start_datetime = start_date.and_hms_opt(14, 0, 0).unwrap(); + let end_datetime = end_date.and_hms_opt(10, 0, 0).unwrap(); + + // Convert calendar id to UUID + let calendar_event_id = Uuid::parse_str(v["id"].as_str().unwrap()).unwrap(); + + // Display values to be updated + // println!("person : {}", personid); + // println!("title : {}", v["title"]); + // println!("start : {:#?}", start_datetime); + // println!("end : {:#?}", end_datetime); + + let event = sqlx::query_scalar::<_, uuid::Uuid>( + r#"with cet as (select id from calendar_event_types where name = 'Reservation' and state = 'Requested') + update calendar_events + set updated_by = $1, + updated_at = now(), + event_type_id = cet.id, + title = $2, + start_time = $3, + end_time = $4 + from cet + where calendar_events.id = $5 + returning calendar_events.id"# + ) + .bind(personid) + .bind(v["title"].as_str().unwrap()) + .bind(start_datetime) + .bind(end_datetime) + .bind(calendar_event_id) + .fetch_one(&db_pool) + .await + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Error creating event: {}", e), + ) + }); + + let event_id = event.clone(); + + // println!("Event: {:#?}", event); + + eventstring = get_event(event_id.unwrap(), &db_pool).await; + + // println!("{:#?}", eventstring); + } + } + + eventstring +} + #[derive(Template)] #[template(path = "newevent.html")] struct EventTemplate { diff --git a/backend/src/main.rs b/backend/src/main.rs index cb4f00a..3f1db7e 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -34,6 +34,8 @@ use wishlist::{ user_wishlist_returned_item, user_wishlist_save_item, wishlists, }; +use crate::calendar::update_event; + //use email::send_emails; #[derive(Clone)] @@ -95,6 +97,7 @@ async fn main() { .route("/calendar/createevent", post(create_event)) .route("/calendar/newevent", get(new_event)) .route("/calendar/newrequest", post(new_request)) + .route("/calendar/updaterequest", post(update_event)) // Wishlist .route("/wishlists", get(wishlists)) .route("/userwishlist/{user_id}", get(user_wishlist)) diff --git a/backend/src/rbac.rs b/backend/src/rbac.rs index 415f83d..264e6dd 100644 --- a/backend/src/rbac.rs +++ b/backend/src/rbac.rs @@ -43,5 +43,5 @@ fn permission_matches(pattern: &str, resource: &str) -> bool { pattern_segments.iter() .zip(resource_segments.iter()) - .all(|(p, r)| p == r || *p == "*") + .all(|(p, r)| p == r || *p == "*" || *r == "*") } diff --git a/backend/templates/calendar.html b/backend/templates/calendar.html index b9598d7..f6b8feb 100644 --- a/backend/templates/calendar.html +++ b/backend/templates/calendar.html @@ -8,7 +8,7 @@
- + + + + {% endblock center %} {% block scripts %} @@ -63,6 +87,16 @@ $('#eventStart').val(info.startStr); $('#eventEnd').val(info.endStr); $('#eventDetailsModal').modal('show'); + }, + eventClick: function (info) { + if (info.event.title != "In use") { + $('#eventEditTitle').val(info.event.title); + $('#eventEditStart').val(moment(info.event.start).format('YYYY-MM-DD')); + $('#eventEditEnd').val(moment(info.event.end).format('YYYY-MM-DD')); + $('#eventEditModal').modal('show'); + // Store the event for later update + window.calEvent = info.event; + } } }); @@ -110,6 +144,55 @@ document.getElementById('eventDetailsModalClose').addEventListener('click', function () { $('#eventDetailsModal').modal('hide'); }); + + $('#saveEvent').on('click', function () { + var updatedEvent = { + id: window.calEvent.id, + title: $('#eventEditTitle').val(), + start: $('#eventEditStart').val(), + end: $('#eventEditEnd').val() + }; + + // Save the updates to the record + fetch('/calendar/updaterequest', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(updatedEvent) + }) + .then(response => { + if (!response.ok) throw new Error('Network response was not ok'); + return response.json(); + }) + .then(data => { + // Optionally, use the response to add the event to the calendar + $('#eventDetailsModal').modal('hide'); + // Update the original event object + if (window.calEvent.title != data.title) { + window.calEvent.setProp('title', data.title); + } + if (window.calEvent.start != data.start) { + window.calEvent.setStart(data.start); + } + if (window.calEvent.end != data.end) { + window.calEvent.setEnd(data.end); + } + window.calEvent.setProp('allDay', data.allDay); + window.calEvent.setProp('backgroundColor', data.backgroundColor); + + e.target.reset(); + }) + .catch(error => { + console.error('Error creating event:', error); + //alert('An error occurred while creating the event.'); + }); + + // Hide the popup + $('#eventEditModal').modal('hide'); + }); + + $('#closeEventEdit').on('click', function () { + $('#eventEditModal').modal('hide'); + }); }); {% endblock scripts %} \ No newline at end of file