diff --git a/backend/migrations/20250604023549_calendar_event_type_state.down.sql b/backend/migrations/20250604023549_calendar_event_type_state.down.sql new file mode 100644 index 0000000..ca2a547 --- /dev/null +++ b/backend/migrations/20250604023549_calendar_event_type_state.down.sql @@ -0,0 +1,29 @@ +-- Add down migration script here +ALTER TABLE if exists calendar_event_types + drop column if exists state; + +DO $$ +BEGIN + -- Check if the constraint 'unique_calendar_et_name' already exists + IF EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'unique_calendar_et_name' + AND conrelid = 'public.calendar_event_types'::regclass -- Specify table to narrow down + ) THEN + -- If it exists, drop the constraint + ALTER TABLE IF EXISTS public.calendar_event_types + DROP CONSTRAINT unique_calendar_et_name; + + RAISE NOTICE 'Constraint unique_calendar_et_name dropped from table calendar_event_types.'; + ELSE + RAISE NOTICE 'Constraint unique_calendar_et_name doesn''t exist on table calendar_event_types.'; + END IF; +END +$$; + +-- Add the constraint +ALTER TABLE IF EXISTS public.calendar_event_types +ADD CONSTRAINT unique_calendar_et_name UNIQUE (name); + +RAISE NOTICE 'Constraint unique_calendar_et_name added to table calendar_event_types.'; \ No newline at end of file diff --git a/backend/migrations/20250604023549_calendar_event_type_state.up.sql b/backend/migrations/20250604023549_calendar_event_type_state.up.sql new file mode 100644 index 0000000..f95fe3b --- /dev/null +++ b/backend/migrations/20250604023549_calendar_event_type_state.up.sql @@ -0,0 +1,23 @@ +-- Add up migration script here +ALTER TABLE if exists calendar_event_types + ADD COLUMN IF NOT EXISTS state character varying(25); + +DO $$ +BEGIN + -- Check if the constraint 'unique_calendar_et_name' already exists + IF EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'unique_calendar_et_name' + AND conrelid = 'public.calendar_event_types'::regclass -- Specify table to narrow down + ) THEN + -- If it exists, drop the constraint + ALTER TABLE IF EXISTS public.calendar_event_types + DROP CONSTRAINT unique_calendar_et_name; + END IF; +END +$$; + +-- Add the constraint +ALTER TABLE IF EXISTS public.calendar_event_types +ADD CONSTRAINT unique_calendar_et_name UNIQUE (name, state); diff --git a/backend/migrations/20250606135159_default_calendars.down.sql b/backend/migrations/20250606135159_default_calendars.down.sql new file mode 100644 index 0000000..d2f607c --- /dev/null +++ b/backend/migrations/20250606135159_default_calendars.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250606135159_default_calendars.up.sql b/backend/migrations/20250606135159_default_calendars.up.sql new file mode 100644 index 0000000..c29e86d --- /dev/null +++ b/backend/migrations/20250606135159_default_calendars.up.sql @@ -0,0 +1,36 @@ +-- Truncate tables +TRUNCATE TABLE calendar_event_types, calendar; + +-- Add default calendar and event types +insert into calendar (created_by, updated_by, name, colour) +select id, id, 'Cottage', 'blue' from users where email = 'admin@jean-marie.ca'; + +insert into calendar (created_by, updated_by, name, colour) +select id, id, 'Family tree', 'brown' from users where email = 'admin@jean-marie.ca'; + +insert into calendar_event_types (created_by, updated_by, name, state, colour) +select id, id, 'Reservation', 'Requested', 'purple' from users where email = 'admin@jean-marie.ca'; + +insert into calendar_event_types (created_by, updated_by, name, state, colour) +select id, id, 'Reservation', 'Approved', 'green' from users where email = 'admin@jean-marie.ca'; + +insert into calendar_event_types (created_by, updated_by, name, state, colour) +select id, id, 'Reservation', 'Confirmed', 'blue' from users where email = 'admin@jean-marie.ca'; + +insert into calendar_event_types (created_by, updated_by, name, state, colour) +select id, id, 'Reservation', 'Tentative', 'light-purple' from users where email = 'admin@jean-marie.ca'; + +insert into calendar_event_types (created_by, updated_by, name, state, colour) +select id, id, 'Reservation', 'Rejected', 'red' from users where email = 'admin@jean-marie.ca'; + +insert into calendar_event_types (created_by, updated_by, name, state, colour) +select id, id, 'Reservation', 'Cancelled', 'light-red' from users where email = 'admin@jean-marie.ca'; + +insert into calendar_event_types (created_by, updated_by, name, state, colour) +select id, id, 'Life event', 'Birthday', 'green' from users where email = 'admin@jean-marie.ca'; + +insert into calendar_event_types (created_by, updated_by, name, state, colour) +select id, id, 'Life event', 'Anniversary', 'orange' from users where email = 'admin@jean-marie.ca'; + +insert into calendar_event_types (created_by, updated_by, name, state, colour) +select id, id, 'Life event', 'Other', 'light-orange' from users where email = 'admin@jean-marie.ca'; \ No newline at end of file diff --git a/backend/src/calendar.rs b/backend/src/calendar.rs index 738a29c..2284a4d 100644 --- a/backend/src/calendar.rs +++ b/backend/src/calendar.rs @@ -1,10 +1,14 @@ use askama::Template; use axum::{ - extract::{Path, Query, State}, response::{Html, IntoResponse, Redirect, Response}, Extension, Form + extract::{Query, State}, + response::{Html, IntoResponse, Redirect, Response}, + Extension, Form, }; +use chrono::Days; use http::StatusCode; use serde::{Deserialize, Serialize}; -use sqlx::{FromRow, PgPool, Row}; +use sqlx::{postgres::PgRow, Error, FromRow, PgPool, Row}; +use tracing::event; use uuid::Uuid; use crate::{ @@ -94,13 +98,41 @@ pub async fn get_calendars(db_pool: PgPool) -> Vec { calendars } +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( + r#"select to_json(json_build_object( + 'title', ce.title, + 'start', ce.start_time, + 'end', ce.end_time, + 'allDay', false, + 'backgroundColor', cet.colour)) + from calendar_events ce + join calendar_event_types cet on cet.id = ce.event_type_id + where ce.id = $1"#, + ) + .bind(event_id) + .fetch_one(db_pool) + .await + .unwrap()); + + if let Ok(json_string) = event { + if let Ok(stringevents) = json_string.try_get_raw(0).map(|v| v.as_str().unwrap_or("")) { + //println!("Event: {:?}", stringevents); + eventstring = stringevents.to_string(); + } + } + + eventstring +} + pub async fn get_events( - Path(calendar): Path, State(db_pool): State, Query(params): Query, Extension(user_data): Extension>, ) -> String { - //println!("Calendar: {}", calendar); //println!("Paramters: {:?}", params); // Is the user logged in? let logged_in = user_data.is_some(); @@ -109,32 +141,40 @@ pub async fn get_events( let mut eventstring: String = "[]".to_string(); if logged_in { + // User is logged in + //println!("User is 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(); if is_authorized("/calendar", user_data, db_pool.clone()).await { + // User is authorized + //println!("User is authorized"); + // Get requested calendar events from database let events = sqlx::query( r#"select to_json(json_agg(json_build_object( 'title', ce.title, 'start', ce.start_time, 'end', ce.end_time, - 'allDay', false))) + 'allDay', false, + 'backgroundColor', cet.colour))) from calendar_events ce join calendar c on c.id = ce.calendar_id join calendar_event_types cet on cet.id = ce.event_type_id where ce.celebrate = true - and c.name = $1 - and start_time > $2 - and start_time < $3"#, + and c.name = 'Cottage' + and start_time > $1 + and start_time < $2"#, ) - .bind(calendar) .bind(chrono::DateTime::parse_from_rfc3339(¶ms.start).unwrap()) .bind(chrono::DateTime::parse_from_rfc3339(¶ms.end).unwrap()) .fetch_one(&db_pool) .await; + //println!("Events: {:?}", events); + if let Ok(json_string) = events { if let Ok(stringevents) = json_string.try_get_raw(0).map(|v| v.as_str().unwrap_or("")) @@ -185,7 +225,7 @@ 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(); + 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(); @@ -215,6 +255,89 @@ pub async fn create_event( Redirect::to(&redirect_url).into_response() } +#[derive(Deserialize, Serialize, Debug)] +pub struct NewRequest { + pub start: String, + pub end: String, +} + +pub async fn new_request( + State(db_pool): State, + Extension(user_data): Extension>, + request: axum::http::Request, +) -> impl IntoResponse { + let logged_in = user_data.is_some(); + + // Set default events + let mut eventstring: String = "[]".to_string(); + + if logged_in { + // User is logged in + //println!("User is 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 is_authorized("/calendar", user_data, db_pool.clone()).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 params: NewRequest = serde_json::from_str(&body_str).unwrap(); + + 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 start_datetime = start_date.and_hms_opt(14, 0, 0).unwrap(); + let end_datetime = end_date.and_hms_opt(10, 0, 0).unwrap(); + + let event = sqlx::query_scalar::<_, uuid::Uuid>( + r#"insert into calendar_events (created_by, updated_by, calendar_id, event_type_id, title, start_time, end_time) + select p.id as created_by, + p.id as updated_by, + c.id as calendar_id, + cet.id as event_type_id, + p.given_name as title, + $1 as start_time, + $2 as end_time + from calendar c, + calendar_event_types cet, + people p + where c.name = 'Cottage' + and cet.name = 'Reservation' + and cet.state = 'Requested' + and p.id = $3 + returning id"# + ) + .bind(start_datetime) + .bind(end_datetime) + .bind(personid) + .fetch_one(&db_pool) + .await + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Error creating event: {}", e), + ) + }); + + //println!("Event: {:#?}", &event.unwrap()); + + let event_id = event.clone(); + + eventstring = get_event(event_id.unwrap(), &db_pool).await; + } + } + + eventstring +} + #[derive(Template)] #[template(path = "newevent.html")] struct EventTemplate { diff --git a/backend/src/main.rs b/backend/src/main.rs index c2ca45d..7743d83 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,6 +1,6 @@ use axum::{ middleware, - routing::{get, get_service, post, put}, + routing::{get, get_service, post}, Extension, Router, }; use dotenvy::var; @@ -31,6 +31,8 @@ use wishlist::{ user_wishlist_delete_item, user_wishlist_edit_item, user_wishlist_received_item, user_wishlist_returned_item, user_wishlist_save_item, wishlists, }; + +use crate::calendar::new_request; //use email::send_emails; #[derive(Clone)] @@ -86,9 +88,10 @@ async fn main() { ) // Calendar .route("/calendar", get(calendar)) - .route("/getevents/{calendar}", get(get_events)) - .route("/createevent", post(create_event)) - .route("/newevent", get(new_event)) + .route("/calendar/getevents", get(get_events)) + .route("/calendar/createevent", post(create_event)) + .route("/calendar/newevent", get(new_event)) + .route("/calendar/newrequest", post(new_request)) // Wishlist .route("/wishlists", get(wishlists)) .route("/userwishlist/{user_id}", get(user_wishlist)) diff --git a/backend/templates/calendar.html b/backend/templates/calendar.html index 2c4d8ba..b9598d7 100644 --- a/backend/templates/calendar.html +++ b/backend/templates/calendar.html @@ -4,7 +4,6 @@ {% endblock links %} {% block center %} -

Calendar

@@ -13,25 +12,34 @@