docs: add open api docs and javadoc

issue #46
This commit is contained in:
Ricardo Campos
2024-09-18 11:45:20 -03:00
parent 0bdb68c7e2
commit 5aac9c43d6
15 changed files with 181 additions and 29 deletions
@@ -1,10 +1,13 @@
package br.com.tasknoteapp.java_api.controller;
import br.com.tasknoteapp.java_api.exception.UserAlreadyExistsException;
import br.com.tasknoteapp.java_api.exception.UserNotFoundException;
import br.com.tasknoteapp.java_api.request.LoginRequest;
import br.com.tasknoteapp.java_api.response.JwtAuthenticationResponse;
import br.com.tasknoteapp.java_api.service.AuthService;
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.tags.Tag;
import jakarta.validation.Valid;
@@ -17,9 +20,12 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/** This class contains resources for handling authentication. */
@RestController
@RequestMapping("/auth")
@Tag(name = "Authentication", description = "Authentication controller.")
@Tag(
name = "Authentication",
description = "Authentication resources to handle user authentication.")
@AllArgsConstructor
public class AuthenticationController {
@@ -29,7 +35,8 @@ public class AuthenticationController {
* Authenticate a user given his email and password.
*
* @param loginRequest User data containing email and password.
* @return OK if authenticated, 401 - Unauthorized otherwise
* @return JwtAuthenticationResponse with token created.
* @throws UserNotFoundException if user not found
*/
@PostMapping(path = "/signin", consumes = "application/json", produces = "application/json")
@Operation(
@@ -37,7 +44,18 @@ public class AuthenticationController {
description = "SigIn an existing user given his email and password",
responses = {
@ApiResponse(responseCode = "200", description = "User successfully logged in"),
@ApiResponse(responseCode = "400", description = "Wrong or missing information"),
@ApiResponse(
responseCode = "400",
description = "Wrong or missing information",
content = @Content(schema = @Schema(implementation = Void.class))),
@ApiResponse(
responseCode = "403",
description = "Forbidden. Access Denied",
content = @Content(schema = @Schema(implementation = Void.class))),
@ApiResponse(
responseCode = "404",
description = "User not found",
content = @Content(schema = @Schema(implementation = Void.class)))
})
public JwtAuthenticationResponse signin(@RequestBody @Valid LoginRequest loginRequest) {
String token = authService.signin(loginRequest);
@@ -57,8 +75,14 @@ public class AuthenticationController {
description = "Signup a new user given his email and password",
responses = {
@ApiResponse(responseCode = "201", description = "User successfully created and saved"),
@ApiResponse(responseCode = "400", description = "Wrong or missing information"),
@ApiResponse(responseCode = "409", description = "User already exists")
@ApiResponse(
responseCode = "400",
description = "Wrong or missing information",
content = @Content(schema = @Schema(implementation = Void.class))),
@ApiResponse(
responseCode = "409",
description = "User already exists",
content = @Content(schema = @Schema(implementation = Void.class)))
})
public ResponseEntity<JwtAuthenticationResponse> signup(
@RequestBody @Valid LoginRequest loginRequest) {
@@ -1,15 +0,0 @@
package br.com.tasknoteapp.java_api.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/rest/some")
public class SomeController {
@GetMapping
public String some() {
return "Some";
}
}
@@ -1,10 +1,18 @@
package br.com.tasknoteapp.java_api.controller;
import br.com.tasknoteapp.java_api.entity.TaskEntity;
import br.com.tasknoteapp.java_api.exception.TaskNotFoundException;
import br.com.tasknoteapp.java_api.request.TaskPatchRequest;
import br.com.tasknoteapp.java_api.request.TaskRequest;
import br.com.tasknoteapp.java_api.response.TaskResponse;
import br.com.tasknoteapp.java_api.service.TaskService;
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.tags.Tag;
import jakarta.validation.Valid;
import java.util.List;
import lombok.AllArgsConstructor;
@@ -19,27 +27,123 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/** This class contains resources for handling tasks. */
@RestController
@RequestMapping("/rest/tasks")
@Tag(name = "Tasks", description = "Tasks resources to handle user tasks and urls.")
@AllArgsConstructor
public class TaskController {
private final TaskService taskService;
/**
* Get all tasks.
*
* @return List of TaskResponse with all found tasks and its urls, if any.
*/
@GetMapping
@Operation(
summary = "Get all tasks",
description = "Get all tasks for the current user and its urls, if any",
responses = {
@ApiResponse(
responseCode = "200",
description = "Tasks successfully retrieved",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = TaskResponse.class, type = "array"))),
@ApiResponse(
responseCode = "403",
description = "Forbidden. Access Denied",
content = @Content(schema = @Schema(implementation = Void.class)))
})
public List<TaskResponse> getAllTasks() {
return taskService.getAllTasks();
}
/**
* Patch a task.
*
* @param id The task id to be patched.
* @param taskRequest Task data to be patched, including optionally its urls.
* @return TaskResponse containing data that was updated.
* @throws TaskNotFoundException when task not found.
*/
@PatchMapping("/{id}")
@Operation(
summary = "Patch a task",
description = "Patch a task and all its urls. Option to patch only the urls.",
responses = {
@ApiResponse(
responseCode = "200",
description = "Task successfully patched",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = TaskResponse.class))),
@ApiResponse(
responseCode = "403",
description = "Forbidden. Access Denied",
content = @Content(schema = @Schema(implementation = Void.class))),
@ApiResponse(
responseCode = "404",
description = "Task not found",
content = @Content(schema = @Schema(implementation = Void.class)))
})
public ResponseEntity<TaskResponse> putTask(
@PathVariable Long id, @RequestBody @Valid TaskPatchRequest taskRequest) {
@Parameter(
name = "id",
in = ParameterIn.PATH,
description = "Task id to be patched.",
required = true,
schema = @Schema(type = "integer", format = "int64"))
@PathVariable
Long id,
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "Task data to be patched, including optionally its urls.",
required = true)
@RequestBody
@Valid
TaskPatchRequest taskRequest) {
return ResponseEntity.ok(taskService.patchTask(id, taskRequest));
}
// https://restfulapi.net/rest-put-vs-post/
/**
* Create a task.
*
* @param taskRequest Task data to be created, including optionally its urls. Following RESTful
* API pattern from https://restfulapi.net/rest-put-vs-post/.
* @return TaskResponse containing data that was created.
*/
@PostMapping
public ResponseEntity<TaskResponse> postTasks(@RequestBody @Valid TaskRequest taskRequest) {
@Operation(
summary = "Create a task",
description = "Create a task and all its urls.",
responses = {
@ApiResponse(
responseCode = "201",
description = "Task successfully crated.",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = TaskResponse.class))),
@ApiResponse(
responseCode = "400",
description = "Wrong or missing information",
content = @Content(schema = @Schema(implementation = Void.class))),
@ApiResponse(
responseCode = "403",
description = "Forbidden. Access Denied",
content = @Content(schema = @Schema(implementation = Void.class))),
})
public ResponseEntity<TaskResponse> postTasks(
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "Task data to be created, including optionally its urls.",
required = true)
@RequestBody
@Valid
TaskRequest taskRequest) {
TaskEntity createdTask = taskService.createTask(taskRequest);
return ResponseEntity.status(HttpStatus.CREATED).body(TaskResponse.fromEntity(createdTask));
}
@@ -1,5 +1,11 @@
package br.com.tasknoteapp.java_api.request;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
public record TaskPatchRequest(String description, Boolean done, List<TaskUrlPatchRequest> urls) {}
/** This record represents a task patch payload. */
@Schema(description = "Task patch payload.")
public record TaskPatchRequest(
@Schema(description = "Task description. Optional.") String description,
@Schema(description = "Task done definition. Optional.") Boolean done,
@Schema(description = "Task urls. Optional.") List<TaskUrlPatchRequest> urls) {}
@@ -1,6 +1,11 @@
package br.com.tasknoteapp.java_api.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import java.util.List;
public record TaskRequest(@NotNull String description, List<String> urls) {}
/** This record represents a task request to be created. */
@Schema(description = "Task request to be created.")
public record TaskRequest(
@Schema(description = "Task description.") @NotNull String description,
@Schema(description = "Task urls. Optional.") List<String> urls) {}
@@ -2,12 +2,26 @@ package br.com.tasknoteapp.java_api.response;
import br.com.tasknoteapp.java_api.entity.TaskEntity;
import br.com.tasknoteapp.java_api.entity.TaskUrlEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public record TaskResponse(Long id, String description, Boolean done, List<TaskUrlResponse> urls) {
/** This record represents a task and its urls object to be returned. */
@Schema(description = "This record represents a task and its urls object to be returned.")
public record TaskResponse(
@Schema(description = "The id of the task", example = "1") Long id,
@Schema(description = "The description of the task", example = "Task 1") String description,
@Schema(description = "The done status of the task", example = "false") Boolean done,
@Schema(description = "The urls of the task, zero, one or more.", example = "[]")
List<TaskUrlResponse> urls) {
/**
* Creates a TaskResponse given a TaskEntity and its Urls.
*
* @param entity The TaskEntity source data.
* @return TaskResponse instance with all task data and urls, if any.
*/
public static TaskResponse fromEntity(TaskEntity entity) {
List<TaskUrlEntity> urls = entity.getUrls();
List<TaskUrlResponse> urlsResponse = new ArrayList<>();
@@ -1,3 +1,9 @@
package br.com.tasknoteapp.java_api.response;
public record TaskUrlResponse(Long id, String url) {}
import io.swagger.v3.oas.annotations.media.Schema;
/** This record represents a task url object. */
@Schema(description = "This record represents a task url object.")
public record TaskUrlResponse(
@Schema(description = "Task url id", example = "1") Long id,
@Schema(description = "Task url link", example = "http://duckduckgo.com") String url) {}
@@ -5,6 +5,7 @@ import br.com.tasknoteapp.java_api.request.LoginRequest;
import java.util.Optional;
import org.springframework.security.core.userdetails.User;
/** This interface contains methods for handling user Authentication. */
public interface AuthService {
/**
@@ -4,6 +4,7 @@ import java.time.LocalDateTime;
import java.util.Map;
import org.springframework.security.core.userdetails.UserDetails;
/** This interface contains methods for handling user JWT tokens. */
public interface JwtService {
public String getEmailFromToken(String token);
@@ -6,6 +6,7 @@ import br.com.tasknoteapp.java_api.request.TaskRequest;
import br.com.tasknoteapp.java_api.response.TaskResponse;
import java.util.List;
/** This interface contains methods for handling user user Tasks. */
public interface TaskService {
public List<TaskResponse> getAllTasks();
@@ -2,6 +2,7 @@ package br.com.tasknoteapp.java_api.service;
import org.springframework.security.core.userdetails.UserDetailsService;
/** This interface contains methods for handling user details withing Spring Auth. */
public interface UserService {
UserDetailsService userDetailsService();
}
@@ -18,6 +18,7 @@ import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
/** This class contains the implementation for the Auth Service class. */
@Slf4j
@Service
@AllArgsConstructor
@@ -12,8 +12,9 @@ import javax.crypto.SecretKey;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
/** This class contains the implementation for the Jwt Service class. */
@Service
public class JwtServiceImpl implements JwtService {
class JwtServiceImpl implements JwtService {
private final long SECOND = 1000;
private final long MINUTE = SECOND * 60;
@@ -22,6 +22,7 @@ import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/** This class contains the implementation for the Task Service class. */
@Slf4j
@Service
@AllArgsConstructor
@@ -9,9 +9,10 @@ import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
/** This class contains the implementation for the User Service class. */
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
class UserServiceImpl implements UserService {
private final UserRepository userRepository;