diff --git a/src/main.rs b/src/main.rs index 07913a0..ec78e19 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,10 @@ async fn main() { // build our application with some routes let app = Router::new() .route("/signup", get(get_create_user).post(post_create_user)) - .route("/signup_success", get(get_signup_success)) + .route( + "/signup_success", + get(get_signup_success).post(get_signup_success), + ) .with_state(pool); tracing::debug!("binding to 0.0.0.0:3000"); diff --git a/src/users.rs b/src/users.rs index c5b5c18..209da60 100644 --- a/src/users.rs +++ b/src/users.rs @@ -1,4 +1,4 @@ -use std::fmt::Display; +use std::{array, fmt::Display, ops::Deref}; use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, @@ -10,7 +10,7 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; -use sqlx::SqlitePool; +use sqlx::{sqlite::SqliteRow, Row, SqlitePool}; use unicode_segmentation::UnicodeSegmentation; use uuid::Uuid; @@ -19,12 +19,17 @@ use crate::templates::CreateUser; const CREATE_QUERY: &str = "insert into witches (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)"; -#[derive(Debug, Clone, PartialEq, Eq)] +const ID_QUERY: &str = "select * from witches where id = $1"; + +const WITCH_SUCCESS_HEADER: &str = "X-Witch-Success"; + +#[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct User { id: Uuid, username: String, displayname: Option, email: Option, + last_seen: Option, } impl Display for User { @@ -40,13 +45,13 @@ impl Display for User { } } -#[derive(Debug, Clone, sqlx::FromRow, sqlx::Encode)] +#[derive(Debug, Default, Clone, sqlx::Encode)] pub(crate) struct DbUser { id: Uuid, username: String, displayname: Option, email: Option, - last_seen: Option, + last_seen: Option, pwhash: String, } @@ -54,6 +59,27 @@ pub(crate) struct DbUser { #[template(path = "signup_success.html")] pub struct CreateUserSuccess(User); +impl sqlx::FromRow<'_, SqliteRow> for User { + fn from_row(row: &SqliteRow) -> Result { + let bytes: Vec = row.get("id"); + let bytes = bytes.as_slice(); + let bytes: [u8; 16] = bytes.try_into().unwrap(); + let id = Uuid::from_bytes_le(bytes); + let username: String = row.get("username"); + let displayname: Option = row.get("displayname"); + let last_seen: Option = row.get("last_seen"); + let email: Option = row.get("email"); + + Ok(Self { + id, + username, + displayname, + email, + last_seen, + }) + } +} + impl From for User { fn from(dbu: DbUser) -> Self { User { @@ -61,6 +87,7 @@ impl From for User { username: dbu.username, displayname: dbu.displayname, email: dbu.email, + last_seen: dbu.last_seen, } } } @@ -124,20 +151,39 @@ pub async fn post_create_user( tracing::debug!("created {user:?}"); let mut resp = axum::response::Redirect::temporary("/signup_success").into_response(); resp.headers_mut().append( - "X-Witch-Success", + WITCH_SUCCESS_HEADER, user.id.simple().to_string().parse().unwrap(), ); + Ok(resp) } +/// Get handler for successful signup; only meaningful pub async fn get_signup_success( headers: axum::http::HeaderMap, State(pool): State, -) -> CreateUserSuccess { - if let Some(id) = headers.get("X-Witch") { - todo!() +) -> Response { + let user = if let Some(id) = headers.get(WITCH_SUCCESS_HEADER) { + let id = id.to_str().unwrap(); + let id = Uuid::try_parse(id).unwrap_or_default(); + let id_bytes = id.as_bytes(); + sqlx::query_as(ID_QUERY) + .bind(id_bytes.as_slice()) + .fetch_one(&pool) + .await + .unwrap_or_default() + } else { + User::default() + }; + + let mut resp = CreateUserSuccess(user.clone()).into_response(); + + if user.username.is_empty() { + // redirect to front page if we got here without a valid witch header + *resp.status_mut() = StatusCode::TEMPORARY_REDIRECT; + resp.headers_mut().insert("Location", "/".parse().unwrap()); } - todo!() + resp } async fn create_user( @@ -174,6 +220,7 @@ async fn create_user( username: username.to_string(), displayname: displayname.to_owned(), email: email.to_owned(), + last_seen: None, }; Ok(user) }