Create controllers functions

This commit is contained in:
2025-09-08 21:17:09 +02:00
parent f03a6935d5
commit 453ae9ceea
4 changed files with 519 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
use crate::controllers::users::UsersController;
use crate::utils::{error::*, models::*, DbPool};
use chrono::{Duration, Utc};
use sqlx::Row;
use uuid::Uuid;
pub struct AuthController;
impl AuthController {
pub async fn login(pool: &DbPool, username: &str, password: &str) -> AppResult<LoginResponse> {
let user = UsersController::verify_user_credentials(pool, username, password).await?;
let session = Self::create_session(pool, user.id).await?;
Ok(LoginResponse {
token: session.token,
role: user.role,
})
}
pub async fn logout(pool: &DbPool, user_id: i64) -> AppResult<()> {
sqlx::query("DELETE FROM sessions WHERE user_id = ?")
.bind(user_id)
.execute(pool)
.await?;
Ok(())
}
pub async fn create_session(pool: &DbPool, user_id: i64) -> AppResult<Session> {
let token = Self::generate_session_token();
let expires_at = Utc::now() + Duration::days(30);
let result = sqlx::query(
r#"
INSERT INTO sessions (user_id, token, expires_at)
VALUES (?, ?, ?)
"#,
)
.bind(user_id)
.bind(&token)
.bind(expires_at)
.execute(pool)
.await?;
Ok(Session {
id: result.last_insert_rowid(),
user_id,
token,
created_at: Utc::now(),
expires_at,
})
}
pub async fn get_session_by_token(pool: &DbPool, token: &str) -> AppResult<Option<Session>> {
let row = sqlx::query(
r#"
SELECT id, user_id, token, created_at, expires_at
FROM sessions WHERE token = ? AND expires_at > datetime('now')
"#,
)
.bind(token)
.fetch_optional(pool)
.await?;
if let Some(row) = row {
Ok(Some(Session {
id: row.get("id"),
user_id: row.get("user_id"),
token: row.get("token"),
created_at: row.get("created_at"),
expires_at: row.get("expires_at"),
}))
} else {
Ok(None)
}
}
pub async fn authenticate_user(pool: &DbPool, token: &str) -> AppResult<User> {
let session = Self::get_session_by_token(pool, token)
.await?
.ok_or_else(|| auth_error("Invalid or expired session"))?;
let user = UsersController::get_user_by_id(pool, session.user_id).await?;
Ok(user)
}
pub async fn cleanup_expired_sessions(pool: &DbPool) -> AppResult<()> {
sqlx::query("DELETE FROM sessions WHERE expires_at <= datetime('now')")
.execute(pool)
.await?;
Ok(())
}
fn generate_session_token() -> String {
Uuid::new_v4().to_string()
}
#[allow(dead_code)]
pub async fn delete_session_by_token(pool: &DbPool, token: &str) -> AppResult<()> {
sqlx::query("DELETE FROM sessions WHERE token = ?")
.bind(token)
.execute(pool)
.await?;
Ok(())
}
}

View File

