Compare commits

...

2 Commits

Author SHA1 Message Date
Chris Jean-Marie 8032019807 Fix wishlist for new people table 2025-06-02 23:55:25 +00:00
Chris Jean-Marie 682ea672c4 Continue calendar code 2025-06-01 03:14:28 +00:00
20 changed files with 369 additions and 115 deletions

View File

@ -9,5 +9,6 @@
"now.instance.host.url": "", "now.instance.host.url": "",
"now.instance.OAuth.client.id": "", "now.instance.OAuth.client.id": "",
"now.instance.user.password": "", "now.instance.user.password": "",
"now.instance.OAuth.client.secret": "" "now.instance.OAuth.client.secret": "",
"postman.settings.dotenv-detection-notification-visibility": false
} }

View File

@ -0,0 +1,3 @@
-- Remove column from calendar_events
ALTER TABLE if exists calendar_events
drop column if exists state;

View File

@ -0,0 +1,3 @@
-- Add column to calendar_events
ALTER TABLE if exists calendar_events
ADD COLUMN IF NOT EXISTS state character varying(25);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -1,14 +1,15 @@
use askama::Template; use askama::Template;
use axum::{ use axum::{
extract::{Path, Query, State}, response::{Html, IntoResponse, Redirect, Response}, Extension extract::{Path, Query, State}, response::{Html, IntoResponse, Redirect, Response}, Extension, Form
}; };
use http::StatusCode; use http::StatusCode;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{PgPool, Row}; use sqlx::{FromRow, PgPool, Row};
use uuid::Uuid;
use crate::{ use crate::{
middlewares::is_authorized, middlewares::is_authorized,
user::{get_user_roles_display, UserData}, user::{get_user_roles_display, AccountData},
}; };
struct HtmlTemplate<T>(T); struct HtmlTemplate<T>(T);
@ -29,7 +30,9 @@ where
} }
} }
struct Calendar { #[derive(Default, Clone, Debug, Serialize, Deserialize, FromRow)]
pub struct Calendar {
id: Uuid,
name: String, name: String,
colour: String, colour: String,
} }
@ -38,13 +41,13 @@ struct Calendar {
#[template(path = "calendar.html")] #[template(path = "calendar.html")]
struct CalendarTemplate { struct CalendarTemplate {
logged_in: bool, logged_in: bool,
user: UserData, user: AccountData,
user_roles: Vec<crate::user::UserRolesDisplay>, user_roles: Vec<crate::user::UserRolesDisplay>,
calendars: Vec<Calendar>, calendars: Vec<Calendar>,
} }
pub async fn calendar( pub async fn calendar(
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
@ -59,16 +62,7 @@ pub async fn calendar(
// Get user roles // Get user roles
let user_roles = get_user_roles_display(userid, &db_pool.clone()).await; let user_roles = get_user_roles_display(userid, &db_pool.clone()).await;
let calendars: Vec<Calendar> = vec![ let calendars: Vec<Calendar> = get_calendars(db_pool.clone()).await;
Calendar {
name: "Cottage".to_string(),
colour: "green".to_string(),
},
Calendar {
name: "Personal".to_string(),
colour: "blue".to_string(),
},
];
let template = CalendarTemplate { let template = CalendarTemplate {
logged_in, logged_in,
@ -90,13 +84,23 @@ pub struct EventParams {
start: String, start: String,
end: 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( pub async fn get_events(
Path(calendar): Path<String>, Path(calendar): Path<String>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Query(params): Query<EventParams>, Query(params): Query<EventParams>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
) -> String { ) -> String {
println!("Calendar: {}", calendar); //println!("Calendar: {}", calendar);
//println!("Paramters: {:?}", params); //println!("Paramters: {:?}", params);
// Is the user logged in? // Is the user logged in?
let logged_in = user_data.is_some(); let logged_in = user_data.is_some();
@ -111,7 +115,8 @@ pub async fn get_events(
if is_authorized("/calendar", user_data, db_pool.clone()).await { if is_authorized("/calendar", user_data, db_pool.clone()).await {
// Get requested calendar events from database // 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, 'title', ce.title,
'start', ce.start_time, 'start', ce.start_time,
'end', ce.end_time, 'end', ce.end_time,
@ -122,21 +127,131 @@ pub async fn get_events(
where ce.celebrate = true where ce.celebrate = true
and c.name = $1 and c.name = $1
and start_time > $2 and start_time > $2
and start_time < $3"#) and start_time < $3"#,
.bind(calendar) )
.bind(chrono::DateTime::parse_from_rfc3339(&params.start).unwrap()) .bind(calendar)
.bind(chrono::DateTime::parse_from_rfc3339(&params.end).unwrap()) .bind(chrono::DateTime::parse_from_rfc3339(&params.start).unwrap())
.fetch_one(&db_pool) .bind(chrono::DateTime::parse_from_rfc3339(&params.end).unwrap())
.await; .fetch_one(&db_pool)
.await;
if let Ok(json_string) = events { if let Ok(json_string) = events {
if let Ok(stringevents) = json_string.try_get_raw(0).map(|v| v.as_str().unwrap_or("")) { if let Ok(stringevents) =
println!("PgValue: {:?}", stringevents); json_string.try_get_raw(0).map(|v| v.as_str().unwrap_or(""))
eventstring = stringevents.to_string(); {
} //println!("PgValue: {:?}", stringevents);
eventstring = stringevents.to_string();
} }
}
} }
} }
eventstring 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<AccountData>>,
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: AccountData,
user_roles: Vec<crate::user::UserRolesDisplay>,
calendars: Vec<Calendar>,
}
pub async fn new_event(
Extension(user_data): Extension<Option<AccountData>>,
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()
}
}

View File

@ -21,10 +21,10 @@ use sqlx::PgPool;
use std::collections::HashMap; use std::collections::HashMap;
use uuid::Uuid; use uuid::Uuid;
use crate::user::get_useremails_by_role; use crate::user::{get_useremails_by_role, AccountData};
use crate::email::send_emails; use crate::email::send_emails;
use super::{AppError, UserData}; use super::{AppError};
fn get_client(hostname: String) -> Result<BasicClient, AppError> { fn get_client(hostname: String) -> Result<BasicClient, AppError> {
let google_client_id = ClientId::new(var("GOOGLE_CLIENT_ID")?); let google_client_id = ClientId::new(var("GOOGLE_CLIENT_ID")?);
@ -58,7 +58,7 @@ fn get_client(hostname: String) -> Result<BasicClient, AppError> {
} }
pub async fn login( pub async fn login(
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
Query(mut params): Query<HashMap<String, String>>, Query(mut params): Query<HashMap<String, String>>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Host(hostname): Host, Host(hostname): Host,

View File

@ -1,6 +1,6 @@
use axum::{ use axum::{
middleware, middleware,
routing::{get, get_service}, routing::{get, get_service, post, put},
Extension, Router, Extension, Router,
}; };
use dotenvy::var; use dotenvy::var;
@ -20,12 +20,12 @@ mod secret_gift_exchange;
mod user; mod user;
mod wishlist; mod wishlist;
use calendar::{calendar, get_events}; use calendar::{calendar, get_events, create_event, new_event};
use error_handling::AppError; use error_handling::AppError;
use google_oauth::{google_auth_return, login, logout}; use google_oauth::{google_auth_return, login, logout};
use middlewares::inject_user_data; use middlewares::inject_user_data;
use routes::{about, contact, dashboard, index, profile, user_profile, user_profile_account, useradmin}; use routes::{about, contact, dashboard, index, profile, user_profile, user_profile_account, useradmin};
use user::{add_user_role, delete_user_role, UserData}; use user::{add_user_role, delete_user_role, AccountData};
use wishlist::{ use wishlist::{
user_wishlist, user_wishlist_add, user_wishlist_add_item, user_wishlist_bought_item, user_wishlist, user_wishlist_add, user_wishlist_add_item, user_wishlist_bought_item,
user_wishlist_delete_item, user_wishlist_edit_item, user_wishlist_received_item, user_wishlist_delete_item, user_wishlist_edit_item, user_wishlist_received_item,
@ -69,7 +69,7 @@ async fn main() {
println!("SOURCE_DB_URL not set"); println!("SOURCE_DB_URL not set");
} }
let user_data: Option<UserData> = None; let user_data: Option<AccountData> = None;
// build our application with some routes // build our application with some routes
let app = Router::new() let app = Router::new()
@ -87,6 +87,8 @@ async fn main() {
// Calendar // Calendar
.route("/calendar", get(calendar)) .route("/calendar", get(calendar))
.route("/getevents/{calendar}", get(get_events)) .route("/getevents/{calendar}", get(get_events))
.route("/createevent", post(create_event))
.route("/newevent", get(new_event))
// Wishlist // Wishlist
.route("/wishlists", get(wishlists)) .route("/wishlists", get(wishlists))
.route("/userwishlist/{user_id}", get(user_wishlist)) .route("/userwishlist/{user_id}", get(user_wishlist))

View File

@ -1,6 +1,6 @@
use std::path::Path; use std::path::Path;
use super::{AppError, UserData}; use super::{AppError, AccountData};
use axum::{body::Body, extract::State, http::Request, middleware::Next, response::IntoResponse}; use axum::{body::Body, extract::State, http::Request, middleware::Next, response::IntoResponse};
use axum_extra::TypedHeader; use axum_extra::TypedHeader;
use chrono::Utc; use chrono::Utc;
@ -39,15 +39,15 @@ pub async fn inject_user_data(
let id = query.0; let id = query.0;
let expires_at = query.1; let expires_at = query.1;
if expires_at > Utc::now().naive_local() { if expires_at > Utc::now().naive_local() {
let row: UserData = sqlx::query_as( let row: AccountData = sqlx::query_as(
r#"SELECT id, created_at, created_by, updated_at, updated_by, email, name, family_name, given_name FROM users WHERE id = $1"#, r#"SELECT id, created_at, created_by, updated_at, updated_by, email, name, family_name, given_name, person_id FROM users WHERE id = $1"#,
) )
.bind(id) .bind(id)
.fetch_one(&db_pool) .fetch_one(&db_pool)
.await .await
.unwrap(); .unwrap();
request.extensions_mut().insert(Some(UserData { request.extensions_mut().insert(Some(AccountData {
id: row.id, id: row.id,
created_at: row.created_at, created_at: row.created_at,
created_by: row.created_by, created_by: row.created_by,
@ -57,6 +57,7 @@ pub async fn inject_user_data(
name: row.name, name: row.name,
family_name: row.family_name, family_name: row.family_name,
given_name: row.given_name, given_name: row.given_name,
person_id: row.person_id,
})); }));
} }
} }
@ -69,7 +70,7 @@ pub async fn inject_user_data(
Ok(next.run(request).await) Ok(next.run(request).await)
} }
pub async fn is_authorized(path: &str, user_data: Option<UserData>, db_pool: PgPool) -> bool { pub async fn is_authorized(path: &str, user_data: Option<AccountData>, db_pool: PgPool) -> bool {
if let Some(user_data) = user_data { if let Some(user_data) = user_data {
let query: Result<(uuid::Uuid,), _> = match path { let query: Result<(uuid::Uuid,), _> = match path {
"/profile" => { "/profile" => {

View File

@ -10,15 +10,14 @@ use uuid::Uuid;
use crate::{ use crate::{
middlewares::is_authorized, middlewares::is_authorized,
user::{get_other_roles_display, get_user_roles_display, AccountData}, user::{get_other_roles_display, get_user_roles_display, AccountData, PersonData},
UserData,
}; };
#[derive(Template)] #[derive(Template)]
#[template(path = "profile.html")] #[template(path = "profile.html")]
struct ProfileTemplate { struct ProfileTemplate {
logged_in: bool, logged_in: bool,
user: UserData, user: AccountData,
user_roles: Vec<crate::user::UserRolesDisplay>, user_roles: Vec<crate::user::UserRolesDisplay>,
} }
@ -26,9 +25,9 @@ struct ProfileTemplate {
#[template(path = "user.html")] #[template(path = "user.html")]
struct UserProfileTemplate { struct UserProfileTemplate {
logged_in: bool, logged_in: bool,
user: UserData, user: AccountData,
user_roles: Vec<crate::user::UserRolesDisplay>, user_roles: Vec<crate::user::UserRolesDisplay>,
profile: UserData, profile: PersonData,
profile_accounts: Vec<AccountData>, profile_accounts: Vec<AccountData>,
account: AccountData, account: AccountData,
profile_roles: Vec<crate::user::UserRolesDisplay>, profile_roles: Vec<crate::user::UserRolesDisplay>,
@ -57,21 +56,21 @@ where
#[template(path = "index.html")] #[template(path = "index.html")]
struct IndexTemplate { struct IndexTemplate {
logged_in: bool, logged_in: bool,
user: UserData, user: AccountData,
} }
#[derive(Template)] #[derive(Template)]
#[template(path = "dashboard.html")] #[template(path = "dashboard.html")]
struct DashboardTemplate { struct DashboardTemplate {
logged_in: bool, logged_in: bool,
user: UserData, user: AccountData,
user_roles: Vec<crate::user::UserRolesDisplay>, user_roles: Vec<crate::user::UserRolesDisplay>,
fire_rating: String, fire_rating: String,
} }
pub async fn index( pub async fn index(
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
let logged_in = user_data.is_some(); let logged_in = user_data.is_some();
@ -89,7 +88,7 @@ pub async fn index(
} else { } else {
let template = IndexTemplate { let template = IndexTemplate {
logged_in, logged_in,
user: UserData::default(), user: AccountData::default(),
}; };
HtmlTemplate(template).into_response() HtmlTemplate(template).into_response()
} }
@ -97,7 +96,7 @@ pub async fn index(
pub async fn dashboard( pub async fn dashboard(
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
let logged_in = user_data.is_some(); let logged_in = user_data.is_some();
@ -131,7 +130,7 @@ pub async fn dashboard(
/// Handles the profile page. /// Handles the profile page.
pub async fn profile( pub async fn profile(
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
let logged_in = user_data.is_some(); let logged_in = user_data.is_some();
@ -163,18 +162,18 @@ pub async fn profile(
pub async fn user_profile( pub async fn user_profile(
Path(user_id): Path<uuid::Uuid>, Path(user_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
let logged_in = user_data.is_some(); let logged_in = user_data.is_some();
if logged_in { if logged_in {
// Extract the user data. // Extract the user data.
let user = user_data.as_ref().unwrap().clone(); //let user = user_data.as_ref().unwrap().clone();
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default(); //let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
// Extract the user data. // Extract the user data.
let profile: UserData = sqlx::query_as( "SELECT * FROM people WHERE id = $1") let profile: PersonData = sqlx::query_as( "SELECT * FROM people WHERE id = $1")
.bind(user_id) .bind(user_id)
.fetch_one(&db_pool) .fetch_one(&db_pool)
.await .await
@ -201,7 +200,7 @@ pub async fn user_profile(
pub async fn user_profile_account( pub async fn user_profile_account(
Path((user_id, account_id)): Path<(uuid::Uuid, uuid::Uuid)>, Path((user_id, account_id)): Path<(uuid::Uuid, uuid::Uuid)>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
let logged_in = user_data.is_some(); let logged_in = user_data.is_some();
@ -277,13 +276,13 @@ pub async fn user_profile_account(
#[template(path = "useradmin.html")] #[template(path = "useradmin.html")]
struct UserAdminTemplate { struct UserAdminTemplate {
logged_in: bool, logged_in: bool,
user: UserData, user: AccountData,
user_roles: Vec<crate::user::UserRolesDisplay>, user_roles: Vec<crate::user::UserRolesDisplay>,
users: Vec<UserData>, users: Vec<PersonData>,
} }
pub async fn useradmin( pub async fn useradmin(
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
@ -294,7 +293,7 @@ pub async fn useradmin(
let user = user_data.as_ref().unwrap().clone(); let user = user_data.as_ref().unwrap().clone();
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default(); let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
let users = sqlx::query_as::<_, UserData>("SELECT * FROM people order by name") let users = sqlx::query_as::<_, PersonData>("SELECT * FROM people order by name")
.fetch_all(&db_pool) .fetch_all(&db_pool)
.await .await
.unwrap(); .unwrap();
@ -322,15 +321,15 @@ pub async fn useradmin(
#[template(path = "about.html")] #[template(path = "about.html")]
struct AboutTemplate { struct AboutTemplate {
logged_in: bool, logged_in: bool,
user: UserData, user: AccountData,
} }
pub async fn about(Extension(user_data): Extension<Option<UserData>>) -> impl IntoResponse { pub async fn about(Extension(user_data): Extension<Option<AccountData>>) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
let logged_in = user_data.is_some(); let logged_in = user_data.is_some();
// Set empty user // Set empty user
let mut user = UserData::default(); let mut user = AccountData::default();
if logged_in { if logged_in {
// Extract the user data. // Extract the user data.
@ -345,15 +344,15 @@ pub async fn about(Extension(user_data): Extension<Option<UserData>>) -> impl In
#[template(path = "contactus.html")] #[template(path = "contactus.html")]
struct ContactTemplate { struct ContactTemplate {
logged_in: bool, logged_in: bool,
user: UserData, user: AccountData,
} }
pub async fn contact(Extension(user_data): Extension<Option<UserData>>) -> impl IntoResponse { pub async fn contact(Extension(user_data): Extension<Option<AccountData>>) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
let logged_in = user_data.is_some(); let logged_in = user_data.is_some();
// Set empty user // Set empty user
let mut user = UserData::default(); let mut user = AccountData::default();
if logged_in { if logged_in {
// Extract the user data. // Extract the user data.

View File

@ -11,7 +11,7 @@ use sqlx::{FromRow, PgPool};
use crate::{ use crate::{
middlewares::is_authorized, middlewares::is_authorized,
user::{get_user_roles_display, UserData}, user::{get_user_roles_display, AccountData, PersonData},
}; };
/// Select participants from user list /// Select participants from user list
@ -87,13 +87,13 @@ struct GiftExchange {
#[template(path = "giftexchanges.html")] #[template(path = "giftexchanges.html")]
struct GiftExchangesTemplate { struct GiftExchangesTemplate {
logged_in: bool, logged_in: bool,
user: UserData, user: AccountData,
user_roles: Vec<crate::user::UserRolesDisplay>, user_roles: Vec<crate::user::UserRolesDisplay>,
giftexchanges: Vec<GiftExchange>, giftexchanges: Vec<GiftExchange>,
} }
pub async fn giftexchanges( pub async fn giftexchanges(
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
@ -144,16 +144,16 @@ struct GiftExchangeParticipant {
#[template(path = "giftexchange.html")] #[template(path = "giftexchange.html")]
struct GiftExchangeTemplate { struct GiftExchangeTemplate {
logged_in: bool, logged_in: bool,
user: UserData, user: AccountData,
user_roles: Vec<crate::user::UserRolesDisplay>, user_roles: Vec<crate::user::UserRolesDisplay>,
giftexchange: GiftExchange, giftexchange: GiftExchange,
participants: Vec<UserData>, participants: Vec<PersonData>,
non_participants: Vec<UserData>, non_participants: Vec<PersonData>,
} }
pub async fn giftexchange( pub async fn giftexchange(
Path(exchange_id): Path<i64>, Path(exchange_id): Path<i64>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
@ -180,7 +180,7 @@ pub async fn giftexchange(
}; };
// Get participants // Get participants
let participants = sqlx::query_as::<_, UserData>( let participants = sqlx::query_as::<_, PersonData>(
"select * from users where users.id in (select participant_id from gift_exchange_participants where exchange_id = $1)", "select * from users where users.id in (select participant_id from gift_exchange_participants where exchange_id = $1)",
) )
.bind(exchange_id) .bind(exchange_id)
@ -189,7 +189,7 @@ pub async fn giftexchange(
.unwrap(); .unwrap();
// Get non participants // Get non participants
let non_participants = sqlx::query_as::<_, UserData>( let non_participants = sqlx::query_as::<_, PersonData>(
"select * from users where users.id not in (select participant_id from gift_exchange_participants where exchange_id = $1)", "select * from users where users.id not in (select participant_id from gift_exchange_participants where exchange_id = $1)",
) )
.bind(exchange_id) .bind(exchange_id)

View File

@ -12,7 +12,7 @@ use uuid::Uuid;
use crate::middlewares::is_authorized; use crate::middlewares::is_authorized;
#[derive(Default, Clone, Debug, Serialize, Deserialize, FromRow)] #[derive(Default, Clone, Debug, Serialize, Deserialize, FromRow)]
pub struct UserData { pub struct PersonData {
pub id: uuid::Uuid, pub id: uuid::Uuid,
pub created_at: chrono::NaiveDateTime, pub created_at: chrono::NaiveDateTime,
pub created_by: uuid::Uuid, pub created_by: uuid::Uuid,
@ -137,7 +137,7 @@ pub async fn get_other_roles_display(
pub async fn add_user_role( pub async fn add_user_role(
Path((account_id, role_id)): Path<(uuid::Uuid, uuid::Uuid)>, Path((account_id, role_id)): Path<(uuid::Uuid, uuid::Uuid)>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
if is_authorized("/roles", user_data.clone(), db_pool.clone()).await { if is_authorized("/roles", user_data.clone(), db_pool.clone()).await {
sqlx::query("INSERT INTO user_roles (created_by, updated_by, user_id, role_id) VALUES ($1, $2, $3, $4)") sqlx::query("INSERT INTO user_roles (created_by, updated_by, user_id, role_id) VALUES ($1, $2, $3, $4)")
@ -166,7 +166,7 @@ pub async fn add_user_role(
pub async fn delete_user_role( pub async fn delete_user_role(
Path((account_id, user_role_id)): Path<(uuid::Uuid, uuid::Uuid)>, Path((account_id, user_role_id)): Path<(uuid::Uuid, uuid::Uuid)>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
if is_authorized("/roles", user_data, db_pool.clone()).await { if is_authorized("/roles", user_data, db_pool.clone()).await {
sqlx::query("DELETE FROM user_roles WHERE id = $1") sqlx::query("DELETE FROM user_roles WHERE id = $1")
@ -235,7 +235,7 @@ pub async fn get_useremails_by_role(role_name: String, db_pool: &PgPool) -> Stri
pub async fn user_account( pub async fn user_account(
Path(account_id): Path<uuid::Uuid>, Path(account_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
let logged_in = user_data.is_some(); let logged_in = user_data.is_some();

View File

@ -13,7 +13,7 @@ use uuid::Uuid;
use crate::{ use crate::{
middlewares::is_authorized, middlewares::is_authorized,
user::{ user::{
get_user_roles_display, get_user_wishlist_item_by_id, get_user_wishlist_items, UserData, get_user_roles_display, get_user_wishlist_item_by_id, get_user_wishlist_items, PersonData, AccountData,
UserWishlistItem, UserWishlistItem,
}, },
}; };
@ -40,13 +40,13 @@ where
#[template(path = "userwishlists.html")] #[template(path = "userwishlists.html")]
struct WishListsTemplate { struct WishListsTemplate {
logged_in: bool, logged_in: bool,
user: UserData, user: AccountData,
user_roles: Vec<crate::user::UserRolesDisplay>, user_roles: Vec<crate::user::UserRolesDisplay>,
users: Vec<UserData>, users: Vec<PersonData>,
} }
pub async fn wishlists( pub async fn wishlists(
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
@ -57,7 +57,7 @@ pub async fn wishlists(
let user = user_data.as_ref().unwrap().clone(); let user = user_data.as_ref().unwrap().clone();
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default(); let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
let users = sqlx::query_as::<_, UserData>(r#"SELECT p.* FROM people p let users = sqlx::query_as::<_, PersonData>(r#"SELECT p.* FROM people p
left join user_roles ur on ur.user_id = p.id left join user_roles ur on ur.user_id = p.id
left join roles r on r.id = ur.role_id left join roles r on r.id = ur.role_id
where r.name = 'normal' order by p.name;"#) where r.name = 'normal' order by p.name;"#)
@ -88,17 +88,17 @@ pub async fn wishlists(
#[template(path = "userwishlist.html")] #[template(path = "userwishlist.html")]
struct UserWishListTemplate { struct UserWishListTemplate {
logged_in: bool, logged_in: bool,
user: UserData, user: AccountData,
user_roles: Vec<crate::user::UserRolesDisplay>, user_roles: Vec<crate::user::UserRolesDisplay>,
my_wishlist: bool, my_wishlist: bool,
person: UserData, person: PersonData,
person_wishlist_items: Vec<crate::user::UserWishlistItem>, person_wishlist_items: Vec<crate::user::UserWishlistItem>,
} }
pub async fn user_wishlist( pub async fn user_wishlist(
Path(user_id): Path<uuid::Uuid>, Path(user_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
let logged_in = user_data.is_some(); let logged_in = user_data.is_some();
@ -109,7 +109,7 @@ pub async fn user_wishlist(
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default(); let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
// Extract the user data. // Extract the user data.
let person = sqlx::query_as("SELECT * FROM people WHERE id = $1") let person = sqlx::query_as("SELECT * FROM people WHERE id = $1 or id = (select person_id from users where id = $1)")
.bind(user_id) .bind(user_id)
.fetch_one(&db_pool) .fetch_one(&db_pool)
.await .await
@ -123,7 +123,7 @@ pub async fn user_wishlist(
let person_wishlist_items = get_user_wishlist_items(user_id, &db_pool.clone()).await; let person_wishlist_items = get_user_wishlist_items(user_id, &db_pool.clone()).await;
// Is viewed and viewing user the same (my wishlist)? // Is viewed and viewing user the same (my wishlist)?
let my_wishlist = user_id == userid; let my_wishlist = user_id == user.person_id;
// Create the wishlist template. // Create the wishlist template.
let template = UserWishListTemplate { let template = UserWishListTemplate {
@ -147,16 +147,16 @@ pub async fn user_wishlist(
#[template(path = "userwishlistadd.html")] #[template(path = "userwishlistadd.html")]
struct UserWishListAddTemplate { struct UserWishListAddTemplate {
logged_in: bool, logged_in: bool,
user: UserData, user: AccountData,
user_roles: Vec<crate::user::UserRolesDisplay>, user_roles: Vec<crate::user::UserRolesDisplay>,
person: UserData, person: PersonData,
person_wishlist_items: Vec<crate::user::UserWishlistItem>, person_wishlist_items: Vec<crate::user::UserWishlistItem>,
} }
pub async fn user_wishlist_add( pub async fn user_wishlist_add(
Path(user_id): Path<uuid::Uuid>, Path(user_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
let logged_in = user_data.is_some(); let logged_in = user_data.is_some();
@ -167,7 +167,7 @@ pub async fn user_wishlist_add(
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default(); let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
// Extract the user data. // Extract the user data.
let person = sqlx::query_as("SELECT * FROM users WHERE id = $1") let person = sqlx::query_as("SELECT * FROM people WHERE id = $1")
.bind(user_id) .bind(user_id)
.fetch_one(&db_pool) .fetch_one(&db_pool)
.await .await
@ -206,7 +206,7 @@ pub struct ItemForm {
pub async fn user_wishlist_add_item( pub async fn user_wishlist_add_item(
Path(user_id): Path<uuid::Uuid>, Path(user_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
Form(item_form): Form<ItemForm>, Form(item_form): Form<ItemForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await { if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
@ -221,7 +221,9 @@ pub async fn user_wishlist_add_item(
.await .await
.unwrap(); .unwrap();
let redirect_string = format!("/userwishlist/{user_id}"); // Redirect to wishlist
let person_id = user_data.as_ref().unwrap().person_id;
let redirect_string = format!("/userwishlist/{person_id}");
Redirect::to(&redirect_string).into_response() Redirect::to(&redirect_string).into_response()
} else { } else {
Redirect::to("/").into_response() Redirect::to("/").into_response()
@ -232,7 +234,7 @@ pub async fn user_wishlist_add_item(
#[template(path = "userwishlistedit.html")] #[template(path = "userwishlistedit.html")]
struct UserWishListEditTemplate { struct UserWishListEditTemplate {
logged_in: bool, logged_in: bool,
user: UserData, user: AccountData,
user_roles: Vec<crate::user::UserRolesDisplay>, user_roles: Vec<crate::user::UserRolesDisplay>,
user_wishlist_item: crate::user::UserWishlistItem, user_wishlist_item: crate::user::UserWishlistItem,
} }
@ -240,7 +242,7 @@ struct UserWishListEditTemplate {
pub async fn user_wishlist_edit_item( pub async fn user_wishlist_edit_item(
Path(item_id): Path<uuid::Uuid>, Path(item_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
let logged_in = user_data.is_some(); let logged_in = user_data.is_some();
@ -277,7 +279,7 @@ pub async fn user_wishlist_edit_item(
pub async fn user_wishlist_save_item( pub async fn user_wishlist_save_item(
Path(item_id): Path<uuid::Uuid>, Path(item_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
Form(item_form): Form<ItemForm>, Form(item_form): Form<ItemForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await { if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
@ -294,8 +296,8 @@ pub async fn user_wishlist_save_item(
.await .await
.unwrap(); .unwrap();
let user_id = user_data.as_ref().unwrap().id; let person_id = user_data.as_ref().unwrap().person_id;
let redirect_string = format!("/userwishlist/{user_id}"); let redirect_string = format!("/userwishlist/{person_id}");
Redirect::to(&redirect_string).into_response() Redirect::to(&redirect_string).into_response()
} else { } else {
Redirect::to("/").into_response() Redirect::to("/").into_response()
@ -305,7 +307,7 @@ pub async fn user_wishlist_save_item(
pub async fn user_wishlist_bought_item( pub async fn user_wishlist_bought_item(
Path(item_id): Path<uuid::Uuid>, Path(item_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await { if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
// Update item to purchased // Update item to purchased
@ -335,7 +337,7 @@ pub async fn user_wishlist_bought_item(
pub async fn user_wishlist_received_item( pub async fn user_wishlist_received_item(
Path(user_id): Path<uuid::Uuid>, Path(user_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await { if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
// Update item received time // Update item received time
@ -349,8 +351,8 @@ pub async fn user_wishlist_received_item(
.unwrap(); .unwrap();
// Redirect to user wishlist // Redirect to user wishlist
let userid = user_data.as_ref().unwrap().id; let person_id = user_data.as_ref().unwrap().person_id;
let redirect_string = format!("/userwishlist/{userid}"); let redirect_string = format!("/userwishlist/{person_id}");
Redirect::to(&redirect_string).into_response() Redirect::to(&redirect_string).into_response()
} else { } else {
Redirect::to("/").into_response() Redirect::to("/").into_response()
@ -360,7 +362,7 @@ pub async fn user_wishlist_received_item(
pub async fn user_wishlist_delete_item( pub async fn user_wishlist_delete_item(
Path(item_id): Path<uuid::Uuid>, Path(item_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await { if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
sqlx::query("delete from wishlist_items where id = $1") sqlx::query("delete from wishlist_items where id = $1")
@ -370,8 +372,8 @@ pub async fn user_wishlist_delete_item(
.unwrap(); .unwrap();
// Redirect to user wishlist // Redirect to user wishlist
let userid = user_data.as_ref().unwrap().id; let person_id = user_data.as_ref().unwrap().person_id;
let redirect_string = format!("/userwishlist/{userid}"); let redirect_string = format!("/userwishlist/{person_id}");
Redirect::to(&redirect_string).into_response() Redirect::to(&redirect_string).into_response()
} else { } else {
Redirect::to("/").into_response() Redirect::to("/").into_response()
@ -381,7 +383,7 @@ pub async fn user_wishlist_delete_item(
pub async fn user_wishlist_returned_item( pub async fn user_wishlist_returned_item(
Path(item_id): Path<uuid::Uuid>, Path(item_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>, State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<AccountData>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await { if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
sqlx::query("update wishlist_items set purchased_by = null where id = $1") sqlx::query("update wishlist_items set purchased_by = null where id = $1")

View File

@ -14,6 +14,7 @@
<ul> <ul>
<li><a href="/useradmin">User Administration</a></li> <li><a href="/useradmin">User Administration</a></li>
<li><a href="/giftexchanges">Gift Exchanges</a></li> <li><a href="/giftexchanges">Gift Exchanges</a></li>
<li><a href="/newevent">Add Event</a></li>
</ul> </ul>
{% endif %} {% endif %}
{% endfor %} {% endfor %}

View File

@ -21,6 +21,8 @@
body { body {
font-family: "Montserrat", sans-serif; font-family: "Montserrat", sans-serif;
height: 100vh;
margin: 0;
} }
</style> </style>
{% block links %}{% endblock links %} {% block links %}{% endblock links %}
@ -30,7 +32,8 @@
</head> </head>
<body> <body>
<div class="container-fluid" height="100%"> <div class="container-fluid" height="100vh">
<div class="row vh-100">
<!-- HEADER --> <!-- HEADER -->
<div class="row fixed-top sticky-top"> <div class="row fixed-top sticky-top">
@ -64,7 +67,7 @@
</div> </div>
<!-- CONTENT --> <!-- CONTENT -->
<div class="row flex-grow-1"> <div class="row flex-1">
{% block content %}{% endblock content %} {% block content %}{% endblock content %}
</div> </div>
@ -76,6 +79,7 @@
</footer> </footer>
</div><!-- /.container --> </div><!-- /.container -->
</div> </div>
</div>
</div> </div>

View File

@ -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 %}

View File

@ -9,7 +9,7 @@
<br /> <br />
<h2>List</h2> <h2>List</h2>
{% if my_wishlist %} {% if my_wishlist %}
<a href="/userwishlist/add/{{ user.id }}">Add</a> <a href="/userwishlist/add/{{ person.id }}">Add</a>
{% endif %} {% endif %}
<div class="table-responsive overflow-auto"> <div class="table-responsive overflow-auto">
<table data-toggle="table" class="table table-striped table-bordered"> <table data-toggle="table" class="table table-striped table-bordered">

View File

@ -1,5 +1,5 @@
cargo build --release 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 target/release/jean-marie www@192.168.59.11:/opt/jean-marie
scp runsite.sh 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 scp prod.env www@192.168.59.11:/opt/jean-marie