This commit is contained in:
2026-05-10 03:16:13 +02:00
parent 5f05108132
commit 0b441b0759
77 changed files with 2100 additions and 71 deletions
+33
View File
@@ -0,0 +1,33 @@
pub mod state;
use crate::config::AppConfig;
use crate::database::Database;
use migration::{Migrator, MigratorTrait};
pub use state::AppState;
use std::sync::Arc;
pub struct App {
pub state: AppState,
}
impl App {
pub async fn build(config: AppConfig) -> Result<Self, Box<dyn std::error::Error>> {
let db_manager = Database::init(&config.database.url).await?;
let db = db_manager.get_connection().clone();
Migrator::up(&db, None).await?;
let state = AppState {
db,
config: Arc::new(config),
};
Ok(Self { state })
}
pub async fn run(self) -> Result<(), Box<dyn std::error::Error>> {
tracing::info!("Starting services...");
tokio::select! {}
}
}
+9
View File
@@ -0,0 +1,9 @@
use crate::config::AppConfig;
use sea_orm::DatabaseConnection;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct AppState {
pub db: DatabaseConnection,
pub config: Arc<AppConfig>,
}
+3 -7
View File
@@ -1,6 +1,6 @@
use std::time::Duration;
use sea_orm::{ConnectOptions, Database as SeaDatabase, DatabaseConnection, DbErr};
use migration::{Migrator, MigratorTrait};
use std::time::Duration;
#[derive(Clone)]
pub struct Database {
@@ -19,10 +19,6 @@ impl Database {
let connection = SeaDatabase::connect(opt).await?;
// On lance les migrations ici.
// Si ça échoue, le programme s'arrête proprement à l'init.
Migrator::up(&connection, None).await?;
Ok(Self { connection })
}
@@ -31,4 +27,4 @@ impl Database {
pub fn get_connection(&self) -> &DatabaseConnection {
&self.connection
}
}
}
+1
View File
@@ -0,0 +1 @@
pub mod server;
+23
View File
@@ -0,0 +1,23 @@
use std::net::SocketAddr;
use axum::Router;
use tokio::net::TcpListener;
use crate::config::AppConfig;
use crate::routes;
pub async fn start(config: &AppConfig) -> Result<(), Box<dyn std::error::Error>> {
let addr = SocketAddr::new(
std::net::IpAddr::V4(config.network.host),
config.network.tcp_port,
);
let app: Router = routes::router();
let listener = TcpListener::bind(addr).await?;
tracing::info!(%addr, "HTTP server listening");
axum::serve(listener, app).await?;
Ok(())
}
+7
View File
@@ -1,4 +1,11 @@
pub mod config;
pub mod database;
pub mod http;
pub mod models;
// pub mod permissions_old;
pub mod core;
pub mod permissions;
pub mod repositories;
pub mod routes;
pub mod udp;
pub mod utils;
+9 -2
View File
@@ -1,4 +1,7 @@
use migration::{Migrator, MigratorTrait};
use oxspeak_server_lib::config::AppConfig;
use oxspeak_server_lib::core::App;
use oxspeak_server_lib::database::Database;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -19,10 +22,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
AppConfig::gen_config()?;
tracing::info!(config_path = "config.toml", "Generated");
tracing::info!("Config generated, please edit config.toml");
return Ok(())
return Ok(());
}
let config = AppConfig::load()?;
tracing::info!(?config, "Loaded config");
// 3. Build & Run
let app = App::build(config).await?;
app.run().await;
Ok(())
}
}
+5 -1
View File
@@ -11,9 +11,13 @@ pub struct Model {
pub id: Uuid,
pub server_id: Uuid,
pub name: String,
pub permissions: u64,
pub is_default: bool,
pub created_at: DateTimeUtc,
/// Permissions serveur par défaut (stockées en i64, lues en u64)
pub server_permissions: i64,
pub channel_permissions: i64,
pub voice_permissions: i64,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+20 -1
View File
@@ -1,5 +1,6 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19
use crate::permissions::{ChannelPermission, PermissionSet, ServerPermission, VoicePermission};
use sea_orm::entity::prelude::*;
use sea_orm::prelude::async_trait::async_trait;
use sea_orm::Set;
@@ -13,7 +14,11 @@ pub struct Model {
pub password: Option<String>,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
pub default_permissions: u64,
/// Permissions serveur par défaut (stockées en i64, lues en u64)
pub default_server_permissions: i64,
pub default_channel_permissions: i64,
pub default_voice_permissions: i64,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
@@ -47,9 +52,23 @@ impl Related<super::server_user::Entity> for Entity {
#[async_trait]
impl ActiveModelBehavior for ActiveModel {
fn new() -> Self {
let default_perm = PermissionSet::DEFAULT;
Self {
id: Set(Uuid::new_v4()),
default_server_permissions: Set(default_perm.server.bits() as i64),
default_channel_permissions: Set(default_perm.channel.bits() as i64),
default_voice_permissions: Set(default_perm.voice.bits() as i64),
..ActiveModelTrait::default()
}
}
}
impl Model {
pub fn default_permissions(&self) -> PermissionSet {
PermissionSet {
server: ServerPermission::from_bits_truncate(self.default_server_permissions as u64),
channel: ChannelPermission::from_bits_truncate(self.default_channel_permissions as u64),
voice: VoicePermission::from_bits_truncate(self.default_voice_permissions as u64),
}
}
}
+5 -1
View File
@@ -16,7 +16,11 @@ pub struct Model {
pub updated_at: DateTimeUtc,
pub is_admin: bool,
pub is_owner: bool,
pub permissions: u64,
/// Permissions serveur par défaut (stockées en i64, lues en u64)
pub server_permissions: i64,
pub channel_permissions: i64,
pub voice_permissions: i64,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+77
View File
@@ -0,0 +1,77 @@
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
bitflags! {
/// Permissions liées aux serveurs (gestion globale)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ServerPermission: u64 {
const ManageServer = 1 << 0;
const ManageRoles = 1 << 1;
const KickMember = 1 << 2;
const BanMember = 1 << 3;
// ... jusqu'à 64 perms serveur
}
}
bitflags! {
/// Permissions liées aux canaux (texte + vocal)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ChannelPermission: u64 {
const ReadChannel = 1 << 0;
const SendMessage = 1 << 1;
const DeleteMessage = 1 << 2;
const DeleteOthersMessage = 1 << 3;
const ManageChannel = 1 << 4;
// ...
}
}
bitflags! {
/// Permissions liées au vocal
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct VoicePermission: u64 {
const JoinChannel = 1 << 0;
const VoiceSpeak = 1 << 1;
const VoiceMuteOthers = 1 << 2;
const VoiceDeafenOthers = 1 << 3;
// ...
}
}
/// Agrégat de permissions traversant tous les domaines.
/// Utile pour les API et les contrôles complets.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct PermissionSet {
pub server: ServerPermission,
pub channel: ChannelPermission,
pub voice: VoicePermission,
}
impl PermissionSet {
pub const fn new(
server: ServerPermission,
channel: ChannelPermission,
voice: VoicePermission,
) -> Self {
Self {
server,
channel,
voice,
}
}
pub const DEFAULT: Self = Self::new(
ServerPermission::empty(),
ChannelPermission::from_bits_truncate(
ChannelPermission::ReadChannel.bits()
| ChannelPermission::SendMessage.bits()
| ChannelPermission::DeleteMessage.bits(),
),
VoicePermission::from_bits_truncate(
VoicePermission::JoinChannel.bits() | VoicePermission::VoiceSpeak.bits(),
),
);
}
+140
View File
@@ -0,0 +1,140 @@
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
#[serde(transparent)] // Permet de sérialiser comme un simple entier
pub struct Permission: u64 {
/// Pouvoir voir le canal dans la liste et lire les messages
const ReadChannel = 1 << 0;
/// Pouvoir rejoindre le canal vocal
const JoinChannel = 1 << 1;
/// Pouvoir envoyer des messages
const SendMessage = 1 << 2;
/// Pouvoir supprimer ses propres messages
const DeleteMessage = 1 << 3;
/// Pouvoir supprimer les messages des autres (Modérateur)
const DeleteOthersMessage = 1 << 4;
/// Pouvoir modifier les paramètres du canal
const ManageChannel = 1 << 5;
/// Pouvoir gérer les groupes/permissions du serveur
const ManageRoles = 1 << 6;
/// Pouvoir expulser des membres du serveur
const KickMember = 1 << 7;
/// Pouvoir parler en vocal
const VoiceSpeak = 1 << 8;
/// Pouvoir rendre muet les autres utilisateurs en vocal
const VoiceMuteOthers = 1 << 9;
/// Pouvoir modifier les paramètres globaux du serveur
const ManageServer = 1 << 10;
}
}
impl Permission {
// ─── Presets de permissions ────────────────────────────────────────────
/// Permissions par défaut pour un utilisateur standard
pub const DEFAULT: Self = Self::from_bits_truncate(
Self::ReadChannel.bits()
| Self::JoinChannel.bits()
| Self::SendMessage.bits()
| Self::DeleteMessage.bits()
| Self::VoiceSpeak.bits(),
);
/// Permissions de modération (sans gestion serveur)
pub const MODERATOR: Self = Self::from_bits_truncate(
Self::DEFAULT.bits()
| Self::DeleteOthersMessage.bits()
| Self::KickMember.bits()
| Self::VoiceMuteOthers.bits(),
);
/// Permissions d'administrateur (accès complet hormis l'ownership)
pub const ADMIN: Self = Self::from_bits_truncate(
Self::MODERATOR.bits() | Self::ManageChannel.bits() | Self::ManageRoles.bits(),
);
/// Toutes les permissions (équivalent owner)
pub const OWNER: Self = Self::all();
// ─── Groupes logiques ──────────────────────────────────────────────────
/// Toutes les permissions liées au chat
pub const CHAT_PERMISSIONS: Self = Self::from_bits_truncate(
Self::ReadChannel.bits()
| Self::SendMessage.bits()
| Self::DeleteMessage.bits()
| Self::DeleteOthersMessage.bits(),
);
/// Toutes les permissions liées au vocal
pub const VOICE_PERMISSIONS: Self = Self::from_bits_truncate(
Self::JoinChannel.bits() | Self::VoiceSpeak.bits() | Self::VoiceMuteOthers.bits(),
);
/// Toutes les permissions de gestion (Manage*)
pub const MANAGEMENT_PERMISSIONS: Self = Self::from_bits_truncate(
Self::ManageChannel.bits() | Self::ManageRoles.bits() | Self::ManageServer.bits(),
);
// ─── Helpers de vérification ───────────────────────────────────────────
/// Vérifie si l'utilisateur peut effectuer des actions de modération
#[inline]
pub fn is_moderator(&self) -> bool {
self.contains(Self::DeleteOthersMessage) || self.contains(Self::KickMember)
}
/// Vérifie si l'utilisateur a au moins une permission de gestion
#[inline]
pub fn is_admin(&self) -> bool {
self.intersects(Self::MANAGEMENT_PERMISSIONS)
}
/// Vérifie si l'utilisateur a tous les droits (owner)
#[inline]
pub fn is_owner(&self) -> bool {
*self == Self::OWNER
}
/// Vérifie si l'utilisateur peut accéder à un canal vocal
#[inline]
pub fn can_use_voice(&self) -> bool {
self.contains(Self::JoinChannel) && self.contains(Self::VoiceSpeak)
}
/// Vérifie si l'utilisateur peut interagir avec le chat (lire ET écrire)
#[inline]
pub fn can_chat(&self) -> bool {
self.contains(Self::ReadChannel | Self::SendMessage)
}
// ─── Conversion / Sérialisation ────────────────────────────────────────
/// Retourne la liste des noms des permissions actives
/// Utile pour les API REST ou les logs
pub fn to_names(&self) -> Vec<&'static str> {
self.iter_names().map(|(name, _)| name).collect()
}
/// Construit depuis une liste de noms (pour parsing JSON par ex)
pub fn from_names(names: &[&str]) -> Self {
names
.iter()
.filter_map(|name| Self::from_name(name))
.fold(Self::empty(), |acc, p| acc | p)
}
}
impl From<u64> for Permission {
fn from(bits: u64) -> Self {
Self::from_bits_truncate(bits)
}
}
impl From<Permission> for u64 {
fn from(perms: Permission) -> Self {
perms.bits()
}
}
+5
View File
@@ -0,0 +1,5 @@
Ce module permet de :
- Gérer/simplifier les interactions avec la base de données
- Avoir un système de Signal qui se déclenche lors de modifications
dans la base de données (Création, Modification, Suppression)
+46
View File
@@ -0,0 +1,46 @@
use std::sync::Arc;
use sea_orm::{
ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, QueryFilter,
};
use crate::models::category;
use crate::repositories::RepositoryContext;
#[derive(Clone)]
pub struct CategoryRepository {
pub context: Arc<RepositoryContext>
}
impl CategoryRepository {
pub async fn get_by_id(&self, id: uuid::Uuid) -> Result<Option<category::Model>, DbErr> {
category::Entity::find_by_id(id).one(&self.context.db).await
}
pub async fn get_all(&self) -> Result<Vec<category::Model>, DbErr> {
category::Entity::find().all(&self.context.db).await
}
pub async fn get_by_server_id(&self, server_id: uuid::Uuid) -> Result<Vec<category::Model>, DbErr> {
category::Entity::find()
.filter(category::Column::ServerId.eq(server_id))
.all(&self.context.db)
.await
}
pub async fn update(&self, active: category::ActiveModel) -> Result<category::Model, DbErr> {
let category = active.update(&self.context.db).await?;
self.context.events.emit("Category_updated", category.clone());
Ok(category)
}
pub async fn create(&self, active: category::ActiveModel) -> Result<category::Model, DbErr> {
let category = active.insert(&self.context.db).await?;
self.context.events.emit("Category_created", category.clone());
Ok(category)
}
pub async fn delete(&self, id: uuid::Uuid) -> Result<(), DbErr> {
category::Entity::delete_by_id(id).exec(&self.context.db).await?;
self.context.events.emit("Category_deleted", id);
Ok(())
}
}
+35
View File
@@ -0,0 +1,35 @@
use crate::models::channel;
use crate::repositories::RepositoryContext;
use sea_orm::{ActiveModelTrait, DbErr, EntityTrait};
use std::sync::Arc;
#[derive(Clone)]
pub struct ChannelRepository {
pub context: Arc<RepositoryContext>,
}
impl ChannelRepository {
pub async fn get_by_id(&self, id: uuid::Uuid) -> Result<Option<channel::Model>, DbErr> {
channel::Entity::find_by_id(id).one(&self.context.db).await
}
pub async fn update(&self, active: channel::ActiveModel) -> Result<channel::Model, DbErr> {
let channel = active.update(&self.context.db).await?;
self.context.events.emit("channel_updated", channel.clone());
Ok(channel)
}
pub async fn create(&self, active: channel::ActiveModel) -> Result<channel::Model, DbErr> {
let channel = active.insert(&self.context.db).await?;
self.context.events.emit("channel_created", channel.clone());
Ok(channel)
}
pub async fn delete(&self, id: uuid::Uuid) -> Result<(), DbErr> {
channel::Entity::delete_by_id(id)
.exec(&self.context.db)
.await?;
self.context.events.emit("channel_deleted", id);
Ok(())
}
}
+43
View File
@@ -0,0 +1,43 @@
use crate::models::group;
use crate::repositories::RepositoryContext;
use sea_orm::{ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, QueryFilter};
use std::sync::Arc;
use uuid::Uuid;
#[derive(Clone)]
pub struct GroupRepository {
pub context: Arc<RepositoryContext>,
}
impl GroupRepository {
pub async fn get_all_by_server(&self, server_id: Uuid) -> Result<Vec<group::Model>, DbErr> {
group::Entity::find()
.filter(group::Column::ServerId.eq(server_id))
.all(&self.context.db)
.await
}
pub async fn get_by_id(&self, id: Uuid) -> Result<Option<group::Model>, DbErr> {
group::Entity::find_by_id(id).one(&self.context.db).await
}
pub async fn create(&self, active: group::ActiveModel) -> Result<group::Model, DbErr> {
let group = active.insert(&self.context.db).await?;
self.context.events.emit("group_created", group.clone());
Ok(group)
}
pub async fn update(&self, active: group::ActiveModel) -> Result<group::Model, DbErr> {
let group = active.update(&self.context.db).await?;
self.context.events.emit("group_updated", group.clone());
Ok(group)
}
pub async fn delete(&self, id: Uuid) -> Result<bool, DbErr> {
let res = group::Entity::delete_by_id(id)
.exec(&self.context.db)
.await?;
self.context.events.emit("group_deleted", id);
Ok(res.rows_affected > 0)
}
}
+33
View File
@@ -0,0 +1,33 @@
use std::sync::Arc;
use sea_orm::{DbErr, EntityTrait, ActiveModelTrait};
use crate::models::message;
use crate::repositories::RepositoryContext;
#[derive(Clone)]
pub struct MessageRepository {
pub context: Arc<RepositoryContext>
}
impl MessageRepository {
pub async fn get_by_id(&self, id: uuid::Uuid) -> Result<Option<message::Model>, DbErr> {
message::Entity::find_by_id(id).one(&self.context.db).await
}
pub async fn update(&self, active: message::ActiveModel) -> Result<message::Model, DbErr> {
let message = active.update(&self.context.db).await?;
self.context.events.emit("message_updated", message.clone());
Ok(message)
}
pub async fn create(&self, active: message::ActiveModel) -> Result<message::Model, DbErr> {
let message = active.insert(&self.context.db).await?;
self.context.events.emit("message_created", message.clone());
Ok(message)
}
pub async fn delete(&self, id: uuid::Uuid) -> Result<(), DbErr> {
message::Entity::delete_by_id(id).exec(&self.context.db).await?;
self.context.events.emit("message_deleted", id);
Ok(())
}
}
+63
View File
@@ -0,0 +1,63 @@
use crate::repositories::category::CategoryRepository;
use crate::repositories::channel::ChannelRepository;
use crate::repositories::group::GroupRepository;
use crate::repositories::message::MessageRepository;
use crate::repositories::server::ServerRepository;
use crate::repositories::user::UserRepository;
use event_bus::EventBus;
use sea_orm::DatabaseConnection;
use std::sync::Arc;
mod category;
mod channel;
mod group;
mod message;
mod server;
pub mod types;
mod user;
#[derive(Clone)]
pub struct RepositoryContext {
db: DatabaseConnection,
events: Arc<EventBus>,
}
#[derive(Clone)]
pub struct Repositories {
pub server: ServerRepository,
pub category: CategoryRepository,
pub channel: ChannelRepository,
pub group: GroupRepository,
pub message: MessageRepository,
pub user: UserRepository,
}
impl Repositories {
pub fn new(db: &DatabaseConnection, events: Arc<EventBus>) -> Self {
let context = Arc::new(RepositoryContext {
db: db.clone(),
events,
});
Self {
server: ServerRepository {
context: context.clone(),
},
category: CategoryRepository {
context: context.clone(),
},
channel: ChannelRepository {
context: context.clone(),
},
group: GroupRepository {
context: context.clone(),
},
message: MessageRepository {
context: context.clone(),
},
user: UserRepository {
context: context.clone(),
},
}
}
}
+110
View File
@@ -0,0 +1,110 @@
use super::types::{ServerExplorerItem, ServerTree};
use super::RepositoryContext;
use crate::models::{category, channel, group, server};
use sea_orm::{ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, QueryFilter, Set};
use std::sync::Arc;
use uuid::Uuid;
#[derive(Clone)]
pub struct ServerRepository {
pub context: Arc<RepositoryContext>,
}
impl ServerRepository {
pub async fn get_all(&self) -> Result<Vec<server::Model>, DbErr> {
server::Entity::find().all(&self.context.db).await
}
pub async fn get_by_id(&self, id: uuid::Uuid) -> Result<Option<server::Model>, DbErr> {
server::Entity::find_by_id(id).one(&self.context.db).await
}
pub async fn update(&self, active: server::ActiveModel) -> Result<server::Model, DbErr> {
let server = active.update(&self.context.db).await?;
self.context.events.emit("server_updated", server.clone());
Ok(server)
}
pub async fn create(&self, active: server::ActiveModel) -> Result<server::Model, DbErr> {
let server = active.insert(&self.context.db).await?;
// Créer le groupe par défaut pour le serveur
let default_group = group::ActiveModel {
server_id: Set(server.id),
name: Set("Membres".to_string()),
is_default: Set(true),
..Default::default()
};
default_group.insert(&self.context.db).await?;
self.context.events.emit("server_created", server.clone());
Ok(server)
}
pub async fn delete(&self, id: uuid::Uuid) -> Result<bool, DbErr> {
let res = server::Entity::delete_by_id(id)
.exec(&self.context.db)
.await?;
self.context.events.emit("server_deleted", id);
Ok(res.rows_affected > 0)
}
}
// Helpers
impl ServerRepository {
pub async fn get_tree(&self, server_id: Uuid) -> Result<ServerTree, 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(ServerTree { items })
}
}
+20
View File
@@ -0,0 +1,20 @@
use crate::models::{category, channel};
pub enum ServerExplorerItem {
Category(category::Model, Vec<channel::Model>),
Channel(channel::Model),
}
// Pour pouvoir trier facilement
impl ServerExplorerItem {
pub fn position(&self) -> i32 {
match self {
ServerExplorerItem::Category(cat, _) => cat.position,
ServerExplorerItem::Channel(chan) => chan.position,
}
}
}
pub struct ServerTree {
pub items: Vec<ServerExplorerItem>,
}
+88
View File
@@ -0,0 +1,88 @@
use crate::models::user;
use crate::repositories::RepositoryContext;
use crate::utils::password;
use sea_orm::{
ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, IntoActiveModel, PaginatorTrait,
QueryFilter, Set,
};
use std::sync::Arc;
#[derive(Clone)]
pub struct UserRepository {
pub context: Arc<RepositoryContext>,
}
impl UserRepository {
pub async fn get_all(&self) -> Result<Vec<user::Model>, DbErr> {
user::Entity::find().all(&self.context.db).await
}
pub async fn count(&self) -> Result<u64, DbErr> {
user::Entity::find().count(&self.context.db).await
}
pub async fn get_by_id(&self, id: uuid::Uuid) -> Result<Option<user::Model>, DbErr> {
user::Entity::find_by_id(id).one(&self.context.db).await
}
pub async fn get_by_username(&self, username: String) -> Result<Option<user::Model>, DbErr> {
user::Entity::find()
.filter(user::Column::Username.eq(username))
.one(&self.context.db)
.await
}
pub async fn check_password(
&self,
username: String,
password: String,
) -> Result<user::Model, DbErr> {
let user = self.get_by_username(username).await?;
if let Some(user) = user {
let password_ok = password::verify_password(password.as_str(), user.password.as_str())
.map_err(|_| DbErr::Custom("Password hashing failed".to_string()))?;
if password_ok {
return Ok(user);
}
}
Err(DbErr::Custom("Invalid username or password".to_string()))
}
pub async fn update(&self, active: user::ActiveModel) -> Result<user::Model, DbErr> {
let user = active.update(&self.context.db).await?;
self.context.events.emit("user_updated", user.clone());
Ok(user)
}
pub async fn create(&self, active: user::ActiveModel) -> Result<user::Model, DbErr> {
let user = active.insert(&self.context.db).await?;
self.context.events.emit("user_created", user.clone());
Ok(user)
}
pub async fn set_password(&self, id: uuid::Uuid, password: String) -> Result<(), DbErr> {
let user = self
.get_by_id(id)
.await?
.ok_or_else(|| DbErr::Custom("User not found".to_string()))?;
let mut active = user.into_active_model();
let password = password::hash_password(&password)
.map_err(|_| DbErr::Custom("Password hashing failed".to_string()))?;
active.password = Set(password);
let user = self.update(active).await?;
self.context.events.emit("user_changed", user);
Ok(())
}
pub async fn delete(&self, id: uuid::Uuid) -> Result<(), DbErr> {
user::Entity::delete_by_id(id)
.exec(&self.context.db)
.await?;
self.context.events.emit("user_deleted", id);
Ok(())
}
}
+1
View File
@@ -0,0 +1 @@
pub struct Attachment {}
+10
View File
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateAttachmentRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateAttachmentRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct AttachmentResponse {}
+22
View File
@@ -0,0 +1,22 @@
use axum::http::StatusCode;
use axum::response::IntoResponse;
pub async fn get_all() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn get_by_id() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn create() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn update() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn delete() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
+5
View File
@@ -0,0 +1,5 @@
use super::{domain::Attachment, dto::AttachmentResponse};
pub fn to_response(_item: Attachment) -> AttachmentResponse {
todo!()
}
+6
View File
@@ -0,0 +1,6 @@
pub mod domain;
pub mod dto;
pub mod handlers;
pub mod mapper;
pub mod routes;
pub mod service;
+14
View File
@@ -0,0 +1,14 @@
use axum::{Router, routing::get};
use super::handlers;
pub fn router() -> Router {
Router::new()
.route("/attachments", get(handlers::get_all).post(handlers::create))
.route(
"/attachments/:id",
get(handlers::get_by_id)
.put(handlers::update)
.delete(handlers::delete),
)
}
+21
View File
@@ -0,0 +1,21 @@
use super::domain::Attachment;
pub async fn find_all() -> Vec<Attachment> {
todo!()
}
pub async fn find_by_id(_id: u64) -> Option<Attachment> {
todo!()
}
pub async fn create(_item: Attachment) -> Attachment {
todo!()
}
pub async fn update(_id: u64, _item: Attachment) -> Option<Attachment> {
todo!()
}
pub async fn delete(_id: u64) -> bool {
todo!()
}
+1
View File
@@ -0,0 +1 @@
pub struct Category {}
+10
View File
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateCategoryRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateCategoryRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct CategoryResponse {}
+22
View File
@@ -0,0 +1,22 @@
use axum::http::StatusCode;
use axum::response::IntoResponse;
pub async fn get_all() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn get_by_id() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn create() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn update() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn delete() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
+5
View File
@@ -0,0 +1,5 @@
use super::{domain::Category, dto::CategoryResponse};
pub fn to_response(_item: Category) -> CategoryResponse {
todo!()
}
+6
View File
@@ -0,0 +1,6 @@
pub mod domain;
pub mod dto;
pub mod handlers;
pub mod mapper;
pub mod routes;
pub mod service;
+14
View File
@@ -0,0 +1,14 @@
use axum::{Router, routing::get};
use super::handlers;
pub fn router() -> Router {
Router::new()
.route("/categorys", get(handlers::get_all).post(handlers::create))
.route(
"/categorys/:id",
get(handlers::get_by_id)
.put(handlers::update)
.delete(handlers::delete),
)
}
+21
View File
@@ -0,0 +1,21 @@
use super::domain::Category;
pub async fn find_all() -> Vec<Category> {
todo!()
}
pub async fn find_by_id(_id: u64) -> Option<Category> {
todo!()
}
pub async fn create(_item: Category) -> Category {
todo!()
}
pub async fn update(_id: u64, _item: Category) -> Option<Category> {
todo!()
}
pub async fn delete(_id: u64) -> bool {
todo!()
}
+1
View File
@@ -0,0 +1 @@
pub struct Channel {}
+10
View File
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateChannelRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateChannelRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct ChannelResponse {}
+22
View File
@@ -0,0 +1,22 @@
use axum::http::StatusCode;
use axum::response::IntoResponse;
pub async fn get_all() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn get_by_id() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn create() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn update() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn delete() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
+5
View File
@@ -0,0 +1,5 @@
use super::{domain::Channel, dto::ChannelResponse};
pub fn to_response(_item: Channel) -> ChannelResponse {
todo!()
}
+6
View File
@@ -0,0 +1,6 @@
pub mod domain;
pub mod dto;
pub mod handlers;
pub mod mapper;
pub mod routes;
pub mod service;
+14
View File
@@ -0,0 +1,14 @@
use axum::{Router, routing::get};
use super::handlers;
pub fn router() -> Router {
Router::new()
.route("/channels", get(handlers::get_all).post(handlers::create))
.route(
"/channels/:id",
get(handlers::get_by_id)
.put(handlers::update)
.delete(handlers::delete),
)
}
+21
View File
@@ -0,0 +1,21 @@
use super::domain::Channel;
pub async fn find_all() -> Vec<Channel> {
todo!()
}
pub async fn find_by_id(_id: u64) -> Option<Channel> {
todo!()
}
pub async fn create(_item: Channel) -> Channel {
todo!()
}
pub async fn update(_id: u64, _item: Channel) -> Option<Channel> {
todo!()
}
pub async fn delete(_id: u64) -> bool {
todo!()
}
+1
View File
@@ -0,0 +1 @@
pub struct Group {}
+10
View File
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateGroupRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateGroupRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct GroupResponse {}
+22
View File
@@ -0,0 +1,22 @@
use axum::http::StatusCode;
use axum::response::IntoResponse;
pub async fn get_all() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn get_by_id() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn create() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn update() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn delete() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
+5
View File
@@ -0,0 +1,5 @@
use super::{domain::Group, dto::GroupResponse};
pub fn to_response(_item: Group) -> GroupResponse {
todo!()
}
+6
View File
@@ -0,0 +1,6 @@
pub mod domain;
pub mod dto;
pub mod handlers;
pub mod mapper;
pub mod routes;
pub mod service;
+14
View File
@@ -0,0 +1,14 @@
use axum::{Router, routing::get};
use super::handlers;
pub fn router() -> Router {
Router::new()
.route("/groups", get(handlers::get_all).post(handlers::create))
.route(
"/groups/:id",
get(handlers::get_by_id)
.put(handlers::update)
.delete(handlers::delete),
)
}
+21
View File
@@ -0,0 +1,21 @@
use super::domain::Group;
pub async fn find_all() -> Vec<Group> {
todo!()
}
pub async fn find_by_id(_id: u64) -> Option<Group> {
todo!()
}
pub async fn create(_item: Group) -> Group {
todo!()
}
pub async fn update(_id: u64, _item: Group) -> Option<Group> {
todo!()
}
pub async fn delete(_id: u64) -> bool {
todo!()
}
+1
View File
@@ -0,0 +1 @@
pub struct Message {}
+10
View File
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateMessageRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateMessageRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct MessageResponse {}
+22
View File
@@ -0,0 +1,22 @@
use axum::http::StatusCode;
use axum::response::IntoResponse;
pub async fn get_all() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn get_by_id() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn create() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn update() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn delete() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
+5
View File
@@ -0,0 +1,5 @@
use super::{domain::Message, dto::MessageResponse};
pub fn to_response(_item: Message) -> MessageResponse {
todo!()
}
+6
View File
@@ -0,0 +1,6 @@
pub mod domain;
pub mod dto;
pub mod handlers;
pub mod mapper;
pub mod routes;
pub mod service;
+14
View File
@@ -0,0 +1,14 @@
use axum::{Router, routing::get};
use super::handlers;
pub fn router() -> Router {
Router::new()
.route("/messages", get(handlers::get_all).post(handlers::create))
.route(
"/messages/:id",
get(handlers::get_by_id)
.put(handlers::update)
.delete(handlers::delete),
)
}
+21
View File
@@ -0,0 +1,21 @@
use super::domain::Message;
pub async fn find_all() -> Vec<Message> {
todo!()
}
pub async fn find_by_id(_id: u64) -> Option<Message> {
todo!()
}
pub async fn create(_item: Message) -> Message {
todo!()
}
pub async fn update(_id: u64, _item: Message) -> Option<Message> {
todo!()
}
pub async fn delete(_id: u64) -> bool {
todo!()
}
+20
View File
@@ -0,0 +1,20 @@
use axum::Router;
pub mod attachment;
pub mod category;
pub mod channel;
pub mod group;
pub mod message;
pub mod server;
pub mod user;
pub fn router() -> Router {
Router::new()
.merge(user::routes::router())
.merge(server::routes::router())
.merge(channel::routes::router())
.merge(message::routes::router())
.merge(group::routes::router())
.merge(category::routes::router())
.merge(attachment::routes::router())
}
+1
View File
@@ -0,0 +1 @@
pub struct Server {}
+10
View File
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateServerRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateServerRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct ServerResponse {}
+22
View File
@@ -0,0 +1,22 @@
use axum::http::StatusCode;
use axum::response::IntoResponse;
pub async fn get_all() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn get_by_id() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn create() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn update() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn delete() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
+5
View File
@@ -0,0 +1,5 @@
use super::{domain::Server, dto::ServerResponse};
pub fn to_response(_item: Server) -> ServerResponse {
todo!()
}
+6
View File
@@ -0,0 +1,6 @@
pub mod domain;
pub mod dto;
pub mod handlers;
pub mod mapper;
pub mod routes;
pub mod service;
+14
View File
@@ -0,0 +1,14 @@
use axum::{Router, routing::get};
use super::handlers;
pub fn router() -> Router {
Router::new()
.route("/servers", get(handlers::get_all).post(handlers::create))
.route(
"/servers/:id",
get(handlers::get_by_id)
.put(handlers::update)
.delete(handlers::delete),
)
}
+21
View File
@@ -0,0 +1,21 @@
use super::domain::Server;
pub async fn find_all() -> Vec<Server> {
todo!()
}
pub async fn find_by_id(_id: u64) -> Option<Server> {
todo!()
}
pub async fn create(_item: Server) -> Server {
todo!()
}
pub async fn update(_id: u64, _item: Server) -> Option<Server> {
todo!()
}
pub async fn delete(_id: u64) -> bool {
todo!()
}
+1
View File
@@ -0,0 +1 @@
pub struct User {}
+10
View File
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateUserRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateUserRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct UserResponse {}
+22
View File
@@ -0,0 +1,22 @@
use axum::http::StatusCode;
use axum::response::IntoResponse;
pub async fn get_all() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn get_by_id() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn create() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn update() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn delete() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
+5
View File
@@ -0,0 +1,5 @@
use super::{domain::User, dto::UserResponse};
pub fn to_response(_user: User) -> UserResponse {
todo!()
}
+6
View File
@@ -0,0 +1,6 @@
pub mod domain;
pub mod dto;
pub mod handlers;
pub mod mapper;
pub mod routes;
pub mod service;
+14
View File
@@ -0,0 +1,14 @@
use axum::{routing::get, Router};
use super::handlers;
pub fn router() -> Router {
Router::new()
.route("/users", get(handlers::get_all).post(handlers::create))
.route(
"/users/:id",
get(handlers::get_by_id)
.put(handlers::update)
.delete(handlers::delete),
)
}
+21
View File
@@ -0,0 +1,21 @@
use super::domain::User;
pub async fn find_all() -> Vec<User> {
todo!()
}
pub async fn find_by_id(_id: u64) -> Option<User> {
todo!()
}
pub async fn create(_user: User) -> User {
todo!()
}
pub async fn update(_id: u64, _user: User) -> Option<User> {
todo!()
}
pub async fn delete(_id: u64) -> bool {
todo!()
}
+1
View File
@@ -0,0 +1 @@
pub mod password;
+25
View File
@@ -0,0 +1,25 @@
use argon2::{
password_hash::{phc::PasswordHash, PasswordHasher, PasswordVerifier}, Algorithm, Argon2, Params,
Version,
};
/// Hache un password avec Argon2id
/// Génère automatiquement un salt cryptographiquement sûr
pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
let params = Params::new(65540, 18, 1, None)?;
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
argon2
.hash_password(password.as_bytes())
.map(|hash| hash.to_string())
}
/// Vérifie un password contre son hash
pub fn verify_password(password: &str, hash: &str) -> Result<bool, argon2::password_hash::Error> {
let parsed_hash = PasswordHash::new(hash)?;
let argon2 = Argon2::default();
Ok(argon2
.verify_password(password.as_bytes(), &parsed_hash)
.is_ok())
}