@@ -0,0 +1,208 @@
use crate::utils::{error::*, models::*, DbPool};
use chrono::Utc;
use sqlx::Row;
use uuid::Uuid;
pub struct MachinesController;
impl MachinesController {
pub async fn register_machine(
pool: &DbPool,
code: &str,
uuid: &Uuid,
name: &str,
) -> AppResult<Machine> {
Self::validate_machine_input(name)?;
let provisioning_code = Self::get_provisioning_code(pool, code)
.await?
.ok_or_else(|| validation_error("Invalid provisioning code"))?;
if provisioning_code.used {
return Err(validation_error("Provisioning code already used"));
}
if provisioning_code.expires_at < Utc::now() {
return Err(validation_error("Provisioning code expired"));
}
if Self::machine_exists_by_uuid(pool, uuid).await? {
return Err(conflict_error("Machine with this UUID already exists"));
}
let machine = Self::create_machine(pool, provisioning_code.user_id, uuid, name).await?;
Self::mark_provisioning_code_used(pool, code).await?;
Ok(machine)
}
pub async fn get_machines_for_user(pool: &DbPool, user: &User) -> AppResult<Vec<Machine>> {
if user.role == UserRole::Admin {
Self::get_all_machines(pool).await
} else {
Self::get_machines_by_user_id(pool, user.id).await
}
}
pub async fn delete_machine(pool: &DbPool, machine_id: i64, user: &User) -> AppResult<()> {
let machine = Self::get_machine_by_id(pool, machine_id).await?;
if user.role != UserRole::Admin && machine.user_id != user.id {
return Err(forbidden_error("Access denied"));
}
Self::delete_machine_by_id(pool, machine_id).await
}
pub async fn get_machine_by_id(pool: &DbPool, id: i64) -> AppResult<Machine> {
let row = sqlx::query(
r#"
SELECT id, user_id, uuid, name, created_at
FROM machines WHERE id = ?
"#,
)
.bind(id)
.fetch_one(pool)
.await?;
Ok(Machine {
id: row.get("id"),
user_id: row.get("user_id"),
uuid: Uuid::parse_str(&row.get::<String, _>("uuid")).unwrap(),
name: row.get("name"),
created_at: row.get("created_at"),
})
}
async fn get_all_machines(pool: &DbPool) -> AppResult<Vec<Machine>> {
let rows = sqlx::query(
r#"
SELECT id, user_id, uuid, name, created_at
FROM machines ORDER BY created_at DESC
"#,
)
.fetch_all(pool)
.await?;
let mut machines = Vec::new();
for row in rows {
machines.push(Machine {
id: row.get("id"),
user_id: row.get("user_id"),
uuid: Uuid::parse_str(&row.get::<String, _>("uuid")).unwrap(),
name: row.get("name"),
created_at: row.get("created_at"),
});
}
Ok(machines)
}
async fn get_machines_by_user_id(pool: &DbPool, user_id: i64) -> AppResult<Vec<Machine>> {
let rows = sqlx::query(
r#"
SELECT id, user_id, uuid, name, created_at
FROM machines WHERE user_id = ? ORDER BY created_at DESC
"#,
)
.bind(user_id)
.fetch_all(pool)
.await?;
let mut machines = Vec::new();
for row in rows {
machines.push(Machine {
id: row.get("id"),
user_id: row.get("user_id"),
uuid: Uuid::parse_str(&row.get::<String, _>("uuid")).unwrap(),
name: row.get("name"),
created_at: row.get("created_at"),
});
}
Ok(machines)
}
async fn create_machine(
pool: &DbPool,
user_id: i64,
uuid: &Uuid,
name: &str,
) -> AppResult<Machine> {
let result = sqlx::query(
r#"
INSERT INTO machines (user_id, uuid, name)
VALUES (?, ?, ?)
"#,
)
.bind(user_id)
.bind(uuid.to_string())
.bind(name)
.execute(pool)
.await?;
Self::get_machine_by_id(pool, result.last_insert_rowid()).await
}
async fn machine_exists_by_uuid(pool: &DbPool, uuid: &Uuid) -> AppResult<bool> {
let row = sqlx::query("SELECT COUNT(*) as count FROM machines WHERE uuid = ?")
.bind(uuid.to_string())
.fetch_one(pool)
.await?;
let count: i64 = row.get("count");
Ok(count > 0)
}
async fn delete_machine_by_id(pool: &DbPool, id: i64) -> AppResult<()> {
sqlx::query("DELETE FROM machines WHERE id = ?")
.bind(id)
.execute(pool)
.await?;
Ok(())
}
async fn get_provisioning_code(
pool: &DbPool,
code: &str,
) -> AppResult<Option<ProvisioningCode>> {
let row = sqlx::query(
r#"
SELECT id, user_id, code, created_at, expires_at, used
FROM provisioning_codes WHERE code = ?
"#,
)
.bind(code)
.fetch_optional(pool)
.await?;
if let Some(row) = row {
Ok(Some(ProvisioningCode {
id: row.get("id"),
user_id: row.get("user_id"),
code: row.get("code"),
created_at: row.get("created_at"),
expires_at: row.get("expires_at"),
used: row.get("used"),
}))
} else {
Ok(None)
}
}
async fn mark_provisioning_code_used(pool: &DbPool, code: &str) -> AppResult<()> {
sqlx::query("UPDATE provisioning_codes SET used = 1 WHERE code = ?")
.bind(code)
.execute(pool)
.await?;
Ok(())
}
fn validate_machine_input(name: &str) -> AppResult<()> {
if name.trim().is_empty() {
return Err(validation_error("Machine name cannot be empty"));
}
Ok(())
}
}

View File

@@ -0,0 +1,3 @@
pub mod auth;
pub mod machines;
pub mod users;

View File

