use async_trait::async_trait;
use sea_orm::{Condition, DatabaseConnection, Order};
use uuid::Uuid;
use crate::ApiError;
use crate::core::CRUDResource;
#[async_trait]
pub trait CRUDOperations: Send + Sync {
type Resource: CRUDResource;
async fn before_get_one(&self, _db: &DatabaseConnection, _id: Uuid) -> Result<(), ApiError> {
Ok(()) }
async fn after_get_one(
&self,
_db: &DatabaseConnection,
_entity: &mut Self::Resource,
) -> Result<(), ApiError> {
Ok(()) }
async fn fetch_one(
&self,
db: &DatabaseConnection,
id: Uuid,
) -> Result<Self::Resource, ApiError> {
use sea_orm::EntityTrait;
let model = <Self::Resource as CRUDResource>::EntityType::find_by_id(id)
.one(db)
.await
.map_err(ApiError::database)?
.ok_or_else(|| {
ApiError::not_found(
<Self::Resource as CRUDResource>::RESOURCE_NAME_SINGULAR,
Some(id.to_string()),
)
})?;
Ok(Self::Resource::from(model))
}
async fn before_get_all(
&self,
_db: &DatabaseConnection,
_condition: &Condition,
_order_column: <Self::Resource as CRUDResource>::ColumnType,
_order_direction: &Order,
_offset: u64,
_limit: u64,
) -> Result<(), ApiError> {
Ok(())
}
async fn after_get_all(
&self,
_db: &DatabaseConnection,
_entities: &mut Vec<<Self::Resource as CRUDResource>::ListModel>,
) -> Result<(), ApiError> {
Ok(())
}
async fn fetch_all(
&self,
db: &DatabaseConnection,
condition: &Condition,
order_column: <Self::Resource as CRUDResource>::ColumnType,
order_direction: Order,
offset: u64,
limit: u64,
) -> Result<Vec<<Self::Resource as CRUDResource>::ListModel>, ApiError> {
use sea_orm::{EntityTrait, QueryFilter, QueryOrder, QuerySelect};
let models = <Self::Resource as CRUDResource>::EntityType::find()
.filter(condition.clone())
.order_by(order_column, order_direction)
.offset(offset)
.limit(limit)
.all(db)
.await
.map_err(ApiError::database)?;
Ok(models
.into_iter()
.map(|model| {
<Self::Resource as CRUDResource>::ListModel::from(Self::Resource::from(model))
})
.collect())
}
async fn before_create(
&self,
_db: &DatabaseConnection,
_data: &<Self::Resource as CRUDResource>::CreateModel,
) -> Result<(), ApiError> {
Ok(())
}
async fn after_create(
&self,
_db: &DatabaseConnection,
_entity: &mut Self::Resource,
) -> Result<(), ApiError> {
Ok(())
}
async fn perform_create(
&self,
db: &DatabaseConnection,
data: <Self::Resource as CRUDResource>::CreateModel,
) -> Result<Self::Resource, ApiError> {
use sea_orm::ActiveModelTrait;
let active_model: <Self::Resource as CRUDResource>::ActiveModelType = data.into();
let model = active_model.insert(db).await.map_err(ApiError::database)?;
Ok(Self::Resource::from(model))
}
async fn before_update(
&self,
_db: &DatabaseConnection,
_id: Uuid,
_data: &<Self::Resource as CRUDResource>::UpdateModel,
) -> Result<(), ApiError> {
Ok(())
}
async fn after_update(
&self,
_db: &DatabaseConnection,
_entity: &mut Self::Resource,
) -> Result<(), ApiError> {
Ok(())
}
async fn perform_update(
&self,
db: &DatabaseConnection,
id: Uuid,
data: <Self::Resource as CRUDResource>::UpdateModel,
) -> Result<Self::Resource, ApiError> {
use crate::core::MergeIntoActiveModel;
use sea_orm::{ActiveModelTrait, EntityTrait, IntoActiveModel};
let model = <Self::Resource as CRUDResource>::EntityType::find_by_id(id)
.one(db)
.await
.map_err(ApiError::database)?
.ok_or_else(|| {
ApiError::not_found(
<Self::Resource as CRUDResource>::RESOURCE_NAME_SINGULAR,
Some(id.to_string()),
)
})?;
let existing: <Self::Resource as CRUDResource>::ActiveModelType = model.into_active_model();
let updated_model = data.merge_into_activemodel(existing)?;
let updated = updated_model.update(db).await.map_err(ApiError::database)?;
Ok(Self::Resource::from(updated))
}
async fn before_delete(&self, _db: &DatabaseConnection, _id: Uuid) -> Result<(), ApiError> {
Ok(())
}
async fn after_delete(&self, _db: &DatabaseConnection, _id: Uuid) -> Result<(), ApiError> {
Ok(())
}
async fn perform_delete(&self, db: &DatabaseConnection, id: Uuid) -> Result<Uuid, ApiError> {
use sea_orm::EntityTrait;
let res = <Self::Resource as CRUDResource>::EntityType::delete_by_id(id)
.exec(db)
.await
.map_err(ApiError::database)?;
match res.rows_affected {
0 => Err(ApiError::not_found(
<Self::Resource as CRUDResource>::RESOURCE_NAME_SINGULAR,
Some(id.to_string()),
)),
_ => Ok(id),
}
}
async fn before_delete_many(
&self,
_db: &DatabaseConnection,
_ids: &[Uuid],
) -> Result<(), ApiError> {
Ok(())
}
async fn after_delete_many(
&self,
_db: &DatabaseConnection,
_ids: &[Uuid],
) -> Result<(), ApiError> {
Ok(())
}
async fn perform_delete_many(
&self,
db: &DatabaseConnection,
ids: Vec<Uuid>,
) -> Result<Vec<Uuid>, ApiError> {
use crate::core::UuidIdResult;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QuerySelect};
let batch_limit = <Self::Resource as CRUDResource>::batch_limit();
if ids.len() > batch_limit {
return Err(ApiError::bad_request(format!(
"Batch delete limited to {} items. Received {} items.",
batch_limit,
ids.len()
)));
}
if ids.is_empty() {
return Ok(vec![]);
}
let existing: Vec<UuidIdResult> = <Self::Resource as CRUDResource>::EntityType::find()
.select_only()
.column_as(<Self::Resource as CRUDResource>::ID_COLUMN, "id")
.filter(<Self::Resource as CRUDResource>::ID_COLUMN.is_in(ids.clone()))
.into_model::<UuidIdResult>()
.all(db)
.await
.map_err(ApiError::database)?;
let existing_set: std::collections::HashSet<Uuid> =
existing.into_iter().map(|r| r.id).collect();
if !existing_set.is_empty() {
<Self::Resource as CRUDResource>::EntityType::delete_many()
.filter(
<Self::Resource as CRUDResource>::ID_COLUMN
.is_in(existing_set.iter().copied().collect::<Vec<_>>()),
)
.exec(db)
.await
.map_err(ApiError::database)?;
}
Ok(ids
.into_iter()
.filter(|id| existing_set.contains(id))
.collect())
}
async fn get_one(&self, db: &DatabaseConnection, id: Uuid) -> Result<Self::Resource, ApiError> {
self.before_get_one(db, id).await?;
let mut entity = self.fetch_one(db, id).await?;
self.after_get_one(db, &mut entity).await?;
Ok(entity)
}
async fn get_all(
&self,
db: &DatabaseConnection,
condition: &Condition,
order_column: <Self::Resource as CRUDResource>::ColumnType,
order_direction: Order,
offset: u64,
limit: u64,
) -> Result<Vec<<Self::Resource as CRUDResource>::ListModel>, ApiError> {
self.before_get_all(db, condition, order_column, &order_direction, offset, limit)
.await?;
let mut entities = self
.fetch_all(db, condition, order_column, order_direction, offset, limit)
.await?;
self.after_get_all(db, &mut entities).await?;
Ok(entities)
}
async fn create(
&self,
db: &DatabaseConnection,
data: <Self::Resource as CRUDResource>::CreateModel,
) -> Result<Self::Resource, ApiError> {
self.before_create(db, &data).await?;
let mut entity = self.perform_create(db, data).await?;
self.after_create(db, &mut entity).await?;
Ok(entity)
}
async fn update(
&self,
db: &DatabaseConnection,
id: Uuid,
data: <Self::Resource as CRUDResource>::UpdateModel,
) -> Result<Self::Resource, ApiError> {
self.before_update(db, id, &data).await?;
let mut entity = self.perform_update(db, id, data).await?;
self.after_update(db, &mut entity).await?;
Ok(entity)
}
async fn delete(&self, db: &DatabaseConnection, id: Uuid) -> Result<Uuid, ApiError> {
self.before_delete(db, id).await?;
let deleted_id = self.perform_delete(db, id).await?;
self.after_delete(db, deleted_id).await?;
Ok(deleted_id)
}
async fn delete_many(
&self,
db: &DatabaseConnection,
ids: Vec<Uuid>,
) -> Result<Vec<Uuid>, ApiError> {
self.before_delete_many(db, &ids).await?;
let deleted_ids = self.perform_delete_many(db, ids).await?;
self.after_delete_many(db, &deleted_ids).await?;
Ok(deleted_ids)
}
async fn create_many(
&self,
db: &DatabaseConnection,
data: Vec<<Self::Resource as CRUDResource>::CreateModel>,
) -> Result<Vec<Self::Resource>, ApiError> {
Self::Resource::create_many(db, data).await
}
async fn update_many(
&self,
db: &DatabaseConnection,
updates: Vec<(Uuid, <Self::Resource as CRUDResource>::UpdateModel)>,
) -> Result<Vec<Self::Resource>, ApiError> {
Self::Resource::update_many(db, updates).await
}
}
pub struct DefaultCRUDOperations<T: CRUDResource> {
_phantom: std::marker::PhantomData<T>,
}
impl<T: CRUDResource> DefaultCRUDOperations<T> {
#[must_use]
pub const fn new() -> Self {
Self {
_phantom: std::marker::PhantomData,
}
}
}
impl<T: CRUDResource> Default for DefaultCRUDOperations<T> {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl<T: CRUDResource> CRUDOperations for DefaultCRUDOperations<T> {
type Resource = T;
}