Fix cottage calendar requests
This commit is contained in:
parent
8032019807
commit
e9edd3e82a
|
|
@ -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.';
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1 @@
|
|||
-- Add down migration script here
|
||||
|
|
@ -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';
|
||||
|
|
@ -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<Calendar> {
|
|||
calendars
|
||||
}
|
||||
|
||||
async fn get_event(event_id: Uuid, db_pool: &PgPool) -> String {
|
||||
// Set default events
|
||||
let mut eventstring: String = "[]".to_string();
|
||||
|
||||
let event: Result<PgRow, Error> = 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<String>,
|
||||
State(db_pool): State<PgPool>,
|
||||
Query(params): Query<EventParams>,
|
||||
Extension(user_data): Extension<Option<AccountData>>,
|
||||
) -> 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<PgPool>,
|
||||
Extension(user_data): Extension<Option<AccountData>>,
|
||||
request: axum::http::Request<axum::body::Body>,
|
||||
) -> 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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
<link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/@fullcalendar/daygrid@4.3.0/main.min.css'>
|
||||
{% endblock links %}
|
||||
{% block center %}
|
||||
<h1>Calendar</h1>
|
||||
<div class="mh-100">
|
||||
<div id="calendar" class="fc"></div>
|
||||
</div>
|
||||
|
|
@ -13,25 +12,34 @@
|
|||
<div class="modal fade" id="eventDetailsModal" tabindex="-1" role="dialog" aria-labelledby="eventDetailsModalTitle"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="eventDetailsModalTitle">Event details</h5>
|
||||
<form id="eventDetailsModalForm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="eventDetailsModalTitle">Request dates</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="eventStart">Starting</label>
|
||||
<input type="date" class="form-control" id="eventStart" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="eventEnd">Leaving</label>
|
||||
<input type="date" class="form-control" id="eventEnd" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" id="eventDetailsModalClose" class="btn btn-secondary"
|
||||
data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Send Request</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p id="eventDetailsModalDateRange"></p>
|
||||
<p id="eventDetailsModalBody">Cottage request</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary">Save changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock center %}
|
||||
{% block scripts %}
|
||||
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.15/index.global.min.js'></script>
|
||||
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.17/index.global.min.js'></script>
|
||||
<script src='https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js'></script>
|
||||
<script src='https://cdn.jsdelivr.net/npm/uuid@8.3.2/dist/umd/uuidv4.min.js'></script>
|
||||
<script>
|
||||
|
|
@ -50,29 +58,58 @@
|
|||
center: 'title',
|
||||
right: 'dayGridMonth,timeGridWeek,timeGridDay,multiMonthYear'
|
||||
},
|
||||
eventSources: [
|
||||
{% for calendar in calendars %}
|
||||
{
|
||||
url: '/getevents/{{calendar.name}}',
|
||||
color: '{{calendar.colour}}',
|
||||
},
|
||||
{% endfor %}
|
||||
],
|
||||
select: function (info) {
|
||||
$('#eventDetailsModal').modal('show');
|
||||
$('#eventDetailsModalDateRange').text(info.startStr + ' to ' + info.endStr);
|
||||
|
||||
info.view.calendar.addEvent({
|
||||
id: uuidv4(),
|
||||
title: 'Cottage request',
|
||||
start: info.startStr + 'T14:00:00',
|
||||
end: info.endStr + 'T10:00:00',
|
||||
allDay: false
|
||||
});
|
||||
}
|
||||
events: '/calendar/getevents',
|
||||
select: function (info) {
|
||||
$('#eventStart').val(info.startStr);
|
||||
$('#eventEnd').val(info.endStr);
|
||||
$('#eventDetailsModal').modal('show');
|
||||
}
|
||||
});
|
||||
|
||||
calendar.render();
|
||||
calendar.render();
|
||||
|
||||
document.getElementById('eventDetailsModalForm').addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
var start = document.getElementById('eventStart').value;
|
||||
var end = document.getElementById('eventEnd').value;
|
||||
|
||||
// Prepare the event data
|
||||
var eventData = {
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
|
||||
// Send data to the API
|
||||
fetch('/calendar/newrequest', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(eventData)
|
||||
})
|
||||
.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');
|
||||
calendar.addEvent({
|
||||
title: data.title,
|
||||
start: data.start,
|
||||
end: data.end,
|
||||
allDay: data.allDay,
|
||||
backgroundColor: data.backgroundColor
|
||||
});
|
||||
e.target.reset();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error creating event:', error);
|
||||
alert('An error occurred while creating the event.');
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('eventDetailsModalClose').addEventListener('click', function () {
|
||||
$('#eventDetailsModal').modal('hide');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock scripts %}
|
||||
Loading…
Reference in New Issue