---
to: "generated/services/book-service/src/main.rs"
vars:
  service_name: "Book Management Service"
  framework: "axum"
  port: 8080
  database: "postgresql"
  description: "Comprehensive book management service with CRUD operations, search, and authentication"
rdf_inline:
  - "@prefix ex: <https://example.com/ai-generation/> . ex:BookService ex:manages ex:Book, ex:Author, ex:Category ."
sparql:
  book_operations: "SELECT ?operation WHERE { ex:BookService ex:handles ?operation }"
  author_operations: "SELECT ?operation WHERE { ex:AuthorService ex:handles ?operation }"
  category_operations: "SELECT ?operation WHERE { ex:CategoryService ex:handles ?operation }"
---
//! {{ service_name }}
//! 
//! {{ description }}
//! 
//! This service provides comprehensive book management functionality including:
//! - Book CRUD operations with validation
//! - Author management and relationships
//! - Category classification and filtering
//! - Advanced search with multiple criteria
//! - Pagination and sorting
//! - Authentication and authorization
//! - Database integration with PostgreSQL
//! - Redis caching for performance
//! - Comprehensive logging and monitoring
//! - OpenAPI documentation generation

use axum::{
    extract::{Path, Query, State},
    http::{HeaderMap, StatusCode},
    response::Json,
    routing::{get, post, put, delete},
    Router,
};
use serde::{Deserialize, Serialize};
use sqlx::{PgPool, Row};
use std::{collections::HashMap, sync::Arc};
use tokio::time::{timeout, Duration};
use tower::ServiceBuilder;
use tower_http::{
    cors::CorsLayer,
    trace::TraceLayer,
    timeout::TimeoutLayer,
};
use tracing::{info, error, warn};
use uuid::Uuid;
use chrono::{DateTime, Utc};
use thiserror::Error;
use redis::AsyncCommands;

/// Book entity representing a book in the library
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct Book {
    pub book_id: Uuid,
    pub title: String,
    pub isbn: String,
    pub description: Option<String>,
    pub publication_date: Option<DateTime<Utc>>,
    pub page_count: Option<i32>,
    pub language: String,
    pub price: Option<f64>,
    pub cover_image_url: Option<String>,
    pub author_id: Uuid,
    pub category_id: Uuid,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub is_active: bool,
}

/// Author entity representing a book author
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct Author {
    pub author_id: Uuid,
    pub first_name: String,
    pub last_name: String,
    pub biography: Option<String>,
    pub birth_date: Option<DateTime<Utc>>,
    pub nationality: Option<String>,
    pub website: Option<String>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub is_active: bool,
}

/// Category entity representing a book category
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct Category {
    pub category_id: Uuid,
    pub name: String,
    pub description: Option<String>,
    pub parent_category_id: Option<Uuid>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub is_active: bool,
}

/// Book creation request
#[derive(Debug, Deserialize)]
pub struct CreateBookRequest {
    pub title: String,
    pub isbn: String,
    pub description: Option<String>,
    pub publication_date: Option<DateTime<Utc>>,
    pub page_count: Option<i32>,
    pub language: String,
    pub price: Option<f64>,
    pub cover_image_url: Option<String>,
    pub author_id: Uuid,
    pub category_id: Uuid,
}

/// Book update request
#[derive(Debug, Deserialize)]
pub struct UpdateBookRequest {
    pub title: Option<String>,
    pub isbn: Option<String>,
    pub description: Option<String>,
    pub publication_date: Option<DateTime<Utc>>,
    pub page_count: Option<i32>,
    pub language: Option<String>,
    pub price: Option<f64>,
    pub cover_image_url: Option<String>,
    pub author_id: Option<Uuid>,
    pub category_id: Option<Uuid>,
}

/// Book search request
#[derive(Debug, Deserialize)]
pub struct BookSearchRequest {
    pub query: Option<String>,
    pub author_id: Option<Uuid>,
    pub category_id: Option<Uuid>,
    pub min_price: Option<f64>,
    pub max_price: Option<f64>,
    pub language: Option<String>,
    pub limit: Option<i64>,
    pub offset: Option<i64>,
    pub sort_by: Option<String>,
    pub sort_order: Option<String>,
}

