use super::super::DbPool;
use crate::error::StorageError;
pub async fn update_tweet_archetype(
pool: &DbPool,
tweet_id: &str,
archetype_vibe: &str,
) -> Result<(), StorageError> {
sqlx::query("UPDATE tweet_performance SET archetype_vibe = ? WHERE tweet_id = ?")
.bind(archetype_vibe)
.bind(tweet_id)
.execute(pool)
.await
.map_err(|e| StorageError::Query { source: e })?;
Ok(())
}
pub async fn update_reply_archetype(
pool: &DbPool,
reply_id: &str,
archetype_vibe: &str,
) -> Result<(), StorageError> {
sqlx::query("UPDATE reply_performance SET archetype_vibe = ? WHERE reply_id = ?")
.bind(archetype_vibe)
.bind(reply_id)
.execute(pool)
.await
.map_err(|e| StorageError::Query { source: e })?;
Ok(())
}
pub async fn update_tweet_engagement_score(
pool: &DbPool,
tweet_id: &str,
score: f64,
) -> Result<(), StorageError> {
sqlx::query("UPDATE tweet_performance SET engagement_score = ? WHERE tweet_id = ?")
.bind(score)
.bind(tweet_id)
.execute(pool)
.await
.map_err(|e| StorageError::Query { source: e })?;
Ok(())
}
pub async fn update_reply_engagement_score(
pool: &DbPool,
reply_id: &str,
score: f64,
) -> Result<(), StorageError> {
sqlx::query("UPDATE reply_performance SET engagement_score = ? WHERE reply_id = ?")
.bind(score)
.bind(reply_id)
.execute(pool)
.await
.map_err(|e| StorageError::Query { source: e })?;
Ok(())
}
pub async fn get_max_performance_score(pool: &DbPool) -> Result<f64, StorageError> {
let row: (f64,) = sqlx::query_as(
"SELECT COALESCE(MAX(max_score), 0.0) FROM (\
SELECT MAX(performance_score) as max_score FROM tweet_performance \
UNION ALL \
SELECT MAX(performance_score) as max_score FROM reply_performance\
)",
)
.fetch_one(pool)
.await
.map_err(|e| StorageError::Query { source: e })?;
Ok(row.0)
}
#[derive(Debug, Clone)]
pub struct AncestorRow {
pub content_type: String,
pub id: String,
pub content_preview: String,
pub archetype_vibe: Option<String>,
pub engagement_score: Option<f64>,
pub performance_score: f64,
pub posted_at: String,
}
type AncestorQueryRow = (
String,
String,
String,
Option<String>,
Option<f64>,
f64,
String,
);
fn ancestor_row_from_tuple(r: AncestorQueryRow) -> AncestorRow {
AncestorRow {
content_type: r.0,
id: r.1,
content_preview: r.2,
archetype_vibe: r.3,
engagement_score: r.4,
performance_score: r.5,
posted_at: r.6,
}
}
pub async fn get_scored_ancestors(
pool: &DbPool,
account_id: &str,
topic_keywords: &[String],
min_score: f64,
limit: u32,
) -> Result<Vec<AncestorRow>, StorageError> {
if topic_keywords.is_empty() {
let rows: Vec<AncestorQueryRow> = sqlx::query_as(
"SELECT 'tweet' as content_type, tp.tweet_id, \
SUBSTR(ot.content, 1, 120), \
tp.archetype_vibe, tp.engagement_score, tp.performance_score, \
ot.created_at \
FROM tweet_performance tp \
JOIN original_tweets ot ON ot.tweet_id = tp.tweet_id \
WHERE ot.account_id = ? \
AND tp.engagement_score IS NOT NULL \
AND tp.engagement_score >= ? \
UNION ALL \
SELECT 'reply', rp.reply_id, SUBSTR(rs.reply_content, 1, 120), \
rp.archetype_vibe, rp.engagement_score, rp.performance_score, \
rs.created_at \
FROM reply_performance rp \
JOIN replies_sent rs ON rs.reply_tweet_id = rp.reply_id \
WHERE rs.account_id = ? \
AND rp.engagement_score IS NOT NULL \
AND rp.engagement_score >= ? \
ORDER BY engagement_score DESC \
LIMIT ?",
)
.bind(account_id)
.bind(min_score)
.bind(account_id)
.bind(min_score)
.bind(limit)
.fetch_all(pool)
.await
.map_err(|e| StorageError::Query { source: e })?;
return Ok(rows.into_iter().map(ancestor_row_from_tuple).collect());
}
let topic_placeholders: String = (0..topic_keywords.len())
.map(|_| "?".to_string())
.collect::<Vec<_>>()
.join(", ");
let like_conditions: Vec<String> = (0..topic_keywords.len())
.map(|_| "rs.reply_content LIKE '%' || ? || '%'".to_string())
.collect();
let like_clause = like_conditions.join(" OR ");
let query_str = format!(
"SELECT 'tweet' as content_type, tp.tweet_id, \
SUBSTR(ot.content, 1, 120), \
tp.archetype_vibe, tp.engagement_score, tp.performance_score, \
ot.created_at \
FROM tweet_performance tp \
JOIN original_tweets ot ON ot.tweet_id = tp.tweet_id \
WHERE ot.account_id = ? \
AND tp.engagement_score IS NOT NULL \
AND tp.engagement_score >= ? \
AND (ot.topic IN ({topic_placeholders})) \
UNION ALL \
SELECT 'reply', rp.reply_id, SUBSTR(rs.reply_content, 1, 120), \
rp.archetype_vibe, rp.engagement_score, rp.performance_score, \
rs.created_at \
FROM reply_performance rp \
JOIN replies_sent rs ON rs.reply_tweet_id = rp.reply_id \
WHERE rs.account_id = ? \
AND rp.engagement_score IS NOT NULL \
AND rp.engagement_score >= ? \
AND ({like_clause}) \
ORDER BY engagement_score DESC \
LIMIT ?"
);
let mut query = sqlx::query_as::<_, AncestorQueryRow>(&query_str);
query = query.bind(account_id);
query = query.bind(min_score);
for kw in topic_keywords {
query = query.bind(kw);
}
query = query.bind(account_id);
query = query.bind(min_score);
for kw in topic_keywords {
query = query.bind(kw);
}
query = query.bind(limit);
let rows = query
.fetch_all(pool)
.await
.map_err(|e| StorageError::Query { source: e })?;
Ok(rows.into_iter().map(ancestor_row_from_tuple).collect())
}