This commit is contained in:
2026-03-20 18:07:16 +01:00
parent 45b0bf00ee
commit 1342123990
25 changed files with 2023 additions and 382 deletions

68
src/api/README.md Normal file
View File

@@ -0,0 +1,68 @@
# OxSpeak API Client (TypeScript)
Cette interface permet de communiquer avec les serveurs OxSpeak via Tauri (et plus tard via le Web).
## Installation
Assurez-vous d'avoir installé les dépendances Tauri dans votre projet frontend :
```bash
yarn add @tauri-apps/api
```
## Utilisation
L'API est conçue pour supporter plusieurs serveurs simultanément. Chaque serveur est représenté par une instance de
`IApiClient`.
### Créer un client
```typescript
import { createApiClient } from './api';
const client = createApiClient('http://localhost:8080');
```
### Authentification
```typescript
try {
const response = await client.login({
username: 'mon_pseudo',
password: 'mon_password'
});
console.log('Connecté ! Token:', response.token);
} catch (error) {
console.error('Erreur de connexion:', error);
}
```
### Lister les serveurs
```typescript
const servers = await client.getServerList();
servers.forEach(s => console.log(`Serveur: ${s.name} (${s.id})`));
```
### Créer un canal
```typescript
import { ChannelType } from './api';
const newChannel = await client.createChannel({
name: 'Général',
channel_type: 'text',
server_id: 'server-123'
});
```
## Architecture
L'interface est séparée en trois parties pour permettre une évolution vers une version Web sans casser le code
existant :
1. `types.ts` : Les définitions d'interfaces de données (identiques aux modèles Rust).
2. `client.ts` : L'interface `IApiClient` définissant le contrat.
3. `tauri-client.ts` : L'implémentation concrète utilisant Tauri `invoke`.
Le fichier `index.ts` expose une usine `createApiClient` qui instancie la bonne implémentation.

53
src/api/client.ts Normal file
View File

@@ -0,0 +1,53 @@
import type {
CategoryResponse,
ChannelResponse,
ClaimAdminRequest,
CreateCategoryRequest,
CreateChannelRequest,
CreateMessageRequest,
CreateServerRequest,
LoginRequest,
LoginResponse,
MessageResponse,
ServerResponse,
SshChallengeResponse,
UserResponse,
} from './types';
/**
* Interface générique pour l'API OxSpeak.
* Elle permet de découpler l'implémentation (Tauri, Web, etc.) de l'utilisation.
*/
export interface IApiClient {
readonly baseUrl: string;
// AUTH
login(req: LoginRequest): Promise<LoginResponse>;
claimAdmin(req: ClaimAdminRequest): Promise<void>;
sshChallenge(): Promise<SshChallengeResponse>;
// SERVER
getServerList(): Promise<ServerResponse[]>;
createServer(req: CreateServerRequest): Promise<ServerResponse>;
// CATEGORY
getCategoryList(serverId?: string): Promise<CategoryResponse[]>;
createCategory(req: CreateCategoryRequest): Promise<CategoryResponse>;
// CHANNEL
getChannelList(): Promise<ChannelResponse[]>;
createChannel(req: CreateChannelRequest): Promise<ChannelResponse>;
// MESSAGE
getMessageList(): Promise<MessageResponse[]>;
createMessage(req: CreateMessageRequest): Promise<MessageResponse>;
// USER
getUserList(): Promise<UserResponse[]>;
}

16
src/api/index.ts Normal file
View File

@@ -0,0 +1,16 @@
export * from './types';
export * from './client';
export * from './tauri-client';
import {TauriApiClient} from './tauri-client';
import type {IApiClient} from './client';
/**
* Usine pour créer des clients API.
* Plus tard, cette usine pourra retourner une implémentation Web ou Tauri
* selon l'environnement (isTauri).
*/
export function createApiClient(baseUrl: string): IApiClient {
// Pour le moment, on retourne systématiquement l'implémentation Tauri.
return new TauriApiClient(baseUrl);
}

83
src/api/tauri-client.ts Normal file
View File

