Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Kontrolery - centurionowie struktur Imperium

Salve, doświadczony legioniście! Teraz gdy znasz już architekturę naszego Imperium Romanum, czas poznać szczegóły działania kontrolerów - centurionów poszczególnych struktur. To oni przyjmują rozkazy z zewnętrznego świata i zarządzają operacjami w swoich domenach!

Kontrolery w NestJS

Kontrolery to klasy odpowiedzialne za obsługę żądań HTTP i zwracanie odpowiedzi. To jak centurionowie różnych struktur Imperium - każdy specjalizuje się w swojej dziedzinie i wie, jak reagować na różne sytuacje.

Podstawowy kontroler struktury legionu

1import { Controller, Get, Post, Put, Delete, Body, Param, Query } from '@nestjs/common';
2
3@Controller('legion') // Prefiks wszystkich tras: /legion
4export class LegionController {
5 constructor(private readonly legionService: LegionService) {}
6
7 // GET /legion - lista całego legionu
8 @Get()
9 getAllLegionaries() {
10 return this.legionService.findAll();
11 }
12
13 // GET /legion/5 - konkretny legionista
14 @Get(':id')
15 getLegionary(@Param('id') id: string) {
16 return this.legionService.findById(+id);
17 }
18
19 // POST /legion - rekrutacja nowego legionisty
20 @Post()
21 recruitLegionary(@Body() newLegionary: CreateLegionaryDto) {
22 return this.legionService.recruit(newLegionary);
23 }
24
25 // PUT /legion/5 - awans legionisty
26 @Put(':id/promote')
27 promoteLegionary(@Param('id') id: string, @Body() promotion: PromotionDto) {
28 return this.legionService.promote(+id, promotion);
29 }
30
31 // DELETE /legion/5 - usunięcie z legionu (niestety...)
32 @Delete(':id')
33 dismissLegionary(@Param('id') id: string) {
34 return this.legionService.dismiss(+id);
35 }
36}

Dekoratory HTTP - sygnały imperialne

Każdy dekrator odpowiada różnym typom sygnałów, jakie może wysłać prowincja:

Podstawowe metody HTTP

1@Controller('tributes')
2export class TributesController {
3
4 // GET - pobierz informacje (jak zapytanie o status)
5 @Get()
6 getAllTributes() {
7 return 'List of all collected tributes';
8 }
9
10 // POST - utwórz nowy zasób (jak zgłoszenie nowego trybutu)
11 @Post()
12 reportNewTribute(@Body() tribute: CreateTributeDto) {
13 return `New tribute reported: ${tribute.name}`
1 }
2
3 // PUT - zaktualizuj cały trybut (jak kompletna aktualizacja rejestru)
4 @Put(':id')
5 updateTribute(@Param('id') id: string, @Body() tribute: UpdateTributeDto) {
6 return `Tribute ${id} completely updated`;
7 }
8
9 // PATCH - częściowa aktualizacja (jak poprawka w rejestrze)
10 @Patch(':id')
11 modifyTribute(@Param('id') id: string, @Body() changes: Partial<UpdateTributeDto>) {
12 return `Tribute ${id} partially modified`;
13 }
14
15 // DELETE - usuń trybut (jak oznaczenie trybutu jako zebranego)
16 @Delete(':id')
17 markTributeAsCollected(@Param('id') id: string) {
18 return `Tribute ${id} marked as collected and removed from active list`;
19 }
20}

Parametry żądań - szczegóły rozkazów

Parametry z URL (Route Parameters)

1@Controller('forum')
2export class ForumController {
3
4 // /forum/ballista/5/attack - konkretna ballista
5 @Post(':section/:id/:action')
6 controlForumSection(
7 @Param('section') section: string,
8 @Param('id') id: string,
9 @Param('action') action: string,
10 ) {
11 return `Executing ${action} on ${section} #${id}`;
12 }
13
14 // Walidacja parametrów
15 @Get('legion/:legionaryId')
16 getLegionaryInfo(@Param('legionaryId', ParseIntPipe) legionaryId: number) {
17 // ParseIntPipe automatycznie konwertuje i waliduje
18 return `Information about legionary with ID: ${legionaryId}`;
19 }
20}