@@ -0,0 +1,202 @@
use crate::utils::{error::*, models::*, DbPool};
use bcrypt::{hash, verify, DEFAULT_COST};
use sqlx::Row;
use std::str::FromStr;
pub struct UsersController;
impl UsersController {
pub async fn create_user(
pool: &DbPool,
username: &str,
password: &str,
role: UserRole,
storage_limit_gb: i64,
) -> AppResult<User> {
Self::validate_user_input(username, password)?;
let password_hash = hash(password, DEFAULT_COST)?;
let result = sqlx::query(
r#"
INSERT INTO users (username, password_hash, role, storage_limit_gb)
VALUES (?, ?, ?, ?)
"#,
)
.bind(username)
.bind(&password_hash)
.bind(role.to_string())
.bind(storage_limit_gb)
.execute(pool)
.await?;
Self::get_user_by_id(pool, result.last_insert_rowid()).await
}
pub async fn get_user_by_username(pool: &DbPool, username: &str) -> AppResult<Option<User>> {
let row = sqlx::query(
r#"
SELECT id, username, password_hash, role, storage_limit_gb, created_at
FROM users WHERE username = ?
"#,
)
.bind(username)
.fetch_optional(pool)
.await?;
if let Some(row) = row {
Ok(Some(User {
id: row.get("id"),
username: row.get("username"),
password_hash: row.get("password_hash"),
role: UserRole::from_str(&row.get::<String, _>("role")).unwrap(),
storage_limit_gb: row.get("storage_limit_gb"),
created_at: row.get("created_at"),
}))
} else {
Ok(None)
}
}
pub async fn get_user_by_id(pool: &DbPool, id: i64) -> AppResult<User> {
let row = sqlx::query(
r#"
SELECT id, username, password_hash, role, storage_limit_gb, created_at
FROM users WHERE id = ?
"#,
)
.bind(id)
.fetch_one(pool)
.await?;
Ok(User {
id: row.get("id"),
username: row.get("username"),
password_hash: row.get("password_hash"),
role: UserRole::from_str(&row.get::<String, _>("role")).unwrap(),
storage_limit_gb: row.get("storage_limit_gb"),
created_at: row.get("created_at"),
})
}
pub async fn get_all_users(pool: &DbPool) -> AppResult<Vec<User>> {
let rows = sqlx::query(
r#"
SELECT id, username, password_hash, role, storage_limit_gb, created_at
FROM users ORDER BY created_at DESC
"#,
)
.fetch_all(pool)
.await?;
let mut users = Vec::new();
for row in rows {
users.push(User {
id: row.get("id"),
username: row.get("username"),
password_hash: row.get("password_hash"),
role: UserRole::from_str(&row.get::<String, _>("role")).unwrap(),
storage_limit_gb: row.get("storage_limit_gb"),
created_at: row.get("created_at"),
});
}
Ok(users)
}
pub async fn update_user(
pool: &DbPool,
id: i64,
request: UpdateUserRequest,
) -> AppResult<User> {
let current_user = Self::get_user_by_id(pool, id).await?;
let username = request.username.clone().unwrap_or(current_user.username);
let role = request.role.unwrap_or(current_user.role);
let storage_limit_gb = request
.storage_limit_gb
.unwrap_or(current_user.storage_limit_gb);
if request.username.is_some() || request.password.is_some() {
Self::validate_user_input(
&username,
&request.password.as_deref().unwrap_or("validpassword"),
)?;
}
if let Some(password) = request.password {
let password_hash = hash(&password, DEFAULT_COST)?;
sqlx::query(
r#"
UPDATE users
SET username = ?, password_hash = ?, role = ?, storage_limit_gb = ?
WHERE id = ?
"#,
)
.bind(&username)
.bind(&password_hash)
.bind(role.to_string())
.bind(storage_limit_gb)
.bind(id)
.execute(pool)
.await?;
} else {
sqlx::query(
r#"
UPDATE users
SET username = ?, role = ?, storage_limit_gb = ?
WHERE id = ?
"#,
)
.bind(&username)
.bind(role.to_string())
.bind(storage_limit_gb)
.bind(id)
.execute(pool)
.await?;
}
Self::get_user_by_id(pool, id).await
}
pub async fn delete_user(pool: &DbPool, id: i64) -> AppResult<()> {
Self::get_user_by_id(pool, id).await?;
sqlx::query("DELETE FROM users WHERE id = ?")
.bind(id)
.execute(pool)
.await?;
Ok(())
}
pub async fn verify_user_credentials(
pool: &DbPool,
username: &str,
password: &str,
) -> AppResult<User> {
let user = Self::get_user_by_username(pool, username)
.await?
.ok_or_else(|| auth_error("Invalid credentials"))?;
let is_valid = verify(password, &user.password_hash)?;
if !is_valid {
return Err(auth_error("Invalid credentials"));
}
Ok(user)
}
fn validate_user_input(username: &str, password: &str) -> AppResult<()> {
if username.trim().is_empty() {
return Err(validation_error("Username cannot be empty"));
}
if password.len() < 8 {
return Err(validation_error(
"Password must be at least 8 characters long",
));
}
Ok(())
}
}