indexflow-core 0.1.0

Open Core Search Index Infrastructure
use super::{check_auth_or_redirect, AppState, HtmlTemplate, QueryParams};
use crate::domain::{PipelineStage, Url};
use crate::i18n::{detect_language, get_translations, Translations};
use crate::infrastructure::{DashboardStats, Site};
use askama::Template;
use axum::{
    extract::{Query, State},
    http::HeaderMap,
    response::{IntoResponse, Response},
};

#[derive(Template)]
#[template(path = "seo.html")]
pub struct SeoTemplate {
    pub lang: &'static str,
    pub t: &'static Translations,
    pub stats: DashboardStats,
    pub items: Vec<Url>,
    pub total: i64,
    pub page: i64,
    pub limit: i64,
    pub filter_status: Option<String>,
    pub query_str: Option<String>,
    pub all_sites: Vec<Site>,
    pub current_site_id: i64,
    pub is_running: bool,
    pub dry_run: bool,
}

pub async fn render_seo(
    State(state): State<AppState>,
    headers: HeaderMap,
    Query(q): Query<QueryParams>,
) -> Response {
    if let Some(redirect) = check_auth_or_redirect(&state, &headers).await {
        return redirect;
    }
    let (lang, set_cookie) = detect_language(&headers, q.lang.as_deref());
    let t = get_translations(lang);

    let all_sites = state.site_service.list_sites().await.unwrap_or_default();
    let site = state
        .site_service
        .get_site_or_default(q.site_id)
        .await
        .ok()
        .flatten();
    let current_site_id = site.as_ref().map(|s| s.id).unwrap_or(1);

    let stats = state
        .site_service
        .dashboard_stats(current_site_id)
        .await
        .unwrap_or_default();
    let page = q.page.unwrap_or(1).max(1);
    let limit = q.limit.unwrap_or(50).clamp(1, 200);
    let (items, total) = state
        .url_service
        .list_filtered(
            current_site_id,
            page,
            limit,
            q.q.as_deref(),
            q.status.as_deref(),
            None,
            None,
            false,
            None,
            None,
        )
        .await
        .unwrap_or_default();

    HtmlTemplate(
        SeoTemplate {
            lang: lang.as_str(),
            t,
            stats,
            items,
            total,
            page,
            limit,
            filter_status: q.status,
            query_str: q.q,
            all_sites,
            current_site_id,
            is_running: state
                .site_service
                .pipeline
                .is_running(current_site_id, PipelineStage::SeoGate),
            dry_run: state.dry_run,
        },
        set_cookie,
    )
    .into_response()
}

#[derive(Template)]
#[template(path = "seo_detail.html")]
pub struct SeoDetailTemplate {
    pub lang: &'static str,
    pub t: &'static Translations,
    pub url: Url,
    pub all_sites: Vec<Site>,
    pub current_site_id: i64,
    pub dry_run: bool,
}

pub async fn render_seo_detail_page(
    State(state): State<AppState>,
    headers: HeaderMap,
    axum::extract::Path(id): axum::extract::Path<i64>,
    Query(q): Query<QueryParams>,
) -> Response {
    if let Some(redirect) = check_auth_or_redirect(&state, &headers).await {
        return redirect;
    }
    let (lang, set_cookie) = detect_language(&headers, q.lang.as_deref());
    let t = get_translations(lang);

    let Some(url) = state.url_service.find_by_id(id).await.ok().flatten() else {
        return axum::http::StatusCode::NOT_FOUND.into_response();
    };

    let all_sites = state.site_service.list_sites().await.unwrap_or_default();
    let current_site_id = url.site_id;

    HtmlTemplate(
        SeoDetailTemplate {
            lang: lang.as_str(),
            t,
            url,
            all_sites,
            current_site_id,
            dry_run: state.dry_run,
        },
        set_cookie,
    )
    .into_response()
}