use std::{ fmt::{Debug, Display}, net::SocketAddr, }; use askama::Template; use axum::{ extract::{Form, Path}, http::StatusCode, response::{IntoResponse, Response}, routing::get, Router, }; use serde::{Deserialize, Serialize}; use tower_sessions::{MemoryStore, Session, SessionManagerLayer}; #[macro_use] extern crate justerror; const SIGNUP_KEY: &str = "meow"; #[derive(Default, Deserialize, Serialize)] struct Counter(usize); /// Displays the signup form. async fn get_signup() -> impl IntoResponse { SignupPage::default() } /// Receives the form with the user signup fields filled out. async fn post_signup(session: Session, Form(form): Form) -> impl IntoResponse { todo!() } /// Called from Stripe with the receipt of payment. async fn signup_success(session: Session, receipt: Option>) -> impl IntoResponse { todo!() } #[tokio::main] async fn main() { let session_store = MemoryStore::default(); let session_layer = SessionManagerLayer::new(session_store).with_secure(false); let app = Router::new() //.nest_service("/assets", assets_svc) .route("/signup", get(get_signup).post(post_signup)) .route("/signup_success/:receipt", get(signup_success)) .layer(session_layer); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); axum::serve(listener, app.into_make_service()) .await .unwrap(); } #[Error(desc = "Could not create user.")] #[non_exhaustive] pub struct CreateUserError(#[from] CreateUserErrorKind); impl IntoResponse for CreateUserError { fn into_response(self) -> Response { (StatusCode::FORBIDDEN, format!("{:?}", self.0)).into_response() } } #[Error] #[non_exhaustive] pub enum CreateUserErrorKind { AlreadyExists, #[error(desc = "Usernames must be between 1 and 20 characters long")] BadUsername, PasswordMismatch, #[error(desc = "Password must have at least 4 and at most 50 characters")] BadPassword, #[error(desc = "Display name must be less than 100 characters long")] BadDisplayname, BadEmail, BadPayment, } #[derive(Debug, Default, Deserialize, PartialEq, Eq)] pub struct SignupForm { pub username: String, #[serde(default, deserialize_with = "empty_string_as_none")] pub displayname: Option, #[serde(default, deserialize_with = "empty_string_as_none")] pub email: Option, pub password: String, pub pw_verify: String, pub invitation: String, } #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct User { pub username: String, pub displayname: Option, pub email: Option, pub password: String, pub pw_verify: String, } 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" } 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() } } impl Display for User { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let uname = &self.username; let dname = if let Some(ref n) = self.displayname { n } else { "" }; let email = if let Some(ref e) = self.email { e } else { "" }; write!(f, "Username: {uname}\nDisplayname: {dname}\nEmail: {email}") } } pub(crate) fn empty_string_as_none<'de, D, T>(de: D) -> Result, D::Error> where D: serde::Deserializer<'de>, T: std::str::FromStr, T::Err: std::fmt::Display, { let opt = as serde::Deserialize>::deserialize(de)?; match opt.as_deref() { None | Some("") => Ok(None), Some(s) => std::str::FromStr::from_str(s) .map_err(serde::de::Error::custom) .map(Some), } } #[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)] #[template(path = "signup.html")] pub struct SignupPage { pub username: String, pub displayname: Option, pub email: Option, pub password: String, pub pw_verify: String, }