systemprompt_content/
homepage_prerenderer.rs1use std::path::PathBuf;
2use std::sync::Arc;
3
4use anyhow::Result;
5use async_trait::async_trait;
6use systemprompt_cloud::constants::storage;
7use systemprompt_models::ContentConfigRaw;
8use systemprompt_models::services::ServicesConfig;
9use systemprompt_provider_contracts::{
10 PagePrepareContext, PagePrerenderer, PageRenderSpec, WebConfig,
11};
12
13fn resolve_content_raw<'a>(ctx: &'a PagePrepareContext<'_>) -> Result<&'a ContentConfigRaw> {
14 if let Some(services) = ctx.content_config::<ServicesConfig>() {
15 return Ok(&services.content.raw);
16 }
17 ctx.content_config::<ContentConfigRaw>()
18 .ok_or_else(|| anyhow::anyhow!("ContentConfig not available in context"))
19}
20
21const PAGE_TYPE: &str = "homepage";
22const TEMPLATE_NAME: &str = "homepage";
23const OUTPUT_FILE: &str = "index.html";
24
25#[derive(Debug, Clone, Copy, Default)]
26pub struct DefaultHomepagePrerenderer;
27
28impl DefaultHomepagePrerenderer {
29 #[must_use]
30 pub const fn new() -> Self {
31 Self
32 }
33
34 fn extract_branding(
35 web_config: &WebConfig,
36 content_config: &ContentConfigRaw,
37 ) -> HomepageBranding {
38 let org = &content_config.metadata.structured_data.organization;
39 let branding = &web_config.branding;
40
41 HomepageBranding {
42 org_name: org.name.clone(),
43 org_url: org.url.clone(),
44 org_logo: org.logo.clone(),
45 logo_path: branding
46 .logo
47 .primary
48 .svg
49 .clone()
50 .unwrap_or_else(String::new),
51 favicon_path: branding.favicon.clone(),
52 twitter_handle: branding.twitter_handle.clone(),
53 display_sitename: branding.display_sitename,
54 }
55 }
56}
57
58struct HomepageBranding {
59 org_name: String,
60 org_url: String,
61 org_logo: String,
62 logo_path: String,
63 favicon_path: String,
64 twitter_handle: String,
65 display_sitename: bool,
66}
67
68#[async_trait]
69impl PagePrerenderer for DefaultHomepagePrerenderer {
70 fn page_type(&self) -> &str {
71 PAGE_TYPE
72 }
73
74 fn priority(&self) -> u32 {
75 100
76 }
77
78 async fn prepare(&self, ctx: &PagePrepareContext<'_>) -> Result<Option<PageRenderSpec>> {
79 let content_config = resolve_content_raw(ctx)?;
80
81 let branding = Self::extract_branding(ctx.web_config, content_config);
82
83 let base_data = serde_json::json!({
84 "site": ctx.web_config,
85 "ORG_NAME": branding.org_name,
86 "ORG_URL": branding.org_url,
87 "ORG_LOGO": branding.org_logo,
88 "LOGO_PATH": branding.logo_path,
89 "FAVICON_PATH": branding.favicon_path,
90 "TWITTER_HANDLE": branding.twitter_handle,
91 "DISPLAY_SITENAME": branding.display_sitename,
92 "HEADER_CTA_URL": "/",
93 "JS_BASE_PATH": format!("/{}", storage::JS),
94 "CSS_BASE_PATH": format!("/{}", storage::CSS)
95 });
96
97 Ok(Some(PageRenderSpec::new(
98 TEMPLATE_NAME,
99 base_data,
100 PathBuf::from(OUTPUT_FILE),
101 )))
102 }
103}
104
105pub fn default_homepage_prerenderer() -> Arc<dyn PagePrerenderer> {
106 Arc::new(DefaultHomepagePrerenderer::new())
107}