Added basic Users database support
This commit is contained in:
@@ -15,6 +15,7 @@ regex = "1.12.2"
|
||||
serde = { version = "1.0.228", features = ["derive", "serde_derive"] }
|
||||
serde_json = "1.0.145"
|
||||
sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "postgres", "uuid", "chrono", "migrate"] }
|
||||
thiserror = "2.0.17"
|
||||
tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread"] }
|
||||
tower = { version = "0.5.2", features = ["tokio", "tracing"] }
|
||||
tower-http = { version = "0.6.7", features = ["cors"] }
|
||||
|
||||
8
src/backend/migrations/0002_create_users.sql
Normal file
8
src/backend/migrations/0002_create_users.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
CREATE TABLE users (
|
||||
uuid UUID PRIMARY KEY,
|
||||
username VARCHAR NOT NULL UNIQUE,
|
||||
email VARCHAR UNIQUE,
|
||||
password_hash VARCHAR NOT NULL,
|
||||
first_name VARCHAR,
|
||||
last_name VARCHAR
|
||||
);
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::prelude::*;
|
||||
use crate::{infra::db, prelude::*};
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::http::StatusCode;
|
||||
use anyhow::Result;
|
||||
use axum::{Json, http::StatusCode};
|
||||
use validator::Validate;
|
||||
|
||||
use crate::{
|
||||
@@ -10,14 +11,31 @@ use crate::{
|
||||
};
|
||||
|
||||
pub async fn create(state: Arc<AppState>, new_user: NewUser) -> Result<User, StatusCode> {
|
||||
if let Err(_) = new_user.validate() {
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
new_user.validate().map_err(|e| {
|
||||
error!("User validation failed: {e}");
|
||||
StatusCode::BAD_REQUEST
|
||||
})?;
|
||||
|
||||
let internal = InternalNewUser::try_from(new_user).map_err(|e| {
|
||||
error!("Conversion to InternalUser failed: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
todo!("Hook up return once db setup ready");
|
||||
let created_user = db::user::create(&state.db_pool, internal)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Failed to create new user: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
Ok(User::from(created_user))
|
||||
}
|
||||
|
||||
pub async fn get_all(state: Arc<AppState>) -> Result<Vec<User>, StatusCode> {
|
||||
let users = db::user::get_safe_all(&state.db_pool).await.map_err(|e| {
|
||||
error!("Failed to fetch all users: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
Ok(users)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use axum::response::IntoResponse;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::prelude::FromRow;
|
||||
use uuid::Uuid;
|
||||
use validator::Validate;
|
||||
|
||||
@@ -30,25 +31,25 @@ pub struct NewUser {
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct InternalNewUser {
|
||||
uuid: Uuid,
|
||||
username: String,
|
||||
email: Option<String>,
|
||||
password_hash: String,
|
||||
first_name: Option<String>,
|
||||
last_name: Option<String>,
|
||||
pub uuid: Uuid,
|
||||
pub username: String,
|
||||
pub email: Option<String>,
|
||||
pub password_hash: String,
|
||||
pub first_name: Option<String>,
|
||||
pub last_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize, FromRow)]
|
||||
pub struct InternalUser {
|
||||
uuid: Uuid,
|
||||
username: String,
|
||||
email: Option<String>,
|
||||
password_hash: String,
|
||||
first_name: Option<String>,
|
||||
last_name: Option<String>,
|
||||
pub uuid: Uuid,
|
||||
pub username: String,
|
||||
pub email: Option<String>,
|
||||
pub password_hash: String,
|
||||
pub first_name: Option<String>,
|
||||
pub last_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct User {
|
||||
uuid: Uuid,
|
||||
username: String,
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
use crate::{
|
||||
domain::user::{InternalNewUser, InternalUser, User},
|
||||
prelude::*,
|
||||
};
|
||||
use anyhow::{Ok, Result, anyhow};
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn create(pool: &PgPool, new_user: InternalNewUser) -> Result<InternalUser> {
|
||||
let user = sqlx::query_as::<_, InternalUser>(
|
||||
r#"
|
||||
INSERT INTO users (uuid, username, email, password_hash, first_name, last_name)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING uuid, username, email, password_hash, first_name, last_name
|
||||
"#,
|
||||
)
|
||||
.bind(new_user.uuid)
|
||||
.bind(&new_user.username)
|
||||
.bind(&new_user.email)
|
||||
.bind(&new_user.password_hash)
|
||||
.bind(&new_user.first_name)
|
||||
.bind(&new_user.last_name)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
pub async fn get_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<InternalUser>> {
|
||||
let user = sqlx::query_as::<_, InternalUser>(
|
||||
r#"
|
||||
SELECT uuid, username, email, password_hash, first_name, last_name FROM users WHERE uuid = $
|
||||
"#,
|
||||
)
|
||||
.bind(uuid)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
pub async fn get_safe_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<User>> {
|
||||
let user = sqlx::query_as::<_, User>(
|
||||
r#"
|
||||
SELECT uuid, username, email, first_name, last_name FROM users WHERE uuid = $
|
||||
"#,
|
||||
)
|
||||
.bind(uuid)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
pub async fn get_all(pool: &PgPool) -> Result<Vec<InternalUser>> {
|
||||
let users = sqlx::query_as::<_, InternalUser>(
|
||||
r#"
|
||||
SELECT uuid, username, email, password_hash, first_name, last_name
|
||||
FROM users
|
||||
ORDER BY username ASC
|
||||
"#,
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
Ok(users)
|
||||
}
|
||||
|
||||
pub async fn get_safe_all(pool: &PgPool) -> Result<Vec<User>> {
|
||||
let users = sqlx::query_as::<_, User>(
|
||||
r#"
|
||||
SELECT uuid, username, email, first_name, last_name
|
||||
FROM users
|
||||
ORDER BY username ASC
|
||||
"#,
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
Ok(users)
|
||||
}
|
||||
|
||||
@@ -23,10 +23,13 @@ pub async fn init_router(app_state: Arc<AppState>) -> Router {
|
||||
),
|
||||
)
|
||||
.route(
|
||||
"/api/user/create",
|
||||
"/api/users",
|
||||
post(user_routes::create)
|
||||
.layer(ServiceBuilder::new().layer(middleware::cors()))
|
||||
.with_state(app_state),
|
||||
.with_state(app_state.clone())
|
||||
.get(user_routes::get_all)
|
||||
.layer(ServiceBuilder::new().layer(middleware::cors()))
|
||||
.with_state(app_state.clone()),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,22 @@
|
||||
use crate::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{Json, extract::State, http::StatusCode};
|
||||
use serde_json::json;
|
||||
use tracing::{debug, error, info, warn};
|
||||
use validator::Validate;
|
||||
|
||||
use crate::{
|
||||
core,
|
||||
domain::user::{InternalNewUser, NewUser, User},
|
||||
state::AppState,
|
||||
};
|
||||
use axum::{Json, extract::State, http::StatusCode};
|
||||
|
||||
pub async fn create(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(new_user): Json<NewUser>,
|
||||
) -> Result<Json<User>, StatusCode> {
|
||||
if let Err(_) = new_user.validate() {
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
let internal = InternalNewUser::try_from(new_user).map_err(|e| {
|
||||
error!("Conversion to InternalUser failed: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
todo!("Hook up return once db setup ready");
|
||||
let user = core::user_routines::create(state, new_user).await?;
|
||||
Ok(Json(user))
|
||||
}
|
||||
|
||||
pub async fn get_all(State(state): State<Arc<AppState>>) -> Result<Json<Vec<User>>, StatusCode> {
|
||||
let users = core::user_routines::get_all(state).await?;
|
||||
Ok(Json(users))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user