docs: remove detailed implementation plans for authentication and backend architecture

Deleted extensive implementation plan documents (`AUTH_IMPLEMENTATION_PLAN.md` and `BACKEND_IMPLEMENTATION_PLAN.md`) to streamline documentation efforts and prevent duplication with updated overall project guides.
This commit is contained in:
Mathis H (Avnyr) 2025-05-15 17:16:11 +02:00
parent 2035821e89
commit 7b6da2767e
8 changed files with 0 additions and 4356 deletions

View File

@ -1,890 +0,0 @@
# Plan d'Implémentation de l'Authentification
Ce document détaille le plan d'implémentation du système d'authentification pour l'application de création de groupes, basé sur les spécifications du cahier des charges.
## 1. Vue d'Ensemble
L'application utilisera OAuth 2.0 avec GitHub comme fournisseur d'identité, combiné avec une gestion de session basée sur JWT (JSON Web Tokens). Cette approche offre plusieurs avantages :
- Délégation de l'authentification à un service tiers sécurisé (GitHub)
- Pas besoin de gérer les mots de passe des utilisateurs
- Récupération des informations de profil (nom, avatar) depuis l'API GitHub
- Authentification stateless avec JWT pour une meilleure scalabilité
## 2. Flux d'Authentification
```mermaid
sequenceDiagram
participant User as Utilisateur
participant Frontend as Frontend (Next.js)
participant Backend as Backend (NestJS)
participant GitHub as GitHub OAuth
User->>Frontend: Clic sur "Se connecter avec GitHub"
Frontend->>Backend: Redirection vers /auth/github
Backend->>GitHub: Redirection vers GitHub OAuth
GitHub->>User: Demande d'autorisation
User->>GitHub: Accepte l'autorisation
GitHub->>Backend: Redirection avec code d'autorisation
Backend->>GitHub: Échange code contre token d'accès
GitHub->>Backend: Retourne token d'accès
Backend->>GitHub: Requête informations utilisateur
GitHub->>Backend: Retourne informations utilisateur
Backend->>Backend: Crée/Met à jour l'utilisateur en BDD
Backend->>Backend: Génère JWT (access + refresh tokens)
Backend->>Frontend: Redirection avec tokens JWT
Frontend->>Frontend: Stocke les tokens
Frontend->>User: Affiche interface authentifiée
```
## 3. Structure des Modules
### 3.1 Module d'Authentification
```typescript
// src/modules/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthController } from './controllers/auth.controller';
import { AuthService } from './services/auth.service';
import { GithubStrategy } from './strategies/github.strategy';
import { JwtStrategy } from './strategies/jwt.strategy';
import { JwtRefreshStrategy } from './strategies/jwt-refresh.strategy';
import { UsersModule } from '../users/users.module';
@Module({
imports: [
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
secret: configService.get<string>('JWT_ACCESS_SECRET'),
signOptions: {
expiresIn: configService.get<string>('JWT_ACCESS_EXPIRATION', '15m'),
},
}),
}),
UsersModule,
],
controllers: [AuthController],
providers: [AuthService, GithubStrategy, JwtStrategy, JwtRefreshStrategy],
exports: [AuthService],
})
export class AuthModule {}
```
### 3.2 Stratégies d'Authentification
#### 3.2.1 Stratégie GitHub
```typescript
// src/modules/auth/strategies/github.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-github2';
import { ConfigService } from '@nestjs/config';
import { AuthService } from '../services/auth.service';
@Injectable()
export class GithubStrategy extends PassportStrategy(Strategy, 'github') {
constructor(
private configService: ConfigService,
private authService: AuthService,
) {
super({
clientID: configService.get<string>('GITHUB_CLIENT_ID'),
clientSecret: configService.get<string>('GITHUB_CLIENT_SECRET'),
callbackURL: configService.get<string>('GITHUB_CALLBACK_URL'),
scope: ['read:user'],
});
}
async validate(accessToken: string, refreshToken: string, profile: any) {
const { id, displayName, photos } = profile;
const user = await this.authService.validateGithubUser({
githubId: id,
name: displayName || `user_${id}`,
avatar: photos?.[0]?.value,
});
return user;
}
}
```
#### 3.2.2 Stratégie JWT
```typescript
// src/modules/auth/strategies/jwt.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
import { UsersService } from '../../users/services/users.service';
import { JwtPayload } from '../interfaces/jwt-payload.interface';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(
private configService: ConfigService,
private usersService: UsersService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: configService.get<string>('JWT_ACCESS_SECRET'),
});
}
async validate(payload: JwtPayload) {
const { sub } = payload;
const user = await this.usersService.findById(sub);
if (!user) {
throw new UnauthorizedException('User not found');
}
return user;
}
}
```
#### 3.2.3 Stratégie JWT Refresh
```typescript
// src/modules/auth/strategies/jwt-refresh.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
import { Request } from 'express';
import { UsersService } from '../../users/services/users.service';
import { JwtPayload } from '../interfaces/jwt-payload.interface';
@Injectable()
export class JwtRefreshStrategy extends PassportStrategy(Strategy, 'jwt-refresh') {
constructor(
private configService: ConfigService,
private usersService: UsersService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: configService.get<string>('JWT_REFRESH_SECRET'),
passReqToCallback: true,
});
}
async validate(req: Request, payload: JwtPayload) {
const refreshToken = req.headers.authorization?.replace('Bearer ', '');
if (!refreshToken) {
throw new UnauthorizedException('Refresh token not found');
}
const { sub } = payload;
const user = await this.usersService.findById(sub);
if (!user) {
throw new UnauthorizedException('User not found');
}
return { ...user, refreshToken };
}
}
```
### 3.3 Service d'Authentification
```typescript
// src/modules/auth/services/auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { UsersService } from '../../users/services/users.service';
import { JwtPayload } from '../interfaces/jwt-payload.interface';
import { TokensResponse } from '../interfaces/tokens-response.interface';
import { GithubUserDto } from '../dto/github-user.dto';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
private configService: ConfigService,
) {}
async validateGithubUser(githubUserDto: GithubUserDto) {
const { githubId, name, avatar } = githubUserDto;
// Recherche de l'utilisateur par githubId
let user = await this.usersService.findByGithubId(githubId);
// Si l'utilisateur n'existe pas, on le crée
if (!user) {
user = await this.usersService.create({
githubId,
name,
avatar,
gdprTimestamp: new Date(),
});
} else {
// Mise à jour des informations de l'utilisateur
user = await this.usersService.update(user.id, {
name,
avatar,
});
}
return user;
}
async login(user: any): Promise<TokensResponse> {
const payload: JwtPayload = { sub: user.id };
const [accessToken, refreshToken] = await Promise.all([
this.generateAccessToken(payload),
this.generateRefreshToken(payload),
]);
return {
accessToken,
refreshToken,
expiresIn: this.configService.get<string>('JWT_ACCESS_EXPIRATION', '15m'),
};
}
async refreshTokens(userId: string, refreshToken: string): Promise<TokensResponse> {
// Vérification du refresh token (à implémenter avec une table de tokens révoqués)
const payload: JwtPayload = { sub: userId };
const [accessToken, newRefreshToken] = await Promise.all([
this.generateAccessToken(payload),
this.generateRefreshToken(payload),
]);
return {
accessToken,
refreshToken: newRefreshToken,
expiresIn: this.configService.get<string>('JWT_ACCESS_EXPIRATION', '15m'),
};
}
async logout(userId: string, refreshToken: string): Promise<void> {
// Ajouter le refresh token à la liste des tokens révoqués
// À implémenter avec une table de tokens révoqués
}
private async generateAccessToken(payload: JwtPayload): Promise<string> {
return this.jwtService.signAsync(payload, {
secret: this.configService.get<string>('JWT_ACCESS_SECRET'),
expiresIn: this.configService.get<string>('JWT_ACCESS_EXPIRATION', '15m'),
});
}
private async generateRefreshToken(payload: JwtPayload): Promise<string> {
return this.jwtService.signAsync(payload, {
secret: this.configService.get<string>('JWT_REFRESH_SECRET'),
expiresIn: this.configService.get<string>('JWT_REFRESH_EXPIRATION', '7d'),
});
}
}
```
### 3.4 Contrôleur d'Authentification
```typescript
// src/modules/auth/controllers/auth.controller.ts
import { Controller, Get, Post, UseGuards, Req, Res, Body, HttpCode } from '@nestjs/common';
import { Response } from 'express';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from '../services/auth.service';
import { JwtRefreshGuard } from '../guards/jwt-refresh.guard';
import { GetUser } from '../decorators/get-user.decorator';
import { RefreshTokenDto } from '../dto/refresh-token.dto';
import { Public } from '../decorators/public.decorator';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Public()
@Get('github')
@UseGuards(AuthGuard('github'))
githubAuth() {
// Cette route redirige vers GitHub pour l'authentification
}
@Public()
@Get('github/callback')
@UseGuards(AuthGuard('github'))
async githubAuthCallback(@Req() req, @Res() res: Response) {
const { accessToken, refreshToken } = await this.authService.login(req.user);
// Redirection vers le frontend avec les tokens
const redirectUrl = `${this.configService.get<string>('FRONTEND_URL')}/auth/callback?accessToken=${accessToken}&refreshToken=${refreshToken}`;
return res.redirect(redirectUrl);
}
@Public()
@Post('refresh')
@UseGuards(JwtRefreshGuard)
@HttpCode(200)
async refreshTokens(
@GetUser('id') userId: string,
@GetUser('refreshToken') refreshToken: string,
) {
return this.authService.refreshTokens(userId, refreshToken);
}
@Post('logout')
@HttpCode(200)
async logout(
@GetUser('id') userId: string,
@Body() refreshTokenDto: RefreshTokenDto,
) {
await this.authService.logout(userId, refreshTokenDto.refreshToken);
return { message: 'Logout successful' };
}
@Get('profile')
getProfile(@GetUser() user) {
return user;
}
}
```
### 3.5 Guards et Décorateurs
#### 3.5.1 Guard JWT
```typescript
// src/modules/auth/guards/jwt-auth.guard.ts
import { Injectable, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
return super.canActivate(context);
}
}
```
#### 3.5.2 Guard JWT Refresh
```typescript
// src/modules/auth/guards/jwt-refresh.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtRefreshGuard extends AuthGuard('jwt-refresh') {}
```
#### 3.5.3 Décorateur Public
```typescript
// src/modules/auth/decorators/public.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
```
#### 3.5.4 Décorateur GetUser
```typescript
// src/modules/auth/decorators/get-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const GetUser = createParamDecorator(
(data: string | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
},
);
```
### 3.6 Interfaces et DTOs
#### 3.6.1 Interface JwtPayload
```typescript
// src/modules/auth/interfaces/jwt-payload.interface.ts
export interface JwtPayload {
sub: string;
iat?: number;
exp?: number;
}
```
#### 3.6.2 Interface TokensResponse
```typescript
// src/modules/auth/interfaces/tokens-response.interface.ts
export interface TokensResponse {
accessToken: string;
refreshToken: string;
expiresIn: string;
}
```
#### 3.6.3 DTO GithubUser
```typescript
// src/modules/auth/dto/github-user.dto.ts
import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
export class GithubUserDto {
@IsString()
@IsNotEmpty()
githubId: string;
@IsString()
@IsNotEmpty()
name: string;
@IsString()
@IsOptional()
avatar?: string;
}
```
#### 3.6.4 DTO RefreshToken
```typescript
// src/modules/auth/dto/refresh-token.dto.ts
import { IsString, IsNotEmpty } from 'class-validator';
export class RefreshTokenDto {
@IsString()
@IsNotEmpty()
refreshToken: string;
}
```
## 4. Configuration Globale de l'Authentification
### 4.1 Configuration du Module App
```typescript
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { APP_GUARD } from '@nestjs/core';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './modules/auth/auth.module';
import { UsersModule } from './modules/users/users.module';
import { DatabaseModule } from './database/database.module';
import { JwtAuthGuard } from './modules/auth/guards/jwt-auth.guard';
import { validate } from './config/env.validation';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
validate,
}),
DatabaseModule,
AuthModule,
UsersModule,
],
controllers: [AppController],
providers: [
AppService,
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
],
})
export class AppModule {}
```
### 4.2 Configuration CORS dans main.ts
```typescript
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const configService = app.get(ConfigService);
// Configuration globale des pipes de validation
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
transform: true,
forbidNonWhitelisted: true,
}),
);
// Configuration CORS
app.enableCors({
origin: configService.get<string>('CORS_ORIGIN'),
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
});
// Préfixe global pour les routes API
app.setGlobalPrefix(configService.get<string>('API_PREFIX', 'api'));
const port = configService.get<number>('PORT', 3000);
await app.listen(port);
console.log(`Application is running on: http://localhost:${port}`);
}
bootstrap();
```
## 5. Sécurité et Bonnes Pratiques
### 5.1 Gestion des Tokens
- **Access Token** : Durée de vie courte (15 minutes) pour limiter les risques en cas de vol
- **Refresh Token** : Durée de vie plus longue (7 jours) pour permettre le rafraîchissement de l'access token
- Stockage sécurisé des tokens côté client (localStorage pour l'access token, httpOnly cookie pour le refresh token dans une implémentation plus sécurisée)
- Révocation des tokens en cas de déconnexion ou de suspicion de compromission
### 5.2 Protection contre les Attaques Courantes
- **CSRF** : Utilisation de tokens anti-CSRF pour les opérations sensibles
- **XSS** : Échappement des données utilisateur, utilisation de Content Security Policy
- **Injection** : Validation des entrées avec class-validator, utilisation de paramètres préparés avec DrizzleORM
- **Rate Limiting** : Limitation du nombre de requêtes d'authentification pour prévenir les attaques par force brute
### 5.3 Conformité RGPD
- Enregistrement du timestamp d'acceptation RGPD lors de la création du compte
- Possibilité d'exporter les données personnelles
- Possibilité de supprimer le compte et toutes les données associées
- Renouvellement du consentement tous les 13 mois
## 6. Intégration avec le Frontend
### 6.1 Flux d'Authentification côté Frontend
```typescript
// Exemple de code pour le frontend (Next.js)
// app/auth/github/page.tsx
'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { API_URL } from '@/lib/constants';
export default function GitHubAuthPage() {
const router = useRouter();
useEffect(() => {
// Redirection vers l'endpoint d'authentification GitHub du backend
window.location.href = `${API_URL}/auth/github`;
}, []);
return <div>Redirection vers GitHub pour authentification...</div>;
}
```
```typescript
// app/auth/callback/page.tsx
'use client';
import { useEffect } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { useAuth } from '@/hooks/useAuth';
export default function AuthCallbackPage() {
const router = useRouter();
const searchParams = useSearchParams();
const { login } = useAuth();
useEffect(() => {
const accessToken = searchParams.get('accessToken');
const refreshToken = searchParams.get('refreshToken');
if (accessToken && refreshToken) {
// Stockage des tokens
login(accessToken, refreshToken);
// Redirection vers le dashboard
router.push('/dashboard');
} else {
// Erreur d'authentification
router.push('/auth/error');
}
}, [searchParams, login, router]);
return <div>Finalisation de l'authentification...</div>;
}
```
### 6.2 Hook d'Authentification
```typescript
// hooks/useAuth.tsx
'use client';
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { jwtDecode } from 'jwt-decode';
import { API_URL } from '@/lib/constants';
interface AuthContextType {
isAuthenticated: boolean;
user: any | null;
login: (accessToken: string, refreshToken: string) => void;
logout: () => Promise<void>;
refreshAccessToken: () => Promise<string | null>;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: ReactNode }) {
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
const [user, setUser] = useState<any | null>(null);
const [accessToken, setAccessToken] = useState<string | null>(null);
const [refreshToken, setRefreshToken] = useState<string | null>(null);
useEffect(() => {
// Récupération des tokens depuis le localStorage au chargement
const storedAccessToken = localStorage.getItem('accessToken');
const storedRefreshToken = localStorage.getItem('refreshToken');
if (storedAccessToken && storedRefreshToken) {
try {
// Vérification de l'expiration du token
const decoded = jwtDecode(storedAccessToken);
const currentTime = Date.now() / 1000;
if (decoded.exp && decoded.exp > currentTime) {
setAccessToken(storedAccessToken);
setRefreshToken(storedRefreshToken);
setIsAuthenticated(true);
fetchUserProfile(storedAccessToken);
} else {
// Token expiré, tentative de rafraîchissement
refreshTokens(storedRefreshToken);
}
} catch (error) {
// Token invalide, nettoyage
clearTokens();
}
}
}, []);
const fetchUserProfile = async (token: string) => {
try {
const response = await fetch(`${API_URL}/auth/profile`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (response.ok) {
const userData = await response.json();
setUser(userData);
} else {
throw new Error('Failed to fetch user profile');
}
} catch (error) {
console.error('Error fetching user profile:', error);
}
};
const refreshTokens = async (token: string) => {
try {
const response = await fetch(`${API_URL}/auth/refresh`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
});
if (response.ok) {
const { accessToken: newAccessToken, refreshToken: newRefreshToken } = await response.json();
setAccessToken(newAccessToken);
setRefreshToken(newRefreshToken);
setIsAuthenticated(true);
localStorage.setItem('accessToken', newAccessToken);
localStorage.setItem('refreshToken', newRefreshToken);
fetchUserProfile(newAccessToken);
return newAccessToken;
} else {
throw new Error('Failed to refresh tokens');
}
} catch (error) {
console.error('Error refreshing tokens:', error);
clearTokens();
return null;
}
};
const login = (newAccessToken: string, newRefreshToken: string) => {
setAccessToken(newAccessToken);
setRefreshToken(newRefreshToken);
setIsAuthenticated(true);
localStorage.setItem('accessToken', newAccessToken);
localStorage.setItem('refreshToken', newRefreshToken);
fetchUserProfile(newAccessToken);
};
const logout = async () => {
if (refreshToken) {
try {
await fetch(`${API_URL}/auth/logout`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ refreshToken }),
});
} catch (error) {
console.error('Error during logout:', error);
}
}
clearTokens();
};
const clearTokens = () => {
setAccessToken(null);
setRefreshToken(null);
setIsAuthenticated(false);
setUser(null);
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
};
const refreshAccessToken = async () => {
if (refreshToken) {
return refreshTokens(refreshToken);
}
return null;
};
return (
<AuthContext.Provider
value={{
isAuthenticated,
user,
login,
logout,
refreshAccessToken,
}}
>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}
```
### 6.3 Client API avec Gestion des Tokens
```typescript
// lib/api-client.ts
import { useAuth } from '@/hooks/useAuth';
import { API_URL } from './constants';
export function useApiClient() {
const { isAuthenticated, refreshAccessToken } = useAuth();
const fetchWithAuth = async (endpoint: string, options: RequestInit = {}) => {
if (!isAuthenticated) {
throw new Error('User not authenticated');
}
const accessToken = localStorage.getItem('accessToken');
const headers = {
...options.headers,
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
};
try {
const response = await fetch(`${API_URL}${endpoint}`, {
...options,
headers,
});
// Si le token est expiré (401), on tente de le rafraîchir
if (response.status === 401) {
const newAccessToken = await refreshAccessToken();
if (newAccessToken) {
// Nouvelle tentative avec le token rafraîchi
return fetch(`${API_URL}${endpoint}`, {
...options,
headers: {
...options.headers,
'Content-Type': 'application/json',
Authorization: `Bearer ${newAccessToken}`,
},
});
} else {
throw new Error('Failed to refresh access token');
}
}
return response;
} catch (error) {
console.error('API request failed:', error);
throw error;
}
};
return { fetchWithAuth };
}
```
## 7. Tests
### 7.1 Tests Unitaires
```typescript
// src/modules/auth/services/auth.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { AuthService } from './auth.service';
import { UsersService } from

