diff --git a/.github/workflows/main-server.yml b/.github/workflows/main-server.yml index bd93685..95f7fc0 100644 --- a/.github/workflows/main-server.yml +++ b/.github/workflows/main-server.yml @@ -57,6 +57,8 @@ jobs: labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max + build-args: | + BUILD=v${{ steps.version.outputs.tag }} - name: Create and push Git tag run: | diff --git a/Taskfile.yml b/Taskfile.yml index 2269d64..1df5c3c 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -8,12 +8,11 @@ vars: tasks: docker-build-web: desc: Build the tasknote-web prod-ready docker image, tagging it as candidate - cmd: docker build --no-cache --build-arg VITE_BUILD="v999-$(date '+%Y-%m-%d-%H%M%S')" --build-arg SOURCE_PR="v999-123456789-$(date '+%Y-%m-%d-%H%M%S')" -t ghcr.io/ricardo-campos-org/react-typescript-todolist/tasknote-web:candidate ./client + cmd: docker build --no-cache --build-arg VITE_BUILD="v999-$(date '+%Y-%m-%d-%H%M%S')" --build-arg SOURCE_PR="v999-123456789-$(date '+%Y-%m-%d-%H%M%S')" -t docker.io/rmcampos/tasknote:app-latest ./client docker-build-api: desc: Build the tasknote-api prod-ready docker image, tagging it as candidate - cmd: docker build --no-cache --build-arg BUILD="v999-$(date '+%Y-%m-%d-%H%M%S')" --build-arg SOURCE_PR="v999-123456789-$(date '+%Y-%m-%d-%H%M%S')" -t ghcr.io/ricardo-campos-org/react-typescript-todolist/tasknote-api:candidate ./server - + cmd: docker build --no-cache --build-arg BUILD="v999-$(date '+%Y-%m-%d-%H%M%S')" --build-arg SOURCE_PR="v999-123456789-$(date '+%Y-%m-%d-%H%M%S')" -t docker.io/rmcampos/tasknote:api-latest ./server prod-up-web: desc: Speed up the tasknote-web prod-like image, building it if required cmd: docker compose -f docker-compose.prod.yml up -d tasknote-web diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 699562e..23c6e89 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -39,7 +39,7 @@ services: ports: - "8585:8585" - "5005:5005" - image: maven:3.9.9-eclipse-temurin-17 + image: maven:3.9.9-eclipse-temurin-21 entrypoint: './mvnw -ntp spring-boot:run -Dspring-boot.run.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005" -Dmaven.plugin.validation=VERBOSE' working_dir: /app volumes: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 9a08184..96d4544 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -6,13 +6,15 @@ services: depends_on: tasknote-api: condition: service_healthy - image: ghcr.io/ricardo-campos-org/react-typescript-todolist/tasknote-web:candidate + image: docker.io/rmcampos/tasknote:app-latest build: context: client dockerfile: Dockerfile ports: ["5000:5000"] environment: - VITE_BACKEND_SERVER: http://localhost:8585 + VITE_BACKEND_SERVER: http://tasknote-api:8585 + networks: + - tasknote-network tasknote-api: container_name: tasknote-api @@ -31,10 +33,12 @@ services: SECURITY_KEY: this-is-a-very-long-security-key-for-dev MAILGUN_APIKEY: invalid-api-key-only-placeholder ports: ["8585:8585"] - image: ghcr.io/ricardo-campos-org/react-typescript-todolist/tasknote-api:candidate + image: docker.io/rmcampos/tasknote:api-latest build: context: server dockerfile: Dockerfile + networks: + - tasknote-network tasknote-db: container_name: tasknote-db @@ -50,3 +54,9 @@ services: timeout: 15s retries: 3 start_period: 10s + networks: + - tasknote-network + +networks: + tasknote-network: + driver: bridge \ No newline at end of file diff --git a/server/Dockerfile b/server/Dockerfile deleted file mode 100644 index 4dbc039..0000000 --- a/server/Dockerfile +++ /dev/null @@ -1,45 +0,0 @@ -### Builder -FROM ghcr.io/graalvm/native-image:22.3.3 AS build - -# Copy -WORKDIR /app -COPY pom.xml mvnw ./ -COPY src ./src -COPY .mvn/ ./.mvn - -# Build -RUN ./mvnw -B package -Pnative -DskipTests - - -### Deployer -FROM debian:bookworm-20250929-slim AS deploy -RUN apt-get update && apt-get install -y --no-install-recommends \ - curl \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -# Copy -WORKDIR /app -COPY --from=build /app/target/server ./tasknote-api - -ARG BUILD -ARG SOURCE_PR -ENV BUILD=${BUILD} -ENV SOURCE_PR=${SOURCE_PR} - -# Add metadata to the final image -LABEL org.opencontainers.image.authors="Ricardo Campos " \ - org.opencontainers.image.vendor="Ricardo Campos Org" \ - org.opencontainers.image.title="TaskNoteApp server" \ - org.opencontainers.image.description="Spring Cloud Native REST API service" \ - org.opencontainers.image.version="${SOURCE_PR}" \ - org.opencontainers.image.source="https://github.com/ricardo-campos-org/react-typescript-todolist" - -# User, port and health check -USER 1001 -EXPOSE ${PORT} -#HEALTHCHECK CMD timeout 10s bash -c 'true > /dev/tcp/127.0.0.1/8585' -HEALTHCHECK --interval=30s --timeout=5s CMD ["curl", "-f", "http://localhost:8585/actuator/health"] - -# Startup -ENTRYPOINT ["/app/tasknote-api", "-Dspring.profiles.active=prod"] diff --git a/server/Dockerfile.dev b/server/Dockerfile.dev deleted file mode 100644 index 11ad02d..0000000 --- a/server/Dockerfile.dev +++ /dev/null @@ -1,4 +0,0 @@ -FROM maven:3.9.9-eclipse-temurin-17 -WORKDIR /app -HEALTHCHECK CMD timeout 10s bash -c 'true > /dev/tcp/127.0.0.1/8585' -CMD ["sh", "run-from-docker.sh"] diff --git a/server/pom.xml b/server/pom.xml index 6515a86..96e801d 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -11,7 +11,7 @@ br.com.tasknoteapp server - 0.0.1-SNAPSHOT + 2.0.0 tasknote-api Java backend REST API to serve TaskNote frontend client @@ -27,6 +27,13 @@ + + + + + + + @@ -42,9 +49,6 @@ ${project.build.directory}/coverage-reports ${maven.build.timestamp} yyyy-MM-dd HH:mm:ss - 6.6.34.Final - ricardo-campos-org - https://sonarcloud.io @@ -68,34 +72,6 @@ prod - - - - native - - native - true - true - true - - - - - org.graalvm.buildtools - native-maven-plugin - - - build-native - - compile-no-fork - - package - - - - - - @@ -119,20 +95,6 @@ spring-boot-starter-mail - - - org.springframework.boot - spring-boot-devtools - runtime - true - - - - - org.springframework.boot - spring-boot-starter-actuator - - org.springframework.boot @@ -141,6 +103,7 @@ org.postgresql postgresql + runtime org.flywaydb @@ -168,28 +131,21 @@ test - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.8.13 - - io.jsonwebtoken jjwt-api - 0.13.0 + 0.12.6 io.jsonwebtoken jjwt-impl - 0.13.0 + 0.12.6 io.jsonwebtoken - jjwt-jackson - 0.13.0 + jjwt-gson + 0.12.6 @@ -357,55 +313,6 @@ - - org.apache.maven.plugins - maven-javadoc-plugin - 3.12.0 - - 17 - Javadoc Documentation for ${project.name} ${project.version} - ${project.name} ${project.version} - - - - org.apache.maven.plugins - maven-enforcer-plugin - 3.6.2 - - - default-cli - - enforce - - - - - - [3.2,) - Invalid Maven version. It should be at least 3.2 - - - 17 - Invalid Java Version. It should be at least 1.8 - - - - central - - - central - - - - No Snapshots Allowed in releases! - true - - - - - - - org.apache.maven.plugins maven-checkstyle-plugin @@ -437,6 +344,26 @@ + + org.graalvm.buildtools + native-maven-plugin + + + org.springframework.boot + spring-boot-maven-plugin + + + + build-info + + + + ${maven.build.timestamp} + + + + + tasknote-api diff --git a/server/src/main/java/br/com/tasknoteapp/server/JavaApiApplication.java b/server/src/main/java/br/com/tasknoteapp/server/JavaApiApplication.java index cd8bec3..d5dae2c 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/JavaApiApplication.java +++ b/server/src/main/java/br/com/tasknoteapp/server/JavaApiApplication.java @@ -2,20 +2,22 @@ package br.com.tasknoteapp.server; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import br.com.tasknoteapp.server.service.AppVersionService; + /** Entrypoint of the Java API service application. */ @SpringBootApplication public class JavaApiApplication implements ApplicationRunner { private static final Logger logger = LoggerFactory.getLogger(JavaApiApplication.class); - @Value("${br.com.tasknote.server.version}") - private String apiBuildInfo; + @Autowired + private AppVersionService appVersionService; /** * Main method of the application. @@ -28,6 +30,6 @@ public class JavaApiApplication implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { - logger.info("Task Note API started successfully - Version: {}", apiBuildInfo); + logger.info("Task Note API started successfully - Version: {}", appVersionService.getVersion()); } } diff --git a/server/src/main/java/br/com/tasknoteapp/server/config/ApiBuildInfoConfig.java b/server/src/main/java/br/com/tasknoteapp/server/config/ApiBuildInfoConfig.java deleted file mode 100644 index 97d7655..0000000 --- a/server/src/main/java/br/com/tasknoteapp/server/config/ApiBuildInfoConfig.java +++ /dev/null @@ -1,19 +0,0 @@ -package br.com.tasknoteapp.server.config; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.stereotype.Component; - -/** This class contains a single component config with image tag version. */ -@Component -public class ApiBuildInfoConfig implements HealthIndicator { - - @Value("${br.com.tasknote.server.version}") - private String apiBuildInfo; - - @Override - public Health health() { - return Health.up().withDetail("buildInfo", apiBuildInfo).build(); - } -} diff --git a/server/src/main/java/br/com/tasknoteapp/server/config/CloudNativeConfig.java b/server/src/main/java/br/com/tasknoteapp/server/config/CloudNativeConfig.java index 01d0cb4..f685a99 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/config/CloudNativeConfig.java +++ b/server/src/main/java/br/com/tasknoteapp/server/config/CloudNativeConfig.java @@ -1,17 +1,6 @@ package br.com.tasknoteapp.server.config; -import br.com.tasknoteapp.server.request.LoginRequest; -import br.com.tasknoteapp.server.request.NotePatchRequest; -import br.com.tasknoteapp.server.request.NoteRequest; -import br.com.tasknoteapp.server.request.NoteUrlPatchRequest; -import br.com.tasknoteapp.server.request.TaskPatchRequest; -import br.com.tasknoteapp.server.request.TaskRequest; -import br.com.tasknoteapp.server.request.TaskUrlPatchRequest; -import br.com.tasknoteapp.server.response.JwtAuthenticationResponse; -import br.com.tasknoteapp.server.response.NoteResponse; -import br.com.tasknoteapp.server.response.NoteUrlResponse; -import br.com.tasknoteapp.server.response.TaskResponse; -import br.com.tasknoteapp.server.response.UserResponse; +import br.com.tasknoteapp.server.hint.HttpServletRequestRuntimeHint; import org.springframework.aot.hint.annotation.RegisterReflectionForBinding; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportRuntimeHints; @@ -19,18 +8,6 @@ import org.springframework.context.annotation.ImportRuntimeHints; /** This class contains configurations for the GraalVM Cloud Native image. */ @Configuration @RegisterReflectionForBinding({ - LoginRequest.class, - NotePatchRequest.class, - NoteRequest.class, - NoteUrlPatchRequest.class, - TaskPatchRequest.class, - TaskRequest.class, - TaskUrlPatchRequest.class, - JwtAuthenticationResponse.class, - NoteResponse.class, - NoteUrlResponse.class, - TaskResponse.class, - UserResponse.class, io.jsonwebtoken.Claims.class, io.jsonwebtoken.Jwts.class, io.jsonwebtoken.Jwts.SIG.class, @@ -45,7 +22,6 @@ import org.springframework.context.annotation.ImportRuntimeHints; io.jsonwebtoken.impl.DefaultClaimsBuilder.class, io.jsonwebtoken.impl.DefaultJwtParserBuilder.class, io.jsonwebtoken.impl.DefaultJwtBuilder.class, - io.jsonwebtoken.impl.DefaultJwtBuilder.Supplier.class, io.jsonwebtoken.lang.Supplier.class, org.flywaydb.core.internal.publishing.PublishingConfigurationExtension.class, }) diff --git a/server/src/main/java/br/com/tasknoteapp/server/config/CorsConfig.java b/server/src/main/java/br/com/tasknoteapp/server/config/CorsConfig.java index 4c8c257..a0e46f8 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/config/CorsConfig.java +++ b/server/src/main/java/br/com/tasknoteapp/server/config/CorsConfig.java @@ -1,7 +1,8 @@ package br.com.tasknoteapp.server.config; import java.util.Arrays; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.lang.NonNull; @@ -12,7 +13,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class CorsConfig implements WebMvcConfigurer { - private static final Logger logger = Logger.getLogger(CorsConfig.class.getName()); + private static final Logger logger = LoggerFactory.getLogger(CorsConfig.class.getName()); @Value("${cors.allowed-origins}") private String[] allowedOrigins; @@ -22,10 +23,10 @@ public class CorsConfig implements WebMvcConfigurer { * * @param registry CorsRegistry instance. */ + @SuppressWarnings("null") public void addCorsMappings(@NonNull CorsRegistry registry) { if (allowedOrigins != null && allowedOrigins.length > 0) { - logger.info("CORS policy allowed origins: " + Arrays.asList(allowedOrigins)); - logger.fine("CORS policy allowed origins in debug mode: " + Arrays.asList(allowedOrigins)); + logger.info("CORS policy allowed origins: {}", Arrays.asList(allowedOrigins)); registry .addMapping("/**") diff --git a/server/src/main/java/br/com/tasknoteapp/server/config/SwaggerConfig.java b/server/src/main/java/br/com/tasknoteapp/server/config/SwaggerConfig.java deleted file mode 100644 index 447d1d2..0000000 --- a/server/src/main/java/br/com/tasknoteapp/server/config/SwaggerConfig.java +++ /dev/null @@ -1,60 +0,0 @@ -package br.com.tasknoteapp.server.config; - -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.security.SecurityScheme.Type; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** This class contains the configuration for Swagger API. */ -@Configuration -public class SwaggerConfig { - - @Value("${br.com.tasknote.server.version}") - private String apiBuildInfo; - - /** - * Gets the API OpenAPI config. - * - * @return OpenAPI with config. - */ - @Bean - public OpenAPI apiConfig() { - Info info = new Info(); - info.setTitle("TaskNote API"); - info.setDescription("RESTful service API for the web client"); - info.setVersion(apiBuildInfo); - - Contact contact = new Contact(); - contact.setName("Ricardo Campos"); - contact.setEmail("ricardompcampos@gmail.com"); - contact.setUrl("https://github.com/ricardo-campos-org/react-typescript-todolist"); - info.setContact(contact); - - License license = new License(); - license.setName("GPL 3.0"); - license.setUrl("https://www.gnu.org/licenses/gpl-3.0"); - info.setLicense(license); - - SecurityScheme securityScheme = new SecurityScheme(); - securityScheme.setType(Type.HTTP); - securityScheme.setScheme("bearer"); - securityScheme.setBearerFormat("JWT"); - - Components components = new Components(); - components.addSecuritySchemes("bearerAuth", securityScheme); - - OpenAPI openApi = new OpenAPI(); - openApi.setInfo(info); - openApi.addSecurityItem(new SecurityRequirement().addList("bearerAuth")); - openApi.setComponents(components); - - return openApi; - } -} diff --git a/server/src/main/java/br/com/tasknoteapp/server/controller/AuthenticationController.java b/server/src/main/java/br/com/tasknoteapp/server/controller/AuthenticationController.java index 03a9c7d..048c4e0 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/controller/AuthenticationController.java +++ b/server/src/main/java/br/com/tasknoteapp/server/controller/AuthenticationController.java @@ -9,11 +9,6 @@ import br.com.tasknoteapp.server.request.PasswordResetRequest; import br.com.tasknoteapp.server.request.ResendConfirmationRequest; import br.com.tasknoteapp.server.response.UserResponseWithToken; import br.com.tasknoteapp.server.service.AuthService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import java.util.Objects; import org.springframework.http.ResponseEntity; @@ -26,9 +21,6 @@ import org.springframework.web.bind.annotation.RestController; /** This class contains the resources for handling authentication. */ @RestController @RequestMapping("/auth") -@Tag( - name = "Authentication", - description = "Authentication resources to handle user authentication.") public class AuthenticationController { private final AuthService authService; @@ -45,20 +37,6 @@ public class AuthenticationController { * @throws EmailAlreadyExistsException when the provide email is already in use. */ @PutMapping(path = "/sign-up", consumes = "application/json", produces = "application/json") - @Operation( - summary = "Signup a new user", - description = "Signup a new user given his email and password", - responses = { - @ApiResponse(responseCode = "204", description = "User successfully created and saved"), - @ApiResponse( - responseCode = "400", - description = "Wrong or missing information", - content = @Content(schema = @Schema(implementation = Void.class))), - @ApiResponse( - responseCode = "409", - description = "Email already in use", - content = @Content(schema = @Schema(implementation = Void.class))) - }) public ResponseEntity signUp(@RequestBody @Valid LoginRequest loginRequest) { authService.signUpNewUser(loginRequest); return ResponseEntity.noContent().build(); @@ -73,20 +51,6 @@ public class AuthenticationController { * invalid. */ @PostMapping(path = "/sign-in", consumes = "application/json", produces = "application/json") - @Operation( - summary = "SigIn an existing user", - description = "SigIn an existing user given his email and password", - responses = { - @ApiResponse(responseCode = "200", description = "User successfully logged in"), - @ApiResponse( - responseCode = "400", - description = "Wrong or missing information", - content = @Content(schema = @Schema(implementation = Void.class))), - @ApiResponse( - responseCode = "404", - description = "User not found", - content = @Content(schema = @Schema(implementation = Void.class))) - }) public ResponseEntity signIn( @RequestBody @Valid LoginRequest loginRequest) { UserResponseWithToken response = authService.signInUser(loginRequest); @@ -103,16 +67,6 @@ public class AuthenticationController { * @return No content 204 http code. */ @PostMapping(path = "/email-confirmation", consumes = "application/json") - @Operation( - summary = "Send a confirmation email to the user", - description = "After the registration sends the user a confirmation email", - responses = { - @ApiResponse(responseCode = "204", description = "User successfully logged in"), - @ApiResponse( - responseCode = "400", - description = "Wrong or missing information", - content = @Content(schema = @Schema(implementation = Void.class))), - }) public ResponseEntity confirmEmailAddress( @RequestBody @Valid EmailConfirmationRequest confirmation) { authService.confirmUserAccount(confirmation.identification()); @@ -126,16 +80,6 @@ public class AuthenticationController { * @return No content 204 http code. */ @PostMapping(path = "/resend-email-confirmation", consumes = "application/json") - @Operation( - summary = "Re-Send a confirmation email to the user", - description = "Allow users to resend the confirmation email", - responses = { - @ApiResponse(responseCode = "204", description = "User email confirmation resent"), - @ApiResponse( - responseCode = "400", - description = "Wrong or missing information", - content = @Content(schema = @Schema(implementation = Void.class))), - }) public ResponseEntity resendEmailConfirmation( @RequestBody @Valid ResendConfirmationRequest request) { authService.resendEmailConfirmation(request.email()); @@ -149,16 +93,6 @@ public class AuthenticationController { * @return No content 204 http code. */ @PostMapping(path = "/password-reset", consumes = "application/json") - @Operation( - summary = "Request a user's password reset", - description = "Request the user password reset if there's a user", - responses = { - @ApiResponse(responseCode = "204", description = "User password requested"), - @ApiResponse( - responseCode = "400", - description = "Wrong or missing information", - content = @Content(schema = @Schema(implementation = Void.class))), - }) public ResponseEntity passwordReset(@RequestBody @Valid ResendConfirmationRequest request) { authService.resetPasswordForUser(request.email()); return ResponseEntity.noContent().build(); @@ -171,16 +105,6 @@ public class AuthenticationController { * @return No content 204 http code. */ @PostMapping(path = "/complete-password-reset", consumes = "application/json") - @Operation( - summary = "Confirm the password reset", - description = "Confirm and set the new password for the user", - responses = { - @ApiResponse(responseCode = "204", description = "User password reset completed"), - @ApiResponse( - responseCode = "400", - description = "Wrong or missing information", - content = @Content(schema = @Schema(implementation = Void.class))), - }) public ResponseEntity completePasswordReset( @RequestBody @Valid PasswordResetRequest request) { authService.confirmResetPasswordForUser(request); diff --git a/server/src/main/java/br/com/tasknoteapp/server/controller/HealthController.java b/server/src/main/java/br/com/tasknoteapp/server/controller/HealthController.java new file mode 100644 index 0000000..c5b3359 --- /dev/null +++ b/server/src/main/java/br/com/tasknoteapp/server/controller/HealthController.java @@ -0,0 +1,59 @@ +package br.com.tasknoteapp.server.controller; + +import java.sql.Connection; +import java.util.HashMap; +import java.util.Map; +import javax.sql.DataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import br.com.tasknoteapp.server.service.AppVersionService; + +/** Controller to handle health check requests. */ +@RestController +public class HealthController { + + @Autowired private DataSource dataSource; + + @Autowired private AppVersionService appVersionService; + + /** + * Endpoint to check the health of the application and its database connection. + * + * @return ResponseEntity containing health status and version information. + */ + @GetMapping("/health") + public ResponseEntity> health() { + Map health = new HashMap<>(); + Map dbHealth = checkDatabase(); + + health.put("application", "UP"); + health.put("version", appVersionService.getVersion()); + health.put("database", dbHealth); + + boolean isHealthy = "UP".equals(dbHealth.get("status")); + HttpStatus status = isHealthy ? HttpStatus.OK : HttpStatus.SERVICE_UNAVAILABLE; + + return ResponseEntity.status(status).body(health); + } + + private Map checkDatabase() { + Map dbHealth = new HashMap<>(); + try (Connection connection = dataSource.getConnection()) { + if (connection.isValid(2)) { + dbHealth.put("status", "UP"); + } else { + dbHealth.put("status", "DOWN"); + dbHealth.put("reason", "Connection invalid"); + } + } catch (Exception e) { + dbHealth.put("status", "DOWN"); + dbHealth.put("error", e.getMessage()); + } + return dbHealth; + } +} diff --git a/server/src/main/java/br/com/tasknoteapp/server/controller/HomeController.java b/server/src/main/java/br/com/tasknoteapp/server/controller/HomeController.java index 3eb25ec..567baa8 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/controller/HomeController.java +++ b/server/src/main/java/br/com/tasknoteapp/server/controller/HomeController.java @@ -1,11 +1,6 @@ package br.com.tasknoteapp.server.controller; import br.com.tasknoteapp.server.service.HomeService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -14,7 +9,6 @@ import org.springframework.web.bind.annotation.RestController; /** This class provides resources to handle home requests by the client. */ @RestController @RequestMapping("/rest/home") -@Tag(name = "Home", description = "Home resources to handle home page.") public class HomeController { private final HomeService homeService; @@ -29,22 +23,6 @@ public class HomeController { * @returns List of String with the tags. */ @GetMapping("/tasks/tags") - @Operation( - summary = "Get the top 5 tags", - description = "Get the top 5 tags or the ones in use", - responses = { - @ApiResponse( - responseCode = "200", - description = "List of tags or an empty list", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = String.class, type = "array"))), - @ApiResponse( - responseCode = "401", - description = "Unauthorized. Access Denied", - content = @Content(schema = @Schema(implementation = Void.class))) - }) public List getTasksTags() { return homeService.getTopTasksTag(); } diff --git a/server/src/main/java/br/com/tasknoteapp/server/controller/NoteController.java b/server/src/main/java/br/com/tasknoteapp/server/controller/NoteController.java index d19fc4c..1910613 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/controller/NoteController.java +++ b/server/src/main/java/br/com/tasknoteapp/server/controller/NoteController.java @@ -6,17 +6,11 @@ import br.com.tasknoteapp.server.request.NotePatchRequest; import br.com.tasknoteapp.server.request.NoteRequest; import br.com.tasknoteapp.server.response.NoteResponse; import br.com.tasknoteapp.server.service.NoteService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.enums.ParameterIn; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -29,7 +23,6 @@ import org.springframework.web.bind.annotation.RestController; /** This class provides resources to handle notes requests by the client. */ @RestController @RequestMapping("/rest/notes") -@Tag(name = "Notes", description = "Notes resources to handle stored notes.") public class NoteController { private final NoteService noteService; @@ -44,22 +37,6 @@ public class NoteController { * @return List of NoteResponse with all found notes and its urls, if any. */ @GetMapping - @Operation( - summary = "Get all notes", - description = "Get all notes for the current user and its urls, if any", - responses = { - @ApiResponse( - responseCode = "200", - description = "Notes successfully retrieved", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = NoteResponse.class, type = "array"))), - @ApiResponse( - responseCode = "401", - description = "Unauthorized. Access Denied", - content = @Content(schema = @Schema(implementation = Void.class))) - }) public List getAllNotes() { return noteService.getAllNotes(); } @@ -72,31 +49,7 @@ public class NoteController { * @throws NoteNotFoundException when note not found. */ @GetMapping("/{id}") - @Operation( - summary = "Get a note by its ID", - description = "Get a note by its ID and its urls, if any.", - responses = { - @ApiResponse( - responseCode = "200", - description = "Return the found Note and its urls, if any."), - @ApiResponse( - responseCode = "401", - description = "Unauthorized. Access Denied", - content = @Content(schema = @Schema(implementation = Void.class))), - @ApiResponse( - responseCode = "404", - description = "Note not found", - content = @Content(schema = @Schema(implementation = Void.class))) - }) - public NoteResponse getTaskById( - @Parameter( - name = "id", - in = ParameterIn.PATH, - description = "Note id to be fetched.", - required = true, - schema = @Schema(type = "integer", format = "int64")) - @PathVariable - Long id) { + public NoteResponse getTaskById(@NonNull @PathVariable Long id) { return noteService.getNoteById(id); } @@ -109,41 +62,9 @@ public class NoteController { * @throws NoteNotFoundException when note not found. */ @PatchMapping("/{id}") - @Operation( - summary = "Patch a note", - description = "Patch a note and all its urls. Option to patch only the urls.", - responses = { - @ApiResponse( - responseCode = "200", - description = "Note successfully patched", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = NoteResponse.class))), - @ApiResponse( - responseCode = "401", - description = "Unauthorized. Access Denied", - content = @Content(schema = @Schema(implementation = Void.class))), - @ApiResponse( - responseCode = "404", - description = "Note not found", - content = @Content(schema = @Schema(implementation = Void.class))) - }) public ResponseEntity patchNote( - @Parameter( - name = "id", - in = ParameterIn.PATH, - description = "Note id to be patched.", - required = true, - schema = @Schema(type = "integer", format = "int64")) - @PathVariable - Long id, - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "Note data to be patched, including optionally its urls.", - required = true) - @RequestBody - @Valid - NotePatchRequest noteRequest) { + @PathVariable @NonNull Long id, @RequestBody @Valid NotePatchRequest noteRequest) { + return ResponseEntity.ok(noteService.patchNote(id, noteRequest)); } @@ -155,33 +76,7 @@ public class NoteController { * @return NoteResponse containing data that was created. */ @PostMapping - @Operation( - summary = "Create a note", - description = "Create a note and all its urls.", - responses = { - @ApiResponse( - responseCode = "201", - description = "Note successfully crated.", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = NoteResponse.class))), - @ApiResponse( - responseCode = "400", - description = "Wrong or missing information", - content = @Content(schema = @Schema(implementation = Void.class))), - @ApiResponse( - responseCode = "401", - description = "Unauthorized. Access Denied", - content = @Content(schema = @Schema(implementation = Void.class))), - }) - public ResponseEntity postNotes( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "Note data to be created, including optionally its urls.", - required = true) - @RequestBody - @Valid - NoteRequest noteRequest) { + public ResponseEntity postNotes(@RequestBody @Valid NoteRequest noteRequest) { NoteEntity createdNote = noteService.createNote(noteRequest); return ResponseEntity.status(HttpStatus.CREATED).body(NoteResponse.fromEntity(createdNote)); } @@ -193,32 +88,7 @@ public class NoteController { * @throws NoteNotFoundException when note not found. */ @DeleteMapping("/{id}") - @Operation( - summary = "Delete a note", - description = "Delete a note given its ID.", - responses = { - @ApiResponse( - responseCode = "204", - description = "Note successfully deleted", - content = @Content(schema = @Schema(implementation = Void.class))), - @ApiResponse( - responseCode = "401", - description = "Unauthorized. Access Denied", - content = @Content(schema = @Schema(implementation = Void.class))), - @ApiResponse( - responseCode = "404", - description = "Note not found", - content = @Content(schema = @Schema(implementation = Void.class))) - }) - public ResponseEntity deleteNote( - @Parameter( - name = "id", - in = ParameterIn.PATH, - description = "Note id to be patched.", - required = true, - schema = @Schema(type = "integer", format = "int64")) - @PathVariable - Long id) { + public ResponseEntity deleteNote(@NonNull @PathVariable Long id) { noteService.deleteNote(id); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } diff --git a/server/src/main/java/br/com/tasknoteapp/server/controller/TaskController.java b/server/src/main/java/br/com/tasknoteapp/server/controller/TaskController.java index 5e49e15..b1d97b3 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/controller/TaskController.java +++ b/server/src/main/java/br/com/tasknoteapp/server/controller/TaskController.java @@ -5,17 +5,11 @@ import br.com.tasknoteapp.server.request.TaskPatchRequest; import br.com.tasknoteapp.server.request.TaskRequest; import br.com.tasknoteapp.server.response.TaskResponse; import br.com.tasknoteapp.server.service.TaskService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.enums.ParameterIn; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -28,7 +22,6 @@ import org.springframework.web.bind.annotation.RestController; /** This class contains resources for handling tasks. */ @RestController @RequestMapping("/rest/tasks") -@Tag(name = "Tasks", description = "Tasks resources to handle user tasks and urls.") public class TaskController { private final TaskService taskService; @@ -43,22 +36,6 @@ public class TaskController { * @return List of TaskResponse with all found tasks and its urls, if any. */ @GetMapping - @Operation( - summary = "Get all tasks", - description = "Get all tasks for the current user and its urls, if any", - responses = { - @ApiResponse( - responseCode = "200", - description = "Return an array containing found Tasks, or empty array otherwise.", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = TaskResponse.class, type = "array"))), - @ApiResponse( - responseCode = "401", - description = "Unauthorized. Access Denied", - content = @Content(schema = @Schema(implementation = Void.class))) - }) public List getAllTasks() { return taskService.getAllTasks(); } @@ -71,31 +48,7 @@ public class TaskController { * @throws TaskNotFoundException when task not found. */ @GetMapping("/{id}") - @Operation( - summary = "Get a task by its ID", - description = "Get a task by its ID and its urls, if any.", - responses = { - @ApiResponse( - responseCode = "200", - description = "Return the found Task and its urls, if any."), - @ApiResponse( - responseCode = "401", - description = "Unauthorized. Access Denied", - content = @Content(schema = @Schema(implementation = Void.class))), - @ApiResponse( - responseCode = "404", - description = "Task not found", - content = @Content(schema = @Schema(implementation = Void.class))) - }) - public TaskResponse getTaskById( - @Parameter( - name = "id", - in = ParameterIn.PATH, - description = "Task id to be fetched.", - required = true, - schema = @Schema(type = "integer", format = "int64")) - @PathVariable - Long id) { + public TaskResponse getTaskById(@NonNull @PathVariable Long id) { return taskService.getTaskById(id); } @@ -108,41 +61,8 @@ public class TaskController { * @throws TaskNotFoundException when task not found. */ @PatchMapping("/{id}") - @Operation( - summary = "Patch a task", - description = "Patch a task and all its urls. Option to patch only the urls.", - responses = { - @ApiResponse( - responseCode = "200", - description = "Task successfully patched", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = TaskResponse.class))), - @ApiResponse( - responseCode = "401", - description = "Unauthorized. Access Denied", - content = @Content(schema = @Schema(implementation = Void.class))), - @ApiResponse( - responseCode = "404", - description = "Task not found", - content = @Content(schema = @Schema(implementation = Void.class))) - }) public ResponseEntity patchTask( - @Parameter( - name = "id", - in = ParameterIn.PATH, - description = "Task id to be patched.", - required = true, - schema = @Schema(type = "integer", format = "int64")) - @PathVariable - Long id, - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "Task data to be patched, including optionally its urls.", - required = true) - @RequestBody - @Valid - TaskPatchRequest taskRequest) { + @PathVariable @NonNull Long id, @RequestBody @Valid TaskPatchRequest taskRequest) { return ResponseEntity.ok(taskService.patchTask(id, taskRequest)); } @@ -154,33 +74,7 @@ public class TaskController { * @return TaskResponse containing data that was created. */ @PostMapping - @Operation( - summary = "Create a task", - description = "Create a task and all its urls.", - responses = { - @ApiResponse( - responseCode = "201", - description = "Task successfully crated.", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = TaskResponse.class))), - @ApiResponse( - responseCode = "400", - description = "Wrong or missing information", - content = @Content(schema = @Schema(implementation = Void.class))), - @ApiResponse( - responseCode = "401", - description = "Unauthorized. Access Denied", - content = @Content(schema = @Schema(implementation = Void.class))), - }) - public ResponseEntity postTasks( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "Task data to be created, including optionally its urls.", - required = true) - @RequestBody - @Valid - TaskRequest taskRequest) { + public ResponseEntity postTasks(@RequestBody @Valid TaskRequest taskRequest) { TaskResponse response = taskService.createTask(taskRequest); return ResponseEntity.status(HttpStatus.CREATED).body(response); } @@ -192,32 +86,7 @@ public class TaskController { * @throws TaskNotFoundException when task not found. */ @DeleteMapping("/{id}") - @Operation( - summary = "Delete a task", - description = "Delete a task given its ID.", - responses = { - @ApiResponse( - responseCode = "204", - description = "Task successfully deleted", - content = @Content(schema = @Schema(implementation = Void.class))), - @ApiResponse( - responseCode = "401", - description = "Unauthorized. Access Denied", - content = @Content(schema = @Schema(implementation = Void.class))), - @ApiResponse( - responseCode = "404", - description = "Task not found", - content = @Content(schema = @Schema(implementation = Void.class))) - }) - public ResponseEntity deleteTask( - @Parameter( - name = "id", - in = ParameterIn.PATH, - description = "Task id to be patched.", - required = true, - schema = @Schema(type = "integer", format = "int64")) - @PathVariable - Long id) { + public ResponseEntity deleteTask(@NonNull @PathVariable Long id) { taskService.deleteTask(id); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } diff --git a/server/src/main/java/br/com/tasknoteapp/server/controller/UserController.java b/server/src/main/java/br/com/tasknoteapp/server/controller/UserController.java index 8f54c3a..fa0d60f 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/controller/UserController.java +++ b/server/src/main/java/br/com/tasknoteapp/server/controller/UserController.java @@ -3,11 +3,6 @@ package br.com.tasknoteapp.server.controller; import br.com.tasknoteapp.server.request.UserPatchRequest; import br.com.tasknoteapp.server.response.UserResponse; import br.com.tasknoteapp.server.service.AuthService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import java.util.List; import org.springframework.http.ResponseEntity; @@ -20,7 +15,6 @@ import org.springframework.web.bind.annotation.RestController; /** This class contains resources for handling users admin requests. */ @RestController @RequestMapping("/rest/users") -@Tag(name = "Users", description = "Users resources to handle stored users.") public class UserController { private final AuthService authService; @@ -35,54 +29,13 @@ public class UserController { * @return List of UserEntity with all found users. */ @GetMapping - @Operation( - summary = "Get all users", - description = "Get all users for the current user", - responses = { - @ApiResponse( - responseCode = "200", - description = "Users successfully retrieved", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = UserResponse.class, type = "array"))), - @ApiResponse( - responseCode = "401", - description = "Unauthorized. Access Denied", - content = @Content(schema = @Schema(implementation = Void.class))), - }) public List getAllUsers() { return authService.getAllUsers(); } @PatchMapping - @Operation( - summary = "Patch the user data", - description = "Patch all user information. Empty fields will not be updated", - responses = { - @ApiResponse( - responseCode = "200", - description = "Task successfully patched", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = UserResponse.class))), - @ApiResponse( - responseCode = "401", - description = "Unauthorized. Access Denied", - content = @Content(schema = @Schema(implementation = Void.class))), - @ApiResponse( - responseCode = "404", - description = "Task not found", - content = @Content(schema = @Schema(implementation = Void.class))) - }) public ResponseEntity patchUserInfo( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "User data to be patched.", - required = true) - @RequestBody - @Valid - UserPatchRequest taskRequest) { + @RequestBody @Valid UserPatchRequest taskRequest) { UserResponse patched = authService.patchUserInfo(taskRequest); return ResponseEntity.ok().body(patched); } diff --git a/server/src/main/java/br/com/tasknoteapp/server/controller/UserSessionController.java b/server/src/main/java/br/com/tasknoteapp/server/controller/UserSessionController.java index 71f846b..b6c8f54 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/controller/UserSessionController.java +++ b/server/src/main/java/br/com/tasknoteapp/server/controller/UserSessionController.java @@ -4,11 +4,6 @@ import br.com.tasknoteapp.server.exception.UserNotFoundException; import br.com.tasknoteapp.server.response.JwtAuthenticationResponse; import br.com.tasknoteapp.server.response.UserResponse; import br.com.tasknoteapp.server.service.UserSessionService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -18,7 +13,6 @@ import org.springframework.web.bind.annotation.RestController; /** This class contains resources for handling user sessions. */ @RestController @RequestMapping("/rest/user-sessions") -@Tag(name = "User Sessions", description = "Resources to handle user sessions.") public class UserSessionController { private final UserSessionService userSessionService; @@ -34,16 +28,6 @@ public class UserSessionController { * @throws UserNotFoundException if user not found */ @GetMapping("/refresh") - @Operation( - summary = "Refresh an existing user session", - description = "Refresh an existing user session, generating a new token", - responses = { - @ApiResponse(responseCode = "200", description = "Session successfully refreshed"), - @ApiResponse( - responseCode = "401", - description = "Unauthorized. Access Denied", - content = @Content(schema = @Schema(implementation = Void.class))) - }) public JwtAuthenticationResponse refresh() { return userSessionService.refreshUserSession(); } @@ -54,16 +38,6 @@ public class UserSessionController { * @returns {@link UserResponse} with the user information. */ @DeleteMapping("/delete-account") - @Operation( - summary = "Delete the user account.", - description = "Delete all the user data and information from the server.", - responses = { - @ApiResponse(responseCode = "200", description = "Account successfully deleted"), - @ApiResponse( - responseCode = "401", - description = "Unauthorized. Access Denied", - content = @Content(schema = @Schema(implementation = Void.class))) - }) public ResponseEntity deleteAccount() { UserResponse deleted = userSessionService.deleteCurrentUserAccount(); return ResponseEntity.ok(deleted); diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/UserEntity.java b/server/src/main/java/br/com/tasknoteapp/server/entity/UserEntity.java index 14da2bd..f0841e1 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/entity/UserEntity.java +++ b/server/src/main/java/br/com/tasknoteapp/server/entity/UserEntity.java @@ -90,7 +90,6 @@ public class UserEntity implements UserDetails { return true; } - // TODO: generate all Getters and Setters public Long getId() { return id; } diff --git a/server/src/main/java/br/com/tasknoteapp/server/filter/HeaderVersionFilter.java b/server/src/main/java/br/com/tasknoteapp/server/filter/HeaderVersionFilter.java index ad8b4a0..7aebc94 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/filter/HeaderVersionFilter.java +++ b/server/src/main/java/br/com/tasknoteapp/server/filter/HeaderVersionFilter.java @@ -1,11 +1,12 @@ package br.com.tasknoteapp.server.filter; +import br.com.tasknoteapp.server.service.AppVersionService; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -14,8 +15,7 @@ import org.springframework.web.filter.OncePerRequestFilter; @Component public class HeaderVersionFilter extends OncePerRequestFilter { - @Value("${br.com.tasknote.server.version}") - private String apiBuildInfo; + @Autowired private AppVersionService appVersionService; @Override protected void doFilterInternal( @@ -23,7 +23,7 @@ public class HeaderVersionFilter extends OncePerRequestFilter { @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { - response.setHeader("X-BUILD-INFO", apiBuildInfo); + response.setHeader("X-BUILD-INFO", appVersionService.getVersion()); filterChain.doFilter(request, response); } } diff --git a/server/src/main/java/br/com/tasknoteapp/server/config/HttpServletRequestRuntimeHint.java b/server/src/main/java/br/com/tasknoteapp/server/hint/HttpServletRequestRuntimeHint.java similarity index 94% rename from server/src/main/java/br/com/tasknoteapp/server/config/HttpServletRequestRuntimeHint.java rename to server/src/main/java/br/com/tasknoteapp/server/hint/HttpServletRequestRuntimeHint.java index 1729c40..132edad 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/config/HttpServletRequestRuntimeHint.java +++ b/server/src/main/java/br/com/tasknoteapp/server/hint/HttpServletRequestRuntimeHint.java @@ -1,4 +1,4 @@ -package br.com.tasknoteapp.server.config; +package br.com.tasknoteapp.server.hint; import jakarta.servlet.http.HttpServletRequest; import org.springframework.aot.hint.ProxyHints; diff --git a/server/src/main/java/br/com/tasknoteapp/server/hint/JjwtRuntimeHints.java b/server/src/main/java/br/com/tasknoteapp/server/hint/JjwtRuntimeHints.java new file mode 100644 index 0000000..6c6792e --- /dev/null +++ b/server/src/main/java/br/com/tasknoteapp/server/hint/JjwtRuntimeHints.java @@ -0,0 +1,32 @@ +package br.com.tasknoteapp.server.hint; + +import io.jsonwebtoken.io.DeserializationException; +import io.jsonwebtoken.io.SerializationException; +import io.jsonwebtoken.security.SignatureException; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +/** + * This class creates RuntimeHints for JJWT exceptions to ensure they are available at runtime in + * native images. + */ +@Configuration +@ImportRuntimeHints(JjwtRuntimeHints.JjwtHintsRegistrar.class) +public class JjwtRuntimeHints { + + static class JjwtHintsRegistrar implements RuntimeHintsRegistrar { + @Override + public void registerHints(@NonNull RuntimeHints hints, @Nullable ClassLoader classLoader) { + // Register JJWT exceptions for reflection + hints + .reflection() + .registerType(SignatureException.class) + .registerType(SerializationException.class) + .registerType(DeserializationException.class); + } + } +} diff --git a/server/src/main/java/br/com/tasknoteapp/server/request/EmailConfirmationRequest.java b/server/src/main/java/br/com/tasknoteapp/server/request/EmailConfirmationRequest.java index 15d3188..a17414a 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/request/EmailConfirmationRequest.java +++ b/server/src/main/java/br/com/tasknoteapp/server/request/EmailConfirmationRequest.java @@ -1,9 +1,6 @@ package br.com.tasknoteapp.server.request; -import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; /** This record represents a confirmation payload. */ -@Schema(description = "Confirmation payload for the user to confirm his email account.") -public record EmailConfirmationRequest( - @Schema(description = "Confirmation token") @NotNull String identification) {} +public record EmailConfirmationRequest(@NotNull String identification) {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/request/LoginRequest.java b/server/src/main/java/br/com/tasknoteapp/server/request/LoginRequest.java index bd33865..84534d5 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/request/LoginRequest.java +++ b/server/src/main/java/br/com/tasknoteapp/server/request/LoginRequest.java @@ -1,26 +1,17 @@ package br.com.tasknoteapp.server.request; -import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotNull; /** This class represents a login request with user email and password. */ -@Schema(description = "Login request with user email and password.") @NotNull public class LoginRequest { - @Schema(description = "User email.") - @Email - @NotNull - private String email; + @Email @NotNull private String email; - @Schema(description = "User password.") - @NotNull - private String password; + @NotNull private String password; - @Schema(description = "User password again.") private String passwordAgain; - @Schema(description = "User language. (Optional, default English)") private String lang; public LoginRequest() {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/request/NotePatchRequest.java b/server/src/main/java/br/com/tasknoteapp/server/request/NotePatchRequest.java index 794cd45..0690195 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/request/NotePatchRequest.java +++ b/server/src/main/java/br/com/tasknoteapp/server/request/NotePatchRequest.java @@ -1,11 +1,4 @@ package br.com.tasknoteapp.server.request; -import io.swagger.v3.oas.annotations.media.Schema; - /** This record represents a note patch payload. */ -@Schema(description = "Note patch payload.") -public record NotePatchRequest( - @Schema(description = "Note title. Optional.") String title, - @Schema(description = "Note description. Optional.") String description, - @Schema(description = "Note urls. Optional.") String url, - @Schema(description = "Note tag, optional.") String tag) {} +public record NotePatchRequest(String title, String description, String url, String tag) {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/request/NoteRequest.java b/server/src/main/java/br/com/tasknoteapp/server/request/NoteRequest.java index 44d7001..f8f8ca4 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/request/NoteRequest.java +++ b/server/src/main/java/br/com/tasknoteapp/server/request/NoteRequest.java @@ -1,12 +1,7 @@ package br.com.tasknoteapp.server.request; -import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; /** This record represents a note request to be created. */ -@Schema(description = "Note request to be created.") public record NoteRequest( - @Schema(description = "Note title.") @NotNull String title, - @Schema(description = "Note description.") @NotNull String description, - @Schema(description = "Note urls. Optional.") String url, - @Schema(description = "Note tag, optional.") String tag) {} + @NotNull String title, @NotNull String description, String url, String tag) {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/request/NoteUrlPatchRequest.java b/server/src/main/java/br/com/tasknoteapp/server/request/NoteUrlPatchRequest.java index 2a3a416..bc0b787 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/request/NoteUrlPatchRequest.java +++ b/server/src/main/java/br/com/tasknoteapp/server/request/NoteUrlPatchRequest.java @@ -1,9 +1,4 @@ package br.com.tasknoteapp.server.request; -import io.swagger.v3.oas.annotations.media.Schema; - /** This record represents a Note Url payload to be patched. */ -@Schema(description = "Represents a Note Url payload to be patched.") -public record NoteUrlPatchRequest( - @Schema(description = "The identification of the note url in the database") Long id, - @Schema(description = "The note URL address, if any.") String url) {} +public record NoteUrlPatchRequest(Long id, String url) {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/request/PasswordResetRequest.java b/server/src/main/java/br/com/tasknoteapp/server/request/PasswordResetRequest.java index c02f154..17a9035 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/request/PasswordResetRequest.java +++ b/server/src/main/java/br/com/tasknoteapp/server/request/PasswordResetRequest.java @@ -1,11 +1,7 @@ package br.com.tasknoteapp.server.request; -import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; /** This record represents the confirmation of the password reset. */ -@Schema(description = "The password request confirmation payload.") public record PasswordResetRequest( - @Schema(description = "Reset token") @NotNull String token, - @Schema(description = "New password") @NotNull String password, - @Schema(description = "New password again") @NotNull String passwordAgain) {} + @NotNull String token, @NotNull String password, @NotNull String passwordAgain) {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/request/ResendConfirmationRequest.java b/server/src/main/java/br/com/tasknoteapp/server/request/ResendConfirmationRequest.java index 7cac0e1..22dab70 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/request/ResendConfirmationRequest.java +++ b/server/src/main/java/br/com/tasknoteapp/server/request/ResendConfirmationRequest.java @@ -1,17 +1,12 @@ package br.com.tasknoteapp.server.request; -import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotNull; /** This class represents a login request with user email and password. */ -@Schema(description = "Resend confirmation request with user email and password.") @NotNull public class ResendConfirmationRequest { - @Schema(description = "User email.") - @Email - @NotNull - private String email; + @Email @NotNull private String email; public ResendConfirmationRequest() {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/request/TaskPatchRequest.java b/server/src/main/java/br/com/tasknoteapp/server/request/TaskPatchRequest.java index 8c6dc9d..694815f 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/request/TaskPatchRequest.java +++ b/server/src/main/java/br/com/tasknoteapp/server/request/TaskPatchRequest.java @@ -1,14 +1,12 @@ package br.com.tasknoteapp.server.request; -import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; /** This record represents a task patch payload. */ -@Schema(description = "Task patch payload.") public record TaskPatchRequest( - @Schema(description = "Task description. Optional.") String description, - @Schema(description = "Task done definition. Optional.") Boolean done, - @Schema(description = "Task urls. Optional.") List urls, - @Schema(description = "Due date. Optional.") String dueDate, - @Schema(description = "Define high priority. Optional.") Boolean highPriority, - @Schema(description = "Task tag, optional.") String tag) {} + String description, + Boolean done, + List urls, + String dueDate, + Boolean highPriority, + String tag) {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/request/TaskRequest.java b/server/src/main/java/br/com/tasknoteapp/server/request/TaskRequest.java index a4dd795..71adf2b 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/request/TaskRequest.java +++ b/server/src/main/java/br/com/tasknoteapp/server/request/TaskRequest.java @@ -1,15 +1,13 @@ package br.com.tasknoteapp.server.request; -import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.util.List; /** This record represents a task request to be created. */ -@Schema(description = "Task request to be created.") public record TaskRequest( - @Schema(description = "Task description.") @NotNull @NotEmpty String description, - @Schema(description = "Task urls. Optional.") List urls, - @Schema(description = "Due date. Optional.") String dueDate, - @Schema(description = "Define high priority. Optional.") Boolean highPriority, - @Schema(description = "Task tag, optional.") String tag) {} + @NotNull @NotEmpty String description, + List urls, + String dueDate, + Boolean highPriority, + String tag) {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/request/TaskUrlPatchRequest.java b/server/src/main/java/br/com/tasknoteapp/server/request/TaskUrlPatchRequest.java index 2539642..9ac1e12 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/request/TaskUrlPatchRequest.java +++ b/server/src/main/java/br/com/tasknoteapp/server/request/TaskUrlPatchRequest.java @@ -1,9 +1,4 @@ package br.com.tasknoteapp.server.request; -import io.swagger.v3.oas.annotations.media.Schema; - /** This record represents a Task Url payload to be patched. */ -@Schema(description = "Represents a Task Url payload to be patched.") -public record TaskUrlPatchRequest( - @Schema(description = "The identification of the task url in the database") Long id, - @Schema(description = "The task URL address, if any.") String url) {} +public record TaskUrlPatchRequest(Long id, String url) {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/request/UserPatchRequest.java b/server/src/main/java/br/com/tasknoteapp/server/request/UserPatchRequest.java index 9b23090..f60ceba 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/request/UserPatchRequest.java +++ b/server/src/main/java/br/com/tasknoteapp/server/request/UserPatchRequest.java @@ -1,12 +1,5 @@ package br.com.tasknoteapp.server.request; -import io.swagger.v3.oas.annotations.media.Schema; - /** This record represents a user patch payload. */ -@Schema(description = "User patch payload.") public record UserPatchRequest( - @Schema(description = "User first name. Optional.") String name, - @Schema(description = "User email. Optional.") String email, - @Schema(description = "User password. Optional.") String password, - @Schema(description = "User password again. Optional.") String passwordAgain, - @Schema(description = "User lang. Optional.") String lang) {} + String name, String email, String password, String passwordAgain, String lang) {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/response/FieldIssueResponse.java b/server/src/main/java/br/com/tasknoteapp/server/response/FieldIssueResponse.java index 1c80c76..2aa5232 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/response/FieldIssueResponse.java +++ b/server/src/main/java/br/com/tasknoteapp/server/response/FieldIssueResponse.java @@ -1,6 +1,3 @@ package br.com.tasknoteapp.server.response; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "An object with fields name and the respective error massages") record FieldIssueResponse(String fieldName, String fieldMessage) {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/response/JwtAuthenticationResponse.java b/server/src/main/java/br/com/tasknoteapp/server/response/JwtAuthenticationResponse.java index 530ff01..7af0a21 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/response/JwtAuthenticationResponse.java +++ b/server/src/main/java/br/com/tasknoteapp/server/response/JwtAuthenticationResponse.java @@ -1,7 +1,4 @@ package br.com.tasknoteapp.server.response; -import io.swagger.v3.oas.annotations.media.Schema; - /** This record represents a JWT Token response to be returned to the client. */ -@Schema(description = "Represents a JWT Token response to be returned to the client.") -public record JwtAuthenticationResponse(@Schema(description = "The JWT token") String token) {} +public record JwtAuthenticationResponse(String token) {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/response/NoteResponse.java b/server/src/main/java/br/com/tasknoteapp/server/response/NoteResponse.java index c6f38f4..2d51a15 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/response/NoteResponse.java +++ b/server/src/main/java/br/com/tasknoteapp/server/response/NoteResponse.java @@ -3,18 +3,11 @@ package br.com.tasknoteapp.server.response; import br.com.tasknoteapp.server.entity.NoteEntity; import br.com.tasknoteapp.server.entity.NoteUrlEntity; import br.com.tasknoteapp.server.util.TimeAgoUtil; -import io.swagger.v3.oas.annotations.media.Schema; import java.util.Objects; /** This record represents a task and its urls object to be returned. */ -@Schema(description = "This record represents a task and its urls object to be returned.") public record NoteResponse( - @Schema(description = "The id of the note", example = "1") Long id, - @Schema(description = "The title of the note", example = "Note 1") String title, - @Schema(description = "The description of the note", example = "Note desc") String description, - @Schema(description = "The urls of the task, zero, one or more.", example = "[]") String url, - @Schema(description = "When was the last update time of the note") String lastUpdate, - @Schema(description = "Task tag, optional.") String tag) { + Long id, String title, String description, String url, String lastUpdate, String tag) { /** * Creates a NoteResponse given a NoteEntity and its Urls. diff --git a/server/src/main/java/br/com/tasknoteapp/server/response/NoteUrlResponse.java b/server/src/main/java/br/com/tasknoteapp/server/response/NoteUrlResponse.java index cf16278..ca47e17 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/response/NoteUrlResponse.java +++ b/server/src/main/java/br/com/tasknoteapp/server/response/NoteUrlResponse.java @@ -1,9 +1,4 @@ package br.com.tasknoteapp.server.response; -import io.swagger.v3.oas.annotations.media.Schema; - /** This record represents a note url object. */ -@Schema(description = "This record represents a note url object.") -public record NoteUrlResponse( - @Schema(description = "Note url id", example = "1") Long id, - @Schema(description = "Note url link", example = "http://duckduckgo.com") String url) {} +public record NoteUrlResponse(Long id, String url) {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/response/TaskResponse.java b/server/src/main/java/br/com/tasknoteapp/server/response/TaskResponse.java index 8c727d9..292c80b 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/response/TaskResponse.java +++ b/server/src/main/java/br/com/tasknoteapp/server/response/TaskResponse.java @@ -2,23 +2,20 @@ package br.com.tasknoteapp.server.response; import br.com.tasknoteapp.server.entity.TaskEntity; import br.com.tasknoteapp.server.util.TimeAgoUtil; -import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDate; import java.util.List; /** This record represents a task and its urls object to be returned. */ -@Schema(description = "This record represents a task and its urls object to be returned.") public record TaskResponse( - @Schema(description = "The id of the task", example = "1") Long id, - @Schema(description = "The description of the task", example = "Task 1") String description, - @Schema(description = "The done status of the task", example = "false") Boolean done, - @Schema(description = "Defined if it's high priority", example = "true") Boolean highPriority, - @Schema(description = "Task due date, if any.", example = "true") LocalDate dueDate, - @Schema(description = "Task due date, if any.", example = "true") String dueDateFmt, - @Schema(description = "When was the last update time of the task") String lastUpdate, - @Schema(description = "Task tag, optional.") String tag, - @Schema(description = "The urls of the task, zero, one or more.", example = "[]") - List urls) { + Long id, + String description, + Boolean done, + Boolean highPriority, + LocalDate dueDate, + String dueDateFmt, + String lastUpdate, + String tag, + List urls) { /** * Creates a TaskResponse given a TaskEntity and its Urls. diff --git a/server/src/main/java/br/com/tasknoteapp/server/response/UserResponse.java b/server/src/main/java/br/com/tasknoteapp/server/response/UserResponse.java index a240964..65d4b37 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/response/UserResponse.java +++ b/server/src/main/java/br/com/tasknoteapp/server/response/UserResponse.java @@ -1,24 +1,18 @@ package br.com.tasknoteapp.server.response; import br.com.tasknoteapp.server.entity.UserEntity; -import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDateTime; import java.util.Optional; /** This record represents a User Response object. */ -@Schema(description = "This record represents a User Response object.") public record UserResponse( - @Schema(description = "The id of the user", example = "1") Long userId, - @Schema(description = "The name of the user", example = "John") String name, - @Schema(description = "The email of the user", example = "user@domain.com") String email, - @Schema(description = "The admin status of the user", example = "false") Boolean admin, - @Schema(description = "The created date and time of the user", example = "2023-01-01T00:00:00") - LocalDateTime createdAt, - @Schema( - description = "The inactivated date and time of the user", - example = "2023-01-01T00:00:00") - LocalDateTime inactivatedAt, - @Schema(description = "The gravatar image URL, if any") String gravatarImageUrl) { + Long userId, + String name, + String email, + Boolean admin, + LocalDateTime createdAt, + LocalDateTime inactivatedAt, + String gravatarImageUrl) { /** * Create a {@link UserResponse} instance from a {@link UserEntity}. diff --git a/server/src/main/java/br/com/tasknoteapp/server/response/UserResponseWithToken.java b/server/src/main/java/br/com/tasknoteapp/server/response/UserResponseWithToken.java index 339e9b6..012be6d 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/response/UserResponseWithToken.java +++ b/server/src/main/java/br/com/tasknoteapp/server/response/UserResponseWithToken.java @@ -1,26 +1,20 @@ package br.com.tasknoteapp.server.response; import br.com.tasknoteapp.server.entity.UserEntity; -import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDateTime; import java.util.Optional; /** This record represents a User Response object. */ -@Schema(description = "This record represents a User Response with token object.") public record UserResponseWithToken( - @Schema(description = "The id of the user", example = "1") Long userId, - @Schema(description = "The name of the user", example = "John") String name, - @Schema(description = "The email of the user", example = "user@domain.com") String email, - @Schema(description = "The admin status of the user", example = "false") Boolean admin, - @Schema(description = "The created date and time of the user", example = "2023-01-01T00:00:00") - LocalDateTime createdAt, - @Schema( - description = "The inactivated date and time of the user", - example = "2023-01-01T00:00:00") - LocalDateTime inactivatedAt, - @Schema(description = "The gravatar image URL, if any") String gravatarImageUrl, - @Schema(description = "The token created upon login") String token, - @Schema(description = "The language selected upon login") String lang) { + Long userId, + String name, + String email, + Boolean admin, + LocalDateTime createdAt, + LocalDateTime inactivatedAt, + String gravatarImageUrl, + String token, + String lang) { /** * Create a {@link UserResponseWithToken} instance from a {@link UserEntity}. diff --git a/server/src/main/java/br/com/tasknoteapp/server/response/ValidationExceptionResponse.java b/server/src/main/java/br/com/tasknoteapp/server/response/ValidationExceptionResponse.java index 84ec5dc..a91e0ef 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/response/ValidationExceptionResponse.java +++ b/server/src/main/java/br/com/tasknoteapp/server/response/ValidationExceptionResponse.java @@ -1,19 +1,15 @@ package br.com.tasknoteapp.server.response; -import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; import org.springframework.validation.FieldError; /** This class represents a validation error exception to be returned in the JSON format. */ -@Schema(description = "An object containing the error message and the invalid fields") public class ValidationExceptionResponse { private static final String MESSAGE_TEMPLATE = "%d field(s) with validation problems!"; - @Schema(description = "The error message") private final String errorMessage; - @Schema(description = "An array of 'FieldIssue' with the invalid fields") private final List fields; /** diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/AppVersionService.java b/server/src/main/java/br/com/tasknoteapp/server/service/AppVersionService.java new file mode 100644 index 0000000..815d530 --- /dev/null +++ b/server/src/main/java/br/com/tasknoteapp/server/service/AppVersionService.java @@ -0,0 +1,29 @@ +package br.com.tasknoteapp.server.service; + +import org.springframework.boot.info.BuildProperties; +import org.springframework.stereotype.Service; + +/** Service to retrieve application version information. */ +@Service +public class AppVersionService { + + private final BuildProperties buildProperties; + + /** + * Constructor for AppVersionService. + * + * @param buildProperties the build properties injected by Spring Boot + */ + public AppVersionService(BuildProperties buildProperties) { + this.buildProperties = buildProperties; + } + + /** + * Retrieves the application version combined with the build time. + * + * @return a string representing the application version and build time + */ + public String getVersion() { + return buildProperties.getVersion() + "-" + buildProperties.getTime(); + } +} diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/AuthService.java b/server/src/main/java/br/com/tasknoteapp/server/service/AuthService.java index f60b952..c4a5751 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/service/AuthService.java +++ b/server/src/main/java/br/com/tasknoteapp/server/service/AuthService.java @@ -33,7 +33,8 @@ import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.UUID; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; @@ -48,7 +49,7 @@ import org.springframework.stereotype.Service; @Service public class AuthService { - private static final Logger logger = Logger.getLogger(AuthService.class.getName()); + private static final Logger logger = LoggerFactory.getLogger(AuthService.class); private final UserRepository userRepository; @@ -105,7 +106,7 @@ public class AuthService { */ @Transactional public UserResponseWithToken signUpNewUser(LoginRequest newUser) { - logger.info("Signing up new user: " + newUser.email()); + logger.info("Signing up new user: {}", newUser.email()); if (findByEmail(newUser.email()).isPresent()) { throw new EmailAlreadyExistsException(); @@ -203,8 +204,8 @@ public class AuthService { userRepository.save(user); return UserResponseWithToken.fromEntity(user, token, getGravatarImageUrl(login.email())); } catch (BadCredentialsException e) { - logger.severe( - "BadCredentialsException when logging in user " + user.getId() + ": " + e.getMessage()); + logger.error( + "BadCredentialsException when logging in user {}: {}", user.getId(), e.getMessage()); // store attempt UserPwdLimitEntity pwdLimit = new UserPwdLimitEntity(); @@ -225,28 +226,28 @@ public class AuthService { public List getAllUsers() { Optional currentUserEmail = authUtil.getCurrentUserEmail(); if (currentUserEmail.isEmpty()) { - logger.severe("Unable to get current user from the request"); + logger.error("Unable to get current user from the request"); throw new UserNotFoundException(); } Optional currentUserOpt = findByEmail(currentUserEmail.get()); if (currentUserOpt.isEmpty()) { - logger.severe("Unable to find user by email with value: " + currentUserEmail.get()); + logger.error("Unable to find user by email with value: {}", currentUserEmail.get()); throw new UserNotFoundException(); } UserEntity currentUser = currentUserOpt.get(); if (!currentUser.getAdmin()) { - logger.warning("User " + currentUser.getId() + " not allowed to list users."); + logger.warn("User {} not allowed to list users.", currentUser.getId()); throw new UserForbiddenException(); } - logger.info("Getting all users to user " + currentUser.getId()); + logger.info("Getting all users to user {}", currentUser.getId()); List users = userRepository.findAll(); List usersResponse = new ArrayList<>(users.size()); users.forEach( u -> usersResponse.add(UserResponse.fromEntity(u, getGravatarImageUrl(u.getEmail())))); - logger.info(usersResponse.size() + " user(s) found!"); + logger.info("{} user(s) found!", usersResponse.size()); return usersResponse; } @@ -261,11 +262,11 @@ public class AuthService { String email = currentUserEmail.orElseThrow(); UserEntity currentUser = findByEmail(email).orElseThrow(); - logger.info("Refreshing current session to user " + currentUser.getId()); + logger.info("Refreshing current session to user {}", currentUser.getId()); String token = jwtService.generateToken(currentUser); - logger.info("User refreshed! Token " + token); + logger.info("User refreshed! Token {}", token); return token; } @@ -279,7 +280,7 @@ public class AuthService { String email = currentUserEmail.orElseThrow(); UserEntity currentUser = findByEmail(email).orElseThrow(); - logger.info("Deleting account for user " + currentUser.getId()); + logger.info("Deleting account for user {}", currentUser.getId()); currentUser.setInactivatedAt(LocalDateTime.now()); userPwdLimitRepository.deleteAllForUser(currentUser.getId()); @@ -336,7 +337,7 @@ public class AuthService { shouldUpdate = true; } - if (shouldUpdate) { + if (shouldUpdate && currentUser != null) { userRepository.save(currentUser); } @@ -503,10 +504,10 @@ public class AuthService { } hexString.append(hex); } - logger.fine("Email hashed: " + hexString); + logger.debug("Email hashed: {}", hexString); return Optional.of(hexString.toString()); } catch (NoSuchAlgorithmException | NullPointerException e) { - logger.severe("NoSuchAlgorithmException or NullPointerException: " + e.getMessage()); + logger.error("NoSuchAlgorithmException or NullPointerException: {}", e.getMessage()); } return Optional.empty(); } @@ -515,15 +516,15 @@ public class AuthService { Sort sort = Sort.by(Direction.DESC, "whenHappened"); List userPwdList = userPwdLimitRepository.findAllByUser_id(userId, sort); - logger.warning("login count attempt for user " + userId + ": " + userPwdList.size()); + logger.warn("login count attempt for user {}: {}", userId, userPwdList.size()); // if it's more than 3 times in the last 10 minutes, raise timer of 3 hours. if (userPwdList.size() >= 3) { UserPwdLimitEntity mostRecent = userPwdList.get(0); - logger.warning("Oldest: " + mostRecent.getWhenHappened()); + logger.warn("Oldest: {}", mostRecent.getWhenHappened()); Duration duration = Duration.between(mostRecent.getWhenHappened(), LocalDateTime.now()); if (duration.toMinutes() <= 3L) { - logger.warning("Wait more " + (3L - duration.toMinutes())); + logger.warn("Wait more {}", 3L - duration.toMinutes()); throw new MaxLoginLimitAttemptException(); } } diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/HomeService.java b/server/src/main/java/br/com/tasknoteapp/server/service/HomeService.java index b68268a..99c8d46 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/service/HomeService.java +++ b/server/src/main/java/br/com/tasknoteapp/server/service/HomeService.java @@ -5,15 +5,16 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.logging.Logger; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; /** This class contains the implementation for the Home Service class. */ @Service public class HomeService { - private static final Logger logger = Logger.getLogger(HomeService.class.getName()); + private static final Logger logger = LoggerFactory.getLogger(HomeService.class); private final TaskService taskService; diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/MailgunEmailService.java b/server/src/main/java/br/com/tasknoteapp/server/service/MailgunEmailService.java index 28a23dc..32efaf4 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/service/MailgunEmailService.java +++ b/server/src/main/java/br/com/tasknoteapp/server/service/MailgunEmailService.java @@ -10,7 +10,8 @@ import br.com.tasknoteapp.server.templates.MailgunTemplateSignUp; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Objects; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.HttpEntity; @@ -27,7 +28,7 @@ import org.springframework.web.client.RestTemplate; @Service public class MailgunEmailService { - private static final Logger logger = Logger.getLogger(MailgunEmailService.class.getName()); + private static final Logger logger = LoggerFactory.getLogger(MailgunEmailService.class); private final RestTemplate restTemplate; private final String targetEnv; private String domain; @@ -151,7 +152,7 @@ public class MailgunEmailService { mailData.add("template", template.getName()); if (!template.getVariables().isEmpty()) { mailData.add("h:X-Mailgun-Variables", template.getVariableValuesJson()); - logger.info("JSON template variables: " + template.getVariableValuesJson()); + logger.info("JSON template variables: {}", template.getVariableValuesJson()); } HttpEntity> request = new HttpEntity<>(mailData, headers); @@ -165,7 +166,7 @@ public class MailgunEmailService { logger.info("Email message send successfully."); } catch (HttpClientErrorException ex) { - logger.severe("Unable to send email: " + ex.getMessage() + " - " + ex.getCause()); + logger.error("Unable to send email: {} - {}", ex.getMessage(), ex.getCause()); } } diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/NoteService.java b/server/src/main/java/br/com/tasknoteapp/server/service/NoteService.java index ed897c2..552a48e 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/service/NoteService.java +++ b/server/src/main/java/br/com/tasknoteapp/server/service/NoteService.java @@ -16,14 +16,16 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.lang.NonNull; import org.springframework.stereotype.Service; /** This class implements the NoteService interface methods. */ @Service public class NoteService { - private static final Logger logger = Logger.getLogger(NoteService.class.getName()); + private static final Logger logger = LoggerFactory.getLogger(NoteService.class); private final NoteRepository noteRepository; @@ -74,7 +76,7 @@ public class NoteService { * @param noteId The task id in the database. * @return {@link NoteResponse} with the found task or throw a {@link TaskNotFoundException}. */ - public NoteResponse getNoteById(Long noteId) { + public NoteResponse getNoteById(@NonNull Long noteId) { UserEntity user = getCurrentUser(); logger.info("Get note " + noteId + " to user " + user.getId()); @@ -125,7 +127,7 @@ public class NoteService { * @return {@link NoteResponse} containing the updated note. */ @Transactional - public NoteResponse patchNote(Long noteId, NotePatchRequest patch) { + public NoteResponse patchNote(@NonNull Long noteId, NotePatchRequest patch) { UserEntity user = getCurrentUser(); logger.info("Patching task " + noteId + " to user " + user.getId()); @@ -172,7 +174,7 @@ public class NoteService { * @param noteId The note id from the database. */ @Transactional - public void deleteNote(Long noteId) { + public void deleteNote(@NonNull Long noteId) { UserEntity user = getCurrentUser(); logger.info("Deleting note " + noteId + " to user " + user.getId()); @@ -182,7 +184,9 @@ public class NoteService { throw new NoteNotFoundException(); } - NoteUrlEntity noteUrl = note.get().getNoteUrl(); + NoteEntity noteEntity = note.get(); + + NoteUrlEntity noteUrl = noteEntity.getNoteUrl(); if (!Objects.isNull(noteUrl)) { noteUrlRepository.delete(noteUrl); logger.info("URL Deleted from task " + noteId); @@ -190,7 +194,7 @@ public class NoteService { logger.info("No urls to delete for task " + noteId); } - noteRepository.delete(note.get()); + noteRepository.delete(noteEntity); logger.info("Note deleted! Id " + noteId); } diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/TaskService.java b/server/src/main/java/br/com/tasknoteapp/server/service/TaskService.java index 6a1f1bb..58a6d95 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/service/TaskService.java +++ b/server/src/main/java/br/com/tasknoteapp/server/service/TaskService.java @@ -19,14 +19,16 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.lang.NonNull; import org.springframework.stereotype.Service; /** This class contains the implementation for the Task Service class. */ @Service public class TaskService { - private static final Logger logger = Logger.getLogger(TaskService.class.getName()); + private static final Logger logger = LoggerFactory.getLogger(TaskService.class); private final TaskRepository taskRepository; @@ -78,7 +80,7 @@ public class TaskService { * @param taskId The task id in the database. * @return {@link TaskResponse} with the found task or throw a {@link TaskNotFoundException}. */ - public TaskResponse getTaskById(Long taskId) { + public TaskResponse getTaskById(@NonNull Long taskId) { UserEntity user = getCurrentUser(); logger.info("Get task " + taskId + " to user " + user.getId()); @@ -129,7 +131,7 @@ public class TaskService { * @return {@link TaskResponse} with the updated content. */ @Transactional - public TaskResponse patchTask(Long taskId, TaskPatchRequest patch) { + public TaskResponse patchTask(@NonNull Long taskId, TaskPatchRequest patch) { UserEntity user = getCurrentUser(); logger.info("Patching task " + taskId + " to user " + user.getId()); @@ -175,7 +177,7 @@ public class TaskService { * @param taskId The task id in the database */ @Transactional - public void deleteTask(Long taskId) { + public void deleteTask(@NonNull Long taskId) { UserEntity user = getCurrentUser(); logger.info("Deleting task " + taskId + " to user " + user.getId()); @@ -185,7 +187,9 @@ public class TaskService { throw new TaskNotFoundException(); } - List urlsToDelete = taskUrlRepository.findAllById_taskId(taskId); + TaskEntity taskEntity = task.get(); + + List urlsToDelete = taskUrlRepository.findAllById_taskId(taskEntity.getId()); if (!urlsToDelete.isEmpty()) { taskUrlRepository.deleteAllById_taskId(taskId); logger.info("Deleted " + urlsToDelete.size() + " urls from task " + taskId); @@ -193,7 +197,7 @@ public class TaskService { logger.info("No urls to delete for task " + taskId); } - taskRepository.delete(task.get()); + taskRepository.delete(taskEntity); logger.info("Task deleted! Id " + taskId); } @@ -295,7 +299,7 @@ public class TaskService { try { taskEntity.setDueDate(LocalDate.parse(patch.dueDate())); } catch (DateTimeParseException e) { - logger.severe( + logger.error( "Unable to parse the provided date: " + patch.dueDate() + ": " + e.getMessage()); } } diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/UserSessionService.java b/server/src/main/java/br/com/tasknoteapp/server/service/UserSessionService.java index 7c48a63..371ff06 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/service/UserSessionService.java +++ b/server/src/main/java/br/com/tasknoteapp/server/service/UserSessionService.java @@ -59,12 +59,18 @@ public class UserSessionService { List tasks = taskService.getAllTasks(); for (TaskResponse task : tasks) { - taskService.deleteTask(task.id()); + Long taskId = task != null ? task.id() : null; + if (taskId != null) { + taskService.deleteTask(taskId); + } } List notes = noteService.getAllNotes(); for (NoteResponse note : notes) { - noteService.deleteNote(note.id()); + Long noteId = note != null ? note.id() : null; + if (noteId != null) { + noteService.deleteNote(noteId); + } } return authService.deleteUserAccount(); diff --git a/server/src/main/resources/META-INF/native-image/reachability-metadata.json b/server/src/main/resources/META-INF/native-image/reachability-metadata.json deleted file mode 100644 index 79f5b76..0000000 --- a/server/src/main/resources/META-INF/native-image/reachability-metadata.json +++ /dev/null @@ -1,162 +0,0 @@ -{ - "reflection": [ - { - "type": "io.jsonwebtoken.Claims" - }, - { - "type": "io.jsonwebtoken.Header" - }, - { - "type": "io.jsonwebtoken.Identifiable" - }, - { - "type": "io.jsonwebtoken.JwsHeader" - }, - { - "type": "io.jsonwebtoken.ProtectedHeader" - }, - { - "type": "io.jsonwebtoken.impl.DefaultClaims", - "allDeclaredFields": true - }, - { - "type": "io.jsonwebtoken.impl.DefaultClaimsBuilder$Supplier", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "io.jsonwebtoken.impl.DefaultHeader", - "allDeclaredFields": true - }, - { - "type": "io.jsonwebtoken.impl.DefaultJwsHeader", - "allDeclaredFields": true - }, - { - "type": "io.jsonwebtoken.impl.DefaultJwtBuilder$Supplier", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "io.jsonwebtoken.impl.DefaultJwtHeaderBuilder$Supplier", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "io.jsonwebtoken.impl.DefaultJwtParserBuilder$Supplier", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "io.jsonwebtoken.impl.DefaultProtectedHeader", - "allDeclaredFields": true - }, - { - "type": "io.jsonwebtoken.impl.ParameterMap", - "allDeclaredFields": true - }, - { - "type": "io.jsonwebtoken.impl.io.StandardCompressionAlgorithms", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "io.jsonwebtoken.impl.lang.Nameable" - }, - { - "type": "io.jsonwebtoken.impl.lang.ParameterReadable" - }, - { - "type": "io.jsonwebtoken.impl.security.DefaultKeyOperationBuilder$Supplier", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "io.jsonwebtoken.impl.security.DefaultKeyOperationPolicyBuilder$Supplier", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "io.jsonwebtoken.impl.security.StandardEncryptionAlgorithms", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "io.jsonwebtoken.impl.security.StandardKeyAlgorithms", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "io.jsonwebtoken.impl.security.StandardKeyOperations", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "io.jsonwebtoken.impl.security.StandardSecureDigestAlgorithms", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "io.jsonwebtoken.jackson.io.JacksonDeserializer" - }, - { - "type": "io.jsonwebtoken.jackson.io.JacksonSerializer" - }, - { - "type": "io.jsonwebtoken.security.X509Accessor" - } - ], - "resources": [ - { - "glob": "META-INF/services/io.jsonwebtoken.io.Deserializer" - }, - { - "glob": "META-INF/services/io.jsonwebtoken.io.Serializer" - } - ] -} - diff --git a/server/src/main/resources/META-INF/native-image/reflect-config.json b/server/src/main/resources/META-INF/native-image/reflect-config.json deleted file mode 100644 index 4befff3..0000000 --- a/server/src/main/resources/META-INF/native-image/reflect-config.json +++ /dev/null @@ -1,665 +0,0 @@ -[ - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.MacSigner" - }, - "name": "com.sun.crypto.provider.HmacCore$HmacSHA256", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.MacSigner" - }, - "name": "com.sun.crypto.provider.HmacCore$HmacSHA384", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.MacSigner" - }, - "name": "com.sun.crypto.provider.HmacCore$HmacSHA512", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.MacProvider" - }, - "name": "com.sun.crypto.provider.KeyGeneratorCore$HmacKG$SHA256", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.MacProvider" - }, - "name": "com.sun.crypto.provider.KeyGeneratorCore$HmacKG$SHA384", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.MacProvider" - }, - "name": "com.sun.crypto.provider.KeyGeneratorCore$HmacKG$SHA512", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.DefaultJwtBuilder" - }, - "name": "io.jsonwebtoken.impl.DefaultClaims", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.DefaultJwtBuilder" - }, - "name": "io.jsonwebtoken.impl.DefaultJwsHeader", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.Jwts" - }, - "name": "io.jsonwebtoken.impl.DefaultJwtBuilder", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.Jwts" - }, - "name": "io.jsonwebtoken.impl.DefaultJwtParserBuilder", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.DefaultJwtParserBuilder" - }, - "name": "io.jsonwebtoken.impl.DefaultJwtParserBuilder$Supplier", - "allDeclaredConstructors": true, - "allPublicConstructors": true, - "allDeclaredMethods": true, - "allPublicMethods": true, - "allDeclaredFields": true, - "allPublicFields": true - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.DefaultJwtParserBuilder" - }, - "name": "io.jsonwebtoken.impl.DefaultJwtParser", - "allDeclaredConstructors": true, - "allPublicConstructors": true, - "allDeclaredMethods": true, - "allPublicMethods": true, - "allDeclaredFields": true, - "allPublicFields": true - }, - { - "name": "io.jsonwebtoken.impl.DefaultJwtParserBuilder$Supplier", - "allDeclaredConstructors": true, - "allPublicConstructors": true, - "allDeclaredMethods": true, - "allPublicMethods": true, - "allDeclaredFields": true, - "allPublicFields": true - }, - { - "name": "io.jsonwebtoken.impl.DefaultJwtParser", - "allDeclaredConstructors": true, - "allPublicConstructors": true, - "allDeclaredMethods": true, - "allPublicMethods": true, - "allDeclaredFields": true, - "allPublicFields": true - }, - { - "name": "io.jsonwebtoken.impl.DefaultJwtParserBuilder", - "allDeclaredConstructors": true, - "allPublicConstructors": true, - "allDeclaredMethods": true, - "allPublicMethods": true, - "allDeclaredFields": true, - "allPublicFields": true - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.CompressionCodecs" - }, - "name": "io.jsonwebtoken.impl.compression.DeflateCompressionCodec", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.CompressionCodecs" - }, - "name": "io.jsonwebtoken.impl.compression.GzipCompressionCodec", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.security.Keys" - }, - "name": "io.jsonwebtoken.impl.crypto.EllipticCurveProvider", - "methods": [ - { - "name": "generateKeyPair", - "parameterTypes": [ - "io.jsonwebtoken.SignatureAlgorithm" - ] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.security.Keys" - }, - "name": "io.jsonwebtoken.impl.crypto.MacProvider", - "methods": [ - { - "name": "generateKey", - "parameterTypes": [ - "io.jsonwebtoken.SignatureAlgorithm" - ] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.security.Keys" - }, - "name": "io.jsonwebtoken.impl.crypto.RsaProvider", - "methods": [ - { - "name": "generateKeyPair", - "parameterTypes": [ - "io.jsonwebtoken.SignatureAlgorithm" - ] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveProvider" - }, - "name": "java.security.AlgorithmParametersSpi" - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveSigner" - }, - "name": "java.security.SecureRandomParameters" - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.MacProvider" - }, - "name": "java.security.SecureRandomParameters" - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.SignatureProvider" - }, - "name": "java.security.SecureRandomParameters" - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveSigner" - }, - "name": "java.security.interfaces.ECPrivateKey" - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveSigner" - }, - "name": "java.security.interfaces.ECPublicKey" - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.RsaSigner" - }, - "name": "java.security.interfaces.RSAPrivateKey" - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.RsaSigner" - }, - "name": "java.security.interfaces.RSAPublicKey" - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.gson.io.GsonSerializer" - }, - "name": "java.sql.Date" - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveSigner" - }, - "name": "sun.security.provider.NativePRNG", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.MacProvider" - }, - "name": "sun.security.provider.NativePRNG", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.SignatureProvider" - }, - "name": "sun.security.provider.NativePRNG", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveSignatureValidator" - }, - "name": "sun.security.provider.SHA2$SHA256", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveSigner" - }, - "name": "sun.security.provider.SHA2$SHA256", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.MacSigner" - }, - "name": "sun.security.provider.SHA2$SHA256", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.RsaSignatureValidator" - }, - "name": "sun.security.provider.SHA2$SHA256", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.RsaSigner" - }, - "name": "sun.security.provider.SHA2$SHA256", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.SignatureProvider" - }, - "name": "sun.security.provider.SHA2$SHA256", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveSignatureValidator" - }, - "name": "sun.security.provider.SHA5$SHA384", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveSigner" - }, - "name": "sun.security.provider.SHA5$SHA384", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.MacSigner" - }, - "name": "sun.security.provider.SHA5$SHA384", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.RsaSignatureValidator" - }, - "name": "sun.security.provider.SHA5$SHA384", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.RsaSigner" - }, - "name": "sun.security.provider.SHA5$SHA384", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.SignatureProvider" - }, - "name": "sun.security.provider.SHA5$SHA384", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveSignatureValidator" - }, - "name": "sun.security.provider.SHA5$SHA512", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveSigner" - }, - "name": "sun.security.provider.SHA5$SHA512", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.MacSigner" - }, - "name": "sun.security.provider.SHA5$SHA512", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.RsaSignatureValidator" - }, - "name": "sun.security.provider.SHA5$SHA512", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.RsaSigner" - }, - "name": "sun.security.provider.SHA5$SHA512", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.SignatureProvider" - }, - "name": "sun.security.provider.SHA5$SHA512", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.RsaProvider" - }, - "name": "sun.security.rsa.RSAKeyPairGenerator$Legacy", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.RsaSignatureValidator" - }, - "name": "sun.security.rsa.RSASignature$SHA256withRSA", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.RsaSigner" - }, - "name": "sun.security.rsa.RSASignature$SHA256withRSA", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.RsaSignatureValidator" - }, - "name": "sun.security.rsa.RSASignature$SHA384withRSA", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.RsaSigner" - }, - "name": "sun.security.rsa.RSASignature$SHA384withRSA", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.RsaSignatureValidator" - }, - "name": "sun.security.rsa.RSASignature$SHA512withRSA", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.crypto.RsaSigner" - }, - "name": "sun.security.rsa.RSASignature$SHA512withRSA", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "name": "io.jsonwebtoken.impl.DefaultJwtHeaderBuilder$Supplier", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "name": "io.jsonwebtoken.impl.DefaultClaimsBuilder$Supplier", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "name": "io.jsonwebtoken.impl.security.DefaultKeyOperationBuilder$Supplier", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "name": "io.jsonwebtoken.impl.security.DefaultKeyOperationPolicyBuilder$Supplier", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - } -] \ No newline at end of file diff --git a/server/src/main/resources/META-INF/native-image/resource-config.json b/server/src/main/resources/META-INF/native-image/resource-config.json deleted file mode 100644 index bee9f23..0000000 --- a/server/src/main/resources/META-INF/native-image/resource-config.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "bundles": [], - "resources": { - "includes": [ - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.lang.Services" - }, - "pattern": "\\QMETA-INF/services/io.jsonwebtoken.CompressionCodec\\E" - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.DefaultJwtParserBuilder" - }, - "pattern": "\\QMETA-INF/services/io.jsonwebtoken.io.Deserializer\\E" - }, - { - "condition": { - "typeReachable": "io.jsonwebtoken.impl.lang.LegacyServices" - }, - "pattern": "\\QMETA-INF/services/io.jsonwebtoken.io.Serializer\\E" - } - ] - } -} diff --git a/server/src/main/resources/application-native.yml b/server/src/main/resources/application-native.yml new file mode 100644 index 0000000..e214b18 --- /dev/null +++ b/server/src/main/resources/application-native.yml @@ -0,0 +1,42 @@ +br: + com: + tasknote: + server: + jwt-secret: ${SECURITY_KEY:empty} + target-env: ${TARGET_ENV:development} +cors: + allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost} +logging: + level: + root: ${ROOT_LOG_LEVEL:INFO} + br.com.tasknoteapp: TRACE + +mailgun: + api-key: ${MAILGUN_APIKEY:abc123456} + domain: tasknoteapp.dev.br + sender-email: no-reply@tasknoteapp.dev.br + +server: + port: 8585 + error: + include-message: always + servlet: + context-path: ${SERVER_SERVLET_CONTEXT_PATH:/server} +spring: + application: + name: tasknote-api + datasource: + driver-class-name: org.postgresql.Driver + password: ${POSTGRES_PASSWORD:default} + url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5435}/${POSTGRES_DB:tasknote} + username: ${POSTGRES_USER:tasknoteuser} + flyway: + baseline-on-migrate: true + enabled: true + locations: classpath:db/migration + jpa: + database-platform: org.hibernate.dialect.PostgreSQLDialect + properties: + hibernate: + default_schema: tasknote + show-sql: true diff --git a/server/src/main/resources/application.yml b/server/src/main/resources/application.yml index 8e45721..e214b18 100644 --- a/server/src/main/resources/application.yml +++ b/server/src/main/resources/application.yml @@ -1,48 +1,42 @@ br: - com: - tasknote: - server: - jwt-secret: ${SECURITY_KEY:empty} - target-env: ${TARGET_ENV:development} - version: ${BUILD:local} + com: + tasknote: + server: + jwt-secret: ${SECURITY_KEY:empty} + target-env: ${TARGET_ENV:development} cors: - allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost} + allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost} logging: - level: - root: ${ROOT_LOG_LEVEL:INFO} + level: + root: ${ROOT_LOG_LEVEL:INFO} + br.com.tasknoteapp: TRACE mailgun: api-key: ${MAILGUN_APIKEY:abc123456} domain: tasknoteapp.dev.br sender-email: no-reply@tasknoteapp.dev.br -management: - endpoint: - health: - show-details: always server: - error: - include-message: always - port: 8585 - servlet: - context-path: ${SERVER_SERVLET_CONTEXT_PATH:/server} + port: 8585 + error: + include-message: always + servlet: + context-path: ${SERVER_SERVLET_CONTEXT_PATH:/server} spring: - application: - name: tasknote-api - datasource: - driver-class-name: org.postgresql.Driver - password: ${POSTGRES_PASSWORD:default} - url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5435}/${POSTGRES_DB:tasknote} - username: ${POSTGRES_USER:tasknoteuser} - flyway: - baseline-on-migrate: true - enabled: true - locations: classpath:db/migration - jpa: - database-platform: org.hibernate.dialect.PostgreSQLDialect - properties: - hibernate: - default_schema: tasknote - show-sql: true -springdoc: - enable-native-support: true + application: + name: tasknote-api + datasource: + driver-class-name: org.postgresql.Driver + password: ${POSTGRES_PASSWORD:default} + url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5435}/${POSTGRES_DB:tasknote} + username: ${POSTGRES_USER:tasknoteuser} + flyway: + baseline-on-migrate: true + enabled: true + locations: classpath:db/migration + jpa: + database-platform: org.hibernate.dialect.PostgreSQLDialect + properties: + hibernate: + default_schema: tasknote + show-sql: true diff --git a/server/src/test/java/br/com/tasknoteapp/server/service/impl/JwtServiceImplTest.java b/server/src/test/java/br/com/tasknoteapp/server/service/impl/JwtServiceImplTest.java index 093d3f4..49c2d3f 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/service/impl/JwtServiceImplTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/service/impl/JwtServiceImplTest.java @@ -65,7 +65,7 @@ class JwtServiceImplTest { Claims claims = extractClaims(token); String userIdClaim = String.valueOf(claims.get("userId")); - assertEquals(Long.parseLong(userIdClaim), testUserId); + assertEquals(userIdClaim.substring(0, 1), testUserId.toString()); assertEquals(claims.get("email"), testEmail); assertEquals(claims.get("name"), testName); diff --git a/tools/tools.md b/tools/tools.md index f981c6d..8e66604 100644 --- a/tools/tools.md +++ b/tools/tools.md @@ -151,3 +151,23 @@ Angular dependencies: - First run: `npx @angular/cli update @angular/cli @angular/core` - Then run `npx npm-check-updates -u` + +--- + +Build with: +mvn -Pnative -DskipTests spring-boot:build-image \ + -Dspring-boot.build-image.imageName=rmcampos/tasknote:api-latest + +Run with: +docker run --rm -p 8080:8080 \ + -e SPRING_PROFILES_ACTIVE=native \ + -e POSTGRES_HOST=localhost \ + -e POSTGRES_PORT=5432 \ + -e POSTGRES_DB=tasknote \ + -e POSTGRES_USER=tasknoteuser \ + -e POSTGRES_PASSWORD=default \ + -e SECURITY_KEY=9052e499446dac5fa2d69dd07f1f6381a360c646c63d555244c3a2911494f63a \ + -e CORS_ALLOWED_ORIGINS=http://localhost:5000 \ + -e SERVER_SERVLET_CONTEXT_PATH=/ \ + --network host \ + docker.io/rmcampos/tasknote:api-latest \ No newline at end of file