summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authormrw1593 <botahamec@outlook.com>2023-05-12 22:54:38 -0400
committermrw1593 <botahamec@outlook.com>2023-05-29 10:45:40 -0400
commit3aff5f0123778aa453ab5396cb880a83267ae4ee (patch)
tree82d5bddb485cedfcb5a2f1e6abb8fbbed1f701e8 /src
parent8ec105595db9d2957c7327112e7a0b63d9d59400 (diff)
Add an update_user endpoint
Diffstat (limited to 'src')
-rw-r--r--src/api/users.rs66
-rw-r--r--src/services/db.rs38
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
+}