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:
32
.cursor/rules/api-routes.mdc
Normal file
32
.cursor/rules/api-routes.mdc
Normal 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
31
.cursor/rules/clients.mdc
Normal 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
|
||||||
28
.cursor/rules/components.mdc
Normal file
28
.cursor/rules/components.mdc
Normal 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
|
||||||
30
.cursor/rules/project-structure.mdc
Normal file
30
.cursor/rules/project-structure.mdc
Normal 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/
|
||||||
42
.cursor/rules/services.mdc
Normal file
42
.cursor/rules/services.mdc
Normal 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
0
.cursorignore
Normal file
162
ARCHITECTURE.md
Normal file
162
ARCHITECTURE.md
Normal 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
166
README.md
Normal 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>;
|
||||||
|
};
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user