/// Book response with related data
#[derive(Debug, Serialize)]
pub struct BookResponse {
    pub book_id: Uuid,
    pub title: String,
    pub isbn: String,
    pub description: Option<String>,
    pub publication_date: Option<DateTime<Utc>>,
    pub page_count: Option<i32>,
    pub language: String,
    pub price: Option<f64>,
    pub cover_image_url: Option<String>,
    pub author: AuthorResponse,
    pub category: CategoryResponse,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub is_active: bool,
}

/// Author response
#[derive(Debug, Serialize)]
pub struct AuthorResponse {
    pub author_id: Uuid,
    pub first_name: String,
    pub last_name: String,
    pub biography: Option<String>,
    pub birth_date: Option<DateTime<Utc>>,
    pub nationality: Option<String>,
    pub website: Option<String>,
}

/// Category response
#[derive(Debug, Serialize)]
pub struct CategoryResponse {
    pub category_id: Uuid,
    pub name: String,
    pub description: Option<String>,
    pub parent_category_id: Option<Uuid>,
}

/// Error types for book service
#[derive(Error, Debug)]
pub enum BookError {
    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),
    
    #[error("Redis error: {0}")]
    Redis(#[from] redis::RedisError),
    
    #[error("Book not found: {book_id}")]
    BookNotFound { book_id: Uuid },
    
    #[error("Author not found: {author_id}")]
    AuthorNotFound { author_id: Uuid },
    
    #[error("Category not found: {category_id}")]
    CategoryNotFound { category_id: Uuid },
    
    #[error("ISBN already exists: {isbn}")]
    IsbnExists { isbn: String },
    
    #[error("Invalid input: {message}")]
    InvalidInput { message: String },
    
    #[error("Authentication failed: {reason}")]
    AuthenticationFailed { reason: String },
    
    #[error("Internal server error: {0}")]
    Internal(#[from] anyhow::Error),
}

impl axum::response::IntoResponse for BookError {
    fn into_response(self) -> axum::response::Response {
        let (status, error_message) = match self {
            BookError::BookNotFound { .. } => (StatusCode::NOT_FOUND, self.to_string()),
            BookError::AuthorNotFound { .. } => (StatusCode::NOT_FOUND, self.to_string()),
            BookError::CategoryNotFound { .. } => (StatusCode::NOT_FOUND, self.to_string()),
            BookError::IsbnExists { .. } => (StatusCode::CONFLICT, self.to_string()),
            BookError::InvalidInput { .. } => (StatusCode::BAD_REQUEST, self.to_string()),
            BookError::AuthenticationFailed { .. } => (StatusCode::UNAUTHORIZED, self.to_string()),
            _ => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".to_string()),
        };

        let body = Json(serde_json::json!({
            "error": error_message,
            "timestamp": chrono::Utc::now()
        }));

        (status, body).into_response()
    }
}

/// Book service implementation
pub struct BookService {
    pool: PgPool,
    redis_client: redis::Client,
}

impl BookService {
    pub fn new(pool: PgPool, redis_client: redis::Client) -> Self {
        Self { pool, redis_client }
    }

    /// Create a new book
    pub async fn create_book(&self, request: CreateBookRequest) -> Result<BookResponse, BookError> {
        // Validate ISBN format
        if !self.is_valid_isbn(&request.isbn) {
            return Err(BookError::InvalidInput {
                message: "Invalid ISBN format".to_string(),
            });
        }

        // Check if ISBN already exists
        let existing_book = sqlx::query("SELECT book_id FROM books WHERE isbn = $1")
            .bind(&request.isbn)
            .fetch_optional(&self.pool)
            .await?;

        if existing_book.is_some() {
            return Err(BookError::IsbnExists {
                isbn: request.isbn.clone(),
            });
        }

        // Verify author exists
        let author = sqlx::query_as::<_, Author>(
            "SELECT * FROM authors WHERE author_id = $1 AND is_active = true"
        )
        .bind(request.author_id)
        .fetch_optional(&self.pool)
        .await?
        .ok_or_else(|| BookError::AuthorNotFound { author_id: request.author_id })?;

        // Verify category exists
        let category = sqlx::query_as::<_, Category>(
            "SELECT * FROM categories WHERE category_id = $1 AND is_active = true"
        )
        .bind(request.category_id)
        .fetch_optional(&self.pool)
        .await?
        .ok_or_else(|| BookError::CategoryNotFound { category_id: request.category_id })?;

        let book_id = Uuid::new_v4();
        let now = Utc::now();

        // Insert book into database
        sqlx::query(
            r#"
            INSERT INTO books (book_id, title, isbn, description, publication_date, page_count, language, price, cover_image_url, author_id, category_id, created_at, updated_at, is_active)
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
            "#
        )
        .bind(book_id)
        .bind(&request.title)
        .bind(&request.isbn)
        .bind(&request.description)
        .bind(&request.publication_date)
        .bind(&request.page_count)
        .bind(&request.language)
        .bind(&request.price)
        .bind(&request.cover_image_url)
        .bind(request.author_id)
        .bind(request.category_id)
        .bind(now)
        .bind(now)
        .bind(true)
        .execute(&self.pool)
        .await?;

        info!("Created book: {}", book_id);

        // Return book response with related data
        Ok(BookResponse {
            book_id,
            title: request.title,
            isbn: request.isbn,
            description: request.description,
            publication_date: request.publication_date,
            page_count: request.page_count,
            language: request.language,
            price: request.price,
            cover_image_url: request.cover_image_url,
            author: AuthorResponse {
                author_id: author.author_id,
                first_name: author.first_name,
                last_name: author.last_name,
                biography: author.biography,
                birth_date: author.birth_date,
                nationality: author.nationality,
                website: author.website,
            },
            category: CategoryResponse {
                category_id: category.category_id,
                name: category.name,
                description: category.description,
                parent_category_id: category.parent_category_id,
            },
            created_at: now,
            updated_at: now,
            is_active: true,
        })
    }

    /// Get book by ID
    pub async fn get_book(&self, book_id: Uuid) -> Result<BookResponse, BookError> {
        // Try to get from cache first
        let cache_key = format!("book:{}", book_id);
        let mut redis_conn = self.redis_client.get_async_connection().await?;
        
        if let Ok(cached) = redis_conn.get::<String, String>(&cache_key).await {
            if let Ok(book_response) = serde_json::from_str::<BookResponse>(&cached) {
                return Ok(book_response);
            }
        }

        // Get from database
        let book = sqlx::query_as::<_, Book>(
            r#"
            SELECT b.*, a.*, c.*
            FROM books b
            JOIN authors a ON b.author_id = a.author_id
            JOIN categories c ON b.category_id = c.category_id
            WHERE b.book_id = $1 AND b.is_active = true
            "#
        )
        .bind(book_id)
        .fetch_optional(&self.pool)
        .await?
        .ok_or_else(|| BookError::BookNotFound { book_id })?;

        let author = sqlx::query_as::<_, Author>(
            "SELECT * FROM authors WHERE author_id = $1"
        )
        .bind(book.author_id)
        .fetch_one(&self.pool)
        .await?;

        let category = sqlx::query_as::<_, Category>(
            "SELECT * FROM categories WHERE category_id = $1"
        )
        .bind(book.category_id)
        .fetch_one(&self.pool)
        .await?;

        let response = BookResponse {
            book_id: book.book_id,
            title: book.title,
            isbn: book.isbn,
            description: book.description,
            publication_date: book.publication_date,
            page_count: book.page_count,
            language: book.language,
            price: book.price,
            cover_image_url: book.cover_image_url,
            author: AuthorResponse {
                author_id: author.author_id,
                first_name: author.first_name,
                last_name: author.last_name,
                biography: author.biography,
                birth_date: author.birth_date,
                nationality: author.nationality,
                website: author.website,
            },
            category: CategoryResponse {
                category_id: category.category_id,
                name: category.name,
                description: category.description,
                parent_category_id: category.parent_category_id,
            },
            created_at: book.created_at,
            updated_at: book.updated_at,
            is_active: book.is_active,
        };

        // Cache the result
        let _: () = redis_conn.set_ex(
            &cache_key,
            serde_json::to_string(&response)?,
            3600, // 1 hour
        ).await?;

        Ok(response)
    }

    /// Search books with advanced criteria
    pub async fn search_books(&self, request: BookSearchRequest) -> Result<Vec<BookResponse>, BookError> {
        let mut query = String::from(
            r#"
            SELECT b.*, a.*, c.*
            FROM books b
            JOIN authors a ON b.author_id = a.author_id
            JOIN categories c ON b.category_id = c.category_id
            WHERE b.is_active = true
            "#
        );

        let mut conditions = Vec::new();
        let mut params: Vec<Box<dyn sqlx::Encode<'_, sqlx::Postgres> + Send + Sync>> = Vec::new();
        let mut param_count = 1;

        // Add search conditions
        if let Some(query_text) = &request.query {
            conditions.push(format!("(b.title ILIKE ${} OR b.description ILIKE ${} OR a.first_name ILIKE ${} OR a.last_name ILIKE ${})", 
                param_count, param_count + 1, param_count + 2, param_count + 3));
            let search_pattern = format!("%{}%", query_text);
            params.push(Box::new(search_pattern.clone()));
            params.push(Box::new(search_pattern.clone()));
            params.push(Box::new(search_pattern.clone()));
            params.push(Box::new(search_pattern));
            param_count += 4;
        }

        if let Some(author_id) = request.author_id {
            conditions.push(format!("b.author_id = ${}", param_count));
            params.push(Box::new(author_id));
            param_count += 1;
        }

        if let Some(category_id) = request.category_id {
            conditions.push(format!("b.category_id = ${}", param_count));
            params.push(Box::new(category_id));
            param_count += 1;
        }

        if let Some(min_price) = request.min_price {
            conditions.push(format!("b.price >= ${}", param_count));
            params.push(Box::new(min_price));
            param_count += 1;
        }

        if let Some(max_price) = request.max_price {
            conditions.push(format!("b.price <= ${}", param_count));
            params.push(Box::new(max_price));
            param_count += 1;
        }

        if let Some(language) = &request.language {
            conditions.push(format!("b.language = ${}", param_count));
            params.push(Box::new(language.clone()));
            param_count += 1;
        }

        // Add conditions to query
        if !conditions.is_empty() {
            query.push_str(" AND ");
            query.push_str(&conditions.join(" AND "));
        }

        // Add sorting
        let sort_by = request.sort_by.unwrap_or_else(|| "b.created_at".to_string());
        let sort_order = request.sort_order.unwrap_or_else(|| "DESC".to_string());
        query.push_str(&format!(" ORDER BY {} {}", sort_by, sort_order));

        // Add pagination
        let limit = request.limit.unwrap_or(10);
        let offset = request.offset.unwrap_or(0);
        query.push_str(&format!(" LIMIT {} OFFSET {}", limit, offset));

        // Execute query
        let books = sqlx::query_as::<_, Book>(&query)
            .fetch_all(&self.pool)
            .await?;

        // Convert to responses
        let mut responses = Vec::new();
        for book in books {
            let author = sqlx::query_as::<_, Author>(
                "SELECT * FROM authors WHERE author_id = $1"
            )
            .bind(book.author_id)
            .fetch_one(&self.pool)
            .await?;

            let category = sqlx::query_as::<_, Category>(
                "SELECT * FROM categories WHERE category_id = $1"
            )
            .bind(book.category_id)
            .fetch_one(&self.pool)
            .await?;

            responses.push(BookResponse {
                book_id: book.book_id,
                title: book.title,
                isbn: book.isbn,
                description: book.description,
                publication_date: book.publication_date,
                page_count: book.page_count,
                language: book.language,
                price: book.price,
                cover_image_url: book.cover_image_url,
                author: AuthorResponse {
                    author_id: author.author_id,
                    first_name: author.first_name,
                    last_name: author.last_name,
                    biography: author.biography,
                    birth_date: author.birth_date,
                    nationality: author.nationality,
                    website: author.website,
                },
                category: CategoryResponse {
                    category_id: category.category_id,
                    name: category.name,
                    description: category.description,
                    parent_category_id: category.parent_category_id,
                },
                created_at: book.created_at,
                updated_at: book.updated_at,
                is_active: book.is_active,
            });
        }

        Ok(responses)
    }

    /// Update book
    pub async fn update_book(&self, book_id: Uuid, request: UpdateBookRequest) -> Result<BookResponse, BookError> {
        let now = Utc::now();

        // Update book in database
        let rows_affected = sqlx::query(
            r#"
            UPDATE books 
            SET title = COALESCE($2, title),
                isbn = COALESCE($3, isbn),
                description = COALESCE($4, description),
                publication_date = COALESCE($5, publication_date),
                page_count = COALESCE($6, page_count),
                language = COALESCE($7, language),
                price = COALESCE($8, price),
                cover_image_url = COALESCE($9, cover_image_url),
                author_id = COALESCE($10, author_id),
                category_id = COALESCE($11, category_id),
                updated_at = $12
            WHERE book_id = $1 AND is_active = true
            "#
        )
        .bind(book_id)
        .bind(&request.title)
        .bind(&request.isbn)
        .bind(&request.description)
        .bind(&request.publication_date)
        .bind(&request.page_count)
        .bind(&request.language)
        .bind(&request.price)
        .bind(&request.cover_image_url)
        .bind(&request.author_id)
        .bind(&request.category_id)
        .bind(now)
        .execute(&self.pool)
        .await?
        .rows_affected();

        if rows_affected == 0 {
            return Err(BookError::BookNotFound { book_id });
        }

        info!("Updated book: {}", book_id);

        // Invalidate cache
        let cache_key = format!("book:{}", book_id);
        let mut redis_conn = self.redis_client.get_async_connection().await?;
        let _: () = redis_conn.del(&cache_key).await?;

        // Return updated book
        self.get_book(book_id).await
    }

    /// Delete book (soft delete)
    pub async fn delete_book(&self, book_id: Uuid) -> Result<(), BookError> {
        let rows_affected = sqlx::query(
            "UPDATE books SET is_active = false, updated_at = $2 WHERE book_id = $1 AND is_active = true"
        )
        .bind(book_id)
        .bind(Utc::now())
        .execute(&self.pool)
        .await?
        .rows_affected();

        if rows_affected == 0 {
            return Err(BookError::BookNotFound { book_id });
        }

        // Invalidate cache
        let cache_key = format!("book:{}", book_id);
        let mut redis_conn = self.redis_client.get_async_connection().await?;
        let _: () = redis_conn.del(&cache_key).await?;

        info!("Deleted book: {}", book_id);
        Ok(())
    }

    /// Validate ISBN format
    fn is_valid_isbn(&self, isbn: &str) -> bool {
        // Simple ISBN validation - in production, use a proper ISBN library
        isbn.len() == 13 && isbn.chars().all(|c| c.is_ascii_digit() || c == '-')
    }
}

/// Authentication middleware
async fn authenticate_request(headers: &HeaderMap) -> Result<String, BookError> {
    let auth_header = headers
        .get("authorization")
        .and_then(|h| h.to_str().ok())
        .ok_or_else(|| BookError::AuthenticationFailed { 
            reason: "Missing authorization header".to_string() 
        })?;

    if !auth_header.starts_with("Bearer ") {
        return Err(BookError::AuthenticationFailed { 
            reason: "Invalid authorization format".to_string() 
        });
    }

    let token = &auth_header[7..];
    
    // In a real implementation, validate the JWT token here
    if token.is_empty() {
        return Err(BookError::AuthenticationFailed { 
            reason: "Empty token".to_string() 
        });
    }

    Ok(token.to_string())
}

/// Health check endpoint
async fn health_check() -> Json<serde_json::Value> {
    Json(serde_json::json!({
        "status": "healthy",
        "service": "{{ service_name }}",
        "timestamp": chrono::Utc::now(),
        "version": "1.0.0"
    }))
}

/// Create book endpoint
async fn create_book(
    State(service): State<Arc<BookService>>,
    headers: HeaderMap,
    Json(payload): Json<CreateBookRequest>,
) -> Result<Json<BookResponse>, BookError> {
    // Authenticate request
    let _token = authenticate_request(&headers).await?;

    let book = service.create_book(payload).await?;
    Ok(Json(book))
}

/// Get book endpoint
async fn get_book(
    State(service): State<Arc<BookService>>,
    headers: HeaderMap,
    Path(book_id): Path<Uuid>,
) -> Result<Json<BookResponse>, BookError> {
    // Authenticate request
    let _token = authenticate_request(&headers).await?;

    let book = service.get_book(book_id).await?;
    Ok(Json(book))
}

/// Search books endpoint
async fn search_books(
    State(service): State<Arc<BookService>>,
    headers: HeaderMap,
    Query(params): Query<HashMap<String, String>>,
) -> Result<Json<serde_json::Value>, BookError> {
    // Authenticate request
    let _token = authenticate_request(&headers).await?;

    let request = BookSearchRequest {
        query: params.get("query").cloned(),
        author_id: params.get("author_id").and_then(|s| s.parse().ok()),
        category_id: params.get("category_id").and_then(|s| s.parse().ok()),
        min_price: params.get("min_price").and_then(|s| s.parse().ok()),
        max_price: params.get("max_price").and_then(|s| s.parse().ok()),
        language: params.get("language").cloned(),
        limit: params.get("limit").and_then(|s| s.parse().ok()),
        offset: params.get("offset").and_then(|s| s.parse().ok()),
        sort_by: params.get("sort_by").cloned(),
        sort_order: params.get("sort_order").cloned(),
    };

    let books = service.search_books(request).await?;
    Ok(Json(serde_json::json!({
        "books": books,
        "count": books.len()
    })))
}

/// Update book endpoint
async fn update_book(
    State(service): State<Arc<BookService>>,
    headers: HeaderMap,
    Path(book_id): Path<Uuid>,
    Json(payload): Json<UpdateBookRequest>,
) -> Result<Json<BookResponse>, BookError> {
    // Authenticate request
    let _token = authenticate_request(&headers).await?;

    let book = service.update_book(book_id, payload).await?;
    Ok(Json(book))
}

/// Delete book endpoint
async fn delete_book(
    State(service): State<Arc<BookService>>,
    headers: HeaderMap,
    Path(book_id): Path<Uuid>,
) -> Result<Json<serde_json::Value>, BookError> {
    // Authenticate request
    let _token = authenticate_request(&headers).await?;

    service.delete_book(book_id).await?;
    Ok(Json(serde_json::json!({
        "message": "Book deleted successfully"
    })))
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize tracing
    tracing_subscriber::fmt()
        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
        .init();

    // Get configuration from environment
    let database_url = std::env::var("DATABASE_URL")
        .expect("DATABASE_URL must be set");
    let redis_url = std::env::var("REDIS_URL")
        .expect("REDIS_URL must be set");

    // Create database connection pool
    let pool = PgPool::connect(&database_url)
        .await
        .expect("Failed to connect to database");

    // Run migrations
    sqlx::migrate!("./migrations")
        .run(&pool)
        .await
        .expect("Failed to run migrations");

    // Create Redis client
    let redis_client = redis::Client::open(redis_url)?;

    // Create book service
    let book_service = Arc::new(BookService::new(pool, redis_client));

    // Get port from environment
    let port = std::env::var("BOOK_SERVICE_PORT")
        .unwrap_or_else(|_| "{{ port }}".to_string())
        .parse::<u16>()?;

    info!("Starting {{ service_name }} on port {}", port);

    // Build router with middleware
    let app = Router::new()
        .route("/health", get(health_check))
        .route("/api/v1/books", post(create_book))
        .route("/api/v1/books/search", get(search_books))
        .route("/api/v1/books/:book_id", get(get_book))
        .route("/api/v1/books/:book_id", put(update_book))
        .route("/api/v1/books/:book_id", delete(delete_book))
        .layer(
            ServiceBuilder::new()
                .layer(TraceLayer::new_for_http())
                .layer(TimeoutLayer::new(Duration::from_secs(30)))
                .layer(CorsLayer::permissive())
        )
        .with_state(book_service);

    // Start server
    let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port)).await?;
    axum::serve(listener, app).await?;

    Ok(())
}
