157 lines
4.3 KiB
Rust
157 lines
4.3 KiB
Rust
|
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<SignupForm>) -> impl IntoResponse {
|
||
|
todo!()
|
||
|
}
|
||
|
|
||
|
/// Called from Stripe with the receipt of payment.
|
||
|
async fn signup_success(session: Session, receipt: Option<Path<String>>) -> 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<String>,
|
||
|
#[serde(default, deserialize_with = "empty_string_as_none")]
|
||
|
pub email: Option<String>,
|
||
|
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<String>,
|
||
|
pub email: Option<String>,
|
||
|
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<Option<T>, D::Error>
|
||
|
where
|
||
|
D: serde::Deserializer<'de>,
|
||
|
T: std::str::FromStr,
|
||
|
T::Err: std::fmt::Display,
|
||
|
{
|
||
|
let opt = <Option<String> 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<String>,
|
||
|
pub email: Option<String>,
|
||
|
pub password: String,
|
||
|
pub pw_verify: String,
|
||
|
}
|