Add logging and prettier error pages.
This commit is contained in:
parent
0826f2f0e3
commit
ba027bb1cd
3 changed files with 82 additions and 25 deletions
|
@ -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<Path<String>>,
|
||||
) -> Result<impl IntoResponse, CreateUserError> {
|
||||
Ok(())
|
||||
}
|
||||
*/
|
||||
|
||||
pub async fn post_edit_signup(
|
||||
session: Session,
|
||||
Form(form): Form<SignupForm>,
|
||||
) -> Result<impl IntoResponse, CreateUserError> {
|
||||
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<Path<String>>) -> 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()
|
||||
}
|
||||
|
||||
|
|
41
src/main.rs
41
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<S>
|
||||
where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
fn stripped_clone(self, path: &str, method_router: MethodRouter<S>) -> Self;
|
||||
}
|
||||
|
||||
impl<S> RouterPathStrip<S> for Router<S>
|
||||
where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
fn stripped_clone(self, path: &str, method_router: MethodRouter<S>) -> Self {
|
||||
assert!(path.ends_with('/'));
|
||||
self.route(path, method_router.clone())
|
||||
.route(path.trim_end_matches('/'), method_router)
|
||||
}
|
||||
}
|
||||
|
|
24
src/user.rs
24
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue