use axum::{ extract::{Path, State}, Json, }; use chrono::{DateTime, TimeZone, Utc}; use rand::{distributions::Alphanumeric, Rng}; use serde::{Deserialize, Serialize}; use sqlx::Connection; use crate::{ api::{self, AppState, ApplicationError, UniversalResponseDto}, db, entity::{ availability::Availability, event::{Event, EventType}, }, }; #[derive(Deserialize)] pub struct CreateEventDto { from_date: DateTime, to_date: DateTime, name: String, description: Option, event_type: String, } #[derive(Serialize)] pub struct EventDto { snowflake_id: String, from_date: DateTime, to_date: DateTime, name: String, description: Option, event_type: String, } #[derive(Deserialize)] pub struct CreateAvailabilitiesDto { availabilities: Vec, user_email: Option, user_ip: String, user_name: String, } #[derive(Deserialize, Clone)] pub struct CreateAvailabilityDto { from_date: DateTime, to_date: DateTime, } #[derive(Serialize, Deserialize)] pub struct AvailabilityDto { id: i64, from_date: DateTime, to_date: DateTime, user_name: String, } pub async fn create_event( State(app_state): State, Json(dto): Json, ) -> UniversalResponseDto { let event_uid_size = app_state.event_uid_size; let mut conn = match app_state.db_pool.acquire().await { Ok(c) => c, Err(e) => return api::internal_server_error(e), }; let res = conn .transaction(|txn| { Box::pin(async move { let uid: String = rand::thread_rng() .sample_iter(&Alphanumeric) .take(event_uid_size) .map(char::from) .collect(); let event_type: EventType = dto.event_type.into(); if matches!(event_type, EventType::Unknown) { return Err(ApplicationError::new( "Unknown event type, invalid variant.".to_string(), )); } let event_id = db::insert_event_and_fetch_id( txn, Event { id: -1, snowflake_id: uid, name: dto.name, description: dto.description, from_date: dto.from_date.naive_utc(), to_date: dto.to_date.naive_utc(), event_type, }, ) .await?; let event = db::fetch_event_by_id(txn, event_id).await?; Ok(EventDto { snowflake_id: event.snowflake_id, from_date: Utc.from_utc_datetime(&event.from_date), to_date: Utc.from_utc_datetime(&event.to_date), name: event.name, description: event.description, event_type: event.event_type.to_string(), }) }) }) .await; api::ok::(res) } pub async fn fetch_event( State(app_state): State, Path(event_snowflake_id): Path, ) -> UniversalResponseDto { let mut conn = match app_state.db_pool.acquire().await { Ok(c) => c, Err(e) => return api::internal_server_error(e), }; let res = conn .transaction(|txn| { Box::pin(async move { let event = db::fetch_event_by_snowflake_id(txn, event_snowflake_id).await?; Ok(EventDto { snowflake_id: event.snowflake_id, from_date: Utc.from_utc_datetime(&event.from_date), to_date: Utc.from_utc_datetime(&event.to_date), name: event.name, description: event.description, event_type: event.event_type.to_string(), }) }) }) .await; api::ok::(res) } pub async fn create_availabilities( State(app_state): State, Path(event_snowflake_id): Path, Json(dto): Json, ) -> UniversalResponseDto<()> { let mut conn = match app_state.db_pool.acquire().await { Ok(c) => c, Err(e) => return api::internal_server_error(e), }; let res = conn .transaction(|txn| { Box::pin(async move { let event = db::fetch_event_by_snowflake_id(txn, event_snowflake_id).await?; let current_availabilities = db::fetch_event_availabilities(txn, event.id).await?; let already_submitted = current_availabilities.iter().any(|a| { (dto.user_email.is_none() && a.user_email == dto.user_email) || a.user_ip == dto.user_ip || a.user_name == dto.user_name }); if already_submitted { return Err(ApplicationError::new( "Availability already submitted".to_string(), )); } // TODO: Do these in parallel. // At the moment, it would appear sqlx's transaction does not implement Clone, so it's not possible to execute these concurrently for a in dto.availabilities { db::insert_availability_and_fetch_id( txn, Availability { id: -1, event_id: event.id, from_date: a.from_date.naive_utc(), to_date: a.to_date.naive_utc(), user_email: dto.user_email.clone(), user_ip: dto.user_ip.clone(), user_name: dto.user_name.clone(), }, ) .await?; } Ok(()) }) }) .await; api::ok::<(), ApplicationError>(res) } pub async fn fetch_availabilities( State(app_state): State, Path(event_snowflake_id): Path, ) -> UniversalResponseDto> { let mut conn = match app_state.db_pool.acquire().await { Ok(c) => c, Err(e) => return api::internal_server_error(e), }; let res = conn .transaction(|txn| { Box::pin(async move { let availabilities = db::fetch_event_availabilities_by_event_snowflake_id(txn, event_snowflake_id) .await?; Ok(availabilities .into_iter() .map(|a| AvailabilityDto { id: a.id, user_name: a.user_name, from_date: Utc.from_utc_datetime(&a.from_date), to_date: Utc.from_utc_datetime(&a.to_date), }) .collect()) }) }) .await; api::ok::, ApplicationError>(res) }