This commit is contained in:
2026-01-03 18:00:01 +01:00
parent 26e0400f50
commit 96765342d1
10 changed files with 121 additions and 7 deletions

12
Cargo.lock generated
View File

@@ -1656,6 +1656,7 @@ dependencies = [
"sea-orm", "sea-orm",
"serde", "serde",
"serde_json", "serde_json",
"serde_repr",
"socket2", "socket2",
"tokio", "tokio",
"toml", "toml",
@@ -2351,6 +2352,17 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "serde_repr"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.110",
]
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "1.0.3" version = "1.0.3"

View File

@@ -62,6 +62,7 @@ chrono = "0.4"
parking_lot = "0.12" parking_lot = "0.12"
serde = { version = "1.0", features = ["default", "derive"] } serde = { version = "1.0", features = ["default", "derive"] }
serde_json = { version = "1.0.145", features = ["default"]} serde_json = { version = "1.0.145", features = ["default"]}
serde_repr = "0.1"
toml = "0.9" toml = "0.9"
validator = { version = "0.20", features = ["derive"] } validator = { version = "0.20", features = ["derive"] }
uuid = {version = "1", features = ["v4", "v7", "fast-rng", "serde"]} uuid = {version = "1", features = ["v4", "v7", "fast-rng", "serde"]}

View File

@@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div> <div>
<h3>category : {{category.name}} <button @click="remove">Remove</button></h3> <h3>category : {{category.name}} ({{category.id}})<button @click="remove">Remove</button></h3>
</div> </div>
<hr> <hr>
</div> </div>

View File

@@ -2,7 +2,11 @@
<div> <div>
<div> <div>
<h3>channel : {{channel.name}} <button @click="remove">Remove</button></h3> <h3>channel : {{channel.name}} <button @click="remove">Remove</button></h3>
<p>Type: {{ channel.channel_type }}</p> <ul>
<li v-if="channel.server_id">Server: {{ channel.server_id }}</li>
<li v-if="channel.category_id">Category: {{ channel.category_id }}</li>
<li>Type: {{ channel.channel_type }}</li>
</ul>
</div> </div>
<hr> <hr>
</div> </div>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div> <div>
<h3>server : {{server.name}} <button @click="remove">Remove</button></h3> <h3>server : {{server.name}} ({{server.id}}) <button @click="remove">Remove</button></h3>
</div> </div>
<hr> <hr>

View File