View File

@ -1,223 +0,0 @@
# Plan d'Implémentation du Backend
Ce document détaille le plan d'implémentation du backend pour l'application de création de groupes, basé sur les spécifications du cahier des charges.
## 1. Structure des Dossiers
```
backend/
├── src/
│ ├── main.ts # Point d'entrée de l'application
│ ├── app.module.ts # Module principal
│ ├── config/ # Configuration de l'application
│ │ ├── app.config.ts # Configuration générale
│ │ ├── database.config.ts # Configuration de la base de données
│ │ ├── auth.config.ts # Configuration de l'authentification
│ │ └── env.validation.ts # Validation des variables d'environnement
│ ├── common/ # Utilitaires partagés
│ │ ├── decorators/ # Décorateurs personnalisés
│ │ ├── filters/ # Filtres d'exception
│ │ ├── guards/ # Guards d'authentification et d'autorisation
│ │ ├── interceptors/ # Intercepteurs
│ │ ├── pipes/ # Pipes de validation
│ │ └── utils/ # Fonctions utilitaires
│ ├── modules/ # Modules fonctionnels
│ │ ├── auth/ # Module d'authentification
│ │ │ ├── controllers/ # Contrôleurs d'authentification
│ │ │ ├── services/ # Services d'authentification
│ │ │ ├── guards/ # Guards spécifiques à l'authentification
│ │ │ ├── strategies/ # Stratégies d'authentification (GitHub OAuth)
│ │ │ └── auth.module.ts # Module d'authentification
│ │ ├── users/ # Module de gestion des utilisateurs
│ │ │ ├── controllers/ # Contrôleurs utilisateurs
│ │ │ ├── services/ # Services utilisateurs
│ │ │ ├── dto/ # Objets de transfert de données
│ │ │ └── users.module.ts # Module utilisateurs
│ │ ├── projects/ # Module de gestion des projets
│ │ │ ├── controllers/ # Contrôleurs projets
│ │ │ ├── services/ # Services projets
│ │ │ ├── dto/ # Objets de transfert de données
│ │ │ └── projects.module.ts # Module projets
│ │ ├── persons/ # Module de gestion des personnes
│ │ │ ├── controllers/ # Contrôleurs personnes
│ │ │ ├── services/ # Services personnes
│ │ │ ├── dto/ # Objets de transfert de données
│ │ │ └── persons.module.ts # Module personnes
│ │ ├── groups/ # Module de gestion des groupes
│ │ │ ├── controllers/ # Contrôleurs groupes
│ │ │ ├── services/ # Services groupes
│ │ │ ├── dto/ # Objets de transfert de données
│ │ │ └── groups.module.ts # Module groupes
│ │ ├── tags/ # Module de gestion des tags
│ │ │ ├── controllers/ # Contrôleurs tags
│ │ │ ├── services/ # Services tags
│ │ │ ├── dto/ # Objets de transfert de données
│ │ │ └── tags.module.ts # Module tags
│ │ └── websockets/ # Module de gestion des WebSockets
│ │ ├── gateways/ # Gateways WebSocket
│ │ ├── events/ # Définitions des événements
│ │ └── websockets.module.ts # Module WebSockets
│ └── database/ # Configuration de la base de données
│ ├── migrations/ # Migrations de base de données
│ ├── schema/ # Schéma de base de données (DrizzleORM)
│ └── database.module.ts # Module de base de données
├── test/ # Tests
│ ├── e2e/ # Tests end-to-end
│ └── unit/ # Tests unitaires
└── .env.example # Exemple de fichier d'environnement
```
## 2. Dépendances à Ajouter
```bash
# Dépendances principales
pnpm add @nestjs/config @nestjs/passport passport passport-github2 @nestjs/jwt
pnpm add @nestjs/websockets @nestjs/platform-socket.io socket.io
pnpm add drizzle-orm pg
pnpm add @node-rs/argon2 jose
pnpm add class-validator class-transformer
pnpm add zod zod-validation-error
pnpm add uuid
# Dépendances de développement
pnpm add -D drizzle-kit
pnpm add -D @types/passport-github2 @types/socket.io @types/pg @types/uuid
```
## 3. Configuration de l'Environnement
Créer un fichier `.env.example` avec les variables suivantes :
```
# Application
PORT=3000
NODE_ENV=development
API_PREFIX=api
# Database
DATABASE_URL=postgres://postgres:postgres@localhost:5432/groupmaker
# Authentication
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
GITHUB_CALLBACK_URL=http://localhost:3000/api/auth/github/callback
# JWT
JWT_ACCESS_SECRET=your_access_token_secret
JWT_REFRESH_SECRET=your_refresh_token_secret
JWT_ACCESS_EXPIRATION=15m
JWT_REFRESH_EXPIRATION=7d
# CORS
CORS_ORIGIN=http://localhost:3000
```
## 4. Étapes d'Implémentation
### 4.1 Configuration de Base
1. **Configuration de l'Application**
- Mettre à jour `main.ts` pour inclure la configuration CORS, les préfixes d'API, et les pipes de validation globaux
- Créer un module de configuration pour charger les variables d'environnement avec validation
2. **Configuration de la Base de Données**
- Configurer DrizzleORM avec PostgreSQL
- Définir le schéma de base de données selon le modèle de données spécifié
- Mettre en place les migrations de base de données
### 4.2 Authentification et Autorisation
1. **Authentification GitHub OAuth**
- Implémenter la stratégie d'authentification GitHub
- Créer les endpoints d'authentification (login, callback, refresh, logout)
- Mettre en place la gestion des JWT (génération, validation, rafraîchissement)
2. **Autorisation RBAC**
- Implémenter les guards pour la vérification des rôles
- Créer des décorateurs pour les rôles et les permissions
- Mettre en place la logique de vérification des autorisations
### 4.3 Modules Fonctionnels
1. **Module Utilisateurs**
- Implémenter les opérations CRUD pour les utilisateurs
- Gérer les profils utilisateurs et les préférences
- Implémenter la logique de consentement RGPD
2. **Module Projets**
- Implémenter les opérations CRUD pour les projets
- Gérer les relations avec les utilisateurs, les personnes et les groupes
- Implémenter la logique de partage de projets
3. **Module Personnes**
- Implémenter les opérations CRUD pour les personnes
- Gérer les attributs des personnes (niveau technique, genre, âge, etc.)
- Implémenter la logique d'association avec les tags
4. **Module Groupes**
- Implémenter les opérations CRUD pour les groupes
- Développer les algorithmes de création automatique de groupes équilibrés
- Gérer les relations avec les personnes
5. **Module Tags**
- Implémenter les opérations CRUD pour les tags
- Gérer les types de tags (PROJECT, PERSON)
- Implémenter la logique d'association avec les projets et les personnes
### 4.4 Communication en Temps Réel
1. **WebSockets avec SocketIO**
- Configurer les gateways WebSocket
- Implémenter les événements pour les mises à jour en temps réel
- Gérer les salles pour les projets collaboratifs
### 4.5 Sécurité et Conformité RGPD
1. **Sécurité**
- Implémenter le hachage des mots de passe avec @node-rs/argon2
- Mettre en place des protections contre les attaques courantes (CSRF, XSS, injections SQL)
- Configurer le rate limiting pour prévenir les attaques par force brute
2. **Conformité RGPD**
- Implémenter les fonctionnalités d'export des données personnelles
- Mettre en place la logique de suppression de compte
- Gérer les consentements utilisateurs et leur renouvellement
### 4.6 Tests et Documentation
1. **Tests**
- Écrire des tests unitaires pour les services et les contrôleurs
- Développer des tests e2e pour les API
- Mettre en place des tests d'intégration pour les modules critiques
2. **Documentation**
- Générer la documentation API avec Swagger
- Documenter les endpoints, les modèles de données et les paramètres
- Fournir des exemples d'utilisation des API
## 5. Calendrier d'Implémentation
1. **Semaine 1: Configuration et Base de Données**
- Configuration de l'environnement
- Mise en place de la base de données avec DrizzleORM
- Définition du schéma et création des migrations
2. **Semaine 2: Authentification et Utilisateurs**
- Implémentation de l'authentification GitHub OAuth
- Développement du module utilisateurs
- Mise en place de la gestion des JWT
3. **Semaine 3: Modules Principaux**
- Développement des modules projets, personnes et groupes
- Implémentation des opérations CRUD
- Mise en place des relations entre entités
4. **Semaine 4: Fonctionnalités Avancées**
- Implémentation des WebSockets pour la communication en temps réel
- Développement des algorithmes de création de groupes
- Mise en place des fonctionnalités de sécurité et de conformité RGPD
5. **Semaine 5: Tests et Finalisation**
- Écriture des tests unitaires et e2e
- Documentation de l'API
- Optimisation des performances et correction des bugs

View File

