//! Anime2 (Alqanime) API handlers. use std::sync::Arc; use axum::extract::{Path, Query, State}; use axum::Json; use serde::Deserialize; use tracing::info; use utoipa::{IntoParams, ToSchema}; use crate::application::anime2::use_cases::Anime2UseCases; use crate::application::anime2::use_cases::{ Anime2Response, DetailResponse, FilterResponse, GenresResponse, }; use crate::domain::entity::anime::{ CompleteAnimeItem, GenreAnimeItem, LatestAnimeItem, OngoingAnimeItemWithScore, SearchAnimeItem, }; use crate::infrastructure::repository::AlqanimeRepository; use crate::presentation::dto::common::ApiResponse; use crate::presentation::error::AppError; use crate::presentation::state::AppState; // ============================================================================ // Request DTOs // ============================================================================ /// Filter query parameters for the anime2 filter endpoint. #[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)] pub struct FilterQuery { pub page: Option, pub genre: Option, pub status: Option, pub r#type: Option, pub order: Option, } // ============================================================================ // Helper // ============================================================================ fn make_use_cases(state: &Arc) -> Anime2UseCases { Anime2UseCases::new(AlqanimeRepository::new(), state.redis_pool.clone()) } // ============================================================================ // Handlers // ============================================================================ /// GET /api/anime2 — Anime2 index (ongoing + complete). #[utoipa::path( get, path = "/api/anime2", tag = "anime2", operation_id = "anime2_index", responses( (status = 200, description = "Anime2 index", body = Anime2Response), (status = 500, description = "Internal Server Error"), ) )] pub async fn index( State(app_state): State>, ) -> Result, AppError> { info!("Handling request for anime2 index"); let data = make_use_cases(&app_state).index().await?; Ok(Json(data)) } /// GET /api/anime2/genre_list — List all genres. #[utoipa::path( get, path = "/api/anime2/genre_list", tag = "anime2", operation_id = "anime2_genre_list", responses( (status = 200, description = "Genre list", body = GenresResponse), (status = 500, description = "Internal Server Error"), ) )] pub async fn genre_list( State(app_state): State>, ) -> Result, AppError> { info!("Handling request for anime2 genre list"); let data = make_use_cases(&app_state).genre_list().await?; Ok(Json(data)) } /// GET /api/anime2/filter?page=&genre=&status=&type=&order= — Filter anime. #[utoipa::path( get, path = "/api/anime2/filter", tag = "anime2", operation_id = "anime2_filter", params(FilterQuery), responses( (status = 200, description = "Filter results", body = FilterResponse), (status = 500, description = "Internal Server Error"), ) )] pub async fn filter( State(app_state): State>, Query(params): Query, ) -> Result, AppError> { info!("Handling request for anime2 filter"); let page = params.page.unwrap_or(1); let genre = params.genre.clone(); let status = params.status.clone(); let anime_type = params.r#type.clone(); let order = params.order.clone().unwrap_or_else(|| "update".to_string()); let data = make_use_cases(&app_state) .filter(page, genre, status, anime_type, order) .await?; Ok(Json(data)) } /// GET /api/anime2/detail/{slug} — Anime detail by slug. #[utoipa::path( get, path = "/api/anime2/detail/{slug}", tag = "anime2", operation_id = "anime2_detail_slug", responses( (status = 200, description = "Anime detail", body = DetailResponse), (status = 500, description = "Internal Server Error"), ) )] pub async fn detail_slug( State(app_state): State>, Path(slug): Path, ) -> Result, AppError> { info!("Handling request for anime2 detail slug: {}", slug); let data = make_use_cases(&app_state).detail(slug).await?; Ok(Json(data)) } /// GET /api/anime2/genre/{slug} — First page of genre-filtered results. #[utoipa::path( get, path = "/api/anime2/genre/{slug}", tag = "anime2", operation_id = "anime2_genre_slug_index", responses( (status = 200, description = "Genre page", body = ApiResponse>), (status = 500, description = "Internal Server Error"), ) )] pub async fn genre_slug_index( State(app_state): State>, Path(slug): Path, ) -> Result>>, AppError> { info!("Handling request for anime2 genre slug: {}", slug); let data = make_use_cases(&app_state).genre_slug(slug, 1).await?; Ok(Json(data)) } /// GET /api/anime2/genre/{slug}/{page} — Paginated genre results. #[utoipa::path( get, path = "/api/anime2/genre/{slug}/{page}", tag = "anime2", operation_id = "anime2_genre_slug_page", responses( (status = 200, description = "Genre page with page", body = ApiResponse>), (status = 500, description = "Internal Server Error"), ) )] pub async fn genre_slug_page( State(app_state): State>, Path((slug, page)): Path<(String, u32)>, ) -> Result>>, AppError> { info!( "Handling request for anime2 genre slug: {} page: {}", slug, page ); let data = make_use_cases(&app_state).genre_slug(slug, page).await?; Ok(Json(data)) } /// GET /api/anime2/search/{slug} — Search anime (first page). #[utoipa::path( get, path = "/api/anime2/search/{slug}", tag = "anime2", operation_id = "anime2_search_slug_index", responses( (status = 200, description = "Search results", body = ApiResponse>), (status = 500, description = "Internal Server Error"), ) )] pub async fn search_slug_index( State(app_state): State>, Path(slug): Path, ) -> Result>>, AppError> { info!("Handling request for anime2 search slug: {}", slug); let data = make_use_cases(&app_state).search(slug, 1).await?; Ok(Json(data)) } /// GET /api/anime2/search/{slug}/{page} — Paginated search results. #[utoipa::path( get, path = "/api/anime2/search/{slug}/{page}", tag = "anime2", operation_id = "anime2_search_slug_page", responses( (status = 200, description = "Search results with page", body = ApiResponse>), (status = 500, description = "Internal Server Error"), ) )] pub async fn search_slug_page( State(app_state): State>, Path((slug, page)): Path<(String, u32)>, ) -> Result>>, AppError> { info!( "Handling request for anime2 search slug: {} page: {}", slug, page ); let data = make_use_cases(&app_state).search(slug, page).await?; Ok(Json(data)) } /// GET /api/anime2/latest/{slug} — Latest anime (slug is the page number). #[utoipa::path( get, path = "/api/anime2/latest/{slug}", tag = "anime2", operation_id = "anime2_latest_slug", responses( (status = 200, description = "Latest anime page", body = ApiResponse>), (status = 500, description = "Internal Server Error"), ) )] pub async fn latest_slug( State(app_state): State>, Path(slug): Path, ) -> Result>>, AppError> { let page = slug .parse::() .map_err(|_| AppError::ScraperError(format!("Invalid page number: {}", slug)))?; info!("Handling request for anime2 latest page: {}", page); let data = make_use_cases(&app_state).latest(page).await?; Ok(Json(data)) } /// GET /api/anime2/ongoing_anime/{slug} — Ongoing anime list (slug is the page number). #[utoipa::path( get, path = "/api/anime2/ongoing_anime/{slug}", tag = "anime2", operation_id = "anime2_ongoing_anime_slug", responses( (status = 200, description = "Ongoing anime page", body = ApiResponse>), (status = 500, description = "Internal Server Error"), ) )] pub async fn ongoing_anime_slug( State(app_state): State>, Path(slug): Path, ) -> Result>>, AppError> { let page = slug .parse::() .map_err(|_| AppError::ScraperError(format!("Invalid page number: {}", slug)))?; info!("Handling request for anime2 ongoing page: {}", page); let data = make_use_cases(&app_state).ongoing_anime(page).await?; Ok(Json(data)) } /// GET /api/anime2/complete_anime/{slug} — Complete anime list (slug is the page number). #[utoipa::path( get, path = "/api/anime2/complete_anime/{slug}", tag = "anime2", operation_id = "anime2_complete_anime_slug", responses( (status = 200, description = "Complete anime page", body = ApiResponse>), (status = 500, description = "Internal Server Error"), ) )] pub async fn complete_anime_slug( State(app_state): State>, Path(slug): Path, ) -> Result>>, AppError> { let page = slug .parse::() .map_err(|_| AppError::ScraperError(format!("Invalid page number: {}", slug)))?; info!("Handling request for anime2 complete page: {}", page); let data = make_use_cases(&app_state).complete_anime(page).await?; Ok(Json(data)) }