Skip to main content

reinhardt_admin/server/
dashboard.rs

1//! Dashboard Server Function
2//!
3//! Provides dashboard data retrieval functionality.
4
5use crate::adapters::{AdminSite, DashboardResponse, ModelInfo};
6#[cfg(server)]
7use crate::core::AdminSiteKey;
8#[cfg(server)]
9use reinhardt_di::Depends;
10#[cfg(server)]
11use reinhardt_pages::server_fn::ServerFnRequest;
12use reinhardt_pages::server_fn::{ServerFnError, server_fn};
13
14#[cfg(server)]
15use super::admin_auth::AdminAuthenticatedUser;
16#[cfg(server)]
17use super::error::AdminAuth;
18#[cfg(server)]
19use super::security::{build_csrf_cookie, generate_csrf_token};
20
21/// Get dashboard data
22///
23/// Returns dashboard information including registered models and site metadata.
24///
25/// # Server Function
26///
27/// This function is automatically exposed as an HTTP endpoint by the `#[server_fn]` macro.
28/// The AdminSite dependency is automatically injected via the DI system.
29///
30/// # Authentication
31///
32/// Requires staff (admin) permission to access the admin panel.
33///
34/// # Example
35///
36/// ```ignore
37/// use reinhardt_admin::server::get_dashboard;
38///
39/// // Client-side usage (automatically generates HTTP request)
40/// let dashboard = get_dashboard().await?;
41/// println!("Site: {}", dashboard.site_name);
42/// ```
43#[server_fn]
44pub async fn get_dashboard(
45	#[inject] site: Depends<AdminSiteKey, AdminSite>,
46	#[inject] http_request: ServerFnRequest,
47	#[inject] AdminAuthenticatedUser(_user): AdminAuthenticatedUser,
48) -> Result<DashboardResponse, ServerFnError> {
49	// Authentication and authorization check (Fixes #3679)
50	// AdminAuthenticatedUser injection performs DB lookup to verify is_active and is_staff.
51	// AdminAuth::require_staff() provides the HTTP-level error response.
52	let auth = AdminAuth::from_request(&http_request);
53	auth.require_staff()?;
54
55	// Collect model information
56	let models: Vec<ModelInfo> = site
57		.registered_models()
58		.into_iter()
59		.map(|name| {
60			let list_url = format!("{}/{}/", site.url_prefix(), name.to_lowercase());
61			ModelInfo { name, list_url }
62		})
63		.collect();
64
65	// Build dashboard response with CSRF token for mutation requests
66	let csrf_token = generate_csrf_token();
67
68	// Set the CSRF token as a cookie via the shared cookie jar.
69	// The server function router reads SharedResponseCookies and
70	// applies them as Set-Cookie headers on the HTTP response.
71	let is_secure = http_request.inner().is_secure;
72	let cookie_value = build_csrf_cookie(&csrf_token, is_secure);
73	http_request.add_response_cookie(cookie_value);
74
75	let admin_settings = crate::settings::get_admin_settings();
76
77	Ok(DashboardResponse {
78		site_name: site.name().to_string(),
79		site_header: admin_settings.site_header.clone(),
80		url_prefix: site.url_prefix().to_string(),
81		login_url: admin_settings.login_url.clone(),
82		logout_url: admin_settings.logout_url.clone(),
83		models,
84		csrf_token: Some(csrf_token),
85	})
86}
87
88#[cfg(all(test, server))]
89mod tests {
90	use super::*;
91	use crate::types::ModelInfo;
92	use std::sync::Arc;
93
94	#[tokio::test]
95	async fn test_dashboard_response_structure() {
96		// Create a mock AdminSite
97		let site = Arc::new(AdminSite::new("Test Admin"));
98
99		// Note: This test verifies the response structure without actually calling
100		// the Server Function, since that would require full DI context setup
101		let expected_site_name = site.name().to_string();
102		let expected_url_prefix = site.url_prefix().to_string();
103
104		// Verify site configuration
105		assert_eq!(expected_site_name, "Test Admin");
106		assert_eq!(expected_url_prefix, "/admin");
107	}
108
109	#[test]
110	fn test_model_info_construction() {
111		let model_name = "User".to_string();
112		let list_url = format!("/admin/{}/", model_name.to_lowercase());
113
114		let model_info = ModelInfo {
115			name: model_name.clone(),
116			list_url: list_url.clone(),
117		};
118
119		assert_eq!(model_info.name, "User");
120		assert_eq!(model_info.list_url, "/admin/user/");
121	}
122}