diff --git a/.gitignore b/.gitignore index 549e00a..3b5258c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ HELP.md +pdf/ target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ +htmlReport ### STS ### .apt_generated diff --git a/mvnw.cmd b/mvnw.cmd index 95ba6f5..ed2c040 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -1,10 +1,10 @@ @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file +@REM or more contributor license agreements. See the NOTICE fileEntity @REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file +@REM regarding copyright ownership. The ASF licenses this fileEntity @REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance +@REM "License"); you may not use this fileEntity except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM https://www.apache.org/licenses/LICENSE-2.0 @@ -153,7 +153,7 @@ if exist %WRAPPER_JAR% ( ) @REM End of extension -@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar fileEntity SET WRAPPER_SHA_256_SUM="" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B diff --git a/pom.xml b/pom.xml index 882861c..3d4bc8c 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,7 @@ sempre-alerta 17 + 2.3.0 @@ -37,11 +38,34 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security + spring-security-test + test + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc-openapi-starter-webmvc-ui.version} + + + org.springdoc + springdoc-openapi-starter-webmvc-api + ${springdoc-openapi-starter-webmvc-ui.version} + org.flywaydb flyway-core - + + com.h2database + h2 + test + org.postgresql postgresql @@ -52,6 +76,23 @@ lombok true + + + com.github.slugify + slugify + 3.0.6 + + + com.github.javafaker + javafaker + 1.0.2 + test + + + com.auth0 + java-jwt + 4.4.0 + org.springframework.boot spring-boot-starter-test @@ -93,6 +134,10 @@ lombok + + matheusvict/${project.artifactId}:${project.version} + + IF_NOT_PRESENT diff --git a/src/main/java/com/institutosemprealerta/semprealerta/SempreAlertaApplication.java b/src/main/java/com/institutosemprealerta/semprealerta/SempreAlertaApplication.java index 3c8af65..1efe528 100644 --- a/src/main/java/com/institutosemprealerta/semprealerta/SempreAlertaApplication.java +++ b/src/main/java/com/institutosemprealerta/semprealerta/SempreAlertaApplication.java @@ -1,9 +1,12 @@ package com.institutosemprealerta.semprealerta; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.servers.Server; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication +@OpenAPIDefinition(servers = {@Server(url = "/", description = "Default server url")}) public class SempreAlertaApplication { public static void main(String[] args) { diff --git a/src/main/java/com/institutosemprealerta/semprealerta/application/controllers/AuthenticationController.java b/src/main/java/com/institutosemprealerta/semprealerta/application/controllers/AuthenticationController.java new file mode 100644 index 0000000..693df89 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/application/controllers/AuthenticationController.java @@ -0,0 +1,33 @@ +package com.institutosemprealerta.semprealerta.application.controllers; + +import com.institutosemprealerta.semprealerta.domain.ports.out.request.LoginDTO; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.LoginResponse; +import com.institutosemprealerta.semprealerta.domain.service.AuthenticationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/auth") +@Tag(name = "auth") +public class AuthenticationController { + + private final AuthenticationService authenticationService; + + public AuthenticationController(AuthenticationService authenticationService) { + this.authenticationService = authenticationService; + } + + @PostMapping("/login") + @Operation(summary = "Login", description = "You can login with your email and password") + + public ResponseEntity login(@RequestBody @Valid LoginDTO loginRequestBody) { + LoginResponse token = authenticationService.login(loginRequestBody); + return ResponseEntity.ok(token); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/application/controllers/FilesStorageController.java b/src/main/java/com/institutosemprealerta/semprealerta/application/controllers/FilesStorageController.java new file mode 100644 index 0000000..8dffd37 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/application/controllers/FilesStorageController.java @@ -0,0 +1,89 @@ +package com.institutosemprealerta.semprealerta.application.controllers; + +import com.institutosemprealerta.semprealerta.domain.service.StorageService; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.FileResponse; +import com.institutosemprealerta.semprealerta.swagger.annotations.BadRequestResponse; +import com.institutosemprealerta.semprealerta.swagger.annotations.CreatedResponse; +import com.institutosemprealerta.semprealerta.swagger.annotations.NotFoundResponse; +import com.institutosemprealerta.semprealerta.swagger.annotations.OkResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import java.io.IOException; +import java.net.URI; +import java.util.List; + +@Controller +@RequestMapping("/api/v1/files") +@Tag(name = "Files", description = "Files management") +public class FilesStorageController { + private StorageService storageService; + + public FilesStorageController(StorageService storageService) { + this.storageService = storageService; + } + + @Operation(summary = "Faça o upload de um arquivo", description = "Upload de um arquivo para o servidor") + @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @CreatedResponse + @BadRequestResponse + public ResponseEntity uploadFile(@RequestPart("file") MultipartFile file, @RequestParam("file_type") String fileType) { + + String fileName = storageService.store(file, fileType); + + String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath() + .path("/api/v1/files/download/") + .path(fileName) + .toUriString(); + + URI uri = URI.create(fileDownloadUri); + return ResponseEntity.created(uri).body("File uploaded successfully, file name: " + fileName + " on path: " + fileDownloadUri); + } + + @GetMapping("/download/{fileName:.+}") + @ResponseBody + @Operation(summary = "Download de um arquivo", description = "Baixe um arquivo pelo nome do arquivo") + @OkResponse + @NotFoundResponse + @BadRequestResponse + public ResponseEntity downloadFile( + @PathVariable String fileName, + HttpServletRequest request + ) { + + try { + Resource resource = storageService.loadAsResource(fileName); + String contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath()); + + if (contentType == null) { + contentType = MediaType.APPLICATION_OCTET_STREAM.getType(); + } + + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(contentType)) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"") + .body(resource); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @GetMapping("/list") + @Operation(summary = "List todos os arquivos", description = "Liste todos os arquivos do servidor") + @OkResponse + @BadRequestResponse + public ResponseEntity> listFiles() throws IOException { + List fileNames = storageService.loadAll(); + + return ResponseEntity.ok(fileNames); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/application/controllers/PostController.java b/src/main/java/com/institutosemprealerta/semprealerta/application/controllers/PostController.java new file mode 100644 index 0000000..7be3df5 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/application/controllers/PostController.java @@ -0,0 +1,68 @@ +package com.institutosemprealerta.semprealerta.application.controllers; + +import com.institutosemprealerta.semprealerta.domain.service.PostService; +import com.institutosemprealerta.semprealerta.domain.model.Post; +import com.institutosemprealerta.semprealerta.swagger.annotations.*; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; + +@RestController +@RequestMapping("api/v1/posts") +@Tag(name = "Post", description = "Post management") +public class PostController { + private final PostService postService; + + public PostController(PostService postService) { + this.postService = postService; + } + + @GetMapping + @Operation(summary = "Lista de todos os posts", description = "Lista de todos os posts com paginação") + @OkResponse + public ResponseEntity> getAllPosts(Pageable pageable) { + return ResponseEntity.ok(postService.listAll(pageable)); + } + + @PostMapping + @Operation(summary = "Criar postagem", description = "Crie uma nova postagem") + @CreatedResponse + @BadRequestResponse + public ResponseEntity createPost(@Valid @RequestBody Post post) { + String slug = postService.save(post); + return ResponseEntity.created(URI.create("/api/v1/posts/" + slug)).build(); + } + + @GetMapping("/{slug}") + @Operation(summary = "Pegar post pelo slug", description = "Procura um post pelo seu slug") + @OkResponse + @NotFoundResponse + public ResponseEntity getPostBySlug(@PathVariable String slug) { + return ResponseEntity.ok(postService.findBySlug(slug)); + } + + @PutMapping("/{id}") + @Operation(summary = "Atualizar post", description = "Atualize um post existente pelo seu id") + @NoContentResponse + @NotFoundResponse + @BadRequestResponse + public ResponseEntity updatePost(@PathVariable Long id, @Valid @RequestBody Post post) { + postService.update(id, post); + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/{id}") + @Operation(summary = "Deletar post", description = "Deleta um post existente pelo seu id") + @NoContentResponse + @NotFoundResponse + public ResponseEntity deletePost(@PathVariable Long id) { + postService.delete(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/application/controllers/UserController.java b/src/main/java/com/institutosemprealerta/semprealerta/application/controllers/UserController.java new file mode 100644 index 0000000..a98fa57 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/application/controllers/UserController.java @@ -0,0 +1,71 @@ +package com.institutosemprealerta.semprealerta.application.controllers; + +import com.institutosemprealerta.semprealerta.domain.service.UserService; +import com.institutosemprealerta.semprealerta.domain.model.UserDTO; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.UserResponse; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; +import com.institutosemprealerta.semprealerta.swagger.annotations.*; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("api/v1/user") +@Tag(name = "User", description = "Administração de usuários") +public class UserController { + + private final UserService userService; + + public UserController(UserService userService) { + this.userService = userService; + } + + @PostMapping + @Operation(summary = "Criação de um usuário", description = "Criação de um usuário no sistema") + @CreatedResponse + @ConflictResponse + public ResponseEntity createUser(@Valid @RequestBody UserDTO user) { + userService.save(user.toDomain()); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @GetMapping("/{id}") + @Operation(summary = "Busca de um usuário", description = "Busca de um usuário pelo id") + @OkResponse + @NotFoundResponse + public ResponseEntity findById(@PathVariable int id) { + User userFound = userService.findById(id); + return ResponseEntity.ok().body(UserResponse.toResponse(userFound)); + } + + @GetMapping("/registration/{reg}") + @Operation(summary = "Busca de um usuário", description = "Busca de um usuário pela matrícula") + @OkResponse + @NotFoundResponse + public ResponseEntity findByRegistration(@PathVariable String reg) { + User userFound = userService.findByRegistration(reg); + return ResponseEntity.ok().body(UserResponse.toResponse(userFound)); + } + + @PutMapping("/{id}") + @Operation(summary = "Atualização de um usuário", description = "Atualização de um usuário pelo id") + @NoContentResponse + @NotFoundResponse + @ConflictResponse + public ResponseEntity updateUser(@PathVariable int id, @RequestBody UserDTO user) { + userService.update(id, user.toDomain()); + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/{id}") + @Operation(summary = "Deleção de um usuário", description = "Deleção de um usuário pelo id") + @NoContentResponse + @NotFoundResponse + public ResponseEntity deleteUser(@PathVariable int id) { + userService.delete(id); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/application/handler/GlobalExceptionHandler.java b/src/main/java/com/institutosemprealerta/semprealerta/application/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..1361ba7 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/application/handler/GlobalExceptionHandler.java @@ -0,0 +1,118 @@ +package com.institutosemprealerta.semprealerta.application.handler; + +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.ExceptionPattern; +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.file.FileNotFoundException; +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.file.InvalidFileException; +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.post.PostNotFoundException; +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.user.EmailAlreadyExistsException; +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.user.UserNotFoundException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(UserNotFoundException.class) + public ResponseEntity handlerUserNotFoundException(UserNotFoundException exception) { + LocalDateTime timestamp = LocalDateTime.now(); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body( + ExceptionPattern.builder() + .title("User Not Found Exception") + .status(HttpStatus.NOT_FOUND.value()) + .details(exception.getMessage()) + .timestamp(timestamp) + .developerMessage(exception.getClass().getName()) + .build() + ); + } + + @ExceptionHandler(EmailAlreadyExistsException.class) + public ResponseEntity handlerEmailAlreadyExistsException(EmailAlreadyExistsException bre) { + LocalDateTime timestamp = LocalDateTime.now(); + + return ResponseEntity.status(HttpStatus.CONFLICT).body( + ExceptionPattern.builder() + .title("Email already exists, check the documentation") + .status(HttpStatus.CONFLICT.value()) + .details(bre.getMessage()) + .timestamp(timestamp) + .developerMessage(bre.getClass().getName()) + .build() + ); + } + + @ExceptionHandler(PostNotFoundException.class) + public ResponseEntity handlerPostNotFoundException(PostNotFoundException bre) { + LocalDateTime timestamp = LocalDateTime.now(); + + return ResponseEntity.status(HttpStatus.NOT_FOUND).body( + ExceptionPattern.builder() + .title("Post not found, check the documentation") + .status(HttpStatus.NOT_FOUND.value()) + .details(bre.getMessage()) + .timestamp(timestamp) + .developerMessage(bre.getClass().getName()) + .build() + ); + } + + @ExceptionHandler(FileNotFoundException.class) + public ResponseEntity handlerFileNotFoundException(FileNotFoundException bre) { + LocalDateTime timestamp = LocalDateTime.now(); + + return ResponseEntity.status(HttpStatus.NOT_FOUND).body( + ExceptionPattern.builder() + .title("File not found, check the documentation") + .status(HttpStatus.NOT_FOUND.value()) + .details(bre.getMessage()) + .timestamp(timestamp) + .developerMessage(bre.getClass().getName()) + .build() + ); + } + + @ExceptionHandler(InvalidFileException.class) + public ResponseEntity handlerInvalidFileException(InvalidFileException bre) { + LocalDateTime timestamp = LocalDateTime.now(); + + return ResponseEntity.badRequest().body( + ExceptionPattern.builder() + .title("The file is invalid, check the documentation or try another file type") + .status(HttpStatus.BAD_REQUEST.value()) + .details(bre.getMessage()) + .timestamp(timestamp) + .developerMessage(bre.getClass().getName()) + .build() + ); + } + + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handlerMethodArgumentNotValidExceptionException(MethodArgumentNotValidException exception) { + List fieldError = exception.getBindingResult().getFieldErrors(); + + String fields = fieldError.stream().map(FieldError::getField).collect(Collectors.joining(", ")); + String fieldMessage = fieldError.stream().map(FieldError::getDefaultMessage).collect(Collectors.joining(", ")); + LocalDateTime timestamp = LocalDateTime.now(); + + return ResponseEntity.badRequest().body( + ExceptionPattern.builder() + .title("Bad Request Exception, Invalid Fields") + .status(HttpStatus.BAD_REQUEST.value()) + .details("Check the field(s) error") + .timestamp(timestamp) + .developerMessage(exception.getClass().getName()) + .fields(fields) + .fieldsMessage(fieldMessage) + .build() + ); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/model/File.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/model/File.java new file mode 100644 index 0000000..b3aa5a4 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/model/File.java @@ -0,0 +1,33 @@ +package com.institutosemprealerta.semprealerta.domain.model; + +import lombok.Builder; +@Builder +public class File { + private String fileName; + private String fileDownloadUri; + private String fileType; + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getFileDownloadUri() { + return fileDownloadUri; + } + + public void setFileDownloadUri(String fileDownloadUri) { + this.fileDownloadUri = fileDownloadUri; + } + + public String getFileType() { + return fileType; + } + + public void setFileType(String fileType) { + this.fileType = fileType; + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/model/Post.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/model/Post.java new file mode 100644 index 0000000..7ed0db5 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/model/Post.java @@ -0,0 +1,46 @@ +package com.institutosemprealerta.semprealerta.domain.model; + +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter +@Setter +public class Post { + + @Hidden + private Long id; + + @NotBlank(message = "Title é obrigatorio") + @Schema(description = "Title do post", example = "Titulo do post") + private String title; + + private String slug; + + @NotBlank(message = "Content é obrigatorio") + @Schema(description = "Contenteudo do post", example = "Conteudo do post") + private String content; + + @NotBlank(message = "Banner é obrigatorio") + @Schema(description = "Banner do post", example = "https://www.https://github.com/MatheusVict.png") + private String banner; + + @Hidden + private LocalDateTime createdAt; + + public Post(Long id, String title, String slug, String content, String banner, LocalDateTime createdAt) { + this.id = id; + this.title = title; + this.slug = slug; + this.content = content; + this.banner = banner; + this.createdAt = createdAt; + } + + public Post() { + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/model/UserDTO.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/model/UserDTO.java new file mode 100644 index 0000000..004a824 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/model/UserDTO.java @@ -0,0 +1,70 @@ +package com.institutosemprealerta.semprealerta.domain.model; + + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.Address; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.Contact; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.UserRoles; +import com.institutosemprealerta.semprealerta.utils.DateManipulation; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.*; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +public record UserDTO( + @NotBlank + @Schema(description = "Nome do usuário", example = "Matheus Victor") + String name, + @Email + @Schema(description = "Email do usuário", example = "muryllo@gg.com") + String email, + @NotBlank + @Schema(description = "Senha do usuário", example = "123456") + String password, + @NotBlank + @Schema(description = "Telefone do usuário", example = "123456") + String phone, + @Schema(description = "Gênero do usuário", example = "Masculino") + String gender, + @PastOrPresent + @Schema(description = "Data de nascimento do usuário", example = "1999-12-12") + LocalDate birthDate, + @NotNull + @Schema(description = "Papel do usuário", example = "ADMIN") + UserRoles roles, + @Schema(description = "Rua do usuário", example = "Rua 1") + String street, + @Schema(description = "Número da casa do usuário", example = "123") + String number, + @Schema(description = "Cidade do usuário", example = "Igarassu e Lima") + String city, + @Schema(description = "CEP do usuário", example = "123456") + String zipCode + +) { + public User toDomain() { + //LocalDate birth = DateManipulation.stringToLocalDate(birthDate); + Contact contact = new Contact( + email, + phone + ); + + Address address = new Address( + street, + number, + city, + zipCode + ); + return new User( + name, + password, + gender, + birthDate, + roles, + contact, + address + ); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/in/SlugGenerator.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/in/SlugGenerator.java new file mode 100644 index 0000000..31387bc --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/in/SlugGenerator.java @@ -0,0 +1,5 @@ +package com.institutosemprealerta.semprealerta.domain.ports.in; + +public interface SlugGenerator { + String generate(String input); +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/in/impl/SlugifySlugGenerator.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/in/impl/SlugifySlugGenerator.java new file mode 100644 index 0000000..b2de9fb --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/in/impl/SlugifySlugGenerator.java @@ -0,0 +1,19 @@ +package com.institutosemprealerta.semprealerta.domain.ports.in.impl; + +import com.github.slugify.Slugify; +import com.institutosemprealerta.semprealerta.domain.ports.in.SlugGenerator; +import org.springframework.stereotype.Component; + +@Component +public class SlugifySlugGenerator implements SlugGenerator { + private final Slugify slug; + + public SlugifySlugGenerator() { + this.slug = Slugify.builder().build(); + } + + @Override + public String generate(String input) { + return slug.slugify(input); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/FileRepository.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/FileRepository.java new file mode 100644 index 0000000..a1a8b51 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/FileRepository.java @@ -0,0 +1,13 @@ +package com.institutosemprealerta.semprealerta.domain.ports.out; + +import com.institutosemprealerta.semprealerta.domain.model.File; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.FileResponse; + +import java.util.List; + +public interface FileRepository { + void save(File file); + void delete(Long id); + List listAll(); + +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/PostRepository.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/PostRepository.java new file mode 100644 index 0000000..34e614c --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/PostRepository.java @@ -0,0 +1,22 @@ +package com.institutosemprealerta.semprealerta.domain.ports.out; + +import com.institutosemprealerta.semprealerta.domain.model.Post; +import com.institutosemprealerta.semprealerta.infrastructure.entity.post.PostEntity; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +public interface PostRepository { + String save(Post post); + + void delete(Long id); + + void update(Long id, Post post); + + Page listAll(Pageable pageable); + + Post findBySlug(String slug); + Post findById(Long id); + +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/UserRepository.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/UserRepository.java new file mode 100644 index 0000000..42c6d51 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/UserRepository.java @@ -0,0 +1,15 @@ +package com.institutosemprealerta.semprealerta.domain.ports.out; + +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; + +import java.util.Optional; + +public interface UserRepository { + void save(User user); + Optional findById(int id); + void update(int id, User user); + void delete(int id); + Optional findByRegistration(String registration); + Optional findByEmail(String email); + +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/ExceptionPattern.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/ExceptionPattern.java new file mode 100644 index 0000000..a0c2a12 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/ExceptionPattern.java @@ -0,0 +1,20 @@ +package com.institutosemprealerta.semprealerta.domain.ports.out.exceptions; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter +@Setter +@Builder +public class ExceptionPattern { + private String title; + private int status; + private String details; + private LocalDateTime timestamp; + private String developerMessage; + private String fields; + private String fieldsMessage; +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/file/FileNotFoundException.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/file/FileNotFoundException.java new file mode 100644 index 0000000..be7a345 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/file/FileNotFoundException.java @@ -0,0 +1,7 @@ +package com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.file; + +public class FileNotFoundException extends RuntimeException { + public FileNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/file/InvalidFileException.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/file/InvalidFileException.java new file mode 100644 index 0000000..3f082a0 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/file/InvalidFileException.java @@ -0,0 +1,7 @@ +package com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.file; + +public class InvalidFileException extends RuntimeException { + public InvalidFileException(String message) { + super(message); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/post/PostNotFoundException.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/post/PostNotFoundException.java new file mode 100644 index 0000000..2ac6a26 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/post/PostNotFoundException.java @@ -0,0 +1,7 @@ +package com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.post; + +public class PostNotFoundException extends RuntimeException { + public PostNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/user/EmailAlreadyExistsException.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/user/EmailAlreadyExistsException.java new file mode 100644 index 0000000..f37d95e --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/user/EmailAlreadyExistsException.java @@ -0,0 +1,7 @@ +package com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.user; + +public class EmailAlreadyExistsException extends RuntimeException { + public EmailAlreadyExistsException(String message) { + super(message); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/user/UserNotFoundException.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/user/UserNotFoundException.java new file mode 100644 index 0000000..4241ff4 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/exceptions/user/UserNotFoundException.java @@ -0,0 +1,7 @@ +package com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.user; + +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/request/LoginDTO.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/request/LoginDTO.java new file mode 100644 index 0000000..ac0a8e1 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/request/LoginDTO.java @@ -0,0 +1,14 @@ +package com.institutosemprealerta.semprealerta.domain.ports.out.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; + +public record LoginDTO( + @NotBlank(message = "email is mandatory") + @Schema(description = "email do usuário", example = "muryllo@email.com") + String email, + @NotBlank(message = "password is mandatory") + @Schema(description = "Senha do usuário", example = "12345") + String password +) { +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/responses/FileResponse.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/responses/FileResponse.java new file mode 100644 index 0000000..dd8bbd0 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/responses/FileResponse.java @@ -0,0 +1,12 @@ +package com.institutosemprealerta.semprealerta.domain.ports.out.responses; + +import java.time.LocalDateTime; + +public record FileResponse( + long id, + String fileName, + String fileDownloadUri, + String fileType, + LocalDateTime uploadDate +) { +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/responses/LoginResponse.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/responses/LoginResponse.java new file mode 100644 index 0000000..f72a32a --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/responses/LoginResponse.java @@ -0,0 +1,11 @@ +package com.institutosemprealerta.semprealerta.domain.ports.out.responses; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; + +public record LoginResponse( + @Schema(description = "token de autorização para acessar os recursos", example = "eyn32nklafjçj354335g35") + @JsonProperty("access_token") + String accessToken +) { +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/responses/UserResponse.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/responses/UserResponse.java new file mode 100644 index 0000000..0b1b14a --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/ports/out/responses/UserResponse.java @@ -0,0 +1,34 @@ +package com.institutosemprealerta.semprealerta.domain.ports.out.responses; + +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.Address; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.Contact; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.UserRoles; + +import java.time.LocalDate; + +public record UserResponse( + String registration, + String name, + + String gender, + LocalDate birthDate, + + UserRoles roles, + + Contact contact, + Address address +) { + + public static UserResponse toResponse(User user) { + return new UserResponse( + user.getRegistration(), + user.getName(), + user.getGender(), + user.getBirthDate(), + user.getRoles(), + user.getContact(), + user.getAddress() + ); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/service/AuthenticationService.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/AuthenticationService.java new file mode 100644 index 0000000..1cf782a --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/AuthenticationService.java @@ -0,0 +1,10 @@ +package com.institutosemprealerta.semprealerta.domain.service; + +import com.institutosemprealerta.semprealerta.domain.ports.out.request.LoginDTO; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.LoginResponse; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; + +public interface AuthenticationService { + LoginResponse login(LoginDTO login); + void register(User user); +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/service/PostService.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/PostService.java new file mode 100644 index 0000000..f4ac8c0 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/PostService.java @@ -0,0 +1,18 @@ +package com.institutosemprealerta.semprealerta.domain.service; + +import com.institutosemprealerta.semprealerta.domain.model.Post; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + + +import java.util.List; + +public interface PostService { + String save(Post post); + void delete(Long id); + void update(Long id, Post post); + Page listAll(Pageable pageable); + + Post findBySlug(String slug); + Post findById(Long id); +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/service/StorageService.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/StorageService.java new file mode 100644 index 0000000..ed1106f --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/StorageService.java @@ -0,0 +1,20 @@ +package com.institutosemprealerta.semprealerta.domain.service; + +import com.institutosemprealerta.semprealerta.domain.model.File; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.FileResponse; +import org.springframework.core.io.Resource; +import org.springframework.web.multipart.MultipartFile; + +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Stream; + +public interface StorageService { + void init(); + String store(MultipartFile file, String fileType); + List loadAll(); + Path load(String filename); + Resource loadAsResource(String filename); + void delete(String filename); + void deleteAll(); +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/service/TokenService.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/TokenService.java new file mode 100644 index 0000000..154d3d9 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/TokenService.java @@ -0,0 +1,9 @@ +package com.institutosemprealerta.semprealerta.domain.service; + +import com.institutosemprealerta.semprealerta.domain.model.UserDTO; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; + +public interface TokenService { + String generateToken(User user); + String validateToken(String token); +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/service/UserService.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/UserService.java new file mode 100644 index 0000000..5e10df6 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/UserService.java @@ -0,0 +1,12 @@ +package com.institutosemprealerta.semprealerta.domain.service; + +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; + +public interface UserService { + void save(User user); + void update(int id, User user); + void delete(int id); + User findByRegistration(String registration); + User findByEmail(String email); + User findById(int id); +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/AuthenticationServiceImpl.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/AuthenticationServiceImpl.java new file mode 100644 index 0000000..5b3e4fe --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/AuthenticationServiceImpl.java @@ -0,0 +1,40 @@ +package com.institutosemprealerta.semprealerta.domain.service.impl; + +import com.institutosemprealerta.semprealerta.domain.ports.out.request.LoginDTO; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.LoginResponse; +import com.institutosemprealerta.semprealerta.domain.service.AuthenticationService; +import com.institutosemprealerta.semprealerta.domain.service.TokenService; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Service; + +@Service +public class AuthenticationServiceImpl implements AuthenticationService { + + private final AuthenticationManager authenticationManager; + + private final TokenService tokenService; + + public AuthenticationServiceImpl(AuthenticationManager authenticationManager, TokenService tokenService) { + this.authenticationManager = authenticationManager; + this.tokenService = tokenService; + } + + @Override + public LoginResponse login(LoginDTO login) { + UsernamePasswordAuthenticationToken userNamePassword = + new UsernamePasswordAuthenticationToken(login.email(), login.password()); + Authentication auth = this.authenticationManager.authenticate(userNamePassword); + + String token = tokenService.generateToken((User) auth.getPrincipal()); + + return new LoginResponse(token); + } + + @Override + public void register(User user) { + + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/AuthorizationService.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/AuthorizationService.java new file mode 100644 index 0000000..a1e485d --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/AuthorizationService.java @@ -0,0 +1,21 @@ +package com.institutosemprealerta.semprealerta.domain.service.impl; + +import com.institutosemprealerta.semprealerta.domain.service.UserService; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +public class AuthorizationService implements UserDetailsService { + private final UserService userService; + + public AuthorizationService(UserService userService) { + this.userService = userService; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return userService.findByEmail(username); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/PostServiceImpl.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/PostServiceImpl.java new file mode 100644 index 0000000..e79e3b6 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/PostServiceImpl.java @@ -0,0 +1,60 @@ +package com.institutosemprealerta.semprealerta.domain.service.impl; + +import com.institutosemprealerta.semprealerta.domain.service.PostService; +import com.institutosemprealerta.semprealerta.domain.model.Post; +import com.institutosemprealerta.semprealerta.domain.ports.in.SlugGenerator; +import com.institutosemprealerta.semprealerta.domain.ports.out.PostRepository; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.data.domain.Pageable; + +@Service +public class PostServiceImpl implements PostService { + private final PostRepository postRepository; + private final SlugGenerator slugGenerator; + + public PostServiceImpl(PostRepository postRepository, SlugGenerator slugGenerator) { + this.postRepository = postRepository; + this.slugGenerator = slugGenerator; + } + + @Override + public String save(Post post) { + String slug = this.generateSlug(post.getTitle()); + post.setSlug(slug); + + return postRepository.save(post); + } + + @Override + public void delete(Long id) { + postRepository.delete(id); + } + + @Override + public void update(Long id, Post post) { + String slug = this.generateSlug(post.getTitle()); + post.setSlug(slug); + postRepository.update(id, post); + } + + @Override + public Page listAll(Pageable pageable) { + return postRepository.listAll(pageable); + } + + + @Override + public Post findBySlug(String slug) { + return postRepository.findBySlug(slug); + } + + @Override + public Post findById(Long id) { + return postRepository.findById(id); + } + + private String generateSlug(String title) { + return slugGenerator.generate(title); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/StorageServiceImpl.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/StorageServiceImpl.java new file mode 100644 index 0000000..4085d82 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/StorageServiceImpl.java @@ -0,0 +1,112 @@ +package com.institutosemprealerta.semprealerta.domain.service.impl; + +import com.institutosemprealerta.semprealerta.domain.service.StorageService; +import com.institutosemprealerta.semprealerta.domain.model.File; +import com.institutosemprealerta.semprealerta.domain.ports.out.FileRepository; +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.file.FileNotFoundException; +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.file.InvalidFileException; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.FileResponse; +import com.institutosemprealerta.semprealerta.infrastructure.config.FileStorageProperties; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; +import org.springframework.stereotype.Service; +import org.springframework.util.FileSystemUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +@Service +public class StorageServiceImpl implements StorageService { + private final Path fileStorageLocation; + private final FileRepository fileRepository; + + public StorageServiceImpl(FileStorageProperties fileStorageProperties, FileRepository fileRepository) { + this.fileStorageLocation = Paths.get(fileStorageProperties.getUploadDir()) + .toAbsolutePath().normalize(); + this.fileRepository = fileRepository; + } + + @Override + public void init() { + try { + Files.createDirectories(fileStorageLocation); + } catch (IOException e) { + throw new RuntimeException("Could not create the directory where the uploaded files will be stored.", e); + } + } + + @Override + public String store(MultipartFile file, String fileType) { + if (file.getOriginalFilename() == null || file.getOriginalFilename().isEmpty()) { + throw new InvalidFileException("File name is empty"); + } + String fileName = StringUtils.cleanPath(file.getOriginalFilename()); + + try { + Path targetLocation = fileStorageLocation.resolve(fileName); + init(); + + file.transferTo(targetLocation.toFile()); + + String fileDownloadUri = "/api/v1/files/download/" + fileName; + + File fileData = File.builder() + .fileName(fileName) + .fileType(fileType) + .fileDownloadUri(fileDownloadUri) + .build(); + + this.fileRepository.save(fileData); + } catch (IOException e) { + throw new InvalidFileException("Could not store file " + fileName + ". Please try again!"); + } + return fileName; + } + + @Override + public List loadAll() { + + return this.fileRepository.listAll(); + } + + @Override + public Path load(String filename) { + Path file = fileStorageLocation.resolve(filename).normalize(); + if (!Files.exists(file)) { + throw new FileNotFoundException("File not found " + filename); + } + return file; + } + + @Override + public Resource loadAsResource(String filename) { + URI fileUri = load(filename).toUri(); + try { + return new UrlResource(fileUri); + } catch (MalformedURLException e) { + throw new InvalidFileException("Throwing exception when trying to read file " + filename + e.getMessage()); + } + } + + @Override + public void delete(String filename) { + Path file = load(filename); + try { + Files.deleteIfExists(file); + } catch (IOException e) { + throw new FileNotFoundException("File not found " + filename); + } + } + + @Override + public void deleteAll() { + FileSystemUtils.deleteRecursively(fileStorageLocation.toFile()); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/TokenServiceImpl.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/TokenServiceImpl.java new file mode 100644 index 0000000..3d262d6 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/TokenServiceImpl.java @@ -0,0 +1,55 @@ +package com.institutosemprealerta.semprealerta.domain.service.impl; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTCreationException; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.institutosemprealerta.semprealerta.domain.model.UserDTO; +import com.institutosemprealerta.semprealerta.domain.service.TokenService; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +@Service +public class TokenServiceImpl implements TokenService { + + private String secret = "secret"; + + private final Algorithm encryptionAlgorithm = Algorithm.HMAC256(secret); + + private final String issuer = "sempre-alerta"; + + @Override + public String generateToken(User user) { + try { + return JWT.create() + .withIssuer(issuer) + .withSubject(user.getContact().getEmail()) + .withExpiresAt(generationExpirationDate()) + .sign(encryptionAlgorithm); + } catch (JWTCreationException e) { + throw new JWTCreationException("Error while generating token", e); + } + } + + @Override + public String validateToken(String token) { + try { + return JWT.require(encryptionAlgorithm) + .withIssuer(issuer) + .build() + .verify(token) + .getSubject(); + } catch (JWTVerificationException e) { + return ""; + } + } + + private Instant generationExpirationDate() { + return LocalDateTime.now().plusHours(2).toInstant(ZoneOffset.of("-03:00")); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/UserServiceImpl.java b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..372e151 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/domain/service/impl/UserServiceImpl.java @@ -0,0 +1,54 @@ +package com.institutosemprealerta.semprealerta.domain.service.impl; + +import com.institutosemprealerta.semprealerta.domain.service.UserService; +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.user.UserNotFoundException; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; +import com.institutosemprealerta.semprealerta.domain.ports.out.UserRepository; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +public class UserServiceImpl implements UserService { + + private final UserRepository userRepository; + + public UserServiceImpl(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public void save(User user) { + String newPassword = new BCryptPasswordEncoder().encode(user.getPassword()); + + user.setPassword(newPassword); + this.userRepository.save(user); + } + + @Override + public void update(int id, User user) { + this.userRepository.update(id, user); + } + + @Override + public void delete(int id) { + this.userRepository.delete(id); + } + + @Override + public User findByRegistration(String registration) { + return this.userRepository.findByRegistration(registration) + .orElseThrow(() -> new UserNotFoundException("User not found")); + } + + @Override + public User findByEmail(String email) { + return this.userRepository.findByEmail(email) + .orElseThrow(() -> new UserNotFoundException("User not found")); + } + + @Override + public User findById(int id) { + return this.userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException("User not found")); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaFileRepositoryAdapter.java b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaFileRepositoryAdapter.java new file mode 100644 index 0000000..5b37978 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaFileRepositoryAdapter.java @@ -0,0 +1,38 @@ +package com.institutosemprealerta.semprealerta.infrastructure.adpters; + +import com.institutosemprealerta.semprealerta.domain.model.File; +import com.institutosemprealerta.semprealerta.domain.ports.out.FileRepository; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.FileResponse; +import com.institutosemprealerta.semprealerta.infrastructure.entity.file.FileEntity; +import com.institutosemprealerta.semprealerta.infrastructure.repositories.JpaFileRepository; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class JpaFileRepositoryAdapter implements FileRepository { + + private final JpaFileRepository fileRepository; + + public JpaFileRepositoryAdapter(JpaFileRepository fileRepository) { + this.fileRepository = fileRepository; + } + + @Override + public void save(File file) { + FileEntity fileEntity = FileEntity.fromDomainToModel(file); + fileRepository.save(fileEntity); + } + + @Override + public void delete(Long id) { + fileRepository.deleteById(id); + } + + @Override + public List listAll() { + return fileRepository.findAll().stream() + .map(FileEntity::toResponse) + .toList(); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaPostRepositoryAdapter.java b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaPostRepositoryAdapter.java new file mode 100644 index 0000000..fee725c --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaPostRepositoryAdapter.java @@ -0,0 +1,71 @@ +package com.institutosemprealerta.semprealerta.infrastructure.adpters; + +import com.institutosemprealerta.semprealerta.domain.model.Post; +import com.institutosemprealerta.semprealerta.domain.ports.out.PostRepository; +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.post.PostNotFoundException; +import com.institutosemprealerta.semprealerta.infrastructure.entity.post.PostEntity; +import com.institutosemprealerta.semprealerta.infrastructure.repositories.JpaPostRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; + +@Component +public class JpaPostRepositoryAdapter implements PostRepository { + private final JpaPostRepository jpaPostRepository; + + public JpaPostRepositoryAdapter(JpaPostRepository jpaPostRepository) { + this.jpaPostRepository = jpaPostRepository; + } + + @Override + public String save(Post post) { + boolean slugAlreadyExists = slugAlreadyExists(post.getSlug()); + + if (slugAlreadyExists) { + String newSlug = post.getSlug() + "-" + Math.random(); + post.setSlug(newSlug); + } + + PostEntity postToSave = PostEntity.fromModel(post); + PostEntity postSaved = jpaPostRepository.save(postToSave); + return postSaved.getSlug(); + } + + @Override + public void delete(Long id) { + this.findById(id); + jpaPostRepository.deleteById(id); + } + + @Override + public void update(Long id, Post post) { + this.findById(id); + PostEntity postToUpdate = PostEntity.fromModel(post); + postToUpdate.setId(id); + jpaPostRepository.save(postToUpdate); + } + + @Override + public Page listAll(Pageable pageable) { + return jpaPostRepository.findAll(pageable) + .map(postEntity -> PostEntity.toModel(postEntity)); + } + + @Override + public Post findBySlug(String slug) { + return jpaPostRepository.findBySlug(slug) + .map(postEntity -> PostEntity.toModel(postEntity)) + .orElseThrow(() -> new PostNotFoundException("Post not found")); + } + + @Override + public Post findById(Long id) { + return jpaPostRepository.findById(id) + .map(postEntity -> PostEntity.toModel(postEntity)) + .orElseThrow(() -> new PostNotFoundException("Post not found")); + } + + private boolean slugAlreadyExists(String slug) { + return jpaPostRepository.findBySlug(slug).isPresent(); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaUserRepositoryAdapter.java b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaUserRepositoryAdapter.java new file mode 100644 index 0000000..9875a61 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaUserRepositoryAdapter.java @@ -0,0 +1,60 @@ +package com.institutosemprealerta.semprealerta.infrastructure.adpters; + +import com.institutosemprealerta.semprealerta.domain.ports.out.UserRepository; +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.user.UserNotFoundException; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; +import com.institutosemprealerta.semprealerta.infrastructure.repositories.JpaUserRepository; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +public class JpaUserRepositoryAdapter implements UserRepository { + + private final JpaUserRepository userRepository; + + public JpaUserRepositoryAdapter(JpaUserRepository jpaUserRepository) { + this.userRepository = jpaUserRepository; + } + + @Override + public void save(User user) { + this.userRepository.save(user); + } + + @Override + public Optional findById(int id) { + return this.userRepository.findById(id); + } + + @Override + public void update(int id, User user) { + User userToUpdate = this.userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException("User not found")); + + user.setId(userToUpdate.getId()); + user.setRegistration(userToUpdate.getRegistration()); + + this.userRepository.save(user); + } + + @Override + public void delete(int id) { + User userToDelete = this.userRepository.findById(id) + .orElseThrow(() -> new RuntimeException("User not found")); + + this.userRepository.delete(userToDelete); + } + + @Override + public Optional findByRegistration(String registration) { + return this.userRepository.findByRegistration(registration); + } + + @Override + public Optional findByEmail(String email) { + return this.userRepository.findByEmail(email); + } + + +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/config/ApplicationConfig.java b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/config/ApplicationConfig.java new file mode 100644 index 0000000..b7adbce --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/config/ApplicationConfig.java @@ -0,0 +1,58 @@ +package com.institutosemprealerta.semprealerta.infrastructure.config; + +import com.institutosemprealerta.semprealerta.domain.ports.in.SlugGenerator; +import com.institutosemprealerta.semprealerta.domain.ports.in.impl.SlugifySlugGenerator; +import io.swagger.v3.oas.models.Components; +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.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import java.util.List; + +@Configuration +public class ApplicationConfig { + + @Bean + public SlugGenerator slugGenerator() { + return new SlugifySlugGenerator(); + } + + @Bean + public Pageable defaultPageable() { + return PageRequest.of(0, 10, Sort.by("createdAt").descending()); + } + + public SecurityScheme createAPIKeyScheme() { + return new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .bearerFormat("JWT") + .scheme("bearer"); + } + + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .addSecurityItem(new SecurityRequirement() + .addList("Bearer Authentication")) + .components(new Components().addSecuritySchemes( + "Bearer Authentication", + createAPIKeyScheme() + )) + .info(new Info().title("Instituto Sempre Alerta API") + .description("API do Instituto Sempre Alerta.") + .version("1.0").contact(new Contact().name("Matheus Victor") + .email("matheusvictorhenrique@gmail.com") + .url("https://www.instituto-sempre-alerta.com.br/")) + .license(new License().name("License of API") + .url("https://opensource.org/license/mit/"))); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/config/FileStorageProperties.java b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/config/FileStorageProperties.java new file mode 100644 index 0000000..0624cb3 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/config/FileStorageProperties.java @@ -0,0 +1,18 @@ +package com.institutosemprealerta.semprealerta.infrastructure.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "file") +public class FileStorageProperties { + private String uploadDir; + + public String getUploadDir() { + return uploadDir; + } + + public void setUploadDir(String uploadDir) { + this.uploadDir = uploadDir; + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/config/security/SecurityConfigurations.java b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/config/security/SecurityConfigurations.java new file mode 100644 index 0000000..07b1b0b --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/config/security/SecurityConfigurations.java @@ -0,0 +1,71 @@ +package com.institutosemprealerta.semprealerta.infrastructure.config.security; + +import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +@AllArgsConstructor +public class SecurityConfigurations { + + private final securtityFilter securtityFilter; + + private final String[] AUTH_SWAGGER_WHITELIST = { + "/swagger-ui/**", + "/swagger-ui", + "/swagger-resources/**", + "/webjars/**", + "/v3/api-docs/**", + "/swagger-ui.html" + }; + + private final String[] ACTUATOR_WHITELIST = { + "/actuator", + "/actuator/health", + "/actuator/health/**" + }; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { + return httpSecurity + .csrf(AbstractHttpConfigurer::disable) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(authorize -> authorize + .requestMatchers(AUTH_SWAGGER_WHITELIST).permitAll() + .requestMatchers(ACTUATOR_WHITELIST).permitAll() + .requestMatchers(HttpMethod.POST, "/auth/login").permitAll() + .requestMatchers("/api/v1/user/**").hasRole("ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/files/upload").hasRole("ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/posts/").hasRole("ADMIN") + .requestMatchers(HttpMethod.PUT, "/api/v1/posts/**").hasRole("ADMIN") + .requestMatchers(HttpMethod.DELETE, "/api/v1/posts/**").hasRole("ADMIN") + .anyRequest().authenticated() + ) + .addFilterBefore(securtityFilter, UsernamePasswordAuthenticationFilter.class) + .build(); + } + + @Bean + public AuthenticationManager authenticationManager( + AuthenticationConfiguration authenticationConfiguration + ) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/config/security/securtityFilter.java b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/config/security/securtityFilter.java new file mode 100644 index 0000000..9eb9f7f --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/config/security/securtityFilter.java @@ -0,0 +1,53 @@ +package com.institutosemprealerta.semprealerta.infrastructure.config.security; + +import com.institutosemprealerta.semprealerta.domain.service.TokenService; +import com.institutosemprealerta.semprealerta.domain.service.UserService; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +public class securtityFilter extends OncePerRequestFilter { + private final TokenService tokenService; + private final UserService userService; + + public securtityFilter(TokenService tokenService, UserService userService) { + this.tokenService = tokenService; + this.userService = userService; + } + + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + String token = this.recoverToken(request); + if (token != null) { + String email = tokenService.validateToken(token); + UserDetails user = userService.findByEmail(email); + + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + + } + filterChain.doFilter(request, response); + } + + private String recoverToken(HttpServletRequest request) { + String authHeader = request.getHeader("Authorization"); + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + return null; + } + return authHeader.substring(7, authHeader.length()); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/file/FileEntity.java b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/file/FileEntity.java new file mode 100644 index 0000000..0d55bad --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/file/FileEntity.java @@ -0,0 +1,54 @@ +package com.institutosemprealerta.semprealerta.infrastructure.entity.file; + +import com.institutosemprealerta.semprealerta.domain.model.File; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.FileResponse; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "files") +@Getter +@Setter +public class FileEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String fileName; + private String fileDownloadUri; + private String fileType; + + @CreationTimestamp + private LocalDateTime uploadDate; + + public FileEntity(String fileName, String fileDownloadUri, String fileType) { + this.fileName = fileName; + this.fileDownloadUri = fileDownloadUri; + this.fileType = fileType; + } + + public FileEntity() { + } + + public static FileEntity fromDomainToModel(File file) { + return new FileEntity( + file.getFileName(), + file.getFileDownloadUri(), + file.getFileType() + ); + } + + public static FileResponse toResponse(FileEntity fileEntity) { + return new FileResponse( + fileEntity.getId(), + fileEntity.getFileName(), + fileEntity.getFileDownloadUri(), + fileEntity.getFileType(), + fileEntity.getUploadDate() + ); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/post/PostEntity.java b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/post/PostEntity.java new file mode 100644 index 0000000..ac50c6f --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/post/PostEntity.java @@ -0,0 +1,67 @@ +package com.institutosemprealerta.semprealerta.infrastructure.entity.post; + +import com.institutosemprealerta.semprealerta.domain.model.Post; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.LocalDateTime; + +@Setter +@Getter +@Entity +@Table(name = "post") +public class PostEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(nullable = false) + private String title; + @Column(nullable = false) + private String slug; + @Column(columnDefinition = "TEXT") + private String content; + private String banner; + @CreationTimestamp + @Column(nullable = false, updatable = false) + private LocalDateTime createdAt; + + public PostEntity() { + } + + public PostEntity(Long id, String title, String slug, String content, String banner, LocalDateTime createdAt) { + this.id = id; + this.title = title; + this.slug = slug; + this.content = content; + this.banner = banner; + this.createdAt = createdAt; + } + + public PostEntity(String title, String slug, String content, String banner) { + this.title = title; + this.slug = slug; + this.content = content; + this.banner = banner; + } + + public static PostEntity fromModel(Post post) { + PostEntity postEntity = new PostEntity(); + postEntity.setId(post.getId()); + postEntity.setTitle(post.getTitle()); + postEntity.setSlug(post.getSlug()); + postEntity.setContent(post.getContent()); + postEntity.setBanner(post.getBanner()); + return postEntity; + } + + public static Post toModel(PostEntity postEntity) { + return new Post(postEntity.getId(), postEntity.getTitle(), postEntity.getSlug(), postEntity.getContent(), postEntity.getBanner(), postEntity.getCreatedAt()); + } + + public Post toModel() { + return new Post(this.getId(), this.getTitle(), this.getSlug(), this.getContent(), this.getBanner(), this.getCreatedAt()); + } + +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/Address.java b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/Address.java new file mode 100644 index 0000000..9f09f59 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/Address.java @@ -0,0 +1,19 @@ +package com.institutosemprealerta.semprealerta.infrastructure.entity.user; + +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Embeddable +public class Address { + private String street; + private String number; + private String city; + private String zipCode; +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/Contact.java b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/Contact.java new file mode 100644 index 0000000..5c48e24 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/Contact.java @@ -0,0 +1,17 @@ +package com.institutosemprealerta.semprealerta.infrastructure.entity.user; + +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Embeddable +public class Contact { + private String email; + private String phone; +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/User.java b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/User.java new file mode 100644 index 0000000..8876141 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/User.java @@ -0,0 +1,126 @@ +package com.institutosemprealerta.semprealerta.infrastructure.entity.user; + + +import com.institutosemprealerta.semprealerta.domain.model.UserDTO; +import com.institutosemprealerta.semprealerta.utils.DateManipulation; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; +import java.util.Random; + + +@Entity +@Table(name = "users") +@Getter +@Setter +public class User implements UserDetails { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(unique = true) + private String registration; + private String name; + private String password; + private String gender; + private LocalDate birthDate; + + private UserRoles roles; + @Embedded + private Contact contact; + + @Embedded + private Address address; + + @CreationTimestamp + @Column(name = "created_at", updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP") + private LocalDateTime createdAt; + + public User() { + } + + + public User(String name, String password, String gender, LocalDate birthDate, UserRoles roles, Contact contact, Address address) { + this.name = name; + this.password = password; + this.gender = gender; + this.birthDate = birthDate; + this.roles = roles; + this.contact = contact; + this.address = address; + } + + + @PrePersist + public void generateRegistration() { + String prefix = this.name.substring(0, 3).toLowerCase() + "-"; + this.registration = generateRegistration(prefix); + } + + private String generateRegistration(String prefix) { + StringBuilder registration = new StringBuilder(prefix); + Random random = new Random(); + + for (int i = 0; i < 8; i++) { + int digit = random.nextInt(10); + registration.append(digit); + } + + return registration.toString(); + } + + public User fromModelToDomain(UserDTO dto) { + //LocalDate birth = DateManipulation.stringToLocalDate(dto.birthDate()); + return new User( + dto.name(), + dto.password(), + dto.gender(), + dto.birthDate(), + dto.roles(), + new Contact(dto.email(), dto.phone()), + new Address(dto.street(), dto.number(), dto.city(), dto.zipCode()) + ); + } + + @Override + public Collection getAuthorities() { + if (this.roles == UserRoles.ADMIN) { + return List.of(new SimpleGrantedAuthority("ROLE_ADMIN"), new SimpleGrantedAuthority("ROLE_USER")); + } else { + return List.of(new SimpleGrantedAuthority("ROLE_USER")); + } + } + + @Override + public String getUsername() { + return this.contact.getEmail(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/UserRoles.java b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/UserRoles.java new file mode 100644 index 0000000..330bb8f --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/UserRoles.java @@ -0,0 +1,16 @@ +package com.institutosemprealerta.semprealerta.infrastructure.entity.user; + +public enum UserRoles { + ADMIN("ADMIN"), + USER("USER"); + + private final String role; + + UserRoles(String role) { + this.role = role; + } + + public String getRole() { + return role; + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaFileRepository.java b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaFileRepository.java new file mode 100644 index 0000000..822c2d0 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaFileRepository.java @@ -0,0 +1,9 @@ +package com.institutosemprealerta.semprealerta.infrastructure.repositories; + +import com.institutosemprealerta.semprealerta.infrastructure.entity.file.FileEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface JpaFileRepository extends JpaRepository { +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaPostRepository.java b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaPostRepository.java new file mode 100644 index 0000000..bac153a --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaPostRepository.java @@ -0,0 +1,12 @@ +package com.institutosemprealerta.semprealerta.infrastructure.repositories; + +import com.institutosemprealerta.semprealerta.infrastructure.entity.post.PostEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface JpaPostRepository extends JpaRepository{ + Optional findBySlug(String slug); +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaUserRepository.java b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaUserRepository.java new file mode 100644 index 0000000..fd4967e --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaUserRepository.java @@ -0,0 +1,16 @@ +package com.institutosemprealerta.semprealerta.infrastructure.repositories; + +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface JpaUserRepository extends JpaRepository { + @Query("SELECT u FROM User u WHERE u.contact.email = ?1") + Optional findByEmail(String email); + + Optional findByRegistration(String registration); +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/BadRequestResponse.java b/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/BadRequestResponse.java new file mode 100644 index 0000000..0ebeb1f --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/BadRequestResponse.java @@ -0,0 +1,18 @@ +package com.institutosemprealerta.semprealerta.swagger.annotations; + +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.ExceptionPattern; +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 java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@ApiResponse(responseCode = "400", description = "Erro de requisição inválida, no lado do cliente", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionPattern.class))) +public @interface BadRequestResponse { +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/ConflictResponse.java b/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/ConflictResponse.java new file mode 100644 index 0000000..0dbe12c --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/ConflictResponse.java @@ -0,0 +1,18 @@ +package com.institutosemprealerta.semprealerta.swagger.annotations; + +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.ExceptionPattern; +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 java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@ApiResponse(responseCode = "409", description = "Possui alguns conflitos na requisição", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionPattern.class))) +public @interface ConflictResponse { +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/CreatedResponse.java b/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/CreatedResponse.java new file mode 100644 index 0000000..8ce4829 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/CreatedResponse.java @@ -0,0 +1,14 @@ +package com.institutosemprealerta.semprealerta.swagger.annotations; + +import io.swagger.v3.oas.annotations.responses.ApiResponse; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@ApiResponse(responseCode = "201", description = "Criado com sucesso", useReturnTypeSchema = true) +public @interface CreatedResponse { +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/NoContentResponse.java b/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/NoContentResponse.java new file mode 100644 index 0000000..8016606 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/NoContentResponse.java @@ -0,0 +1,14 @@ +package com.institutosemprealerta.semprealerta.swagger.annotations; + +import io.swagger.v3.oas.annotations.responses.ApiResponse; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@ApiResponse(responseCode = "204", description = "Requisição bem sucedida e sem retorno", useReturnTypeSchema = true) +public @interface NoContentResponse { +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/NotFoundResponse.java b/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/NotFoundResponse.java new file mode 100644 index 0000000..9eb6e93 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/NotFoundResponse.java @@ -0,0 +1,18 @@ +package com.institutosemprealerta.semprealerta.swagger.annotations; + +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.ExceptionPattern; +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 java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@ApiResponse(responseCode = "404", description = "Objeto não encontrado", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionPattern.class))) +public @interface NotFoundResponse { +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/OkResponse.java b/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/OkResponse.java new file mode 100644 index 0000000..cf40ec7 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/swagger/annotations/OkResponse.java @@ -0,0 +1,15 @@ +package com.institutosemprealerta.semprealerta.swagger.annotations; + +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import org.springframework.http.HttpStatus; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@ApiResponse(responseCode = "200", description = "Requisição finalizada com sucesso", useReturnTypeSchema = true) +public @interface OkResponse { +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/utils/DateManipulation.java b/src/main/java/com/institutosemprealerta/semprealerta/utils/DateManipulation.java new file mode 100644 index 0000000..9a4e128 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/utils/DateManipulation.java @@ -0,0 +1,16 @@ +package com.institutosemprealerta.semprealerta.utils; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +public abstract class DateManipulation { + public static LocalDate stringToLocalDate(String date) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + return LocalDate.parse(date, formatter); + } + + public static String localDateToString(LocalDate date) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + return date.format(formatter); + } +} diff --git a/src/main/java/com/institutosemprealerta/semprealerta/utils/HashGeneration.java b/src/main/java/com/institutosemprealerta/semprealerta/utils/HashGeneration.java new file mode 100644 index 0000000..a884f31 --- /dev/null +++ b/src/main/java/com/institutosemprealerta/semprealerta/utils/HashGeneration.java @@ -0,0 +1,10 @@ +package com.institutosemprealerta.semprealerta.utils; + +import java.util.HashSet; +import java.util.Random; +import java.util.Set; + +public abstract class HashGeneration { + + +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..2bc41ea --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,29 @@ +spring: + datasource: + url: jdbc:postgresql://localhost:5432/postgres?serverTimezone=UTC + username: postgres + password: 123 + jpa: + hibernate: + ddl-auto: update + show-sql: true + servlet: + multipart: + max-file-size: 200MB + max-request-size: 215MB + + flyway: + enabled: true + locations: classpath:db/migration + baseline-on-migrate: true + validate-on-migrate: false + +file: + upload-dir: pdf +springdoc: + show-actuator: true +api: + security: + token: + secret: ${SECRET:secret} + diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 8b13789..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..a1d742e --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,7 @@ +file: + upload-dir: pdf + +api: + security: + token: + secret: ${SECRET:secret} \ No newline at end of file diff --git a/src/main/resources/db/migration/V1__create-user-table.sql b/src/main/resources/db/migration/V1__create-user-table.sql new file mode 100644 index 0000000..46d9da6 --- /dev/null +++ b/src/main/resources/db/migration/V1__create-user-table.sql @@ -0,0 +1,21 @@ +CREATE TABLE users +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + registration VARCHAR(255), + name VARCHAR(255), + password VARCHAR(255), + gender VARCHAR(255), + birth_date date, + roles SMALLINT, + created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(), + email VARCHAR(255), + phone VARCHAR(255), + street VARCHAR(255), + number VARCHAR(255), + city VARCHAR(255), + zip_code VARCHAR(255), + CONSTRAINT pk_users PRIMARY KEY (id) +); + +ALTER TABLE users + ADD CONSTRAINT uc_users_registration UNIQUE (registration); \ No newline at end of file diff --git a/src/main/resources/db/migration/V2__create-file-table.sql b/src/main/resources/db/migration/V2__create-file-table.sql new file mode 100644 index 0000000..fd6d482 --- /dev/null +++ b/src/main/resources/db/migration/V2__create-file-table.sql @@ -0,0 +1,9 @@ +CREATE TABLE files +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + file_name VARCHAR(255), + file_download_uri VARCHAR(255), + file_type VARCHAR(255), + upload_date TIMESTAMP WITHOUT TIME ZONE, + CONSTRAINT pk_files PRIMARY KEY (id) +); \ No newline at end of file diff --git a/src/main/resources/db/migration/V3__create-post-table.sql b/src/main/resources/db/migration/V3__create-post-table.sql new file mode 100644 index 0000000..47d9049 --- /dev/null +++ b/src/main/resources/db/migration/V3__create-post-table.sql @@ -0,0 +1,13 @@ +CREATE TABLE post +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + title VARCHAR(255) NOT NULL, + slug VARCHAR(255) NOT NULL, + content TEXT, + banner VARCHAR(255), + CONSTRAINT pk_post PRIMARY KEY (id) +); + + +ALTER TABLE post + ADD COLUMN created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL; diff --git a/src/test/java/com/institutosemprealerta/semprealerta/SempreAlertaApplicationTests.java b/src/test/java/com/institutosemprealerta/semprealerta/SempreAlertaApplicationTests.java index aab2b73..aa4386b 100644 --- a/src/test/java/com/institutosemprealerta/semprealerta/SempreAlertaApplicationTests.java +++ b/src/test/java/com/institutosemprealerta/semprealerta/SempreAlertaApplicationTests.java @@ -1,9 +1,11 @@ package com.institutosemprealerta.semprealerta; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.boot.test.context.SpringBootTest; -@SpringBootTest +@ExtendWith(MockitoExtension.class) class SempreAlertaApplicationTests { @Test diff --git a/src/test/java/com/institutosemprealerta/semprealerta/application/controllers/AuthenticationControllerTest.java b/src/test/java/com/institutosemprealerta/semprealerta/application/controllers/AuthenticationControllerTest.java new file mode 100644 index 0000000..a0653a9 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/application/controllers/AuthenticationControllerTest.java @@ -0,0 +1,66 @@ +package com.institutosemprealerta.semprealerta.application.controllers; + +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.user.UserNotFoundException; +import com.institutosemprealerta.semprealerta.domain.ports.out.request.LoginDTO; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.LoginResponse; +import com.institutosemprealerta.semprealerta.domain.service.AuthenticationService; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.LoginFactory; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.mocks.UserMocks; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("AuthenticationController") +class AuthenticationControllerTest { + + @InjectMocks + private AuthenticationController authenticationController; + + @Mock + private AuthenticationService authenticationService; + + private final LoginResponse token = LoginFactory.INSTANCE.createNewLoginResponse("this-is-the-way"); + private final LoginDTO userCredentials = UserMocks.returnValidLoginDTO(); + + @BeforeEach + void setUp() { + when(authenticationService.login(any(LoginDTO.class))) + .thenReturn(token); + } + + @AfterEach + void tearDown() { + reset(authenticationService); + } + + @Test + @DisplayName("Should login successfully") + void should_Login_Successfully() { + ResponseEntity loginResponse = authenticationController.login(userCredentials); + + assertNotNull(loginResponse); + assertEquals(HttpStatus.OK, loginResponse.getStatusCode()); + assertEquals(token, loginResponse.getBody()); + } + @Test + @DisplayName("Should login with failure") + void should_Login_With_Failure() { + when(authenticationService.login(any(LoginDTO.class))).thenThrow(UserNotFoundException.class); + + assertThrows(UserNotFoundException.class, () -> authenticationController.login(userCredentials)); + } + +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/application/handler/GlobalExceptionHandlerTest.java b/src/test/java/com/institutosemprealerta/semprealerta/application/handler/GlobalExceptionHandlerTest.java new file mode 100644 index 0000000..27cd5a8 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/application/handler/GlobalExceptionHandlerTest.java @@ -0,0 +1,90 @@ +package com.institutosemprealerta.semprealerta.application.handler; + +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.ExceptionPattern; +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.file.FileNotFoundException; +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.file.InvalidFileException; +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.post.PostNotFoundException; +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.user.EmailAlreadyExistsException; +import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.user.UserNotFoundException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("GlobalExceptionHandler") +class GlobalExceptionHandlerTest { + + @InjectMocks + private GlobalExceptionHandler globalExceptionHandler; + + + @Test + @DisplayName("Should handler with user not found exception") + void should_HandlerUserNotFoundException() { + UserNotFoundException exception = new UserNotFoundException("User not found"); + ResponseEntity response = globalExceptionHandler.handlerUserNotFoundException(exception); + + assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + assertNotNull(response); + assertEquals(exception.getMessage(), Objects.requireNonNull(response.getBody()).getDetails()); + assertEquals(exception.getClass().getName(), Objects.requireNonNull(response.getBody()).getDeveloperMessage()); + } + + @Test + @DisplayName("Should handler with email already exists exception") + void should_HandlerEmailAlreadyExistsException() { + EmailAlreadyExistsException exception = new EmailAlreadyExistsException("user email already exists"); + ResponseEntity response = globalExceptionHandler.handlerEmailAlreadyExistsException(exception); + + assertNotNull(response); + assertEquals(HttpStatus.CONFLICT, response.getStatusCode()); + assertEquals(exception.getMessage(), Objects.requireNonNull(response.getBody()).getDetails()); + assertEquals(exception.getClass().getName(), Objects.requireNonNull(response.getBody()).getDeveloperMessage()); + } + + @Test + @DisplayName("Should handler with post not found exception") + void should_HandlerPostNotFoundException() { + PostNotFoundException exception = new PostNotFoundException("Post not found"); + ResponseEntity response = globalExceptionHandler.handlerPostNotFoundException(exception); + + assertNotNull(response); + assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + assertEquals(exception.getMessage(), Objects.requireNonNull(response.getBody()).getDetails()); + assertEquals(exception.getClass().getName(), Objects.requireNonNull(response.getBody()).getDeveloperMessage()); + } + + @Test + @DisplayName("Should handler with file not found exception") + void should_HandlerFileNotFoundException() { + FileNotFoundException exception = new FileNotFoundException("File not found"); + ResponseEntity response = globalExceptionHandler.handlerFileNotFoundException(exception); + + assertNotNull(response); + assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + assertEquals(exception.getMessage(), Objects.requireNonNull(response.getBody()).getDetails()); + assertEquals(exception.getClass().getName(), Objects.requireNonNull(response.getBody()).getDeveloperMessage()); + } + + @Test + @DisplayName("Should handler with invalid file exception") + void should_HandlerInvalidFileException() { + InvalidFileException exception = new InvalidFileException("File type invalid"); + ResponseEntity response = globalExceptionHandler.handlerInvalidFileException(exception); + + assertNotNull(response); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertEquals(exception.getMessage(), Objects.requireNonNull(response.getBody()).getDetails()); + assertEquals(exception.getClass().getName(), Objects.requireNonNull(response.getBody()).getDeveloperMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/application/service/impl/PostServiceImplTest.java b/src/test/java/com/institutosemprealerta/semprealerta/application/service/impl/PostServiceImplTest.java new file mode 100644 index 0000000..bda6f87 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/application/service/impl/PostServiceImplTest.java @@ -0,0 +1,129 @@ +package com.institutosemprealerta.semprealerta.application.service.impl; + +import com.institutosemprealerta.semprealerta.domain.model.Post; +import com.institutosemprealerta.semprealerta.domain.ports.in.SlugGenerator; +import com.institutosemprealerta.semprealerta.domain.ports.out.PostRepository; +import com.institutosemprealerta.semprealerta.domain.service.impl.PostServiceImpl; +import com.institutosemprealerta.semprealerta.infrastructure.entity.post.mocks.PostMocks; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("Post Service Test") +class PostServiceImplTest { + + @InjectMocks + private PostServiceImpl postService; + + @Mock + private PostRepository postRepository; + + @Mock + private SlugGenerator slugGenerator; + + private final Post postMock = PostMocks.returnValidPostModel(); + + @BeforeEach + void setUp() { + List posts = List.of(postMock); + + Pageable pageable = PageRequest.of(0, 10); + Page postPage = new PageImpl<>(posts, pageable, posts.size()); + + when(postService.listAll(any(Pageable.class))).thenReturn(postPage); + when(postRepository.save(any(Post.class))).thenReturn(postMock.getSlug()); + when(postRepository.findBySlug(anyString())).thenReturn(postMock); + when(postRepository.findById(anyLong())).thenReturn(postMock); + doNothing().when(postRepository).delete(anyLong()); + doNothing().when(postRepository).update(anyLong(), any(Post.class)); + + when(slugGenerator.generate(anyString())).thenReturn(postMock.getSlug()); + } + + @AfterEach + void tearDown() { + reset(postRepository, slugGenerator); + } + + @Test + @DisplayName("Should save a post successfully") + void should_SaveAPost_Successfully() { + Post postToCreate = PostMocks.returnValidPostModelToBeCreated(); + String slug = postService.save(postToCreate); + + assertNotNull(slug); + assertEquals(postToCreate.getSlug(), slug); + verify(slugGenerator, times(1)).generate(postToCreate.getTitle()); + } + + @Test + @DisplayName("Should delete a post successfully") + void should_DeleteAPost_Successfully() { + assertDoesNotThrow(() -> postService.delete(postMock.getId())); + verify(postRepository, times(1)).delete(postMock.getId()); + } + + @Test + @DisplayName("Should update a post successfully") + void should_UpdateAPost_Successfully() { + Post postToUpdate = PostMocks.returnValidPostModelToBeUpdated(); + + assertDoesNotThrow(() -> postService.update(postMock.getId(), postToUpdate)); + verify(postRepository, times(1)).update(postMock.getId(), postToUpdate); + verify(slugGenerator, times(1)).generate(postToUpdate.getTitle()); + } + + @Test + @DisplayName("Should list all pageable successfully") + void should_ListAllPageable_Successfully() { + PageRequest pageable = PageRequest.of(0, 10); + + Page posts = postService.listAll(pageable); + + assertNotNull(posts); + assertEquals(1, posts.getTotalElements()); + assertEquals(postMock.getId(), posts.getContent().get(0).getId()); + } + + @Test + @DisplayName("Should find by slug successfully") + void should_FindBySlug_Successfully() { + Post post = postService.findBySlug(postMock.getSlug()); + + assertNotNull(post); + assertEquals(postMock.getId(), post.getId()); + assertEquals(postMock.getTitle(), post.getTitle()); + assertEquals(postMock.getSlug(), post.getSlug()); + assertEquals(postMock.getContent(), post.getContent()); + assertEquals(postMock.getBanner(), post.getBanner()); + } + + @Test + @DisplayName("Should find by id successfully") + void should_FindById_Successfully() { + Post post = postService.findById(postMock.getId()); + + assertNotNull(post); + assertEquals(postMock.getId(), post.getId()); + assertEquals(postMock.getTitle(), post.getTitle()); + assertEquals(postMock.getSlug(), post.getSlug()); + assertEquals(postMock.getContent(), post.getContent()); + assertEquals(postMock.getBanner(), post.getBanner()); + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/application/service/impl/StorageServiceImplTest.java b/src/test/java/com/institutosemprealerta/semprealerta/application/service/impl/StorageServiceImplTest.java new file mode 100644 index 0000000..f38d343 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/application/service/impl/StorageServiceImplTest.java @@ -0,0 +1,149 @@ +package com.institutosemprealerta.semprealerta.application.service.impl; + +import com.institutosemprealerta.semprealerta.domain.model.File; +import com.institutosemprealerta.semprealerta.domain.ports.out.FileRepository; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.FileResponse; +import com.institutosemprealerta.semprealerta.domain.service.impl.StorageServiceImpl; +import com.institutosemprealerta.semprealerta.infrastructure.config.FileStorageProperties; +import com.institutosemprealerta.semprealerta.infrastructure.entity.file.mocks.FileMocks; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.Resource; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.nio.file.*; +import java.nio.file.spi.FileSystemProvider; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class StorageServiceImplTest { + + private StorageServiceImpl storageService; + + @Mock + private FileRepository fileRepository; + + @Mock + private FileStorageProperties fileStorageProperties; + + @Mock + private MultipartFile mockFile; + + @Mock + private FileSystem mockFileSystem; + + @Mock + private FileSystemProvider mockFileSystemProvider; + + private final FileResponse fileResponse = FileMocks.returnValidFileResponse(); + + @BeforeEach + void setUp() throws IOException { + + when(fileStorageProperties.getUploadDir()).thenReturn("pdf"); + storageService = new StorageServiceImpl(fileStorageProperties, fileRepository); + + when(mockFile.getOriginalFilename()).thenReturn("file.txt"); + when(mockFile.isEmpty()).thenReturn(false); + when(mockFile.getSize()).thenReturn(100L); + when(mockFile.getContentType()).thenReturn("text/plain"); + + + Path pathToDirectory = Paths.get("pdf"); + Path pathToFile = Paths.get("pdf/file.txt"); + if (!Files.exists(pathToDirectory)) { + Files.createDirectories(pathToDirectory); + } + if (!Files.exists(pathToFile)) { + Files.createFile(pathToFile); + } + + when(mockFileSystem.provider()).thenReturn(mockFileSystemProvider); + + + when(fileRepository.listAll()).thenReturn(List.of(fileResponse)); + doNothing().when(fileRepository).save(any(File.class)); + + } + + @AfterEach + void tearDown() { + reset( + fileRepository, + fileStorageProperties, + mockFile, + mockFileSystem, + mockFileSystemProvider + ); + } + + @AfterAll + static void afterAll() { + Path pathToFile = Paths.get("pdf/file.txt"); + try { + Files.deleteIfExists(pathToFile); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + void init() { + } + + @Test + @DisplayName("Should Store File With Valid Name") + void should_Store_File_With_ValidName() { + String fileName = storageService.store(mockFile, "text/plain"); + + assertNotNull(fileName); + assertEquals("file.txt", fileName); + verify(fileRepository, times(1)).save(any(File.class)); + } + + @Test + @DisplayName("should load file path") + void should_Load_File_Path() { + assertDoesNotThrow(() -> storageService.load("file.txt")); + } + + @Test + @DisplayName("Should Load All Files Successfully") + void should_LoadAll_Files_Successfully() throws IOException { + List allFiles = storageService.loadAll(); + + assertNotNull(allFiles); + assertEquals(1, allFiles.size()); + assertEquals(fileResponse.fileName(), allFiles.get(0).fileName()); + verify(fileRepository, times(1)).listAll(); + } + + @Test + @DisplayName("Should Load File Successfully") + void should_loadAsResource_Successfully() { + String fileName = "file.txt"; + + Resource response = storageService.loadAsResource(fileName); + + assertNotNull(response); + } + + @Test + @DisplayName("Should Load File As Resource Successfully") + void should_Delete_A_Resource() { + String fileName = fileResponse.fileName(); + assertDoesNotThrow(() -> storageService.delete(fileName)); + + } + + @Test + @DisplayName("Should Delete File Successfully") + void should_DeleteAll_Successfully() { + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/application/service/impl/UserServiceImplTest.java b/src/test/java/com/institutosemprealerta/semprealerta/application/service/impl/UserServiceImplTest.java new file mode 100644 index 0000000..c80629c --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/application/service/impl/UserServiceImplTest.java @@ -0,0 +1,100 @@ +package com.institutosemprealerta.semprealerta.application.service.impl; + +import com.institutosemprealerta.semprealerta.domain.ports.out.UserRepository; +import com.institutosemprealerta.semprealerta.domain.service.impl.UserServiceImpl; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.mocks.UserMocks; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class UserServiceImplTest { + + @InjectMocks + private UserServiceImpl userService; + + @Mock + private UserRepository userRepository; + + @BeforeEach + void setUp() { + User userValid = UserMocks.returnValidUserEntity(); + + lenient().when(userRepository.findById(ArgumentMatchers.anyInt())).thenReturn(Optional.of(userValid)); + lenient().when(userRepository.findByRegistration(ArgumentMatchers.anyString())).thenReturn(Optional.of(userValid)); + lenient().when(userRepository.findByEmail(ArgumentMatchers.anyString())).thenReturn(Optional.of(userValid)); + lenient().doNothing().when(userRepository).save(ArgumentMatchers.any(User.class)); + lenient().doNothing().when(userRepository).update(ArgumentMatchers.anyInt(), ArgumentMatchers.any(User.class)); + lenient().doNothing().when(userRepository).delete(ArgumentMatchers.anyInt()); + } + + @AfterEach + void tearDown() { + reset(userRepository); + } + + @Test + @DisplayName("Should Save User Successfully") + void should_Save_User_Successfully() { + User userToCreate = UserMocks.returnValidUserToCreate(); + assertDoesNotThrow(() -> userService.save(userToCreate)); + } + + @Test + @DisplayName("Should Update User Successfully") + void should_update_User_Successfully() { + User userToUpdate = UserMocks.returnValidUserToUpdate(); + assertDoesNotThrow(() -> userService.update(1, userToUpdate)); + } + + @Test + @DisplayName("Should Delete User Successfully") + void should_Delete_User_Successfully() { + assertDoesNotThrow(() -> userService.delete(1)); + } + + @Test + @DisplayName("Should Find User By Registration Successfully") + void should_findUserByRegistration_Successfully() { + User expectedUser = UserMocks.returnValidUserEntity(); + User userFound = userService.findByRegistration(expectedUser.getRegistration()); + + assertEquals(expectedUser.getRegistration(), userFound.getRegistration()); + assertEquals(expectedUser.getName(), userFound.getName()); + assertNotNull(userFound.getId()); + } + + @Test + @DisplayName("Should Find User By Email Successfully") + void should_findUserByEmail_Successfully() { + User expectedUser = UserMocks.returnValidUserEntity(); + User userFound = userService.findByEmail(expectedUser.getContact().getEmail()); + + assertEquals(expectedUser.getContact().getEmail(), userFound.getContact().getEmail()); + assertEquals(expectedUser.getName(), userFound.getName()); + assertNotNull(userFound.getId()); + } + + @Test + @DisplayName("Should Find User By Id Successfully") + void should_findUserById_Successfully() { + User expectedUser = UserMocks.returnValidUserEntity(); + User userFound = userService.findById(1); + + assertEquals(expectedUser.getRegistration(), userFound.getRegistration()); + assertEquals(expectedUser.getName(), userFound.getName()); + assertNotNull(userFound.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/domain/ports/in/impl/SlugifySlugGeneratorTest.java b/src/test/java/com/institutosemprealerta/semprealerta/domain/ports/in/impl/SlugifySlugGeneratorTest.java new file mode 100644 index 0000000..e48293b --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/domain/ports/in/impl/SlugifySlugGeneratorTest.java @@ -0,0 +1,48 @@ +package com.institutosemprealerta.semprealerta.domain.ports.in.impl; + +import com.github.slugify.Slugify; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.util.ReflectionUtils; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@DisplayName("Slugify slug generator test") +class SlugifySlugGeneratorTest { + + @InjectMocks + private SlugifySlugGenerator slugifySlugGenerator; + + @Mock + Slugify slugify; + + @BeforeEach + void setUp() { + + } + + @AfterEach + void turnDown() { + reset(slugify); + } + + @Test + @DisplayName("Should generate slug successfully") + void should_GenerateSlug_Successfully() { + String input = "Test Input"; + String expectedSlug = "test-input"; + when(slugify.slugify(input)).thenReturn(expectedSlug); + + String result = slugifySlugGenerator.generate(input); + assertEquals(expectedSlug, result); + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/domain/service/impl/AuthenticationServiceImplTest.java b/src/test/java/com/institutosemprealerta/semprealerta/domain/service/impl/AuthenticationServiceImplTest.java new file mode 100644 index 0000000..4cf8545 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/domain/service/impl/AuthenticationServiceImplTest.java @@ -0,0 +1,69 @@ +package com.institutosemprealerta.semprealerta.domain.service.impl; + +import com.institutosemprealerta.semprealerta.domain.ports.out.request.LoginDTO; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.LoginResponse; +import com.institutosemprealerta.semprealerta.domain.service.TokenService; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.Contact; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.mocks.UserMocks; +import org.springframework.security.core.Authentication; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; + + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@DisplayName("Authentication service") +class AuthenticationServiceImplTest { + + @InjectMocks + private AuthenticationServiceImpl authenticationService; + + @Mock + private AuthenticationManager authenticationManager; + + @Mock + private TokenService tokenService; + + private final Contact contact = UserMocks.returnValidContact(); + private LoginDTO loginDTO; + + private final String token = "mockToken"; + + @BeforeEach + void setUp() { + loginDTO = new LoginDTO(contact.getEmail(), "123"); + User user = new User(); + user.setContact(contact); + + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null); + when(authenticationManager.authenticate(any(Authentication.class))).thenReturn(authentication); + + + when(tokenService.generateToken(any(User.class))).thenReturn(token); + + } + + @AfterEach + void tearDown() { + reset(tokenService, authenticationManager); + } + + @Test + void login() { + LoginResponse response = authenticationService.login(loginDTO); + assertEquals(token, response.accessToken()); + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/domain/service/impl/AuthorizationServiceTest.java b/src/test/java/com/institutosemprealerta/semprealerta/domain/service/impl/AuthorizationServiceTest.java new file mode 100644 index 0000000..cba3a4c --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/domain/service/impl/AuthorizationServiceTest.java @@ -0,0 +1,51 @@ +package com.institutosemprealerta.semprealerta.domain.service.impl; + +import com.institutosemprealerta.semprealerta.domain.service.UserService; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.mocks.UserMocks; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.userdetails.UserDetails; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@DisplayName("Authorization Service") +class AuthorizationServiceTest { + + @InjectMocks + private AuthorizationService authorizationService; + + @Mock + private UserService userService; + + private final User validUserEntity = UserMocks.returnValidUserEntity(); + + @BeforeEach + void setUp() { + when(userService.findByEmail(anyString())) + .thenReturn(validUserEntity); + } + + @AfterEach + void tearDown() { + reset(userService); + } + + @Test + @DisplayName("Should load user by username successfully") + void should_LoadUserByUsername_Successfully() { + UserDetails response = authorizationService.loadUserByUsername(validUserEntity.getContact().getEmail()); + + assertNotNull(response); + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/domain/service/impl/TokenServiceImplTest.java b/src/test/java/com/institutosemprealerta/semprealerta/domain/service/impl/TokenServiceImplTest.java new file mode 100644 index 0000000..c30aff4 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/domain/service/impl/TokenServiceImplTest.java @@ -0,0 +1,81 @@ +package com.institutosemprealerta.semprealerta.domain.service.impl; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTCreationException; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.Contact; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.mocks.UserMocks; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@DisplayName("Token service") +class TokenServiceImplTest { + + @InjectMocks + private TokenServiceImpl tokenService; + + @Mock + User user; + + @Mock + Contact contact; + + private final String secret = "secret"; + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(tokenService, "secret", secret); + + when(user.getContact()).thenReturn(contact); + when(user.getContact().getEmail()) + .thenReturn(UserMocks.returnValidContact().getEmail()); + } + + @AfterEach + void turnDown() { + reset(user, contact); + } + + @Test + @DisplayName("Should generate token successfully") + void should_GenerateToken_Successfully() { + String token = tokenService.generateToken(user); + + assertNotNull(token); + } + + @Test + @DisplayName("Should validate token successfully") + void should_ValidateToken_Successfully() { + String token = tokenService.generateToken(user); + String subject = tokenService.validateToken(token); + + assertEquals(user.getContact().getEmail(), subject); + } + + @Test + @DisplayName("Should validate token with failure") + void should_ValidateToken_With_Failure() { + String invalidToken = "where are you now?"; + String invalidSubject = tokenService.validateToken(invalidToken); + + assertTrue(invalidSubject.isBlank()); + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaFileRepositoryAdapterTest.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaFileRepositoryAdapterTest.java new file mode 100644 index 0000000..5bf3ffe --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaFileRepositoryAdapterTest.java @@ -0,0 +1,71 @@ +package com.institutosemprealerta.semprealerta.infrastructure.adpters; + +import com.institutosemprealerta.semprealerta.domain.model.File; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.FileResponse; +import com.institutosemprealerta.semprealerta.infrastructure.entity.file.FileEntity; +import com.institutosemprealerta.semprealerta.infrastructure.entity.file.FileEntityFactory; +import com.institutosemprealerta.semprealerta.infrastructure.entity.file.mocks.FileMocks; +import com.institutosemprealerta.semprealerta.infrastructure.repositories.JpaFileRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class JpaFileRepositoryAdapterTest { + + @InjectMocks + private JpaFileRepositoryAdapter jpaFileRepositoryAdapter; + + @Mock + private JpaFileRepository jpaFileRepository; + + private final FileEntity validFile = FileEntityFactory.INSTANCE.create( + "file.txt", + "http://localhost:8080/api/v1/files/download/1", + "text/plain" + ); + + @BeforeEach + void setUp() { + + when(jpaFileRepository.save(ArgumentMatchers.any(FileEntity.class))).thenReturn(validFile); + when(jpaFileRepository.findAll()).thenReturn(List.of(validFile)); + doNothing().when(jpaFileRepository).deleteById(ArgumentMatchers.anyLong()); + } + + @AfterEach + void tearDown() { + reset(jpaFileRepository); + } + + @Test + void save() { + File fileToCreate = FileMocks.createFile(); + assertDoesNotThrow(() -> jpaFileRepositoryAdapter.save(fileToCreate)); + } + + @Test + void delete() { + assertDoesNotThrow(() -> jpaFileRepositoryAdapter.delete(1L)); + } + + @Test + void listAll() { + validFile.setId(1L); + List fileResponses = jpaFileRepositoryAdapter.listAll(); + + assertNotNull(fileResponses); + assertFalse(fileResponses.isEmpty()); + assertEquals(validFile.getId(), fileResponses.get(0).id()); + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaPostRepositoryAdapterTest.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaPostRepositoryAdapterTest.java new file mode 100644 index 0000000..88f0c72 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaPostRepositoryAdapterTest.java @@ -0,0 +1,118 @@ +package com.institutosemprealerta.semprealerta.infrastructure.adpters; + +import com.institutosemprealerta.semprealerta.domain.model.Post; +import com.institutosemprealerta.semprealerta.infrastructure.entity.post.PostEntity; +import com.institutosemprealerta.semprealerta.infrastructure.entity.post.mocks.PostMocks; +import com.institutosemprealerta.semprealerta.infrastructure.repositories.JpaPostRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("JpaPostRepositoryAdapterTest") +class JpaPostRepositoryAdapterTest { + + @InjectMocks + private JpaPostRepositoryAdapter jpaPostRepositoryAdapter; + + @Mock + private JpaPostRepository jpaPostRepository; + + private final PostEntity postMocks = PostMocks.returnValidPostEntity(); + + @BeforeEach + void setUp() { + List postEntityList = List.of(postMocks); + + Pageable pageable = PageRequest.of(0, 10); + Page postEntityPage = new PageImpl<>(postEntityList, pageable, postEntityList.size()); + + when(jpaPostRepository.findAll(any(Pageable.class))).thenReturn(postEntityPage); + when(jpaPostRepository.save(any(PostEntity.class))).thenReturn(postMocks); + when(jpaPostRepository.findBySlug(any(String.class))).thenReturn(java.util.Optional.of(postMocks)); + when(jpaPostRepository.findById(any(Long.class))).thenReturn(java.util.Optional.of(postMocks)); + doNothing().when(jpaPostRepository).deleteById(any(Long.class)); + } + + @AfterEach + void tearDown() { + reset(jpaPostRepository); + } + + @Test + @DisplayName("Should Save A Post Successfully") + void should_Save_A_Post_Successfully() { + PostEntity postEntity = PostMocks.returnValidPostToBeCreated(); + + String slug = jpaPostRepositoryAdapter.save(PostEntity.toModel(postEntity)); + + assertNotNull(slug); + assertEquals(postEntity.getSlug(), slug); + } + + @Test + @DisplayName("Should Delete A Post Successfully") + void should_Delete_A_Post_Successfully() { + assertDoesNotThrow(() -> jpaPostRepositoryAdapter.delete(postMocks.getId())); + } + + @Test + @DisplayName("Should Update A Post Successfully") + void should_Update_A_Post_Successfully() { + PostEntity postEntity = PostMocks.returnValidPostToBeUpdated(); + + assertDoesNotThrow(() -> jpaPostRepositoryAdapter.update(postMocks.getId(), PostEntity.toModel(postEntity))); + } + + @Test + @DisplayName("Should ListAll PageablePost Successfully") + void should_ListAll_PageablePost_Successfully() { + PageRequest pageable = PageRequest.of(0, 10); + Page postPage = jpaPostRepositoryAdapter.listAll(pageable); + + assertNotNull(postPage); + assertEquals(1, postPage.getTotalElements()); + assertEquals(postMocks.getId(), postPage.getContent().get(0).getId()); + } + + @Test + @DisplayName("Should Find A PostBySlug Successfully") + void should_Find_A_PostBySlug_Successfully() { + Post post = jpaPostRepositoryAdapter.findBySlug(postMocks.getSlug()); + + assertNotNull(post); + assertEquals(postMocks.getId(), post.getId()); + assertEquals(postMocks.getTitle(), post.getTitle()); + assertEquals(postMocks.getSlug(), post.getSlug()); + assertEquals(postMocks.getContent(), post.getContent()); + assertEquals(postMocks.getBanner(), post.getBanner()); + } + + @Test + @DisplayName("Should Find A Post By Id Successfully") + void should_Find_A_Post_ById_Successfully() { + Post post = jpaPostRepositoryAdapter.findById(postMocks.getId()); + + assertNotNull(post); + assertEquals(postMocks.getId(), post.getId()); + assertEquals(postMocks.getTitle(), post.getTitle()); + assertEquals(postMocks.getSlug(), post.getSlug()); + assertEquals(postMocks.getContent(), post.getContent()); + assertEquals(postMocks.getBanner(), post.getBanner()); + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaUserRepositoryAdapterTest.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaUserRepositoryAdapterTest.java new file mode 100644 index 0000000..e43c962 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/adpters/JpaUserRepositoryAdapterTest.java @@ -0,0 +1,109 @@ +package com.institutosemprealerta.semprealerta.infrastructure.adpters; + +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.mocks.UserMocks; +import com.institutosemprealerta.semprealerta.infrastructure.repositories.JpaUserRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.reset; + +@ExtendWith(MockitoExtension.class) +@DisplayName("JpaUserRepositoryAdapter") +class JpaUserRepositoryAdapterTest { + + @InjectMocks + private JpaUserRepositoryAdapter jpaUserRepositoryAdapter; + + @Mock + private JpaUserRepository userRepository; + + + @BeforeEach + void setUp() { + User userValid = UserMocks.returnValidUserEntity(); + + lenient().when(userRepository.findById(ArgumentMatchers.anyInt())).thenReturn(Optional.of(userValid)); + lenient().when(userRepository.findByRegistration(ArgumentMatchers.anyString())).thenReturn(Optional.of(userValid)); + lenient().when(userRepository.findByEmail(ArgumentMatchers.anyString())).thenReturn(Optional.of(userValid)); + lenient().when(userRepository.save(ArgumentMatchers.any(User.class))).thenReturn(userValid); + lenient().doNothing().when(userRepository).delete(ArgumentMatchers.any(User.class)); + } + + @AfterEach + void tearDown() { + reset(userRepository); + } + + @Test + @DisplayName("Should Save User Successfully") + void should_Save_UserSuccessfully() { + User userToCreate = UserMocks.returnValidUserToCreate(); + assertDoesNotThrow(() -> jpaUserRepositoryAdapter.save(userToCreate)); + } + + @Test + @DisplayName("Should Find User By Id Successfully") + void should_findUserById_Successfully() { + User expectedUser = UserMocks.returnValidUserEntity(); + + User user = jpaUserRepositoryAdapter.findById(1).orElse(new User()); + + assertNotNull(user); + assertEquals(expectedUser.getId(), user.getId()); + assertEquals(expectedUser.getRegistration(), user.getRegistration()); + assertEquals(expectedUser.getName(), user.getName()); + + } + + @Test + @DisplayName("Should Update A User Successfully") + void should_Update_A_User_Successfully() { + User userToUpdate = UserMocks.returnValidUserToUpdate(); + assertDoesNotThrow(() -> jpaUserRepositoryAdapter.update(1, userToUpdate)); + } + + @Test + @DisplayName("Should Delete A User Successfully") + void should_Delete_A_User_Successfully() { + assertDoesNotThrow(() -> jpaUserRepositoryAdapter.delete(1)); + } + + @Test + @DisplayName("Should Find User By Registration Successfully") + void should_findUserByRegistration_Successfully() { + User expectedUser = UserMocks.returnValidUserEntity(); + String expectedRegistration = expectedUser.getRegistration(); + User userFound = jpaUserRepositoryAdapter.findByRegistration(expectedRegistration).orElse(new User()); + + assertNotNull(userFound); + assertEquals(expectedRegistration, userFound.getRegistration()); + assertEquals(expectedUser.getName(), userFound.getName()); + assertNotNull(userFound.getId()); + } + + @Test + @DisplayName("Should Find User By Email Successfully") + void should_findUserByEmail_Successfully() { + + User expectedUser = UserMocks.returnValidUserEntity(); + String expectedEmail = expectedUser.getContact().getEmail(); + User userFound = jpaUserRepositoryAdapter.findByEmail(expectedEmail).orElse(new User()); + + assertNotNull(userFound); + assertEquals(expectedEmail, userFound.getContact().getEmail()); + assertEquals(expectedUser.getName(), userFound.getName()); + assertNotNull(userFound.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/controllers/FilesStorageControllerTest.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/controllers/FilesStorageControllerTest.java new file mode 100644 index 0000000..73a3e28 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/controllers/FilesStorageControllerTest.java @@ -0,0 +1,111 @@ +package com.institutosemprealerta.semprealerta.infrastructure.controllers; + +import com.institutosemprealerta.semprealerta.application.controllers.FilesStorageController; +import com.institutosemprealerta.semprealerta.domain.service.StorageService; +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class FilesStorageControllerTest { + + @InjectMocks + FilesStorageController filesStorageController; + + @Mock + StorageService storageService; + + @Mock + Resource mockResource; + + @Mock + HttpServletRequest mockRequest; + + + private final MultipartFile mockFile = new MockMultipartFile("file.txt", "content".getBytes()); + + @BeforeEach + void setUp() throws IOException { + String contentFile = "Hello, World!"; + InputStream is = new ByteArrayInputStream(contentFile.getBytes(StandardCharsets.UTF_8)); + when(mockResource.getInputStream()).thenReturn(is); + + when(mockResource.getFilename()).thenReturn("file.txt"); + when(storageService.store(any(), any())).thenReturn("file.txt"); + + Path tempFile = Files.createTempFile("temp", ".txt"); + File file = tempFile.toFile(); + when(mockResource.getFile()).thenReturn(file); + + when(storageService.loadAsResource(any())).thenReturn(mockResource); + } + + @AfterEach + void tearDown() { + reset(storageService, mockResource); + } + + @Test + @DisplayName("Should upload file successfully") + void should_UploadFile_Successfully() { + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + ServletRequestAttributes sra = new ServletRequestAttributes(request, response); + RequestContextHolder.setRequestAttributes(sra); + + String fileType = "pdf"; + String expected = "File uploaded successfully, file name: file.txt on path: http://localhost/api/v1/files/download/file.txt"; + ResponseEntity responseEntity = filesStorageController.uploadFile(mockFile, fileType); + + assertTrue(responseEntity.getStatusCode().is2xxSuccessful()); + assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode()); + assertEquals(expected, responseEntity.getBody()); + } + + @Test + @DisplayName("Should download file successfully") + void should_DownloadFile_Successfully() { + MockHttpServletRequest request = new MockHttpServletRequest(); + + ResponseEntity response = filesStorageController.downloadFile("file.txt", request); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("attachment; filename=\"file.txt\"", response.getHeaders().get(HttpHeaders.CONTENT_DISPOSITION).get(0)); + } + + @Test + @DisplayName("Should list files successfully") + void should_ListFiles_Successfully() { + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/controllers/PostControllerTest.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/controllers/PostControllerTest.java new file mode 100644 index 0000000..6eaaec8 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/controllers/PostControllerTest.java @@ -0,0 +1,110 @@ +package com.institutosemprealerta.semprealerta.infrastructure.controllers; + +import com.institutosemprealerta.semprealerta.application.controllers.PostController; +import com.institutosemprealerta.semprealerta.domain.service.PostService; +import com.institutosemprealerta.semprealerta.domain.model.Post; +import com.institutosemprealerta.semprealerta.infrastructure.entity.post.mocks.PostMocks; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.List; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("Given a PostController") +class PostControllerTest { + + @InjectMocks + private PostController postController; + + @Mock + private PostService postService; + + private final Post postMock = PostMocks.returnValidPostModel(); + + @BeforeEach + void setUp() { + List posts = List.of(postMock); + + Pageable pageable = PageRequest.of(0, 10); + Page postPage = new PageImpl<>(posts, pageable, posts.size()); + + when(postService.listAll(pageable)).thenReturn(postPage); + when(postService.save(postMock)).thenReturn(postMock.getSlug()); + when(postService.findBySlug(postMock.getSlug())).thenReturn(postMock); + doNothing().when(postService).update(postMock.getId(), postMock); + doNothing().when(postService).delete(postMock.getId()); + } + + @AfterEach + void tearDown() { + reset(postService); + } + + @Test + @DisplayName("When getAllPosts, then return a pageable list of posts") + void should_GetAllPosts_Successfully() { + PageRequest pageable = PageRequest.of(0, 10); + ResponseEntity> postPage = postController.getAllPosts(pageable); + + assertNotNull(postPage); + assertEquals(HttpStatus.OK, postPage.getStatusCode()); + assertEquals(1, Objects.requireNonNull(postPage.getBody()).getTotalElements()); + assertEquals(postMock, postPage.getBody().getContent().get(0)); + } + + @Test + @DisplayName("When createPost, then return a URI") + void should_CreatePost_Successfully() { + ResponseEntity response = postController.createPost(postMock); + + assertNotNull(response); + assertEquals(HttpStatus.CREATED, response.getStatusCode()); + assertEquals("/api/v1/posts/" + postMock.getSlug(), response.getHeaders().getLocation().getPath()); + } + + @Test + @DisplayName("When getPostBySlug, then return a post") + void should_GetPostBySlug_Successfully() { + ResponseEntity response = postController.getPostBySlug(postMock.getSlug()); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(postMock, response.getBody()); + } + + @Test + @DisplayName("When updatePost, then return no content") + void should_UpdatePost_Successfully() { + ResponseEntity response = postController.updatePost(postMock.getId(), postMock); + + assertNotNull(response); + assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode()); + verify(postService, times(1)).update(postMock.getId(), postMock); + } + + @Test + @DisplayName("When deletePost, then return no content") + void should_DeletePost_Successfully() { + ResponseEntity response = postController.deletePost(postMock.getId()); + + assertNotNull(response); + assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode()); + verify(postService, times(1)).delete(postMock.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/controllers/UserControllerTest.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/controllers/UserControllerTest.java new file mode 100644 index 0000000..50faac5 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/controllers/UserControllerTest.java @@ -0,0 +1,114 @@ +package com.institutosemprealerta.semprealerta.infrastructure.controllers; + +import com.institutosemprealerta.semprealerta.application.controllers.UserController; +import com.institutosemprealerta.semprealerta.domain.service.UserService; +import com.institutosemprealerta.semprealerta.domain.model.UserDTO; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.UserResponse; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.mocks.UserMocks; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.Objects; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("User Controller") +class UserControllerTest { + + @InjectMocks + private UserController userController; + + @Mock + private UserService userService; + + @BeforeEach + void setUp() { + User validUser = UserMocks.returnValidUserEntity(); + + lenient().doNothing().when(userService).save(ArgumentMatchers.any(User.class)); + lenient().when(userService.findById(ArgumentMatchers.anyInt())).thenReturn(validUser); + lenient().when(userService.findByRegistration(ArgumentMatchers.anyString())).thenReturn(validUser); + lenient().doNothing().when(userService).update(ArgumentMatchers.anyInt(), ArgumentMatchers.any(User.class)); + lenient().doNothing().when(userService).delete(ArgumentMatchers.anyInt()); + + } + + @AfterEach + void tearDown() { + reset(userService); + } + + @Test + @DisplayName("Should create user successfully") + void should_CreateUser_Successfully() { + UserDTO userDTO = UserMocks.returnValidUserDTO(); + + ResponseEntity response = userController.createUser(userDTO); + + assertNotNull(response); + assertTrue(response.getStatusCode().is2xxSuccessful()); + assertEquals(HttpStatus.CREATED.value(), response.getStatusCode().value()); + assertFalse(response.hasBody()); + } + + @Test + @DisplayName("Should find user by id successfully") + void should_FindUserById_Successfully() { + ResponseEntity response = userController.findById(1); + User expectedUser = UserMocks.returnValidUserEntity(); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + assertTrue(response.hasBody()); + assertEquals(HttpStatus.OK.value(), response.getStatusCode().value()); + assertEquals(expectedUser.getName(), Objects.requireNonNull(response.getBody()).name()); + } + + @Test + @DisplayName("Should find user by registration successfully") + void should_findByUserRegistration_Successfully() { + User expectedUser = UserMocks.returnValidUserEntity(); + String expectedRegistration = expectedUser.getRegistration(); + + ResponseEntity response = userController.findByRegistration(expectedRegistration); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + assertEquals(HttpStatus.OK.value(), response.getStatusCode().value()); + assertTrue(response.hasBody()); + assertEquals(expectedUser.getName(), Objects.requireNonNull(response.getBody()).name()); + } + + @Test + @DisplayName("Should update user successfully") + void should_updateUser_Successfully() { + UserDTO userDTO = UserMocks.returnValidUserDTO(); + ResponseEntity response = userController.updateUser(1, userDTO); + + assertNotNull(response); + assertTrue(response.getStatusCode().is2xxSuccessful()); + assertEquals(HttpStatus.NO_CONTENT.value(), response.getStatusCode().value()); + assertFalse(response.hasBody()); + } + + @Test + @DisplayName("Should delete user successfully") + void should_deleteUser_Successfully() { + ResponseEntity response = userController.deleteUser(1); + + assertNotNull(response); + assertTrue(response.getStatusCode().is2xxSuccessful()); + assertEquals(HttpStatus.NO_CONTENT.value(), response.getStatusCode().value()); + assertFalse(response.hasBody()); + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/file/FileEntityFactory.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/file/FileEntityFactory.java new file mode 100644 index 0000000..a3d2303 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/file/FileEntityFactory.java @@ -0,0 +1,26 @@ +package com.institutosemprealerta.semprealerta.infrastructure.entity.file; + +import com.institutosemprealerta.semprealerta.domain.model.File; + +public class FileEntityFactory { + public static final FileEntityFactory INSTANCE = new FileEntityFactory(); + + private FileEntityFactory() { + } + + public FileEntity create(File file) { + return new FileEntity(file.getFileName(), file.getFileDownloadUri(), file.getFileType()); + } + + public FileEntity create( + String fileName, + String fileDownloadUri, + String fileType + ) { + return new FileEntity(fileName, fileDownloadUri, fileType); + } + + public FileEntity create() { + return new FileEntity(); + } +} diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/file/mocks/FileMocks.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/file/mocks/FileMocks.java new file mode 100644 index 0000000..9ff7602 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/file/mocks/FileMocks.java @@ -0,0 +1,25 @@ +package com.institutosemprealerta.semprealerta.infrastructure.entity.file.mocks; + +import com.institutosemprealerta.semprealerta.domain.model.File; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.FileResponse; +import com.institutosemprealerta.semprealerta.infrastructure.entity.file.FileEntity; + +import java.time.LocalDateTime; + +public class FileMocks { + public static FileEntity createFileEntity() { + return new FileEntity("fileName", "fileDownloadUri", "fileType"); + } + + public static File createFile() { + return File.builder() + .fileName("fileName") + .fileDownloadUri("fileDownloadUri") + .fileType("fileType") + .build(); + } + + public static FileResponse returnValidFileResponse() { + return new FileResponse(1L, "file.txt", "/api/v1/download/1", "fileType", LocalDateTime.now()); + } +} diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/post/PostEntityFactory.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/post/PostEntityFactory.java new file mode 100644 index 0000000..5af5cec --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/post/PostEntityFactory.java @@ -0,0 +1,48 @@ +package com.institutosemprealerta.semprealerta.infrastructure.entity.post; + +import java.time.LocalDateTime; + +public class PostEntityFactory { + public static PostEntityFactory INSTANCE = new PostEntityFactory(); + + private PostEntityFactory() { + } + + public PostEntity createPostEntity() { + return new PostEntity(); + } + + public PostEntity createPostEntity( + String title, + String slug, + String content, + String banner + ) { + return new PostEntity( + title, + slug, + content, + banner + ); + } + + public PostEntity createPostEntity( + Long id, + String title, + String slug, + String content, + String banner, + LocalDateTime createdAt + ) { + return new PostEntity( + id, + title, + slug, + content, + banner, + createdAt + ); + } + + +} diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/post/mocks/PostMocks.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/post/mocks/PostMocks.java new file mode 100644 index 0000000..83cd1bb --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/post/mocks/PostMocks.java @@ -0,0 +1,83 @@ +package com.institutosemprealerta.semprealerta.infrastructure.entity.post.mocks; + +import com.github.javafaker.Faker; +import com.institutosemprealerta.semprealerta.domain.model.Post; +import com.institutosemprealerta.semprealerta.infrastructure.entity.post.PostEntity; +import com.institutosemprealerta.semprealerta.infrastructure.entity.post.PostEntityFactory; + +import java.time.LocalDateTime; + +public class PostMocks { + private static final Faker faker = new Faker(); + + private static final Long id = faker.number().randomNumber(); + private static final String title = faker.lorem().sentence(); + private static final String slug = faker.internet().slug(); + private static final String content = faker.lorem().paragraph(); + private static final String banner = faker.internet().image(); + private static final LocalDateTime createdAt = LocalDateTime.now(); + + public static PostEntity returnValidPostEntity() { + return PostEntityFactory.INSTANCE.createPostEntity( + id, + title, + slug, + content, + banner, + createdAt + ); + } + + public static PostEntity returnValidPostToBeCreated() { + return PostEntityFactory.INSTANCE.createPostEntity( + title, + slug, + content, + banner + ); + } + + public static PostEntity returnValidPostToBeUpdated() { + return PostEntityFactory.INSTANCE.createPostEntity( + id, + title, + slug, + faker.dune().saying(), + banner, + createdAt + ); + } + + public static Post returnValidPostModel() { + return new Post( + id, + title, + slug, + content, + banner, + createdAt + ); + } + + public static Post returnValidPostModelToBeCreated() { + return new Post( + null, + title, + slug, + content, + banner, + null + ); + } + + public static Post returnValidPostModelToBeUpdated() { + return new Post( + id, + title, + slug, + faker.dune().saying(), + banner, + createdAt + ); + } +} diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/ContactFactory.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/ContactFactory.java new file mode 100644 index 0000000..d2b227e --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/ContactFactory.java @@ -0,0 +1,16 @@ +package com.institutosemprealerta.semprealerta.infrastructure.entity.user; + +public class ContactFactory { + public static final ContactFactory INSTANCE = new ContactFactory(); + + private ContactFactory() { + } + + public Contact newContact() { + return new Contact(); + } + + public Contact newContact(String email, String phone) { + return new Contact(email, phone); + } +} diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/LoginFactory.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/LoginFactory.java new file mode 100644 index 0000000..1d04c44 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/LoginFactory.java @@ -0,0 +1,18 @@ +package com.institutosemprealerta.semprealerta.infrastructure.entity.user; + +import com.institutosemprealerta.semprealerta.domain.ports.out.request.LoginDTO; +import com.institutosemprealerta.semprealerta.domain.ports.out.responses.LoginResponse; + +public class LoginFactory { + public static final LoginFactory INSTANCE = new LoginFactory(); + + private LoginFactory() {} + + public LoginResponse createNewLoginResponse(String token) { + return new LoginResponse(token); + } + + public LoginDTO createNewLoginDTO(String email, String password) { + return new LoginDTO(email, password); + } +} diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/UserEntityFactory.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/UserEntityFactory.java new file mode 100644 index 0000000..c83382c --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/UserEntityFactory.java @@ -0,0 +1,31 @@ +package com.institutosemprealerta.semprealerta.infrastructure.entity.user; + +import java.time.LocalDate; + +public class UserEntityFactory { + public static final UserEntityFactory INSTANCE = new UserEntityFactory(); + + private UserEntityFactory() { + } + + public User newUser() { + return new User(); + } + + public User newUser( + String name, + String password, + String gender, + LocalDate birthday, + UserRoles userRoles, + String email, + String phone, + Address address + ) { + Contact contact = ContactFactory.INSTANCE.newContact(email, phone); + + return new User(name, password, gender, birthday, userRoles, contact, address); + } + + +} diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/mocks/UserMocks.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/mocks/UserMocks.java new file mode 100644 index 0000000..ddd84e5 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/entity/user/mocks/UserMocks.java @@ -0,0 +1,74 @@ +package com.institutosemprealerta.semprealerta.infrastructure.entity.user.mocks; + +import com.institutosemprealerta.semprealerta.domain.model.UserDTO; +import com.institutosemprealerta.semprealerta.domain.ports.out.request.LoginDTO; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +public class UserMocks { + public static User returnValidUserEntity() { + User user = UserEntityFactory.INSTANCE.newUser(); + + LocalDate birthDate = LocalDate.of(1990, 1, 1); + + user.setId(1L); + user.setRegistration("123456"); + user.setName("John Doe"); + user.setBirthDate(birthDate); + user.setGender("M"); + user.setContact(returnValidContact()); + user.setPassword("123456"); + user.setAddress(returnValidAddress()); + user.setRoles(UserRoles.USER); + user.setCreatedAt(LocalDateTime.now()); + + return user; + } + + public static User returnValidUserToCreate() { + User user = returnValidUserEntity(); + user.setId(null); + return user; + } + + public static User returnValidUserToUpdate() { + User user = returnValidUserEntity(); + user.setRegistration("654321"); + return user; + } + + public static Contact returnValidContact() { + return ContactFactory.INSTANCE.newContact("user@email.com", "123456789"); + } + + public static Address returnValidAddress() { + return new Address("Street", "123", "NY", "123546"); + } + + + public static UserDTO returnValidUserDTO() { + User user = returnValidUserEntity(); + return new UserDTO( + user.getName(), + user.getContact().getEmail(), + user.getPassword(), + user.getContact().getPhone(), + user.getGender(), + user.getBirthDate(), + user.getRoles(), + user.getAddress().getStreet(), + user.getAddress().getNumber(), + user.getAddress().getCity(), + user.getAddress().getZipCode() + ); + } + + public static LoginDTO returnValidLoginDTO() { + return LoginFactory.INSTANCE.createNewLoginDTO( + "user@email.com", + "1234" + ); + } +} diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaFileRepositoryTest.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaFileRepositoryTest.java new file mode 100644 index 0000000..c81d418 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaFileRepositoryTest.java @@ -0,0 +1,49 @@ +package com.institutosemprealerta.semprealerta.infrastructure.repositories; + +import com.institutosemprealerta.semprealerta.infrastructure.entity.file.FileEntity; +import com.institutosemprealerta.semprealerta.infrastructure.entity.file.mocks.FileMocks; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import static org.junit.jupiter.api.Assertions.*; + +@DataJpaTest +@DisplayName("JpaFileRepositoryTest") +class JpaFileRepositoryTest { + + @Autowired + private JpaFileRepository jpaFileRepository; + + + @AfterEach + void tearDown() { + this.jpaFileRepository.deleteAll(); + } + + @Test + @DisplayName("Should save file data to database") + void should_save_file_data_to_database() { + + FileEntity fileToCreate = FileMocks.createFileEntity(); + FileEntity fileCreated = this.jpaFileRepository.save(fileToCreate); + + assertNotNull(fileCreated); + assertEquals(fileToCreate.getFileName(), fileCreated.getFileName()); + assertEquals(fileToCreate.getFileDownloadUri(), fileCreated.getFileDownloadUri()); + assertEquals(fileToCreate.getFileType(), fileCreated.getFileType()); + } + + @Test + @DisplayName("Should delete file by file id") + void should_delete_file_by_file_id() { + FileEntity fileToCreate = FileMocks.createFileEntity(); + FileEntity fileCreated = this.jpaFileRepository.save(fileToCreate); + + assertDoesNotThrow(() -> this.jpaFileRepository.deleteById(fileCreated.getId())); + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaPostRepositoryTest.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaPostRepositoryTest.java new file mode 100644 index 0000000..fe8c085 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaPostRepositoryTest.java @@ -0,0 +1,49 @@ +package com.institutosemprealerta.semprealerta.infrastructure.repositories; + +import com.institutosemprealerta.semprealerta.infrastructure.entity.post.PostEntity; +import com.institutosemprealerta.semprealerta.infrastructure.entity.post.PostEntityFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import static org.junit.jupiter.api.Assertions.*; + +@DataJpaTest +@DisplayName("JpaPostRepositoryTest") +class JpaPostRepositoryTest { + + @Autowired + private JpaPostRepository jpaPostRepository; + + private final PostEntity postToCreate = PostEntityFactory.INSTANCE.createPostEntity( + "title", + "slug", + "content", + "banner" + ); + + @BeforeEach + void setUp() { + this.jpaPostRepository.save(postToCreate); + } + + @AfterEach + void tearDown() { + this.jpaPostRepository.deleteAll(); + } + + @Test + @DisplayName("Should find post by slug") + void should_find_post_by_slug() { + PostEntity postFound = this.jpaPostRepository.findBySlug("slug").orElse(null); + + assertNotNull(postFound); + assertEquals(postToCreate.getTitle(), postFound.getTitle()); + assertEquals(postToCreate.getSlug(), postFound.getSlug()); + assertEquals(postToCreate.getContent(), postFound.getContent()); + assertEquals(postToCreate.getBanner(), postFound.getBanner()); + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaUserRepositoryTest.java b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaUserRepositoryTest.java new file mode 100644 index 0000000..4b1cfcd --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/infrastructure/repositories/JpaUserRepositoryTest.java @@ -0,0 +1,69 @@ +package com.institutosemprealerta.semprealerta.infrastructure.repositories; + +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User; +import com.institutosemprealerta.semprealerta.infrastructure.entity.user.UserEntityFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.time.LocalDate; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +@DataJpaTest +@DisplayName("Tests for JpaUserRepository") +class JpaUserRepositoryTest { + + @Autowired + private JpaUserRepository jpaUserRepository; + + @BeforeEach + void setUp() { + User userToCreate = UserEntityFactory.INSTANCE.newUser( + "Teste", + "123456", + "M", + LocalDate.now(), + null, + "user@email.com", + "123456", + null + ); + this.jpaUserRepository.save(userToCreate); + } + + @AfterEach + void tearDown() { + this.jpaUserRepository.deleteAll(); + } + + @Test + void findByEmail() { + Optional userFound = this.jpaUserRepository.findByEmail("user@email.com"); + LocalDate now = LocalDate.now(); + + assertTrue(userFound.isPresent()); + assertNotNull(userFound.get().getId()); + assertEquals("Teste", userFound.get().getName()); + assertEquals("123456", userFound.get().getPassword()); + assertEquals("M", userFound.get().getGender()); + assertEquals(now, userFound.get().getBirthDate()); + + } + + @Test + void findByRegistration() { + Optional userFound = this.jpaUserRepository.findByEmail("user@email.com"); + + userFound.ifPresent(user -> { + Optional userByRegistration = this.jpaUserRepository.findByRegistration(user.getRegistration()); + assertTrue(userByRegistration.isPresent()); + assertEquals(user, userByRegistration.get()); + assertNotNull(userByRegistration.get().getId()); + }); + } +} \ No newline at end of file diff --git a/src/test/java/com/institutosemprealerta/semprealerta/wrapper/PageableResponse.java b/src/test/java/com/institutosemprealerta/semprealerta/wrapper/PageableResponse.java new file mode 100644 index 0000000..2820e19 --- /dev/null +++ b/src/test/java/com/institutosemprealerta/semprealerta/wrapper/PageableResponse.java @@ -0,0 +1,42 @@ +package com.institutosemprealerta.semprealerta.wrapper; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; + +import java.util.List; + +import static com.fasterxml.jackson.annotation.JsonCreator.Mode.PROPERTIES; + +@Getter +@Setter +public class PageableResponse extends PageImpl { + private boolean first; + private boolean last; + private int totalPages; + private int numberOfElements; + + @JsonCreator(mode = PROPERTIES ) + public PageableResponse(@JsonProperty("content") List content, + @JsonProperty("number") int number, + @JsonProperty("size") int size, + @JsonProperty("totalElements") int totalElements, + @JsonProperty("last") boolean last, + @JsonProperty("first") boolean first, + @JsonProperty("totalPages") int totalPages, + @JsonProperty("numberOfElements") int numberOfElements, + @JsonProperty("pageable") JsonNode pageable, + @JsonProperty("sort") JsonNode sort) { + super(content, PageRequest.of(number, size), totalElements); + + this.last = last; + this.first = first; + this.totalPages = totalPages; + this.numberOfElements = numberOfElements; + + } +} \ No newline at end of file