@@ -0,0 +1,83 @@
import {invoke} from '@tauri-apps/api/core';
import type {IApiClient} from './client';
import type {
CategoryResponse,
ChannelResponse,
ClaimAdminRequest,
CreateCategoryRequest,
CreateChannelRequest,
CreateMessageRequest,
CreateServerRequest,
LoginRequest,
LoginResponse,
MessageResponse,
ServerResponse,
SshChallengeResponse,
UserResponse,
} from './types';
/**
* Implémentation Tauri de l'interface API.
* Cette version utilise 'invoke' pour communiquer avec le backend Rust.
*/
export class TauriApiClient implements IApiClient {
constructor(public readonly baseUrl: string) {
}
// AUTH
async login(req: LoginRequest): Promise<LoginResponse> {
return await invoke<LoginResponse>('api_login', {baseUrl: this.baseUrl, req});
}
async claimAdmin(req: ClaimAdminRequest): Promise<void> {
return await invoke<void>('api_claim_admin', {baseUrl: this.baseUrl, req});
}
async sshChallenge(): Promise<SshChallengeResponse> {
return await invoke<SshChallengeResponse>('api_ssh_challenge', {baseUrl: this.baseUrl});
}
// SERVER
async getServerList(): Promise<ServerResponse[]> {
return await invoke<ServerResponse[]>('api_server_list', {baseUrl: this.baseUrl});
}
async createServer(req: CreateServerRequest): Promise<ServerResponse> {
return await invoke<ServerResponse>('api_server_create', {baseUrl: this.baseUrl, req});
}
// CATEGORY
async getCategoryList(serverId?: string): Promise<CategoryResponse[]> {
return await invoke<CategoryResponse[]>('api_category_list', {
baseUrl: this.baseUrl,
serverId: serverId ?? null
});
}
async createCategory(req: CreateCategoryRequest): Promise<CategoryResponse> {
return await invoke<CategoryResponse>('api_category_create', {baseUrl: this.baseUrl, req});
}
// CHANNEL
async getChannelList(): Promise<ChannelResponse[]> {
return await invoke<ChannelResponse[]>('api_channel_list', {baseUrl: this.baseUrl});
}
async createChannel(req: CreateChannelRequest): Promise<ChannelResponse> {
return await invoke<ChannelResponse>('api_channel_create', {baseUrl: this.baseUrl, req});
}
// MESSAGE
async getMessageList(): Promise<MessageResponse[]> {
return await invoke<MessageResponse[]>('api_message_list', {baseUrl: this.baseUrl});
}
async createMessage(req: CreateMessageRequest): Promise<MessageResponse> {
return await invoke<MessageResponse>('api_message_create', {baseUrl: this.baseUrl, req});
}
// USER
async getUserList(): Promise<UserResponse[]> {
return await invoke<UserResponse[]>('api_user_list', {baseUrl: this.baseUrl});
}
}

100
src/api/types.ts Normal file
View File

@@ -0,0 +1,100 @@
// --- AUTH ---
export interface LoginRequest {
username: string;
password?: string;
}
export interface LoginResponse {
token: string;
username: string;
}
export interface ClaimAdminRequest {
token: string;
username: string;
password?: string;
}
export interface SshChallengeResponse {
challenge: string;
}
// --- SERVER ---
export interface CreateServerRequest {
name: string;
password?: string;
}
export interface ServerResponse {
id: string;
name: string;
created_at: string;
updated_at: string;
}
// --- CATEGORY ---
export interface CreateCategoryRequest {
server_id: string;
name: string;
}
export interface CategoryResponse {
id: string;
name: string;
}
// --- CHANNEL ---
export type ChannelType = 'text' | 'voice' | 'd_m';
export interface CreateChannelRequest {
channel_type: ChannelType;
category_id?: string;
name?: string;
position?: number;
server_id?: string;
}
export interface ChannelResponse {
id: string;
name?: string;
position: number;
channel_type: ChannelType;
category_id?: string;
}
// --- MESSAGE ---
export interface CreateMessageRequest {
channel_id: string;
content: string;
}
export interface MessageResponse {
id: string;
channel_id: string;
author_id: string;
content: string;
}
// --- USER ---
export interface CreateUserRequest {
username: string;
}
export interface UserResponse {
id: string;
username: string;
pub_key: string;
}
// --- ERRORS ---
export interface ApiError {
type: string;
data: string | { status: number, body: string };
}

70
src/config/README.md Normal file
View File

@@ -0,0 +1,70 @@
# Système de Configuration OxSpeak
Ce module permet de gérer la configuration globale de l'application (serveurs enregistrés et identités).
## Utilisation
Le système utilise une interface générique `IConfigClient` qui permet d'utiliser le même code pour Tauri et
une version Web (localStorage).
### Détection de l'environnement
La factory `createConfigClient()` détecte automatiquement si l'application tourne dans Tauri ou dans un navigateur
standard :
- **Tauri** : Utilise les commandes Rust et le stockage TOML local.
- **Web** : Utilise le `localStorage` du navigateur.
### Écoute des changements
Vous pouvez écouter les modifications de configuration de manière réactive :
```typescript
const configClient = createConfigClient();
// S'abonner aux changements
const unlisten = await configClient.onChanged((newConfig) => {
console.log('La config a changé !', newConfig);
});
// Plus tard, pour arrêter d'écouter
// unlisten();
```
### Exemple en TypeScript
```typescript
import {createConfigClient} from '@/config';
const configClient = createConfigClient();
// Récupérer la configuration
async function loadConfig() {
try {
const config = await configClient.get();
console.log('Serveurs configurés:', config.servers);
} catch (error) {
console.error('Erreur lors du chargement de la config:', error);
}
}
// Mettre à jour la configuration
async function addServer(name: string, identityId: string) {
const config = await configClient.get();
config.servers.push({
adresse: name,
identity: identityId
});
await configClient.update(config);
console.log('Serveur ajouté !');
}
```
### Structures de données
- **ConfigTree** : La racine de la configuration contenant les serveurs et les identités.
- **ConfigServer** : Définit un serveur (adresse et identité liée).
- **IdentityConfig** : Définit une identité utilisateur avec sa clé privée SSH.
- **IdentityMode** : `private_key_path` (chemin vers le fichier) ou `private_key_base64` (clé brute encodée).

80
src/config/client.ts Normal file
View File

@@ -0,0 +1,80 @@
import {invoke} from '@tauri-apps/api/core';
import {listen} from '@tauri-apps/api/event';
import type {ConfigTree} from './types';
export interface IConfigClient {
get(): Promise<ConfigTree>;
update(config: ConfigTree): Promise<void>;
/**
* Écoute les changements de configuration.
* Retourne une fonction pour arrêter l'écoute.
*/
onChanged(callback: (config: ConfigTree) => void): Promise<() => void>;
}
/**
* Implémentation Tauri pour le client de configuration.
*/
export class TauriConfigClient implements IConfigClient {
async get(): Promise<ConfigTree> {
return await invoke<ConfigTree>('config_get');
}
async update(config: ConfigTree): Promise<void> {
await invoke('config_update', {newConfig: config});
}
async onChanged(callback: (config: ConfigTree) => void): Promise<() => void> {
const unlisten = await listen<ConfigTree>('config-changed', (event) => {
callback(event.payload);
});
return unlisten;
}
}
/**
* Implémentation Web (LocalStorage) pour le client de configuration.
* Utile pour le développement hors-Tauri ou la future version Web.
*/
export class WebConfigClient implements IConfigClient {
private STORAGE_KEY = 'ox-speak-config';
async get(): Promise<ConfigTree> {
const data = localStorage.getItem(this.STORAGE_KEY);
if (data) {
try {
return JSON.parse(data);
} catch (e) {
console.error('Failed to parse config from localStorage', e);
}
}
return {servers: [], identities: []};
}
async update(config: ConfigTree): Promise<void> {
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(config));
// Simuler un événement de changement pour les autres composants de la même page
window.dispatchEvent(new CustomEvent('ox-config-changed', {detail: config}));
}
async onChanged(callback: (config: ConfigTree) => void): Promise<() => void> {
const handler = (event: any) => {
callback(event.detail);
};
window.addEventListener('ox-config-changed', handler);
return () => window.removeEventListener('ox-config-changed', handler);
}
}
/**
* Usine pour créer le client de configuration approprié.
*/
export function createConfigClient(): IConfigClient {
// Détection automatique de l'environnement
if ((window as any).__TAURI_INTERNALS__) {
return new TauriConfigClient();
}
return new WebConfigClient();
}

2
src/config/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from './types';
export * from './client';

18
src/config/types.ts Normal file
View File

@@ -0,0 +1,18 @@
export type IdentityMode = 'private_key_path' | 'private_key_base64';
export interface IdentityConfig {
id: string;
username: string;
private_key: string;
mode: IdentityMode;
}
export interface ConfigServer {
adresse: string;
identity: string;
}
export interface ConfigTree {
servers: ConfigServer[];
identities: IdentityConfig[];
}