init
This commit is contained in:
+18
-27
@@ -1,9 +1,7 @@
|
||||
use crate::models::category;
|
||||
use crate::models::server_item_order::{self, OrderedResourceType};
|
||||
use crate::models::server_item_order::OrderedResourceType;
|
||||
use crate::services::ServicesContext;
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QuerySelect, Set, TransactionTrait,
|
||||
};
|
||||
use sea_orm::{ActiveModelTrait, EntityTrait, Set, TransactionTrait};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -24,6 +22,13 @@ impl CategoryService {
|
||||
) -> Result<category::Model, anyhow::Error> {
|
||||
let db = &self.service_context.repositories.server.context.db;
|
||||
let event_bus = &self.service_context.event_bus;
|
||||
let server_order = self
|
||||
.service_context
|
||||
.services
|
||||
.get()
|
||||
.expect("services initialized")
|
||||
.server_order
|
||||
.clone();
|
||||
|
||||
let cat = db
|
||||
.transaction::<_, category::Model, anyhow::Error>(|txn| {
|
||||
@@ -35,25 +40,9 @@ impl CategoryService {
|
||||
};
|
||||
let cat = active.insert(txn).await?;
|
||||
|
||||
let max_order: Option<i64> = server_item_order::Entity::find()
|
||||
.filter(server_item_order::Column::ServerId.eq(server_id))
|
||||
.filter(server_item_order::Column::ParentCategoryId.is_null())
|
||||
.select_only()
|
||||
.column_as(server_item_order::Column::OrderKey.max(), "max_key")
|
||||
.into_tuple::<Option<i64>>()
|
||||
.one(txn)
|
||||
.await?
|
||||
.flatten();
|
||||
|
||||
let order_item = server_item_order::ActiveModel {
|
||||
server_id: Set(server_id),
|
||||
resource_id: Set(cat.id),
|
||||
resource_type: Set(OrderedResourceType::Category),
|
||||
parent_category_id: Set(None),
|
||||
order_key: Set(max_order.unwrap_or(0) + 1),
|
||||
..Default::default()
|
||||
};
|
||||
order_item.insert(txn).await?;
|
||||
server_order
|
||||
.append(txn, server_id, cat.id, OrderedResourceType::Category, None)
|
||||
.await?;
|
||||
|
||||
Ok(cat)
|
||||
})
|
||||
@@ -98,10 +87,12 @@ impl CategoryService {
|
||||
|
||||
let txn = db.begin().await?;
|
||||
|
||||
server_item_order::Entity::delete_many()
|
||||
.filter(server_item_order::Column::ResourceId.eq(id))
|
||||
.filter(server_item_order::Column::ResourceType.eq(OrderedResourceType::Category))
|
||||
.exec(&txn)
|
||||
self.service_context
|
||||
.services
|
||||
.get()
|
||||
.expect("services initialized")
|
||||
.server_order
|
||||
.remove(&txn, id, OrderedResourceType::Category)
|
||||
.await?;
|
||||
|
||||
let res = category::Entity::delete_by_id(id).exec(&txn).await?;
|
||||
|
||||
+37
-45
@@ -1,10 +1,11 @@
|
||||
use crate::domain::dto::channel::{CreateChannelRequest, UpdateChannelRequest};
|
||||
use crate::models::server_item_order::OrderedResourceType;
|
||||
use crate::models::{channel, role};
|
||||
use crate::permissions::PermissionSet;
|
||||
use crate::services::ServicesContext;
|
||||
use crate::services::permission::PermissionService;
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QuerySelect, Set, TransactionTrait,
|
||||
ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set, TransactionTrait,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
@@ -25,6 +26,13 @@ impl ChannelService {
|
||||
) -> Result<channel::Model, anyhow::Error> {
|
||||
let db = &self.service_context.repositories.server.context.db;
|
||||
let event_bus = &self.service_context.event_bus;
|
||||
let server_order = self
|
||||
.service_context
|
||||
.services
|
||||
.get()
|
||||
.expect("services initialized")
|
||||
.server_order
|
||||
.clone();
|
||||
|
||||
let channel = db
|
||||
.transaction::<_, channel::Model, anyhow::Error>(|txn| {
|
||||
@@ -42,35 +50,15 @@ impl ChannelService {
|
||||
|
||||
// 2. Si server_id est présent, enregistrement de l'ordre d'affichage
|
||||
if let Some(server_id) = payload.server_id {
|
||||
let max_order: Option<i64> =
|
||||
crate::models::server_item_order::Entity::find()
|
||||
.filter(
|
||||
crate::models::server_item_order::Column::ServerId
|
||||
.eq(server_id),
|
||||
)
|
||||
.select_only()
|
||||
.column_as(
|
||||
crate::models::server_item_order::Column::OrderKey.max(),
|
||||
"max_key",
|
||||
)
|
||||
.into_tuple::<Option<i64>>()
|
||||
.one(txn)
|
||||
.await?
|
||||
.flatten();
|
||||
|
||||
let next_order = max_order.unwrap_or(0) + 1;
|
||||
|
||||
let order_item = crate::models::server_item_order::ActiveModel {
|
||||
server_id: Set(server_id),
|
||||
resource_id: Set(channel.id),
|
||||
resource_type: Set(
|
||||
crate::models::server_item_order::OrderedResourceType::Channel,
|
||||
),
|
||||
parent_category_id: Set(payload.category_id),
|
||||
order_key: Set(next_order),
|
||||
..Default::default()
|
||||
};
|
||||
order_item.insert(txn).await?;
|
||||
server_order
|
||||
.append(
|
||||
txn,
|
||||
server_id,
|
||||
channel.id,
|
||||
OrderedResourceType::Channel,
|
||||
payload.category_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 3. Attribution des permissions par défaut au rôle par défaut
|
||||
if let Some(default_role) = role::Entity::find()
|
||||
@@ -130,17 +118,19 @@ impl ChannelService {
|
||||
|
||||
let channel = active.update(&txn).await?;
|
||||
|
||||
// Update server_item_order parent_category_id or server_id if needed
|
||||
if let Some(server_id) = channel.server_id {
|
||||
crate::models::server_item_order::Entity::update_many()
|
||||
.set(crate::models::server_item_order::ActiveModel {
|
||||
parent_category_id: Set(channel.category_id),
|
||||
..Default::default()
|
||||
})
|
||||
.filter(crate::models::server_item_order::Column::ResourceId.eq(channel.id))
|
||||
.exec(&txn)
|
||||
.await?;
|
||||
}
|
||||
self.service_context
|
||||
.services
|
||||
.get()
|
||||
.expect("services initialized")
|
||||
.server_order
|
||||
.sync(
|
||||
&txn,
|
||||
channel.id,
|
||||
OrderedResourceType::Channel,
|
||||
channel.server_id,
|
||||
channel.category_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
@@ -155,10 +145,12 @@ impl ChannelService {
|
||||
|
||||
let txn = db.begin().await?;
|
||||
|
||||
// Delete associated order records
|
||||
crate::models::server_item_order::Entity::delete_many()
|
||||
.filter(crate::models::server_item_order::Column::ResourceId.eq(id))
|
||||
.exec(&txn)
|
||||
self.service_context
|
||||
.services
|
||||
.get()
|
||||
.expect("services initialized")
|
||||
.server_order
|
||||
.remove(&txn, id, OrderedResourceType::Channel)
|
||||
.await?;
|
||||
|
||||
let res = channel::Entity::delete_by_id(id).exec(&txn).await?;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
use crate::models::server_item_order::{self, OrderedResourceType};
|
||||
use crate::services::ServicesContext;
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, DatabaseTransaction, EntityTrait, QueryFilter, QuerySelect, Set,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServerOrderService {
|
||||
@@ -11,9 +16,303 @@ impl ServerOrderService {
|
||||
Self { service_context }
|
||||
}
|
||||
|
||||
pub(crate) async fn append(
|
||||
&self,
|
||||
txn: &DatabaseTransaction,
|
||||
server_id: Uuid,
|
||||
resource_id: Uuid,
|
||||
resource_type: OrderedResourceType,
|
||||
parent_category_id: Option<Uuid>,
|
||||
) -> Result<server_item_order::Model, anyhow::Error> {
|
||||
self.sync(
|
||||
txn,
|
||||
resource_id,
|
||||
resource_type,
|
||||
Some(server_id),
|
||||
parent_category_id,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("Unable to create server order"))
|
||||
}
|
||||
|
||||
pub(crate) async fn sync(
|
||||
&self,
|
||||
txn: &DatabaseTransaction,
|
||||
resource_id: Uuid,
|
||||
resource_type: OrderedResourceType,
|
||||
server_id: Option<Uuid>,
|
||||
parent_category_id: Option<Uuid>,
|
||||
) -> Result<Option<server_item_order::Model>, anyhow::Error> {
|
||||
let existing = server_item_order::Entity::find()
|
||||
.filter(server_item_order::Column::ResourceId.eq(resource_id))
|
||||
.filter(server_item_order::Column::ResourceType.eq(resource_type))
|
||||
.one(txn)
|
||||
.await?;
|
||||
|
||||
let Some(server_id) = server_id else {
|
||||
if let Some(order) = existing {
|
||||
server_item_order::Entity::delete_by_id(order.id)
|
||||
.exec(txn)
|
||||
.await?;
|
||||
}
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let order_key = match existing.as_ref() {
|
||||
Some(order)
|
||||
if order.server_id == server_id
|
||||
&& order.parent_category_id == parent_category_id =>
|
||||
{
|
||||
order.order_key
|
||||
}
|
||||
_ => {
|
||||
self.next_order_key(txn, server_id, parent_category_id)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
let order = if let Some(order) = existing {
|
||||
let mut active: server_item_order::ActiveModel = order.into();
|
||||
active.server_id = Set(server_id);
|
||||
active.parent_category_id = Set(parent_category_id);
|
||||
active.order_key = Set(order_key);
|
||||
active.update(txn).await?
|
||||
} else {
|
||||
server_item_order::ActiveModel {
|
||||
server_id: Set(server_id),
|
||||
resource_id: Set(resource_id),
|
||||
resource_type: Set(resource_type),
|
||||
parent_category_id: Set(parent_category_id),
|
||||
order_key: Set(order_key),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(txn)
|
||||
.await?
|
||||
};
|
||||
|
||||
Ok(Some(order))
|
||||
}
|
||||
|
||||
pub(crate) async fn remove(
|
||||
&self,
|
||||
txn: &DatabaseTransaction,
|
||||
resource_id: Uuid,
|
||||
resource_type: OrderedResourceType,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
server_item_order::Entity::delete_many()
|
||||
.filter(server_item_order::Column::ResourceId.eq(resource_id))
|
||||
.filter(server_item_order::Column::ResourceType.eq(resource_type))
|
||||
.exec(txn)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn next_order_key(
|
||||
&self,
|
||||
txn: &DatabaseTransaction,
|
||||
server_id: Uuid,
|
||||
parent_category_id: Option<Uuid>,
|
||||
) -> Result<i64, anyhow::Error> {
|
||||
let mut query = server_item_order::Entity::find()
|
||||
.filter(server_item_order::Column::ServerId.eq(server_id));
|
||||
query = match parent_category_id {
|
||||
Some(category_id) => {
|
||||
query.filter(server_item_order::Column::ParentCategoryId.eq(category_id))
|
||||
}
|
||||
None => query.filter(server_item_order::Column::ParentCategoryId.is_null()),
|
||||
};
|
||||
|
||||
let max_order = query
|
||||
.select_only()
|
||||
.column_as(server_item_order::Column::OrderKey.max(), "max_key")
|
||||
.into_tuple::<Option<i64>>()
|
||||
.one(txn)
|
||||
.await?
|
||||
.flatten()
|
||||
.unwrap_or(0);
|
||||
|
||||
Ok(max_order + 1)
|
||||
}
|
||||
|
||||
pub async fn exemple_call_other_service(&self) {
|
||||
if let Some(services) = self.service_context.services.get() {
|
||||
services.permission_sync.start_listen_event().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::models::{category, server};
|
||||
use crate::repositories::Repositories;
|
||||
use event_bus::EventBus;
|
||||
use migration::{Migrator, MigratorTrait};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, Database, DatabaseConnection, EntityTrait, Set,
|
||||
TransactionTrait,
|
||||
};
|
||||
|
||||
async fn service() -> ServerOrderService {
|
||||
let db = Database::connect("sqlite::memory:").await.unwrap();
|
||||
Migrator::up(&db, None).await.unwrap();
|
||||
|
||||
let context = Arc::new(ServicesContext {
|
||||
repositories: Arc::new(Repositories::new(db)),
|
||||
event_bus: Arc::new(EventBus::new()),
|
||||
services: std::sync::OnceLock::new(),
|
||||
});
|
||||
ServerOrderService::new(context)
|
||||
}
|
||||
|
||||
async fn seed_server(db: &DatabaseConnection, server_id: Uuid) {
|
||||
server::ActiveModel {
|
||||
id: Set(server_id),
|
||||
name: Set("test".to_owned()),
|
||||
is_default: Set(false),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(db)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sync_uses_scoped_keys_and_preserves_position() {
|
||||
let service = service().await;
|
||||
let db = &service.service_context.repositories.server.context.db;
|
||||
let server_id = Uuid::new_v4();
|
||||
let category_id = Uuid::new_v4();
|
||||
let category_resource_id = Uuid::new_v4();
|
||||
let root_channel_id = Uuid::new_v4();
|
||||
let nested_channel_id = Uuid::new_v4();
|
||||
seed_server(db, server_id).await;
|
||||
category::ActiveModel {
|
||||
id: Set(category_id),
|
||||
server_id: Set(server_id),
|
||||
name: Set("category".to_owned()),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let txn = db.begin().await.unwrap();
|
||||
service
|
||||
.append(
|
||||
&txn,
|
||||
server_id,
|
||||
category_resource_id,
|
||||
OrderedResourceType::Category,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
service
|
||||
.append(
|
||||
&txn,
|
||||
server_id,
|
||||
root_channel_id,
|
||||
OrderedResourceType::Channel,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
service
|
||||
.append(
|
||||
&txn,
|
||||
server_id,
|
||||
nested_channel_id,
|
||||
OrderedResourceType::Channel,
|
||||
Some(category_id),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
txn.commit().await.unwrap();
|
||||
|
||||
let txn = db.begin().await.unwrap();
|
||||
let unchanged = service
|
||||
.sync(
|
||||
&txn,
|
||||
nested_channel_id,
|
||||
OrderedResourceType::Channel,
|
||||
Some(server_id),
|
||||
Some(category_id),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(unchanged.order_key, 1);
|
||||
|
||||
let moved = service
|
||||
.sync(
|
||||
&txn,
|
||||
nested_channel_id,
|
||||
OrderedResourceType::Channel,
|
||||
Some(server_id),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(moved.order_key, 3);
|
||||
txn.commit().await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sync_none_removes_order_and_rollback_is_atomic() {
|
||||
let service = service().await;
|
||||
let db = &service.service_context.repositories.server.context.db;
|
||||
let server_id = Uuid::new_v4();
|
||||
let resource_id = Uuid::new_v4();
|
||||
seed_server(db, server_id).await;
|
||||
|
||||
let txn = db.begin().await.unwrap();
|
||||
service
|
||||
.append(
|
||||
&txn,
|
||||
server_id,
|
||||
resource_id,
|
||||
OrderedResourceType::Channel,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
txn.rollback().await.unwrap();
|
||||
|
||||
assert!(
|
||||
server_item_order::Entity::find()
|
||||
.filter(server_item_order::Column::ResourceId.eq(resource_id))
|
||||
.one(db)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
|
||||
let txn = db.begin().await.unwrap();
|
||||
service
|
||||
.append(
|
||||
&txn,
|
||||
server_id,
|
||||
resource_id,
|
||||
OrderedResourceType::Channel,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
service
|
||||
.sync(&txn, resource_id, OrderedResourceType::Channel, None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
txn.commit().await.unwrap();
|
||||
|
||||
assert!(
|
||||
server_item_order::Entity::find()
|
||||
.filter(server_item_order::Column::ResourceId.eq(resource_id))
|
||||
.one(db)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user