Skip to main content

systemprompt_generator/prerender/
engine.rs

1use std::collections::HashSet;
2use std::path::PathBuf;
3
4use anyhow::Result;
5use systemprompt_database::DbPool;
6use systemprompt_template_provider::{ComponentContext, PageContext, PagePrepareContext};
7use tokio::fs;
8
9use crate::error::PublishError;
10use crate::prerender::content::process_all_sources;
11use crate::prerender::context::{load_prerender_context, PrerenderContext};
12use crate::prerender::utils::{merge_json_data, render_components};
13
14pub async fn prerender_content(db_pool: DbPool) -> Result<()> {
15    let ctx = load_prerender_context(db_pool).await?;
16    let total_rendered = process_all_sources(&ctx).await?;
17    tracing::info!(items_rendered = total_rendered, "Prerendering completed");
18    Ok(())
19}
20
21#[derive(Debug)]
22pub struct PagePrerenderResult {
23    pub page_type: String,
24    pub output_path: PathBuf,
25}
26
27pub async fn prerender_pages(db_pool: DbPool) -> Result<Vec<PagePrerenderResult>> {
28    let ctx = load_prerender_context(db_pool).await?;
29    prerender_pages_with_context(&ctx).await
30}
31
32pub async fn prerender_pages_with_context(
33    ctx: &PrerenderContext,
34) -> Result<Vec<PagePrerenderResult>> {
35    let prerenderers = ctx.template_registry.page_prerenderers();
36
37    if prerenderers.is_empty() {
38        tracing::warn!("No page prerenderers registered - no pages will be rendered");
39        return Ok(Vec::new());
40    }
41
42    let prerenderer_count = prerenderers.len();
43    let page_types: Vec<_> = prerenderers.iter().map(|p| p.page_type()).collect();
44    tracing::info!(
45        count = prerenderer_count,
46        page_types = ?page_types,
47        "Discovered page prerenderers"
48    );
49
50    let prepare_ctx =
51        PagePrepareContext::new(&ctx.web_config, &ctx.config, &ctx.db_pool, &ctx.dist_dir);
52
53    let mut results = Vec::new();
54    let mut rendered_page_types: HashSet<String> = HashSet::new();
55
56    for prerenderer in prerenderers {
57        let page_type = prerenderer.page_type();
58
59        if rendered_page_types.contains(page_type) {
60            tracing::debug!(
61                page_type = %page_type,
62                priority = prerenderer.priority(),
63                "Skipping prerenderer, page type already rendered by higher-priority prerenderer"
64            );
65            continue;
66        }
67
68        let render_spec = prerenderer
69            .prepare(&prepare_ctx)
70            .await
71            .map_err(|e| PublishError::page_prerenderer_failed(page_type, e.to_string()))?;
72
73        let Some(spec) = render_spec else {
74            tracing::debug!(page_type = %page_type, "Prerenderer returned None, skipping");
75            continue;
76        };
77
78        if !ctx.template_registry.has_template(&spec.template_name) {
79            tracing::warn!(
80                page_type = %page_type,
81                template = %spec.template_name,
82                "Template not found, skipping page"
83            );
84            continue;
85        }
86
87        let mut page_data = spec.base_data;
88
89        let page_ctx = PageContext::new(page_type, &ctx.web_config, &ctx.config, &ctx.db_pool);
90        let providers = ctx.template_registry.page_providers_for(page_type);
91        let provider_ids: Vec<_> = providers.iter().map(|p| p.provider_id()).collect();
92
93        tracing::debug!(
94            page_type = %page_type,
95            provider_count = providers.len(),
96            provider_ids = ?provider_ids,
97            "Collecting page data from providers"
98        );
99
100        for provider in &providers {
101            let data = provider.provide_page_data(&page_ctx).await.map_err(|e| {
102                PublishError::provider_failed(provider.provider_id(), e.to_string())
103            })?;
104            merge_json_data(&mut page_data, &data);
105        }
106
107        let component_ctx = ComponentContext::for_page(&ctx.web_config);
108        render_components(
109            &ctx.template_registry,
110            page_type,
111            &component_ctx,
112            &mut page_data,
113        )
114        .await;
115
116        let html = ctx
117            .template_registry
118            .render(&spec.template_name, &page_data)
119            .map_err(|e| PublishError::render_failed(&spec.template_name, None, e.to_string()))?;
120
121        let output_path = ctx.dist_dir.join(&spec.output_path);
122
123        if let Some(parent) = output_path.parent() {
124            fs::create_dir_all(parent).await?;
125        }
126
127        fs::write(&output_path, html).await?;
128
129        tracing::info!(
130            page_type = %page_type,
131            path = %output_path.display(),
132            "Generated page"
133        );
134
135        rendered_page_types.insert(page_type.to_string());
136
137        results.push(PagePrerenderResult {
138            page_type: page_type.to_string(),
139            output_path,
140        });
141    }
142
143    Ok(results)
144}