@@ -63,6 +63,12 @@ impl MigrationTrait for Migration {
.string() .string()
.not_null() .not_null()
) )
.col(
ColumnDef::new(Alias::new("position"))
.integer()
.not_null()
.default(0)
)
.col( .col(
ColumnDef::new(Alias::new("created_at")) ColumnDef::new(Alias::new("created_at"))
.date_time() .date_time()

View File

@@ -11,6 +11,7 @@ pub struct Model {
pub id: Uuid, pub id: Uuid,
pub server_id: Uuid, pub server_id: Uuid,
pub name: String, pub name: String,
pub position: i32,
pub created_at: DateTime, pub created_at: DateTime,
pub updated_at: DateTime, pub updated_at: DateTime,
} }

View File

@@ -3,6 +3,19 @@
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
use sea_orm::prelude::async_trait::async_trait; use sea_orm::prelude::async_trait::async_trait;
use sea_orm::Set; use sea_orm::Set;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
#[sea_orm(rs_type = "i32", db_type = "Integer")]
#[serde(rename_all = "snake_case")]
pub enum ChannelType {
#[sea_orm(num_value = 0)]
Text,
#[sea_orm(num_value = 1)]
Voice,
#[sea_orm(num_value = 3)]
DM,
}
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "channel")] #[sea_orm(table_name = "channel")]
@@ -12,7 +25,7 @@ pub struct Model {
pub server_id: Option<Uuid>, pub server_id: Option<Uuid>,
pub category_id: Option<Uuid>, pub category_id: Option<Uuid>,
pub position: i32, pub position: i32,
pub channel_type: i32, pub channel_type: ChannelType,
pub name: Option<String>, pub name: Option<String>,
pub created_at: DateTime, pub created_at: DateTime,
pub updated_at: DateTime, pub updated_at: DateTime,

View File

@@ -1,6 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use sea_orm::{DbErr, EntityTrait, ActiveModelTrait}; use sea_orm::{DbErr, EntityTrait, ActiveModelTrait, QueryFilter, ColumnTrait, QueryOrder};
use crate::models::server; use uuid::Uuid;
use crate::models::{category, channel, server};
use crate::repositories::RepositoryContext; use crate::repositories::RepositoryContext;
#[derive(Clone)] #[derive(Clone)]
@@ -34,4 +35,79 @@ impl ServerRepository {
self.context.events.emit("server_deleted", id); self.context.events.emit("server_deleted", id);
Ok(res.rows_affected > 0) Ok(res.rows_affected > 0)
} }
}
pub enum ServerExplorerItem {
Category(category::Model, Vec<channel::Model>),
Channel(channel::Model),
}
// Pour pouvoir trier facilement
impl ServerExplorerItem {
fn position(&self) -> i32 {
match self {
ServerExplorerItem::Category(cat, _) => cat.position,
ServerExplorerItem::Channel(chan) => chan.position,
}
}
}
pub struct ServerLayout {
pub items: Vec<ServerExplorerItem>,
}
// Helpers
impl ServerRepository {
pub async fn get_channels_tree(&self, server_id: Uuid) -> Result<ServerLayout, DbErr> {
// 1. Récupération des catégories avec leurs channels
let categories_with_channels = category::Entity::find()
.filter(category::Column::ServerId.eq(server_id))
.find_with_related(channel::Entity)
.all(&self.context.db)
.await?;
// 2. Récupération des channels orphelins (sans catégorie)
let orphan_channels = channel::Entity::find()
.filter(channel::Column::ServerId.eq(server_id))
.filter(channel::Column::CategoryId.is_null())
.all(&self.context.db)
.await?;
// 3. Transformation et tri des enfants
let mut items: Vec<ServerExplorerItem> = Vec::new();
for (cat, mut channels) in categories_with_channels {
// On trie les channels internes (obligatoire car SQL ne garantit aucun ordre ici)
channels.sort_by(|a, b| {
a.position.cmp(&b.position).then(a.created_at.cmp(&b.created_at))
});
items.push(ServerExplorerItem::Category(cat, channels));
}
for chan in orphan_channels {
items.push(ServerExplorerItem::Channel(chan));
}
// 4. Tri final de la liste globale (Mélange catégories et orphelins)
items.sort_by(|a, b| {
let pos_cmp = a.position().cmp(&b.position());
if pos_cmp == std::cmp::Ordering::Equal {
// Départage par date si position identique
let date_a = match a {
ServerExplorerItem::Category(c, _) => c.created_at,
ServerExplorerItem::Channel(c) => c.created_at,
};
let date_b = match b {
ServerExplorerItem::Category(c, _) => c.created_at,
ServerExplorerItem::Channel(c) => c.created_at,
};
date_a.cmp(&date_b)
} else {
pos_cmp
}
});
Ok(ServerLayout { items })
}
} }

View File

@@ -2,6 +2,7 @@ use serde::{Serialize, Deserialize};
use sea_orm::ActiveValue::Set; use sea_orm::ActiveValue::Set;
use uuid::Uuid; use uuid::Uuid;
use crate::models::channel; use crate::models::channel;
use crate::models::channel::ChannelType;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct ChannelSerializer { pub struct ChannelSerializer {
@@ -11,7 +12,7 @@ pub struct ChannelSerializer {
pub category_id: Option<Uuid>, pub category_id: Option<Uuid>,
pub name: Option<String>, pub name: Option<String>,
pub position: Option<i32>, pub position: Option<i32>,
pub channel_type: i32, pub channel_type: ChannelType,
#[serde(skip_deserializing)] #[serde(skip_deserializing)]
pub created_at: Option<String>, pub created_at: Option<String>,