use askama::Template; use axum::{ body::{self, Body}, extract::{FromRequest, Path, Request, State}, response::{Html, IntoResponse, Redirect, Response}, Extension, Form, Json, RequestExt, }; use http::{header::CONTENT_TYPE, StatusCode}; use serde::{Deserialize, Serialize}; use sqlx::{FromRow, PgPool}; use crate::{ middlewares::is_authorized, user::{get_user_roles_display, UserData}, }; /// Select participants from user list /// create group id for exchange /// allow user to only see their recipient but whole list of participants /// link to recipient wish list /// button to create selections /// /// Database schema /// Table - gift_exchange /// Columns - id -> number /// - created_by -> number /// - created_at -> number /// - updated_by -> number /// - updated_at -> number /// - name -> text /// - exchange_date -> number /// /// Table - gift_exchange_participants /// Columns - id -> number /// - created_by -> number /// - created_at -> number /// - updated_by -> number /// - updated_at -> number /// - exchange_id -> number (reference gift_exchange table) /// - participant_id -> number (reference user table) /// - gifter_id -> number (reference user table) /// /// Pages - sge_list /// - list of gift exchanges user is part of /// - sge_exchange /// - exchange details /// - list of participants /// - sge_edit /// - create new exchange /// - edit existing exchange /// - sge_participant_edit /// - add or remove participant to exchange /// /// API - select gifters 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(Default, Clone, Debug, Serialize, Deserialize, FromRow)] struct GiftExchange { id: i64, created_at: i64, created_by: i64, updated_at: i64, updated_by: i64, name: String, exchange_date: i64, status: i64, } #[derive(Template)] #[template(path = "giftexchanges.html")] struct GiftExchangesTemplate { logged_in: bool, user: UserData, user_roles: Vec, giftexchanges: Vec, } pub async fn giftexchanges( 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(); let giftexchanges = sqlx::query_as::<_, GiftExchange>("SELECT * FROM gift_exchange") .fetch_all(&db_pool) .await .unwrap(); if is_authorized("/giftexchange", user_data, db_pool.clone()).await { // Get user roles let user_roles = get_user_roles_display(userid, &db_pool.clone()).await; let template = GiftExchangesTemplate { logged_in, user, user_roles, giftexchanges, }; HtmlTemplate(template).into_response() } else { Redirect::to("/").into_response() } } else { Redirect::to("/").into_response() } } #[derive(Default, Clone, Debug, Serialize, Deserialize, FromRow)] struct GiftExchangeParticipant { id: i64, created_at: i64, created_by: i64, updated_at: i64, updated_by: i64, exchange_id: i64, participant_id: i64, gifter_id: i64, } #[derive(Template)] #[template(path = "giftexchange.html")] struct GiftExchangeTemplate { logged_in: bool, user: UserData, user_roles: Vec, giftexchange: GiftExchange, participants: Vec, non_participants: Vec, } pub async fn giftexchange( Path(exchange_id): Path, 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("/giftexchange", user_data, db_pool.clone()).await { // Get user roles let user_roles = get_user_roles_display(userid, &db_pool.clone()).await; // Get gift exchange let giftexchange = match sqlx::query_as( "SELECT * FROM gift_exchange WHERE id = ?") .bind(exchange_id) .fetch_one(&db_pool) .await { Ok(giftexchange) => giftexchange, Err(_) => GiftExchange::default(), }; // Get participants let participants = sqlx::query_as::<_, UserData>( "select * from users where users.id in (select participant_id from gift_exchange_participants where exchange_id = $1)", ) .bind(exchange_id) .fetch_all(&db_pool) .await .unwrap(); // Get non participants let non_participants = sqlx::query_as::<_, UserData>( "select * from users where users.id not in (select participant_id from gift_exchange_participants where exchange_id = $1)", ) .bind(exchange_id) .fetch_all(&db_pool) .await .unwrap(); let template = GiftExchangeTemplate { logged_in, user, user_roles, giftexchange, participants, non_participants, }; HtmlTemplate(template).into_response() } else { Redirect::to("/").into_response() } } else { Redirect::to("/").into_response() } } #[derive(Deserialize, Debug)] pub struct ExchangeForm { name: String, exchange_date: String, non_participants: Vec, } pub async fn giftexchange_save( State(_db_pool): State, request: Request, ) -> impl IntoResponse { let content_type_header = request.headers().get(CONTENT_TYPE); let _content_type = content_type_header.and_then(|value| value.to_str().ok()); /* if let Some(content_type) = content_type { if content_type.starts_with("application/json") { let payload = request .extract() .await .map_err(IntoResponse::into_response); } if content_type.starts_with("application/x-www-form-urlencoded") { let payload = request .extract() .await .map_err(IntoResponse::into_response); } } */ let (req_parts, map_request_body) = request.into_parts(); let bytes = match body::to_bytes(map_request_body, usize::MAX).await { Ok(bytes) => bytes, Err(err) => { return Err(( StatusCode::BAD_REQUEST, format!("failed to read request body: {}", err), )); } }; println!("Saving gift exchange: {:?}", req_parts); println!("Saving gift exchange: {:?} ", bytes); Ok(Redirect::to("/").into_response()) } #[derive(Debug, Serialize, Deserialize)] struct Payload { foo: String, }