From 03d49aa16a98a12fe599d3e77af14309589f6bdb Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Tue, 26 Aug 2025 13:21:08 +0200 Subject: [PATCH] fix: improve skill category syncing and data structure - Enhanced skill category syncing by ensuring id and name properties are included, boosting data integrity. - Cleaned up admin exports related to skill categories for better maintainability. --- .cursor/rules/api-routes.mdc | 32 ++++++ .cursor/rules/clients.mdc | 31 ++++++ .cursor/rules/components.mdc | 28 +++++ .cursor/rules/project-structure.mdc | 30 +++++ .cursor/rules/services.mdc | 42 +++++++ .cursorignore | 0 ARCHITECTURE.md | 162 +++++++++++++++++++++++++++ README.md | 166 ++++++++++++++++++++++++++++ 8 files changed, 491 insertions(+) create mode 100644 .cursor/rules/api-routes.mdc create mode 100644 .cursor/rules/clients.mdc create mode 100644 .cursor/rules/components.mdc create mode 100644 .cursor/rules/project-structure.mdc create mode 100644 .cursor/rules/services.mdc create mode 100644 .cursorignore create mode 100644 ARCHITECTURE.md create mode 100644 README.md diff --git a/.cursor/rules/api-routes.mdc b/.cursor/rules/api-routes.mdc new file mode 100644 index 0000000..36409d5 --- /dev/null +++ b/.cursor/rules/api-routes.mdc @@ -0,0 +1,32 @@ +--- +globs: app/api/**/*.ts +--- + +# API Routes Rules + +1. Routes MUST only use services for data access +2. Routes MUST handle input validation +3. Routes MUST return typed responses +4. Routes MUST use proper error handling + +Example of correct API route: + +```typescript +import { MyService } from "@/services/my-service"; + +export async function GET(request: Request) { + try { + const service = new MyService(pool); + const data = await service.getData(); + return Response.json(data); + } catch (error) { + return Response.error(); + } +} +``` + +❌ FORBIDDEN: + +- Direct database queries +- Business logic implementation +- Untyped responses diff --git a/.cursor/rules/clients.mdc b/.cursor/rules/clients.mdc new file mode 100644 index 0000000..3f35f89 --- /dev/null +++ b/.cursor/rules/clients.mdc @@ -0,0 +1,31 @@ +--- +globs: clients/**/*.ts +--- + +# HTTP Clients Rules + +1. All HTTP calls MUST be in clients/domains/ +2. Each domain MUST have its own client +3. Clients MUST use the base HTTP client +4. Clients MUST type their responses + +Example of correct client: + +```typescript +import { HttpClient } from "@/clients/base/http-client"; +import { MyData } from "@/lib/types"; + +export class MyClient { + constructor(private httpClient: HttpClient) {} + + async getData(): Promise { + return this.httpClient.get("/api/data"); + } +} +``` + +❌ FORBIDDEN: + +- Direct fetch calls +- Business logic in clients +- Untyped responses diff --git a/.cursor/rules/components.mdc b/.cursor/rules/components.mdc new file mode 100644 index 0000000..443b131 --- /dev/null +++ b/.cursor/rules/components.mdc @@ -0,0 +1,28 @@ +--- +globs: components/**/*.tsx +--- + +# Components Rules + +1. UI components MUST be in components/ui/ +2. Feature components MUST be in their feature folder +3. Components MUST use clients for data fetching +4. Components MUST be properly typed + +Example of correct component: + +```typescript +import { useMyClient } from '@/hooks/use-my-client'; + +export const MyComponent = () => { + const { data } = useMyClient(); + return
{data.map(item => )}
; +}; +``` + +❌ FORBIDDEN: + +- Direct service usage +- Direct database queries +- Direct fetch calls +- Untyped props diff --git a/.cursor/rules/project-structure.mdc b/.cursor/rules/project-structure.mdc new file mode 100644 index 0000000..7fc5a30 --- /dev/null +++ b/.cursor/rules/project-structure.mdc @@ -0,0 +1,30 @@ +--- +alwaysApply: true +--- + +# Project Structure Rules + +1. Backend: + - [services/](mdc:services/) - ALL database access + - [app/api/](mdc:app/api/) - API routes using services + +2. Frontend: + - [clients/](mdc:clients/) - HTTP clients + - [components/](mdc:components/) - React components + - [hooks/](mdc:hooks/) - React hooks + +3. Shared: + - [lib/](mdc:lib/) - Types and utilities + - [scripts/](mdc:scripts/) - Utility scripts + +Key Files: + +- [services/database.ts](mdc:services/database.ts) - Database pool +- [clients/base/http-client.ts](mdc:clients/base/http-client.ts) - Base HTTP client +- [lib/types.ts](mdc:lib/types.ts) - Shared types + +❌ FORBIDDEN: + +- Database access outside services/ +- HTTP calls outside clients/ +- Business logic in components/ diff --git a/.cursor/rules/services.mdc b/.cursor/rules/services.mdc new file mode 100644 index 0000000..02a8673 --- /dev/null +++ b/.cursor/rules/services.mdc @@ -0,0 +1,42 @@ +--- +globs: services/*.ts +--- + +# Services Rules + +1. Services MUST contain ALL PostgreSQL queries +2. Services are the ONLY layer allowed to communicate with the database +3. Each service MUST: + - Use the pool from [services/database.ts](mdc:services/database.ts) + - Implement proper transaction management + - Handle errors and logging + - Validate data before insertion + - Have a clear interface + +Example of correct service implementation: + +```typescript +export class MyService { + constructor(private pool: Pool) {} + + async myMethod(): Promise { + const client = await this.pool.connect(); + try { + await client.query("BEGIN"); + // ... queries + await client.query("COMMIT"); + } catch (error) { + await client.query("ROLLBACK"); + throw error; + } finally { + client.release(); + } + } +} +``` + +❌ FORBIDDEN: + +- Direct database queries outside services +- Raw SQL in API routes +- Database logic in components diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 0000000..e69de29 diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..235b1ba --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,162 @@ +# Architecture PeakSkills + +## Principes Fondamentaux + +### 1. Services (Backend) + +- ✅ TOUTES les requêtes PostgreSQL DOIVENT être dans les services/ +- ✅ Les services sont la SEULE couche autorisée à communiquer avec la base de données +- ✅ Chaque service doit avoir une interface claire +- ✅ Les services doivent gérer les transactions +- ✅ Les services doivent logger les erreurs +- ✅ Les services doivent valider les données avant insertion +- ❌ AUCUNE requête SQL en dehors des services + +### 2. Routes API (Backend) + +- ✅ Les routes API doivent UNIQUEMENT utiliser les services +- ✅ Les routes doivent gérer la validation des entrées +- ✅ Les routes doivent retourner des réponses typées +- ❌ AUCUNE requête SQL directe dans les routes + +### 3. Clients HTTP (Frontend) + +- ✅ Les appels HTTP doivent être dans clients/domains/ +- ✅ Chaque domaine a son propre client +- ✅ Les clients doivent typer leurs réponses +- ❌ AUCUNE logique métier dans les clients + +### 4. Components (Frontend) + +- ✅ UI réutilisable dans components/ui/ +- ✅ Composants feature dans leur dossier dédié +- ✅ Export via index.ts +- ❌ AUCUN appel direct aux services + +## Exemple d'Architecture + +### Service (Backend) + +```typescript +// services/skills-service.ts +export class SkillsService { + constructor(private pool: Pool) {} + + async getSkillsByCategory(categoryId: string): Promise { + const client = await this.pool.connect(); + try { + await client.query("BEGIN"); + + const result = await client.query( + ` + SELECT s.*, c.name as category_name + FROM skills s + JOIN skill_categories c ON s.category_id = c.id + WHERE c.id = $1 + `, + [categoryId] + ); + + await client.query("COMMIT"); + return result.rows; + } catch (error) { + await client.query("ROLLBACK"); + logger.error("Failed to get skills by category", { error, categoryId }); + throw new DatabaseError("Failed to get skills"); + } finally { + client.release(); + } + } +} +``` + +### Route API + +```typescript +// app/api/skills/[categoryId]/route.ts +import { SkillsService } from "@/services/skills-service"; + +export async function GET( + request: Request, + { params: { categoryId } }: { params: { categoryId: string } } +) { + try { + const skillsService = new SkillsService(pool); + const skills = await skillsService.getSkillsByCategory(categoryId); + return Response.json(skills); + } catch (error) { + return Response.error(); + } +} +``` + +### Client HTTP (Frontend) + +```typescript +// clients/domains/skills-client.ts +export class SkillsClient { + constructor(private httpClient: HttpClient) {} + + async getSkillsByCategory(categoryId: string): Promise { + return this.httpClient.get(`/api/skills/${categoryId}`); + } +} +``` + +### Composant (Frontend) + +```typescript +// components/skills/SkillList.tsx +export const SkillList = ({ categoryId }: { categoryId: string }) => { + const { skills, loading } = useSkills(categoryId); + + if (loading) return ; + + return ( +
+ {skills.map(skill => ( + + ))} +
+ ); +}; +``` + +### Hook (Frontend) + +```typescript +// hooks/useSkills.ts +export const useSkills = (categoryId: string) => { + const skillsClient = useSkillsClient(); + const [skills, setSkills] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + skillsClient + .getSkillsByCategory(categoryId) + .then(setSkills) + .finally(() => setLoading(false)); + }, [categoryId]); + + return { skills, loading }; +}; +``` + +## Validation de l'Architecture + +Pour s'assurer que l'architecture est respectée : + +1. **Review de Code** + - Vérifier qu'aucune requête SQL n'est en dehors des services + - Vérifier que les composants n'accèdent pas directement aux services + - Vérifier que les types sont correctement utilisés + +2. **Tests** + - Tests unitaires pour les services + - Tests d'intégration pour les routes API + - Tests de composants pour le frontend + +3. **Monitoring** + - Logger les erreurs de service + - Monitorer les performances des requêtes + - Tracer les appels API diff --git a/README.md b/README.md new file mode 100644 index 0000000..689b75f --- /dev/null +++ b/README.md @@ -0,0 +1,166 @@ +# PeakSkills - Architecture et Organisation du Code + +## Structure du Projet + +``` +peakSkills/ +├── app/ # Pages Next.js et routes API +├── clients/ # Clients HTTP pour les appels externes +│ ├── base/ # Client HTTP de base +│ └── domains/ # Clients par domaine métier +├── components/ # Composants React +│ ├── ui/ # Composants UI réutilisables +│ └── [feature]/ # Composants spécifiques aux features +├── services/ # Services métier +├── lib/ # Types et utilitaires partagés +├── hooks/ # Hooks React personnalisés +├── scripts/ # Scripts utilitaires +│ └── migrations/ # Scripts de migration +├── data/ # Données statiques +└── styles/ # Styles globaux +``` + +## Règles de Structure + +### 1. Clients HTTP (`clients/`) + +- Toute la logique d'appels HTTP doit être encapsulée dans des clients dédiés +- Chaque domaine métier a son propre client (ex: `auth-client.ts`, `skills-client.ts`) +- Les clients utilisent le client de base (`http-client.ts`) pour les appels +- Les clients ne contiennent que la logique d'appel, pas de logique métier + +Exemple : + +```typescript +// clients/domains/skills-client.ts +export class SkillsClient { + constructor(private httpClient: HttpClient) {} + + async getSkills(): Promise { + return this.httpClient.get("/api/skills"); + } +} +``` + +### 2. Services Backend (`services/`) + +- SEULE couche autorisée à faire des requêtes PostgreSQL +- Contiennent toute la logique métier et d'accès aux données +- Implémentent une interface claire +- Organisés par domaine métier +- Gèrent les transactions +- Valident les données +- Utilisent le pool de connexion de database.ts + +Exemple : + +```typescript +// services/skills-service.ts +export interface ISkillsService { + getSkillsByCategory(category: string): Promise; +} + +export class SkillsService implements ISkillsService { + constructor(private pool: Pool) {} + + async getSkillsByCategory(category: string): Promise { + const query = ` + SELECT s.*, c.name as category_name + FROM skills s + JOIN skill_categories c ON s.category_id = c.id + WHERE c.id = $1 + `; + + const result = await this.pool.query(query, [category]); + return result.rows; + } +} +``` + +### 3. Composants (`components/`) + +- Composants UI réutilisables dans `ui/` +- Composants spécifiques dans leur dossier feature +- Export via `index.ts` +- Utilisation de TypeScript (`.tsx`) + +### 4. Pages (`app/`) + +- Structure selon le routing Next.js +- Utilisation des composants +- Pas de logique métier directe + +### 5. Types (`lib/`) + +- Types partagés dans `types.ts` +- Types spécifiques dans `[domain]-types.ts` +- Interfaces commencent par "I" + +## Bonnes Pratiques + +1. **Séparation des Responsabilités** + - Les composants gèrent l'UI + - Les services gèrent la logique métier + - Les clients gèrent les appels HTTP + +2. **Typage** + - Tout doit être typé + - Utiliser des interfaces pour les contrats + - Éviter `any` + +3. **Organisation du Code** + - Un fichier = une responsabilité + - Export via `index.ts` + - Documentation en français + +4. **Tests** + - Tests unitaires à côté du code + - Tests d'intégration dans `__tests__` + - Mocks dans `__mocks__` + +## Patterns à Éviter + +❌ Ne pas mettre de logique métier dans les composants +❌ Ne pas faire d'appels HTTP directs +❌ Ne pas dupliquer les types +❌ Ne pas mélanger les responsabilités + +## Exemples + +### ✅ Bon Pattern + +```typescript +// components/skills/SkillList.tsx +export const SkillList = () => { + const { skills } = useSkills(); // Hook personnalisé + return
{skills.map(skill => )}
; +}; + +// hooks/useSkills.ts +export const useSkills = () => { + const skillsService = useSkillsService(); + const [skills, setSkills] = useState([]); + + useEffect(() => { + skillsService.getSkills().then(setSkills); + }, []); + + return { skills }; +}; +``` + +### ❌ Mauvais Pattern + +```typescript +// components/skills/SkillList.tsx +export const SkillList = () => { + const [skills, setSkills] = useState([]); + + useEffect(() => { + // ❌ Appel HTTP direct dans le composant + fetch('/api/skills').then(res => res.json()).then(setSkills); + }, []); + + return
{skills.map(skill => )}
; +}; +```