Query Parameters - dodatkowe instrukcje

1@Controller('engineering')
2export class EngineeringController {
3
4 // /engineering/route?from=rome&to=province&speed=fast
5 @Get('route')
6 calculateRoute(
7 @Query('from') fromCity: string,
8 @Query('to') toDestination: string,
9 @Query('speed') speed?: string,
10 ) {
11 return {
12 route: `From ${fromCity} to ${toDestination}`,
13 speed: speed || 'normal',
14 estimatedTime: this.calculateTime(fromCity, toDestination, speed),
15 };
16 }
17
18 // Walidacja query parameters
19 @Get('search')
20 searchTributes(
21 @Query('lat', ParseFloatPipe) latitude: number,
22 @Query('lng', ParseFloatPipe) longitude: number,
23 @Query('radius', new ParseIntPipe({ optional: true })) radius?: number,
24 ) {
25 return `Searching for tributes at coordinates: ${latitude}, ${longitude}`;
26 }
27}

Body - ładunek wiadomości

1@Controller('campaign')
2export class CampaignController {
3
4 // Pojedynczy obiekt w body
5 @Post('attack')
6 initiateAttack(@Body() attackPlan: AttackPlanDto) {
7 return `Attack initiated with ${attackPlan.weaponType} against ${attackPlan.target}`;
8 }
9
10 // Walidacja i transformacja body
11 @Post('formation')
12 setCampaignFormation(@Body(ValidationPipe) formation: CampaignFormationDto) {
13 return `Campaign formation set: ${formation.type}`;
14 }
15
16 // Częściowa aktualizacja z body
17 @Patch('strategy')
18 updateStrategy(@Body() updates: Partial<CampaignStrategyDto>) {
19 return `Strategy updated with: ${Object.keys(updates).join(', ')}`;
20 }
21}

Headers - sygnały imperialne

1@Controller('communication')
2export class CommunicationController {
3
4 @Post('message')
5 sendMessage(
6 @Body() message: MessageDto,
7 @Headers('authorization') authToken: string,
8 @Headers('forum-id') forumId: string,
9 ) {
10 return `Message sent from forum ${forumId}`;
11 }
12
13 // Wszystkie headers naraz
14 @Get('status')
15 getForumStatus(@Headers() headers: Record<string, string>) {
16 return {
17 requestOrigin: headers['user-agent'],
18 timestamp: new Date().toISOString(),
19 };
20 }
21}

DTOs - rejestry imperialne

1import { IsString, IsNumber, IsOptional, IsEnum, Min, Max } from 'class-validator';
2
3export class CreateLegionaryDto {
4 @IsString()
5 name: string;
6
7 @IsEnum(['consul', 'engineer', 'ballista-operator', 'cook', 'medicus'])
8 specialization: string;
9
10 @IsNumber()
11 @Min(1)
12 @Max(10)
13 experienceLevel: number;
14
15 @IsOptional()
16 @IsString()
17 backstory?: string;
18}
19
20export class AttackPlanDto {
21 @IsString()
22 target: string;
23
24 @IsEnum(['ballista', 'pilum', 'gladius', 'testudo'])
25 weaponType: string;
26
27 @IsNumber()
28 @Min(1)
29 @Max(100)
30 intensity: number;
31}

Zaawansowane funkcje kontrolerów

Async/Await - operacje wymagające czasu

1@Controller('exploration')
2export class ExplorationController {
3
4 @Get('province/:id')
5 async exploreProvince(@Param('id') provinceId: string) {
6 // Symulacja długotrwałej eksploracji
7 const tributeMap = await this.mapService.getTributeMap(provinceId);
8 const weather = await this.weatherService.checkConditions(provinceId);
9 const legion = await this.legionService.getAvailableExplorers();
10
11 return {
12 province: provinceId,
13 tributePossibility: tributeMap.likelihood,
14 weather: weather.condition,
15 readyLegion: legion.length,
16 };
17 }
18
19 @Post('campaign')
20 async launchCampaign(@Body() campaign: CampaignDto) {
21 try {
22 const result = await this.campaignService.launch(campaign);
23 return { success: true, campaign: result };
24 } catch (error) {
25 throw new BadRequestException(`Campaign failed: ${error.message}`);
26 }
27 }
28}

