diff options
| -rw-r--r-- | src/api/users.rs | 66 | ||||
| -rw-r--r-- | src/services/db.rs | 38 |
2 files changed, 87 insertions, 17 deletions
diff --git a/src/api/users.rs b/src/api/users.rs index 5e409fd..edade22 100644 --- a/src/api/users.rs +++ b/src/api/users.rs @@ -1,46 +1,46 @@ use actix_web::http::{header, StatusCode}; -use actix_web::{post, web, HttpResponse, ResponseError, Scope}; +use actix_web::{post, put, web, HttpResponse, ResponseError, Scope}; use raise::yeet; use serde::Deserialize; use sqlx::MySqlPool; use thiserror::Error; +use uuid::Uuid; use crate::models::User; use crate::services::crypto::PasswordHash; -use crate::services::db::{new_user, username_is_used}; -use crate::services::id::new_user_id; +use crate::services::{db, id}; #[derive(Clone, Deserialize)] -struct CreateUser { +struct UserRequest { username: Box<str>, password: Box<str>, } #[derive(Debug, Clone, Hash, Error)] #[error("An account with the given username already exists.")] -struct CreateUserError { +struct UsernameTakenError { username: Box<str>, } -impl ResponseError for CreateUserError { +impl ResponseError for UsernameTakenError { fn status_code(&self) -> StatusCode { StatusCode::CONFLICT } } -#[post("")] +#[post("/")] async fn create_user( - body: web::Json<CreateUser>, + body: web::Json<UserRequest>, conn: web::Data<MySqlPool>, -) -> Result<HttpResponse, CreateUserError> { +) -> Result<HttpResponse, UsernameTakenError> { let conn = conn.get_ref(); - let user_id = new_user_id(conn).await.unwrap(); + let user_id = id::new_user_id(conn).await.unwrap(); let username = body.username.clone(); let password = PasswordHash::new(&body.password).unwrap(); - if username_is_used(conn, &body.username).await.unwrap() { - yeet!(CreateUserError { username }); + if db::username_is_used(conn, &body.username).await.unwrap() { + yeet!(UsernameTakenError { username }); } let user = User { @@ -49,7 +49,7 @@ async fn create_user( password, }; - new_user(conn, user).await.unwrap(); + db::new_user(conn, &user).await.unwrap(); let response = HttpResponse::Created() .insert_header((header::LOCATION, format!("users/{user_id}"))) @@ -57,6 +57,44 @@ async fn create_user( Ok(response) } +#[put("/{user_id}")] +async fn update_user( + user_id: web::Path<Uuid>, + body: web::Json<UserRequest>, + conn: web::Data<MySqlPool>, +) -> Result<HttpResponse, UsernameTakenError> { + let conn = conn.get_ref(); + + let user_id = user_id.to_owned(); + let username = body.username.clone(); + let password = PasswordHash::new(&body.password).unwrap(); + + let old_username = db::get_username(conn, user_id) + .await + .unwrap() + .unwrap() + .into_boxed_str(); + if username != old_username && db::username_is_used(conn, &body.username).await.unwrap() { + yeet!(UsernameTakenError { username }) + } + + let user = User { + user_id, + username, + password, + }; + + db::update_username(conn, &user).await.unwrap(); + + let response = HttpResponse::NoContent() + .insert_header((header::LOCATION, format!("users/{user_id}"))) + .finish(); + + Ok(response) +} + pub fn service() -> Scope { - web::scope("users").service(create_user) + web::scope("/users") + .service(create_user) + .service(update_user) } diff --git a/src/services/db.rs b/src/services/db.rs index efa9584..b508e1b 100644 --- a/src/services/db.rs +++ b/src/services/db.rs @@ -1,5 +1,5 @@ use exun::*; -use sqlx::{query, query_scalar, Executor, MySql, MySqlPool}; +use sqlx::{mysql::MySqlQueryResult, query, query_scalar, Executor, MySql, MySqlPool}; use uuid::Uuid; use crate::models::User; @@ -38,10 +38,21 @@ pub async fn username_is_used<'c>( Ok(exists) } +pub async fn get_username<'c>( + conn: impl Executor<'c, Database = MySql>, + user_id: Uuid, +) -> Result<Option<String>, RawUnexpected> { + let username = query_scalar!(r"SELECT username FROM users where user_id = ?", user_id) + .fetch_optional(conn) + .await?; + + Ok(username) +} + pub async fn new_user<'c>( conn: impl Executor<'c, Database = MySql>, - user: User, -) -> Result<sqlx::mysql::MySqlQueryResult, sqlx::Error> { + user: &User, +) -> Result<MySqlQueryResult, sqlx::Error> { query!( r"INSERT INTO users (user_id, username, password_hash, password_salt, password_version) VALUES (?, ?, ?, ?, ?)", @@ -54,3 +65,24 @@ pub async fn new_user<'c>( .execute(conn) .await } + +pub async fn update_username<'c>( + conn: impl Executor<'c, Database = MySql>, + user: &User, +) -> Result<MySqlQueryResult, sqlx::Error> { + query!( + r"UPDATE users SET + username = ?, + password_hash = ?, + password_salt = ?, + password_version = ? + WHERE user_id = ?", + user.username(), + user.password_hash(), + user.password_salt(), + user.password_version(), + user.user_id + ) + .execute(conn) + .await +} |
