use anyhow::Result; use async_session::{async_trait, Session, SessionStore}; use chrono::{DateTime, TimeDelta, Utc}; use diesel::{pg::Pg, prelude::*, upsert::excluded}; use crate::schema::browser_sessions::dsl::*; #[derive(Clone, Debug, Identifiable, Queryable, Selectable)] #[diesel(table_name = crate::schema::browser_sessions)] #[diesel(check_for_backend(Pg))] pub struct BrowserSession { pub id: String, pub serialized: String, pub last_seen_at: DateTime, } #[derive(Clone)] pub struct PgStore { pool: deadpool_diesel::postgres::Pool, } impl PgStore { pub fn new(pool: deadpool_diesel::postgres::Pool) -> PgStore { Self { pool } } } impl std::fmt::Debug for PgStore { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "PgStore")?; Ok(()).into() } } #[async_trait] impl SessionStore for PgStore { async fn load_session(&self, cookie_value: String) -> Result> { let session_id = Session::id_from_cookie_value(&cookie_value)?; let timestamp_stale = Utc::now() - TimeDelta::days(7); let conn = self.pool.get().await?; let row = conn .interact(move |conn| { // Drop all sessions without recent activity diesel::delete(browser_sessions.filter(last_seen_at.lt(timestamp_stale))) .execute(conn)?; diesel::update(browser_sessions.filter(id.eq(session_id))) .set(last_seen_at.eq(diesel::dsl::now)) .returning(BrowserSession::as_returning()) .get_result(conn) .optional() }) .await .unwrap()?; Ok(match row { Some(session) => Some(serde_json::from_str::( session.serialized.as_str(), )?), None => None, }) } async fn store_session(&self, session: Session) -> Result> { let serialized_data = serde_json::to_string(&session)?; let session_id = session.id().to_string(); let conn = self.pool.get().await?; conn.interact(move |conn| { diesel::insert_into(browser_sessions) .values(( id.eq(session_id), serialized.eq(serialized_data), last_seen_at.eq(diesel::dsl::now), )) .on_conflict(id) .do_update() .set(( serialized.eq(excluded(serialized)), last_seen_at.eq(excluded(last_seen_at)), )) .execute(conn) }) .await .unwrap()?; session.reset_data_changed(); Ok(session.into_cookie_value()) } async fn destroy_session(&self, session: Session) -> Result<()> { let conn = self.pool.get().await?; conn.interact(move |conn| { diesel::delete(browser_sessions.filter(id.eq(session.id().to_string()))).execute(conn) }) .await .unwrap()?; Ok(()) } async fn clear_store(&self) -> Result<()> { let conn = self.pool.get().await?; conn.interact(move |conn| diesel::delete(browser_sessions).execute(conn)) .await .unwrap()?; Ok(()) } }