use sqlx::Row;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewRule {
pub title: String,
pub origin: String,
pub source_repo: Option<String>,
}
pub async fn new_rules_since(
db: &difflore_core::SqlitePool,
prev_ts_ms: Option<i64>,
repo_aliases: &[String],
limit: usize,
) -> Result<Vec<NewRule>, String> {
if repo_aliases.is_empty() {
return Ok(Vec::new());
}
let repos_json = serde_json::to_string(repo_aliases).map_err(|e| format!("aliases: {e}"))?;
let watermark_iso = prev_ts_ms
.and_then(chrono::DateTime::<chrono::Utc>::from_timestamp_millis)
.map_or_else(|| "1970-01-01T00:00:00Z".to_owned(), |dt| dt.to_rfc3339());
let limit_i64 = i64::try_from(limit).unwrap_or(5);
let rows = sqlx::query(
r"SELECT name, origin, source_repo
FROM skills
WHERE status = 'active'
AND source_repo IS NOT NULL
AND TRIM(source_repo) <> ''
AND LOWER(source_repo) IN (SELECT value FROM json_each(?1))
AND datetime(installed_at) > datetime(?2)
ORDER BY datetime(installed_at) DESC, id DESC
LIMIT ?3",
)
.bind(repos_json)
.bind(watermark_iso)
.bind(limit_i64)
.fetch_all(db)
.await
.map_err(|e| format!("query skills: {e}"))?;
let mut out = Vec::with_capacity(rows.len());
for row in rows {
let title: String = row.try_get("name").unwrap_or_default();
let origin: String = row
.try_get("origin")
.unwrap_or_else(|_| "manual".to_owned());
let source_repo: Option<String> = row.try_get("source_repo").ok();
if title.trim().is_empty() {
continue;
}
out.push(NewRule {
title,
origin,
source_repo,
});
}
Ok(out)
}