diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 53ab4c6..fe9c9ac 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -26,13 +26,6 @@ runtime - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - ${springdoc.version} - - org.slf4j slf4j-api diff --git a/infrastructure/pom.xml b/infrastructure/pom.xml index 12257c7..9e4fd99 100644 --- a/infrastructure/pom.xml +++ b/infrastructure/pom.xml @@ -74,6 +74,14 @@ 1.39.0 + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc.version} + + com.google.apis google-api-services-gmail diff --git a/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/config/OpenApiConfig.java b/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/config/OpenApiConfig.java new file mode 100644 index 0000000..a500f5b --- /dev/null +++ b/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/config/OpenApiConfig.java @@ -0,0 +1,46 @@ +package com.pablotj.restemailbridge.infrastructure.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.parameters.Parameter; +import io.swagger.v3.oas.models.media.StringSchema; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springdoc.core.customizers.OpenApiCustomizer; +@Configuration +public class OpenApiConfig { + + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI() + .info(new Info() + .title("Rest Email Bridge API") + .version("v1") + .description("API for sending and managing emails") + .license(new License().name("Apache 2.0").url("https://www.apache.org/licenses/LICENSE-2.0")) + ); + } + + + @Bean + public OpenApiCustomizer globalHeaderCustomizer() { + return openApi -> openApi.getPaths().values().forEach(pathItem -> + pathItem.readOperations().forEach(operation -> + operation.addParametersItem( + new Parameter() + .in("header") + .name("Accept-Language") + .description("Language for messages (en, es, gl)") + .required(false) + .schema(new StringSchema() + ._default("en") + .addEnumItem("en") + .addEnumItem("es") + .addEnumItem("gl")) + ) + ) + ); + } +} \ No newline at end of file diff --git a/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/rest/MailController.java b/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/rest/MailController.java index 5d7340a..5983ed7 100644 --- a/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/rest/MailController.java +++ b/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/rest/MailController.java @@ -3,6 +3,13 @@ package com.pablotj.restemailbridge.infrastructure.rest; import com.pablotj.restemailbridge.application.dto.EmailDTO; import com.pablotj.restemailbridge.application.usecase.SendEmailUseCase; import com.pablotj.restemailbridge.infrastructure.rest.dto.SendMailRequest; +import io.swagger.v3.oas.annotations.Operation; +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 jakarta.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -10,18 +17,76 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +/** + * REST controller responsible for handling email-related requests. + *

+ * Exposes endpoints under {@code /v1/mail} to send emails through the system. + * Delegates business logic to the {@link SendEmailUseCase}. + */ @RestController @RequestMapping("/v1/mail") +@Tag(name = "Mail API", description = "Endpoints for sending emails") public class MailController { private final SendEmailUseCase sendEmailUseCase; + /** + * Creates a new {@link MailController} instance. + * + * @param sendEmailUseCase the use case responsible for sending emails + */ public MailController(SendEmailUseCase sendEmailUseCase) { this.sendEmailUseCase = sendEmailUseCase; } - + /** + * Sends a new email using the provided request data. + *

+ * The request payload is validated using {@link jakarta.validation.Valid}. + * + * @param request the email request containing sender, subject and body + * @return {@link ResponseEntity} with HTTP 200 (OK) if the email is sent successfully + */ @PostMapping + @Operation( + summary = "Send an email", + description = "Sends an email using the provided sender, subject, and body.", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + required = true, + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + name = "Basic email", + value = "{ \"from\": \"user@example.com\", \"subject\": \"Hello\", \"body\": \"Hi there!\" }" + ) + ) + ) + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Email sent successfully"), + @ApiResponse(responseCode = "400", description = "Invalid request payload"), + @ApiResponse( + responseCode = "401", + description = "Unauthorized – missing or invalid authentication token", + content = @Content(schema = @Schema(hidden = true)) + ), + @ApiResponse( + responseCode = "403", + description = "Forbidden – the authenticated user cannot send to the specified recipient", + content = @Content(schema = @Schema(hidden = true)) + ), + @ApiResponse( + responseCode = "422", + description = "Unprocessable Entity – domain validation failed (e.g. invalid email address, business rule violation)", + content = @Content( + mediaType = "application/json", + schema = @Schema( + example = "{ \"error\": \"Invalid recipient domain\" }" + ) + ) + ), + @ApiResponse(responseCode = "500", description = "Unexpected server error") + }) public ResponseEntity send(@Valid @RequestBody SendMailRequest request) { sendEmailUseCase.handle(new EmailDTO(request.from(), request.subject(), request.body())); return ResponseEntity.ok().build(); diff --git a/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/rest/dto/SendMailRequest.java b/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/rest/dto/SendMailRequest.java index 11c752d..c9143ec 100644 --- a/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/rest/dto/SendMailRequest.java +++ b/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/rest/dto/SendMailRequest.java @@ -1,12 +1,26 @@ package com.pablotj.restemailbridge.infrastructure.rest.dto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import org.hibernate.validator.constraints.Length; +@Schema(description = "Request payload to send an email") public record SendMailRequest( - @NotBlank @Email @Length(min = 4, max = 100) String from, - @NotBlank @Length(min=1, max = 30) String subject, - @NotBlank @Length(min=1, max = 4000) String body + @NotBlank(message = "{email.from.blank}") + @Email(message = "{email.from.invalid}") + @Length(min = 4, max = 100, message = "email.from.length}") + @Schema(description = "Sender email address", example = "user@example.com") + String from, + + @NotBlank(message = "{email.subject.blank}") + @Length(min=1, max = 30, message = "{email.subject.length}") + @Schema(description = "Email subject", example = "Welcome to RestEmailBridge") + String subject, + + @NotBlank(message = "{email.body.blank}") + @Length(min=1, max = 4000, message = "{email.body.length}") + @Schema(description = "Email body content", example = "Hello, thanks for signing up!") + String body ) { } \ No newline at end of file