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.
This commit is contained in:
Julien Froidefond
2025-08-26 13:21:08 +02:00
parent 9d11a725d7
commit 03d49aa16a
8 changed files with 491 additions and 0 deletions

View File

@@ -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

31
.cursor/rules/clients.mdc Normal file
View File

@@ -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<MyData[]> {
return this.httpClient.get("/api/data");
}
}
```
❌ FORBIDDEN:
- Direct fetch calls
- Business logic in clients
- Untyped responses

View File

@@ -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 <div>{data.map(item => <Item key={item.id} {...item} />)}</div>;
};
```
❌ FORBIDDEN:
- Direct service usage
- Direct database queries
- Direct fetch calls
- Untyped props

View File

@@ -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/

View File

@@ -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<Result> {
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

0
.cursorignore Normal file
View File

162
ARCHITECTURE.md Normal file
View File

@@ -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<Skill[]> {
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<Skill[]> {
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 <Spinner />;
return (
<div>
{skills.map(skill => (
<SkillCard key={skill.id} skill={skill} />
))}
</div>
);
};
```
### Hook (Frontend)
```typescript
// hooks/useSkills.ts
export const useSkills = (categoryId: string) => {
const skillsClient = useSkillsClient();
const [skills, setSkills] = useState<Skill[]>([]);
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

166
README.md Normal file
View File

@@ -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<Skill[]> {
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<Skill[]>;
}
export class SkillsService implements ISkillsService {
constructor(private pool: Pool) {}
async getSkillsByCategory(category: string): Promise<Skill[]> {
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 <div>{skills.map(skill => <SkillCard skill={skill} />)}</div>;
};
// hooks/useSkills.ts
export const useSkills = () => {
const skillsService = useSkillsService();
const [skills, setSkills] = useState<Skill[]>([]);
useEffect(() => {
skillsService.getSkills().then(setSkills);
}, []);
return { skills };
};
```
### ❌ Mauvais Pattern
```typescript
// components/skills/SkillList.tsx
export const SkillList = () => {
const [skills, setSkills] = useState<Skill[]>([]);
useEffect(() => {
// ❌ Appel HTTP direct dans le composant
fetch('/api/skills').then(res => res.json()).then(setSkills);
}, []);
return <div>{skills.map(skill => <SkillCard skill={skill} />)}</div>;
};
```