use serde::{Deserialize, Serialize}; use sqlx::FromRow; use utoipa::ToSchema; use uuid::Uuid; #[derive(Debug, Serialize, Deserialize, FromRow, Clone, PartialEq, Eq, ToSchema)] pub struct User { pub id: i64, pub user_id: String, // API token pub role: UserRole, } #[derive(Debug, Serialize, Deserialize, sqlx::Type, Clone, PartialEq, Eq, Default, ToSchema)] #[sqlx(type_name = "TEXT")] pub enum UserRole { #[serde(rename = "user")] #[default] User, #[serde(rename = "admin")] Admin, } #[derive(Debug, Serialize, Deserialize, FromRow, Clone, PartialEq, ToSchema)] pub struct UserWithLocation { pub id: i64, pub user_id: String, pub role: UserRole, pub location_id: i64, pub latitude: f64, pub longitude: f64, } pub struct UserRepository<'a> { pub db: &'a sqlx::SqlitePool, } impl<'a> UserRepository<'a> { pub async fn list_users(&self) -> Result, sqlx::Error> { sqlx::query_as::<_, User>("SELECT id, user_id, role FROM users") .fetch_all(self.db) .await } pub async fn get_user_by_id(&self, id: i64) -> Result, sqlx::Error> { sqlx::query_as::<_, User>("SELECT id, user_id, role FROM users WHERE id = ?") .bind(id) .fetch_optional(self.db) .await } pub async fn get_user_by_user_id(&self, user_id: &str) -> Result, sqlx::Error> { sqlx::query_as::<_, User>("SELECT id, user_id, role FROM users WHERE user_id = ?") .bind(user_id) .fetch_optional(self.db) .await } pub async fn create_user( &self, user_id: Option, role: Option, ) -> Result { let user_id = user_id.unwrap_or_else(|| Uuid::new_v4().to_string()); let role = role.unwrap_or_default(); sqlx::query_as::<_, User>( "INSERT INTO users (user_id, role) VALUES (?, ?) RETURNING id, user_id, role", ) .bind(user_id) .bind(role) .fetch_one(self.db) .await } pub async fn update_user(&self, id: i64, role: UserRole) -> Result { sqlx::query_as::<_, User>( "UPDATE users SET role = ? WHERE id = ? RETURNING id, user_id, role", ) .bind(role) .bind(id) .fetch_one(self.db) .await } pub async fn delete_user(&self, id: i64) -> Result<(), sqlx::Error> { sqlx::query("DELETE FROM users WHERE id = ?") .bind(id) .execute(self.db) .await?; Ok(()) } pub async fn list_users_with_locations(&self) -> Result, sqlx::Error> { sqlx::query_as::<_, UserWithLocation>( "SELECT u.id, u.user_id, u.role, l.id as location_id, l.latitude, l.longitude FROM users u LEFT JOIN locations l ON u.id = l.user_id WHERE l.id IS NOT NULL", ) .fetch_all(self.db) .await } pub async fn any_admin_exists(&self) -> Result { let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM users WHERE role = ?") .bind(UserRole::Admin) .fetch_one(self.db) .await?; Ok(count.0 > 0) } } #[cfg(test)] mod tests { use super::*; use sqlx::{Executor, SqlitePool}; async fn setup_db() -> SqlitePool { let pool = SqlitePool::connect(":memory:").await.unwrap(); pool.execute( "CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL UNIQUE, role TEXT NOT NULL DEFAULT 'user' );", ) .await .unwrap(); pool } #[tokio::test] async fn test_create_and_get_user() { let db = setup_db().await; let repo = UserRepository { db: &db }; let user = repo.create_user(None, Some(UserRole::Admin)).await.unwrap(); assert_eq!(user.role, UserRole::Admin); let fetched = repo .get_user_by_user_id(&user.user_id) .await .unwrap() .unwrap(); assert_eq!(fetched.user_id, user.user_id); } #[tokio::test] async fn test_update_user() { let db = setup_db().await; let repo = UserRepository { db: &db }; let user = repo.create_user(None, Some(UserRole::User)).await.unwrap(); let updated = repo.update_user(user.id, UserRole::Admin).await.unwrap(); assert_eq!(updated.role, UserRole::Admin); } #[tokio::test] async fn test_delete_user() { let db = setup_db().await; let repo = UserRepository { db: &db }; let user = repo.create_user(None, None).await.unwrap(); repo.delete_user(user.id).await.unwrap(); let fetched = repo.get_user_by_id(user.id).await.unwrap(); assert!(fetched.is_none()); } #[tokio::test] async fn test_list_users() { let db = setup_db().await; let repo = UserRepository { db: &db }; repo.create_user(None, Some(UserRole::User)).await.unwrap(); repo.create_user(None, Some(UserRole::Admin)).await.unwrap(); let users = repo.list_users().await.unwrap(); assert_eq!(users.len(), 2); } #[tokio::test] async fn test_any_admin_exists() { let db = setup_db().await; let repo = UserRepository { db: &db }; // No admin yet assert!(!repo.any_admin_exists().await.unwrap()); // Add a user (not admin) repo.create_user(None, Some(UserRole::User)).await.unwrap(); assert!(!repo.any_admin_exists().await.unwrap()); // Add an admin repo.create_user(None, Some(UserRole::Admin)).await.unwrap(); assert!(repo.any_admin_exists().await.unwrap()); } }