@ -1,347 +0,0 @@
# Diagrammes de Flux Métier
Ce document présente les diagrammes de séquence pour les principaux flux métier de l'application de création de groupes.
## Table des Matières
1. [Flux d'Authentification](#1-flux-dauthentification)
2. [Flux de Création et Gestion de Projet](#2-flux-de-création-et-gestion-de-projet)
3. [Flux de Gestion des Personnes](#3-flux-de-gestion-des-personnes)
4. [Flux de Création de Groupe](#4-flux-de-création-de-groupe)
- [4.1 Création Manuelle](#41-création-manuelle)
- [4.2 Création Automatique](#42-création-automatique)
5. [Flux de Collaboration en Temps Réel](#5-flux-de-collaboration-en-temps-réel)
## 1. Flux d'Authentification
Le flux d'authentification utilise OAuth 2.0 avec GitHub comme fournisseur d'identité.
```mermaid
sequenceDiagram
participant User as Utilisateur
participant Frontend as Frontend (Next.js)
participant Backend as Backend (NestJS)
participant GitHub as GitHub OAuth
User->>Frontend: Clic sur "Se connecter avec GitHub"
Frontend->>Backend: Redirection vers /auth/github
Backend->>GitHub: Redirection vers GitHub OAuth
GitHub->>User: Demande d'autorisation
User->>GitHub: Accepte l'autorisation
GitHub->>Backend: Redirection avec code d'autorisation
Backend->>GitHub: Échange code contre token d'accès
GitHub->>Backend: Retourne token d'accès
Backend->>GitHub: Requête informations utilisateur
GitHub->>Backend: Retourne informations utilisateur
Backend->>Backend: Crée/Met à jour l'utilisateur en BDD
Backend->>Backend: Génère JWT (access + refresh tokens)
Backend->>Frontend: Redirection avec tokens JWT
Frontend->>Frontend: Stocke les tokens
Frontend->>User: Affiche interface authentifiée
```
## 2. Flux de Création et Gestion de Projet
Ce flux illustre le processus de création et de gestion d'un projet par un utilisateur.
```mermaid
sequenceDiagram
participant User as Utilisateur
participant Frontend as Frontend (Next.js)
participant API as API (NestJS)
participant DB as Base de données
User->>Frontend: Accède au dashboard
Frontend->>API: GET /api/projects
API->>DB: Requête les projets de l'utilisateur
DB->>API: Retourne les projets
API->>Frontend: Retourne la liste des projets
Frontend->>User: Affiche les projets existants
User->>Frontend: Clic sur "Créer un nouveau projet"
Frontend->>User: Affiche le formulaire de création
User->>Frontend: Remplit le formulaire (nom, description)
Frontend->>API: POST /api/projects
API->>DB: Insère le nouveau projet
DB->>API: Confirme la création
API->>Frontend: Retourne les détails du projet créé
Frontend->>User: Affiche la page du projet
User->>Frontend: Modifie les détails du projet
Frontend->>API: PATCH /api/projects/{id}
API->>DB: Met à jour le projet
DB->>API: Confirme la mise à jour
API->>Frontend: Retourne les détails mis à jour
Frontend->>User: Affiche les détails mis à jour
User->>Frontend: Clic sur "Supprimer le projet"
Frontend->>User: Demande confirmation
User->>Frontend: Confirme la suppression
Frontend->>API: DELETE /api/projects/{id}
API->>DB: Supprime le projet et ses données associées
DB->>API: Confirme la suppression
API->>Frontend: Retourne confirmation
Frontend->>User: Redirige vers le dashboard
```
## 3. Flux de Gestion des Personnes
Ce flux illustre le processus d'ajout et de gestion des personnes dans un projet.
```mermaid
sequenceDiagram
participant User as Utilisateur
participant Frontend as Frontend (Next.js)
participant API as API (NestJS)
participant DB as Base de données
participant WS as WebSocket
User->>Frontend: Accède à un projet
Frontend->>API: GET /api/projects/{id}
API->>DB: Requête les détails du projet
DB->>API: Retourne les détails du projet
API->>Frontend: Retourne les détails du projet
Frontend->>User: Affiche la page du projet
User->>Frontend: Clic sur "Gérer les personnes"
Frontend->>API: GET /api/projects/{id}/persons
API->>DB: Requête les personnes du projet
DB->>API: Retourne les personnes
API->>Frontend: Retourne la liste des personnes
Frontend->>User: Affiche la liste des personnes
User->>Frontend: Clic sur "Ajouter une personne"
Frontend->>User: Affiche le formulaire d'ajout
User->>Frontend: Remplit les attributs (prénom, nom, genre, niveau technique, etc.)
Frontend->>API: POST /api/projects/{id}/persons
API->>DB: Insère la nouvelle personne
DB->>API: Confirme l'ajout
API->>Frontend: Retourne les détails de la personne
API->>WS: Émet événement "personAdded"
WS->>Frontend: Notifie les clients connectés
Frontend->>User: Met à jour la liste des personnes
User->>Frontend: Modifie les attributs d'une personne
Frontend->>API: PATCH /api/persons/{id}
API->>DB: Met à jour la personne
DB->>API: Confirme la mise à jour
API->>Frontend: Retourne les détails mis à jour
API->>WS: Émet événement "personUpdated"
WS->>Frontend: Notifie les clients connectés
Frontend->>User: Affiche les détails mis à jour
User->>Frontend: Clic sur "Supprimer une personne"
Frontend->>User: Demande confirmation
User->>Frontend: Confirme la suppression
Frontend->>API: DELETE /api/persons/{id}
API->>DB: Supprime la personne
DB->>API: Confirme la suppression
API->>Frontend: Retourne confirmation
API->>WS: Émet événement "personDeleted"
WS->>Frontend: Notifie les clients connectés
Frontend->>User: Met à jour la liste des personnes
User->>Frontend: Ajoute un tag à une personne
Frontend->>API: POST /api/persons/{id}/tags
API->>DB: Associe le tag à la personne
DB->>API: Confirme l'association
API->>Frontend: Retourne la personne mise à jour
API->>WS: Émet événement "personTagged"
WS->>Frontend: Notifie les clients connectés
Frontend->>User: Affiche la personne avec le tag
```
## 4. Flux de Création de Groupe
### 4.1 Création Manuelle
Ce flux illustre le processus de création manuelle de groupes.
```mermaid
sequenceDiagram
participant User as Utilisateur
participant Frontend as Frontend (Next.js)
participant API as API (NestJS)
participant DB as Base de données
participant WS as WebSocket
User->>Frontend: Accède à un projet
Frontend->>API: GET /api/projects/{id}
API->>DB: Requête les détails du projet
DB->>API: Retourne les détails du projet
API->>Frontend: Retourne les détails du projet
Frontend->>User: Affiche la page du projet
User->>Frontend: Clic sur "Créer des groupes"
Frontend->>API: GET /api/projects/{id}/persons
API->>DB: Requête les personnes du projet
DB->>API: Retourne les personnes
API->>Frontend: Retourne la liste des personnes
Frontend->>User: Affiche l'interface de création de groupes
User->>Frontend: Clic sur "Création manuelle"
Frontend->>User: Affiche l'interface de glisser-déposer
User->>Frontend: Crée un nouveau groupe
Frontend->>API: POST /api/projects/{id}/groups
API->>DB: Insère le nouveau groupe
DB->>API: Confirme la création
API->>Frontend: Retourne les détails du groupe
API->>WS: Émet événement "groupCreated"
WS->>Frontend: Notifie les clients connectés
Frontend->>User: Affiche le groupe créé
User->>Frontend: Glisse-dépose des personnes dans le groupe
Frontend->>API: POST /api/groups/{id}/persons
API->>DB: Associe les personnes au groupe
DB->>API: Confirme l'association
API->>Frontend: Retourne le groupe mis à jour
API->>WS: Émet événement "personMoved"
WS->>Frontend: Notifie les clients connectés
Frontend->>User: Affiche le groupe avec les personnes
User->>Frontend: Renomme le groupe
Frontend->>API: PATCH /api/groups/{id}
API->>DB: Met à jour le nom du groupe
DB->>API: Confirme la mise à jour
API->>Frontend: Retourne les détails mis à jour
API->>WS: Émet événement "groupUpdated"
WS->>Frontend: Notifie les clients connectés
Frontend->>User: Affiche le groupe renommé
User->>Frontend: Clic sur "Enregistrer les groupes"
Frontend->>API: PUT /api/projects/{id}/groups/save
API->>DB: Enregistre l'état final des groupes
DB->>API: Confirme l'enregistrement
API->>Frontend: Retourne confirmation
Frontend->>User: Affiche message de confirmation
```
### 4.2 Création Automatique
Ce flux illustre le processus de création automatique de groupes équilibrés.
```mermaid
sequenceDiagram
participant User as Utilisateur
participant Frontend as Frontend (Next.js)
participant API as API (NestJS)
participant DB as Base de données
participant Algorithm as Algorithme de Groupes
participant WS as WebSocket
User->>Frontend: Accède à un projet
Frontend->>API: GET /api/projects/{id}
API->>DB: Requête les détails du projet
DB->>API: Retourne les détails du projet
API->>Frontend: Retourne les détails du projet
Frontend->>User: Affiche la page du projet
User->>Frontend: Clic sur "Créer des groupes"
Frontend->>API: GET /api/projects/{id}/persons
API->>DB: Requête les personnes du projet
DB->>API: Retourne les personnes
API->>Frontend: Retourne la liste des personnes
Frontend->>User: Affiche l'interface de création de groupes
User->>Frontend: Clic sur "Création automatique"
Frontend->>User: Affiche les options de génération
User->>Frontend: Définit le nombre de groupes souhaités
User->>Frontend: Sélectionne un preset (équilibré par niveau, etc.)
Frontend->>API: POST /api/projects/{id}/groups/generate
API->>Algorithm: Transmet les personnes et les paramètres
Algorithm->>Algorithm: Exécute l'algorithme de répartition
Algorithm->>API: Retourne les groupes générés
API->>DB: Insère les groupes générés
DB->>API: Confirme la création
API->>Frontend: Retourne les groupes générés
API->>WS: Émet événement "groupsGenerated"
WS->>Frontend: Notifie les clients connectés
Frontend->>User: Affiche les groupes générés
User->>Frontend: Ajuste manuellement certains groupes
Frontend->>API: PATCH /api/groups/{id}/persons
API->>DB: Met à jour les associations
DB->>API: Confirme la mise à jour
API->>Frontend: Retourne les groupes mis à jour
API->>WS: Émet événement "groupsUpdated"
WS->>Frontend: Notifie les clients connectés
Frontend->>User: Affiche les groupes ajustés
User->>Frontend: Clic sur "Enregistrer les groupes"
Frontend->>API: PUT /api/projects/{id}/groups/save
API->>DB: Enregistre l'état final des groupes
DB->>API: Confirme l'enregistrement
API->>Frontend: Retourne confirmation
Frontend->>User: Affiche message de confirmation
```
## 5. Flux de Collaboration en Temps Réel
Ce flux illustre le processus de collaboration en temps réel entre plusieurs utilisateurs travaillant sur le même projet.
```mermaid
sequenceDiagram
participant User1 as Utilisateur 1
participant Frontend1 as Frontend 1
participant User2 as Utilisateur 2
participant Frontend2 as Frontend 2
participant API as API (NestJS)
participant WS as WebSocket Gateway
participant DB as Base de données
User1->>Frontend1: Se connecte au projet
Frontend1->>API: GET /api/projects/{id}
API->>DB: Requête les détails du projet
DB->>API: Retourne les détails du projet
API->>Frontend1: Retourne les détails du projet
Frontend1->>User1: Affiche la page du projet
Frontend1->>WS: Connexion WebSocket
Frontend1->>WS: Rejoint la salle "project:{id}"
WS->>Frontend1: Confirme la connexion
User2->>Frontend2: Se connecte au même projet
Frontend2->>API: GET /api/projects/{id}
API->>DB: Requête les détails du projet
DB->>API: Retourne les détails du projet
API->>Frontend2: Retourne les détails du projet
Frontend2->>User2: Affiche la page du projet
Frontend2->>WS: Connexion WebSocket
Frontend2->>WS: Rejoint la salle "project:{id}"
WS->>Frontend2: Confirme la connexion
WS->>Frontend1: Notifie qu'un autre utilisateur a rejoint
Frontend1->>User1: Affiche notification "Utilisateur 2 a rejoint"
User1->>Frontend1: Crée un nouveau groupe
Frontend1->>API: POST /api/projects/{id}/groups
API->>DB: Insère le nouveau groupe
DB->>API: Confirme la création
API->>Frontend1: Retourne les détails du groupe
API->>WS: Émet événement "groupCreated"
WS->>Frontend2: Transmet l'événement "groupCreated"
Frontend2->>User2: Met à jour l'interface avec le nouveau groupe
User2->>Frontend2: Déplace une personne dans le groupe
Frontend2->>API: PATCH /api/groups/{id}/persons
API->>DB: Met à jour les associations
DB->>API: Confirme la mise à jour
API->>Frontend2: Retourne le groupe mis à jour
API->>WS: Émet événement "personMoved"
WS->>Frontend1: Transmet l'événement "personMoved"
Frontend1->>User1: Met à jour l'interface avec le mouvement
User1->>Frontend1: Renomme le groupe
Frontend1->>API: PATCH /api/groups/{id}
API->>DB: Met à jour le nom du groupe
DB->>API: Confirme la mise à jour
API->>Frontend1: Retourne les détails mis à jour
API->>WS: Émet événement "groupUpdated"
WS->>Frontend2: Transmet l'événement "groupUpdated"
Frontend2->>User2: Met à jour l'interface avec le nouveau nom
User2->>Frontend2: Se déconnecte du projet
Frontend2->>WS: Quitte la salle "project:{id}"
WS->>Frontend1: Notifie qu'un utilisateur a quitté
Frontend1->>User1: Affiche notification "Utilisateur 2 a quitté"
```

View File

@ -1,488 +0,0 @@
# Plan d'Implémentation du Schéma de Base de Données
Ce document détaille le plan d'implémentation du schéma de base de données pour l'application de création de groupes, basé sur le modèle de données spécifié dans le cahier des charges.
## 1. Schéma DrizzleORM
Le schéma sera implémenté en utilisant DrizzleORM avec PostgreSQL. Voici la définition des tables et leurs relations.
### 1.1 Table `users`
```typescript
import { pgTable, uuid, varchar, text, timestamp, jsonb } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(), // UUIDv7 pour l'ordre chronologique
name: varchar('name', { length: 100 }).notNull(),
avatar: text('avatar'), // URL depuis l'API Github
githubId: varchar('githubId', { length: 50 }).notNull().unique(),
gdprTimestamp: timestamp('gdprTimestamp', { withTimezone: true }),
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updatedAt', { withTimezone: true }).defaultNow().notNull(),
metadata: jsonb('metadata').default({})
}, (table) => {
return {
githubIdIdx: index('githubId_idx').on(table.githubId),
createdAtIdx: index('createdAt_idx').on(table.createdAt)
};
});
```
### 1.2 Table `projects`
```typescript
import { pgTable, uuid, varchar, text, timestamp, jsonb, foreignKey } from 'drizzle-orm/pg-core';
import { users } from './users';
export const projects = pgTable('projects', {
id: uuid('id').primaryKey().defaultRandom(),
name: varchar('name', { length: 100 }).notNull(),
description: text('description'),
ownerId: uuid('ownerId').notNull().references(() => users.id, { onDelete: 'cascade' }),
settings: jsonb('settings').default({}),
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updatedAt', { withTimezone: true }).defaultNow().notNull()
}, (table) => {
return {
nameIdx: index('name_idx').on(table.name),
ownerIdIdx: index('ownerId_idx').on(table.ownerId),
createdAtIdx: index('createdAt_idx').on(table.createdAt)
};
});
```
### 1.3 Enum `gender`
```typescript
export const gender = pgEnum('gender', ['MALE', 'FEMALE', 'NON_BINARY']);
```
### 1.4 Enum `oralEaseLevel`
```typescript
export const oralEaseLevel = pgEnum('oralEaseLevel', ['SHY', 'RESERVED', 'COMFORTABLE']);
```
### 1.5 Table `persons`
```typescript
import { pgTable, uuid, varchar, smallint, boolean, timestamp, jsonb, foreignKey } from 'drizzle-orm/pg-core';
import { projects } from './projects';
import { gender, oralEaseLevel } from './enums';
export const persons = pgTable('persons', {
id: uuid('id').primaryKey().defaultRandom(),
firstName: varchar('firstName', { length: 50 }).notNull(),
lastName: varchar('lastName', { length: 50 }).notNull(),
gender: gender('gender').notNull(),
technicalLevel: smallint('technicalLevel').notNull(),
hasTechnicalTraining: boolean('hasTechnicalTraining').notNull().default(false),
frenchSpeakingLevel: smallint('frenchSpeakingLevel').notNull(),
oralEaseLevel: oralEaseLevel('oralEaseLevel').notNull(),
age: smallint('age'),
projectId: uuid('projectId').notNull().references(() => projects.id, { onDelete: 'cascade' }),
attributes: jsonb('attributes').default({}),
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updatedAt', { withTimezone: true }).defaultNow().notNull()
}, (table) => {
return {
firstNameIdx: index('firstName_idx').on(table.firstName),
lastNameIdx: index('lastName_idx').on(table.lastName),
projectIdIdx: index('projectId_idx').on(table.projectId),
nameCompositeIdx: index('name_composite_idx').on(table.firstName, table.lastName)
};
});
```
### 1.6 Table `groups`
```typescript
import { pgTable, uuid, varchar, timestamp, jsonb, foreignKey } from 'drizzle-orm/pg-core';
import { projects } from './projects';
export const groups = pgTable('groups', {
id: uuid('id').primaryKey().defaultRandom(),
name: varchar('name', { length: 100 }).notNull(),
projectId: uuid('projectId').notNull().references(() => projects.id, { onDelete: 'cascade' }),
metadata: jsonb('metadata').default({}),
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updatedAt', { withTimezone: true }).defaultNow().notNull()
}, (table) => {
return {
nameIdx: index('name_idx').on(table.name),
projectIdIdx: index('projectId_idx').on(table.projectId)
};
});
```
### 1.7 Enum `tagType`
```typescript
export const tagType = pgEnum('tagType', ['PROJECT', 'PERSON']);
```
### 1.8 Table `tags`
```typescript
import { pgTable, uuid, varchar, timestamp, foreignKey } from 'drizzle-orm/pg-core';
import { tagType } from './enums';
export const tags = pgTable('tags', {
id: uuid('id').primaryKey().defaultRandom(),
name: varchar('name', { length: 50 }).notNull(),
color: varchar('color', { length: 7 }).notNull(),
type: tagType('type').notNull(),
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updatedAt', { withTimezone: true }).defaultNow().notNull()
}, (table) => {
return {
nameIdx: index('name_idx').on(table.name),
typeIdx: index('type_idx').on(table.type)
};
});
```
### 1.9 Table `personToGroup` (Relation)
```typescript
import { pgTable, uuid, timestamp, foreignKey } from 'drizzle-orm/pg-core';
import { persons } from './persons';
import { groups } from './groups';
export const personToGroup = pgTable('person_to_group', {
id: uuid('id').primaryKey().defaultRandom(),
personId: uuid('personId').notNull().references(() => persons.id, { onDelete: 'cascade' }),
groupId: uuid('groupId').notNull().references(() => groups.id, { onDelete: 'cascade' }),
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull()
}, (table) => {
return {
personIdIdx: index('personId_idx').on(table.personId),
groupIdIdx: index('groupId_idx').on(table.groupId),
personGroupUniqueIdx: uniqueIndex('person_group_unique_idx').on(table.personId, table.groupId)
};
});
```
### 1.10 Table `personToTag` (Relation)
```typescript
import { pgTable, uuid, timestamp, foreignKey } from 'drizzle-orm/pg-core';
import { persons } from './persons';
import { tags } from './tags';
export const personToTag = pgTable('person_to_tag', {
id: uuid('id').primaryKey().defaultRandom(),
personId: uuid('personId').notNull().references(() => persons.id, { onDelete: 'cascade' }),
tagId: uuid('tagId').notNull().references(() => tags.id, { onDelete: 'cascade' }),
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull()
}, (table) => {
return {
personIdIdx: index('personId_idx').on(table.personId),
tagIdIdx: index('tagId_idx').on(table.tagId),
personTagUniqueIdx: uniqueIndex('person_tag_unique_idx').on(table.personId, table.tagId)
};
});
```
### 1.11 Table `projectToTag` (Relation)
```typescript
import { pgTable, uuid, timestamp, foreignKey } from 'drizzle-orm/pg-core';
import { projects } from './projects';
import { tags } from './tags';
export const projectToTag = pgTable('project_to_tag', {
id: uuid('id').primaryKey().defaultRandom(),
projectId: uuid('projectId').notNull().references(() => projects.id, { onDelete: 'cascade' }),
tagId: uuid('tagId').notNull().references(() => tags.id, { onDelete: 'cascade' }),
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull()
}, (table) => {
return {
projectIdIdx: index('projectId_idx').on(table.projectId),
tagIdIdx: index('tagId_idx').on(table.tagId),
projectTagUniqueIdx: uniqueIndex('project_tag_unique_idx').on(table.projectId, table.tagId)
};
});
```
## 2. Relations et Types
### 2.1 Relations
```typescript
// Définition des relations pour les requêtes
export const relations = {
users: {
projects: one(users, {
fields: [users.id],
references: [projects.ownerId],
}),
},
projects: {
owner: many(projects, {
fields: [projects.ownerId],
references: [users.id],
}),
persons: one(projects, {
fields: [projects.id],
references: [persons.projectId],
}),
groups: one(projects, {
fields: [projects.id],
references: [groups.projectId],
}),
tags: many(projects, {
through: {
table: projectToTag,
fields: [projectToTag.projectId, projectToTag.tagId],
references: [projects.id, tags.id],
},
}),
},
persons: {
project: many(persons, {
fields: [persons.projectId],
references: [projects.id],
}),
group: many(persons, {
through: {
table: personToGroup,
fields: [personToGroup.personId, personToGroup.groupId],
references: [persons.id, groups.id],
},
}),
tags: many(persons, {
through: {
table: personToTag,
fields: [personToTag.personId, personToTag.tagId],
references: [persons.id, tags.id],
},
}),
},
groups: {
project: many(groups, {
fields: [groups.projectId],
references: [projects.id],
}),
persons: many(groups, {
through: {
table: personToGroup,
fields: [personToGroup.groupId, personToGroup.personId],
references: [groups.id, persons.id],
},
}),
},
tags: {
persons: many(tags, {
through: {
table: personToTag,
fields: [personToTag.tagId, personToTag.personId],
references: [tags.id, persons.id],
},
}),
projects: many(tags, {
through: {
table: projectToTag,
fields: [projectToTag.tagId, projectToTag.projectId],
references: [tags.id, projects.id],
},
}),
},
};
```
### 2.2 Types Inférés
```typescript
// Types inférés à partir du schéma
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Project = typeof projects.$inferSelect;
export type NewProject = typeof projects.$inferInsert;
export type Person = typeof persons.$inferSelect;
export type NewPerson = typeof persons.$inferInsert;
export type Group = typeof groups.$inferSelect;
export type NewGroup = typeof groups.$inferInsert;
export type Tag = typeof tags.$inferSelect;
export type NewTag = typeof tags.$inferInsert;
export type PersonToGroup = typeof personToGroup.$inferSelect;
export type NewPersonToGroup = typeof personToGroup.$inferInsert;
export type PersonToTag = typeof personToTag.$inferSelect;
export type NewPersonToTag = typeof personToTag.$inferInsert;
export type ProjectToTag = typeof projectToTag.$inferSelect;
export type NewProjectToTag = typeof projectToTag.$inferInsert;
```
## 3. Migrations
### 3.1 Configuration de Drizzle Kit
Créer un fichier `drizzle.config.ts` à la racine du projet backend :
```typescript
import type { Config } from 'drizzle-kit';
import * as dotenv from 'dotenv';
dotenv.config();
export default {
schema: './src/database/schema/*.ts',
out: './src/database/migrations',
driver: 'pg',
dbCredentials: {
connectionString: process.env.DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/groupmaker',
},
verbose: true,
strict: true,
} satisfies Config;
```
### 3.2 Scripts pour les Migrations
Ajouter les scripts suivants au `package.json` du backend :
```json
{
"scripts": {
"db:generate": "drizzle-kit generate:pg",
"db:migrate": "ts-node src/database/migrate.ts",
"db:studio": "drizzle-kit studio"
}
}
```
### 3.3 Script de Migration
Créer un fichier `src/database/migrate.ts` :
```typescript
import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { Pool } from 'pg';
import * as dotenv from 'dotenv';
dotenv.config();
const main = async () => {
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
const db = drizzle(pool);
console.log('Running migrations...');
await migrate(db, { migrationsFolder: './src/database/migrations' });
console.log('Migrations completed successfully');
await pool.end();
};
main().catch((err) => {
console.error('Migration failed');
console.error(err);
process.exit(1);
});
```
## 4. Module de Base de Données
### 4.1 Module Database
Créer un fichier `src/database/database.module.ts` :
```typescript
import { Module, Global } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { Pool } from 'pg';
import { drizzle } from 'drizzle-orm/node-postgres';
import * as schema from './schema';
export const DATABASE_POOL = 'DATABASE_POOL';
export const DRIZZLE = 'DRIZZLE';
@Global()
@Module({
imports: [ConfigModule],
providers: [
{
provide: DATABASE_POOL,
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
const pool = new Pool({
connectionString: configService.get<string>('DATABASE_URL'),
});
// Test the connection
const client = await pool.connect();
try {
await client.query('SELECT NOW()');
console.log('Database connection established successfully');
} finally {
client.release();
}
return pool;
},
},
{
provide: DRIZZLE,
inject: [DATABASE_POOL],
useFactory: (pool: Pool) => {
return drizzle(pool, { schema });
},
},
],
exports: [DATABASE_POOL, DRIZZLE],
})
export class DatabaseModule {}
```
### 4.2 Index des Schémas
Créer un fichier `src/database/schema/index.ts` pour exporter tous les schémas :
```typescript
export * from './users';
export * from './projects';
export * from './persons';
export * from './groups';
export * from './tags';
export * from './personToGroup';
export * from './personToTag';
export * from './projectToTag';
export * from './enums';
export * from './relations';
```
## 5. Stratégie d'Indexation
Les index suivants seront créés pour optimiser les performances des requêtes :
1. **Index Primaires** : Sur toutes les clés primaires (UUIDv7)
2. **Index Secondaires** : Sur les clés étrangères pour accélérer les jointures
3. **Index Composites** : Sur les champs fréquemment utilisés ensemble dans les requêtes
4. **Index Partiels** : Pour les requêtes filtrées fréquentes
5. **Index de Texte** : Pour les recherches sur les champs textuels (noms, descriptions)
## 6. Optimisation des Formats de Données
Les types de données PostgreSQL seront optimisés pour chaque cas d'usage :
1. **UUID** : Pour les identifiants (UUIDv7 pour l'ordre chronologique)
2. **JSONB** : Pour les données flexibles et semi-structurées (metadata, settings, attributes)
3. **ENUM** : Types PostgreSQL natifs pour les valeurs fixes (gender, oralEaseLevel, tagType)
4. **VARCHAR** : Avec contraintes pour les chaînes de caractères variables
5. **TIMESTAMP WITH TIME ZONE** : Pour les dates avec gestion des fuseaux horaires
6. **SMALLINT** : Pour les valeurs numériques entières de petite taille (technicalLevel, age)
7. **BOOLEAN** : Pour les valeurs booléennes (hasTechnicalTraining)
Ces optimisations permettront d'améliorer les performances des requêtes, de réduire l'empreinte mémoire et d'assurer l'intégrité des données.

View File

@ -1,761 +0,0 @@
# Guide d'Implémentation du Backend
Ce document présente un guide complet pour l'implémentation du backend de l'application de création de groupes, basé sur les spécifications du cahier des charges et les plans détaillés précédemment établis.
## Table des Matières
1. [Vue d'Ensemble](#1-vue-densemble)
2. [Préparation de l'Environnement](#2-préparation-de-lenvironnement)
3. [Structure du Projet](#3-structure-du-projet)
4. [Configuration de Base](#4-configuration-de-base)
5. [Base de Données](#5-base-de-données)
6. [Authentification](#6-authentification)
7. [Modules Fonctionnels](#7-modules-fonctionnels)
8. [Communication en Temps Réel](#8-communication-en-temps-réel)
9. [Sécurité et Conformité RGPD](#9-sécurité-et-conformité-rgpd)
10. [Tests et Documentation](#10-tests-et-documentation)
11. [Déploiement](#11-déploiement)
12. [Calendrier d'Implémentation](#12-calendrier-dimplémentation)
## 1. Vue d'Ensemble
L'application est une plateforme de création et de gestion de groupes qui permet aux utilisateurs de créer des groupes en prenant en compte divers paramètres et de conserver un historique des groupes précédemment créés.
### 1.1 Architecture Globale
L'application suit une architecture monorepo avec séparation claire entre le frontend et le backend :
- **Frontend** : Application Next.js avec App Router et Server Components
- **Backend** : API NestJS avec PostgreSQL et DrizzleORM
- **Communication** : API REST pour les opérations CRUD et WebSockets pour les mises à jour en temps réel
- **Authentification** : OAuth 2.0 avec GitHub et JWT pour la gestion des sessions
## 2. Préparation de l'Environnement
### 2.1 Installation des Dépendances
```bash
# Installation des dépendances principales
pnpm add @nestjs/config @nestjs/passport passport passport-github2 @nestjs/jwt
pnpm add @nestjs/websockets @nestjs/platform-socket.io socket.io
pnpm add drizzle-orm pg
pnpm add @node-rs/argon2 jose
pnpm add class-validator class-transformer
pnpm add zod zod-validation-error
pnpm add uuid
# Installation des dépendances de développement
pnpm add -D drizzle-kit
pnpm add -D @types/passport-github2 @types/socket.io @types/pg @types/uuid
```
### 2.2 Configuration de l'Environnement
Créer un fichier `.env.example` à la racine du projet backend :
```
# Application
PORT=3000
NODE_ENV=development
API_PREFIX=api
# Database
DATABASE_URL=postgres://postgres:postgres@localhost:5432/groupmaker
# Authentication
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
GITHUB_CALLBACK_URL=http://localhost:3000/api/auth/github/callback
# JWT
JWT_ACCESS_SECRET=your_access_token_secret
JWT_REFRESH_SECRET=your_refresh_token_secret
JWT_ACCESS_EXPIRATION=15m
JWT_REFRESH_EXPIRATION=7d
# CORS
CORS_ORIGIN=http://localhost:3000
FRONTEND_URL=http://localhost:3000
```
## 3. Structure du Projet
La structure du projet backend suivra l'organisation suivante :
```
backend/
├── src/
│ ├── main.ts # Point d'entrée de l'application
│ ├── app.module.ts # Module principal
│ ├── config/ # Configuration de l'application
│ │ ├── app.config.ts # Configuration générale
│ │ ├── database.config.ts # Configuration de la base de données
│ │ ├── auth.config.ts # Configuration de l'authentification
│ │ └── env.validation.ts # Validation des variables d'environnement
│ ├── common/ # Utilitaires partagés
│ │ ├── decorators/ # Décorateurs personnalisés
│ │ ├── filters/ # Filtres d'exception
│ │ ├── guards/ # Guards d'authentification et d'autorisation
│ │ ├── interceptors/ # Intercepteurs
│ │ ├── pipes/ # Pipes de validation
│ │ └── utils/ # Fonctions utilitaires
│ ├── modules/ # Modules fonctionnels
│ │ ├── auth/ # Module d'authentification
│ │ ├── users/ # Module de gestion des utilisateurs
│ │ ├── projects/ # Module de gestion des projets
│ │ ├── persons/ # Module de gestion des personnes
│ │ ├── groups/ # Module de gestion des groupes
│ │ ├── tags/ # Module de gestion des tags
│ │ └── websockets/ # Module de gestion des WebSockets
│ └── database/ # Configuration de la base de données
│ ├── migrations/ # Migrations de base de données
│ ├── schema/ # Schéma de base de données (DrizzleORM)
│ └── database.module.ts # Module de base de données
├── test/ # Tests
│ ├── e2e/ # Tests end-to-end
│ └── unit/ # Tests unitaires
└── .env.example # Exemple de fichier d'environnement
```
## 4. Configuration de Base
### 4.1 Point d'Entrée de l'Application
Mettre à jour le fichier `src/main.ts` :
```typescript
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const configService = app.get(ConfigService);
// Configuration globale des pipes de validation
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
transform: true,
forbidNonWhitelisted: true,
}),
);
// Configuration CORS
app.enableCors({
origin: configService.get<string>('CORS_ORIGIN'),
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
});
// Préfixe global pour les routes API
app.setGlobalPrefix(configService.get<string>('API_PREFIX', 'api'));
const port = configService.get<number>('PORT', 3000);
await app.listen(port);
console.log(`Application is running on: http://localhost:${port}`);
}
bootstrap();
```
### 4.2 Module Principal
Mettre à jour le fichier `src/app.module.ts` :
```typescript
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { APP_GUARD } from '@nestjs/core';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { DatabaseModule } from './database/database.module';
import { AuthModule } from './modules/auth/auth.module';
import { UsersModule } from './modules/users/users.module';
import { ProjectsModule } from './modules/projects/projects.module';
import { PersonsModule } from './modules/persons/persons.module';
import { GroupsModule } from './modules/groups/groups.module';
import { TagsModule } from './modules/tags/tags.module';
import { WebSocketsModule } from './modules/websockets/websockets.module';
import { JwtAuthGuard } from './modules/auth/guards/jwt-auth.guard';
import { validate } from './config/env.validation';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
validate,
}),
DatabaseModule,
AuthModule,
UsersModule,
ProjectsModule,
PersonsModule,
GroupsModule,
TagsModule,
WebSocketsModule,
],
controllers: [AppController],
providers: [
AppService,
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
],
})
export class AppModule {}
```
### 4.3 Validation des Variables d'Environnement
Créer le fichier `src/config/env.validation.ts` :
```typescript
import { plainToClass } from 'class-transformer';
import { IsEnum, IsNumber, IsString, validateSync } from 'class-validator';
enum Environment {
Development = 'development',
Production = 'production',
Test = 'test',
}
class EnvironmentVariables {
@IsEnum(Environment)
NODE_ENV: Environment;
@IsNumber()
PORT: number;
@IsString()
API_PREFIX: string;
@IsString()
DATABASE_URL: string;
@IsString()
GITHUB_CLIENT_ID: string;
@IsString()
GITHUB_CLIENT_SECRET: string;
@IsString()
GITHUB_CALLBACK_URL: string;
@IsString()
JWT_ACCESS_SECRET: string;
@IsString()
JWT_REFRESH_SECRET: string;
@IsString()
JWT_ACCESS_EXPIRATION: string;
@IsString()
JWT_REFRESH_EXPIRATION: string;
@IsString()
CORS_ORIGIN: string;
@IsString()
FRONTEND_URL: string;
}
export function validate(config: Record<string, unknown>) {
const validatedConfig = plainToClass(
EnvironmentVariables,
{
...config,
PORT: config.PORT ? parseInt(config.PORT as string, 10) : 3000,
},
{ enableImplicitConversion: true },
);
const errors = validateSync(validatedConfig, {
skipMissingProperties: false,
});
if (errors.length > 0) {
throw new Error(errors.toString());
}
return validatedConfig;
}
```
## 5. Base de Données
### 5.1 Configuration de DrizzleORM
Créer le fichier `drizzle.config.ts` à la racine du projet backend :
```typescript
import type { Config } from 'drizzle-kit';
import * as dotenv from 'dotenv';
dotenv.config();
export default {
schema: './src/database/schema/*.ts',
out: './src/database/migrations',
driver: 'pg',
dbCredentials: {
connectionString: process.env.DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/groupmaker',
},
verbose: true,
strict: true,
} satisfies Config;
```
### 5.2 Module de Base de Données
Créer le fichier `src/database/database.module.ts` :
```typescript
import { Module, Global } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { Pool } from 'pg';
import { drizzle } from 'drizzle-orm/node-postgres';
import * as schema from './schema';
export const DATABASE_POOL = 'DATABASE_POOL';
export const DRIZZLE = 'DRIZZLE';
@Global()
@Module({
imports: [ConfigModule],
providers: [
{
provide: DATABASE_POOL,
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
const pool = new Pool({
connectionString: configService.get<string>('DATABASE_URL'),
});
// Test the connection
const client = await pool.connect();
try {
await client.query('SELECT NOW()');
console.log('Database connection established successfully');
} finally {
client.release();
}
return pool;
},
},
{
provide: DRIZZLE,
inject: [DATABASE_POOL],
useFactory: (pool: Pool) => {
return drizzle(pool, { schema });
},
},
],
exports: [DATABASE_POOL, DRIZZLE],
})
export class DatabaseModule {}
```
### 5.3 Script de Migration
Créer le fichier `src/database/migrate.ts` :
```typescript
import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { Pool } from 'pg';
import * as dotenv from 'dotenv';
dotenv.config();
const main = async () => {
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
const db = drizzle(pool);
console.log('Running migrations...');
await migrate(db, { migrationsFolder: './src/database/migrations' });
console.log('Migrations completed successfully');
await pool.end();
};
main().catch((err) => {
console.error('Migration failed');
console.error(err);
process.exit(1);
});
```
### 5.4 Schéma de Base de Données
Créer les fichiers de schéma dans le dossier `src/database/schema/` selon le plan détaillé dans le document DATABASE_SCHEMA_PLAN.md.
### 5.5 Scripts pour les Migrations
Ajouter les scripts suivants au `package.json` du backend :
```json
{
"scripts": {
"db:generate": "drizzle-kit generate:pg",
"db:migrate": "ts-node src/database/migrate.ts",
"db:studio": "drizzle-kit studio"
}
}
```
## 6. Authentification
### 6.1 Module d'Authentification
Créer le fichier `src/modules/auth/auth.module.ts` selon le plan détaillé dans le document AUTH_IMPLEMENTATION_PLAN.md.
### 6.2 Stratégies d'Authentification
Implémenter les stratégies d'authentification (GitHub, JWT, JWT Refresh) selon le plan détaillé dans le document AUTH_IMPLEMENTATION_PLAN.md.
### 6.3 Service d'Authentification
Implémenter le service d'authentification selon le plan détaillé dans le document AUTH_IMPLEMENTATION_PLAN.md.
### 6.4 Contrôleur d'Authentification
Implémenter le contrôleur d'authentification selon le plan détaillé dans le document AUTH_IMPLEMENTATION_PLAN.md.
### 6.5 Guards et Décorateurs
Implémenter les guards et décorateurs d'authentification selon le plan détaillé dans le document AUTH_IMPLEMENTATION_PLAN.md.
## 7. Modules Fonctionnels
### 7.1 Module Utilisateurs
#### 7.1.1 Service Utilisateurs
```typescript
// src/modules/users/services/users.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { DRIZZLE } from '../../../database/database.module';
import { Inject } from '@nestjs/common';
import { eq } from 'drizzle-orm';
import * as schema from '../../../database/schema';
import { CreateUserDto } from '../dto/create-user.dto';
import { UpdateUserDto } from '../dto/update-user.dto';
@Injectable()
export class UsersService {
constructor(@Inject(DRIZZLE) private readonly db: any) {}
async create(createUserDto: CreateUserDto) {
const [user] = await this.db
.insert(schema.users)
.values(createUserDto)
.returning();
return user;
}
async findAll() {
return this.db.select().from(schema.users);
}
async findById(id: string) {
const [user] = await this.db
.select()
.from(schema.users)
.where(eq(schema.users.id, id));
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return user;
}
async findByGithubId(githubId: string) {
const [user] = await this.db
.select()
.from(schema.users)
.where(eq(schema.users.githubId, githubId));
return user;
}
async update(id: string, updateUserDto: UpdateUserDto) {
const [user] = await this.db
.update(schema.users)
.set({
...updateUserDto,
updatedAt: new Date(),
})
.where(eq(schema.users.id, id))
.returning();
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return user;
}
async remove(id: string) {
const [user] = await this.db
.delete(schema.users)
.where(eq(schema.users.id, id))
.returning();
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return user;
}
async updateGdprConsent(id: string) {
return this.update(id, { gdprTimestamp: new Date() });
}
async exportUserData(id: string) {
const user = await this.findById(id);
const projects = await this.db
.select()
.from(schema.projects)
.where(eq(schema.projects.ownerId, id));
return {
user,
projects,
};
}
}
```
#### 7.1.2 Contrôleur Utilisateurs
```typescript
// src/modules/users/controllers/users.controller.ts
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
UseGuards,
} from '@nestjs/common';
import { UsersService } from '../services/users.service';
import { CreateUserDto } from '../dto/create-user.dto';
import { UpdateUserDto } from '../dto/update-user.dto';
import { GetUser } from '../../auth/decorators/get-user.decorator';
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
import { RolesGuard } from '../../auth/guards/roles.guard';
import { Roles } from '../../auth/decorators/roles.decorator';
import { Role } from '../../auth/enums/role.enum';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMIN)
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMIN)
findAll() {
return this.usersService.findAll();
}
@Get('profile')
@UseGuards(JwtAuthGuard)
getProfile(@GetUser() user) {
return user;
}
@Get(':id')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMIN)
findOne(@Param('id') id: string) {
return this.usersService.findById(id);
}
@Patch(':id')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMIN)
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.usersService.update(id, updateUserDto);
}
@Delete(':id')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMIN)
remove(@Param('id') id: string) {
return this.usersService.remove(id);
}
@Post('gdpr-consent')
@UseGuards(JwtAuthGuard)
updateGdprConsent(@GetUser('id') userId: string) {
return this.usersService.updateGdprConsent(userId);
}
@Get('export-data')
@UseGuards(JwtAuthGuard)
exportUserData(@GetUser('id') userId: string) {
return this.usersService.exportUserData(userId);
}
}
```
### 7.2 Module Projets
Implémenter le module de gestion des projets avec les opérations CRUD et les relations avec les utilisateurs, les personnes et les groupes.
### 7.3 Module Personnes
Implémenter le module de gestion des personnes avec les opérations CRUD et les attributs spécifiés (niveau technique, genre, âge, etc.).
### 7.4 Module Groupes
Implémenter le module de gestion des groupes avec les opérations CRUD et les algorithmes de création automatique de groupes équilibrés.
### 7.5 Module Tags
Implémenter le module de gestion des tags avec les opérations CRUD et la gestion des types de tags (PROJECT, PERSON).
## 8. Communication en Temps Réel
### 8.1 Module WebSockets
Implémenter le module WebSockets selon le plan détaillé dans le document WEBSOCKET_IMPLEMENTATION_PLAN.md.
### 8.2 Gateways WebSocket
Implémenter les gateways WebSocket (Projets, Groupes, Notifications) selon le plan détaillé dans le document WEBSOCKET_IMPLEMENTATION_PLAN.md.
### 8.3 Service WebSocket
Implémenter le service WebSocket selon le plan détaillé dans le document WEBSOCKET_IMPLEMENTATION_PLAN.md.
## 9. Sécurité et Conformité RGPD
### 9.1 Sécurité
#### 9.1.1 Protection contre les Attaques Courantes
- Implémenter la protection CSRF pour les opérations sensibles
- Configurer les en-têtes de sécurité (Content-Security-Policy, X-XSS-Protection, etc.)
- Utiliser des paramètres préparés avec DrizzleORM pour prévenir les injections SQL
- Mettre en place le rate limiting pour prévenir les attaques par force brute
#### 9.1.2 Gestion des Tokens
- Implémenter la révocation des tokens JWT
- Configurer la rotation des clés de signature
- Mettre en place la validation complète des tokens (signature, expiration, émetteur)
### 9.2 Conformité RGPD
#### 9.2.1 Gestion du Consentement
- Implémenter l'enregistrement du timestamp d'acceptation RGPD
- Mettre en place le renouvellement du consentement tous les 13 mois
#### 9.2.2 Droits des Utilisateurs
- Implémenter l'export des données personnelles
- Mettre en place la suppression de compte avec option de conservation ou suppression des projets
## 10. Tests et Documentation
### 10.1 Tests
#### 10.1.1 Tests Unitaires
Écrire des tests unitaires pour les services et les contrôleurs en utilisant Jest.
#### 10.1.2 Tests E2E
Développer des tests end-to-end pour les API en utilisant Supertest.
### 10.2 Documentation
#### 10.2.1 Documentation API
Générer la documentation API avec Swagger en utilisant les décorateurs NestJS.
#### 10.2.2 Documentation Technique
Documenter l'architecture, les modèles de données et les flux d'interaction.
## 11. Déploiement
### 11.1 Conteneurisation
Créer un Dockerfile pour le backend :
```dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install
COPY . .
RUN pnpm build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/package.json /app/pnpm-lock.yaml ./
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/main"]
```
### 11.2 CI/CD
Configurer un workflow CI/CD avec GitHub Actions pour l'intégration et le déploiement continus.
## 12. Calendrier d'Implémentation
1. **Semaine 1: Configuration et Base de Données**
- Configuration de l'environnement
- Mise en place de la base de données avec DrizzleORM
- Définition du schéma et création des migrations
2. **Semaine 2: Authentification et Utilisateurs**
- Implémentation de l'authentification GitHub OAuth
- Développement du module utilisateurs
- Mise en place de la gestion des JWT
3. **Semaine 3: Modules Principaux**
- Développement des modules projets, personnes et groupes
- Implémentation des opérations CRUD
- Mise en place des relations entre entités
4. **Semaine 4: Fonctionnalités Avancées**
- Implémentation des WebSockets pour la communication en temps réel
- Développement des algorithmes de création de groupes
- Mise en place des fonctionnalités de sécurité et de conformité RGPD
5. **Semaine 5: Tests et Finalisation**
- Écriture des tests unitaires et e2e
- Documentation de l'API
- Optimisation des performances et correction des bugs

View File

@ -1,83 +0,0 @@
# Résumé et Prochaines Étapes
## Résumé du Travail Effectué
Nous avons élaboré un plan de bataille complet pour l'implémentation du backend de l'application de création de groupes, basé sur les spécifications du cahier des charges. Ce travail a abouti à la création de plusieurs documents détaillés :
1. **BACKEND_IMPLEMENTATION_PLAN.md** : Plan général d'implémentation du backend, incluant la structure des dossiers, les dépendances à ajouter, la configuration de l'environnement, et les étapes d'implémentation.
2. **DATABASE_SCHEMA_PLAN.md** : Plan détaillé du schéma de base de données, incluant la définition des tables, les relations, les types, les migrations, et les stratégies d'optimisation.
3. **AUTH_IMPLEMENTATION_PLAN.md** : Plan d'implémentation du système d'authentification avec OAuth 2.0 via GitHub et JWT, incluant les stratégies, services, contrôleurs, guards et décorateurs.
4. **WEBSOCKET_IMPLEMENTATION_PLAN.md** : Plan d'implémentation du système de communication en temps réel avec Socket.IO, incluant les gateways, services, et événements.
5. **IMPLEMENTATION_GUIDE.md** : Guide complet combinant tous les plans précédents et fournissant une feuille de route claire pour l'implémentation du backend.
Ces documents fournissent une base solide pour le développement du backend, avec des instructions détaillées pour chaque composant du système.
## Prochaines Étapes
Pour mettre en œuvre ce plan, voici les prochaines étapes à suivre :
### 1. Configuration Initiale
- [ ] Installer les dépendances nécessaires avec pnpm
- [ ] Créer le fichier .env à partir du modèle .env.example
- [ ] Configurer la structure de base du projet selon le plan
### 2. Base de Données
- [ ] Implémenter les schémas de base de données avec DrizzleORM
- [ ] Configurer le module de base de données dans NestJS
- [ ] Générer et exécuter les migrations initiales
### 3. Authentification
- [ ] Implémenter le module d'authentification avec GitHub OAuth
- [ ] Configurer les stratégies JWT pour la gestion des sessions
- [ ] Mettre en place les guards et décorateurs pour la protection des routes
### 4. Modules Fonctionnels
- [ ] Implémenter le module utilisateurs
- [ ] Implémenter le module projets
- [ ] Implémenter le module personnes
- [ ] Implémenter le module groupes
- [ ] Implémenter le module tags
### 5. Communication en Temps Réel
- [ ] Configurer Socket.IO avec NestJS
- [ ] Implémenter les gateways WebSocket pour les projets, groupes et notifications
- [ ] Mettre en place le service WebSocket pour la gestion des connexions
### 6. Sécurité et Conformité RGPD
- [ ] Implémenter les mesures de sécurité (protection CSRF, validation des entrées, etc.)
- [ ] Mettre en place les fonctionnalités de conformité RGPD (consentement, export de données, etc.)
### 7. Tests et Documentation
- [ ] Écrire des tests unitaires pour les services et contrôleurs
- [ ] Développer des tests e2e pour les API
- [ ] Générer la documentation API avec Swagger
### 8. Déploiement
- [ ] Créer le Dockerfile pour la conteneurisation
- [ ] Configurer le workflow CI/CD avec GitHub Actions
## Recommandations
1. **Approche Itérative** : Suivre une approche itérative en implémentant d'abord les fonctionnalités de base, puis en ajoutant progressivement les fonctionnalités plus avancées.
2. **Tests Continus** : Écrire des tests au fur et à mesure du développement pour s'assurer que les fonctionnalités sont correctement implémentées.
3. **Documentation** : Documenter le code et les API au fur et à mesure pour faciliter la maintenance et l'évolution du projet.
4. **Revue de Code** : Effectuer des revues de code régulières pour s'assurer de la qualité du code et du respect des bonnes pratiques.
5. **Suivi du Calendrier** : Suivre le calendrier d'implémentation proposé pour s'assurer que le projet progresse selon le planning prévu.
En suivant ce plan et ces recommandations, l'implémentation du backend de l'application de création de groupes devrait être réalisée de manière efficace et conforme aux spécifications du cahier des charges.

View File

@ -1,801 +0,0 @@
# Plan d'Implémentation des WebSockets
Ce document détaille le plan d'implémentation du système de communication en temps réel via WebSockets pour l'application de création de groupes, basé sur les spécifications du cahier des charges.
## 1. Vue d'Ensemble
L'application utilisera Socket.IO pour établir une communication bidirectionnelle en temps réel entre le client et le serveur. Cette fonctionnalité permettra :
- La mise à jour instantanée des groupes
- Les notifications en temps réel
- La collaboration simultanée entre utilisateurs
## 2. Architecture WebSocket
```mermaid
sequenceDiagram
participant Client as Client (Next.js)
participant Gateway as WebSocket Gateway (NestJS)
participant Service as Services NestJS
participant DB as Base de données
Client->>Gateway: Connexion WebSocket
Gateway->>Gateway: Authentification (JWT)
Gateway->>Client: Connexion établie
Client->>Gateway: Rejoindre une salle (projet)
Gateway->>Client: Confirmation d'adhésion à la salle
Note over Client,Gateway: Communication bidirectionnelle
Client->>Gateway: Événement (ex: modification groupe)
Gateway->>Service: Traitement de l'événement
Service->>DB: Mise à jour des données
Service->>Gateway: Résultat de l'opération
Gateway->>Client: Diffusion aux clients concernés
```
## 3. Structure des Modules
### 3.1 Module WebSockets
```typescript
// src/modules/websockets/websockets.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { ProjectsModule } from '../projects/projects.module';
import { GroupsModule } from '../groups/groups.module';
import { UsersModule } from '../users/users.module';
import { ProjectsGateway } from './gateways/projects.gateway';
import { GroupsGateway } from './gateways/groups.gateway';
import { NotificationsGateway } from './gateways/notifications.gateway';
import { WebSocketService } from './services/websocket.service';
@Module({
imports: [
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
secret: configService.get<string>('JWT_ACCESS_SECRET'),
}),
}),
ProjectsModule,
GroupsModule,
UsersModule,
],
providers: [
ProjectsGateway,
GroupsGateway,
NotificationsGateway,
WebSocketService,
],
exports: [WebSocketService],
})
export class WebSocketsModule {}
```
### 3.2 Gateways WebSocket
#### 3.2.1 Gateway de Base
```typescript
// src/modules/websockets/gateways/base.gateway.ts
import {
OnGatewayConnection,
OnGatewayDisconnect,
OnGatewayInit,
WebSocketServer,
} from '@nestjs/websockets';
import { Logger, UseGuards } from '@nestjs/common';
import { Server, Socket } from 'socket.io';
import { JwtService } from '@nestjs/jwt';
import { WsJwtGuard } from '../guards/ws-jwt.guard';
import { WebSocketService } from '../services/websocket.service';
export abstract class BaseGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer() server: Server;
protected logger = new Logger(this.constructor.name);
constructor(
protected readonly jwtService: JwtService,
protected readonly webSocketService: WebSocketService,
) {}
afterInit(server: Server) {
this.webSocketService.setServer(server);
this.logger.log('WebSocket Gateway initialized');
}
@UseGuards(WsJwtGuard)
async handleConnection(client: Socket) {
try {
const token = this.extractTokenFromHeader(client);
if (!token) {
this.disconnect(client);
return;
}
const payload = this.jwtService.verify(token);
const userId = payload.sub;
// Associer l'ID utilisateur au socket
client.data.userId = userId;
// Ajouter le client à la liste des clients connectés
this.webSocketService.addClient(userId, client.id);
this.logger.log(`Client connected: ${client.id}, User: ${userId}`);
} catch (error) {
this.logger.error(`Connection error: ${error.message}`);
this.disconnect(client);
}
}
handleDisconnect(client: Socket) {
const userId = client.data.userId;
if (userId) {
this.webSocketService.removeClient(userId, client.id);
}
this.logger.log(`Client disconnected: ${client.id}`);
}
private extractTokenFromHeader(client: Socket): string | undefined {
const auth = client.handshake.auth.token || client.handshake.headers.authorization;
if (!auth) return undefined;
const parts = auth.split(' ');
if (parts.length === 2 && parts[0] === 'Bearer') {
return parts[1];
}
return undefined;
}
private disconnect(client: Socket) {
client.disconnect();
}
}
```
#### 3.2.2 Gateway des Projets
```typescript
// src/modules/websockets/gateways/projects.gateway.ts
import {
WebSocketGateway,
SubscribeMessage,
MessageBody,
ConnectedSocket,
} from '@nestjs/websockets';
import { UseGuards } from '@nestjs/common';
import { Socket } from 'socket.io';
import { JwtService } from '@nestjs/jwt';
import { ProjectsService } from '../../projects/services/projects.service';
import { BaseGateway } from './base.gateway';
import { WsJwtGuard } from '../guards/ws-jwt.guard';
import { WebSocketService } from '../services/websocket.service';
import { JoinProjectDto } from '../dto/join-project.dto';
import { ProjectUpdatedEvent } from '../events/project-updated.event';
@WebSocketGateway({
cors: {
origin: '*',
},
namespace: 'projects',
})
export class ProjectsGateway extends BaseGateway {
constructor(
protected readonly jwtService: JwtService,
protected readonly webSocketService: WebSocketService,
private readonly projectsService: ProjectsService,
) {
super(jwtService, webSocketService);
}
@UseGuards(WsJwtGuard)
@SubscribeMessage('joinProject')
async handleJoinProject(
@ConnectedSocket() client: Socket,
@MessageBody() data: JoinProjectDto,
) {
try {
const { projectId } = data;
const userId = client.data.userId;
// Vérifier si l'utilisateur a accès au projet
const hasAccess = await this.projectsService.checkUserAccess(projectId, userId);
if (!hasAccess) {
return { error: 'Access denied to this project' };
}
// Rejoindre la salle du projet
const roomName = `project:${projectId}`;
await client.join(roomName);
// Enregistrer l'association utilisateur-projet
this.webSocketService.addUserToProject(userId, projectId, client.id);
this.logger.log(`User ${userId} joined project ${projectId}`);
return { success: true, message: `Joined project ${projectId}` };
} catch (error) {
this.logger.error(`Error joining project: ${error.message}`);
return { error: 'Failed to join project' };
}
}
@UseGuards(WsJwtGuard)
@SubscribeMessage('leaveProject')
async handleLeaveProject(
@ConnectedSocket() client: Socket,
@MessageBody() data: JoinProjectDto,
) {
try {
const { projectId } = data;
const userId = client.data.userId;
// Quitter la salle du projet
const roomName = `project:${projectId}`;
await client.leave(roomName);
// Supprimer l'association utilisateur-projet
this.webSocketService.removeUserFromProject(userId, projectId, client.id);
this.logger.log(`User ${userId} left project ${projectId}`);
return { success: true, message: `Left project ${projectId}` };
} catch (error) {
this.logger.error(`Error leaving project: ${error.message}`);
return { error: 'Failed to leave project' };
}
}
@UseGuards(WsJwtGuard)
@SubscribeMessage('projectUpdated')
async handleProjectUpdated(
@ConnectedSocket() client: Socket,
@MessageBody() event: ProjectUpdatedEvent,
) {
try {
const { projectId, data } = event;
const userId = client.data.userId;
// Vérifier si l'utilisateur a accès au projet
const hasAccess = await this.projectsService.checkUserAccess(projectId, userId);
if (!hasAccess) {
return { error: 'Access denied to this project' };
}
// Diffuser la mise à jour à tous les clients dans la salle du projet
const roomName = `project:${projectId}`;
this.server.to(roomName).emit('projectUpdated', {
projectId,
data,
updatedBy: userId,
timestamp: new Date().toISOString(),
});
this.logger.log(`Project ${projectId} updated by user ${userId}`);
return { success: true };
} catch (error) {
this.logger.error(`Error updating project: ${error.message}`);
return { error: 'Failed to update project' };
}
}
}
```
#### 3.2.3 Gateway des Groupes
```typescript
// src/modules/websockets/gateways/groups.gateway.ts
import {
WebSocketGateway,
SubscribeMessage,
MessageBody,
ConnectedSocket,
} from '@nestjs/websockets';
import { UseGuards } from '@nestjs/common';
import { Socket } from 'socket.io';
import { JwtService } from '@nestjs/jwt';
import { GroupsService } from '../../groups/services/groups.service';
import { ProjectsService } from '../../projects/services/projects.service';
import { BaseGateway } from './base.gateway';
import { WsJwtGuard } from '../guards/ws-jwt.guard';
import { WebSocketService } from '../services/websocket.service';
import { GroupCreatedEvent } from '../events/group-created.event';
import { GroupUpdatedEvent } from '../events/group-updated.event';
import { GroupDeletedEvent } from '../events/group-deleted.event';
import { PersonMovedEvent } from '../events/person-moved.event';
@WebSocketGateway({
cors: {
origin: '*',
},
namespace: 'groups',
})
export class GroupsGateway extends BaseGateway {
constructor(
protected readonly jwtService: JwtService,
protected readonly webSocketService: WebSocketService,
private readonly groupsService: GroupsService,
private readonly projectsService: ProjectsService,
) {
super(jwtService, webSocketService);
}
@UseGuards(WsJwtGuard)
@SubscribeMessage('groupCreated')
async handleGroupCreated(
@ConnectedSocket() client: Socket,
@MessageBody() event: GroupCreatedEvent,
) {
try {
const { projectId, group } = event;
const userId = client.data.userId;
// Vérifier si l'utilisateur a accès au projet
const hasAccess = await this.projectsService.checkUserAccess(projectId, userId);
if (!hasAccess) {
return { error: 'Access denied to this project' };
}
// Diffuser la création du groupe à tous les clients dans la salle du projet
const roomName = `project:${projectId}`;
this.server.to(roomName).emit('groupCreated', {
projectId,
group,
createdBy: userId,
timestamp: new Date().toISOString(),
});
this.logger.log(`Group created in project ${projectId} by user ${userId}`);
return { success: true };
} catch (error) {
this.logger.error(`Error creating group: ${error.message}`);
return { error: 'Failed to create group' };
}
}
@UseGuards(WsJwtGuard)
@SubscribeMessage('groupUpdated')
async handleGroupUpdated(
@ConnectedSocket() client: Socket,
@MessageBody() event: GroupUpdatedEvent,
) {
try {
const { projectId, groupId, data } = event;
const userId = client.data.userId;
// Vérifier si l'utilisateur a accès au projet
const hasAccess = await this.projectsService.checkUserAccess(projectId, userId);
if (!hasAccess) {
return { error: 'Access denied to this project' };
}
// Diffuser la mise à jour du groupe à tous les clients dans la salle du projet
const roomName = `project:${projectId}`;
this.server.to(roomName).emit('groupUpdated', {
projectId,
groupId,
data,
updatedBy: userId,
timestamp: new Date().toISOString(),
});
this.logger.log(`Group ${groupId} updated in project ${projectId} by user ${userId}`);
return { success: true };
} catch (error) {
this.logger.error(`Error updating group: ${error.message}`);
return { error: 'Failed to update group' };
}
}
@UseGuards(WsJwtGuard)
@SubscribeMessage('groupDeleted')
async handleGroupDeleted(
@ConnectedSocket() client: Socket,
@MessageBody() event: GroupDeletedEvent,
) {
try {
const { projectId, groupId } = event;
const userId = client.data.userId;
// Vérifier si l'utilisateur a accès au projet
const hasAccess = await this.projectsService.checkUserAccess(projectId, userId);
if (!hasAccess) {
return { error: 'Access denied to this project' };
}
// Diffuser la suppression du groupe à tous les clients dans la salle du projet
const roomName = `project:${projectId}`;
this.server.to(roomName).emit('groupDeleted', {
projectId,
groupId,
deletedBy: userId,
timestamp: new Date().toISOString(),
});
this.logger.log(`Group ${groupId} deleted from project ${projectId} by user ${userId}`);
return { success: true };
} catch (error) {
this.logger.error(`Error deleting group: ${error.message}`);
return { error: 'Failed to delete group' };
}
}
@UseGuards(WsJwtGuard)
@SubscribeMessage('personMoved')
async handlePersonMoved(
@ConnectedSocket() client: Socket,
@MessageBody() event: PersonMovedEvent,
) {
try {
const { projectId, personId, fromGroupId, toGroupId } = event;
const userId = client.data.userId;
// Vérifier si l'utilisateur a accès au projet
const hasAccess = await this.projectsService.checkUserAccess(projectId, userId);
if (!hasAccess) {
return { error: 'Access denied to this project' };
}
// Diffuser le déplacement de la personne à tous les clients dans la salle du projet
const roomName = `project:${projectId}`;
this.server.to(roomName).emit('personMoved', {
projectId,
personId,
fromGroupId,
toGroupId,
movedBy: userId,
timestamp: new Date().toISOString(),
});
this.logger.log(`Person ${personId} moved from group ${fromGroupId} to group ${toGroupId} in project ${projectId} by user ${userId}`);
return { success: true };
} catch (error) {
this.logger.error(`Error moving person: ${error.message}`);
return { error: 'Failed to move person' };
}
}
}
```
#### 3.2.4 Gateway des Notifications
```typescript
// src/modules/websockets/gateways/notifications.gateway.ts
import {
WebSocketGateway,
SubscribeMessage,
MessageBody,
ConnectedSocket,
} from '@nestjs/websockets';
import { UseGuards } from '@nestjs/common';
import { Socket } from 'socket.io';
import { JwtService } from '@nestjs/jwt';
import { BaseGateway } from './base.gateway';
import { WsJwtGuard } from '../guards/ws-jwt.guard';
import { WebSocketService } from '../services/websocket.service';
import { NotificationEvent } from '../events/notification.event';
@WebSocketGateway({
cors: {
origin: '*',
},
namespace: 'notifications',
})
export class NotificationsGateway extends BaseGateway {
constructor(
protected readonly jwtService: JwtService,
protected readonly webSocketService: WebSocketService,
) {
super(jwtService, webSocketService);
}
@UseGuards(WsJwtGuard)
@SubscribeMessage('subscribeToNotifications')
async handleSubscribeToNotifications(@ConnectedSocket() client: Socket) {
try {
const userId = client.data.userId;
// Rejoindre la salle des notifications personnelles
const roomName = `user:${userId}:notifications`;
await client.join(roomName);
this.logger.log(`User ${userId} subscribed to notifications`);
return { success: true, message: 'Subscribed to notifications' };
} catch (error) {
this.logger.error(`Error subscribing to notifications: ${error.message}`);
return { error: 'Failed to subscribe to notifications' };
}
}
@UseGuards(WsJwtGuard)
@SubscribeMessage('unsubscribeFromNotifications')
async handleUnsubscribeFromNotifications(@ConnectedSocket() client: Socket) {
try {
const userId = client.data.userId;
// Quitter la salle des notifications personnelles
const roomName = `user:${userId}:notifications`;
await client.leave(roomName);
this.logger.log(`User ${userId} unsubscribed from notifications`);
return { success: true, message: 'Unsubscribed from notifications' };
} catch (error) {
this.logger.error(`Error unsubscribing from notifications: ${error.message}`);
return { error: 'Failed to unsubscribe from notifications' };
}
}
@UseGuards(WsJwtGuard)
@SubscribeMessage('sendNotification')
async handleSendNotification(
@ConnectedSocket() client: Socket,
@MessageBody() event: NotificationEvent,
) {
try {
const { recipientId, type, data } = event;
const senderId = client.data.userId;
// Diffuser la notification à l'utilisateur spécifique
const roomName = `user:${recipientId}:notifications`;
this.server.to(roomName).emit('notification', {
type,
data,
senderId,
timestamp: new Date().toISOString(),
});
this.logger.log(`Notification sent from user ${senderId} to user ${recipientId}`);
return { success: true };
} catch (error) {
this.logger.error(`Error sending notification: ${error.message}`);
return { error: 'Failed to send notification' };
}
}
}
```
### 3.3 Service WebSocket
```typescript
// src/modules/websockets/services/websocket.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { Server } from 'socket.io';
@Injectable()
export class WebSocketService {
private server: Server;
private readonly logger = new Logger(WebSocketService.name);
// Map des clients connectés par utilisateur
private readonly connectedClients = new Map<string, Set<string>>();
// Map des projets par utilisateur
private readonly userProjects = new Map<string, Set<string>>();
// Map des utilisateurs par projet
private readonly projectUsers = new Map<string, Set<string>>();
// Map des sockets par projet
private readonly projectSockets = new Map<string, Set<string>>();
setServer(server: Server) {
this.server = server;
}
getServer(): Server {
return this.server;
}
// Gestion des clients connectés
addClient(userId: string, socketId: string) {
if (!this.connectedClients.has(userId)) {
this.connectedClients.set(userId, new Set());
}
this.connectedClients.get(userId).add(socketId);
this.logger.debug(`Client ${socketId} added for user ${userId}`);
}
removeClient(userId: string, socketId: string) {
if (this.connectedClients.has(userId)) {
this.connectedClients.get(userId).delete(socketId);
if (this.connectedClients.get(userId).size === 0) {
this.connectedClients.delete(userId);
}
}
// Nettoyer les associations projet-utilisateur
this.cleanupUserProjects(userId, socketId);
this.logger.debug(`Client ${socketId} removed for user ${userId}`);
}
isUserConnected(userId: string): boolean {
return this.connectedClients.has(userId) && this.connectedClients.get(userId).size > 0;
}
getUserSocketIds(userId: string): string[] {
if (!this.connectedClients.has(userId)) {
return [];
}
return Array.from(this.connectedClients.get(userId));
}
// Gestion des associations utilisateur-projet
addUserToProject(userId: string, projectId: string, socketId: string) {
// Ajouter le projet à l'utilisateur
if (!this.userProjects.has(userId)) {
this.userProjects.set(userId, new Set());
}
this.userProjects.get(userId).add(projectId);
// Ajouter l'utilisateur au projet
if (!this.projectUsers.has(projectId)) {
this.projectUsers.set(projectId, new Set());
}
this.projectUsers.get(projectId).add(userId);
// Ajouter le socket au projet
if (!this.projectSockets.has(projectId)) {
this.projectSockets.set(projectId, new Set());
}
this.projectSockets.get(projectId).add(socketId);
this.logger.debug(`User ${userId} added to project ${projectId} with socket ${socketId}`);
}
removeUserFromProject(userId: string, projectId: string, socketId: string) {
// Supprimer le socket du projet
if (this.projectSockets.has(projectId)) {
this.projectSockets.get(projectId).delete(socketId);
if (this.projectSockets.get(projectId).size === 0) {
this.projectSockets.delete(projectId);
}
}
// Vérifier si l'utilisateur a d'autres sockets connectés au projet
const userSocketIds = this.getUserSocketIds(userId);
const hasOtherSocketsInProject = userSocketIds.some(sid =>
sid !== socketId && this.projectSockets.has(projectId) && this.projectSockets.get(projectId).has(sid)
);
// Si l'utilisateur n'a plus de sockets connectés au projet, supprimer l'association
if (!hasOtherSocketsInProject) {
// Supprimer le projet de l'utilisateur
if (this.userProjects.has(userId)) {
this.userProjects.get(userId).delete(projectId);
if (this.userProjects.get(userId).size === 0) {
this.userProjects.delete(userId);
}
}
// Supprimer l'utilisateur du projet
if (this.projectUsers.has(projectId)) {
this.projectUsers.get(projectId).delete(userId);
if (this.projectUsers.get(projectId).size === 0) {
this.projectUsers.delete(projectId);
}
}
}
this.logger.debug(`User ${userId} removed from project ${projectId} with socket ${socketId}`);
}
getUserProjects(userId: string): string[] {
if (!this.userProjects.has(userId)) {
return [];
}
return Array.from(this.userProjects.get(userId));
}
getProjectUsers(projectId: string): string[] {
if (!this.projectUsers.has(projectId)) {
return [];
}
return Array.from(this.projectUsers.get(projectId));
}
// Nettoyage des associations lors de la déconnexion
private cleanupUserProjects(userId: string, socketId: string) {
const projectIds = this.getUserProjects(userId);
for (const projectId of projectIds) {
this.removeUserFromProject(userId, projectId, socketId);
}
}
// Méthodes pour envoyer des messages
sendToUser(userId: string, event: string, data: any) {
const socketIds = this.getUserSocketIds(userId);
for (const socketId of socketIds) {
this.server.to(socketId).emit(event, data);
}
this.logger.debug(`Event ${event} sent to user ${userId}`);
}
sendToProject(projectId: string, event: string, data: any) {
const roomName = `project:${projectId}`;
this.server.to(roomName).emit(event, data);
this.logger.debug(`Event ${event} sent to project ${projectId}`);
}
broadcastToAll(event: string, data: any) {
this.server.emit(event, data);
this.logger.debug(`Event ${event} broadcasted to all connected clients`);
}
}
```
### 3.4 Guard WebSocket JWT
```typescript
// src/modules/websockets/guards/ws-jwt.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';
@Injectable()
export class WsJwtGuard implements CanActivate {
constructor(private readonly jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
try {
const client: Socket = context.switchToWs().getClient();
const token = this.extractTokenFromHeader(client);
if (!token) {
throw new WsException('Unauthorized');
}
const payload = this.jwtService.verify(token);
client.data.userId = payload.sub;
return true;
} catch (error) {
throw new WsException('Unauthorized');
}
}
private extractTokenFromHeader(client: Socket): string | undefined {
const auth = client.handshake.auth.token || client.handshake.headers.authorization;
if (!auth) return undefined;
const parts = auth.split(' ');
if (parts.length === 2 && parts[0] === 'Bearer') {
return parts[1];
}
return undefined;
}
}
```
### 3.5 DTOs et Événements
#### 3.5.1 DTO JoinProject
```typescript
// src/modules/websockets/dto/join-project.dto.ts
import { IsUUID, IsNotEmpty } from 'class-validator';
export class JoinProjectDto {
@IsUUID()
@IsNotEmpty()
projectId: string;
}
```
#### 3.5.2 Événement ProjectUpdated
```typescript

