use std::sync::Arc;
use async_trait::async_trait;
use sea_orm::{ActiveValue::Set, ColumnTrait, Condition, EntityTrait, QueryOrder};
use serde_json::Value as JsonValue;
use time::OffsetDateTime;
use toolkit_db::secure::{AccessScope, SecureDeleteExt, SecureEntityExt, SecureInsertExt};
use uuid::Uuid;
use crate::domain::error::ChatEngineError;
use crate::domain::ports::{BufferedEvent, StreamEventBuffer};
use crate::infra::db::entity::stream_event::{
self as stream_event_entity, Entity as StreamEventEntity,
};
use crate::infra::db::repo::ChatEngineDb;
pub struct SeaStreamEventBuffer {
db: Arc<ChatEngineDb>,
}
impl SeaStreamEventBuffer {
#[must_use]
pub fn new(db: Arc<ChatEngineDb>) -> Self {
Self { db }
}
}
fn seq_to_i64(seq: u64) -> i64 {
i64::try_from(seq).unwrap_or(i64::MAX)
}
#[async_trait]
impl StreamEventBuffer for SeaStreamEventBuffer {
async fn append(
&self,
message_id: Uuid,
seq: u64,
event: JsonValue,
expires_at: OffsetDateTime,
) -> Result<(), ChatEngineError> {
let conn = self.db.conn()?;
let scope = AccessScope::allow_all();
let am = stream_event_entity::ActiveModel {
message_id: Set(message_id),
seq: Set(seq_to_i64(seq)),
event: Set(event),
created_at: Set(OffsetDateTime::now_utc()),
expires_at: Set(expires_at),
};
StreamEventEntity::insert(am)
.secure()
.scope_unchecked(&scope)?
.exec(&conn)
.await?;
Ok(())
}
async fn read_since(
&self,
message_id: Uuid,
after_seq: Option<u64>,
) -> Result<Vec<BufferedEvent>, ChatEngineError> {
let conn = self.db.conn()?;
let scope = AccessScope::allow_all();
let mut cond = Condition::all().add(stream_event_entity::Column::MessageId.eq(message_id));
if let Some(after) = after_seq {
cond = cond.add(stream_event_entity::Column::Seq.gt(seq_to_i64(after)));
}
let rows = StreamEventEntity::find()
.order_by_asc(stream_event_entity::Column::Seq)
.secure()
.scope_with(&scope)
.filter(cond)
.all(&conn)
.await?;
Ok(rows
.into_iter()
.map(|r| BufferedEvent {
seq: u64::try_from(r.seq).unwrap_or(0),
event: r.event,
})
.collect())
}
async fn delete_expired(&self, now: OffsetDateTime) -> Result<u64, ChatEngineError> {
let conn = self.db.conn()?;
let scope = AccessScope::allow_all();
let res = StreamEventEntity::delete_many()
.secure()
.scope_with(&scope)
.filter(Condition::all().add(stream_event_entity::Column::ExpiresAt.lte(now)))
.exec(&conn)
.await?;
Ok(res.rows_affected)
}
}
#[cfg(test)]
#[path = "stream_event_repo_tests.rs"]
mod stream_event_repo_tests;