refactor: revew all design of services, clients, deadcode, ...

This commit is contained in:
Julien Froidefond
2025-08-24 22:03:15 +02:00
parent f4dcc89c11
commit 6fba622003
63 changed files with 969 additions and 1846 deletions

115
clients/README.md Normal file
View File

@@ -0,0 +1,115 @@
# API Clients Architecture
Cette architecture respecte les principes SOLID en séparant les responsabilités par domaine métier et en évitant le code mort.
## Structure
```
clients/
├── base/
│ └── http-client.ts # Classe de base avec logique HTTP commune
├── domains/
│ ├── evaluation-client.ts # Client pour les évaluations (lecture + modification)
│ ├── teams-client.ts # Client pour la gestion des équipes (lecture + CRUD)
│ ├── skills-client.ts # Client pour les compétences (lecture + création)
│ ├── auth-client.ts # Client pour l'authentification (login, logout, getCurrentUser)
│ └── admin-client.ts # Client pour la gestion admin (skills, teams, users)
├── index.ts # Exports publics de tous les clients
├── client.ts # Wrapper client-side sécurisé
└── README.md # Ce fichier
```
## Services associés
- **`services/auth-service.ts`** - Service d'authentification côté client (renommé depuis auth-utils)
- **`services/evaluation-service.ts`** - Service d'évaluation côté serveur
- **`services/teams-service.ts`** - Service des équipes côté serveur
- **`services/skills-service.ts`** - Service des compétences côté serveur
## Principes
- **Single Responsibility** : Chaque client gère un seul domaine métier
- **Open/Closed** : Facile d'étendre sans modifier le code existant
- **Liskov Substitution** : Tous les clients héritent de BaseHttpClient
- **Interface Segregation** : Chaque client expose uniquement ses méthodes
- **Dependency Inversion** : Dépend de l'abstraction BaseHttpClient
## Clients par responsabilité
### EvaluationClient
- **`loadUserEvaluation()`** - Chargement d'une évaluation utilisateur
- **`saveUserEvaluation()`** - Sauvegarde d'une évaluation
- **`updateSkillLevel()`** - Mise à jour du niveau d'une skill
- **`updateSkillMentorStatus()`** - Mise à jour du statut mentor
- **`updateSkillLearningStatus()`** - Mise à jour du statut d'apprentissage
- **`addSkillToEvaluation()`** - Ajout d'une skill à l'évaluation
- **`removeSkillFromEvaluation()`** - Suppression d'une skill
### SkillsClient
- **`loadSkillCategories()`** - Chargement des catégories de skills
- **`createSkill()`** - Création d'une nouvelle skill
### TeamsClient
- **`loadTeams()`** - Chargement des équipes
- **`createTeam()`** - Création d'une équipe
- **`updateTeam()`** - Mise à jour d'une équipe
- **`deleteTeam()`** - Suppression d'une équipe
### AuthClient
- **`login()`** - Authentification d'un utilisateur
- **`getCurrentUser()`** - Récupération de l'utilisateur actuel
- **`logout()`** - Déconnexion d'un utilisateur
### AdminClient
- **`getSkills()`** - Récupération de toutes les skills
- **`createSkill()`** - Création d'une nouvelle skill
- **`updateSkill()`** - Mise à jour d'une skill
- **`deleteSkill()`** - Suppression d'une skill
- **`getTeams()`** - Récupération de toutes les équipes
- **`createTeam()`** - Création d'une nouvelle équipe
- **`updateTeam()`** - Mise à jour d'une équipe
- **`deleteTeam()`** - Suppression d'une équipe
- **`deleteDirection()`** - Suppression d'une direction
- **`getTeamMembers()`** - Récupération des membres d'une équipe
- **`removeTeamMember()`** - Suppression d'un membre d'équipe
- **`deleteUser()`** - Suppression d'un utilisateur
## Utilisation
### Import direct
```typescript
import {
evaluationClient,
teamsClient,
skillsClient,
authClient,
adminClient,
} from "@/clients";
```
### Import client-side sécurisé
```typescript
import {
evaluationClient,
teamsClient,
skillsClient,
authClient,
adminClient,
} from "@/services/client";
```
## Avantages
- **Code mort supprimé** : Plus de méthodes dupliquées
- **Architecture simple** : Chaque client gère son domaine complet
- **Performance** : Seules les méthodes nécessaires sont importées
- **Maintenabilité** : Architecture claire et logique
- **Testabilité** : Chaque client peut être testé indépendamment
- **Séparation claire** : Client HTTP vs services métier

View File

