2024-01-11 12:52:41 +02:00
use std ::net ::SocketAddr ;
2024-01-08 20:09:26 +02:00
use axum ::{
2024-01-11 12:52:41 +02:00
extract ::{ Path , State , ConnectInfo } ,
Json , http ::StatusCode , Extension ,
2024-01-08 20:09:26 +02:00
} ;
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 } ,
2024-01-11 12:52:41 +02:00
}
2024-01-08 20:09:26 +02:00
} ;
#[ derive(Deserialize) ]
pub struct CreateEventDto {
2024-01-09 17:23:40 +02:00
from_date : Option < DateTime < Utc > > ,
to_date : Option < DateTime < Utc > > ,
2024-01-08 20:09:26 +02:00
name : String ,
description : Option < String > ,
event_type : String ,
2024-01-09 17:23:40 +02:00
duration : i32
2024-01-08 20:09:26 +02:00
}
#[ derive(Serialize) ]
pub struct EventDto {
snowflake_id : String ,
2024-01-09 17:23:40 +02:00
from_date : Option < DateTime < Utc > > ,
to_date : Option < DateTime < Utc > > ,
2024-01-08 20:09:26 +02:00
name : String ,
description : Option < String > ,
event_type : String ,
2024-01-09 17:23:40 +02:00
duration : i32
2024-01-08 20:09:26 +02:00
}
#[ derive(Deserialize) ]
pub struct CreateAvailabilitiesDto {
availabilities : Vec < CreateAvailabilityDto > ,
user_email : Option < String > ,
user_name : String ,
}
#[ derive(Deserialize, Clone) ]
pub struct CreateAvailabilityDto {
from_date : DateTime < Utc > ,
to_date : DateTime < Utc > ,
}
#[ derive(Serialize, Deserialize) ]
pub struct AvailabilityDto {
id : i64 ,
from_date : DateTime < Utc > ,
to_date : DateTime < Utc > ,
user_name : String ,
}
pub async fn create_event (
State ( app_state ) : State < AppState > ,
Json ( dto ) : Json < CreateEventDto > ,
) -> UniversalResponseDto < EventDto > {
let event_uid_size = app_state . event_uid_size ;
let mut conn = match app_state . db_pool . acquire ( ) . await {
Ok ( c ) = > c ,
2024-01-11 12:52:41 +02:00
Err ( e ) = > return api ::internal_server_error ( e . into ( ) ) ,
2024-01-08 20:09:26 +02:00
} ;
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 ) {
2024-01-11 12:52:41 +02:00
return Err ( ApplicationError ::new ( " Unknown event type, invalid variant. " . to_string ( ) , StatusCode ::UNPROCESSABLE_ENTITY ) ) ;
}
if matches! ( event_type , EventType ::SpecificDate ) & & dto . from_date . is_none ( ) {
return Err ( ApplicationError ::new ( " SpecificDate event type supplied, but missing from_date " . to_string ( ) , StatusCode ::UNPROCESSABLE_ENTITY ) ) ;
}
if matches! ( event_type , EventType ::DateRange ) {
match ( dto . from_date , dto . to_date ) {
( Some ( from ) , Some ( to ) ) = > {
if from > = to {
return Err ( ApplicationError ::new ( " Supplied from_date is later than or equal to to_date " . to_string ( ) , StatusCode ::UNPROCESSABLE_ENTITY ) ) ;
}
if ( to - from ) . num_days ( ) < 1 {
return Err ( ApplicationError ::new ( " Difference between from_date and to_date is less than 1 day " . to_string ( ) , StatusCode ::UNPROCESSABLE_ENTITY ) ) ;
}
if ( to - from ) . num_days ( ) > 14 {
return Err ( ApplicationError ::new ( " Difference between from_date and to_date is greater than 14 days ( current supported maximum ) " . to_string ( ) , StatusCode ::UNPROCESSABLE_ENTITY ) ) ;
}
} ,
_ = > return Err ( ApplicationError ::new ( " DateRange event type supplied, but missing either from_date or to_date " . to_string ( ) , StatusCode ::UNPROCESSABLE_ENTITY ) )
}
2024-01-08 20:09:26 +02:00
}
let event_id = db ::insert_event_and_fetch_id (
txn ,
Event {
id : - 1 ,
snowflake_id : uid ,
name : dto . name ,
description : dto . description ,
2024-01-09 17:23:40 +02:00
from_date : dto . from_date . map ( | d | d . naive_utc ( ) ) ,
to_date : dto . to_date . map ( | d | d . naive_utc ( ) ) ,
2024-01-08 20:09:26 +02:00
event_type ,
2024-01-09 17:23:40 +02:00
duration : dto . duration
2024-01-08 20:09:26 +02:00
} ,
)
. await ? ;
let event = db ::fetch_event_by_id ( txn , event_id ) . await ? ;
Ok ( EventDto {
snowflake_id : event . snowflake_id ,
2024-01-09 17:23:40 +02:00
from_date : event . from_date . map ( | d | Utc . from_utc_datetime ( & d ) ) ,
to_date : event . to_date . map ( | d | Utc . from_utc_datetime ( & d ) ) ,
2024-01-08 20:09:26 +02:00
name : event . name ,
description : event . description ,
event_type : event . event_type . to_string ( ) ,
2024-01-09 17:23:40 +02:00
duration : event . duration
2024-01-08 20:09:26 +02:00
} )
} )
} )
. await ;
2024-01-11 12:52:41 +02:00
api ::ok ::< EventDto > ( res )
2024-01-08 20:09:26 +02:00
}
pub async fn fetch_event (
State ( app_state ) : State < AppState > ,
Path ( event_snowflake_id ) : Path < String > ,
) -> UniversalResponseDto < EventDto > {
let mut conn = match app_state . db_pool . acquire ( ) . await {
Ok ( c ) = > c ,
2024-01-11 12:52:41 +02:00
Err ( e ) = > return api ::internal_server_error ( e . into ( ) ) ,
2024-01-08 20:09:26 +02:00
} ;
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 ,
2024-01-09 17:23:40 +02:00
from_date : event . from_date . map ( | d | Utc . from_utc_datetime ( & d ) ) ,
to_date : event . to_date . map ( | d | Utc . from_utc_datetime ( & d ) ) ,
2024-01-08 20:09:26 +02:00
name : event . name ,
description : event . description ,
event_type : event . event_type . to_string ( ) ,
2024-01-09 17:23:40 +02:00
duration : event . duration
2024-01-08 20:09:26 +02:00
} )
} )
} )
. await ;
2024-01-11 12:52:41 +02:00
api ::ok ::< EventDto > ( res )
2024-01-08 20:09:26 +02:00
}
pub async fn create_availabilities (
State ( app_state ) : State < AppState > ,
Path ( event_snowflake_id ) : Path < String > ,
2024-01-11 12:52:41 +02:00
ConnectInfo ( addr ) : ConnectInfo < SocketAddr > ,
2024-01-08 20:09:26 +02:00
Json ( dto ) : Json < CreateAvailabilitiesDto > ,
) -> UniversalResponseDto < ( ) > {
let mut conn = match app_state . db_pool . acquire ( ) . await {
Ok ( c ) = > c ,
2024-01-11 12:52:41 +02:00
Err ( e ) = > return api ::internal_server_error ( e . into ( ) ) ,
2024-01-08 20:09:26 +02:00
} ;
let res = conn
. transaction ( | txn | {
Box ::pin ( async move {
2024-01-11 12:52:41 +02:00
let user_ip = format! ( " {} " , addr . ip ( ) ) ;
2024-01-08 20:09:26 +02:00
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 | {
2024-01-11 12:52:41 +02:00
( dto . user_email . is_some ( ) & & a . user_email . is_some ( ) & & a . user_email = = dto . user_email )
| | a . user_ip = = user_ip
2024-01-08 20:09:26 +02:00
| | a . user_name = = dto . user_name
} ) ;
if already_submitted {
return Err ( ApplicationError ::new (
" Availability already submitted " . to_string ( ) ,
2024-01-11 12:52:41 +02:00
StatusCode ::UNPROCESSABLE_ENTITY
2024-01-08 20:09:26 +02:00
) ) ;
}
// 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 ( ) ,
2024-01-11 12:52:41 +02:00
user_ip : user_ip . clone ( ) ,
2024-01-08 20:09:26 +02:00
user_name : dto . user_name . clone ( ) ,
} ,
)
. await ? ;
}
Ok ( ( ) )
} )
} )
. await ;
2024-01-11 12:52:41 +02:00
api ::ok ::< ( ) > ( res )
2024-01-08 20:09:26 +02:00
}
pub async fn fetch_availabilities (
State ( app_state ) : State < AppState > ,
Path ( event_snowflake_id ) : Path < String > ,
) -> UniversalResponseDto < Vec < AvailabilityDto > > {
let mut conn = match app_state . db_pool . acquire ( ) . await {
Ok ( c ) = > c ,
2024-01-11 12:52:41 +02:00
Err ( e ) = > return api ::internal_server_error ( e . into ( ) ) ,
2024-01-08 20:09:26 +02:00
} ;
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 ;
2024-01-11 12:52:41 +02:00
api ::ok ::< Vec < AvailabilityDto > > ( res )
2024-01-08 20:09:26 +02:00
}