Como Criar Endpoints - ecosif-auth

Este guia explica como criar novos endpoints REST no ecosif-auth seguindo os padrões do projeto.

Estrutura Básica

1. Criar Controller

Crie uma nova classe Controller no pacote io.ecosif.auth.controller:

package io.ecosif.auth.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping("/api/exemplo")
@Tag(name = "Exemplo", description = "Endpoints de exemplo")
public class ExemploController {

    @GetMapping
    @Operation(summary = "Listar exemplos", description = "Retorna lista de exemplos")
    public ResponseEntity<?> listar() {
        log.info("Listando exemplos");
        // Implementação
        return ResponseEntity.ok().build();
    }
}

2. Criar DTO (se necessário)

Crie DTOs no pacote io.ecosif.auth.dto:

package io.ecosif.auth.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import javax.validation.constraints.NotBlank;

@Data
@Schema(description = "DTO de exemplo")
public class ExemploDTO {

    @NotBlank(message = "Nome é obrigatório")
    @Schema(description = "Nome do exemplo", example = "Exemplo 1", required = true)
    private String nome;

    @Schema(description = "Descrição do exemplo", example = "Descrição do exemplo")
    private String descricao;
}

3. Criar Service (se necessário)

Crie Services no pacote io.ecosif.auth.service:

package io.ecosif.auth.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@Transactional
public class ExemploService {

    public void processar(String nome) {
        log.info("Processando: {}", nome);
        // Lógica de negócio
    }
}

Padrões e Convenções

Anotações Swagger/OpenAPI

Documente endpoints com anotações Swagger:

@PostMapping
@Operation(
    summary = "Criar exemplo",
    description = "Cria um novo exemplo no sistema"
)
@ApiResponses(value = {
    @ApiResponse(
        responseCode = "200",
        description = "Criado com sucesso",
        content = @Content(schema = @Schema(implementation = ExemploDTO.class))
    ),
    @ApiResponse(
        responseCode = "400",
        description = "Dados inválidos",
        content = @Content(schema = @Schema(implementation = ApiResponse.class))
    )
})
public ResponseEntity<?> criar(@Valid @RequestBody ExemploDTO dto) {
    // ...
}

Validação

Valide DTOs usando Bean Validation:

@PostMapping
public ResponseEntity<?> criar(
    @Valid @RequestBody ExemploDTO dto
) {
    // DTO já foi validado automaticamente
    // Se inválido, retorna 400 Bad Request automaticamente
}

Tratamento de Erros

Use exceções customizadas para erros de negócio:

@PostMapping
public ResponseEntity<?> criar(@Valid @RequestBody ExemploDTO dto) {
    try {
        exemploService.processar(dto);
        return ResponseEntity.ok().build();
    } catch (BadRequestException e) {
        throw e; // Será tratado pelo GlobalExceptionHandler
    }
}

Logging

Use @Slf4j para logging:

@Slf4j
@RestController
public class ExemploController {

    @PostMapping
    public ResponseEntity<?> criar(@Valid @RequestBody ExemploDTO dto) {
        log.info("Criando exemplo: {}", dto.getNome());
        // ...
        log.debug("Detalhes: {}", dto); // Use DEBUG para informações detalhadas
        // ...
        if (erro) {
            log.warn("Aviso: {}", mensagem);
        }
        if (erroGrave) {
            log.error("Erro ao processar: {}", dto, exception);
        }
    }
}

Autenticação

Para proteger endpoints, eles já são protegidos por padrão. O token JWT é validado automaticamente pelo TokenAuthenticationFilter.

Para acessar o usuário autenticado:

@GetMapping("/meu-perfil")
public ResponseEntity<?> meuPerfil(
    @AuthenticationPrincipal LocalUser localUser
) {
    User user = localUser.getUser();
    // Usar informações do usuário
}

Ou use a anotação customizada:

import io.ecosif.auth.config.CurrentUser;

@GetMapping("/meu-perfil")
public ResponseEntity<?> meuPerfil(
    @CurrentUser LocalUser localUser
) {
    User user = localUser.getUser();
}

Códigos HTTP

Use códigos HTTP apropriados:

Exemplo Completo

Controller

package io.ecosif.auth.controller;

