systemprompt_generator/prerender/
engine.rs1use 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}