commandby wbrunovieira

Create Controller

Cria Controller completo com testes e configurações

Installs: 0
Used in: 1 repos
Updated: 2d ago
$npx ai-builder add command wbrunovieira/create-controller

Installs to .claude/commands/create-controller.md

# Criar Controller e Testes

## Contexto

Siga os padrões de desenvolvimento já carregados via `/exec-prompt` e analise a estrutura de controllers existente.

### Arquivos de Referência para Análise:

- Controllers: !`find src/infra/controllers -name "*.controller.ts" | head -5`
- Modules: !`find src/infra/http/modules -name "*.module.ts" | head -5`
- Testes: !`find src/infra/controllers/tests -name "*.spec.ts" | head -5`
- Request files: !`find requests -name "*.http" | head -5`

## Sua Tarefa

Crie um Controller completo para **$ARGUMENTS** incluindo:

### 1. Controller com Validações e Documentação Swagger

````typescript
import { ApiTags, ApiOperation, ApiResponse, ApiBody, ApiParam, ApiQuery, ApiBearerAuth } from '@nestjs/swagger';

@ApiTags('[Tag Name]') // Ex: 'Users', 'Courses', 'Videos'
@Controller('[route]')
export class [Nome]Controller {
  constructor(private readonly useCase: [Nome]UseCase) {}

  @ApiOperation({ 
    summary: '[Ação concisa]', // Ex: 'Create new user'
    description: `[Descrição detalhada incluindo]:
      - O que o endpoint faz
      - Regras de negócio importantes
      - Requisitos de autenticação/autorização
      - Rate limiting se aplicável
      - Próximos passos após sucesso
    `
  })
  @ApiBody({ /* configuração específica por verbo */ })
  @ApiResponse({ /* respostas padronizadas */ })
  @[HttpVerb]('[endpoint]')
  async handle(@Body() body: [Nome]Dto): Promise<HttpResponse> {
    // Implementação
  }
}
````

### 2. DTOs com Swagger e Class-Validator

#### Request DTOs
```typescript
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsEmail, MinLength } from 'class-validator';

export class Create[Nome]Dto {
  @ApiProperty({
    example: 'valor exemplo realista',
    description: 'Descrição clara do campo e suas regras',
    required: true,
    minLength: 6, // se aplicável
  })
  @IsString()
  @IsNotEmpty()
  field: string;
}
```

#### Response DTOs (OBRIGATÓRIO para boa documentação)
```typescript
// Success Response
export class [Nome]ResponseDto {
  @ApiProperty({
    example: '550e8400-e29b-41d4-a716-446655440000',
    description: 'Unique identifier',
  })
  id: string;

  @ApiProperty({
    example: 'valor exemplo',
    description: 'Descrição do campo',
  })
  field: string;
}

// Error Response (seguir RFC 7807)
export class [Nome]ErrorResponseDto {
  @ApiProperty({
    example: 'https://api.revalidaitalia.com/errors/[error-type]',
    description: 'URI reference that identifies the problem type',
  })
  type: string;

  @ApiProperty({
    example: '[Error Title]',
    description: 'Short, human-readable summary',
  })
  title: string;

  @ApiProperty({ example: 400 })
  status: number;

  @ApiProperty({
    example: 'Detailed error message',
    description: 'Human-readable explanation',
  })
  detail: string;

  @ApiProperty({ example: '/[route]' })
  instance: string;

  @ApiProperty({
    example: '550e8400-e29b-41d4-a716-446655440000',
    description: 'Trace ID for support',
  })
  traceId: string;
}
```

## 📚 GUIA DE PADRÕES DE DOCUMENTAÇÃO SWAGGER

### 🎯 Princípios Fundamentais
1. **Segurança First**: Nunca exponha detalhes de validação em erros de autenticação
2. **Developer Experience**: Exemplos realistas, não "test123"
3. **Contexto de Negócio**: Explique o "por quê", não apenas o "como"
4. **Próximos Passos**: Sempre indique o que fazer após sucesso

### 📋 Checklist Obrigatório para TODA Rota

