Continue calendar code
This commit is contained in:
parent
f7e643ffbd
commit
682ea672c4
|
|
@ -9,5 +9,6 @@
|
|||
"now.instance.host.url": "",
|
||||
"now.instance.OAuth.client.id": "",
|
||||
"now.instance.user.password": "",
|
||||
"now.instance.OAuth.client.secret": ""
|
||||
"now.instance.OAuth.client.secret": "",
|
||||
"postman.settings.dotenv-detection-notification-visibility": false
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
-- Remove column from calendar_events
|
||||
ALTER TABLE if exists calendar_events
|
||||
drop column if exists state;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
-- Add column to calendar_events
|
||||
ALTER TABLE if exists calendar_events
|
||||
ADD COLUMN IF NOT EXISTS state character varying(25);
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
-- Remove default calendar and event types
|
||||
DELETE FROM calendar WHERE name = 'Cottage';
|
||||
DELETE FROM calendar WHERE name = 'Family tree';
|
||||
|
||||
DELETE FROM calendar_event_types WHERE name = 'Reservation';
|
||||
DELETE FROM calendar_event_types WHERE name = 'Life event';
|
||||
|
||||
-- Remove constraints
|
||||
ALTER TABLE public.calendar DROP CONSTRAINT unique_name;
|
||||
ALTER TABLE public.calendar_event_types DROP CONSTRAINT unique_name;
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
-- Add required constraints
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Check if the constraint 'unique_name' already exists
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'unique_calendar_name'
|
||||
AND conrelid = 'public.calendar'::regclass -- Specify table to narrow down
|
||||
) THEN
|
||||
-- If it doesn't exist, add the constraint
|
||||
ALTER TABLE IF EXISTS public.calendar
|
||||
ADD CONSTRAINT unique_calendar_name UNIQUE (name);
|
||||
RAISE NOTICE 'Constraint unique_calendar_name added to table calendar.';
|
||||
ELSE
|
||||
RAISE NOTICE 'Constraint unique_calendar_name already exists on table calendar.';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Check if the constraint 'unique_name' already exists
|
||||
IF NOT 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 doesn't exist, 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.';
|
||||
ELSE
|
||||
RAISE NOTICE 'Constraint unique_calendar_et_name already exists on table calendar_event_types.';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- Add default calendar and event types
|
||||
insert into calendar (created_by, updated_by, name)
|
||||
select id, id, 'Cottage' from users where email = 'admin@jean-marie.ca'
|
||||
on conflict (name) do nothing;
|
||||
|
||||
insert into calendar (created_by, updated_by, name)
|
||||
select id, id, 'Family tree' from users where email = 'admin@jean-marie.ca'
|
||||
on conflict (name) do nothing;
|
||||
|
||||
insert into calendar_event_types (created_by, updated_by, name)
|
||||
select id, id, 'Reservation' from users where email = 'admin@jean-marie.ca'
|
||||
on conflict (name) do nothing;
|
||||
|
||||
insert into calendar_event_types (created_by, updated_by, name)
|
||||
select id, id, 'Life event' from users where email = 'admin@jean-marie.ca'
|
||||
on conflict (name) do nothing;
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
-- Add down migration script here
|
||||
ALTER TABLE if exists calendar
|
||||
drop column if exists colour;
|
||||
|
||||
ALTER TABLE if exists calendar_event_types
|
||||
drop column if exists colour;
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
-- Add up migration script here
|
||||
ALTER TABLE if exists calendar
|
||||
ADD COLUMN IF NOT EXISTS colour character varying(50);
|
||||
|
||||
ALTER TABLE if exists calendar_event_types
|
||||
ADD COLUMN IF NOT EXISTS colour character varying(50);
|
||||
|
|
@ -1,10 +1,14 @@
|
|||
use core::fmt;
|
||||
|
||||
use askama::Template;
|
||||
use axum::{
|
||||
extract::{Path, Query, State}, response::{Html, IntoResponse, Redirect, Response}, Extension
|
||||
body::{self, Body}, extract::{Path, Query, State}, response::{Html, IntoResponse, Redirect, Response}, Extension, Form
|
||||
};
|
||||
use chrono::{FixedOffset, Utc};
|
||||
use http::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{PgPool, Row};
|
||||
use sqlx::{FromRow, PgPool, Row};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
middlewares::is_authorized,
|
||||
|
|
@ -29,7 +33,9 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
struct Calendar {
|
||||
#[derive(Default, Clone, Debug, Serialize, Deserialize, FromRow)]
|
||||
pub struct Calendar {
|
||||
id: Uuid,
|
||||
name: String,
|
||||
colour: String,
|
||||
}
|
||||
|
|
@ -59,16 +65,7 @@ pub async fn calendar(
|
|||
// Get user roles
|
||||
let user_roles = get_user_roles_display(userid, &db_pool.clone()).await;
|
||||
|
||||
let calendars: Vec<Calendar> = vec![
|
||||
Calendar {
|
||||
name: "Cottage".to_string(),
|
||||
colour: "green".to_string(),
|
||||
},
|
||||
Calendar {
|
||||
name: "Personal".to_string(),
|
||||
colour: "blue".to_string(),
|
||||
},
|
||||
];
|
||||
let calendars: Vec<Calendar> = get_calendars(db_pool.clone()).await;
|
||||
|
||||
let template = CalendarTemplate {
|
||||
logged_in,
|
||||
|
|
@ -90,13 +87,23 @@ pub struct EventParams {
|
|||
start: String,
|
||||
end: String,
|
||||
}
|
||||
|
||||
pub async fn get_calendars(db_pool: PgPool) -> Vec<Calendar> {
|
||||
let calendars: Vec<Calendar> = sqlx::query_as(r#"select id, name, colour from calendar"#)
|
||||
.fetch_all(&db_pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
calendars
|
||||
}
|
||||
|
||||
pub async fn get_events(
|
||||
Path(calendar): Path<String>,
|
||||
State(db_pool): State<PgPool>,
|
||||
Query(params): Query<EventParams>,
|
||||
Extension(user_data): Extension<Option<UserData>>,
|
||||
) -> String {
|
||||
println!("Calendar: {}", calendar);
|
||||
//println!("Calendar: {}", calendar);
|
||||
//println!("Paramters: {:?}", params);
|
||||
// Is the user logged in?
|
||||
let logged_in = user_data.is_some();
|
||||
|
|
@ -111,7 +118,8 @@ pub async fn get_events(
|
|||
|
||||
if is_authorized("/calendar", user_data, db_pool.clone()).await {
|
||||
// Get requested calendar events from database
|
||||
let events = sqlx::query(r#"select to_json(json_agg(json_build_object(
|
||||
let events = sqlx::query(
|
||||
r#"select to_json(json_agg(json_build_object(
|
||||
'title', ce.title,
|
||||
'start', ce.start_time,
|
||||
'end', ce.end_time,
|
||||
|
|
@ -122,7 +130,8 @@ pub async fn get_events(
|
|||
where ce.celebrate = true
|
||||
and c.name = $1
|
||||
and start_time > $2
|
||||
and start_time < $3"#)
|
||||
and start_time < $3"#,
|
||||
)
|
||||
.bind(calendar)
|
||||
.bind(chrono::DateTime::parse_from_rfc3339(¶ms.start).unwrap())
|
||||
.bind(chrono::DateTime::parse_from_rfc3339(¶ms.end).unwrap())
|
||||
|
|
@ -130,8 +139,10 @@ pub async fn get_events(
|
|||
.await;
|
||||
|
||||
if let Ok(json_string) = events {
|
||||
if let Ok(stringevents) = json_string.try_get_raw(0).map(|v| v.as_str().unwrap_or("")) {
|
||||
println!("PgValue: {:?}", stringevents);
|
||||
if let Ok(stringevents) =
|
||||
json_string.try_get_raw(0).map(|v| v.as_str().unwrap_or(""))
|
||||
{
|
||||
//println!("PgValue: {:?}", stringevents);
|
||||
eventstring = stringevents.to_string();
|
||||
}
|
||||
}
|
||||
|
|
@ -140,3 +151,110 @@ pub async fn get_events(
|
|||
|
||||
eventstring
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct Event {
|
||||
pub id: uuid::Uuid,
|
||||
pub created_at: chrono::NaiveDateTime,
|
||||
pub created_by: uuid::Uuid,
|
||||
pub updated_at: chrono::NaiveDateTime,
|
||||
pub updated_by: uuid::Uuid,
|
||||
pub calendar_id: uuid::Uuid,
|
||||
pub event_type_id: uuid::Uuid,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub state: String,
|
||||
pub start_time: chrono::NaiveDateTime,
|
||||
pub end_time: chrono::NaiveDateTime,
|
||||
pub celebrate: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct EventCreate {
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub state: String,
|
||||
pub calendar_id: uuid::Uuid,
|
||||
pub event_type_id: uuid::Uuid,
|
||||
pub start_time: String,
|
||||
pub end_time: String,
|
||||
}
|
||||
|
||||
pub async fn create_event(
|
||||
State(db_pool): State<PgPool>,
|
||||
Extension(user_data): Extension<Option<UserData>>,
|
||||
Form(event): Form<EventCreate>,
|
||||
) -> impl IntoResponse {
|
||||
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 start_datetime = start_date.and_hms_opt(14, 0, 0).unwrap();
|
||||
let end_datetime = end_date.and_hms_opt(10, 0, 0).unwrap();
|
||||
|
||||
let start_dt = start_datetime.and_utc();
|
||||
let end_dt = end_datetime.and_utc();
|
||||
|
||||
let _ = sqlx::query(
|
||||
r#"INSERT INTO calendar_events (created_by, updated_by, calendar_id, event_type_id, title, description, state, start_time, end_time)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)"#)
|
||||
.bind(user_data.as_ref().unwrap().id)// Created by current user
|
||||
.bind(user_data.as_ref().unwrap().id) // Updated by current user
|
||||
.bind(event.calendar_id)
|
||||
.bind(event.event_type_id)
|
||||
.bind(event.title)
|
||||
.bind(event.description)
|
||||
.bind(event.state)
|
||||
.bind(start_dt)
|
||||
.bind(end_dt)
|
||||
.execute(&db_pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, format!("Error creating event: {}", e))
|
||||
});
|
||||
}
|
||||
|
||||
let redirect_url = format!("/calendar");
|
||||
Redirect::to(&redirect_url).into_response()
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "newevent.html")]
|
||||
struct EventTemplate {
|
||||
logged_in: bool,
|
||||
user: UserData,
|
||||
user_roles: Vec<crate::user::UserRolesDisplay>,
|
||||
calendars: Vec<Calendar>,
|
||||
}
|
||||
|
||||
pub async fn new_event(
|
||||
Extension(user_data): Extension<Option<UserData>>,
|
||||
State(db_pool): State<PgPool>,
|
||||
) -> impl IntoResponse {
|
||||
// Is the user logged in?
|
||||
let logged_in = user_data.is_some();
|
||||
|
||||
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();
|
||||
|
||||
if is_authorized("/calendar", user_data, db_pool.clone()).await {
|
||||
// Get user roles
|
||||
let user_roles = get_user_roles_display(userid, &db_pool.clone()).await;
|
||||
let calendars: Vec<Calendar> = get_calendars(db_pool.clone()).await;
|
||||
|
||||
let template = EventTemplate {
|
||||
logged_in,
|
||||
user,
|
||||
user_roles,
|
||||
calendars,
|
||||
};
|
||||
HtmlTemplate(template).into_response()
|
||||
} else {
|
||||
Redirect::to("/").into_response()
|
||||
}
|
||||
} else {
|
||||
Redirect::to("/").into_response()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use axum::{
|
||||
middleware,
|
||||
routing::{get, get_service},
|
||||
routing::{get, get_service, post, put},
|
||||
Extension, Router,
|
||||
};
|
||||
use dotenvy::var;
|
||||
|
|
@ -20,7 +20,7 @@ mod secret_gift_exchange;
|
|||
mod user;
|
||||
mod wishlist;
|
||||
|
||||
use calendar::{calendar, get_events};
|
||||
use calendar::{calendar, get_events, create_event, new_event};
|
||||
use error_handling::AppError;
|
||||
use google_oauth::{google_auth_return, login, logout};
|
||||
use middlewares::inject_user_data;
|
||||
|
|
@ -87,6 +87,8 @@ async fn main() {
|
|||
// Calendar
|
||||
.route("/calendar", get(calendar))
|
||||
.route("/getevents/{calendar}", get(get_events))
|
||||
.route("/createevent", post(create_event))
|
||||
.route("/newevent", get(new_event))
|
||||
// Wishlist
|
||||
.route("/wishlists", get(wishlists))
|
||||
.route("/userwishlist/{user_id}", get(user_wishlist))
|
||||
|
|
|
|||
|
|
@ -170,8 +170,8 @@ pub async fn user_profile(
|
|||
|
||||
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 user = user_data.as_ref().unwrap().clone();
|
||||
//let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
|
||||
|
||||
// Extract the user data.
|
||||
let profile: UserData = sqlx::query_as( "SELECT * FROM people WHERE id = $1")
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
<ul>
|
||||
<li><a href="/useradmin">User Administration</a></li>
|
||||
<li><a href="/giftexchanges">Gift Exchanges</a></li>
|
||||
<li><a href="/newevent">Add Event</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
body {
|
||||
font-family: "Montserrat", sans-serif;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
{% block links %}{% endblock links %}
|
||||
|
|
@ -30,7 +32,8 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid" height="100%">
|
||||
<div class="container-fluid" height="100vh">
|
||||
<div class="row vh-100">
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="row fixed-top sticky-top">
|
||||
|
|
@ -64,7 +67,7 @@
|
|||
</div>
|
||||
|
||||
<!-- CONTENT -->
|
||||
<div class="row flex-grow-1">
|
||||
<div class="row flex-1">
|
||||
{% block content %}{% endblock content %}
|
||||
</div>
|
||||
|
||||
|
|
@ -76,6 +79,7 @@
|
|||
</footer>
|
||||
</div><!-- /.container -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
{% extends "authorized.html" %}
|
||||
{% block center %}
|
||||
<h1>Create Event</h1>
|
||||
|
||||
<form method="post" action="/createevent">
|
||||
<div>
|
||||
<label for="title">Title:</label>
|
||||
<input type="text" id="title" name="title" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="description">Description:</label>
|
||||
<textarea id="description" name="description"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="state">State:</label>
|
||||
<select id="state" name="state" required>
|
||||
<option value="pending">Pending</option>
|
||||
<option value="approved">Approved</option>
|
||||
<option value="rejected">Rejected</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="calendar_id">Calendar ID:</label>
|
||||
<select id="calendar_id" name="calendar_id" required>
|
||||
{% for cal in calendars %}
|
||||
<option value="{{ cal.id }}">{{ cal.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="event_type_id">Event Type ID:</label>
|
||||
<input type="text" id="event_type_id" name="event_type_id" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="start_time">Start Date:</label>
|
||||
<input type="date" id="start_time" name="start_time" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="end_time">End Date:</label>
|
||||
<input type="date" id="end_time" name="end_time" required>
|
||||
</div>
|
||||
<button type="submit">Create Event</button>
|
||||
</form>
|
||||
{% endblock center %}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
cargo build --release
|
||||
ssh www@192.168.59.11 'pkill jean-marie'
|
||||
#ssh www@192.168.59.11 'pkill jean-marie'
|
||||
scp target/release/jean-marie www@192.168.59.11:/opt/jean-marie
|
||||
scp runsite.sh www@192.168.59.11:/opt/jean-marie
|
||||
scp prod.env www@192.168.59.11:/opt/jean-marie
|
||||
|
|
|
|||
Loading…
Reference in New Issue