refactor: revew all design of services, clients, deadcode, ...
This commit is contained in:
113
lib/README.md
Normal file
113
lib/README.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Server-Side Utilities Architecture
|
||||
|
||||
Cette architecture respecte les principes SOLID en séparant clairement les responsabilités côté serveur et en évitant le code mort.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
lib/
|
||||
├── evaluation-utils.ts # Utilitaires pour les évaluations
|
||||
├── evaluation-actions.ts # Actions d'évaluation
|
||||
├── score-utils.ts # Utilitaires pour les scores
|
||||
├── skill-file-loader.ts # Chargement des fichiers de skills
|
||||
├── types.ts # Types TypeScript généraux
|
||||
├── admin-types.ts # Types pour l'administration
|
||||
├── utils.ts # Utilitaires généraux
|
||||
├── category-icons.ts # Icônes des catégories
|
||||
├── tech-colors.ts # Couleurs des technologies
|
||||
├── pattern-colors.ts # Couleurs des patterns
|
||||
└── README.md # Ce fichier
|
||||
```
|
||||
|
||||
## Responsabilités
|
||||
|
||||
### evaluation-utils.ts
|
||||
|
||||
- **Utilitaires** pour les évaluations côté client
|
||||
- **Génération de données** pour les graphiques radar
|
||||
|
||||
### evaluation-actions.ts
|
||||
|
||||
- **Actions d'évaluation** côté serveur
|
||||
- **Gestion des profils** utilisateur
|
||||
|
||||
### score-utils.ts
|
||||
|
||||
- **Calcul des scores** et niveaux de compétences
|
||||
- **Logique métier** pour les évaluations
|
||||
|
||||
### skill-file-loader.ts
|
||||
|
||||
- **Chargement des fichiers** de compétences depuis le système de fichiers
|
||||
- **Parsing des données** JSON
|
||||
|
||||
### types.ts
|
||||
|
||||
- **Types TypeScript** généraux de l'application
|
||||
- **Interfaces** pour les entités principales
|
||||
|
||||
### admin-types.ts
|
||||
|
||||
- **Types pour l'administration** et les statistiques
|
||||
- **Interfaces** pour TeamMember, TeamStats, DirectionStats
|
||||
|
||||
## Services d'authentification
|
||||
|
||||
### AuthClient (client-side uniquement)
|
||||
|
||||
- **`login()`** - Authentification côté client
|
||||
- **`getCurrentUser()`** - Récupération utilisateur côté client
|
||||
- **`logout()`** - Déconnexion côté client
|
||||
|
||||
### AuthService (server-side uniquement)
|
||||
|
||||
- **`getUserUuidFromCookie()`** - Récupère l'UUID depuis le cookie côté serveur
|
||||
- **`isUserAuthenticated()`** - Vérifie l'authentification côté serveur
|
||||
|
||||
## Séparation client/serveur
|
||||
|
||||
- **`clients/domains/auth-client.ts`** - Côté client uniquement (React components, hooks)
|
||||
- **`services/auth-service.ts`** - Côté serveur uniquement (API routes, pages)
|
||||
- **Pas de duplication** entre les deux services
|
||||
|
||||
## Utilisation
|
||||
|
||||
### Import direct des services
|
||||
|
||||
```typescript
|
||||
import { AuthService } from "@/services";
|
||||
import { SkillsService, TeamsService } from "@/services";
|
||||
import { evaluationService } from "@/services/evaluation-service";
|
||||
```
|
||||
|
||||
### Utilisation dans les pages
|
||||
|
||||
```typescript
|
||||
export default async function HomePage() {
|
||||
const userUuid = await AuthService.getUserUuidFromCookie();
|
||||
|
||||
if (!userUuid) {
|
||||
redirect("/login");
|
||||
}
|
||||
|
||||
const [userEvaluation, skillCategories, teams] = await Promise.all([
|
||||
evaluationService.getServerUserEvaluation(userUuid!),
|
||||
SkillsService.getSkillCategories(),
|
||||
TeamsService.getTeams(),
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
## Avantages
|
||||
|
||||
- **Séparation claire** : Chaque fichier a une seule responsabilité
|
||||
- **Pas de code mort** : Utilisation directe des services existants
|
||||
- **Maintenabilité** : Code organisé et facile à comprendre
|
||||
- **Réutilisabilité** : Fonctions modulaires et indépendantes
|
||||
- **Testabilité** : Chaque module peut être testé séparément
|
||||
- **Évolutivité** : Facile d'ajouter de nouvelles fonctionnalités
|
||||
- **Architecture logique** : L'authentification est dans les services d'auth
|
||||
- **Séparation client/serveur** : Pas de confusion entre les deux environnements
|
||||
- **Noms cohérents** : AuthService pour le serveur, AuthClient pour le client
|
||||
- **Imports directs** : Plus de wrapper inutile, appel direct des services
|
||||
- **Simplicité** : Architecture claire et directe
|
||||
35
lib/admin-types.ts
Normal file
35
lib/admin-types.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
// Types pour l'administration et les statistiques
|
||||
|
||||
export interface TeamMember {
|
||||
uuid: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
skills: Array<{
|
||||
skillId: string;
|
||||
skillName: string;
|
||||
category: string;
|
||||
level: number;
|
||||
canMentor: boolean;
|
||||
wantsToLearn: boolean;
|
||||
}>;
|
||||
joinDate: string;
|
||||
}
|
||||
|
||||
export interface TeamStats {
|
||||
teamId: string;
|
||||
teamName: string;
|
||||
direction: string;
|
||||
totalMembers: number;
|
||||
averageSkillLevel: number;
|
||||
topSkills: Array<{ skillName: string; averageLevel: number; icon?: string }>;
|
||||
skillCoverage: number; // Percentage of skills evaluated
|
||||
members: TeamMember[];
|
||||
}
|
||||
|
||||
export interface DirectionStats {
|
||||
direction: string;
|
||||
teams: TeamStats[];
|
||||
totalMembers: number;
|
||||
averageSkillLevel: number;
|
||||
topCategories: Array<{ category: string; averageLevel: number }>;
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { UserProfile } from "./types";
|
||||
|
||||
/**
|
||||
* Service d'authentification côté client
|
||||
*/
|
||||
export class AuthService {
|
||||
/**
|
||||
* Authentifie un utilisateur et créé le cookie
|
||||
*/
|
||||
static async login(
|
||||
profile: UserProfile
|
||||
): Promise<{ user: UserProfile & { uuid: string }; userUuid: string }> {
|
||||
const response = await fetch("/api/auth", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(profile),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to authenticate user");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère l'utilisateur actuel depuis le cookie
|
||||
*/
|
||||
static async getCurrentUser(): Promise<UserProfile | null> {
|
||||
try {
|
||||
const response = await fetch("/api/auth", {
|
||||
method: "GET",
|
||||
credentials: "same-origin",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.user;
|
||||
} catch (error) {
|
||||
console.error("Failed to get current user:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Déconnecte l'utilisateur (supprime le cookie)
|
||||
*/
|
||||
static async logout(): Promise<void> {
|
||||
const response = await fetch("/api/auth", {
|
||||
method: "DELETE",
|
||||
credentials: "same-origin",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to logout");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constantes pour les cookies
|
||||
*/
|
||||
export const COOKIE_NAME = "peakSkills_userId";
|
||||
export const COOKIE_MAX_AGE = 30 * 24 * 60 * 60; // 30 jours
|
||||
@@ -1,36 +0,0 @@
|
||||
import { SkillCategory, Team } from "./types";
|
||||
|
||||
export async function loadSkillCategories(): Promise<SkillCategory[]> {
|
||||
try {
|
||||
const response = await fetch("/api/skills");
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("Failed to load skill categories:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load skill categories from local files (fallback or development mode)
|
||||
* This is a client-side safe alternative that still uses the API
|
||||
* For server-side file loading, use loadSkillCategoriesFromFiles from skill-file-loader
|
||||
*/
|
||||
export async function loadSkillCategoriesFromAPI(): Promise<SkillCategory[]> {
|
||||
return loadSkillCategories();
|
||||
}
|
||||
|
||||
export async function loadTeams(): Promise<Team[]> {
|
||||
try {
|
||||
const response = await fetch("/api/teams");
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("Failed to load teams:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -160,8 +160,8 @@ export async function initializeEmptyEvaluation(
|
||||
try {
|
||||
// Simplement créer le profil via l'auth, pas besoin de créer une évaluation vide
|
||||
// Le backend créera automatiquement l'évaluation lors du premier accès
|
||||
const { AuthService } = await import("@/lib/auth-utils");
|
||||
await AuthService.login(profile);
|
||||
const { authClient } = await import("@/clients");
|
||||
await authClient.login(profile);
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize evaluation:", error);
|
||||
throw error;
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
UserEvaluation,
|
||||
SkillCategory,
|
||||
} from "./types";
|
||||
import { apiClient } from "../services/api-client";
|
||||
import { evaluationClient } from "../clients";
|
||||
|
||||
export function calculateCategoryScore(
|
||||
categoryEvaluation: CategoryEvaluation
|
||||
@@ -45,28 +45,6 @@ export function generateRadarData(
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveUserEvaluation(
|
||||
evaluation: UserEvaluation
|
||||
): Promise<void> {
|
||||
try {
|
||||
await apiClient.saveUserEvaluation(evaluation);
|
||||
} catch (error) {
|
||||
console.error("Failed to save user evaluation:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadUserEvaluation(
|
||||
profile: UserEvaluation["profile"]
|
||||
): Promise<UserEvaluation | null> {
|
||||
try {
|
||||
return await apiClient.loadUserEvaluation(profile);
|
||||
} catch (error) {
|
||||
console.error("Failed to load user evaluation:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function createEmptyEvaluation(
|
||||
categories: SkillCategory[]
|
||||
): CategoryEvaluation[] {
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
import { cookies } from "next/headers";
|
||||
import { COOKIE_NAME } from "./auth-utils";
|
||||
import { evaluationService } from "@/services/evaluation-service";
|
||||
import { TeamsService } from "@/services/teams-service";
|
||||
import { SkillsService } from "@/services/skills-service";
|
||||
import { SkillCategory, Team } from "./types";
|
||||
|
||||
/**
|
||||
* Récupère l'UUID utilisateur depuis le cookie côté serveur
|
||||
*/
|
||||
export async function getUserUuidFromCookie(): Promise<string | null> {
|
||||
const cookieStore = await cookies();
|
||||
const userUuidCookie = cookieStore.get("peakSkills_userId");
|
||||
|
||||
if (!userUuidCookie?.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return userUuidCookie.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère l'ID utilisateur depuis le cookie côté serveur (legacy)
|
||||
*/
|
||||
export async function getUserIdFromCookie(): Promise<number | null> {
|
||||
const cookieStore = await cookies();
|
||||
const userIdCookie = cookieStore.get("peakSkills_userId");
|
||||
|
||||
if (!userIdCookie?.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Essayer de parser comme number pour backward compatibility
|
||||
const userId = parseInt(userIdCookie.value);
|
||||
return isNaN(userId) ? null : userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère l'évaluation complète de l'utilisateur côté serveur
|
||||
*/
|
||||
export async function getServerUserEvaluation() {
|
||||
const userUuid = await getUserUuidFromCookie();
|
||||
|
||||
if (!userUuid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Charger directement l'évaluation par UUID
|
||||
const userEvaluation = await evaluationService.loadUserEvaluationByUuid(
|
||||
userUuid
|
||||
);
|
||||
|
||||
return userEvaluation;
|
||||
} catch (error) {
|
||||
console.error("Failed to get user evaluation:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge les catégories de compétences côté serveur depuis PostgreSQL
|
||||
*/
|
||||
export async function getServerSkillCategories(): Promise<SkillCategory[]> {
|
||||
try {
|
||||
return await SkillsService.getSkillCategories();
|
||||
} catch (error) {
|
||||
console.error("Failed to load skill categories:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge les équipes côté serveur depuis PostgreSQL
|
||||
*/
|
||||
export async function getServerTeams(): Promise<Team[]> {
|
||||
try {
|
||||
return await TeamsService.getTeams();
|
||||
} catch (error) {
|
||||
console.error("Failed to load teams:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie simplement si l'utilisateur est authentifié via le cookie
|
||||
*/
|
||||
export async function isUserAuthenticated(): Promise<boolean> {
|
||||
const userUuid = await getUserUuidFromCookie();
|
||||
return !!userUuid;
|
||||
}
|
||||
@@ -43,6 +43,7 @@ export interface UserProfile {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
teamId: string;
|
||||
uuid?: string;
|
||||
}
|
||||
|
||||
export interface SkillEvaluation {
|
||||
|
||||
Reference in New Issue
Block a user