Warmup: trådbasert henting — hele tråder lastes komplett

messages-modus: hent de N nyeste trådene (sortert etter siste
aktivitet), inkludert alle svar. Ingen orphan-replies.

days-modus: finn alle tråder med minst én melding innenfor
tidsvinduet, last hele tråden (også eldre trådstartere).

all-modus: uendret, henter alt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
vegard 2026-03-16 02:33:55 +01:00
parent a6ca0f602d
commit 2ed50d51a9

View file

@ -72,49 +72,25 @@ pub async fn run(
continue; continue;
} }
// Bygg WHERE-clause basert på warmup-modus // Trådbasert henting: finn kvalifiserende tråder, hent alle meldinger i disse
let (where_clause, limit) = match ch.config.warmup_mode.as_str() { let rows = match ch.config.warmup_mode.as_str() {
"all" => {
// Alt — ingen filtrering
fetch_messages_all(pool, &ch.id).await?
},
"messages" => { "messages" => {
// Siste N tråder (sortert etter nyeste melding i tråden)
let n = ch.config.warmup_value.unwrap_or(default_limit); let n = ch.config.warmup_value.unwrap_or(default_limit);
(String::new(), n) fetch_messages_by_threads(pool, &ch.id, n).await?
}, },
"days" => { "days" => {
// Alle tråder med minst én melding i tidsvinduet
let days = ch.config.warmup_value.unwrap_or(30); let days = ch.config.warmup_value.unwrap_or(30);
(format!("AND m.created_at >= now() - interval '{} days'", days), i64::MAX) fetch_messages_by_days(pool, &ch.id, days).await?
}, },
// "all" og alt annet _ => fetch_messages_all(pool, &ch.id).await?,
_ => (String::new(), i64::MAX),
}; };
let query = format!(
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
"#,
where_clause
);
let rows: Vec<(String, String, String, String, String, String, String, Option<String>, String)> =
sqlx::query_as(&query)
.bind(&ch.id)
.bind(limit)
.fetch_all(pool)
.await?;
if rows.is_empty() { if rows.is_empty() {
info!(channel = %ch.name, mode = %ch.config.warmup_mode, "Ingen meldinger å laste"); info!(channel = %ch.name, mode = %ch.config.warmup_mode, "Ingen meldinger å laste");
continue; continue;
@ -205,6 +181,107 @@ pub async fn run(
Ok(()) Ok(())
} }
type MessageRow = (String, String, String, String, String, String, String, Option<String>, String);
const MESSAGE_COLUMNS: &str = r#"
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
"#;
const MESSAGE_JOINS: &str = r#"
JOIN nodes n ON n.id = m.id
LEFT JOIN users u ON u.authentik_id = m.author_id
"#;
/// Hent alle meldinger i en kanal.
async fn fetch_messages_all(pool: &PgPool, channel_id: &str) -> anyhow::Result<Vec<MessageRow>> {
let query = format!(
"SELECT {} FROM messages m {} WHERE m.channel_id = $1::uuid ORDER BY m.created_at",
MESSAGE_COLUMNS, MESSAGE_JOINS
);
Ok(sqlx::query_as(&query).bind(channel_id).fetch_all(pool).await?)
}
/// Hent de N nyeste trådene + alle meldinger i disse.
/// En "tråd" = en rotmelding (reply_to IS NULL) med alle svar.
/// Sortert etter nyeste melding i tråden.
async fn fetch_messages_by_threads(pool: &PgPool, channel_id: &str, limit: i64) -> anyhow::Result<Vec<MessageRow>> {
// Finn rot-IDer for de N nyeste trådene.
// En tråds "siste aktivitet" er max(created_at) blant rot + alle svar.
// Løse meldinger (reply_to peker på noe utenfor kanalen) teller som egen tråd.
let query = format!(
r#"
WITH thread_roots AS (
-- Finn rot for hver melding: følg reply_to opp til NULL, eller til utenfor kanalen
SELECT DISTINCT COALESCE(
(SELECT r.id FROM messages r
WHERE r.id = m.reply_to AND r.channel_id = m.channel_id AND r.reply_to IS NULL),
CASE WHEN m.reply_to IS NULL THEN m.id END,
m.id -- orphan-svar behandles som egen tråd
) AS root_id
FROM messages m
WHERE m.channel_id = $1::uuid
),
ranked_threads AS (
SELECT tr.root_id,
max(m.created_at) AS last_activity
FROM thread_roots tr
JOIN messages m ON m.channel_id = $1::uuid
AND (m.id = tr.root_id OR m.reply_to = tr.root_id)
GROUP BY tr.root_id
ORDER BY last_activity DESC
LIMIT $2
)
SELECT {}
FROM messages m
{}
WHERE m.channel_id = $1::uuid
AND (m.id IN (SELECT root_id FROM ranked_threads)
OR m.reply_to IN (SELECT root_id FROM ranked_threads))
ORDER BY m.created_at
"#,
MESSAGE_COLUMNS, MESSAGE_JOINS
);
Ok(sqlx::query_as(&query).bind(channel_id).bind(limit).fetch_all(pool).await?)
}
/// Hent alle tråder som har minst én melding innen siste N dager.
/// Inkluderer hele tråden (også eldre trådstartere).
async fn fetch_messages_by_days(pool: &PgPool, channel_id: &str, days: i64) -> anyhow::Result<Vec<MessageRow>> {
let query = format!(
r#"
WITH qualifying_roots AS (
-- Finn trådrøtter for meldinger innenfor tidsvinduet
SELECT DISTINCT COALESCE(
(SELECT r.id FROM messages r
WHERE r.id = m.reply_to AND r.channel_id = m.channel_id AND r.reply_to IS NULL),
CASE WHEN m.reply_to IS NULL THEN m.id END,
m.id
) AS root_id
FROM messages m
WHERE m.channel_id = $1::uuid
AND m.created_at >= now() - make_interval(days => $2)
)
SELECT {}
FROM messages m
{}
WHERE m.channel_id = $1::uuid
AND (m.id IN (SELECT root_id FROM qualifying_roots)
OR m.reply_to IN (SELECT root_id FROM qualifying_roots))
ORDER BY m.created_at
"#,
MESSAGE_COLUMNS, MESSAGE_JOINS
);
Ok(sqlx::query_as(&query).bind(channel_id).bind(days as i32).fetch_all(pool).await?)
}
async fn call_reducer( async fn call_reducer(
http: &Client, http: &Client,
base_url: &str, base_url: &str,