- [ ] **@ApiTags**: Tag consistente com o domínio
- [ ] **@ApiOperation**: Summary + Description detalhada
- [ ] **@ApiBearerAuth**: Se requer autenticação
- [ ] **DTOs de Request**: Com @ApiProperty e exemplos realistas
- [ ] **DTOs de Response**: Sucesso E erro (RFC 7807)
- [ ] **Exemplos múltiplos**: Cenários válidos e inválidos
- [ ] **Status codes**: Todos os possíveis com descrição
- [ ] **Rate limiting**: Documentar se aplicável
- [ ] **Próximos passos**: Links para endpoints relacionados

### 🔧 Padrões por Verbo HTTP

#### GET - Buscar Recursos
```typescript
@ApiOperation({ 
  summary: 'Get [resource] by ID',
  description: `
    Retrieves detailed information about a specific [resource].
    
    ## Access Control
    - Students: Can only access their own resources
    - Instructors: Can access resources in their courses
    - Admins: Full access
    
    ## Response Includes
    - Basic [resource] information
    - Related entities (if expanded)
    - Computed fields (e.g., progress, completion)
    
    ## Performance Notes
    - Results are cached for 5 minutes
    - Use If-None-Match header for conditional requests
  `
})
@ApiParam({
  name: 'id',
  description: '[Resource] unique identifier (UUID v4)',
  example: '550e8400-e29b-41d4-a716-446655440000',
})
@ApiQuery({
  name: 'expand',
  required: false,
  description: 'Comma-separated list of relations to include',
  example: 'user,course,progress',
})
@ApiResponse({ 
  status: 200, 
  description: '[Resource] found successfully',
  type: [Nome]ResponseDto,
})
@ApiResponse({ 
  status: 404, 
  description: '[Resource] not found',
  type: NotFoundErrorDto,
})
@ApiBearerAuth()
@Get(':id')
```

#### GET (List) - Listar Recursos
```typescript
@ApiOperation({ 
  summary: 'List [resources] with pagination',
  description: `
    Returns a paginated list of [resources] with optional filtering and sorting.
    
    ## Filtering
    - By status: ?status=active,pending
    - By date: ?createdAfter=2024-01-01
    - By search: ?search=keyword
    
    ## Sorting
    - Default: createdAt DESC
    - Options: name, date, status
    
    ## Pagination
    - Default page size: 20
    - Maximum page size: 100
  `
})
@ApiQuery({
  name: 'page',
  required: false,
  description: 'Page number (1-indexed)',
  example: 1,
  type: Number,
})
@ApiQuery({
  name: 'limit',
  required: false,
  description: 'Items per page (max: 100)',
  example: 20,
  type: Number,
})
@ApiQuery({
  name: 'sort',
  required: false,
  description: 'Sort field and direction',
  example: 'createdAt:desc',
  enum: ['name:asc', 'name:desc', 'createdAt:asc', 'createdAt:desc'],
})
@ApiResponse({ 
  status: 200, 
  description: 'List retrieved successfully',
  type: Paginated[Nome]ResponseDto,
})
```

#### POST - Criar Recurso
```typescript
@ApiOperation({ 
  summary: 'Create new [resource]',
  description: `
    Creates a new [resource] with the provided data.
    
    ## Validation Rules
    - [Field1]: Required, must be unique
    - [Field2]: Optional, max 255 characters
    - [Field3]: Must be a valid enum value
    
    ## Business Rules
    - [Specific business rule 1]
    - [Specific business rule 2]
    
    ## After Creation
    - Resource is immediately available
    - Webhook notifications are sent (if configured)
    - Related resources are updated
    
    ## Next Steps
    - GET /[resources]/{id} - View created resource
    - PUT /[resources]/{id} - Update resource
    - POST /[resources]/{id}/activate - Activate resource
  `
})
@ApiBody({ 
  type: Create[Nome]Dto,
  description: '[Resource] data to create',
  examples: {
    complete: {
      summary: 'Complete example',
      description: 'All fields populated',
      value: {
        name: 'Example Name',
        description: 'Detailed description',
        status: 'active'
      }
    },
    minimal: {
      summary: 'Minimal example',
      description: 'Only required fields',
      value: {
        name: 'Example Name'
      }
    }
  }
})
@ApiResponse({ 
  status: 201, 
  description: '[Resource] created successfully',
  type: [Nome]ResponseDto,
  headers: {
    Location: {
      description: 'URL of created resource',
      schema: { type: 'string', example: '/[resources]/550e8400-e29b-41d4-a716-446655440000' }
    }
  }
})
@ApiResponse({ 
  status: 400, 
  description: 'Validation error',
  type: ValidationErrorDto,
})
@ApiResponse({ 
  status: 409, 
  description: 'Conflict - resource already exists',
  type: ConflictErrorDto,
})
```

