use crate::db::Pool;
use crate::db::database::interact_err;
use anyhow::{Context, Result};
use rusqlite::params;
use uuid::Uuid;
#[derive(Clone)]
pub struct SessionSkillsRepository {
pool: Pool,
}
impl SessionSkillsRepository {
pub fn new(pool: Pool) -> Self {
Self { pool }
}
pub async fn record(&self, session_id: Uuid, slug: &str, epoch: u64) -> Result<()> {
let sid = session_id.to_string();
let slug = slug.to_string();
let epoch = epoch as i64;
self.pool
.get()
.await
.context("Failed to get connection")?
.interact(move |conn| {
conn.execute(
"INSERT INTO session_seen_skills (session_id, slug, epoch) VALUES (?1, ?2, ?3) \
ON CONFLICT(session_id, slug) DO UPDATE \
SET seen_at = strftime('%s', 'now'), epoch = excluded.epoch",
params![sid, slug, epoch],
)
})
.await
.map_err(interact_err)?
.context("Failed to record seen skill")?;
Ok(())
}
pub async fn all(&self) -> Result<Vec<(Uuid, String, Option<i64>)>> {
let rows = self
.pool
.get()
.await
.context("Failed to get connection")?
.interact(move |conn| {
let mut stmt =
conn.prepare("SELECT session_id, slug, epoch FROM session_seen_skills")?;
let mapped = stmt.query_map([], |r| {
Ok((
r.get::<_, String>(0)?,
r.get::<_, String>(1)?,
r.get::<_, Option<i64>>(2)?,
))
})?;
mapped.collect::<rusqlite::Result<Vec<(String, String, Option<i64>)>>>()
})
.await
.map_err(interact_err)?
.context("Failed to read seen skills")?;
Ok(rows
.into_iter()
.filter_map(|(sid, slug, epoch)| Uuid::parse_str(&sid).ok().map(|id| (id, slug, epoch)))
.collect())
}
pub async fn prune_missing_sessions(&self) -> Result<u64> {
let n = self
.pool
.get()
.await
.context("Failed to get connection")?
.interact(move |conn| {
conn.execute(
"DELETE FROM session_seen_skills \
WHERE session_id NOT IN (SELECT id FROM sessions)",
[],
)
})
.await
.map_err(interact_err)?
.context("Failed to prune seen skills")?;
Ok(n as u64)
}
}