diff --git a/migrations/20230426221940_init.up.sql b/migrations/20230426221940_init.up.sql index 111a881..7d53573 100644 --- a/migrations/20230426221940_init.up.sql +++ b/migrations/20230426221940_init.up.sql @@ -67,7 +67,7 @@ create table if not exists watch_notes ( ); -- indices, not needed for covens -create index if not exists witch_dex on witches ( name, email ); +create index if not exists witch_dex on witches ( username, email ); create index if not exists watch_dex on watches ( title, runtime, release_date ); create index if not exists ww_dex on witch_watch ( witch, watch, public ); create index if not exists note_dex on watch_notes ( witch, watch, public ); diff --git a/src/main.rs b/src/main.rs index 9a00060..bb8f66d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,10 @@ async fn main() { let pool = db::get_pool().await; + let _ = witch_watch::users::create_user("joe", &None, &None, &[], &pool) + .await + .unwrap(); + // build our application with some routes use handlers::*; let app = Router::new() diff --git a/src/users.rs b/src/users.rs index 5d25df9..96ab8bd 100644 --- a/src/users.rs +++ b/src/users.rs @@ -2,9 +2,12 @@ use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, }; +use sqlx::{error::DatabaseError, Sqlite, SqlitePool}; +use tracing::log::log; use uuid::Uuid; -const CREATE_QUERY: &str = "insert into witches (id, username, pwhash) values ($1, $2, $3)"; +const CREATE_QUERY: &str = + "insert into witches (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)"; pub struct User { id: Uuid, @@ -34,17 +37,56 @@ impl From for User { } } -async fn create_user(username: &User, password: &[u8]) -> Result { +pub async fn create_user( + username: &str, + displayname: &Option, + email: &Option, + password: &[u8], + pool: &SqlitePool, +) -> Result { // Argon2 with default params (Argon2id v19) let argon2 = Argon2::default(); let salt = SaltString::generate(&mut OsRng); - let password_hash = argon2 + let pwhash = argon2 .hash_password(password, &salt) .unwrap() // safe to unwrap, we know the salt is valid .to_string(); - todo!() + let id = Uuid::new_v4(); + let id_bytes = sqlx::types::Uuid::from_u128(id.as_u128()); + let res = sqlx::query(CREATE_QUERY) + .bind(id_bytes) + .bind(username) + .bind(displayname) + .bind(email) + .bind(pwhash) + .execute(pool) + .await; + + match res { + Ok(_) => { + let user = User { + id, + username: username.to_string(), + displayname: displayname.to_owned(), + email: email.to_owned(), + }; + Ok(user) + } + Err(sqlx::Error::Database(db)) => { + if let Some(exit) = db.code() { + if exit.parse().unwrap_or(0) == 2067u32 { + Err(CreateUserErrorKind::AlreadyExists.into()) + } else { + Err(CreateUserErrorKind::Unknown.into()) + } + } else { + Err(CreateUserErrorKind::Unknown.into()) + } + } + _ => unreachable!(), + } } #[Error(desc = "Could not create user.")] @@ -55,6 +97,10 @@ pub struct CreateUserError(#[from] CreateUserErrorKind); #[non_exhaustive] pub enum CreateUserErrorKind { AlreadyExists, + #[error( + desc = "Usernames must be less than 20 characters and contain only ascii letters, numbers, and dashes." + )] + BadUsername, PasswordMismatch, MissingFields, Unknown,