diff --git a/src/handlers.rs b/src/handlers.rs index 8733d87..f3b708c 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -23,27 +23,35 @@ lazy_static! { #[Error(desc = "Could not create user.")] #[non_exhaustive] -pub struct CreateUserError(#[from] CreateUserErrorKind); +pub struct CreateUserError(#[from] pub CreateUserErrorKind); impl IntoResponse for CreateUserError { fn into_response(self) -> Response { - (StatusCode::FORBIDDEN, format!("{:?}", self.0)).into_response() + let mut resp = SignupErrorPage(format!("{self}")).into_response(); + *resp.status_mut() = StatusCode::FORBIDDEN; + resp } } #[Error] #[non_exhaustive] pub enum CreateUserErrorKind { + #[error(desc = "That username already exists")] AlreadyExists, #[error(desc = "Usernames must be between 1 and 50 characters long")] BadUsername, + #[error(desc = "Your passwords didn't match")] PasswordMismatch, #[error(desc = "Password must have at least 4 and at most 100 characters")] BadPassword, #[error(desc = "Display name must be less than 100 characters long")] BadDisplayname, + #[error(desc = "Your email is too short, it simply can't be real")] BadEmail, + #[error(desc = "We could not verify your payment")] BadPayment, + #[error(desc = "We couldn't retrieve your info from this browser session")] + NoFormFound, } #[derive(Debug, Default, Deserialize, PartialEq, Eq)] @@ -76,28 +84,38 @@ pub async fn post_signup( )) } +/* +/// Handles the case when there was an error in the form pub async fn get_edit_signup( session: Session, receipt: Option>, ) -> Result { Ok(()) } +*/ -pub async fn post_edit_signup( - session: Session, - Form(form): Form, -) -> Result { - Ok(()) -} - -/// Called from Stripe with the receipt of payment. +/// Redirected from Stripe with the receipt of payment. pub async fn payment_success(session: Session, receipt: Option>) -> impl IntoResponse { let user: User = session.get(&SIGNUP_KEY).await.unwrap().unwrap_or_default(); + + if receipt.is_none() { + log::info!("Got {:?} from the session, but no receipt.", &user); + return CreateUserError(CreateUserErrorKind::BadPayment).into_response(); + } + let Path(receipt) = receipt.unwrap(); + if user == User::default() { - return SignupErrorPage("who you?".to_string()).into_response(); + log::warn!("Could not find user in session; got receipt {}", receipt); + return CreateUserError(CreateUserErrorKind::NoFormFound).into_response(); } - // TODO: check Stripe for the receipt, verify it's legit + // TODO: call the forgejo admin api to create the user + + session.delete().await.unwrap_or_else(|e| { + log::error!("Got error deleting {} from session, got {}", &user, e); + }); + + log::info!("Added {:?}", &user); SignupSuccessPage(user).into_response() } diff --git a/src/main.rs b/src/main.rs index f02da4c..80252ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,9 @@ -use std::net::SocketAddr; +use std::{io::Write, net::SocketAddr}; -use axum::{routing::get, Router}; +use axum::{ + routing::{get, MethodRouter}, + Router, +}; use tower_http::services::ServeDir; use tower_sessions::{Expiry, MemoryStore, SessionManagerLayer}; @@ -20,6 +23,7 @@ use user::User; #[tokio::main] async fn main() { + init(); // for javascript and css let assets_dir = std::env::current_dir().unwrap().join("assets"); let assets_svc = ServeDir::new(assets_dir.as_path()); @@ -33,7 +37,8 @@ async fn main() { // the core application, defining the routes and handlers let app = Router::new() .nest_service("/assets", assets_svc) - .route("/signup", get(get_signup).post(post_signup)) + .stripped_clone("/signup/", get(get_signup).post(post_signup)) + .stripped_clone("/payment_success/", get(payment_success)) .route("/payment_success/:receipt", get(payment_success)) .layer(session_layer) .into_make_service(); @@ -43,3 +48,33 @@ async fn main() { let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); axum::serve(listener, app).await.unwrap(); } + +fn init() { + dotenvy::dotenv().expect("Could not read .env file."); + env_logger::builder() + .format(|buf, record| { + // + let ts = buf.timestamp(); + writeln!(buf, "{}: {}", ts, record.args()) + }) + .init(); +} + +/// Adds both routes, with and without a trailing slash. +trait RouterPathStrip +where + S: Clone + Send + Sync + 'static, +{ + fn stripped_clone(self, path: &str, method_router: MethodRouter) -> Self; +} + +impl RouterPathStrip for Router +where + S: Clone + Send + Sync + 'static, +{ + fn stripped_clone(self, path: &str, method_router: MethodRouter) -> Self { + assert!(path.ends_with('/')); + self.route(path, method_router.clone()) + .route(path.trim_end_matches('/'), method_router) + } +} diff --git a/src/user.rs b/src/user.rs index c0bdcff..908d9f0 100644 --- a/src/user.rs +++ b/src/user.rs @@ -13,17 +13,21 @@ pub struct User { impl Debug for User { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let pw_check = if self.password == self.pw_verify { - "password matched" + if self == &User::default() { + write!(f, "Default User") } else { - "PASSWORD MISMATCH" - }; - f.debug_struct("User") - .field("username", &self.username) - .field("displayname", &self.displayname) - .field("email", &self.email) - .field("pw-check", &pw_check) - .finish() + let pw_check = if self.password == self.pw_verify { + "password matched" + } else { + "PASSWORD MISMATCH" + }; + f.debug_struct("User") + .field("username", &self.username) + .field("displayname", &self.displayname) + .field("email", &self.email) + .field("pw-check", &pw_check) + .finish() + } } }