#### PUT/PATCH - Atualizar Recurso
```typescript
@ApiOperation({ 
  summary: 'Update [resource]',
  description: `
    Updates an existing [resource]. 
    ${isPatch ? 'Partial updates allowed - only send fields to change.' : 'Full replacement - all fields required.'}
    
    ## Updatable Fields
    - name: Change display name
    - description: Update description
    - status: Change status (see allowed transitions)
    
    ## Non-Updatable Fields
    - id: Immutable
    - createdAt: Immutable
    - createdBy: Immutable
    
    ## Status Transitions
    - draft → published (requires admin)
    - published → archived (requires owner or admin)
    - archived → published (requires admin)
    
    ## Validation
    - Same rules as creation apply
    - Additional transition rules may apply
    
    ## Side Effects
    - Modified timestamp updated
    - Audit log entry created
    - Cache invalidated
  `
})
@ApiParam({
  name: 'id',
  description: '[Resource] ID to update',
  example: '550e8400-e29b-41d4-a716-446655440000',
})
@ApiBody({ 
  type: Update[Nome]Dto,
  description: `[Resource] data to update ${isPatch ? '(partial)' : '(complete)'}`,
  examples: {
    updateName: {
      summary: 'Update name only',
      value: { name: 'New Name' }
    },
    updateStatus: {
      summary: 'Change status',
      value: { status: 'published' }
    }
  }
})
@ApiResponse({ 
  status: 200, 
  description: '[Resource] updated successfully',
  type: [Nome]ResponseDto,
})
@ApiResponse({ 
  status: 404, 
  description: '[Resource] not found',
  type: NotFoundErrorDto,
})
@ApiResponse({ 
  status: 422, 
  description: 'Invalid state transition',
  type: BusinessRuleErrorDto,
})
```

#### DELETE - Remover Recurso
```typescript
@ApiOperation({ 
  summary: 'Delete [resource]',
  description: `
    Permanently deletes a [resource] and all associated data.
    
    ## Delete Behavior
    - ${isSoftDelete ? 'Soft delete - marked as deleted but retained' : 'Hard delete - permanently removed'}
    - Related entities: ${cascadeBehavior}
    
    ## Prerequisites
    - No active dependencies
    - User must be owner or admin
    - Resource must be in deletable state
    
    ## Alternatives
    Consider these before deletion:
    - PUT /[resources]/{id} with status: 'archived' - Soft removal
    - PUT /[resources]/{id} with active: false - Deactivation
    
    ## Warning
    This action cannot be undone. All related data will be affected.
  `
})
@ApiParam({
  name: 'id',
  description: '[Resource] ID to delete',
  example: '550e8400-e29b-41d4-a716-446655440000',
})
@ApiResponse({ 
  status: 204, 
  description: '[Resource] deleted successfully',
})
@ApiResponse({ 
  status: 404, 
  description: '[Resource] not found',
  type: NotFoundErrorDto,
})
@ApiResponse({ 
  status: 409, 
  description: 'Cannot delete - has active dependencies',
  type: ConflictErrorDto,
  examples: {
    hasDependencies: {
      summary: 'Has active dependencies',
      value: {
        type: 'https://api.revalidaitalia.com/errors/has-dependencies',
        title: 'Resource Has Dependencies',
        status: 409,
        detail: 'Cannot delete course with enrolled students',
        instance: '/courses/123',
        dependencies: {
          students: 45,
          lessons: 12
        }
      }
    }
  }
})
```

### 🔒 Padrões de Segurança

