use reqwest::Client; use sqlx::PgPool; use tracing::{info, warn}; /// Oppvarming: les siste N meldinger per aktive kanal fra PG og last inn i SpacetimeDB. pub async fn run( pool: &PgPool, http: &Client, spacetimedb_url: &str, module: &str, limit: i64, ) -> anyhow::Result<()> { info!(limit, "Starter oppvarming (PG → SpacetimeDB)"); // Finn aktive kanaler (kanaler med meldinger) let channels: Vec<(String,)> = sqlx::query_as( "SELECT DISTINCT channel_id::text FROM messages WHERE channel_id IS NOT NULL" ) .fetch_all(pool) .await?; if channels.is_empty() { info!("Ingen aktive kanaler funnet — oppvarming fullført"); return Ok(()); } info!(channels = channels.len(), "Aktive kanaler funnet"); let mut total_messages = 0u64; let mut total_reactions = 0u64; for (channel_id,) in &channels { // Rydd kanalen i SpacetimeDB først for å unngå duplikater if let Err(e) = call_reducer(http, spacetimedb_url, module, "clear_channel", &serde_json::json!({ "channel_id": channel_id })).await { warn!(channel_id, error = %e, "Kunne ikke rydde kanal — hopper over"); continue; } // Hent meldinger med forfatterinfo let rows: Vec<(String, String, String, String, String, String, String, Option, String)> = sqlx::query_as( r#" SELECT m.id::text, m.channel_id::text, n.workspace_id::text, COALESCE(m.author_id, ''), COALESCE(u.name, 'Ukjent'), COALESCE(m.body, ''), COALESCE(m.message_type, 'text'), m.reply_to::text, m.created_at::text FROM messages m JOIN nodes n ON n.id = m.id LEFT JOIN users u ON u.authentik_id = m.author_id WHERE m.channel_id = $1::uuid ORDER BY m.created_at DESC LIMIT $2 "# ) .bind(channel_id) .bind(limit) .fetch_all(pool) .await?; if rows.is_empty() { continue; } // Bygg JSON-array let messages: Vec = rows.iter().map(|r| { serde_json::json!({ "id": r.0, "channel_id": r.1, "workspace_id": r.2, "author_id": r.3, "author_name": r.4, "body": r.5, "message_type": r.6, "reply_to": r.7.as_deref().unwrap_or(""), "created_at": r.8 }) }).collect(); let count = messages.len(); let json_str = serde_json::to_string(&messages)?; if let Err(e) = call_reducer(http, spacetimedb_url, module, "load_messages", &serde_json::json!({ "messages_json": json_str })).await { warn!(channel_id, error = %e, "Feil ved lasting av meldinger"); continue; } total_messages += count as u64; // Hent reaksjoner for denne kanalens meldinger let reaction_rows: Vec<(String, String, String, String)> = sqlx::query_as( r#" SELECT mr.message_id::text, COALESCE(mr.user_id, ''), COALESCE(u.name, 'Ukjent'), mr.reaction FROM message_reactions mr JOIN messages m ON m.id = mr.message_id LEFT JOIN users u ON u.authentik_id = mr.user_id WHERE m.channel_id = $1::uuid "# ) .bind(channel_id) .fetch_all(pool) .await?; if !reaction_rows.is_empty() { let reactions: Vec = reaction_rows.iter().map(|r| { serde_json::json!({ "message_id": r.0, "user_id": r.1, "user_name": r.2, "reaction": r.3 }) }).collect(); let reactions_json = serde_json::to_string(&reactions)?; if let Err(e) = call_reducer(http, spacetimedb_url, module, "load_reactions", &serde_json::json!({ "reactions_json": reactions_json })).await { warn!(channel_id, error = %e, "Feil ved lasting av reaksjoner"); } else { total_reactions += reaction_rows.len() as u64; } } info!(channel_id, messages = count, reactions = reaction_rows.len(), "Kanal oppvarmet"); } info!( channels = channels.len(), messages = total_messages, reactions = total_reactions, "Oppvarming fullført" ); Ok(()) } async fn call_reducer( http: &Client, base_url: &str, module: &str, reducer: &str, args: &serde_json::Value, ) -> anyhow::Result<()> { let url = format!("{}/v1/database/{}/call/{}", base_url, module, reducer); let resp = http .post(&url) .json(args) .send() .await?; if !resp.status().is_success() { let status = resp.status(); let body = resp.text().await.unwrap_or_default(); anyhow::bail!("{} feilet ({}): {}", reducer, status, body); } Ok(()) }