Response customization - dostosowanie odpowiedzi

1import { Res, HttpStatus } from '@nestjs/common';
2import { Response } from 'express';
3
4@Controller('combat')
5export class CombatController {
6 
7 @Post('duel')
8 async startDuel(@Body() duelRequest: DuelDto, @Res() response: Response) {
9 const result = await this.combatService.simulateDuel(duelRequest);
10 
11 if (result.winner === 'player') {
12 return response.status(HttpStatus.OK).json({
13 message: 'Victory! You won the duel!',
14 reward: result.reward,
15 });
16 } else {
17 return response.status(HttpStatus.ACCEPTED).json({
18 message: 'Defeat... but you fought bravely!',
19 experience: result.experienceGained,
20 });
21 }
22 }
23}

Request/Response intercepting

1@Controller('inventory')
2export class InventoryController {
3
4 @Get('weapons')
5 getWeapons(@Req() request: Request) {
6 const userAgent = request.headers['user-agent'];
7 const isFromMobileForum = userAgent?.includes('MobileForum');
8
9 const weapons = this.inventoryService.getWeapons();
10
11 if (isFromMobileForum) {
12 // Simplified response for mobile forums
13 return weapons.map(w => ({ name: w.name, damage: w.damage }));
14 }
15
16 return weapons; // Full details for desktop command centers
17 }
18}

Obsługa błędów - problemy w kampanii

1import {
2 NotFoundException,
3 BadRequestException,
4 ForbiddenException,
5 ConflictException
6} from '@nestjs/common';
7
8@Controller('tributes')
9export class TributesController {
10
11 @Get(':id')
12 async getTribute(@Param('id') id: string) {
13 const tribute = await this.tributesService.findById(+id);
14
15 if (!tribute) {
16 throw new NotFoundException(`Tribute with ID ${id} not found in our registries!`);
17 }
18
19 return tribute;
20 }
21
22 @Post()
23 async claimTribute(@Body() claim: TributeClaimDto) {
24 const existingClaim = await this.tributesService.findClaim(claim.province);
25
26 if (existingClaim) {
27 throw new ConflictException('This tribute has already been claimed by another legion!');
28 }
29
30 if (!claim.legionSize || claim.legionSize < 3) {
31 throw new BadRequestException('You need at least 3 legionaries to claim a tribute!');
32 }
33
34 return this.tributesService.claimTribute(claim);
35 }
36}

Route patterns - zaawansowane ścieżki

1@Controller('legion-forces')
2export class LegionForcesController {
3
4 // Wildcard routes
5 @Get('cohort/*/status')
6 getAnyCohortStatus(@Param() params: string[]) {
7 return `Status of cohort: ${params[0]}`;
8 }
9
10 // Optional parameters
11 @Get('formation/:type/:cohorts?')
12 getFormation(
13 @Param('type') formationType: string,
14 @Param('cohorts') cohortCount?: string,
15 ) {
16 return {
17 formation: formationType,
18 cohorts: cohortCount ? +cohortCount : 5, // default 5 cohorts
19 };
20 }
21
22 // Route versioning
23 @Version('1')
24 @Get('attack-plan')
25 getAttackPlanV1() {
26 return 'Basic attack plan (v1)';
27 }
28
29 @Version('2')
30 @Get('attack-plan')
31 getAttackPlanV2() {
32 return 'Advanced attack plan with tactical formations (v2)';
33 }
34}

Best practices dla kontrolerów

  1. Jeden cel na kontroler - każdy kontroler odpowiada za jedną domenę
  2. Cienkie kontrolery - logika biznesowa w serwisach
  3. Walidacja na wejściu - używaj DTOs z dekoratorami
  4. Obsługa błędów - rzucaj odpowiednie wyjątki
  5. Async/await - dla operacji asynchronicznych
  6. Dokumentacja - opisuj endpointy dla Swagger

Kontrolery to pierwsza linia kontaktu z zewnętrznym światem - muszą być niezawodne jak doświadczeni centurionowie, którzy wiedzą, jak reagować na każdą sytuację w kampanii!

W następnym module poznamy serwisy - specjalistów, którzy wykonują prawdziwą pracę w Imperium!

Vai a CodeWorlds