init
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
use serde::Deserialize;
|
||||
use utoipa::ToSchema;
|
||||
use validator::Validate;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Deserialize, Validate, ToSchema)]
|
||||
pub struct JoinRequest {
|
||||
@@ -11,4 +12,5 @@ pub struct JoinRequest {
|
||||
#[validate(must_match(other = "password", message = "Passwords do not match"))]
|
||||
pub password_valid: String,
|
||||
pub superuser_token: Option<String>,
|
||||
pub server_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
@@ -9,4 +9,5 @@ pub mod message;
|
||||
pub mod reaction;
|
||||
pub mod role;
|
||||
pub mod server;
|
||||
pub mod server_item_order;
|
||||
pub mod user;
|
||||
|
||||
@@ -14,6 +14,11 @@ pub struct CreateServerRequest {
|
||||
pub is_default: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct JoinServerRequest {
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UpdateServerRequest {
|
||||
pub name: String,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
use crate::models::server_item_order::OrderedResourceType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct ServerItemOrderReference {
|
||||
pub resource_id: Uuid,
|
||||
pub resource_type: OrderedResourceType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ServerItemOrderPosition {
|
||||
Before,
|
||||
After,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct ReorderServerItemRequest {
|
||||
pub server_id: Uuid,
|
||||
pub resource_id: Uuid,
|
||||
pub resource_type: OrderedResourceType,
|
||||
/// Nouvelle catégorie parente du canal. Doit être nul pour une catégorie.
|
||||
pub parent_category_id: Option<Uuid>,
|
||||
/// Élément devant ou derrière lequel insérer la ressource.
|
||||
pub reference: Option<ServerItemOrderReference>,
|
||||
pub position: ServerItemOrderPosition,
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
use crate::models::{channel, channel_user_read_state, message};
|
||||
use crate::repositories::{AnyResult, RepositoryContext};
|
||||
use chrono::Utc;
|
||||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QuerySelect, Set};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QuerySelect, Set,
|
||||
RelationTrait, sea_query::OnConflict,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
@@ -12,6 +15,58 @@ pub struct ReadStateRepository {
|
||||
}
|
||||
|
||||
impl ReadStateRepository {
|
||||
/// Marque tous les canaux d'un serveur comme lus en une opération groupée.
|
||||
pub async fn mark_server_read(&self, server_id: Uuid, user_id: Uuid) -> AnyResult<()> {
|
||||
let last_messages = message::Entity::find()
|
||||
.select_only()
|
||||
.column(message::Column::ChannelId)
|
||||
.column_as(message::Column::Id.max(), "last_read_message_id")
|
||||
.join(
|
||||
sea_orm::JoinType::InnerJoin,
|
||||
message::Relation::Channel.def(),
|
||||
)
|
||||
.filter(channel::Column::ServerId.eq(server_id))
|
||||
.group_by(message::Column::ChannelId)
|
||||
.into_tuple::<(Uuid, Uuid)>()
|
||||
.all(&self.context.db)
|
||||
.await?;
|
||||
|
||||
if last_messages.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let now = Utc::now();
|
||||
let states = last_messages
|
||||
.into_iter()
|
||||
.map(|(channel_id, last_read_message_id)| {
|
||||
channel_user_read_state::ActiveModel {
|
||||
id: Set(Uuid::now_v7()),
|
||||
channel_id: Set(channel_id),
|
||||
user_id: Set(user_id),
|
||||
last_read_message_id: Set(Some(last_read_message_id)),
|
||||
updated_at: Set(now),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
channel_user_read_state::Entity::insert_many(states)
|
||||
.on_conflict(
|
||||
OnConflict::columns([
|
||||
channel_user_read_state::Column::ChannelId,
|
||||
channel_user_read_state::Column::UserId,
|
||||
])
|
||||
.update_columns([
|
||||
channel_user_read_state::Column::LastReadMessageId,
|
||||
channel_user_read_state::Column::UpdatedAt,
|
||||
])
|
||||
.to_owned(),
|
||||
)
|
||||
.exec(&self.context.db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
&self,
|
||||
channel_id: Uuid,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{AnyResult, RepositoryContext};
|
||||
use crate::models::{role, server, server_role_permission, server_user, server_user_permission};
|
||||
use sea_orm::prelude::*;
|
||||
use sea_orm::{ActiveModelTrait, QuerySelect, Set};
|
||||
use sea_orm::{ActiveModelTrait, JoinType, QuerySelect, RelationTrait, Set};
|
||||
|
||||
use sea_orm::sea_query::OnConflict;
|
||||
use std::sync::Arc;
|
||||
@@ -17,6 +17,27 @@ impl ServerRepository {
|
||||
Ok(server::Entity::find().all(&self.context.db).await?)
|
||||
}
|
||||
|
||||
pub async fn get_all_for_user(&self, user_id: Uuid) -> AnyResult<Vec<server::Model>> {
|
||||
Ok(server::Entity::find()
|
||||
.join(JoinType::InnerJoin, server::Relation::ServerUser.def())
|
||||
.filter(server_user::Column::UserId.eq(user_id))
|
||||
.distinct()
|
||||
.all(&self.context.db)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_by_id_for_user(
|
||||
&self,
|
||||
id: Uuid,
|
||||
user_id: Uuid,
|
||||
) -> AnyResult<Option<server::Model>> {
|
||||
Ok(server::Entity::find_by_id(id)
|
||||
.join(JoinType::InnerJoin, server::Relation::ServerUser.def())
|
||||
.filter(server_user::Column::UserId.eq(user_id))
|
||||
.one(&self.context.db)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_by_id(&self, id: Uuid) -> AnyResult<Option<server::Model>> {
|
||||
Ok(server::Entity::find_by_id(id).one(&self.context.db).await?)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,23 @@ pub async fn join(
|
||||
State(state): State<AppState>,
|
||||
ValidatedJson(payload): ValidatedJson<JoinRequest>,
|
||||
) -> Result<impl IntoResponse, HTTPError> {
|
||||
let target_server_id = if let Some(server_id) = payload.server_id {
|
||||
let server = state
|
||||
.repositories
|
||||
.server
|
||||
.get_by_id(server_id)
|
||||
.await?
|
||||
.ok_or(HTTPError::NotFound)?;
|
||||
if server.password.is_some() {
|
||||
return Err(HTTPError::BadRequest(
|
||||
"This server requires a password; join it after creating your account".into(),
|
||||
));
|
||||
}
|
||||
server_id
|
||||
} else {
|
||||
state.default_server.id
|
||||
};
|
||||
|
||||
let user_exists = state
|
||||
.repositories
|
||||
.user
|
||||
@@ -56,12 +73,18 @@ pub async fn join(
|
||||
state
|
||||
.repositories
|
||||
.server
|
||||
.add_user(state.default_server.id, user.id),
|
||||
.add_user(target_server_id, user.id),
|
||||
state
|
||||
.repositories
|
||||
.role
|
||||
.add_to_default(user.id, state.default_server.id)
|
||||
.add_to_default(user.id, target_server_id)
|
||||
)?;
|
||||
|
||||
state
|
||||
.repositories
|
||||
.read_state
|
||||
.mark_server_read(target_server_id, user.id)
|
||||
.await?;
|
||||
|
||||
Ok(StatusCode::CREATED)
|
||||
}
|
||||
|
||||
@@ -214,6 +214,14 @@ pub async fn create(
|
||||
)
|
||||
.await
|
||||
.map_err(|error| HTTPError::BadRequest(error.to_string()))?;
|
||||
|
||||
// L'auteur a forcément lu le message qu'il vient d'envoyer.
|
||||
state
|
||||
.repositories
|
||||
.read_state
|
||||
.set(message.channel_id, user.id, Some(message.id))
|
||||
.await?;
|
||||
|
||||
let attachments = state
|
||||
.repositories
|
||||
.message
|
||||
|
||||
@@ -16,12 +16,14 @@ pub mod message;
|
||||
pub mod openapi;
|
||||
pub mod role;
|
||||
pub mod server;
|
||||
pub mod server_item_order;
|
||||
pub mod user;
|
||||
|
||||
pub fn router() -> OxRouter {
|
||||
// Routes nécessitant une authentification
|
||||
let secure_routes = Router::new()
|
||||
.merge(server::routes::router())
|
||||
.merge(server_item_order::routes::router())
|
||||
.merge(category::routes::router())
|
||||
.merge(channel::routes::router())
|
||||
.merge(conversation::routes::router())
|
||||
|
||||
@@ -18,9 +18,11 @@ use utoipa::{Modify, OpenApi};
|
||||
server::handlers::get_all,
|
||||
server::handlers::get_by_id,
|
||||
server::handlers::create,
|
||||
server::handlers::join,
|
||||
server::handlers::update,
|
||||
server::handlers::delete,
|
||||
server::handlers::get_tree,
|
||||
server_item_order::handlers::reorder,
|
||||
category::handlers::get_all,
|
||||
category::handlers::get_by_id,
|
||||
category::handlers::create,
|
||||
@@ -64,7 +66,11 @@ use utoipa::{Modify, OpenApi};
|
||||
crate::domain::dto::user::UpdateUserRequest,
|
||||
crate::domain::dto::server::ServerResponse,
|
||||
crate::domain::dto::server::CreateServerRequest,
|
||||
crate::domain::dto::server::JoinServerRequest,
|
||||
crate::domain::dto::server::UpdateServerRequest,
|
||||
crate::domain::dto::server_item_order::ReorderServerItemRequest,
|
||||
crate::domain::dto::server_item_order::ServerItemOrderReference,
|
||||
crate::domain::dto::server_item_order::ServerItemOrderPosition,
|
||||
crate::domain::dto::category::CategoryResponse,
|
||||
crate::domain::dto::category::CreateCategoryRequest,
|
||||
crate::domain::dto::category::UpdateCategoryRequest,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::core::state::AppState;
|
||||
use crate::domain::dto::server::{
|
||||
CreateServerRequest, ServerResponse, ServerRolePermissionResponse, ServerTreeResponse,
|
||||
CreateServerRequest, JoinServerRequest, ServerResponse, ServerRolePermissionResponse,
|
||||
ServerTreeResponse,
|
||||
ServerUserPermissionResponse, SetServerPermissionRequest, UpdateServerRequest,
|
||||
};
|
||||
use crate::http::context::{CurrentUser, Superuser};
|
||||
@@ -54,7 +55,7 @@ pub async fn get_all(
|
||||
user: CurrentUser,
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<Vec<ServerResponse>>, HTTPError> {
|
||||
let servers = state.repositories.server.get_all().await?;
|
||||
let servers = state.repositories.server.get_all_for_user(user.id).await?;
|
||||
let unread_counts = state
|
||||
.repositories
|
||||
.read_state
|
||||
@@ -117,14 +118,14 @@ pub async fn get_by_id(
|
||||
)
|
||||
)]
|
||||
pub async fn create(
|
||||
_admin: Superuser,
|
||||
user: CurrentUser,
|
||||
State(state): State<AppState>,
|
||||
Json(payload): Json<CreateServerRequest>,
|
||||
) -> Result<(StatusCode, Json<ServerResponse>), HTTPError> {
|
||||
let server = state
|
||||
.services
|
||||
.server
|
||||
.create_server(payload.name, payload.is_default)
|
||||
.create_server(payload.name, payload.password, payload.is_default, user.id)
|
||||
.await?;
|
||||
Ok((
|
||||
StatusCode::CREATED,
|
||||
@@ -132,6 +133,41 @@ pub async fn create(
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/servers/{id}/join",
|
||||
request_body = JoinServerRequest,
|
||||
responses(
|
||||
(status = 200, description = "Serveur rejoint", body = ServerResponse),
|
||||
(status = 404, description = "Serveur introuvable"),
|
||||
(status = 400, description = "Mot de passe incorrect")
|
||||
),
|
||||
params(("id" = Uuid, Path, description = "ID du serveur")),
|
||||
tag = "Servers",
|
||||
security(("bearerAuth" = []))
|
||||
)]
|
||||
pub async fn join(
|
||||
user: CurrentUser,
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
Json(payload): Json<JoinServerRequest>,
|
||||
) -> Result<Json<ServerResponse>, HTTPError> {
|
||||
let result = state
|
||||
.services
|
||||
.server
|
||||
.join_server(id, user.id, payload.password)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(server) => Ok(Json(mapper::server_model_to_server_response(server))),
|
||||
Err(error) if error.to_string() == "Server not found" => Err(HTTPError::NotFound),
|
||||
Err(error) if error.to_string() == "Invalid server password" => {
|
||||
Err(HTTPError::BadRequest("Invalid server password".into()))
|
||||
}
|
||||
Err(error) => Err(HTTPError::Internal(error)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Met à jour un serveur existant
|
||||
#[utoipa::path(
|
||||
put,
|
||||
@@ -251,6 +287,7 @@ pub async fn list_user_permissions(
|
||||
.get_by_id(server_id)
|
||||
.await?
|
||||
.ok_or(HTTPError::NotFound)?;
|
||||
|
||||
let permissions = state
|
||||
.repositories
|
||||
.server
|
||||
@@ -491,7 +528,7 @@ pub async fn get_tree(
|
||||
state
|
||||
.repositories
|
||||
.server
|
||||
.get_by_id(server_id)
|
||||
.get_by_id_for_user(server_id, user.id)
|
||||
.await?
|
||||
.ok_or(HTTPError::NotFound)?;
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use super::handlers;
|
||||
use crate::core::state::AppState;
|
||||
use axum::{Router, routing::get};
|
||||
use axum::{Router, routing::{get, post}};
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/servers", get(handlers::get_all).post(handlers::create))
|
||||
.route("/servers/{id}/join", post(handlers::join))
|
||||
.route(
|
||||
"/servers/{id}",
|
||||
get(handlers::get_by_id)
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
use crate::core::state::AppState;
|
||||
use crate::domain::dto::server_item_order::ReorderServerItemRequest;
|
||||
use crate::domain::events::server_tree::ServerTreeInvalidatedEvent;
|
||||
use crate::http::context::CurrentUser;
|
||||
use crate::http::error::HTTPError;
|
||||
use axum::http::StatusCode;
|
||||
use axum::{Json, extract::State};
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/server-item-orders/reorder",
|
||||
request_body = ReorderServerItemRequest,
|
||||
responses((status = 204, description = "Ordre mis à jour")),
|
||||
tag = "Server item orders",
|
||||
security(("bearerAuth" = []))
|
||||
)]
|
||||
pub async fn reorder(
|
||||
_user: CurrentUser,
|
||||
State(state): State<AppState>,
|
||||
Json(payload): Json<ReorderServerItemRequest>,
|
||||
) -> Result<StatusCode, HTTPError> {
|
||||
let server_id = payload.server_id;
|
||||
state
|
||||
.repositories
|
||||
.server
|
||||
.get_by_id(server_id)
|
||||
.await?
|
||||
.ok_or(HTTPError::NotFound)?;
|
||||
|
||||
state.services.server_order.reorder(payload).await?;
|
||||
state.event_bus.emit(
|
||||
"server_tree_invalidated",
|
||||
ServerTreeInvalidatedEvent {
|
||||
server_id,
|
||||
user_ids: None,
|
||||
},
|
||||
);
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod handlers;
|
||||
pub mod routes;
|
||||
@@ -0,0 +1,7 @@
|
||||
use super::handlers;
|
||||
use crate::core::state::AppState;
|
||||
use axum::{Router, routing::put};
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new().route("/server-item-orders/reorder", put(handlers::reorder))
|
||||
}
|
||||
+81
-3
@@ -1,5 +1,4 @@
|
||||
use crate::models::{role, server, server_user};
|
||||
use crate::repositories::Repositories;
|
||||
use crate::models::{role, role_user, server, server_user};
|
||||
use crate::services::ServicesContext;
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, Set,
|
||||
@@ -21,7 +20,9 @@ impl ServerService {
|
||||
pub async fn create_server(
|
||||
&self,
|
||||
name: String,
|
||||
password: Option<String>,
|
||||
is_default: bool,
|
||||
owner_id: Uuid,
|
||||
) -> Result<server::Model, anyhow::Error> {
|
||||
let db = &self.service_context.repositories.server.context.db;
|
||||
let event_bus = &self.service_context.event_bus;
|
||||
@@ -30,7 +31,9 @@ impl ServerService {
|
||||
|
||||
let active = server::ActiveModel {
|
||||
name: Set(name),
|
||||
password: Set(password),
|
||||
is_default: Set(is_default),
|
||||
owner_id: Set(Some(owner_id)),
|
||||
..Default::default()
|
||||
};
|
||||
let srv = active.insert(&txn).await?;
|
||||
@@ -41,7 +44,22 @@ impl ServerService {
|
||||
is_default: Set(true),
|
||||
..Default::default()
|
||||
};
|
||||
default_group.insert(&txn).await?;
|
||||
let default_group = default_group.insert(&txn).await?;
|
||||
|
||||
server_user::ActiveModel {
|
||||
server_id: Set(srv.id),
|
||||
user_id: Set(owner_id),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(&txn)
|
||||
.await?;
|
||||
|
||||
role_user::ActiveModel {
|
||||
role_id: Set(default_group.id),
|
||||
user_id: Set(owner_id),
|
||||
}
|
||||
.insert(&txn)
|
||||
.await?;
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
@@ -50,6 +68,66 @@ impl ServerService {
|
||||
Ok(srv)
|
||||
}
|
||||
|
||||
pub async fn join_server(
|
||||
&self,
|
||||
server_id: Uuid,
|
||||
user_id: Uuid,
|
||||
password: Option<String>,
|
||||
) -> Result<server::Model, anyhow::Error> {
|
||||
let db = &self.service_context.repositories.server.context.db;
|
||||
let event_bus = &self.service_context.event_bus;
|
||||
let txn = db.begin().await?;
|
||||
let srv = server::Entity::find_by_id(server_id)
|
||||
.one(&txn)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("Server not found"))?;
|
||||
|
||||
if srv.password != password {
|
||||
return Err(anyhow::anyhow!("Invalid server password"));
|
||||
}
|
||||
|
||||
if server_user::Entity::find()
|
||||
.filter(server_user::Column::ServerId.eq(server_id))
|
||||
.filter(server_user::Column::UserId.eq(user_id))
|
||||
.one(&txn)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
txn.commit().await?;
|
||||
return Ok(srv);
|
||||
}
|
||||
|
||||
server_user::ActiveModel {
|
||||
server_id: Set(server_id),
|
||||
user_id: Set(user_id),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(&txn)
|
||||
.await?;
|
||||
|
||||
let default_role = role::Entity::find()
|
||||
.filter(role::Column::ServerId.eq(server_id))
|
||||
.filter(role::Column::IsDefault.eq(true))
|
||||
.one(&txn)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("Default role not found"))?;
|
||||
role_user::ActiveModel {
|
||||
role_id: Set(default_role.id),
|
||||
user_id: Set(user_id),
|
||||
}
|
||||
.insert(&txn)
|
||||
.await?;
|
||||
|
||||
txn.commit().await?;
|
||||
event_bus.emit("server_user_created", (server_id, user_id));
|
||||
self.service_context
|
||||
.repositories
|
||||
.read_state
|
||||
.mark_server_read(server_id, user_id)
|
||||
.await?;
|
||||
Ok(srv)
|
||||
}
|
||||
|
||||
pub async fn update_server(
|
||||
&self,
|
||||
id: Uuid,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use crate::domain::dto::server_item_order::{ReorderServerItemRequest, ServerItemOrderPosition};
|
||||
use crate::models::server_item_order::{self, OrderedResourceType};
|
||||
use crate::models::{category, channel};
|
||||
use crate::services::ServicesContext;
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, DatabaseTransaction, EntityTrait, QueryFilter, QuerySelect, Set,
|
||||
ActiveModelTrait, ColumnTrait, DatabaseTransaction, EntityTrait, QueryFilter, QueryOrder,
|
||||
QuerySelect, Set, TransactionTrait,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
@@ -107,6 +110,144 @@ impl ServerOrderService {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn reorder(&self, request: ReorderServerItemRequest) -> Result<(), anyhow::Error> {
|
||||
let db = &self.service_context.repositories.server.context.db;
|
||||
let txn = db.begin().await?;
|
||||
|
||||
let mut orders = server_item_order::Entity::find()
|
||||
.filter(server_item_order::Column::ServerId.eq(request.server_id))
|
||||
.order_by_asc(server_item_order::Column::ParentCategoryId)
|
||||
.order_by_asc(server_item_order::Column::OrderKey)
|
||||
.all(&txn)
|
||||
.await?;
|
||||
|
||||
let target_index = orders
|
||||
.iter()
|
||||
.position(|order| {
|
||||
order.resource_id == request.resource_id
|
||||
&& order.resource_type == request.resource_type
|
||||
})
|
||||
.ok_or_else(|| anyhow::anyhow!("Ordered resource not found"))?;
|
||||
let target = orders.remove(target_index);
|
||||
|
||||
if request.resource_type == OrderedResourceType::Category
|
||||
&& request.parent_category_id.is_some()
|
||||
{
|
||||
return Err(anyhow::anyhow!("A category cannot have a parent category"));
|
||||
}
|
||||
|
||||
if let Some(category_id) = request.parent_category_id {
|
||||
let parent = category::Entity::find_by_id(category_id)
|
||||
.one(&txn)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("Parent category not found"))?;
|
||||
if parent.server_id != request.server_id {
|
||||
return Err(anyhow::anyhow!("Parent category belongs to another server"));
|
||||
}
|
||||
}
|
||||
|
||||
if request.resource_type == OrderedResourceType::Channel {
|
||||
let channel_model = channel::Entity::find_by_id(request.resource_id)
|
||||
.one(&txn)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("Channel not found"))?;
|
||||
if channel_model.server_id != Some(request.server_id) {
|
||||
return Err(anyhow::anyhow!("Channel belongs to another server"));
|
||||
}
|
||||
if channel_model.category_id != request.parent_category_id {
|
||||
let mut active: channel::ActiveModel = channel_model.into();
|
||||
active.category_id = Set(request.parent_category_id);
|
||||
active.update(&txn).await?;
|
||||
}
|
||||
} else {
|
||||
let category_model = category::Entity::find_by_id(request.resource_id)
|
||||
.one(&txn)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("Category not found"))?;
|
||||
if category_model.server_id != request.server_id {
|
||||
return Err(anyhow::anyhow!("Category belongs to another server"));
|
||||
}
|
||||
}
|
||||
|
||||
let reference_index = request.reference.as_ref().map(|reference| {
|
||||
orders.iter().position(|order| {
|
||||
order.resource_id == reference.resource_id
|
||||
&& order.resource_type == reference.resource_type
|
||||
})
|
||||
});
|
||||
let Some(reference_index) = reference_index.flatten() else {
|
||||
if request.reference.is_some() {
|
||||
return Err(anyhow::anyhow!("Order reference not found"));
|
||||
}
|
||||
let insertion_index = orders
|
||||
.iter()
|
||||
.filter(|order| order.parent_category_id == request.parent_category_id)
|
||||
.count();
|
||||
let destination_indices: Vec<usize> = orders
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, order)| {
|
||||
(order.parent_category_id == request.parent_category_id).then_some(index)
|
||||
})
|
||||
.collect();
|
||||
let insert_at = destination_indices
|
||||
.get(insertion_index.saturating_sub(1))
|
||||
.map(|index| index + 1)
|
||||
.unwrap_or_else(|| {
|
||||
orders
|
||||
.iter()
|
||||
.position(|order| order.parent_category_id == request.parent_category_id)
|
||||
.unwrap_or(orders.len())
|
||||
});
|
||||
orders.insert(
|
||||
insert_at,
|
||||
server_item_order::Model {
|
||||
parent_category_id: request.parent_category_id,
|
||||
order_key: target.order_key,
|
||||
..target
|
||||
},
|
||||
);
|
||||
self.persist_reordered(&txn, orders).await?;
|
||||
txn.commit().await?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let reference = orders[reference_index].clone();
|
||||
if reference.parent_category_id != request.parent_category_id {
|
||||
return Err(anyhow::anyhow!("Order reference belongs to another list"));
|
||||
}
|
||||
|
||||
let insert_at = match request.position {
|
||||
ServerItemOrderPosition::Before => reference_index,
|
||||
ServerItemOrderPosition::After => reference_index + 1,
|
||||
};
|
||||
orders.insert(
|
||||
insert_at,
|
||||
server_item_order::Model {
|
||||
parent_category_id: request.parent_category_id,
|
||||
order_key: target.order_key,
|
||||
..target
|
||||
},
|
||||
);
|
||||
|
||||
self.persist_reordered(&txn, orders).await?;
|
||||
txn.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn persist_reordered(
|
||||
&self,
|
||||
txn: &DatabaseTransaction,
|
||||
orders: Vec<server_item_order::Model>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
for (index, order) in orders.into_iter().enumerate() {
|
||||
let mut active: server_item_order::ActiveModel = order.into();
|
||||
active.order_key = Set((index as i64) + 1);
|
||||
active.update(txn).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn next_order_key(
|
||||
&self,
|
||||
txn: &DatabaseTransaction,
|
||||
|
||||
Reference in New Issue
Block a user