Skip to main content

systemprompt_generator/prerender/
list.rs

1use std::path::Path;
2
3use anyhow::{Context, Result};
4use systemprompt_database::DbPool;
5use systemprompt_models::{ContentConfigRaw, FullWebConfig, ParentRoute};
6use systemprompt_template_provider::{ComponentContext, PageContext};
7use systemprompt_templates::TemplateRegistry;
8use tokio::fs;
9
10use crate::prerender::utils::{merge_json_data, render_components};
11
12pub struct RenderListParams<'a> {
13    pub items: &'a [serde_json::Value],
14    pub config: &'a ContentConfigRaw,
15    pub web_config: &'a FullWebConfig,
16    pub list_config: &'a ParentRoute,
17    pub source_name: &'a str,
18    pub template_registry: &'a TemplateRegistry,
19    pub dist_dir: &'a Path,
20    pub index_content: Option<&'a serde_json::Value>,
21    pub db_pool: &'a DbPool,
22}
23
24impl std::fmt::Debug for RenderListParams<'_> {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        f.debug_struct("RenderListParams")
27            .field("source_name", &self.source_name)
28            .field("items_count", &self.items.len())
29            .field("has_index_content", &self.index_content.is_some())
30            .finish_non_exhaustive()
31    }
32}
33
34pub async fn render_list_route(params: RenderListParams<'_>) -> Result<()> {
35    let RenderListParams {
36        items,
37        config,
38        web_config,
39        list_config,
40        source_name,
41        template_registry,
42        dist_dir,
43        index_content,
44        db_pool,
45    } = params;
46
47    let list_content_type = format!("{source_name}-list");
48
49    let template_name = template_registry
50        .find_template_for_content_type(&list_content_type)
51        .ok_or_else(|| anyhow::anyhow!("No template for: {list_content_type}"))?;
52
53    let mut list_data = serde_json::json!({
54        "HAS_INDEX_CONTENT": index_content.is_some(),
55    });
56
57    let mut page_ctx =
58        PageContext::new(&list_content_type, web_config, config, db_pool).with_all_items(items);
59
60    if let Some(item) = index_content {
61        page_ctx = page_ctx.with_content_item(item);
62    }
63
64    for provider in template_registry.page_providers_for(&list_content_type) {
65        let data = provider
66            .provide_page_data(&page_ctx)
67            .await
68            .map_err(|e| anyhow::anyhow!("Provider {} failed: {e}", provider.provider_id()))?;
69        merge_json_data(&mut list_data, &data);
70    }
71
72    let component_ctx = ComponentContext::for_list(web_config, items);
73    render_components(
74        template_registry,
75        &list_content_type,
76        &component_ctx,
77        &mut list_data,
78    )
79    .await;
80
81    let list_html = template_registry
82        .render(template_name, &list_data)
83        .context("Failed to render list route")?;
84
85    let list_dir = dist_dir.join(list_config.url.trim_start_matches('/'));
86    fs::create_dir_all(&list_dir).await?;
87    fs::write(list_dir.join("index.html"), &list_html).await?;
88
89    tracing::debug!(path = %list_config.url, "Generated list route");
90    Ok(())
91}