Add logging and prettier error pages.

This commit is contained in:
Joe Ardent 2024-03-01 17:27:20 -08:00
parent 0826f2f0e3
commit ba027bb1cd
3 changed files with 82 additions and 25 deletions

View file

@ -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()
}

View file

@ -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)
}
}

View file

@ -13,6 +13,9 @@ pub struct User {
impl Debug for User {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self == &User::default() {
write!(f, "Default User")
} else {
let pw_check = if self.password == self.pw_verify {
"password matched"
} else {
@ -25,6 +28,7 @@ impl Debug for User {
.field("pw-check", &pw_check)
.finish()
}
}
}
impl Display for User {