import io.ecosif.auth.dto.ApiResponse;
import io.ecosif.auth.dto.ExemploDTO;
import io.ecosif.auth.exception.BadRequestException;
import io.ecosif.auth.service.ExemploService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@Slf4j
@RestController
@RequestMapping("/api/exemplo")
@Tag(name = "Exemplo", description = "Endpoints de exemplo")
public class ExemploController {

    private final ExemploService exemploService;

    public ExemploController(ExemploService exemploService) {
        this.exemploService = exemploService;
    }

    @GetMapping
    @Operation(
        summary = "Listar exemplos",
        description = "Retorna lista de exemplos"
    )
    @ApiResponses(value = {
        @ApiResponse(
            responseCode = "200",
            description = "Lista obtida com sucesso"
        )
    })
    public ResponseEntity<?> listar() {
        log.info("Listando exemplos");
        // Implementação
        return ResponseEntity.ok().build();
    }

    @PostMapping
    @Operation(
        summary = "Criar exemplo",
        description = "Cria um novo exemplo no sistema"
    )
    @ApiResponses(value = {
        @ApiResponse(
            responseCode = "201",
            description = "Criado com sucesso",
            content = @Content(schema = @Schema(implementation = ExemploDTO.class))
        ),
        @ApiResponse(
            responseCode = "400",
            description = "Dados inválidos",
            content = @Content(schema = @Schema(implementation = ApiResponse.class))
        )
    })
    public ResponseEntity<ExemploDTO> criar(
        @Valid @RequestBody ExemploDTO dto
    ) {
        log.info("Criando exemplo: {}", dto.getNome());

        try {
            exemploService.processar(dto);
            return ResponseEntity.status(HttpStatus.CREATED).body(dto);
        } catch (BadRequestException e) {
            throw e; // Tratado pelo GlobalExceptionHandler
        }
    }

    @GetMapping("/{id}")
    @Operation(
        summary = "Obter exemplo por ID",
        description = "Retorna um exemplo específico pelo ID"
    )
    @ApiResponses(value = {
        @ApiResponse(
            responseCode = "200",
            description = "Exemplo encontrado",
            content = @Content(schema = @Schema(implementation = ExemploDTO.class))
        ),
        @ApiResponse(
            responseCode = "404",
            description = "Exemplo não encontrado"
        )
    })
    public ResponseEntity<ExemploDTO> obterPorId(@PathVariable Long id) {
        log.info("Buscando exemplo com ID: {}", id);
        // Implementação
        return ResponseEntity.ok().build();
    }
}

Service

package io.ecosif.auth.service;

import io.ecosif.auth.dto.ExemploDTO;
import io.ecosif.auth.exception.BadRequestException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@Transactional
public class ExemploService {

    public void processar(ExemploDTO dto) {
        log.info("Processando exemplo: {}", dto.getNome());

        // Validações de negócio
        if (dto.getNome().length() < 3) {
            throw new BadRequestException("Nome deve ter pelo menos 3 caracteres");
        }

        // Lógica de negócio
        // ...
    }
}

DTO

package io.ecosif.auth.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

@Data
@Schema(description = "DTO de exemplo")
public class ExemploDTO {

    @NotBlank(message = "Nome é obrigatório")
    @Size(min = 3, max = 100, message = "Nome deve ter entre 3 e 100 caracteres")
    @Schema(description = "Nome do exemplo", example = "Exemplo 1", required = true)
    private String nome;

    @Schema(description = "Descrição do exemplo", example = "Descrição do exemplo")
    private String descricao;
}

Testes

Crie testes para seus endpoints:

package io.ecosif.auth.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
class ExemploControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void should_CreateExample_When_ValidData() throws Exception {
        ExemploDTO dto = new ExemploDTO();
        dto.setNome("Teste");
        dto.setDescricao("Descrição do teste");

        mockMvc.perform(post("/api/exemplo")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(dto)))
            .andExpect(status().isCreated());
    }
}

Documentação OpenAPI

Após criar o endpoint, a documentação será automaticamente gerada e disponível em: - Swagger UI: http://localhost:8080/swagger-ui.html - OpenAPI JSON: http://localhost:8080/v3/api-docs

Checklist

Antes de considerar o endpoint completo:

Recursos