docs(api): add API documentation

This commit is contained in:
Pablo de la Torre Jamardo 2025-09-15 08:25:26 +02:00
parent d417a46a06
commit fb0ddf391f
5 changed files with 137 additions and 11 deletions

View File

@ -26,13 +26,6 @@
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<!-- Swagger/OpenAPI -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>

View File

@ -74,6 +74,14 @@
<version>1.39.0</version> <version>1.39.0</version>
</dependency> </dependency>
<!-- Swagger/OpenAPI -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.google.apis</groupId> <groupId>com.google.apis</groupId>
<artifactId>google-api-services-gmail</artifactId> <artifactId>google-api-services-gmail</artifactId>

View File

@ -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"))
)
)
);
}
}

View File

@ -3,6 +3,13 @@ package com.pablotj.restemailbridge.infrastructure.rest;
import com.pablotj.restemailbridge.application.dto.EmailDTO; import com.pablotj.restemailbridge.application.dto.EmailDTO;
import com.pablotj.restemailbridge.application.usecase.SendEmailUseCase; import com.pablotj.restemailbridge.application.usecase.SendEmailUseCase;
import com.pablotj.restemailbridge.infrastructure.rest.dto.SendMailRequest; 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 jakarta.validation.Valid;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping; 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.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
/**
* REST controller responsible for handling email-related requests.
* <p>
* Exposes endpoints under {@code /v1/mail} to send emails through the system.
* Delegates business logic to the {@link SendEmailUseCase}.
*/
@RestController @RestController
@RequestMapping("/v1/mail") @RequestMapping("/v1/mail")
@Tag(name = "Mail API", description = "Endpoints for sending emails")
public class MailController { public class MailController {
private final SendEmailUseCase sendEmailUseCase; private final SendEmailUseCase sendEmailUseCase;
/**
* Creates a new {@link MailController} instance.
*
* @param sendEmailUseCase the use case responsible for sending emails
*/
public MailController(SendEmailUseCase sendEmailUseCase) { public MailController(SendEmailUseCase sendEmailUseCase) {
this.sendEmailUseCase = sendEmailUseCase; this.sendEmailUseCase = sendEmailUseCase;
} }
/**
* Sends a new email using the provided request data.
* <p>
* 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 @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<Void> send(@Valid @RequestBody SendMailRequest request) { public ResponseEntity<Void> send(@Valid @RequestBody SendMailRequest request) {
sendEmailUseCase.handle(new EmailDTO(request.from(), request.subject(), request.body())); sendEmailUseCase.handle(new EmailDTO(request.from(), request.subject(), request.body()));
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();

View File

@ -1,12 +1,26 @@
package com.pablotj.restemailbridge.infrastructure.rest.dto; package com.pablotj.restemailbridge.infrastructure.rest.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.Length;
@Schema(description = "Request payload to send an email")
public record SendMailRequest( public record SendMailRequest(
@NotBlank @Email @Length(min = 4, max = 100) String from, @NotBlank(message = "{email.from.blank}")
@NotBlank @Length(min=1, max = 30) String subject, @Email(message = "{email.from.invalid}")
@NotBlank @Length(min=1, max = 4000) String body @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
) { ) {
} }