use async_trait::async_trait;
use sqlx::PgPool;
use std::sync::Arc;
use systemprompt_database::DbPool;
use systemprompt_identifiers::UserId;
use systemprompt_traits::{OwnerReassignment, ReassignedRows};
#[derive(Debug, Clone)]
pub struct AiOwnerReassignment {
write_pool: Arc<PgPool>,
}
impl AiOwnerReassignment {
pub fn new(db: &DbPool) -> Self {
let write_pool = db.write_pool();
Self { write_pool }
}
}
fn shared_error(e: sqlx::Error) -> systemprompt_traits::RepositoryError {
systemprompt_traits::RepositoryError::database(e)
}
#[async_trait]
impl OwnerReassignment for AiOwnerReassignment {
fn domain(&self) -> &'static str {
"ai"
}
async fn reassign_owner(
&self,
from: &UserId,
to: &UserId,
) -> Result<ReassignedRows, systemprompt_traits::RepositoryError> {
let mut tx = self.write_pool.begin().await.map_err(shared_error)?;
let requests = sqlx::query!(
"UPDATE ai_requests SET user_id = $2, updated_at = CURRENT_TIMESTAMP WHERE user_id = $1",
from.as_str(),
to.as_str()
)
.execute(&mut *tx)
.await
.map_err(shared_error)?
.rows_affected();
let quota_buckets = sqlx::query!(
"DELETE FROM ai_quota_buckets WHERE subject_kind = 'user' AND subject_id = $1",
from.as_str()
)
.execute(&mut *tx)
.await
.map_err(shared_error)?
.rows_affected();
let thought_signatures = sqlx::query!(
"UPDATE ai_gateway_thought_signatures SET user_id = $2 WHERE user_id = $1 \
AND NOT EXISTS (SELECT 1 FROM ai_gateway_thought_signatures t \
WHERE t.user_id = $2 AND t.conversation_id = ai_gateway_thought_signatures.conversation_id \
AND t.tool_use_id = ai_gateway_thought_signatures.tool_use_id)",
from.as_str(),
to.as_str()
)
.execute(&mut *tx)
.await
.map_err(shared_error)?
.rows_affected();
tx.commit().await.map_err(shared_error)?;
Ok(ReassignedRows {
tables: vec![
("ai_requests", requests),
("ai_quota_buckets", quota_buckets),
("ai_gateway_thought_signatures", thought_signatures),
],
})
}
}