From 3560839075cf564870e71e2f1609f6c57d02cb4e Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sat, 19 Oct 2024 17:02:52 -0300 Subject: [PATCH] docs: update API documentation --- .../com/ifsp/tickets/infra/api/AuthAPI.java | 102 +++++++++----- .../br/com/ifsp/tickets/infra/api/CepAPI.java | 17 ++- .../ifsp/tickets/infra/api/CompanyAPI.java | 83 +++++++++--- .../ifsp/tickets/infra/api/EnrollmentAPI.java | 59 +++++--- .../com/ifsp/tickets/infra/api/EventAPI.java | 127 ++++++++++-------- .../com/ifsp/tickets/infra/api/TicketAPI.java | 56 ++++++-- .../api/controllers/ExceptionController.java | 67 ++++----- .../tickets/infra/config/WebServerConfig.java | 28 +++- .../infra/shared/APIErrorResponse.java | 18 +++ .../tickets/infra/shared/package-info.java | 4 - 10 files changed, 371 insertions(+), 190 deletions(-) create mode 100644 infrastructure/src/main/java/br/com/ifsp/tickets/infra/shared/APIErrorResponse.java delete mode 100644 infrastructure/src/main/java/br/com/ifsp/tickets/infra/shared/package-info.java diff --git a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/AuthAPI.java b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/AuthAPI.java index 2742a9c..a6259e8 100644 --- a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/AuthAPI.java +++ b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/AuthAPI.java @@ -6,8 +6,12 @@ import br.com.ifsp.tickets.infra.contexts.user.models.register.RegisterRequest; import br.com.ifsp.tickets.infra.contexts.user.models.update.UpdateUserRequest; import br.com.ifsp.tickets.infra.contexts.user.models.user.UserResponse; +import br.com.ifsp.tickets.infra.shared.APIErrorResponse; +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.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import org.springframework.http.MediaType; @@ -19,53 +23,89 @@ public interface AuthAPI { @PostMapping(value = "/login", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "User logged in successfully"), - @ApiResponse(responseCode = "401", description = "Invalid credentials") - }) + @Operation( + summary = "Login", + description = "Authenticate user", + responses = { + @ApiResponse(responseCode = "200", description = "User logged in successfully", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = LoginResponse.class))), + @ApiResponse(responseCode = "401", description = "Invalid credentials", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity login(@RequestBody LoginRequest request); @PostMapping(value = "/register", consumes = MediaType.APPLICATION_JSON_VALUE) - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "User registered successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request") - }) + @Operation( + summary = "Register user", + description = "Register a new user", + responses = { + @ApiResponse(responseCode = "201", description = "User registered successfully"), + @ApiResponse(responseCode = "400", description = "Invalid request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity register(@RequestBody RegisterRequest request); @GetMapping(value = "/{id}") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "User found successfully"), - @ApiResponse(responseCode = "404", description = "User not found") - }) + @Operation( + summary = "Get user by id", + description = "Get user information by id", + security = { + @SecurityRequirement(name = "bearer") + }, + responses = { + @ApiResponse(responseCode = "200", description = "User found successfully", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = UserResponse.class))), + @ApiResponse(responseCode = "401", description = "Invalid credentials", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "403", description = "Access denied", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "404", description = "User not found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity getUserById(@PathVariable String id); @PutMapping(value = "/{id}") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "User updated successfully"), - @ApiResponse(responseCode = "401", description = "Invalid credentials"), - @ApiResponse(responseCode = "403", description = "Access denied"), - @ApiResponse(responseCode = "404", description = "User not found") - }) + @Operation( + summary = "Update user", + description = "Update user information", + security = { + @SecurityRequirement(name = "bearer") + }, + responses = { + @ApiResponse(responseCode = "200", description = "User updated successfully", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = UserResponse.class))), + @ApiResponse(responseCode = "401", description = "Invalid credentials", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "403", description = "Access denied", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "404", description = "User not found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity updateUser(@PathVariable String id, @RequestBody UpdateUserRequest request); @PostMapping(value = "/activate/{token}") - @ApiResponses(value = { - @ApiResponse(responseCode = "204", description = "User activated successfully"), - @ApiResponse(responseCode = "400", description = "Invalid token") - }) + @Operation( + summary = "Activate user", + description = "Activate user account", + responses = { + @ApiResponse(responseCode = "204", description = "User activated successfully"), + @ApiResponse(responseCode = "400", description = "Invalid token", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity activate(@PathVariable String token); @PostMapping(value = "/recovery/{login}") - @ApiResponses(value = { - @ApiResponse(responseCode = "204", description = "Recovery request sent successfully"), - @ApiResponse(responseCode = "400", description = "Invalid login") - }) + @Operation( + summary = "Forgot password", + description = "Send recovery request to user", + responses = { + @ApiResponse(responseCode = "204", description = "Recovery request sent successfully"), + @ApiResponse(responseCode = "400", description = "Invalid login", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity forgotPassword(@PathVariable String login, HttpServletRequest request); @PostMapping(value = "/recovery/change") - @ApiResponses(value = { - @ApiResponse(responseCode = "204", description = "Password changed successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request") - }) + @Operation( + summary = "Change password", + description = "Change user password", + responses = { + @ApiResponse(responseCode = "204", description = "Password changed successfully"), + @ApiResponse(responseCode = "400", description = "Invalid request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity accountRecovery(@RequestBody RecoveryRequest request); } diff --git a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/CepAPI.java b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/CepAPI.java index b5ddfd7..05d9112 100644 --- a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/CepAPI.java +++ b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/CepAPI.java @@ -1,8 +1,11 @@ package br.com.ifsp.tickets.infra.api; import br.com.ifsp.tickets.infra.contexts.zipcode.models.ZipCodeResponse; +import br.com.ifsp.tickets.infra.shared.APIErrorResponse; +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 org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -14,9 +17,13 @@ public interface CepAPI { @GetMapping(value = "/{cep}", produces = "application/json") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Cep found"), - @ApiResponse(responseCode = "404", description = "Cep not found") - }) + @Operation( + summary = "Get address by cep", + description = "Get address information by brazilian zip code", + responses = { + @ApiResponse(responseCode = "200", description = "Cep found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ZipCodeResponse.class))), + @ApiResponse(responseCode = "404", description = "Cep not found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity get(@PathVariable String cep); } diff --git a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/CompanyAPI.java b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/CompanyAPI.java index 0d73d49..c354188 100644 --- a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/CompanyAPI.java +++ b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/CompanyAPI.java @@ -5,9 +5,13 @@ import br.com.ifsp.tickets.infra.contexts.company.models.CreateCompanyRequest; import br.com.ifsp.tickets.infra.contexts.company.models.SearchCompanyResponse; import br.com.ifsp.tickets.infra.contexts.company.models.UpdateCompanyRequest; +import br.com.ifsp.tickets.infra.shared.APIErrorResponse; import br.com.ifsp.tickets.infra.shared.search.AdvancedSearchRequest; +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.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -17,42 +21,79 @@ public interface CompanyAPI { @PostMapping(consumes = "application/json") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "Company created successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request") - }) + @Operation( + summary = "Create company", + description = "Create a new company", + security = { + @SecurityRequirement(name = "bearer") + }, + responses = { + @ApiResponse(responseCode = "201", description = "Company created successfully"), + @ApiResponse(responseCode = "400", description = "Invalid request", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "401", description = "Invalid credentials", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "403", description = "Access denied", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity create(@RequestBody CreateCompanyRequest request); @PutMapping(value = "/{id}", consumes = "application/json", produces = "application/json") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Company updated successfully"), - @ApiResponse(responseCode = "404", description = "Company not found"), - }) + @Operation( + summary = "Update company", + description = "Update company information", + security = { + @SecurityRequirement(name = "bearer") + }, + responses = { + @ApiResponse(responseCode = "200", description = "Company updated successfully", content = @Content(schema = @Schema(implementation = CompanyResponse.class))), + @ApiResponse(responseCode = "400", description = "Invalid request", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "401", description = "Invalid credentials", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "403", description = "Access denied", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "404", description = "Company not found", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity update(@PathVariable String id, @RequestBody UpdateCompanyRequest request); @PostMapping( value = "/search", produces = "application/json") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Companies list"), - @ApiResponse(responseCode = "400", description = "Invalid request") - }) + @Operation( + summary = "Search companies", + description = "Search companies by name, cnpj, state, city, neighborhood, street, number, complement, zip code, phone, email, website, and contact name", + responses = { + @ApiResponse(responseCode = "200", description = "Companies found", content = @Content(schema = @Schema(implementation = Pagination.class))), + @ApiResponse(responseCode = "400", description = "Invalid request", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity> search(@RequestParam(name = "page", required = false, defaultValue = "0") Integer page, @RequestParam(name = "perPage", required = false, defaultValue = "10") Integer perPage, @RequestBody AdvancedSearchRequest request); @GetMapping(value = "/{id}", produces = "application/json") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Company found"), - @ApiResponse(responseCode = "404", description = "Company not found") - }) + @Operation( + summary = "Get company by id", + description = "Get company information by id", + responses = { + @ApiResponse(responseCode = "200", description = "Company found", content = @Content(schema = @Schema(implementation = CompanyResponse.class))), + @ApiResponse(responseCode = "404", description = "Company not found", content = @Content(schema = @Schema(implementation = APIErrorResponse.class)) + ) + } + ) ResponseEntity get(@PathVariable String id); @DeleteMapping(value = "/{id}") - @ApiResponses(value = { - @ApiResponse(responseCode = "204", description = "Company deleted successfully"), - @ApiResponse(responseCode = "404", description = "Company not found") - }) + @Operation( + summary = "Delete company", + description = "Delete company by id", + security = { + @SecurityRequirement(name = "bearer") + }, + responses = { + @ApiResponse(responseCode = "204", description = "Company deleted successfully"), + @ApiResponse(responseCode = "401", description = "Invalid credentials", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "403", description = "Access denied", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "404", description = "Company not found", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity delete(@PathVariable String id); } diff --git a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/EnrollmentAPI.java b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/EnrollmentAPI.java index 6f7b12f..9a3e072 100644 --- a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/EnrollmentAPI.java +++ b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/EnrollmentAPI.java @@ -5,9 +5,15 @@ import br.com.ifsp.tickets.infra.contexts.enrollment.core.models.EnrollmentResponse; import br.com.ifsp.tickets.infra.contexts.enrollment.upsert.models.CreateUpsertEnrollmentRequest; import br.com.ifsp.tickets.infra.contexts.event.sale.payment.models.CreatePaymentRequest; +import br.com.ifsp.tickets.infra.shared.APIErrorResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Webhook; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +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 org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -19,30 +25,49 @@ public interface EnrollmentAPI { @PostMapping(consumes = "application/json") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "Enrollment created successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request") - }) + @Operation( + summary = "Create enrollment", + description = "Create a new enrollment", + responses = { + @ApiResponse(responseCode = "201", description = "Enrollment created successfully", content = @Content(schema = @Schema(implementation = String.class), mediaType = MediaType.TEXT_PLAIN_VALUE, examples = @ExampleObject(value = "3266a228-d496-4841-b31c-195d1a3e9ee5"))), + @ApiResponse(responseCode = "400", description = "Invalid request", content = @Content(schema = @Schema(implementation = APIErrorResponse.class), mediaType = "application/json")) + } + ) ResponseEntity create(@RequestBody CreateEnrollmentRequest request); @GetMapping(value = "/list", produces = "application/json") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Enrollments retrieved successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request") - }) + @Operation( + summary = "List enrollments", + description = "List enrollments by user", + responses = { + @ApiResponse(responseCode = "200", description = "Enrollments retrieved successfully"), + @ApiResponse(responseCode = "400", description = "Invalid request", content = @Content(schema = @Schema(implementation = APIErrorResponse.class), mediaType = "application/json")) + } + ) ResponseEntity> findByUser(); @PostMapping(consumes = "application/json", value = "/upsert") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "Upsert enrollment created successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request") - }) + @Operation( + summary = "Create or update enrollment", + description = "Create or update an enrollment", + responses = { + @ApiResponse(responseCode = "201", description = "Upsert enrollment created successfully"), + @ApiResponse(responseCode = "400", description = "Invalid request", content = @Content(schema = @Schema(implementation = APIErrorResponse.class), mediaType = "application/json")) + } + ) ResponseEntity createUpsertEnrollment(@RequestBody CreateUpsertEnrollmentRequest request); @PostMapping(consumes = "application/json", value = "/webhook") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "Upsert enrollment created successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request") - }) + @Webhook( + name = "Payment Webhook", + operation = @Operation( + summary = "Payment webhook", + description = "Webhook for payment", + responses = { + @ApiResponse(responseCode = "201", description = "Webhook received successfully"), + @ApiResponse(responseCode = "400", description = "Invalid request", content = @Content(schema = @Schema(implementation = APIErrorResponse.class), mediaType = "application/json")) + } + ) + ) ResponseEntity webhook(@RequestBody CreatePaymentRequest request); } diff --git a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/EventAPI.java b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/EventAPI.java index 0cd2d6a..ba89000 100644 --- a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/EventAPI.java +++ b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/EventAPI.java @@ -1,20 +1,18 @@ package br.com.ifsp.tickets.infra.api; import br.com.ifsp.tickets.domain.shared.search.Pagination; -import br.com.ifsp.tickets.infra.api.controllers.ExceptionController; import br.com.ifsp.tickets.infra.contexts.event.core.models.CreateEventRequest; import br.com.ifsp.tickets.infra.contexts.event.core.models.EventResponse; import br.com.ifsp.tickets.infra.contexts.event.core.models.SearchEventResponse; import br.com.ifsp.tickets.infra.contexts.event.sale.ticket.models.CreateTicketSaleRequest; import br.com.ifsp.tickets.infra.contexts.event.sale.ticket.models.TicketSaleResponse; +import br.com.ifsp.tickets.infra.shared.APIErrorResponse; import br.com.ifsp.tickets.infra.shared.search.AdvancedSearchRequest; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.enums.ParameterIn; 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.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -26,43 +24,63 @@ public interface EventAPI { @PostMapping(consumes = "application/json") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "Event created successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request") - }) + @Operation( + summary = "Create event", + description = "Create a new event", + security = { + @SecurityRequirement(name = "bearer") + }, + responses = { + @ApiResponse(responseCode = "201", description = "Event created successfully"), + @ApiResponse(responseCode = "400", description = "Invalid request", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "401", description = "Invalid credentials", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "403", description = "Access denied", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity create(@RequestBody CreateEventRequest request); @PostMapping( value = "/search", produces = "application/json") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Events list"), - @ApiResponse(responseCode = "400", description = "Invalid request") - }) + @Operation( + summary = "Search events", + description = "Search events by name, date, location, or category", + responses = { + @ApiResponse(responseCode = "200", description = "Events retrieved successfully"), + @ApiResponse(responseCode = "400", description = "Invalid request", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity> search(@RequestParam(name = "page", required = false, defaultValue = "0") Integer page, @RequestParam(name = "perPage", required = false, defaultValue = "10") Integer perPage, @RequestBody AdvancedSearchRequest request); @GetMapping(value = "/{id}", produces = "application/json") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Event found"), - @ApiResponse(responseCode = "404", description = "Event not found") - }) + @Operation( + summary = "Get event by id", + description = "Get event information by id", + security = { + @SecurityRequirement(name = "bearer") + }, + responses = { + @ApiResponse(responseCode = "200", description = "Event found successfully", content = @Content(mediaType = "application/json", schema = @Schema(implementation = EventResponse.class))), + @ApiResponse(responseCode = "401", description = "Invalid credentials", content = @Content(mediaType = "application/json", schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "403", description = "Access denied", content = @Content(mediaType = "application/json", schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "404", description = "Event not found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity get(@PathVariable String id); @GetMapping( value = "/{id}/thumbnail" ) @Operation( - operationId = "getThumbnail", summary = "Obter thumbnail", description = "Obter thumbnail do evento.", - parameters = @Parameter(name = "id", description = "ID do evento a ser consultado", in = ParameterIn.PATH, example = "c971869b-2dd3-4934-beac-172a9a229735", required = true), responses = { @ApiResponse(responseCode = "200", description = "Thumbnail found", content = @Content(mediaType = "image/png", schema = @Schema(type = "string", format = "binary"))), - @ApiResponse(responseCode = "400", description = "Invalid id", content = @Content(schema = @Schema(implementation = ExceptionController.CustomErrorResponse.class))), - @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(schema = @Schema(implementation = ExceptionController.CustomErrorResponse.class))), - @ApiResponse(responseCode = "404", description = "Thumbnail not found", content = @Content(schema = @Schema(implementation = ExceptionController.CustomErrorResponse.class))) + @ApiResponse(responseCode = "400", description = "Invalid id", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "404", description = "Thumbnail not found", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))) }, method = "GET" ) @@ -71,25 +89,17 @@ ResponseEntity> search(@RequestParam(name = "pag @DeleteMapping( value = "/{id}/thumbnail" ) - @ApiResponses( - value = { - @ApiResponse(responseCode = "204", description = "Thumbnail deleted"), - @ApiResponse(responseCode = "400", description = "Invalid id"), - @ApiResponse(responseCode = "401", description = "Unauthorized"), - @ApiResponse(responseCode = "404", description = "Thumbnail not found") - } - ) @Operation( - operationId = "deleteThumbnail", summary = "Deletar thumbnail", description = "Deletar thumbnail do evento.", - parameters = @Parameter(name = "id", description = "ID do evento a ser consultado", in = ParameterIn.PATH, example = "c971869b-2dd3-4934-beac-172a9a229735", required = true), - method = "DELETE", + security = { + @SecurityRequirement(name = "bearer") + }, responses = { @ApiResponse(responseCode = "204", description = "Thumbnail deleted"), - @ApiResponse(responseCode = "400", description = "Invalid id", content = @Content(schema = @Schema(implementation = ExceptionController.CustomErrorResponse.class))), - @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(schema = @Schema(implementation = ExceptionController.CustomErrorResponse.class))), - @ApiResponse(responseCode = "404", description = "Thumbnail not found", content = @Content(schema = @Schema(implementation = ExceptionController.CustomErrorResponse.class))) + @ApiResponse(responseCode = "400", description = "Invalid id", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "404", description = "Thumbnail not found", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))) } ) ResponseEntity deleteThumbnail(@PathVariable String id); @@ -99,37 +109,46 @@ ResponseEntity> search(@RequestParam(name = "pag consumes = MediaType.MULTIPART_FORM_DATA_VALUE ) @Operation( - operationId = "uploadThumbnail", summary = "Upload thumbnail", description = "Upload thumbnail do evento.", - parameters = { - @Parameter(name = "id", description = "ID do evento a ser consultado", in = ParameterIn.PATH, example = "c971869b-2dd3-4934-beac-172a9a229735", required = true), - @Parameter(name = "file", description = "Arquivo de imagem", in = ParameterIn.QUERY, required = true, content = @Content(mediaType = "multipart/form-data", schema = @Schema(type = "string", format = "binary"))), - @Parameter(name = "fileName", description = "Nome do arquivo", in = ParameterIn.QUERY, required = true, example = "thumbnail") + security = { + @SecurityRequirement(name = "bearer") }, responses = { - @ApiResponse(responseCode = "200", description = "Thumbnail uploaded"), - @ApiResponse(responseCode = "400", description = "Invalid id", content = @Content(schema = @Schema(implementation = ExceptionController.CustomErrorResponse.class))), - @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(schema = @Schema(implementation = ExceptionController.CustomErrorResponse.class))), - @ApiResponse(responseCode = "404", description = "Event not found", content = @Content(schema = @Schema(implementation = ExceptionController.CustomErrorResponse.class))) - }, - method = "POST" + @ApiResponse(responseCode = "202", description = "Thumbnail uploaded"), + @ApiResponse(responseCode = "400", description = "Invalid id", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "404", description = "Event not found", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))) + } ) ResponseEntity uploadThumbnail(@PathVariable String id, @RequestParam("file") MultipartFile file, @RequestParam("fileName") String fileName); @PostMapping( value = "/{id}/ticketSale", consumes = "application/json") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "Ticket Sale created successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request") - }) + @Operation( + summary = "Create ticket sale", + description = "Create a new ticket sale", + security = { + @SecurityRequirement(name = "bearer") + }, + responses = { + @ApiResponse(responseCode = "201", description = "Ticket Sale created successfully"), + @ApiResponse(responseCode = "400", description = "Invalid request", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "401", description = "Invalid credentials", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "403", description = "Access denied", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity createTicketSale(@PathVariable String id, @RequestBody CreateTicketSaleRequest request); @GetMapping( value = "/{id}/ticketSale", consumes = "application/json") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Ticket Sale list"), - @ApiResponse(responseCode = "400", description = "Invalid request") - }) + @Operation( + summary = "Get ticket sale by event id", + description = "Get ticket sale by event id", + responses = { + @ApiResponse(responseCode = "200", description = "Ticket Sale list"), + @ApiResponse(responseCode = "400", description = "Invalid request", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity> getTicketSaleByEventId(@PathVariable String id); } diff --git a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/TicketAPI.java b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/TicketAPI.java index a7eb805..77fb59b 100644 --- a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/TicketAPI.java +++ b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/TicketAPI.java @@ -2,8 +2,12 @@ import br.com.ifsp.tickets.domain.shared.search.Pagination; import br.com.ifsp.tickets.infra.contexts.ticket.models.TicketResponse; +import br.com.ifsp.tickets.infra.shared.APIErrorResponse; +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.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -13,25 +17,51 @@ public interface TicketAPI { @PatchMapping(value = "/{id}/check", produces = "application/json") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Ticket checked successfully"), - @ApiResponse(responseCode = "404", description = "Ticket not found"), - }) + @Operation( + summary = "Check ticket", + description = "Check ticket by id", + security = { + @SecurityRequirement(name = "bearer") + }, + responses = { + @ApiResponse(responseCode = "202", description = "Ticket checked successfully"), + @ApiResponse(responseCode = "404", description = "Ticket not found", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "400", description = "Invalid request", content = @Content(schema = @Schema(implementation = APIErrorResponse.class))), + } + ) ResponseEntity check(@PathVariable String id); @GetMapping(value = "/{id}", produces = "application/json") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Ticket retrieved successfully"), - @ApiResponse(responseCode = "404", description = "Ticket not found"), - }) + @Operation( + summary = "Get ticket by id", + description = "Get ticket information by id", + security = { + @SecurityRequirement(name = "bearer") + }, + responses = { + @ApiResponse(responseCode = "200", description = "Ticket found successfully", content = @Content(mediaType = "application/json", schema = @Schema(implementation = TicketResponse.class))), + @ApiResponse(responseCode = "401", description = "Invalid credentials", content = @Content(mediaType = "application/json", schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "403", description = "Access denied", content = @Content(mediaType = "application/json", schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "404", description = "Ticket not found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity get(@PathVariable String id); @GetMapping(value = "/search/user/{id}", produces = "application/json") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Tickets retrieved successfully"), - @ApiResponse(responseCode = "404", description = "Tickets not found"), - }) + @Operation( + summary = "Search tickets by user", + description = "Search tickets by user id", + security = { + @SecurityRequirement(name = "bearer") + }, + responses = { + @ApiResponse(responseCode = "200", description = "Tickets retrieved successfully", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Pagination.class))), + @ApiResponse(responseCode = "401", description = "Invalid credentials", content = @Content(mediaType = "application/json", schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "403", description = "Access denied", content = @Content(mediaType = "application/json", schema = @Schema(implementation = APIErrorResponse.class))), + @ApiResponse(responseCode = "404", description = "Tickets not found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = APIErrorResponse.class))) + } + ) ResponseEntity> simpleSearch(@PathVariable String id, @RequestParam(name = "page", required = false, defaultValue = "0") Integer page, @RequestParam(name = "perPage", required = false, defaultValue = "10") Integer perPage, @RequestParam(name = "terms", required = false, defaultValue = "") String terms); diff --git a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/controllers/ExceptionController.java b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/controllers/ExceptionController.java index d564b1c..ebc14d7 100644 --- a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/controllers/ExceptionController.java +++ b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/controllers/ExceptionController.java @@ -2,7 +2,7 @@ import br.com.ifsp.tickets.domain.shared.exceptions.*; import br.com.ifsp.tickets.domain.shared.validation.Error; -import com.fasterxml.jackson.annotation.JsonInclude; +import br.com.ifsp.tickets.infra.shared.APIErrorResponse; import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; @@ -13,7 +13,6 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import javax.naming.AuthenticationException; -import java.util.List; import java.util.stream.Stream; @ControllerAdvice @@ -21,100 +20,90 @@ public class ExceptionController extends ResponseEntityExceptionHandler { @ExceptionHandler(ValidationException.class) - public ResponseEntity handleNotificationException(ValidationException ex, HttpServletRequest request) { - final CustomErrorResponse errorResponse = new CustomErrorResponse( + public ResponseEntity handleNotificationException(ValidationException ex, HttpServletRequest request) { + final APIErrorResponse APIErrorResponse = new APIErrorResponse( ex.getMessage(), HttpStatus.BAD_REQUEST.value(), - ex.getErrors().stream().map(ErrorResponse::from).toList() + ex.getErrors().stream().map(APIError::from).toList() ); - return ResponseEntity.badRequest().body(errorResponse); + return ResponseEntity.badRequest().body(APIErrorResponse); } @ExceptionHandler(IllegalArgumentException.class) - public ResponseEntity handleIllegalArgumentException(IllegalArgumentException ex, HttpServletRequest request) { - final CustomErrorResponse errorResponse = new CustomErrorResponse( + public ResponseEntity handleIllegalArgumentException(IllegalArgumentException ex, HttpServletRequest request) { + final APIErrorResponse APIErrorResponse = new APIErrorResponse( "Illegal json attribute value", HttpStatus.BAD_REQUEST.value(), - Stream.of(new Error(ex.getMessage())).map(ErrorResponse::from).toList() + Stream.of(new Error(ex.getMessage())).map(APIError::from).toList() ); - return ResponseEntity.badRequest().body(errorResponse); + return ResponseEntity.badRequest().body(APIErrorResponse); } @ExceptionHandler(IllegalCommandField.class) - public ResponseEntity handleIllegalCommandField(IllegalCommandField ex, HttpServletRequest request) { - final CustomErrorResponse errorResponse = new CustomErrorResponse( + public ResponseEntity handleIllegalCommandField(IllegalCommandField ex, HttpServletRequest request) { + final APIErrorResponse APIErrorResponse = new APIErrorResponse( ex.getMessage(), HttpStatus.BAD_REQUEST.value(), - ex.getErrors().stream().map(ErrorResponse::from).toList() + ex.getErrors().stream().map(APIError::from).toList() ); - return ResponseEntity.badRequest().body(errorResponse); + return ResponseEntity.badRequest().body(APIErrorResponse); } @ExceptionHandler(NotFoundException.class) - public ResponseEntity handleNotFoundException(NotFoundException ex, HttpServletRequest request) { - final CustomErrorResponse errorResponse = new CustomErrorResponse( + public ResponseEntity handleNotFoundException(NotFoundException ex, HttpServletRequest request) { + final APIErrorResponse APIErrorResponse = new APIErrorResponse( ex.getMessage(), HttpStatus.NOT_FOUND.value(), null ); - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(APIErrorResponse); } @ExceptionHandler(AuthenticationException.class) - public ResponseEntity handleAuthenticationException(AuthenticationException ex, HttpServletRequest request) { - final CustomErrorResponse errorResponse = new CustomErrorResponse( + public ResponseEntity handleAuthenticationException(AuthenticationException ex, HttpServletRequest request) { + final APIErrorResponse APIErrorResponse = new APIErrorResponse( ex.getMessage(), HttpStatus.UNAUTHORIZED.value(), null ); - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(APIErrorResponse); } @ExceptionHandler(IllegalResourceAccessException.class) - public ResponseEntity handleIllegalResourceAccess(IllegalResourceAccessException ex, HttpServletRequest request) { - final CustomErrorResponse errorResponse = new CustomErrorResponse( + public ResponseEntity handleIllegalResourceAccess(IllegalResourceAccessException ex, HttpServletRequest request) { + final APIErrorResponse APIErrorResponse = new APIErrorResponse( ex.getMessage(), HttpStatus.FORBIDDEN.value(), null ); - return ResponseEntity.status(HttpStatus.FORBIDDEN).body(errorResponse); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(APIErrorResponse); } @ExceptionHandler(DomainException.class) - public ResponseEntity handleDomainException(DomainException ex, HttpServletRequest request) { - final CustomErrorResponse errorResponse = new CustomErrorResponse( + public ResponseEntity handleDomainException(DomainException ex, HttpServletRequest request) { + final APIErrorResponse APIErrorResponse = new APIErrorResponse( ex.getMessage(), HttpStatus.BAD_REQUEST.value(), null ); log.warn(ex.getMessage(), ex.getCause()); - return ResponseEntity.badRequest().body(errorResponse); + return ResponseEntity.badRequest().body(APIErrorResponse); } - @JsonInclude(JsonInclude.Include.NON_NULL) - public record CustomErrorResponse( - @JsonProperty("message") - String message, - @JsonProperty("status") - int status, - @JsonProperty("errors") - List errors - ) { - } - public record ErrorResponse( + public record APIError( @JsonProperty("message") String message ) { - public static ErrorResponse from(Error error) { - return new ErrorResponse(error.message()); + public static APIError from(Error error) { + return new APIError(error.message()); } } diff --git a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/config/WebServerConfig.java b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/config/WebServerConfig.java index 13b47eb..72f6b91 100644 --- a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/config/WebServerConfig.java +++ b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/config/WebServerConfig.java @@ -4,14 +4,13 @@ import br.com.ifsp.tickets.domain.communication.message.Message; import br.com.ifsp.tickets.domain.communication.message.type.MessageSubject; import br.com.ifsp.tickets.domain.communication.message.type.MessageType; -import br.com.ifsp.tickets.domain.company.ICompanyGateway; -import br.com.ifsp.tickets.domain.event.IEventGateway; -import br.com.ifsp.tickets.domain.user.IUserGateway; import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.info.Contact; import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.models.tags.Tag; import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; +import org.springdoc.core.customizers.OpenApiCustomizer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -20,24 +19,27 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.security.crypto.password.PasswordEncoder; import java.io.IOException; import java.io.InputStream; +import java.util.List; import java.util.Scanner; +import java.util.stream.Collectors; @Configuration @EnableScheduling @EnableAsync @ComponentScan("br.com.ifsp.tickets.infra") @Slf4j -@OpenAPIDefinition(info = @Info(title = "Tickets API", version = "v1", description = "Tickets API Documentation", contact = @Contact(name = "Leonardo da Silva", email = "oproprioleonardo@gmail.com", url = "https://linktr.ee/_oleonardosilva"))) +@OpenAPIDefinition( + info = @Info(title = "Tickets API", version = "v1", description = "Tickets API Documentation", contact = @Contact(name = "IFSP CBT - Informática", email = "ifspcbt.informatica@gmail.com", url = "https://linktr.ee/_oleonardosilva")) +) public class WebServerConfig { private final ResourceLoader resourceLoader; @Autowired - public WebServerConfig(ResourceLoader resourceLoader, IMessageGateway messageGateway, PasswordEncoder passwordEncoder, IUserGateway userGateway, ICompanyGateway companyGateway, IEventGateway eventGateway) { + public WebServerConfig(ResourceLoader resourceLoader, IMessageGateway messageGateway) { this.resourceLoader = resourceLoader; log.info("Creating default messages..."); @@ -77,4 +79,18 @@ public OkHttpClient okHttpClient() { return new OkHttpClient.Builder().build(); } + @Bean + public OpenApiCustomizer alphabeticalTagsCustomizer() { + return openApi -> { + final List tags = openApi.getTags(); + + if (tags != null) { + final List sortedTags = tags.stream() + .sorted((tag1, tag2) -> tag1.getName().compareToIgnoreCase(tag2.getName())) + .collect(Collectors.toList()); + + openApi.setTags(sortedTags); + } + }; + } } diff --git a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/shared/APIErrorResponse.java b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/shared/APIErrorResponse.java new file mode 100644 index 0000000..4ba865f --- /dev/null +++ b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/shared/APIErrorResponse.java @@ -0,0 +1,18 @@ +package br.com.ifsp.tickets.infra.shared; + +import br.com.ifsp.tickets.infra.api.controllers.ExceptionController; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record APIErrorResponse( + @JsonProperty("message") + String message, + @JsonProperty("status") + int status, + @JsonProperty("errors") + List errors +) { +} \ No newline at end of file diff --git a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/shared/package-info.java b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/shared/package-info.java deleted file mode 100644 index 696dcb9..0000000 --- a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/shared/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NonNullApi -package br.com.ifsp.tickets.infra.shared; - -import org.springframework.lang.NonNullApi; \ No newline at end of file