#### Autenticação
```typescript
// Para rotas autenticadas
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)

// Para rotas públicas
@Public()

// Para roles específicas
@Roles('admin', 'instructor')
@UseGuards(RolesGuard)
```

#### Mensagens de Erro
```typescript
// NUNCA expor detalhes em erros de autenticação
❌ "Email not found"
❌ "Password must be at least 6 characters"
✅ "Invalid credentials"

// Para outros erros, ser específico ajuda
✅ "Course is full (50/50 students)"
✅ "Cannot delete course with active enrollments"
```

### 📊 Exemplos Realistas por Domínio

#### Auth/Users
- Emails: mario.rossi@medicina.it, admin@revalidaitalia.com
- Nomes: Mario Rossi, Giulia Bianchi
- Roles: student, instructor, admin

#### Courses
- Títulos: "Anatomia Humana I", "Fisiologia Cardiovascular"
- Códigos: "MED-101", "MED-205"
- Status: draft, published, archived

#### Videos
- Títulos: "Introdução ao Sistema Nervoso", "Aula 3 - Sinapse Neural"
- Durações: 1800 (30 min), 3600 (1 hora)
- Providers: pandavideo, youtube, vimeo

### 3. Module Configuration

```typescript
@Module({
  controllers: [[Nome]Controller],
  providers: [
    [Nome]UseCase,
    {
      provide: 'I[Entity]Repository',
      useClass: Prisma[Entity]Repository,
    },
  ],
})
export class [Nome]Module {}
```
### 4. Estrutura de Testes

Criar em `src/infra/controllers/tests/[controller-name]/`:

```
shared/
  [nome]-controller-test-setup.ts    # Configuração de mocks
  [nome]-controller-test-helpers.ts  # Helpers para testes
  [nome]-controller-test-data.ts     # Dados de teste
```

Testes por verbo HTTP:
- `get-[nome].controller.spec.ts`
- `post-[nome].controller.spec.ts`
- `put-[nome].controller.spec.ts`
- `delete-[nome].controller.spec.ts`

### 5. Request File

Criar/atualizar `requests/[controller-name].http`:

```http
### Create [Entity]
POST {{BASE_URL}}/[route]
Content-Type: application/json
Authorization: Bearer {{TOKEN}}

{
  "field": "value"
}

### Get [Entity]
GET {{BASE_URL}}/[route]/{{id}}
Authorization: Bearer {{TOKEN}}

### Update [Entity]
PUT {{BASE_URL}}/[route]/{{id}}
Content-Type: application/json
Authorization: Bearer {{TOKEN}}

{
  "field": "updated value"
}

### Delete [Entity]
DELETE {{BASE_URL}}/[route]/{{id}}
Authorization: Bearer {{TOKEN}}
```
🚨 PRINCÍPIO CRÍTICO DE TESTES
Quando testes falharem, SEMPRE corrija a implementação, NUNCA os testes!
Localização das Correções:

