Cours NestJS · Module 12: Conteneurs et CI/CD
PROJET : Application complète de l'Empire romain
Dans cette leçon6
Ave, légat ! Le consul Caesar.js t'a confié le plus grand défi - créer une application complète pour gérer l'Empire romain. Ce sera un véritable test de toutes les compétences que tu as acquises pendant ton entraînement NestJS !
Spécification du projet : Imperium Management System
Ton application doit être un système complet de gestion des légions romaines, prenant en charge :
1. Système d'utilisateurs et autorisation
- Inscription et connexion des légionnaires avec différents rangs
- Authentification JWT avec refresh tokens
- Contrôle d'accès basé sur les rôles (RBAC)
- Authentification multi-facteurs pour les consuls
2. Gestion des légions
- Légions - différents types, capacités, équipements
- Soldats - légionnaires avec compétences et historiques
- Campagnes - expéditions de tributs avec planification d'itinéraire
- Forts - bases opérationnelles avec ressources
3. Système de tributs
- Gestion de l'inventaire - coffres, tributs, artéfacts
- Système d'échange - échange entre légions
- Salle des enchères - enchères sur des objets rares
- Suivi de la valeur - suivi de la valeur au fil du temps
4. Communication et coordination
- Messagerie en temps réel - communication WebSocket
- Annonces de légion - annonces pour les légions
- Coordination des batailles - coordination pendant les batailles
- Alertes météo - notifications météorologiques
5. Analytique et rapports
- Tableaux de bord de performance - statistiques des légions
- Rapports de tributs - analyses des profits
- Analytique des légions - efficacité des légions
- Prédictions (predictive insights) - prédictions pilotées par l'IA
Structure technique
Modules principaux
1// app.module.ts
2import { Module } from '@nestjs/common';
3import { ConfigModule } from '@nestjs/config';
4import { TypeOrmModule } from '@nestjs/typeorm';
5import { GraphQLModule } from '@nestjs/graphql';
6import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
7import { BullModule } from '@nestjs/bull';
8import { CacheModule } from '@nestjs/cache-manager';
9import { EventEmitterModule } from '@nestjs/event-emitter';
10
11// Modules personnalisés
12import { AuthModule } from './auth/auth.module';
13import { LegionModule } from './legion/legion.module';
14import { LegionaryModule } from './legionary/legionary.module';
15import { TributeModule } from './tribute/tribute.module';
16import { CampaignModule } from './campaign/campaign.module';
17import { CommunicationModule } from './communication/communication.module';
18import { AnalyticsModule } from './analytics/analytics.module';
19import { WebsocketModule } from './websocket/websocket.module';
20
21// Configuration
22import { databaseConfig, redisConfig, appConfig } from './config';
23import { validationSchema } from './config/validation.schema';
24
25@Module({
26 imports: [
27 // Configuration
28 ConfigModule.forRoot({
29 isGlobal: true,
30 load: [databaseConfig, redisConfig, appConfig],
31 validationSchema,
32 }),
33
34 // Base de données
35 TypeOrmModule.forRootAsync({
36 useFactory: (configService: ConfigService) => configService.get('database'),
37 inject: [ConfigService],
38 }),
39
40 // GraphQL
41 GraphQLModule.forRoot<ApolloDriverConfig>({
42 driver: ApolloDriver,
43 autoSchemaFile: 'schema.gql',
44 subscriptions: {
45 'graphql-ws': true,
46 },
47 context: ({ req, connection }) =>
48 connection ? { req: connection.context } : { req },
49 }),
50
51 // Redis et mise en cache
52 BullModule.forRootAsync({
53 useFactory: (configService: ConfigService) => configService.get('redis'),
54 inject: [ConfigService],
55 }),
56
57 CacheModule.registerAsync({
58 useFactory: (configService: ConfigService) => ({
59 store: 'redis',
60 ...configService.get('redis'),
61 ttl: 300, // 5 minutes par défaut
62 }),
63 inject: [ConfigService],
64 }),
65
66 // Système d'événements
67 EventEmitterModule.forRoot(),
68
69 // Modules fonctionnels
70 AuthModule,
71 LegionModule,
72 LegionaryModule,
73 TributeModule,
74 CampaignModule,
75 CommunicationModule,
76 AnalyticsModule,
77 WebsocketModule,
78 ],
79 providers: [
80 // Guards globaux
81 {
82 provide: APP_GUARD,
83 useClass: JwtAuthGuard,
84 },
85 {
86 provide: APP_GUARD,
87 useClass: RolesGuard,
88 },
89 // Interceptors globaux
90 {
91 provide: APP_INTERCEPTOR,
92 useClass: LoggingInterceptor,
93 },
94 {
95 provide: APP_INTERCEPTOR,
96 useClass: TransformInterceptor,
97 },
98 // Filters globaux
99 {
100 provide: APP_FILTER,
101 useClass: AllExceptionsFilter,
102 },
103 ],
104})
105export class AppModule {}Implémentation de fonctionnalités avancées
1// auth/strategies/jwt.strategy.ts
2@Injectable()
3export class JwtStrategy extends PassportStrategy(Strategy) {
4 constructor(
5 private configService: ConfigService,
6 private userService: UserService,
7 ) {
8 super({
9 jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
10 ignoreExpiration: false,
11 secretOrKey: configService.get<string>('jwt.secret'),
12 });
13 }
14
15 async validate(payload: any) {
16 const user = await this.userService.findById(payload.sub);
17 if (!user || !user.isActive) {
18 throw new UnauthorizedException('Legionary not found or inactive');
19 }
20 return user;
21 }
22}
23
24// websocket/legion-communication.gateway.ts
25@WebSocketGateway({
26 cors: {
27 origin: '*',
28 },
29})
30export class LegionCommunicationGateway
31 implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
32
33 @WebSocketServer()
34 server: Server;
35
36 private logger: Logger = new Logger('LegionCommunicationGateway');
37 private legionaryConnections = new Map<string, string>(); // socketId -> legionaryId
38
39 afterInit(server: Server) {
40 this.logger.log('Legion Communication Gateway initialized');
41 }
42
43 async handleConnection(client: Socket, ...args: any[]) {
44 try {
45 const token = client.handshake.auth.token;
46 const legionary = await this.authService.validateToken(token);
47
48 this.legionaryConnections.set(client.id, legionary.id);
49 client.join(`cohort_${legionary.cohortId}`);
50 client.join(`legion_${legionary.legionId}`);
51
52 this.server.to(`cohort_${legionary.cohortId}`).emit('legionaryJoined', {
53 legionary: legionary.name,
54 message: `${legionary.name} joined the communication channel`,
55 });
56
57 this.logger.log(`Legionary ${legionary.name} connected to legion communication`);
58 } catch (error) {
59 this.logger.error('Connection failed:', error);
60 client.disconnect();
61 }
62 }
63
64 handleDisconnect(client: Socket) {
65 const legionaryId = this.legionaryConnections.get(client.id);
66 if (legionaryId) {
67 this.legionaryConnections.delete(client.id);
68 this.logger.log(`Legionary ${legionaryId} disconnected`);
69 }
70 }
71
72 @SubscribeMessage('cohortMessage')
73 handleCohortMessage(client: Socket, payload: any) {
74 const legionaryId = this.legionaryConnections.get(client.id);
75 const legionary = this.userService.findById(legionaryId);
76
77 client.to(`cohort_${legionary.cohortId}`).emit('cohortMessage', {
78 from: legionary.name,
79 message: payload.message,
80 timestamp: new Date(),
81 });
82 }
83
84 @SubscribeMessage('legionAnnouncement')
85 @UseGuards(WsJwtGuard, WsRolesGuard)
86 @Roles('CENTURION', 'CONSUL')
87 handleLegionAnnouncement(client: Socket, payload: any) {
88 const legionaryId = this.legionaryConnections.get(client.id);
89 const legionary = this.userService.findById(legionaryId);
90
91 this.server.to(`legion_${legionary.legionId}`).emit('legionAnnouncement', {
92 from: legionary.name,
93 rank: legionary.rank,
94 message: payload.message,
95 priority: payload.priority || 'normal',
96 timestamp: new Date(),
97 });
98 }
99}
100
101// analytics/services/advanced-analytics.service.ts
102@Injectable()
103export class AdvancedAnalyticsService {
104 constructor(
105 private dataSource: DataSource,
106 @InjectQueue('analytics') private analyticsQueue: Queue,
107 ) {}
108
109 async generateLegionPerformanceReport(legionId: number, period: string) {
110 const job = await this.analyticsQueue.add('legion-performance', {
111 legionId,
112 period,
113 });
114
115 return { jobId: job.id };
116 }
117
118 async getPredictiveTributeInsights(legionId: number) {
119 // Prédictions par machine learning (exemple TensorFlow.js)
120 const historicalData = await this.getHistoricalTributeData(legionId);
121 const predictions = await this.mlPredict(historicalData);
122
123 return {
124 predictedTributeValue: predictions.value,
125 confidence: predictions.confidence,
126 recommendedActions: predictions.actions,
127 timeframe: '30 days',
128 };
129 }
130
131 private async mlPredict(data: any[]) {
132 // Exemple de modèle ML simple
133 const values = data.map(d => d.value);
134 const trend = this.calculateTrend(values);
135 const seasonality = this.calculateSeasonality(data);
136
137 return {
138 value: trend.nextPrediction * seasonality.factor,
139 confidence: Math.min(trend.confidence * seasonality.confidence, 0.95),
140 actions: this.generateRecommendations(trend, seasonality),
141 };
142 }
143
144 @Cron('0 2 * * *') // Chaque jour à 2h00 du matin
145 async generateDailyReports() {
146 const legions = await this.getActiveLegions();
147
148 for (const legion of legions) {
149 await this.analyticsQueue.add('daily-report', {
150 legionId: legion.id,
151 date: new Date(),
152 });
153 }
154 }
155}
156
157// tribute/services/auction.service.ts
158@Injectable()
159export class AuctionService {
160 constructor(
161 private auctionRepository: AuctionRepository,
162 private eventEmitter: EventEmitter2,
163 @InjectQueue('auction') private auctionQueue: Queue,
164 ) {}
165
166 async createAuction(tributeId: number, sellerId: number, startingPrice: number, duration: number) {
167 const auction = await this.auctionRepository.create({
168 tributeId,
169 sellerId,
170 startingPrice,
171 currentPrice: startingPrice,
172 endTime: new Date(Date.now() + duration * 1000),
173 status: 'ACTIVE',
174 });
175
176 // Planifie la fin de l'enchère
177 await this.auctionQueue.add(
178 'end-auction',
179 { auctionId: auction.id },
180 { delay: duration * 1000 }
181 );
182
183 this.eventEmitter.emit('auction.created', auction);
184 return auction;
185 }
186
187 async placeBid(auctionId: number, bidderId: number, amount: number) {
188 const auction = await this.auctionRepository.findById(auctionId);
189
190 if (auction.status !== 'ACTIVE' || new Date() > auction.endTime) {
191 throw new BadRequestException('Auction is not active');
192 }
193
194 if (amount <= auction.currentPrice) {
195 throw new BadRequestException('Bid must be higher than current price');
196 }
197
198 // Anti-sniping : prolonge l'enchère si une offre arrive dans les 5 dernières minutes
199 const timeLeft = auction.endTime.getTime() - Date.now();
200 if (timeLeft < 5 * 60 * 1000) {
201 auction.endTime = new Date(auction.endTime.getTime() + 5 * 60 * 1000);
202 }
203
204 auction.currentPrice = amount;
205 auction.currentBidderId = bidderId;
206
207 await this.auctionRepository.save(auction);
208
209 this.eventEmitter.emit('auction.bid', {
210 auctionId,
211 bidderId,
212 amount,
213 timeLeft: Math.max(0, timeLeft),
214 });
215
216 return auction;
217 }
218
219 @OnEvent('auction.end')
220 async handleAuctionEnd(auctionId: number) {
221 const auction = await this.auctionRepository.findById(auctionId);
222
223 if (auction.currentBidderId) {
224 await this.transferTribute(auction.tributeId, auction.currentBidderId);
225 await this.transferFunds(auction.currentBidderId, auction.sellerId, auction.currentPrice);
226
227 auction.status = 'COMPLETED';
228 auction.winnerId = auction.currentBidderId;
229 } else {
230 auction.status = 'NO_BIDS';
231 }
232
233 await this.auctionRepository.save(auction);
234 this.eventEmitter.emit('auction.completed', auction);
235 }
236}Tâches à réaliser
Étape 1 : Architecture de base (Semaine 1)
- Configuration du projet - NestJS, TypeORM, Redis
- Système d'authentification - JWT, refresh tokens, accès basé sur les rôles
- Entités de base - bases de User, Legion, Cohort, Tribute
- Endpoints de l'API - opérations CRUD pour les entités de base
- Tests de base - tests unitaires pour les services
Étape 2 : Fonctionnalités métier (Semaine 2)
- Système de campagnes - planifier et exécuter des campagnes
- Système d'échange - échange de tributs entre légionnaires
- Gestion des légions - gérer les légions et les compétences
- Système de notifications - notifications en temps réel
- Validation avancée - règles métier complexes
Étape 3 : Temps réel et avancé (Semaine 3)
- Intégration WebSocket - communication en temps réel
- Système d'enchères - enchères de tributs
- Tableau de bord analytique - statistiques et métriques
- Tâches d'arrière-plan - traitement de tâches asynchrones
- Optimisation des performances - mise en cache, index, optimisations
Étape 4 : Production et avancé (Semaine 4)
- Implémentation GraphQL - API alternative
- Rate limiting - protection contre le spam
- Monitoring - health checks, métriques, alertes
- Déploiement - Docker, CI/CD, gestion des environnements
- Sécurité avancée - en-têtes de sécurité, CORS, chiffrement des données
Critères d'évaluation
Fonctionnalité (25%)
- Toutes les fonctionnalités requises fonctionnent correctement
- Système complet d'autorisation et de permissions
- Communication en temps réel entre les utilisateurs
- Fonctionnalités métier avancées (enchères, missions, échange)
Qualité du code (25%)
- Architecture et structure de projet lisibles
- Utilisation appropriée des patrons de conception
- Style de code et conventions de nommage cohérents
- Gestion des erreurs et validation appropriées
Performance et évolutivité (20%)
- Optimisation des requêtes de base de données
- Stratégies de mise en cache efficaces
- Traitement des tâches d'arrière-plan
- Rate limiting et gestion des ressources
Tests (15%)
- Tests unitaires complets (>80% de couverture)
- Tests d'intégration pour les flux clés
- Tests E2E pour les fonctionnalités principales
- Tests de performance
Documentation (15%)
- Documentation de l'API (Swagger/OpenAPI)
- README avec instructions d'installation et de déploiement
- Documentation de l'architecture
- Commentaires de code là où c'est nécessaire
Points bonus
- Souscriptions GraphQL pour les mises à jour en temps réel
- Machine learning pour prédire la valeur des tributs
- Architecture microservices avec files de messages
- Monitoring avancé avec Prometheus/Grafana
- Application mobile communiquant avec l'API
Exemples d'endpoints de l'API
1// Gestion des légions
2GET /api/legions # Liste les légions
3POST /api/legions # Crée une légion
4GET /api/legions/:id # Détails d'une légion
5PUT /api/legions/:id # Met à jour une légion
6DELETE /api/legions/:id # Supprime une légion
7
8// Gestion des cohortes
9GET /api/cohorts # Liste les cohortes
10POST /api/cohorts # Ajoute une cohorte
11GET /api/cohorts/:id # Détails d'une cohorte
12PUT /api/cohorts/:id # Met à jour une cohorte
13POST /api/cohorts/:id/assign-legionaries # Assigne des légionnaires
14POST /api/cohorts/:id/campaigns # Crée une campagne
15
16// Système de tributs
17GET /api/tributes # Liste les tributs
18POST /api/tributes # Ajoute un tribut
19GET /api/tributes/:id # Détails d'un tribut
20PUT /api/tributes/:id/transfer # Transfère un tribut
21POST /api/tributes/:id/auction # Met aux enchères
22
23// Enchères
24GET /api/auctions # Enchères actives
25POST /api/auctions/:id/bid # Place une offre
26GET /api/auctions/:id/history # Historique des offres
27
28// Analytique
29GET /api/analytics/legion/:id # Statistiques d'une légion
30GET /api/analytics/tribute-trends # Tendances de la valeur des tributs
31GET /api/analytics/legion-performance # Performance des légions
32
33// Événements WebSocket en temps réel
34cohort:message # Messages dans une cohorte
35legion:announcement # Annonces de légion
36auction:bid # Nouvelle offre d'enchère
37campaign:update # Mise à jour de campagne
38tribute:discovered # Tribut découvertCeci est le plus grand défi au service de l'Empire romain ! Bonne chance, légat - l'Empire compte sur toi !
Tu as repéré une erreur dans cette leçon ?