Bulk re-rendering ved temaendring (oppgave 14.14): paginert batch-jobb via jobbkø
Når theme eller theme_config endres på en samling, trigges paginert bulk re-rendering av alle artikler (100 om gangen). Artikler serveres med gammelt tema til de er re-rendret — renderer_version identifiserer hvilke som gjenstår. Duplikatsjekk mot eksisterende pending/running jobber. - publishing.rs: trigger_bulk_rerender() med paginert SQL-query - intentions.rs: theme/theme_config endring detekteres i update_node - RENDERER_VERSION bumped til 2 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8155783fc4
commit
cf38721459
3 changed files with 153 additions and 6 deletions
|
|
@ -844,15 +844,25 @@ pub async fn update_node(
|
||||||
let title = req.title.unwrap_or(existing.title.unwrap_or_default());
|
let title = req.title.unwrap_or(existing.title.unwrap_or_default());
|
||||||
let content = req.content.unwrap_or(existing.content.unwrap_or_default());
|
let content = req.content.unwrap_or(existing.content.unwrap_or_default());
|
||||||
|
|
||||||
// Hent gammelt custom_domain før existing.metadata flyttes
|
// Hent gamle publishing-verdier før existing.metadata flyttes
|
||||||
let old_domain = existing.metadata
|
let old_publishing = existing.metadata
|
||||||
.get("traits")
|
.get("traits")
|
||||||
.and_then(|t| t.get("publishing"))
|
.and_then(|t| t.get("publishing"));
|
||||||
|
|
||||||
|
let old_domain = old_publishing
|
||||||
.and_then(|p| p.get("custom_domain"))
|
.and_then(|p| p.get("custom_domain"))
|
||||||
.and_then(|d| d.as_str())
|
.and_then(|d| d.as_str())
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
|
let old_theme = old_publishing
|
||||||
|
.and_then(|p| p.get("theme"))
|
||||||
|
.cloned();
|
||||||
|
|
||||||
|
let old_theme_config = old_publishing
|
||||||
|
.and_then(|p| p.get("theme_config"))
|
||||||
|
.cloned();
|
||||||
|
|
||||||
let metadata = req.metadata.unwrap_or(existing.metadata);
|
let metadata = req.metadata.unwrap_or(existing.metadata);
|
||||||
|
|
||||||
// -- Valider traits for samlingsnoder (oppgave 13.1) --
|
// -- Valider traits for samlingsnoder (oppgave 13.1) --
|
||||||
|
|
@ -867,6 +877,20 @@ pub async fn update_node(
|
||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
let domain_changed = old_domain != new_domain && node_kind == "collection";
|
let domain_changed = old_domain != new_domain && node_kind == "collection";
|
||||||
|
|
||||||
|
// -- Sjekk om theme eller theme_config er endret (for bulk re-rendering, oppgave 14.14) --
|
||||||
|
let theme_changed = if node_kind == "collection" {
|
||||||
|
let new_publishing = metadata
|
||||||
|
.get("traits")
|
||||||
|
.and_then(|t| t.get("publishing"));
|
||||||
|
|
||||||
|
let new_theme = new_publishing.and_then(|p| p.get("theme")).cloned();
|
||||||
|
let new_theme_config = new_publishing.and_then(|p| p.get("theme_config")).cloned();
|
||||||
|
|
||||||
|
old_theme != new_theme || old_theme_config != new_theme_config
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
let metadata_str = metadata.to_string();
|
let metadata_str = metadata.to_string();
|
||||||
|
|
||||||
let node_id_str = req.node_id.to_string();
|
let node_id_str = req.node_id.to_string();
|
||||||
|
|
@ -922,6 +946,26 @@ pub async fn update_node(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -- Bulk re-rendering hvis theme/theme_config endret (oppgave 14.14) --
|
||||||
|
if theme_changed {
|
||||||
|
let db = state.db.clone();
|
||||||
|
let collection_id = req.node_id;
|
||||||
|
tokio::spawn(async move {
|
||||||
|
match crate::publishing::trigger_bulk_rerender(&db, collection_id).await {
|
||||||
|
Ok(count) => tracing::info!(
|
||||||
|
collection_id = %collection_id,
|
||||||
|
articles = count,
|
||||||
|
"Bulk re-rendering trigget etter temaendring"
|
||||||
|
),
|
||||||
|
Err(e) => tracing::error!(
|
||||||
|
collection_id = %collection_id,
|
||||||
|
error = %e,
|
||||||
|
"Feil ved bulk re-rendering etter temaendring"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Json(UpdateNodeResponse { node_id: req.node_id }))
|
Ok(Json(UpdateNodeResponse { node_id: req.node_id }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ use crate::AppState;
|
||||||
|
|
||||||
/// Renderer-versjon. Økes ved mal-/template-endringer.
|
/// Renderer-versjon. Økes ved mal-/template-endringer.
|
||||||
/// Brukes for å identifisere artikler som trenger re-rendering (oppgave 14.14).
|
/// Brukes for å identifisere artikler som trenger re-rendering (oppgave 14.14).
|
||||||
pub const RENDERER_VERSION: i64 = 1;
|
pub const RENDERER_VERSION: i64 = 2;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Tema-konfigurasjon fra publishing-trait
|
// Tema-konfigurasjon fra publishing-trait
|
||||||
|
|
@ -1411,6 +1411,110 @@ pub fn start_publish_scheduler(db: PgPool) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Bulk re-rendering ved temaendring (oppgave 14.14)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Paginert batch-jobb: finn artikler som trenger re-rendering og enqueue
|
||||||
|
/// render_article-jobber i grupper på 100. Artikler serveres med gammelt
|
||||||
|
/// tema til de er re-rendret — renderer_version i metadata identifiserer
|
||||||
|
/// hvilke som gjenstår.
|
||||||
|
///
|
||||||
|
/// Kalles når theme eller theme_config endres på en samling.
|
||||||
|
pub async fn trigger_bulk_rerender(
|
||||||
|
db: &PgPool,
|
||||||
|
collection_id: Uuid,
|
||||||
|
) -> Result<usize, String> {
|
||||||
|
let batch_size: i64 = 100;
|
||||||
|
let mut total_enqueued: usize = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Finn neste batch artikler som trenger re-rendering.
|
||||||
|
// Filtrerer ut artikler som allerede har pending/running render-jobb.
|
||||||
|
let article_ids: Vec<(Uuid,)> = sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT e.source_id
|
||||||
|
FROM edges e
|
||||||
|
JOIN nodes n ON n.id = e.source_id
|
||||||
|
WHERE e.target_id = $1
|
||||||
|
AND e.edge_type = 'belongs_to'
|
||||||
|
AND (
|
||||||
|
n.metadata->'rendered'->>'renderer_version' IS NULL
|
||||||
|
OR (n.metadata->'rendered'->>'renderer_version')::bigint < $2
|
||||||
|
)
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM job_queue jq
|
||||||
|
WHERE jq.job_type = 'render_article'
|
||||||
|
AND jq.status IN ('pending', 'running', 'retry')
|
||||||
|
AND jq.payload->>'node_id' = n.id::text
|
||||||
|
)
|
||||||
|
LIMIT $3
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(collection_id)
|
||||||
|
.bind(RENDERER_VERSION)
|
||||||
|
.bind(batch_size)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Feil ved henting av artikler for bulk rerender: {e}"))?;
|
||||||
|
|
||||||
|
if article_ids.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let batch_count = article_ids.len();
|
||||||
|
|
||||||
|
for (article_id,) in &article_ids {
|
||||||
|
let payload = serde_json::json!({
|
||||||
|
"node_id": article_id.to_string(),
|
||||||
|
"collection_id": collection_id.to_string(),
|
||||||
|
});
|
||||||
|
if let Err(e) = jobs::enqueue(db, "render_article", payload, Some(collection_id), 3).await {
|
||||||
|
tracing::error!(
|
||||||
|
article_id = %article_id,
|
||||||
|
collection_id = %collection_id,
|
||||||
|
error = %e,
|
||||||
|
"Kunne ikke enqueue render_article ved temaendring"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
total_enqueued += batch_count;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
collection_id = %collection_id,
|
||||||
|
batch = batch_count,
|
||||||
|
total = total_enqueued,
|
||||||
|
"Bulk rerender batch enqueued"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Hvis batchen var mindre enn batch_size, er vi ferdige
|
||||||
|
if (batch_count as i64) < batch_size {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueue render av forsiden til slutt (lavere prioritet)
|
||||||
|
let index_payload = serde_json::json!({
|
||||||
|
"collection_id": collection_id.to_string(),
|
||||||
|
});
|
||||||
|
if let Err(e) = jobs::enqueue(db, "render_index", index_payload, Some(collection_id), 4).await {
|
||||||
|
tracing::error!(
|
||||||
|
collection_id = %collection_id,
|
||||||
|
error = %e,
|
||||||
|
"Kunne ikke enqueue render_index ved temaendring"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
collection_id = %collection_id,
|
||||||
|
total_articles = total_enqueued,
|
||||||
|
"Bulk re-rendering enqueued ved temaendring"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(total_enqueued)
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Tester
|
// Tester
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
|
||||||
3
tasks.md
3
tasks.md
|
|
@ -156,8 +156,7 @@ Uavhengige faser kan fortsatt plukkes.
|
||||||
- [x] 14.11 Redaktørens arbeidsflate: frontend-visning av noder med `submitted_to`-edge til samling, gruppert på status. Kanban-stil drag-and-drop for statusendring. Siste kolonne ("Planlagt") setter `publish_at` i edge-metadata.
|
- [x] 14.11 Redaktørens arbeidsflate: frontend-visning av noder med `submitted_to`-edge til samling, gruppert på status. Kanban-stil drag-and-drop for statusendring. Siste kolonne ("Planlagt") setter `publish_at` i edge-metadata.
|
||||||
- [x] 14.12 Planlagt publisering: maskinrommet sjekker periodisk (cron/intervall) for `belongs_to`-edges med `publish_at` i fortiden som ikke er rendret. Ved treff: render HTML → CAS → oppdater RSS.
|
- [x] 14.12 Planlagt publisering: maskinrommet sjekker periodisk (cron/intervall) for `belongs_to`-edges med `publish_at` i fortiden som ikke er rendret. Ved treff: render HTML → CAS → oppdater RSS.
|
||||||
- [x] 14.13 Redaksjonell samtale: ved innsending kan redaktør opprette kommunikasjonsnode knyttet til artikkel + forfatter for diskusjon/feedback utover kort notat i edge-metadata.
|
- [x] 14.13 Redaksjonell samtale: ved innsending kan redaktør opprette kommunikasjonsnode knyttet til artikkel + forfatter for diskusjon/feedback utover kort notat i edge-metadata.
|
||||||
- [~] 14.14 Bulk re-rendering: batch-jobb via jobbkø ved temaendring. Paginert (100 artikler om gangen), oppdaterer `renderer_version`. Artikler serveres med gammelt tema til re-rendret.
|
- [x] 14.14 Bulk re-rendering: batch-jobb via jobbkø ved temaendring. Paginert (100 artikler om gangen), oppdaterer `renderer_version`. Artikler serveres med gammelt tema til re-rendret.
|
||||||
> Påbegynt: 2026-03-18T02:23
|
|
||||||
- [ ] 14.15 Dynamiske sider: kategori-sider (filtrert på tag-edges), arkiv (kronologisk med månedsgruppering), søk (PG fulltekst). Alle paginerte, cachet i maskinrommet. Om-side som statisk CAS-node.
|
- [ ] 14.15 Dynamiske sider: kategori-sider (filtrert på tag-edges), arkiv (kronologisk med månedsgruppering), søk (PG fulltekst). Alle paginerte, cachet i maskinrommet. Om-side som statisk CAS-node.
|
||||||
- [ ] 14.16 Presentasjonselementer som noder: publisert tittel, ingress, OG-bilde, undertittel er egne noder med `title`/`summary`/`og_image`-edges til artikkelen. Frontend for å opprette/redigere varianter. Ref: `docs/concepts/publisering.md` § "Presentasjonselementer".
|
- [ ] 14.16 Presentasjonselementer som noder: publisert tittel, ingress, OG-bilde, undertittel er egne noder med `title`/`summary`/`og_image`-edges til artikkelen. Frontend for å opprette/redigere varianter. Ref: `docs/concepts/publisering.md` § "Presentasjonselementer".
|
||||||
- [ ] 14.17 A/B-testing: maskinrommet roterer varianter ved forside-rendering, logger impressions/klikk per variant, normaliserer CTR mot tidspunkt-baseline. Etter statistisk signifikans markeres vinner. Redaktør kan overstyre. Edge-metadata: `ab_status`, `impressions`, `clicks`, `ctr`.
|
- [ ] 14.17 A/B-testing: maskinrommet roterer varianter ved forside-rendering, logger impressions/klikk per variant, normaliserer CTR mot tidspunkt-baseline. Etter statistisk signifikans markeres vinner. Redaktør kan overstyre. Edge-metadata: `ab_status`, `impressions`, `clicks`, `ctr`.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue