diff --git a/backend/migrations/20241113203107_calendar_tables.down.sql b/backend/migrations/20241113203107_calendar_tables.down.sql new file mode 100644 index 0000000..42af142 --- /dev/null +++ b/backend/migrations/20241113203107_calendar_tables.down.sql @@ -0,0 +1,5 @@ +-- Add down migration script here + +drop table if exists `calendar_events`; +drop table if exists `calendar_event_types`; +drop table if exists `calendar`; diff --git a/backend/migrations/20241113203107_calendar_tables.up.sql b/backend/migrations/20241113203107_calendar_tables.up.sql new file mode 100644 index 0000000..bd7f1e6 --- /dev/null +++ b/backend/migrations/20241113203107_calendar_tables.up.sql @@ -0,0 +1,45 @@ +-- Add up migration script here + +-- Calendars +-- 1 - Cottage +-- 2 - Family tree +create table calendar ( + id integer not null primary key autoincrement, + created_at integer not null default CURRENT_TIMESTAMP, + created_by integer not null default 0, + updated_at integer null default CURRENT_TIMESTAMP, + updated_by integer not null default 0, + name varchar(255) not null +) + +-- Event types +-- 1 - Rental +-- 2 - Life event +create table calendar_event_types ( + id integer not null primary key autoincrement, + created_at integer not null default CURRENT_TIMESTAMP, + created_by integer not null default 0, + updated_at integer null default CURRENT_TIMESTAMP, + updated_by integer not null default 0, + name varchar(255) not null +) + +create table calendar_events ( + id integer not null primary key autoincrement, + created_at integer not null default CURRENT_TIMESTAMP, + created_by integer not null default 0, + updated_at integer null default CURRENT_TIMESTAMP, + updated_by integer not null default 0, + calendar_id integer not null, + event_type_id integer not null, + title varchar(255) not null, + description varchar(255) null, + start_time integer null, + end_time integer null, + repeat_type integer not null default 0, -- 0 - None, 1 - Daily, 2 - Weekly, 3 - Monthly, 4 - Yearly, 5 - Day of week, 6 - Day of month + repeat_interval integer not null default 0, + celebrate boolean not null default 0 + + foreign key (calendar_id) references calendar(id), + foreign key (event_type_id) references calendar_event_types(id) +) diff --git a/backend/src/calendar.rs b/backend/src/calendar.rs new file mode 100644 index 0000000..5b603dc --- /dev/null +++ b/backend/src/calendar.rs @@ -0,0 +1,75 @@ +use askama::Template; +use askama_axum::{IntoResponse, Response}; +use axum::{ + extract::State, + response::{Html, Redirect}, + Extension, +}; +use http::StatusCode; +use sqlx::SqlitePool; + +use crate::{ + middlewares::is_authorized, + user::{get_user_roles_display, UserData}, +}; + +struct HtmlTemplate(T); + +impl IntoResponse for HtmlTemplate +where + T: Template, +{ + fn into_response(self) -> Response { + match self.0.render() { + Ok(html) => Html(html).into_response(), + Err(err) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to render template. Error: {}", err), + ) + .into_response(), + } + } +} + +#[derive(Template)] +#[template(path = "cottagecalendar.html")] +struct CottageCalendarTemplate { + logged_in: bool, + name: String, + user_roles: Vec, +} + +pub async fn cottagecalendar( + Extension(user_data): Extension>, + State(db_pool): State, +) -> impl IntoResponse { + let user_name = user_data.as_ref().map(|s| s.name.clone()); + let logged_in = user_name.is_some(); + let name = user_name.unwrap_or_default(); + + let user_id = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default(); + + if is_authorized("/cottagecalendar", user_data, db_pool.clone()).await { + // Get user roles + let user_roles = get_user_roles_display(user_id, &db_pool.clone()).await; + + let template = CottageCalendarTemplate { + logged_in, + name, + user_roles, + }; + HtmlTemplate(template).into_response() + } else { + Redirect::to("/").into_response() + } +} + +async fn get_next_event(db_pool: &SqlitePool) -> Option { + let next_event = sqlx::query_as::<_, (String, String)>( + "SELECT date, title FROM events ORDER BY date ASC LIMIT 1", + ) + .fetch_one(db_pool) + .await; + + next_event.map(|(date, title)| format!("{} - {}", date, title)) +} \ No newline at end of file diff --git a/backend/src/main.rs b/backend/src/main.rs index db8086c..2bbabd6 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,12 +1,17 @@ -use std::net::SocketAddr; use axum::{ - middleware, routing::{get, get_service}, Extension, Router + middleware, + routing::{get, get_service}, + Extension, Router, }; use secret_gift_exchange::{giftexchange, giftexchange_save, giftexchanges}; -use sqlx::{sqlite::SqlitePoolOptions, SqlitePool}; +use calendar::cottagecalendar; use sqlx::migrate::Migrator; +use sqlx::{sqlite::SqlitePoolOptions, SqlitePool}; +use std::net::SocketAddr; use tower_http::services::ServeDir; +mod calendar; +mod email; mod error_handling; mod google_oauth; mod middlewares; @@ -17,16 +22,16 @@ mod email; mod secret_gift_exchange; use error_handling::AppError; +use google_oauth::{google_auth_return, login, logout}; use middlewares::inject_user_data; -use google_oauth::{login, logout, google_auth_return}; -use routes::{about, contact, cottagecalendar, dashboard, index, profile, user_profile, useradmin}; +use routes::{about, contact, dashboard, index, profile, user_profile, useradmin}; use user::{add_user_role, delete_user_role, UserData}; use wishlist::{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_returned_item, user_wishlist_save_item, wishlists}; //use email::send_emails; #[derive(Clone)] pub struct AppState { - pub db_pool: SqlitePool + pub db_pool: SqlitePool, } #[tokio::main] @@ -38,8 +43,10 @@ async fn main() { .max_connections(5) .connect("sqlite://db/db.sqlite3") .await; - - let app_state = AppState {db_pool:db_pool.expect("Failed to get db_pool")}; + + let app_state = AppState { + db_pool: db_pool.expect("Failed to get db_pool"), + }; static MIGRATOR: Migrator = sqlx::migrate!(); @@ -86,10 +93,12 @@ async fn main() { .route("/login", get(login)) .route("/logout", get(logout)) .route("/google_auth_return", get(google_auth_return)) - .route_layer(middleware::from_fn_with_state(app_state.db_pool.clone(), inject_user_data)) + .route_layer(middleware::from_fn_with_state( + app_state.db_pool.clone(), + inject_user_data, + )) .with_state(app_state.db_pool.clone()) - .layer(Extension(user_data)) - ; + .layer(Extension(user_data)); // Send email indicating server has started //let recipients = get_useremails_by_role("admin".to_string(), &app_state.db_pool).await; @@ -102,5 +111,4 @@ async fn main() { .serve(app.into_make_service()) .await .unwrap(); - } diff --git a/backend/src/routes.rs b/backend/src/routes.rs index 8ff78c1..6bda349 100644 --- a/backend/src/routes.rs +++ b/backend/src/routes.rs @@ -278,41 +278,3 @@ pub async fn contact(Extension(user_data): Extension>) -> impl let template = ContactTemplate { logged_in, user }; HtmlTemplate(template) } - -#[derive(Template)] -#[template(path = "cottagecalendar.html")] -struct CottageCalendarTemplate { - logged_in: bool, - user: UserData, - user_roles: Vec, -} - -pub async fn cottagecalendar( - Extension(user_data): Extension>, - State(db_pool): State, -) -> 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("/cottagecalendar", user_data, db_pool.clone()).await { - // Get user roles - let user_roles = get_user_roles_display(userid, &db_pool.clone()).await; - - let template = CottageCalendarTemplate { - logged_in, - user, - user_roles, - }; - HtmlTemplate(template).into_response() - } else { - Redirect::to("/").into_response() - } - } else { - Redirect::to("/").into_response() - } -}