Validação de entrada → controllers/dtos/*.dto.ts
Mapeamento de dados → controllers/*.controller.ts
Tratamento de erros → filters/*.filter.ts
Status HTTP → Controller methods

Checklist de Implementação

 Criar Controller em src/infra/controllers/
 Criar/atualizar DTOs com validações
 Verificar/criar Module e registrar providers
 Criar estrutura de testes com shared/
 Implementar testes por verbo HTTP
 Criar/atualizar arquivo .http
 Adicionar em dev-setup se necessário
 Executar testes

Estrutura do Teste
typescriptdescribe('[HttpVerb] [Nome]Controller', () => {
  let app: INestApplication
  let useCase: [Nome]UseCase

  beforeEach(async () => {
    const { testingModule, mockUseCase } = await create[Nome]ControllerTestSetup()
    useCase = mockUseCase
    app = testingModule.createNestApplication()
    await app.init()
  })

  it('should return [expected] when valid request', async () => {
    // Arrange
    const validData = create[Nome]TestData()
    jest.spyOn(useCase, 'execute').mockResolvedValue(expectedResponse)

    // Act & Assert
    await request(app.getHttpServer())
      .[httpVerb]('/[route]')
      .send(validData)
      .expect(expectedStatus)
  })

  it('should return 400 when invalid data', async () => {
    // Testes de validação
  })

  it('should handle use-case errors appropriately', async () => {
    // Testes de erro
  })
})
Comandos de Execução
#### Build do Projeto (IMPORTANTE):
```bash
# Após criar/modificar controller, execute build em background
docker exec ead-backend-dev pnpm run build

Testar Controller:
bashdocker exec -it ead-backend-dev sh -c "pnpm test src/infra/controllers/tests/[controller-name]/[verb]-[nome].controller.spec.ts"
Se modificou dev-setup:
bashdocker exec ead-backend-dev pnpm seed:dev
Reiniciar sistema:
bashdocker compose down && docker compose up -d
Perguntas Antes de Começar

Qual verbo HTTP (GET, POST, PUT, DELETE)?
Qual a rota/endpoint específico?
Quais campos no request body (se aplicável)?
Quais validações são necessárias?
Qual status HTTP de sucesso esperado?
Precisa de autenticação/autorização?
Há query parameters ou path parameters?

Aguarde respostas sobre os requisitos específicos antes de implementar.

## 📖 DOCUMENTAÇÃO SWAGGER - GUIA COMPLETO

### 🎨 Estrutura de Documentação por Camadas

#### 1. Nível Global (main.ts)
```typescript
const config = new DocumentBuilder()
  .setTitle('Revalida Italia API')
  .setDescription(`Descrição completa com:
    - Overview do sistema
    - Fluxo de autenticação
    - Rate limiting
    - Roles e permissões
  `)
  .setVersion('1.0.0')
  .addBearerAuth()
  .addTag('TagName', 'Tag description')
  .setContact()
  .setLicense()
  .addServer()
  .build();
```

#### 2. Nível de Controller
```typescript
@ApiTags('Domain Name')
@ApiBearerAuth() // se todas as rotas precisam auth
@Controller('route')
export class Controller {
```

#### 3. Nível de Endpoint
```typescript
@ApiOperation({ summary, description })
@ApiBody() / @ApiParam() / @ApiQuery()
@ApiResponse() // múltiplos para cada status
@Public() / @Roles() / @ApiBearerAuth()
@Get() / @Post() / @Put() / @Delete()
```

### 🚀 Quick Reference - Decorators por Verbo

| Verbo | Body | Params | Query | Status Success |
|-------|------|--------|-------|----------------|
| GET (single) | ❌ | ✅ id | ✅ expand | 200 |
| GET (list) | ❌ | ❌ | ✅ page,limit,sort,filter | 200 |
| POST | ✅ | ❌ | ❌ | 201 + Location header |
| PUT | ✅ | ✅ id | ❌ | 200 |
| PATCH | ✅ | ✅ id | ❌ | 200 |
| DELETE | ❌ | ✅ id | ❌ | 204 |

### ⚠️ Erros Comuns e Como Evitar

#### ❌ NÃO FAÇA:
```typescript
// Expor detalhes de segurança
@ApiResponse({ 
  status: 401, 
  description: 'User not found in database' 
})

// Exemplos genéricos
@ApiProperty({ example: 'string' })

// Documentação vaga
@ApiOperation({ summary: 'Updates resource' })

// Esquecer autenticação
@Post('admin-only-route')
// Faltou @ApiBearerAuth() e @Roles('admin')
```

#### ✅ FAÇA:
```typescript
// Mensagem genérica para segurança
@ApiResponse({ 
  status: 401, 
  description: 'Authentication failed' 
})

// Exemplos realistas
@ApiProperty({ 
  example: 'mario.rossi@medicina.it',
  description: 'Email do estudante'
})

// Documentação detalhada
@ApiOperation({ 
  summary: 'Update course status',
  description: `Changes course status with validation...`
})

// Sempre documentar auth
@ApiBearerAuth()
@Roles('admin')
@Post('admin-only-route')
```

### 📝 Template de Documentação Completa

```typescript
import { 
  Controller, Get, Post, Put, Delete, 
  Body, Param, Query, UseGuards 
} from '@nestjs/common';
import { 
  ApiTags, ApiOperation, ApiResponse, ApiBody, 
  ApiParam, ApiQuery, ApiBearerAuth, ApiHeader 
} from '@nestjs/swagger';

@ApiTags('Resources')
@Controller('resources')
export class ResourceController {
  
  @ApiOperation({ 
    summary: 'Create new resource',
    description: `
      Creates a new resource with validation.
      
      ## Business Rules
      - Rule 1 explanation
      - Rule 2 explanation
      
      ## Access Control
      - Who can access
      - Special permissions
      
      ## Side Effects
      - What happens after
      - Related updates
      
      ## Next Steps
      - GET /resources/{id}
      - PUT /resources/{id}
    `
  })
  @ApiBody({ 
    type: CreateResourceDto,
    description: 'Resource data',
    examples: {
      complete: {
        summary: 'All fields',
        value: { /* realistic data */ }
      },
      minimal: {
        summary: 'Required only',
        value: { /* minimal data */ }
      }
    }
  })
  @ApiResponse({ 
    status: 201, 
    description: 'Created successfully',
    type: ResourceResponseDto,
    headers: {
      Location: {
        description: 'URL of created resource',
        schema: { type: 'string' }
      }
    }
  })
  @ApiResponse({ 
    status: 400, 
    description: 'Validation error',
    type: ValidationErrorDto,
    examples: {
      missingField: {
        summary: 'Required field missing',
        value: { /* error example */ }
      }
    }
  })
  @ApiResponse({ 
    status: 401, 
    description: 'Authentication required',
    type: UnauthorizedErrorDto
  })
  @ApiResponse({ 
    status: 403, 
    description: 'Insufficient permissions',
    type: ForbiddenErrorDto
  })
  @ApiResponse({ 
    status: 409, 
    description: 'Resource already exists',
    type: ConflictErrorDto
  })
  @ApiBearerAuth()
  @UseGuards(JwtAuthGuard)
  @Post()
  async create(@Body() dto: CreateResourceDto) {
    // Implementation
  }
}
```

### 🔍 Checklist Final de Revisão

Antes de considerar a documentação completa, verifique:

#### Documentação Técnica
- [ ] Todos os parâmetros têm @ApiProperty com example e description
- [ ] Todos os status codes possíveis estão documentados
- [ ] DTOs de request E response estão criados
- [ ] Exemplos são realistas (não "test123")
- [ ] Headers especiais documentados (se houver)
- [ ] Rate limiting mencionado (se aplicável)

#### Contexto de Negócio
- [ ] Explica O QUE o endpoint faz
- [ ] Explica QUEM pode acessar
- [ ] Explica QUANDO usar
- [ ] Lista regras de negócio
- [ ] Indica próximos passos
- [ ] Menciona efeitos colaterais

#### Segurança
- [ ] @ApiBearerAuth() em rotas autenticadas
- [ ] @Public() em rotas públicas
- [ ] @Roles() quando necessário
- [ ] Mensagens de erro não expõem detalhes sensíveis
- [ ] Validações documentadas sem expor regras de segurança

#### Developer Experience
- [ ] Summary claro e direto
- [ ] Description com markdown formatado
- [ ] Exemplos cobrem casos comuns
- [ ] Links para endpoints relacionados
- [ ] Troubleshooting de erros comuns

### 💡 Dicas de Produtividade

1. **Crie snippets** para os padrões mais comuns
2. **Copie de endpoints similares** já bem documentados
3. **Use o Swagger UI** para testar enquanto desenvolve
4. **Documente DURANTE** o desenvolvimento, não depois
5. **Peça revisão** de outro dev na documentação

### 🎯 Objetivo Final

A documentação deve permitir que um desenvolvedor:
- Entenda o endpoint em < 30 segundos
- Consiga fazer a primeira chamada em < 2 minutos
- Saiba lidar com erros sem perguntar
- Entenda o contexto de negócio
- Saiba os próximos passos

Se sua documentação atinge esses objetivos, está no padrão esperado!
````

Quick Install

$npx ai-builder add command wbrunovieira/create-controller

Details

Type
command
Slug
wbrunovieira/create-controller
Created
6d ago