lmrc-cli 0.3.16

CLI tool for scaffolding LMRC Stack infrastructure projects
Documentation
//! User HTTP handlers - Inbound adapters
//!
//! These handlers convert HTTP requests into domain operations and
//! domain responses into HTTP responses.
//!
//! In hexagonal architecture, these are "inbound adapters" - they adapt
//! external requests (HTTP) to the core application logic.
//!
//! ## RESTful Endpoints
//!
//! - GET    /api/users          - List users (with pagination)
//! - GET    /api/users/:id      - Get user by ID
//! - POST   /api/users          - Create new user
//! - PUT    /api/users/:id      - Update user
//! - DELETE /api/users/:id      - Delete user
//! - GET    /api/users/search   - Search users by email

use crate::error::Result;
use crate::features::examples::models::{CreateUserRequest, UpdateUserRequest};
use crate::features::examples::repository::UserRepository;
use crate::features::examples::service::UserService;
use crate::state::AppState;
use axum::{
    extract::{Path, Query, State},
    http::StatusCode,
    response::IntoResponse,
    Json,
};
use serde::Deserialize;
use uuid::Uuid;

/// Query parameters for listing users
#[derive(Debug, Deserialize)]
pub struct ListUsersQuery {
    #[serde(default = "default_page")]
    pub page: u64,
    #[serde(default = "default_per_page")]
    pub per_page: u64,
}

fn default_page() -> u64 {
    1
}

fn default_per_page() -> u64 {
    10
}

/// Query parameters for searching users
#[derive(Debug, Deserialize)]
pub struct SearchUsersQuery {
    pub email: String,
}

/// List users with pagination
///
/// GET /api/users?page=1&per_page=10
pub async fn list_users(
    State(state): State<AppState>,
    Query(query): Query<ListUsersQuery>,
) -> Result<impl IntoResponse> {
    let repository = UserRepository::new(state.db.clone());
    let service = UserService::new(repository);

    let response = service.list_users(query.page, query.per_page).await?;

    Ok(Json(response))
}

/// Get a user by ID
///
/// GET /api/users/:id
pub async fn get_user(
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> Result<impl IntoResponse> {
    let repository = UserRepository::new(state.db.clone());
    let service = UserService::new(repository);

    let user = service.get_user(id).await?;

    Ok(Json(user))
}

/// Create a new user
///
/// POST /api/users
/// Content-Type: application/json
///
/// {
///   "email": "user@example.com",
///   "name": "John Doe"
/// }
pub async fn create_user(
    State(state): State<AppState>,
    Json(request): Json<CreateUserRequest>,
) -> Result<impl IntoResponse> {
    let repository = UserRepository::new(state.db.clone());
    let service = UserService::new(repository);

    let user = service.create_user(request).await?;

    Ok((StatusCode::CREATED, Json(user)))
}

/// Update an existing user
///
/// PUT /api/users/:id
/// Content-Type: application/json
///
/// {
///   "email": "updated@example.com",
///   "name": "Updated Name"
/// }
pub async fn update_user(
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
    Json(request): Json<UpdateUserRequest>,
) -> Result<impl IntoResponse> {
    let repository = UserRepository::new(state.db.clone());
    let service = UserService::new(repository);

    let user = service.update_user(id, request).await?;

    Ok(Json(user))
}

/// Delete a user
///
/// DELETE /api/users/:id
pub async fn delete_user(
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> Result<impl IntoResponse> {
    let repository = UserRepository::new(state.db.clone());
    let service = UserService::new(repository);

    service.delete_user(id).await?;

    Ok(StatusCode::NO_CONTENT)
}

/// Search users by email
///
/// GET /api/users/search?email=user@example.com
pub async fn search_users(
    State(state): State<AppState>,
    Query(query): Query<SearchUsersQuery>,
) -> Result<impl IntoResponse> {
    let repository = UserRepository::new(state.db.clone());
    let service = UserService::new(repository);

    let user = service.search_by_email(&query.email).await?;

    match user {
        Some(user) => Ok(Json(Some(user))),
        None => Ok(Json(None)),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_default_pagination() {
        assert_eq!(default_page(), 1);
        assert_eq!(default_per_page(), 10);
    }
}