init
This commit is contained in:
1217
src-tauri/Cargo.lock
generated
1217
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -18,15 +18,17 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri = { version = "2.10.3", features = [] }
|
||||
tauri-plugin-opener = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
opusic-sys = "0.5"
|
||||
tokio = { version = "1.49", features = ["full"] }
|
||||
opusic-sys = "0.6"
|
||||
tokio = { version = "1.50", features = ["full"] }
|
||||
reqwest = { version = "0.13", features = ["json", "rustls"] }
|
||||
thiserror = "2.0"
|
||||
|
||||
figment = "0.10.19"
|
||||
parking_lot = "0.12"
|
||||
toml = "0.9"
|
||||
ssh-key = { version = "0.6", features = ["default", "crypto"] }
|
||||
base64 = "0.22"
|
||||
toml = "1.0.7+spec-1.1.0"
|
||||
ssh-key = { version = "0.7.0-rc.9", features = ["default", "crypto"] }
|
||||
base64 = "0.22"
|
||||
|
||||
247
src-tauri/src/api/client.rs
Normal file
247
src-tauri/src/api/client.rs
Normal file
@@ -0,0 +1,247 @@
|
||||
use crate::api::models::*;
|
||||
use parking_lot::RwLock;
|
||||
use reqwest::{header, Client, Method, RequestBuilder, Response};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Erreurs possibles lors de l'utilisation du client API.
|
||||
#[derive(Debug, thiserror::Error, Serialize)]
|
||||
#[serde(tag = "type", content = "data")]
|
||||
pub enum ApiError {
|
||||
#[error("Erreur de requête HTTP: {0}")]
|
||||
Http(String),
|
||||
#[error("Erreur de désérialisation: {0}")]
|
||||
Json(String),
|
||||
#[error("Réponse d'erreur du serveur: {status} - {body}")]
|
||||
ServerError { status: u16, body: String },
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for ApiError {
|
||||
fn from(err: reqwest::Error) -> Self {
|
||||
ApiError::Http(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for ApiError {
|
||||
fn from(err: serde_json::Error) -> Self {
|
||||
ApiError::Json(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub type ApiResult<T> = Result<T, ApiError>;
|
||||
|
||||
/// Un client API générique capable de se connecter à plusieurs serveurs.
|
||||
/// Il gère l'authentification par token JWT de manière thread-safe.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ApiClient {
|
||||
base_url: String,
|
||||
inner: Client,
|
||||
token: Arc<RwLock<Option<String>>>,
|
||||
}
|
||||
|
||||
impl ApiClient {
|
||||
/// Initialise un nouveau client pour un serveur spécifique.
|
||||
pub fn new(base_url: String) -> Self {
|
||||
let mut headers = header::HeaderMap::new();
|
||||
headers.insert(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
);
|
||||
|
||||
let inner = Client::builder()
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
.unwrap_or_else(|_| Client::new());
|
||||
|
||||
Self {
|
||||
base_url: base_url.trim_end_matches('/').to_string(),
|
||||
inner,
|
||||
token: Arc::new(RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Définit le token JWT à utiliser pour les requêtes authentifiées.
|
||||
pub fn set_token(&self, token: String) {
|
||||
let mut lock = self.token.write();
|
||||
*lock = Some(token);
|
||||
}
|
||||
|
||||
/// Récupère le token JWT actuel s'il existe.
|
||||
pub fn get_token(&self) -> Option<String> {
|
||||
self.token.read().clone()
|
||||
}
|
||||
|
||||
/// Supprime le token JWT actuel.
|
||||
pub fn clear_token(&self) {
|
||||
let mut lock = self.token.write();
|
||||
*lock = None;
|
||||
}
|
||||
|
||||
/// Construit une requête HTTP avec l'authentification si disponible.
|
||||
fn build_request(&self, method: Method, path: &str) -> RequestBuilder {
|
||||
let url = if path.starts_with("http") {
|
||||
path.to_string()
|
||||
} else {
|
||||
format!("{}/{}", self.base_url, path.trim_start_matches('/'))
|
||||
};
|
||||
let mut rb = self.inner.request(method, url);
|
||||
|
||||
if let Some(token) = self.token.read().as_ref() {
|
||||
rb = rb.header(header::AUTHORIZATION, format!("Bearer {}", token));
|
||||
}
|
||||
|
||||
rb
|
||||
}
|
||||
|
||||
async fn handle_response<T: DeserializeOwned>(&self, response: Response) -> ApiResult<T> {
|
||||
let status = response.status();
|
||||
if status.is_success() {
|
||||
let data = response.json::<T>().await?;
|
||||
Ok(data)
|
||||
} else {
|
||||
let body = response.text().await.unwrap_or_default();
|
||||
Err(ApiError::ServerError {
|
||||
status: status.as_u16(),
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get<T: DeserializeOwned>(&self, path: &str) -> ApiResult<T> {
|
||||
let response = self.build_request(Method::GET, path).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
pub async fn post<B: Serialize, T: DeserializeOwned>(
|
||||
&self,
|
||||
path: &str,
|
||||
body: &B,
|
||||
) -> ApiResult<T> {
|
||||
let response = self
|
||||
.build_request(Method::POST, path)
|
||||
.json(body)
|
||||
.send()
|
||||
.await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
pub async fn post_empty<T: DeserializeOwned>(&self, path: &str) -> ApiResult<T> {
|
||||
let response = self.build_request(Method::POST, path).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
pub async fn put<B: Serialize, T: DeserializeOwned>(
|
||||
&self,
|
||||
path: &str,
|
||||
body: &B,
|
||||
) -> ApiResult<T> {
|
||||
let response = self
|
||||
.build_request(Method::PUT, path)
|
||||
.json(body)
|
||||
.send()
|
||||
.await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
pub async fn delete<T: DeserializeOwned>(&self, path: &str) -> ApiResult<T> {
|
||||
let response = self.build_request(Method::DELETE, path).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
// --- ECOSYSTEMS ---
|
||||
|
||||
// Auth
|
||||
pub async fn login(&self, req: &LoginRequest) -> ApiResult<LoginResponse> {
|
||||
self.post("api/auth/login", req).await
|
||||
}
|
||||
|
||||
pub async fn claim_admin(&self, req: &ClaimAdminRequest) -> ApiResult<()> {
|
||||
self.post("api/auth/claim-admin", req).await
|
||||
}
|
||||
|
||||
pub async fn ssh_challenge(&self) -> ApiResult<SshChallengeResponse> {
|
||||
self.post_empty("api/auth/ssh-challenge").await
|
||||
}
|
||||
|
||||
// Server
|
||||
pub async fn server_list(&self) -> ApiResult<Vec<ServerResponse>> {
|
||||
self.get("api/server").await
|
||||
}
|
||||
|
||||
pub async fn server_create(&self, req: &CreateServerRequest) -> ApiResult<ServerResponse> {
|
||||
self.post("api/server", req).await
|
||||
}
|
||||
|
||||
// Category
|
||||
pub async fn category_list(&self, server_id: Option<&str>) -> ApiResult<Vec<CategoryResponse>> {
|
||||
let path = if let Some(id) = server_id {
|
||||
format!("api/category?server_id={}", id)
|
||||
} else {
|
||||
"api/category".to_string()
|
||||
};
|
||||
self.get(&path).await
|
||||
}
|
||||
|
||||
pub async fn category_create(
|
||||
&self,
|
||||
req: &CreateCategoryRequest,
|
||||
) -> ApiResult<CategoryResponse> {
|
||||
self.post("api/category", req).await
|
||||
}
|
||||
|
||||
pub async fn category_detail(&self, id: &str) -> ApiResult<CategoryResponse> {
|
||||
self.get(&format!("api/category/{}", id)).await
|
||||
}
|
||||
|
||||
pub async fn category_update(
|
||||
&self,
|
||||
id: &str,
|
||||
req: &CreateCategoryRequest,
|
||||
) -> ApiResult<CategoryResponse> {
|
||||
self.put(&format!("api/category/{}", id), req).await
|
||||
}
|
||||
|
||||
pub async fn category_delete(&self, id: &str) -> ApiResult<()> {
|
||||
self.delete(&format!("api/category/{}", id)).await
|
||||
}
|
||||
|
||||
// Channel
|
||||
pub async fn channel_list(&self) -> ApiResult<Vec<ChannelResponse>> {
|
||||
self.get("api/channel").await
|
||||
}
|
||||
|
||||
pub async fn channel_create(&self, req: &CreateChannelRequest) -> ApiResult<ChannelResponse> {
|
||||
self.post("api/channel", req).await
|
||||
}
|
||||
|
||||
pub async fn channel_detail(&self, id: &str) -> ApiResult<ChannelResponse> {
|
||||
self.get(&format!("api/channel/{}", id)).await
|
||||
}
|
||||
|
||||
pub async fn channel_update(
|
||||
&self,
|
||||
id: &str,
|
||||
req: &CreateChannelRequest,
|
||||
) -> ApiResult<ChannelResponse> {
|
||||
self.put(&format!("api/channel/{}", id), req).await
|
||||
}
|
||||
|
||||
pub async fn channel_delete(&self, id: &str) -> ApiResult<()> {
|
||||
self.delete(&format!("api/channel/{}", id)).await
|
||||
}
|
||||
|
||||
// Message
|
||||
pub async fn message_list(&self) -> ApiResult<Vec<MessageResponse>> {
|
||||
self.get("api/message").await
|
||||
}
|
||||
|
||||
pub async fn message_create(&self, req: &CreateMessageRequest) -> ApiResult<MessageResponse> {
|
||||
self.post("api/message", req).await
|
||||
}
|
||||
|
||||
// User
|
||||
pub async fn user_list(&self) -> ApiResult<Vec<UserResponse>> {
|
||||
self.get("api/user").await
|
||||
}
|
||||
}
|
||||
133
src-tauri/src/api/commands.rs
Normal file
133
src-tauri/src/api/commands.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
use crate::api::models::*;
|
||||
use crate::api::ApiResult;
|
||||
use crate::app::state::AppState;
|
||||
use tauri::{command, State};
|
||||
|
||||
// --- AUTH ---
|
||||
|
||||
#[command]
|
||||
pub async fn api_login(
|
||||
state: State<'_, AppState>,
|
||||
base_url: String,
|
||||
req: LoginRequest,
|
||||
) -> ApiResult<LoginResponse> {
|
||||
let client = state.api.get_or_create_client(base_url);
|
||||
let res = client.login(&req).await?;
|
||||
client.set_token(res.token.clone());
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn api_claim_admin(
|
||||
state: State<'_, AppState>,
|
||||
base_url: String,
|
||||
req: ClaimAdminRequest,
|
||||
) -> ApiResult<()> {
|
||||
let client = state.api.get_or_create_client(base_url);
|
||||
client.claim_admin(&req).await
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn api_ssh_challenge(
|
||||
state: State<'_, AppState>,
|
||||
base_url: String,
|
||||
) -> ApiResult<SshChallengeResponse> {
|
||||
let client = state.api.get_or_create_client(base_url);
|
||||
client.ssh_challenge().await
|
||||
}
|
||||
|
||||
// --- SERVER ---
|
||||
|
||||
#[command]
|
||||
pub async fn api_server_list(
|
||||
state: State<'_, AppState>,
|
||||
base_url: String,
|
||||
) -> ApiResult<Vec<ServerResponse>> {
|
||||
let client = state.api.get_or_create_client(base_url);
|
||||
client.server_list().await
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn api_server_create(
|
||||
state: State<'_, AppState>,
|
||||
base_url: String,
|
||||
req: CreateServerRequest,
|
||||
) -> ApiResult<ServerResponse> {
|
||||
let client = state.api.get_or_create_client(base_url);
|
||||
client.server_create(&req).await
|
||||
}
|
||||
|
||||
// --- CATEGORY ---
|
||||
|
||||
#[command]
|
||||
pub async fn api_category_list(
|
||||
state: State<'_, AppState>,
|
||||
base_url: String,
|
||||
server_id: Option<String>,
|
||||
) -> ApiResult<Vec<CategoryResponse>> {
|
||||
let client = state.api.get_or_create_client(base_url);
|
||||
client.category_list(server_id.as_deref()).await
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn api_category_create(
|
||||
state: State<'_, AppState>,
|
||||
base_url: String,
|
||||
req: CreateCategoryRequest,
|
||||
) -> ApiResult<CategoryResponse> {
|
||||
let client = state.api.get_or_create_client(base_url);
|
||||
client.category_create(&req).await
|
||||
}
|
||||
|
||||
// --- CHANNEL ---
|
||||
|
||||
#[command]
|
||||
pub async fn api_channel_list(
|
||||
state: State<'_, AppState>,
|
||||
base_url: String,
|
||||
) -> ApiResult<Vec<ChannelResponse>> {
|
||||
let client = state.api.get_or_create_client(base_url);
|
||||
client.channel_list().await
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn api_channel_create(
|
||||
state: State<'_, AppState>,
|
||||
base_url: String,
|
||||
req: CreateChannelRequest,
|
||||
) -> ApiResult<ChannelResponse> {
|
||||
let client = state.api.get_or_create_client(base_url);
|
||||
client.channel_create(&req).await
|
||||
}
|
||||
|
||||
// --- MESSAGE ---
|
||||
|
||||
#[command]
|
||||
pub async fn api_message_list(
|
||||
state: State<'_, AppState>,
|
||||
base_url: String,
|
||||
) -> ApiResult<Vec<MessageResponse>> {
|
||||
let client = state.api.get_or_create_client(base_url);
|
||||
client.message_list().await
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn api_message_create(
|
||||
state: State<'_, AppState>,
|
||||
base_url: String,
|
||||
req: CreateMessageRequest,
|
||||
) -> ApiResult<MessageResponse> {
|
||||
let client = state.api.get_or_create_client(base_url);
|
||||
client.message_create(&req).await
|
||||
}
|
||||
|
||||
// --- USER ---
|
||||
|
||||
#[command]
|
||||
pub async fn api_user_list(
|
||||
state: State<'_, AppState>,
|
||||
base_url: String,
|
||||
) -> ApiResult<Vec<UserResponse>> {
|
||||
let client = state.api.get_or_create_client(base_url);
|
||||
client.user_list().await
|
||||
}
|
||||
7
src-tauri/src/api/mod.rs
Normal file
7
src-tauri/src/api/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
pub mod client;
|
||||
pub mod commands;
|
||||
pub mod models;
|
||||
pub mod state;
|
||||
|
||||
pub use client::{ApiClient, ApiError, ApiResult};
|
||||
pub use state::ApiManager;
|
||||
115
src-tauri/src/api/models.rs
Normal file
115
src-tauri/src/api/models.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// --- AUTH ---
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct LoginRequest {
|
||||
pub username: String,
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct LoginResponse {
|
||||
pub token: String,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ClaimAdminRequest {
|
||||
pub token: String,
|
||||
pub username: String,
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct SshChallengeResponse {
|
||||
pub challenge: String,
|
||||
}
|
||||
|
||||
// --- SERVER ---
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct CreateServerRequest {
|
||||
pub name: String,
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
pub struct ServerResponse {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
// --- CATEGORY ---
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct CreateCategoryRequest {
|
||||
pub server_id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct CategoryResponse {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
// --- CHANNEL ---
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ChannelType {
|
||||
Text,
|
||||
Voice,
|
||||
DM,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct CreateChannelRequest {
|
||||
pub channel_type: ChannelType,
|
||||
pub category_id: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub position: Option<i32>,
|
||||
pub server_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ChannelResponse {
|
||||
pub id: String,
|
||||
pub name: Option<String>,
|
||||
pub position: i32,
|
||||
pub channel_type: ChannelType,
|
||||
pub category_id: Option<String>,
|
||||
}
|
||||
|
||||
// --- MESSAGE ---
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct CreateMessageRequest {
|
||||
pub channel_id: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct MessageResponse {
|
||||
pub id: String,
|
||||
pub channel_id: String,
|
||||
pub author_id: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
// --- USER ---
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct CreateUserRequest {
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct UserResponse {
|
||||
pub id: String,
|
||||
pub username: String,
|
||||
pub pub_key: String,
|
||||
}
|
||||
35
src-tauri/src/api/state.rs
Normal file
35
src-tauri/src/api/state.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use crate::api::client::ApiClient;
|
||||
use parking_lot::RwLock;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Gère plusieurs instances de ApiClient (une par serveur).
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ApiManager {
|
||||
clients: RwLock<HashMap<String, ApiClient>>,
|
||||
}
|
||||
|
||||
impl ApiManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
clients: RwLock::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère ou crée un client pour une base_url donnée.
|
||||
pub fn get_or_create_client(&self, base_url: String) -> ApiClient {
|
||||
let mut clients = self.clients.write();
|
||||
if let Some(client) = clients.get(&base_url) {
|
||||
client.clone()
|
||||
} else {
|
||||
let client = ApiClient::new(base_url.clone());
|
||||
clients.insert(base_url, client.clone());
|
||||
client
|
||||
}
|
||||
}
|
||||
|
||||
/// Supprime un client.
|
||||
pub fn remove_client(&self, base_url: &str) {
|
||||
let mut clients = self.clients.write();
|
||||
clients.remove(base_url);
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod ox_speak_app;
|
||||
pub mod ox_speak_app;
|
||||
pub mod state;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::config::ConfigManager;
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub struct OxSpeakApp {
|
||||
tauri_handle: AppHandle,
|
||||
@@ -7,17 +7,7 @@ pub struct OxSpeakApp {
|
||||
}
|
||||
|
||||
impl OxSpeakApp {
|
||||
pub async fn new(tauri_handle: AppHandle) -> Self {
|
||||
// Chargement de la configuration
|
||||
let config_dir = tauri_handle
|
||||
.path()
|
||||
.app_config_dir()
|
||||
.expect("Failed to get app config dir");
|
||||
|
||||
let config = ConfigManager::load(config_dir)
|
||||
.await
|
||||
.expect("Failed to load config");
|
||||
|
||||
pub async fn new(tauri_handle: AppHandle, config: ConfigManager) -> Self {
|
||||
Self {
|
||||
tauri_handle,
|
||||
config,
|
||||
|
||||
19
src-tauri/src/app/state.rs
Normal file
19
src-tauri/src/app/state.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use crate::api::state::ApiManager;
|
||||
use crate::config::ConfigManager;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// État global de l'application.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AppState {
|
||||
pub api: ApiManager,
|
||||
pub config: OnceLock<ConfigManager>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
api: ApiManager::new(),
|
||||
config: OnceLock::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src-tauri/src/config/commands.rs
Normal file
21
src-tauri/src/config/commands.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use crate::app::state::AppState;
|
||||
use crate::config::config::ConfigTree;
|
||||
use tauri::{command, State};
|
||||
|
||||
#[command]
|
||||
pub async fn config_get(state: State<'_, AppState>) -> Result<ConfigTree, String> {
|
||||
let config = state.config.get().ok_or("Config non initialisée")?;
|
||||
Ok(config.get_config())
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn config_update(
|
||||
state: State<'_, AppState>,
|
||||
new_config: ConfigTree,
|
||||
) -> Result<(), String> {
|
||||
let config = state.config.get().ok_or("Config non initialisée")?;
|
||||
config
|
||||
.update_config(new_config)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
@@ -5,35 +5,37 @@ use ssh_key::PrivateKey;
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use tokio::fs;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConfigManager {
|
||||
tree_config: Arc<Mutex<ConfigTree>>, // On garanti l'unicité avec un Mutex
|
||||
path: PathBuf,
|
||||
app_handle: Option<AppHandle>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
struct ConfigTree {
|
||||
pub struct ConfigTree {
|
||||
pub servers: Vec<ConfigServer>,
|
||||
pub identities: Vec<IdentityConfig>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
struct ConfigServer {
|
||||
pub struct ConfigServer {
|
||||
pub adresse: String,
|
||||
pub identity: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum IdentityMode {
|
||||
pub enum IdentityMode {
|
||||
PrivateKeyPath,
|
||||
PrivateKeyBase64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
struct IdentityConfig {
|
||||
pub struct IdentityConfig {
|
||||
pub id: String,
|
||||
pub username: String,
|
||||
pub private_key: String,
|
||||
@@ -41,7 +43,26 @@ struct IdentityConfig {
|
||||
}
|
||||
|
||||
impl ConfigManager {
|
||||
pub async fn load(config_dir: PathBuf) -> Result<Self, std::io::Error> {
|
||||
pub fn get_config(&self) -> ConfigTree {
|
||||
self.tree_config.lock().clone()
|
||||
}
|
||||
|
||||
pub async fn update_config(&self, new_config: ConfigTree) -> Result<(), std::io::Error> {
|
||||
{
|
||||
let mut lock = self.tree_config.lock();
|
||||
*lock = new_config.clone();
|
||||
}
|
||||
self.save().await?;
|
||||
|
||||
// Notifier le front-end du changement
|
||||
if let Some(handle) = &self.app_handle {
|
||||
let _ = handle.emit("config-changed", new_config);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn load(config_dir: PathBuf, app_handle: AppHandle) -> Result<Self, std::io::Error> {
|
||||
let config_path = config_dir.join("config.toml");
|
||||
|
||||
if config_path.exists() && config_path.is_file() {
|
||||
@@ -50,14 +71,15 @@ impl ConfigManager {
|
||||
return Ok(Self {
|
||||
tree_config: Arc::new(Mutex::new(tree_config)),
|
||||
path: config_path,
|
||||
app_handle: Some(app_handle),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Self::create(config_dir).await
|
||||
Self::create(config_dir, app_handle).await
|
||||
}
|
||||
|
||||
async fn create(config_dir: PathBuf) -> Result<Self, std::io::Error> {
|
||||
async fn create(config_dir: PathBuf, app_handle: AppHandle) -> Result<Self, std::io::Error> {
|
||||
let config_path = config_dir.join("config.toml");
|
||||
|
||||
let tree_config = ConfigTree {
|
||||
@@ -68,6 +90,7 @@ impl ConfigManager {
|
||||
let config = ConfigManager {
|
||||
tree_config: Arc::new(Mutex::new(tree_config)),
|
||||
path: config_path,
|
||||
app_handle: Some(app_handle),
|
||||
};
|
||||
|
||||
config.save().await?;
|
||||
@@ -83,10 +106,11 @@ impl ConfigManager {
|
||||
}
|
||||
|
||||
// Lock le mutex pour lire tree_config
|
||||
let tree_config = self.tree_config.lock();
|
||||
let serialized = toml::to_string_pretty(&*tree_config)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
|
||||
drop(tree_config); // Libère le lock explicitement
|
||||
let serialized = {
|
||||
let tree_config = self.tree_config.lock();
|
||||
toml::to_string_pretty(&*tree_config)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?
|
||||
};
|
||||
|
||||
fs::write(&self.path, serialized).await?;
|
||||
Ok(())
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
mod config;
|
||||
pub mod commands;
|
||||
pub mod config;
|
||||
pub use config::ConfigManager;
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
// .expect("error while running tauri application");
|
||||
// }
|
||||
|
||||
mod app;
|
||||
pub mod tauri_app;
|
||||
mod utils;
|
||||
mod app;
|
||||
|
||||
pub mod api;
|
||||
mod config;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::api::commands::*;
|
||||
use crate::app::ox_speak_app::OxSpeakApp;
|
||||
use crate::app::state::AppState;
|
||||
use crate::config::commands::*;
|
||||
use crate::config::ConfigManager;
|
||||
use std::sync::Arc;
|
||||
use tauri::{Manager, WindowEvent};
|
||||
|
||||
@@ -15,12 +19,43 @@ pub async fn run() {
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.manage(AppState::new())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
greet,
|
||||
api_login,
|
||||
api_claim_admin,
|
||||
api_ssh_challenge,
|
||||
api_server_list,
|
||||
api_server_create,
|
||||
api_category_list,
|
||||
api_category_create,
|
||||
api_channel_list,
|
||||
api_channel_create,
|
||||
api_message_list,
|
||||
api_message_create,
|
||||
api_user_list,
|
||||
config_get,
|
||||
config_update,
|
||||
])
|
||||
.setup(|app| {
|
||||
let handle = app.handle().clone();
|
||||
|
||||
// Démarrer le backend
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let ox_speak = Arc::new(OxSpeakApp::new(handle.clone()).await);
|
||||
let app_state = handle.state::<AppState>();
|
||||
let config_dir = handle
|
||||
.path()
|
||||
.app_config_dir()
|
||||
.expect("Failed to get app config dir");
|
||||
|
||||
let config = ConfigManager::load(config_dir, handle.clone())
|
||||
.await
|
||||
.expect("Failed to load config");
|
||||
|
||||
// Mettre la config dans AppState
|
||||
let _ = app_state.config.set(config.clone());
|
||||
|
||||
let ox_speak = Arc::new(OxSpeakApp::new(handle.clone(), config).await);
|
||||
handle.manage(ox_speak.clone());
|
||||
ox_speak.run().await;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user