@@ -0,0 +1,69 @@
export abstract class BaseHttpClient {
protected baseUrl: string;
constructor() {
this.baseUrl = process.env.NEXT_PUBLIC_API_URL || "/api/";
}
protected async request<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const url = `${this.baseUrl}${endpoint}`;
const defaultOptions: RequestInit = {
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
...options.headers,
},
...options,
};
try {
const response = await fetch(url, defaultOptions);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// Handle empty responses
if (
response.status === 204 ||
response.headers.get("content-length") === "0"
) {
return {} as T;
}
return await response.json();
} catch (error) {
console.error(`Request failed for ${endpoint}:`, error);
throw error;
}
}
protected async get<T>(endpoint: string): Promise<T> {
return this.request<T>(endpoint);
}
protected async post<T>(endpoint: string, data?: any): Promise<T> {
return this.request<T>(endpoint, {
method: "POST",
body: data ? JSON.stringify(data) : undefined,
});
}
protected async put<T>(endpoint: string, data?: any): Promise<T> {
return this.request<T>(endpoint, {
method: "PUT",
body: data ? JSON.stringify(data) : undefined,
});
}
protected async delete<T>(endpoint: string, data?: any): Promise<T> {
return this.request<T>(endpoint, {
method: "DELETE",
body: data ? JSON.stringify(data) : undefined,
});
}
}

View File

@@ -0,0 +1,86 @@
import { BaseHttpClient } from "../base/http-client";
export interface Skill {
id: string;
name: string;
description: string;
icon: string;
categoryId: string;
category: string;
usageCount: number;
}
export interface Team {
id: string;
name: string;
direction: string;
memberCount: number;
}
export interface TeamMember {
id: string;
firstName: string;
lastName: string;
fullName: string;
joinedAt: string;
}
export class AdminClient extends BaseHttpClient {
// Skills Management
async getSkills(): Promise<Skill[]> {
return await this.get<Skill[]>(`/admin/skills`);
}
async createSkill(
skillData: Omit<Skill, "id" | "usageCount">
): Promise<Skill> {
return await this.post<Skill>(`/admin/skills`, skillData);
}
async updateSkill(skillData: Skill): Promise<Skill> {
return await this.put<Skill>(`/admin/skills`, skillData);
}
async deleteSkill(skillId: string): Promise<void> {
await this.delete(`/admin/skills?id=${skillId}`);
}
// Teams Management
async getTeams(): Promise<Team[]> {
return await this.get<Team[]>(`/admin/teams`);
}
async createTeam(teamData: Omit<Team, "id" | "memberCount">): Promise<Team> {
return await this.post<Team>(`/admin/teams`, teamData);
}
async updateTeam(teamData: Team): Promise<Team> {
return await this.put<Team>(`/admin/teams`, teamData);
}
async deleteTeam(teamId: string): Promise<void> {
await this.delete(`/admin/teams?id=${teamId}`);
}
async deleteDirection(direction: string): Promise<void> {
await this.delete(
`/admin/teams?direction=${encodeURIComponent(direction)}`
);
}
// Team Members
async getTeamMembers(teamId: string): Promise<TeamMember[]> {
return await this.get<TeamMember[]>(`/admin/teams/${teamId}/members`);
}
async removeTeamMember(teamId: string, memberId: string): Promise<void> {
await this.delete(`/admin/teams/${teamId}/members`, {
memberId,
});
}
// User Management
async deleteUser(userId: string): Promise<void> {
await this.delete(`/admin/users/${userId}`);
}
}

View File

@@ -0,0 +1,33 @@
import { BaseHttpClient } from "../base/http-client";
import { UserProfile } from "../../lib/types";
export class AuthClient extends BaseHttpClient {
/**
* Authentifie un utilisateur et créé le cookie
*/
async login(
profile: UserProfile
): Promise<{ user: UserProfile & { uuid: string }; userUuid: string }> {
return await this.post("/auth", profile);
}
/**
* Récupère l'utilisateur actuel depuis le cookie
*/
async getCurrentUser(): Promise<UserProfile | null> {
try {
const response = await this.get<{ user: UserProfile }>("/auth");
return response.user;
} catch (error) {
console.error("Failed to get current user:", error);
return null;
}
}
/**
* Déconnecte l'utilisateur (supprime le cookie)
*/
async logout(): Promise<void> {
await this.delete("/auth");
}
}

View File

@@ -0,0 +1,26 @@
import { BaseHttpClient } from "../base/http-client";
import { SkillCategory } from "../../lib/types";
export class SkillsClient extends BaseHttpClient {
/**
* Crée une nouvelle skill
*/
async createSkill(
categoryId: string,
skill: {
id: string;
name: string;
description: string;
icon?: string;
links: string[];
}
): Promise<boolean> {
try {
await this.post(`/skills/${categoryId}`, skill);
return true;
} catch (error) {
console.error("Erreur lors de la création de la skill:", error);
return false;
}
}
}

14
clients/index.ts Normal file
View File

@@ -0,0 +1,14 @@
// Import all client classes first
import { SkillsClient } from "./domains/skills-client";
import { AuthClient } from "./domains/auth-client";
import { AdminClient } from "./domains/admin-client";
// Export all client classes
export { SkillsClient } from "./domains/skills-client";
export { AuthClient } from "./domains/auth-client";
export { AdminClient } from "./domains/admin-client";
// Create and export client instances
export const skillsClient = new SkillsClient();
export const authClient = new AuthClient();
export const adminClient = new AdminClient();