feat: add article_history DB module (check, insert, cleanup)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>master
parent
c271c240a2
commit
5a928aa990
@ -0,0 +1,76 @@
|
|||||||
|
//! Article history: tracks which article URLs have been used in past syntheses.
|
||||||
|
//!
|
||||||
|
//! Prevents the same article from appearing in multiple syntheses.
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use sqlx::PgPool;
|
||||||
|
use uuid::Uuid;
|
||||||
|
use crate::errors::AppError;
|
||||||
|
|
||||||
|
/// Check which URL hashes already exist in history for this user.
|
||||||
|
///
|
||||||
|
/// Returns the set of url_hashes that were found (i.e., already used).
|
||||||
|
pub async fn check_urls_exist(
|
||||||
|
pool: &PgPool,
|
||||||
|
user_id: Uuid,
|
||||||
|
url_hashes: &[String],
|
||||||
|
) -> Result<HashSet<String>, AppError> {
|
||||||
|
if url_hashes.is_empty() {
|
||||||
|
return Ok(HashSet::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let rows = sqlx::query_scalar::<_, String>(
|
||||||
|
"SELECT url_hash FROM article_history WHERE user_id = $1 AND url_hash = ANY($2)",
|
||||||
|
)
|
||||||
|
.bind(user_id)
|
||||||
|
.bind(url_hashes)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(rows.into_iter().collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert article URLs into history.
|
||||||
|
///
|
||||||
|
/// Uses ON CONFLICT DO NOTHING to silently skip duplicates.
|
||||||
|
pub async fn insert_urls(
|
||||||
|
pool: &PgPool,
|
||||||
|
user_id: Uuid,
|
||||||
|
urls: &[(String, String)], // Vec<(url, url_hash)>
|
||||||
|
) -> Result<(), AppError> {
|
||||||
|
if urls.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (url, url_hash) in urls {
|
||||||
|
sqlx::query(
|
||||||
|
"INSERT INTO article_history (user_id, url_hash, url) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING",
|
||||||
|
)
|
||||||
|
.bind(user_id)
|
||||||
|
.bind(url_hash)
|
||||||
|
.bind(url)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete history entries older than N days for this user.
|
||||||
|
///
|
||||||
|
/// Returns the number of deleted rows.
|
||||||
|
pub async fn cleanup_old(
|
||||||
|
pool: &PgPool,
|
||||||
|
user_id: Uuid,
|
||||||
|
days: i32,
|
||||||
|
) -> Result<u64, AppError> {
|
||||||
|
let result = sqlx::query(
|
||||||
|
"DELETE FROM article_history WHERE user_id = $1 AND created_at < now() - make_interval(days => $2)",
|
||||||
|
)
|
||||||
|
.bind(user_id)
|
||||||
|
.bind(days)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(result.rows_affected())
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue