init
This commit is contained in:
68
src/api/README.md
Normal file
68
src/api/README.md
Normal 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
53
src/api/client.ts
Normal 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
16
src/api/index.ts
Normal 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
83
src/api/tauri-client.ts
Normal 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
100
src/api/types.ts
Normal 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
70
src/config/README.md
Normal 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
80
src/config/client.ts
Normal 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
2
src/config/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './types';
|
||||
export * from './client';
|
||||
18
src/config/types.ts
Normal file
18
src/config/types.ts
Normal 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[];
|
||||
}
|
||||
Reference in New Issue
Block a user