763
cdc.md
View File

@ -1,763 +0,0 @@
# Cahier des Charges - Application de Création de Groupes
## 1. Introduction
### 1.1 Contexte du Projet
Ce document constitue le cahier des charges pour le développement d'une application web dédiée à la création et à la gestion de groupes. Cette application permettra aux utilisateurs de créer des groupes en prenant en compte divers paramètres et de conserver un historique des groupes précédemment créés.
### 1.2 Objectifs du Projet
- Développer une application permettant la création de groupes selon différents critères
- Maintenir un historique des groupes créés pour éviter les duplications
- Offrir une interface intuitive et responsive
- Assurer la sécurité des données utilisateurs
- Respecter les normes RGPD
## 2. Architecture Technique
### 2.1 Stack Technologique
L'application sera développée en utilisant les technologies suivantes:
#### Frontend:
- **NextJS**: Framework React pour le rendu côté serveur et la génération de sites statiques
- Utilisation des App Router et Server Components pour optimiser les performances
- Implémentation du SSR (Server-Side Rendering) pour améliorer le SEO et le temps de chargement initial
- Utilisation des API Routes pour les endpoints spécifiques au frontend
- **SWR**: Bibliothèque React Hooks pour la récupération de données
- Mise en cache intelligente et revalidation automatique des données
- Stratégies de récupération optimisées (stale-while-revalidate)
- Gestion des états de chargement, d'erreur et de données
- Revalidation automatique lors du focus de la fenêtre et de la reconnexion réseau
- Déduplication des requêtes multiples vers le même endpoint
- **ShadcnUI**: Bibliothèque de composants UI pour un design cohérent
- Composants accessibles et personnalisables
- Thèmes adaptables pour le mode clair/sombre
- Intégration avec Tailwind CSS pour la stylisation
- **React Hook Form**: Gestion des formulaires
- Validation des données côté client
- Gestion efficace des erreurs de formulaire
- Intégration avec Zod pour la validation de schéma
- **Motion**: Bibliothèque pour les animations et le dynamisme de l'interface
- Animations fluides et performantes
- Transitions entre les pages
- Effets visuels pour améliorer l'expérience utilisateur
#### Backend:
- **NestJS**: Framework Node.js pour construire des applications serveur efficaces et scalables
- Architecture modulaire basée sur les décorateurs
- Injection de dépendances pour une meilleure testabilité
- Support intégré pour TypeScript
- Utilisation des Guards, Interceptors et Pipes pour la gestion des requêtes
- **PostgreSQL**: Système de gestion de base de données relationnelle
- Modélisation des données avec relations complexes
- Utilisation de DrizzleORM comme ORM pour interagir avec la base de données
- Migrations SQL déclaratives et type-safe
- Approche code-first pour la définition du schéma
- Optimisation des formats de données PostgreSQL (JSONB pour les données flexibles, UUID pour les identifiants, ENUM pour les valeurs fixes)
- Stratégie d'indexation avancée pour améliorer les performances des requêtes
- **SocketIO**: Bibliothèque pour la communication en temps réel
- Mise à jour instantanée des groupes
- Notifications en temps réel
- Collaboration simultanée entre utilisateurs
- **@node-rs/argon2**: Bibliothèque pour le hachage sécurisé des mots de passe
- Implémentation en Rust pour des performances optimales
- Protection contre les attaques par force brute
- Configuration adaptée aux recommandations de sécurité actuelles
- **jose**: Bibliothèque pour la gestion des JWT (JSON Web Tokens)
- Authentification stateless
- Signature et vérification des tokens
- Gestion des expirations et du rafraîchissement des tokens
#### Authentification:
- **OAuth2.0 + OIDC**: Via compte GitHub pour l'authentification sécurisée
- Flux d'autorisation code avec PKCE
- Récupération des informations de profil via l'API GitHub
- Gestion des scopes pour limiter les accès
- Implémentation côté backend pour sécuriser le processus d'authentification
### 2.2 Architecture Applicative
L'application suivra une architecture monorepo avec séparation claire entre le frontend et le backend.
#### 2.2.1 Structure du Monorepo
```
/
├── apps/
│ ├── web/ # Application frontend NextJS
│ │ ├── public/ # Fichiers statiques
│ │ ├── src/
│ │ │ ├── app/ # App Router de NextJS
│ │ │ ├── components/ # Composants React réutilisables
│ │ │ ├── hooks/ # Custom hooks React
│ │ │ ├── lib/ # Utilitaires et configurations
│ │ │ └── styles/ # Styles globaux
│ │ └── ...
│ │
│ └── api/ # Application backend NestJS
│ ├── src/
│ │ ├── modules/ # Modules NestJS
│ │ ├── common/ # Utilitaires partagés
│ │ ├── config/ # Configuration de l'application
│ │ └── main.ts # Point d'entrée de l'application
│ └── ...
├── packages/ # Packages partagés
│ ├── database/ # Configuration DrizzleORM et modèles
│ ├── eslint-config/ # Configuration ESLint partagée
│ ├── tsconfig/ # Configuration TypeScript partagée
│ └── ui/ # Bibliothèque de composants UI partagés
└── ...
```
#### 2.2.2 Gestion du Workspace avec PNPM
Le projet utilise PNPM pour la gestion du workspace et des packages. PNPM offre plusieurs avantages par rapport à d'autres gestionnaires de packages:
- **Efficacité de stockage**: Utilise un stockage partagé pour éviter la duplication des packages
- **Gestion de monorepo**: Facilite la gestion des dépendances entre les packages du monorepo
- **Performance**: Installation et mise à jour des dépendances plus rapides
- **Déterminisme**: Garantit que les mêmes dépendances sont installées de manière cohérente
Exemples d'utilisation de PNPM dans le monorepo:
```bash
# Exécuter une commande dans un package spécifique
pnpm --filter <package-name> <command>
# Exemple : démarrer le frontend uniquement
pnpm --filter web dev
# Installer une dépendance dans un package spécifique
pnpm --filter <package-name> add <dependency>
# Installer une dépendance de développement dans un package spécifique
pnpm --filter <package-name> add -D <dependency>
```
#### 2.2.3 Communication entre les Services
- API REST pour les opérations CRUD standard
- WebSockets via SocketIO pour les communications en temps réel
- Authentification via JWT pour sécuriser les échanges
#### 2.2.4 Architecture et Flux d'Interactions
Le diagramme ci-dessous illustre les interactions entre les différents composants du système:
```mermaid
flowchart TB
subgraph Client["Client (Navigateur)"]
FE["Frontend (NextJS)"]
end
subgraph Server["Serveur"]
BE["Backend (NestJS)"]
WS["WebSocket (SocketIO)"]
end
subgraph Storage["Stockage"]
DB[(PostgreSQL)]
end
subgraph External["Services Externes"]
GH["API GitHub"]
end
FE <--> BE
FE <--> WS
BE <--> DB
BE <--> GH
BE <--> WS
classDef client fill:#9c27b0,stroke:#ffffff,stroke-width:2px
classDef server fill:#3f51b5,stroke:#ffffff,stroke-width:2px
classDef storage fill:#4caf50,stroke:#ffffff,stroke-width:2px
classDef external fill:#ff9800,stroke:#ffffff,stroke-width:2px
class Client client
class Server server
class Storage storage
class External external
```
Ce diagramme montre les principaux flux d'interactions:
1. **Frontend ↔ Backend**: Communication via API REST pour les opérations CRUD standard
- Requêtes HTTP pour la création, lecture, mise à jour et suppression de données
- Authentification via JWT pour sécuriser les échanges
- Validation des données côté client et serveur
2. **Frontend ↔ WebSocket**: Communication en temps réel
- Notifications instantanées
- Mises à jour en direct des groupes
- Collaboration entre utilisateurs
3. **Backend ↔ Base de données**: Persistance des données
- Requêtes SQL optimisées via DrizzleORM
- Transactions pour garantir l'intégrité des données
- Utilisation d'index pour des performances optimales
4. **Backend ↔ API GitHub**: Récupération des données utilisateur
- Récupération des avatars utilisateurs
- Authentification des utilisateurs
- Récupération des informations de profil
5. **Backend ↔ WebSocket**: Gestion des événements
- Diffusion des mises à jour aux clients connectés
- Gestion des salles pour les projets collaboratifs
- Notification des changements en temps réel
Cette architecture permet une séparation claire des responsabilités tout en offrant une expérience utilisateur fluide et réactive.
#### 2.2.5 Déploiement
- Conteneurisation avec Docker pour assurer la cohérence entre les environnements
- CI/CD via GitHub Actions pour l'intégration et le déploiement continus
- Infrastructure scalable pour gérer les pics de charge
### 2.3 Modèle de Données
#### 2.3.1 Entités Principales
1. **User**
- id: UUIDv7 (clé primaire, type `uuid` optimisé pour l'indexation)
- name: String (type `varchar(100)`)
- avatar: String (URL depuis l'API Github, type `text`)
- githubId: String (pour l'authentification OAuth, type `varchar(50)` avec index)
- gdprTimestamp: DateTime (timestamp d'acceptation RGPD, type `timestamptz`)
- createdAt: DateTime (type `timestamptz` avec index)
- updatedAt: DateTime (type `timestamptz`)
- metadata: JSON (données flexibles, type `jsonb`)
2. **Project**
- id: UUIDv7 (clé primaire, type `uuid` optimisé pour l'indexation)
- name: String (type `varchar(100)` avec index)
- description: String (type `text`)
- ownerId: UUID (clé étrangère vers User, type `uuid` avec index)
- settings: JSON (configurations personnalisées, type `jsonb`)
- createdAt: DateTime (type `timestamptz` avec index)
- updatedAt: DateTime (type `timestamptz`)
3. **Person**
- id: UUIDv7 (clé primaire, type `uuid` optimisé pour l'indexation)
- firstName: String (type `varchar(50)` avec index partiel)
- lastName: String (type `varchar(50)` avec index partiel)
- gender: Enum (MALE, FEMALE, NON_BINARY, type `enum` natif PostgreSQL)
- technicalLevel: Integer (type `smallint` pour économie d'espace)
- hasTechnicalTraining: Boolean (type `boolean`)
- frenchSpeakingLevel: Integer (type `smallint` pour économie d'espace)
- oralEaseLevel: Enum (SHY, RESERVED, COMFORTABLE, type `enum` natif PostgreSQL)
- age: Integer (type `smallint` pour économie d'espace)
- projectId: UUID (clé étrangère vers Project, type `uuid` avec index)
- attributes: JSON (attributs additionnels flexibles, type `jsonb`)
- tags: Relation vers PersonTag (table de jointure avec index)
- createdAt: DateTime (type `timestamptz`)
- updatedAt: DateTime (type `timestamptz`)
4. **Group**
- id: UUIDv7 (clé primaire, type `uuid` optimisé pour l'indexation)
- name: String (type `varchar(100)` avec index)
- projectId: UUID (clé étrangère vers Project, type `uuid` avec index)
- metadata: JSON (données additionnelles, type `jsonb`)
- members: Relation vers Person (table de jointure avec index)
- createdAt: DateTime (type `timestamptz`)
- updatedAt: DateTime (type `timestamptz`)
5. **Tag**
- id: UUIDv7 (clé primaire, type `uuid` optimisé pour l'indexation)
- name: String (type `varchar(50)` avec index)
- color: String (code couleur, type `varchar(7)`)
- type: Enum (PROJECT, PERSON, type `enum` natif PostgreSQL)
- persons: Relation vers Person (table de jointure avec index)
- projects: Relation vers Project (table de jointure avec index)
- createdAt: DateTime (type `timestamptz`)
- updatedAt: DateTime (type `timestamptz`)
#### 2.3.2 Relations
- Un **User** peut avoir plusieurs **Projects**
- Un **Project** appartient à un seul **User**
- Un **Project** contient plusieurs **Persons**
- Un **Project** peut avoir plusieurs **Groups**
- Un **Project** peut être associé à plusieurs **Tags** de type PROJECT
- Une **Person** appartient à un seul **Project**
- Une **Person** peut être associée à plusieurs **Tags** de type PERSON
- Une **Person** peut être membre d'un seul **Group** à la fois
- Un **Group** appartient à un seul **Project**
- Un **Group** peut contenir plusieurs **Persons**
- Les **Tags** sont globaux et gérés par les administrateurs
#### 2.3.3 Schéma de Base de Données
Le schéma sera implémenté via DrizzleORM, permettant une définition type-safe des tables et relations avec une approche code-first. Les migrations SQL seront générées automatiquement à partir des changements de schéma, offrant un contrôle précis sur l'évolution de la base de données.
##### 2.3.3.1 Modèle Conceptuel de Données (MCD)
Le diagramme ci-dessous représente le modèle conceptuel de données de l'application, montrant les entités et leurs relations:
```mermaid
erDiagram
USER {
uuid id PK
string name
string avatar
string githubId
datetime gdprTimestamp
datetime createdAt
datetime updatedAt
json metadata
}
PROJECT {
uuid id PK
string name
string description
uuid ownerId FK
json settings
datetime createdAt
datetime updatedAt
}
PERSON {
uuid id PK
string firstName
string lastName
enum gender
int technicalLevel
boolean hasTechnicalTraining
int frenchSpeakingLevel
enum oralEaseLevel
int age
uuid projectId FK
json attributes
datetime createdAt
datetime updatedAt
}
GROUP {
uuid id PK
string name
uuid projectId FK
json metadata
datetime createdAt
datetime updatedAt
}
TAG {
uuid id PK
string name
string color
enum type
datetime createdAt
datetime updatedAt
}
USER ||--o{ PROJECT : "possède"
PROJECT ||--o{ PERSON : "contient"
PROJECT ||--o{ GROUP : "contient"
PROJECT }o--o{ TAG : "est associé à"
PERSON }o--o{ TAG : "est associée à"
PERSON }o--|| GROUP : "est membre de"
```
##### 2.3.3.2 Modèle Logique de Données (MLD)
Le diagramme ci-dessous représente le modèle logique de données, montrant les tables, leurs champs et les relations entre elles:
```mermaid
erDiagram
users {
uuid id PK
varchar(100) name
text avatar
varchar(50) githubId
timestamptz gdprTimestamp
timestamptz createdAt
timestamptz updatedAt
jsonb metadata
}
projects {
uuid id PK
varchar(100) name
text description
uuid ownerId FK
jsonb settings
timestamptz createdAt
timestamptz updatedAt
}
persons {
uuid id PK
varchar(50) firstName
varchar(50) lastName
enum gender
smallint technicalLevel
boolean hasTechnicalTraining
smallint frenchSpeakingLevel
enum oralEaseLevel
smallint age
uuid projectId FK
jsonb attributes
timestamptz createdAt
timestamptz updatedAt
}
groups {
uuid id PK
varchar(100) name
uuid projectId FK
jsonb metadata
timestamptz createdAt
timestamptz updatedAt
}
tags {
uuid id PK
varchar(50) name
varchar(7) color
enum type
timestamptz createdAt
timestamptz updatedAt
}
person_to_group {
uuid id PK
uuid personId FK
uuid groupId FK
timestamptz createdAt
}
person_to_tag {
uuid id PK
uuid personId FK
uuid tagId FK
timestamptz createdAt
}
project_to_tag {
uuid id PK
uuid projectId FK
uuid tagId FK
timestamptz createdAt
}
users ||--o{ projects : "ownerId"
projects ||--o{ persons : "projectId"
projects ||--o{ groups : "projectId"
projects ||--o{ project_to_tag : "projectId"
persons ||--o{ person_to_group : "personId"
groups ||--o{ person_to_group : "groupId"
persons ||--o{ person_to_tag : "personId"
tags ||--o{ person_to_tag : "tagId"
tags ||--o{ project_to_tag : "tagId"
```
##### 2.3.3.3 Stratégie d'Indexation
Pour optimiser les performances des requêtes, les stratégies d'indexation suivantes seront mises en place:
- Index primaires sur toutes les clés primaires (UUIDv7)
- Index secondaires sur les clés étrangères pour accélérer les jointures
- Index composites sur les champs fréquemment utilisés ensemble dans les requêtes
- Index partiels pour les requêtes filtrées fréquentes
- Index de texte pour les recherches sur les champs textuels (noms, descriptions)
DrizzleORM facilite la définition de ces index directement dans le schéma avec une syntaxe déclarative:
```typescript
// Exemple de définition d'index avec DrizzleORM
import { pgTable, uuid, varchar, timestamp, index } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
name: varchar('name', { length: 255 }),
githubId: varchar('githubId', { length: 50 }),
gdprTimestamp: timestamp('gdprTimestamp', { withTimezone: true }),
}, (table) => {
return {
githubIdIdx: index('githubId_idx').on(table.githubId),
nameIdx: index('name_idx').on(table.name),
};
});
```
##### 2.3.3.4 Optimisation des Formats de Données
Les types de données PostgreSQL seront optimisés pour chaque cas d'usage:
- Utilisation de `UUID` pour les identifiants (UUIDv7 pour l'ordre chronologique)
- Type `JSONB` pour les données flexibles et semi-structurées
- Types `ENUM` PostgreSQL natifs pour les valeurs fixes (genres, niveaux d'aisance)
- Type `TEXT` avec contraintes pour les chaînes de caractères variables
- Types `TIMESTAMP WITH TIME ZONE` pour les dates avec gestion des fuseaux horaires
- Utilisation de `NUMERIC` pour les valeurs nécessitant une précision exacte
Ces optimisations permettront d'améliorer les performances des requêtes, de réduire l'empreinte mémoire et d'assurer l'intégrité des données.
##### 2.3.3.5 Modèle Simplifié pour Utilisateurs Non-Techniques
Le diagramme ci-dessous présente une version simplifiée du modèle de données, conçue pour être facilement compréhensible par des personnes non-techniques:
```mermaid
flowchart TD
User[Utilisateur] -->|Crée et gère| Project[Projet]
Project -->|Contient| Person[Personnes]
Project -->|Organise en| Group[Groupes]
Project -->|Associé à| Tag[Tags/Étiquettes]
Person -->|Appartient à| Group
Person -->|Associée à| Tag
Admin[Administrateur] -->|Gère| Tag
classDef user fill:#9c27b0,stroke:#ffffff,stroke-width:2px
classDef project fill:#3f51b5,stroke:#ffffff,stroke-width:2px
classDef person fill:#4caf50,stroke:#ffffff,stroke-width:2px
classDef group fill:#f44336,stroke:#ffffff,stroke-width:2px
classDef tag fill:#ff9800,stroke:#ffffff,stroke-width:2px
classDef admin fill:#00bcd4,stroke:#ffffff,stroke-width:2px
class User user
class Project project
class Person person
class Group group
class Tag tag
class Admin admin
```
Ce diagramme illustre les concepts clés de l'application:
- Un **Utilisateur** crée et gère des projets
- Chaque **Projet** contient des personnes et des groupes et peut être associé à des tags
- Les **Personnes** sont organisées en groupes et peuvent être associées à des tags
- Les **Groupes** sont composés de personnes
- Les **Tags** permettent de catégoriser les personnes et les projets selon différents critères
- L'**Administrateur** gère les tags globaux utilisés dans toute l'application
Cette représentation simplifiée permet aux parties prenantes non-techniques de comprendre facilement la structure générale de l'application sans avoir à se plonger dans les détails techniques du modèle de données.
## 3. Spécifications Fonctionnelles
### 3.1 Interface Utilisateur
#### 3.1.1 Principes Généraux
- Approche "mobile first" pour l'ensemble du site
- Interface de type dashboard pour le frontend
- Design responsive s'adaptant à tous les appareils (mobile, tablette, desktop)
- Accessibilité conforme aux normes WCAG 2.1 niveau AA
- Thème clair/sombre avec détection automatique des préférences système
#### 3.1.2 Structure Globale
- **Header**:
- Logo et nom de l'application
- Navigation principale
- Fonctionnalités de gestion de compte et de connexion
- Recherche de projets enregistrés
- Indicateur de notifications
- **Footer**:
- Liens vers les pages légales obligatoires (CGU, Politique de confidentialité, Mentions légales)
- Liens vers la documentation
- Informations de contact
- Sélecteur de langue
- **Page d'accueil**:
- Présentation des fonctionnalités aux utilisateurs anonymes
- Bac à sable interactif pour tester l'application sans inscription
- Témoignages et cas d'utilisation
- Appel à l'action pour la création de compte
- **Dashboard**:
- Vue d'ensemble des projets de l'utilisateur
- Statistiques et métriques sur l'utilisation
- Accès rapide aux fonctionnalités principales
- Notifications et alertes
#### 3.1.3 Composants UI Spécifiques
- Utilisation de ShadcnUI pour les composants de base (boutons, champs de formulaire, modales, etc.)
- Animations et transitions avec Motion pour améliorer l'expérience utilisateur
- Formulaires optimisés avec React Hook Form pour une validation instantanée
- Visualisations interactives pour les groupes créés
### 3.2 Gestion des Utilisateurs
#### 3.2.1 Inscription et Authentification
- Création de compte utilisateur obligatoire pour utiliser pleinement les fonctionnalités
- Authentification via OAuth2.0 avec GitHub:
- Flux d'authentification sécurisé avec redirection vers GitHub
- Récupération des informations de base du profil (nom, avatar) depuis l'API GitHub
- Possibilité d'étendre à d'autres fournisseurs d'identité dans le futur
- Gestion des sessions utilisateur avec JWT (JSON Web Tokens):
- Token d'accès avec durée de validité limitée (15 minutes)
- Token de rafraîchissement pour prolonger la session (validité de 7 jours)
- Révocation des tokens en cas de déconnexion ou de suspicion de compromission
- Gestion des autorisations basée sur les rôles (RBAC):
- Rôle administrateur pour la gestion globale:
- Gestion des tags globaux (création, modification, suppression)
- Attribution des types de tags (PROJECT, PERSON)
- Surveillance de l'utilisation des tags
- Gestion des utilisateurs et de leurs droits
- Rôle utilisateur standard pour la création et gestion de projets personnels
- Rôle invité pour l'accès en lecture seule à des projets partagés
#### 3.2.2 Profil Utilisateur
- Gestion des informations personnelles:
- Modification du nom d'affichage
- Affichage de l'avatar récupéré depuis l'API GitHub
- Gestion des préférences (notifications, thème, langue)
- Gestion du consentement RGPD (timestamp)
- Tableau de bord personnel:
- Vue d'ensemble des projets créés
- Statistiques d'utilisation
- Activité récente
- Gestion des notifications:
- Alertes système
- Rappels pour les projets en cours
- Notifications de partage
#### 3.2.3 Gestion des Données Utilisateur
- Accès à l'historique des projets de groupe enregistrés
- Export des données personnelles au format JSON ou CSV
- Suppression de compte avec option de conservation ou suppression des projets
- Conformité RGPD avec droit à l'oubli et portabilité des données
### 3.3 Système d'Administration
#### 3.3.1 Interface d'Administration
- Tableau de bord administrateur dédié:
- Vue d'ensemble de l'utilisation de l'application
- Statistiques sur les utilisateurs, projets, et tags
- Alertes et notifications système
- Accès sécurisé réservé aux utilisateurs avec le rôle administrateur
- Interface distincte de l'application principale
#### 3.3.2 Gestion des Tags Globaux
- Interface de création et gestion des tags:
- Création de nouveaux tags avec nom et couleur
- Définition du type de tag (PROJECT ou PERSON)
- Modification des tags existants
- Suppression des tags non utilisés
- Visualisation de l'utilisation des tags:
- Nombre de projets et personnes associés à chaque tag
- Statistiques d'utilisation par utilisateur
- Possibilité de fusionner des tags similaires
- Exportation de la liste des tags au format CSV
#### 3.3.3 Gestion des Utilisateurs
- Liste complète des utilisateurs avec filtres et recherche
- Modification des rôles utilisateur (administrateur, utilisateur standard, invité)
- Surveillance de l'activité des utilisateurs
- Possibilité de désactiver temporairement un compte utilisateur
- Vérification du statut RGPD des utilisateurs
### 3.4 Création et Gestion de Groupes
#### 3.4.1 Création de Projet de Groupe
- Possibilité de créer une liste de personnes qui seront placées dans les groupes
- Attribution de "tags" aux personnes
- Définition d'échelles de niveau personnalisées
- Nom de projet unique à l'échelle de l'utilisateur
#### 3.4.2 Attributs des Personnes
Chaque personne dans le système sera caractérisée par les attributs suivants :
- Prénom
- Nom
- Genre (Masculin, féminin, non binaire)
- Niveau d'aisance technique
- Expérience préalable en formation technique
- Capacité d'expression en français
- Niveau d'aisance à l'oral (timide, réservé, à l'aise)
- Âge
#### 3.4.3 Interface de Création Manuelle
- Affichage de la liste des personnes sur le côté (format desktop minimum)
- Possibilité de réaliser manuellement les groupes
- Option de renommer chaque groupe manuellement
#### 3.4.4 Assistant à la Création de Groupe
- Fonctionnalité de création aléatoire de groupes
- L'utilisateur définit le nombre de groupes souhaités
- Attribution obligatoire d'un nom à chaque groupe
- Sélection de presets pour la génération de groupes équilibrés:
- Groupes équilibrés pour la progression du niveau
- Groupes équilibrés par niveau de compétence
### 3.5 Communication en Temps Réel
- Utilisation de SocketIO pour les mises à jour en temps réel
- Notification des modifications de groupes aux utilisateurs concernés
- Collaboration possible entre utilisateurs sur un même projet de groupe
## 4. Exigences Techniques
### 4.1 Développement
- Respect des principes SOLID
- Application des conventions de nommage standard
- Tests unitaires et tests e2e de l'API
- Documentation technique complète
### 4.2 Sécurité et Conformité
#### 4.2.1 Protection des Données
- Chiffrement des données sensibles en base de données
- Hachage sécurisé des mots de passe avec @node-rs/argon2:
- Utilisation de sel unique pour chaque utilisateur
- Paramètres de hachage conformes aux recommandations OWASP
- Implémentation en Rust pour des performances optimales et une résistance aux attaques par force brute
- Gestion des tokens JWT avec la bibliothèque jose:
- Signatures avec algorithme RS256
- Rotation des clés de signature
- Validation complète des tokens (signature, expiration, émetteur)
- Mise en place de mécanismes de défense contre les attaques courantes:
- Protection CSRF (Cross-Site Request Forgery)
- Protection XSS (Cross-Site Scripting)
- Protection contre les injections SQL
- Rate limiting pour prévenir les attaques par force brute
#### 4.2.2 Conformité RGPD
- Conformité aux exigences RGPD et aux lois françaises:
- Minimisation des données collectées
- Finalité claire de la collecte de données
- Durée de conservation limitée et justifiée
- Mise en œuvre des droits des utilisateurs:
- Droit d'accès aux données personnelles
- Droit de rectification
- Droit à l'effacement (droit à l'oubli)
- Droit à la portabilité des données
- Droit d'opposition au traitement
- Renouvellement du consentement utilisateur tous les 13 mois pour les conditions générales d'utilisation et les cookies
- Registre des activités de traitement
- Procédure de notification en cas de violation de données
#### 4.2.3 Audit et Traçabilité
- Journalisation des actions sensibles:
- Connexions et déconnexions
- Modifications de données importantes
- Accès aux données personnelles
- Conservation des logs pendant une durée conforme aux exigences légales
- Système d'alerte en cas d'activité suspecte
- Audits de sécurité réguliers
### 4.3 Performance et Monitoring
#### 4.3.1 Objectifs de Performance
- Temps de chargement initial < 2 secondes (95ème percentile)
- Temps de réponse API < 300ms (95ème percentile)
- Temps d'exécution des requêtes SQL complexes < 100ms (95ème percentile)
- Disponibilité > 99.9%
- Support de 1000 utilisateurs simultanés minimum
- Utilisation efficiente des index pour les requêtes fréquentes
#### 4.3.2 Optimisation de la Base de Données
- Analyse régulière des plans d'exécution des requêtes avec `EXPLAIN ANALYZE`
- Mise en place d'un processus de maintenance automatisé:
- VACUUM régulier pour récupérer l'espace disque
- ANALYZE pour mettre à jour les statistiques du planificateur
- REINDEX pour maintenir l'efficacité des index
- Partitionnement des tables volumineuses pour améliorer les performances
- Utilisation de la mise en cache des requêtes fréquentes
- Optimisation des requêtes N+1 via l'utilisation appropriée des jointures et des relations
#### 4.3.3 Monitoring et Observabilité
- Mise en place d'outils de monitoring:
- Métriques d'application (temps de réponse, taux d'erreur)
- Métriques système (CPU, mémoire, disque)
- Métriques utilisateur (nombre de sessions, actions effectuées)
- Métriques de base de données:
- Temps d'exécution des requêtes
- Utilisation des index
- Taux de cache hit/miss
- Nombre de connexions actives
- Taille des tables et des index
- Alerting automatique en cas de dégradation des performances
- Tableau de bord de supervision pour l'équipe technique
- Analyse des logs centralisée
- Traçage des requêtes lentes avec pg_stat_statements