@ -12,7 +12,7 @@ use askama::{Template};
use sqlx ::{ FromRow , query_as , postgres ::{ PgPool , PgQueryResult , PgPoolOptions } , query , query_scalar } ;
use axum ::{ Form , response ::IntoResponse , routing ::get , Router , body ::Body , response ::Response } ;
use axum ::extract ::State ;
use axum_csrf ::{ CsrfConfig , CsrfLayer , CsrfToken };
use axum_csrf ::{ CsrfConfig , CsrfLayer , CsrfToken , CsrfError };
#[ derive(Serialize, sqlx::Type, Deserialize, Debug, Clone, Copy) ]
#[ serde(rename_all = " lowercase " ) ]
@ -28,8 +28,14 @@ enum Campus {
#[ template(path = " course_interest.html " ) ]
struct FormPage {
authentication_key : String ,
interest_distirbuted : i64 ,
interest_compilers : i64 ,
}
#[ derive(Template, Deserialize, Serialize) ]
#[ template(path = " thank_you.html " ) ]
struct InterestPage {
compiler_design : i64 ,
distributed_systems : i64 ,
calgary_cpsc_group : i64 ,
}
#[ derive(Debug, FromRow) ]
@ -80,6 +86,7 @@ struct HTMLFormResponse {
struct Interest {
distributed_systems : Option < i64 > ,
compiler_design : Option < i64 > ,
calgary_cpsc_group : Option < i64 > ,
}
impl DBFormResponse {
@ -125,7 +132,8 @@ FROM responses;
r #"
SELECT
SUM ( CASE WHEN distributed_systems = true THEN 1 ELSE 0 END ) AS distributed_systems ,
SUM ( CASE WHEN compiler_design = true THEN 1 ELSE 0 END ) AS compiler_design
SUM ( CASE WHEN compiler_design = true THEN 1 ELSE 0 END ) AS compiler_design ,
SUM ( CASE WHEN calgary_cpsc_group = true THEN 1 ELSE 0 END ) AS calgary_cpsc_group
FROM responses ;
" #)
. fetch_one ( & * pool )
@ -137,11 +145,8 @@ async fn index(
token : CsrfToken ,
State ( ServerState { db } ) : State < ServerState > ,
) -> Result < ( CsrfToken , FormPage ) , FormError > {
let responses_already = DBFormResponse ::interest_counts ( & db ) . await ? ;
let tmpl = FormPage {
authentication_key : token . authenticity_token ( ) . unwrap ( ) ,
interest_distirbuted : responses_already . distributed_systems . unwrap_or ( 0 i64 ) ,
interest_compilers : responses_already . compiler_design . unwrap_or ( 0 i64 ) ,
} ;
Ok ( ( token , tmpl ) )
}
@ -150,6 +155,7 @@ async fn index(
pub enum FormError {
EmailError ( String ) ,
DatabaseError ( String ) ,
FormValidationError ( String ) ,
}
impl IntoResponse for FormError {
fn into_response ( self ) -> Response < Body > {
@ -161,11 +167,20 @@ impl IntoResponse for FormError {
Self ::DatabaseError ( s ) = > {
eprintln! ( "{}" , s ) ;
"There was an error saving your response to the database."
}
} ,
Self ::FormValidationError ( s ) = > {
eprintln! ( "{}" , s ) ;
"There was an error processing the form; it should be resolved by going back, refreshing the page, then submitting again."
} ,
} . to_string ( ) . into_response ( )
}
}
impl From < CsrfError > for FormError {
fn from ( e : CsrfError ) -> Self {
Self ::FormValidationError ( format! ( "{:?}" , e ) )
}
}
impl From < lettre ::address ::AddressError > for FormError {
fn from ( e : lettre ::address ::AddressError ) -> Self {
Self ::EmailError ( format! ( "{:?}" , e ) )
@ -191,11 +206,9 @@ async fn check_key(
token : CsrfToken ,
State ( ServerState { db } ) : State < ServerState > ,
Form ( payload ) : Form < HTMLFormResponse > ,
) -> Result < String , FormError > {
) -> Result < InterestPage , FormError > {
// Verfiy the Hash and return the String message.
if token . verify ( & payload . authentication_key ) . is_err ( ) {
return Ok ( "CSRF token is invalid. This is usually because somebody is doing something nasty." . to_string ( ) ) ;
}
token . verify ( & payload . authentication_key ) ? ;
let db_form_resposne = payload . clone ( ) . into ( ) ;
let _ = DBFormResponse ::insert ( & db , & db_form_resposne ) . await ? ;
let smtp_host = env ::var ( "SMTP_HOST" ) . unwrap ( ) ;
@ -207,8 +220,8 @@ async fn check_key(
. bcc ( "Tait Hoyem <tait.hoyem@uleth.ca>" . parse ( ) ? )
. subject ( "Thank Your For Your Interest" )
. multipart ( MultiPart ::alternative_plain_html (
String ::from ( "T est") ,
String ::from ( "<p> What is the secondPart? </p>") ,
String ::from ( "T hank you for registering you inter est in one of: \r\n\r\n- Compiler Design\r\n- Distributed Systems\r\n- Calgary-based computer-science group\r\n\r\nWe will keep you informed of the progress as we receive more signatories. You will only be contacted if there is an update regarding a thing you have registered interest in.\r\n\r\nIf you'd like to opt out, please let me, tait@tait.tech know. ") ,
String ::from ( "<p> Thank you for registering you interest in one of:</p> <ul><li>Compiler Design</li><li>Distributed Systems</li><li>Calgary-based computer-science group</li></ul><p>We will keep you informed of the progress as we receive more signatories. You will only be contacted if there is an update regarding a thing you have registered interest in.</p><p>If you'd like to opt out, please let me, tait@tait.tech know. </p>") ,
) ) ? ;
let sender = AsyncSmtpTransport ::< lettre ::Tokio1Executor > ::starttls_relay ( & smtp_host ) ?
. credentials ( Credentials ::new (
@ -218,7 +231,13 @@ async fn check_key(
. authentication ( vec! [ Mechanism ::Login ] )
. build ( ) ;
let result = sender . send ( msg ) . await ;
Ok ( format! ( "{:?}" , payload ) )
let db_standings = DBFormResponse ::interest_counts ( & db ) . await ? ;
let standings = InterestPage {
calgary_cpsc_group : db_standings . calgary_cpsc_group . unwrap_or ( 0 i64 ) ,
compiler_design : db_standings . compiler_design . unwrap_or ( 0 i64 ) ,
distributed_systems : db_standings . distributed_systems . unwrap_or ( 0 i64 ) ,
} ;
Ok ( standings )
}
#[ derive(Clone) ]