home_handler.rs 186 lignes · 6178 octets
/// Home Handler
/// Feature 004: UI Post-Auth
///
/// Handles the home page after user authentication.
use askama::Template;
use axum::{
    extract::{Extension, State},
    response::IntoResponse,
};
use sea_orm::DatabaseConnection;
use std::collections::HashMap;
use std::sync::Arc;

use crate::dto::home_dto::AccountStatDTO;
use crate::dto::navigation_dto::NavigationDTO;
use crate::dto::user_profile_dto::UserProfileDTO;
use rustwarden_core::auth::jwt::Claims;
use rustwarden_core::error::AppError;
use rustwarden_core::i18n::extractor::Locale;
use rustwarden_core::i18n::service::I18nService;
use rustwarden_core::services::user_service::UserService;

/// Home page template
#[derive(Template)]
#[template(path = "home.html")]
#[allow(dead_code)]
struct HomeTemplate {
    page_title: String,
    csrf_token: String,
    app_name: String,
    app_version: String,
    app_domain: String,
    app_description: String,
    analytics_opt_out: crate::analytics::AnalyticsOptOutContext,
    username: String,
    email: String,
    initials: String,
    avatar_color: String,
    roles_text: String,
    nav_items: Vec<NavItem>,
    has_pending_password_change: bool,
    welcome_message: String,
    home_subtitle: String,
    home_my_account: String,
    account_stats: Vec<AccountStatDTO>,
    user_menu_profile: String,
    user_menu_logout: String,
}

/// Simplified navigation item for templates
#[derive(Clone)]
#[allow(dead_code)]
struct NavItem {
    #[allow(dead_code)]
    id: String,
    label: String,
    icon: String,
    url: String,
    active: bool,
    enabled: bool,
    badge: String,
    has_badge: bool,
    disabled_message: String,
    has_disabled_message: bool,
}

rustwarden_core::impl_template_response!(HomeTemplate);

/// Handler for GET /home - Home page (PROTECTED)
pub async fn home(
    Extension(claims): Extension<Claims>,
    Extension(i18n_service): Extension<Arc<I18nService>>,
    Extension(locale): Extension<Locale>,
    analytics: rustwarden_core::config::analytics::AnalyticsContext,
    State(db): State<DatabaseConnection>,
) -> Result<impl IntoResponse, AppError> {
    let username = claims.username.clone();
    tracing::debug!("Rendering home page for user: {}", username);

    let user_model = UserService::get_user_by_username(&db, &username)
        .await?
        .ok_or_else(|| {
            tracing::warn!("User {} from session not found in database", username);
            AppError::NotFound("User not found".to_string())
        })?;

    let roles = UserService::get_user_roles(&db, user_model.id).await?;
    let user = UserProfileDTO::new(user_model, roles.clone());

    let locale_str = locale.as_str();

    let mut vars = HashMap::new();
    vars.insert("username".to_string(), user.username.clone());
    let welcome_message = i18n_service
        .translate_with_vars("home.welcome", locale_str, &vars)
        .await;

    let home_subtitle = i18n_service.translate("home.subtitle", locale_str).await;
    let home_my_account = i18n_service.translate("home.my_account", locale_str).await;

    let stat_username_label = i18n_service
        .translate("home.stat_username", locale_str)
        .await;
    let stat_email_label = i18n_service.translate("home.stat_email", locale_str).await;
    let stat_member_since_label = i18n_service
        .translate("home.stat_member_since", locale_str)
        .await;
    let stat_roles_label = i18n_service.translate("home.stat_roles", locale_str).await;

    let account_stats = vec![
        AccountStatDTO::new(&stat_username_label, &user.username, "user"),
        AccountStatDTO::new(&stat_email_label, &user.email, "mail"),
        AccountStatDTO::new(
            &stat_member_since_label,
            &user.created_at.format("%d/%m/%Y").to_string(),
            "calendar",
        ),
        AccountStatDTO::new(&stat_roles_label, &user.roles.join(", "), "shield"),
    ];

    let user_menu_profile = i18n_service
        .translate("user_menu.profile", locale_str)
        .await;
    let user_menu_logout = i18n_service.translate("user_menu.logout", locale_str).await;

    let navigation = NavigationDTO::for_user(&roles, &i18n_service, &locale, Some(&db)).await;
    let nav_items: Vec<NavItem> = navigation
        .items
        .iter()
        .enumerate()
        .map(|(i, item)| NavItem {
            id: item.id.clone(),
            label: item.label.clone(),
            icon: item.icon.clone(),
            url: item.url.clone(),
            active: i == 0,
            enabled: item.enabled,
            badge: item.badge.clone(),
            has_badge: item.has_badge,
            disabled_message: item.disabled_message.clone(),
            has_disabled_message: item.has_disabled_message,
        })
        .collect();

    use rustwarden_core::services::app_settings_service::AppSettingsService;
    let app_domain = AppSettingsService::get_setting(&db, "app_domain")
        .await?
        .map(|s| s.value)
        .unwrap_or_else(|| "localhost".to_string());
    let app_description = AppSettingsService::get_setting(&db, "app_description")
        .await?
        .map(|s| s.value)
        .unwrap_or_else(|| "User accounts and authentication".to_string());

    let layout = rustwarden_core::templates::LayoutContext::new("", "");

    let template = HomeTemplate {
        page_title: layout.page_title.clone(),
        csrf_token: layout.csrf_token.clone(),
        app_name: layout.app_name.clone(),
        app_version: layout.app_version.clone(),
        app_domain,
        app_description,
        analytics_opt_out: crate::analytics::AnalyticsOptOutContext::build(
            &analytics,
            &i18n_service,
            locale.as_str(),
        )
        .await,
        username: user.username.clone(),
        email: user.email.clone(),
        initials: user.initials.clone(),
        avatar_color: user.avatar_color.clone(),
        roles_text: user.roles.join(", "),
        nav_items,
        has_pending_password_change: false,
        welcome_message,
        home_subtitle,
        home_my_account,
        account_stats,
        user_menu_profile,
        user_menu_logout,
    };

    Ok(template)
}