refactor: revew all design of services, clients, deadcode, ...
This commit is contained in:
115
clients/README.md
Normal file
115
clients/README.md
Normal 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
|
||||
69
clients/base/http-client.ts
Normal file
69
clients/base/http-client.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
86
clients/domains/admin-client.ts
Normal file
86
clients/domains/admin-client.ts
Normal 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}`);
|
||||
}
|
||||
}
|
||||
33
clients/domains/auth-client.ts
Normal file
33
clients/domains/auth-client.ts
Normal 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");
|
||||
}
|
||||
}
|
||||
26
clients/domains/skills-client.ts
Normal file
26
clients/domains/skills-client.ts
Normal 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
14
clients/index.ts
Normal 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();
|
||||
Reference in New Issue
Block a user