feat: upgrade to spring boot 3.5.9

This commit is contained in:
2026-01-17 15:48:46 -03:00
parent 2e24cf06ed
commit a305a95970
56 changed files with 402 additions and 1796 deletions
+2
View File
@@ -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: |
+2 -3
View File
@@ -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
+1 -1
View File
@@ -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:
+13 -3
View File
@@ -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
-45
View File
@@ -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 <ricardompcampos@gmail.com>" \
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"]
-4
View File
@@ -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"]
+33 -106
View File
@@ -11,7 +11,7 @@
<groupId>br.com.tasknoteapp</groupId>
<artifactId>server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<version>2.0.0</version>
<name>tasknote-api</name>
<description>Java backend REST API to serve TaskNote frontend client</description>
@@ -27,6 +27,13 @@
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<!-- Properties -->
<properties>
@@ -42,9 +49,6 @@
<jacoco.output.data>${project.build.directory}/coverage-reports</jacoco.output.data>
<timestamp>${maven.build.timestamp}</timestamp>
<maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format>
<hibernate.version>6.6.34.Final</hibernate.version>
<sonar.organization>ricardo-campos-org</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
</properties>
<!-- Profiles -->
@@ -68,34 +72,6 @@
<build.profile.id>prod</build.profile.id>
</properties>
</profile>
<!-- Native -->
<profile>
<id>native</id>
<properties>
<build.profile.id>native</build.profile.id>
<skip.integration.tests>true</skip.integration.tests>
<skip.unit.tests>true</skip.unit.tests>
<jacoco.skip>true</jacoco.skip>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<!-- Dependencies -->
@@ -119,20 +95,6 @@
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- Devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- OOps & Tools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Database -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -141,6 +103,7 @@
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
@@ -168,28 +131,21 @@
<scope>test</scope>
</dependency>
<!-- Documentation -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.13</version>
</dependency>
<!-- Authentication -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.13.0</version>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.13.0</version>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.13.0</version>
<artifactId>jjwt-gson</artifactId>
<version>0.12.6</version>
</dependency>
</dependencies>
@@ -357,55 +313,6 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.12.0</version>
<configuration>
<source>17</source>
<doctitle>Javadoc Documentation for ${project.name} ${project.version}</doctitle>
<windowtitle>${project.name} ${project.version}</windowtitle>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.6.2</version>
<executions>
<execution>
<id>default-cli</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<dependencyConvergence/>
<requireMavenVersion>
<version>[3.2,)</version>
<message>Invalid Maven version. It should be at least 3.2</message>
</requireMavenVersion>
<requireJavaVersion>
<version>17</version>
<message>Invalid Java Version. It should be at least 1.8</message>
</requireJavaVersion>
<requireNoRepositories>
<allowedRepositories>
<id>central</id>
</allowedRepositories>
<allowedPluginRepositories>
<id>central</id>
</allowedPluginRepositories>
</requireNoRepositories>
<requireReleaseDeps>
<message>No Snapshots Allowed in releases!</message>
<onlyWhenRelease>true</onlyWhenRelease>
</requireReleaseDeps>
<banDuplicatePomDependencyVersions/>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
@@ -437,6 +344,26 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
<configuration>
<additionalProperties>
<timestamp>${maven.build.timestamp}</timestamp>
</additionalProperties>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<finalName>tasknote-api</finalName>
@@ -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());
}
}
@@ -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();
}
}
@@ -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,
})
@@ -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("/**")
@@ -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;
}
}
@@ -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<Void> 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<UserResponseWithToken> 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<Void> 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<Void> 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<Void> 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<Void> completePasswordReset(
@RequestBody @Valid PasswordResetRequest request) {
authService.confirmResetPasswordForUser(request);
@@ -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<Map<String, Object>> health() {
Map<String, Object> health = new HashMap<>();
Map<String, String> 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<String, String> checkDatabase() {
Map<String, String> 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;
}
}
@@ -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<String> getTasksTags() {
return homeService.getTopTasksTag();
}
@@ -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<NoteResponse> 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<NoteResponse> 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<NoteResponse> 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<NoteResponse> 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<Void> 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<Void> deleteNote(@NonNull @PathVariable Long id) {
noteService.deleteNote(id);
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
@@ -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<TaskResponse> 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<TaskResponse> 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<TaskResponse> postTasks(
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "Task data to be created, including optionally its urls.",
required = true)
@RequestBody
@Valid
TaskRequest taskRequest) {
public ResponseEntity<TaskResponse> 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<Void> 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<Void> deleteTask(@NonNull @PathVariable Long id) {
taskService.deleteTask(id);
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
@@ -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<UserResponse> 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<UserResponse> 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);
}
@@ -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<UserResponse> deleteAccount() {
UserResponse deleted = userSessionService.deleteCurrentUserAccount();
return ResponseEntity.ok(deleted);
@@ -90,7 +90,6 @@ public class UserEntity implements UserDetails {
return true;
}
// TODO: generate all Getters and Setters
public Long getId() {
return id;
}
@@ -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);
}
}
@@ -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;
@@ -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);
}
}
}
@@ -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) {}
@@ -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() {}
@@ -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) {}
@@ -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) {}
@@ -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) {}
@@ -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) {}
@@ -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() {}
@@ -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<String> 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<String> urls,
String dueDate,
Boolean highPriority,
String tag) {}
@@ -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<String> 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<String> urls,
String dueDate,
Boolean highPriority,
String tag) {}
@@ -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) {}
@@ -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) {}
@@ -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) {}
@@ -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) {}
@@ -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.
@@ -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) {}
@@ -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<String> urls) {
Long id,
String description,
Boolean done,
Boolean highPriority,
LocalDate dueDate,
String dueDateFmt,
String lastUpdate,
String tag,
List<String> urls) {
/**
* Creates a TaskResponse given a TaskEntity and its Urls.
@@ -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}.
@@ -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}.
@@ -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<FieldIssueResponse> fields;
/**
@@ -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();
}
}
@@ -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<UserResponse> getAllUsers() {
Optional<String> 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<UserEntity> 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<UserEntity> users = userRepository.findAll();
List<UserResponse> 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<UserPwdLimitEntity> 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();
}
}
@@ -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;
@@ -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<MultiValueMap<String, String>> 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());
}
}
@@ -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);
}
@@ -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<TaskUrlEntity> urlsToDelete = taskUrlRepository.findAllById_taskId(taskId);
TaskEntity taskEntity = task.get();
List<TaskUrlEntity> 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());
}
}
@@ -59,12 +59,18 @@ public class UserSessionService {
List<TaskResponse> 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<NoteResponse> 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();
@@ -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": "<init>",
"parameterTypes": []
}
]
},
{
"type": "io.jsonwebtoken.impl.DefaultHeader",
"allDeclaredFields": true
},
{
"type": "io.jsonwebtoken.impl.DefaultJwsHeader",
"allDeclaredFields": true
},
{
"type": "io.jsonwebtoken.impl.DefaultJwtBuilder$Supplier",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"type": "io.jsonwebtoken.impl.DefaultJwtHeaderBuilder$Supplier",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"type": "io.jsonwebtoken.impl.DefaultJwtParserBuilder$Supplier",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"type": "io.jsonwebtoken.impl.DefaultProtectedHeader",
"allDeclaredFields": true
},
{
"type": "io.jsonwebtoken.impl.ParameterMap",
"allDeclaredFields": true
},
{
"type": "io.jsonwebtoken.impl.io.StandardCompressionAlgorithms",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"type": "io.jsonwebtoken.impl.lang.Nameable"
},
{
"type": "io.jsonwebtoken.impl.lang.ParameterReadable"
},
{
"type": "io.jsonwebtoken.impl.security.DefaultKeyOperationBuilder$Supplier",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"type": "io.jsonwebtoken.impl.security.DefaultKeyOperationPolicyBuilder$Supplier",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"type": "io.jsonwebtoken.impl.security.StandardEncryptionAlgorithms",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"type": "io.jsonwebtoken.impl.security.StandardKeyAlgorithms",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"type": "io.jsonwebtoken.impl.security.StandardKeyOperations",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"type": "io.jsonwebtoken.impl.security.StandardSecureDigestAlgorithms",
"methods": [
{
"name": "<init>",
"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"
}
]
}
@@ -1,665 +0,0 @@
[
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.MacSigner"
},
"name": "com.sun.crypto.provider.HmacCore$HmacSHA256",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.MacSigner"
},
"name": "com.sun.crypto.provider.HmacCore$HmacSHA384",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.MacSigner"
},
"name": "com.sun.crypto.provider.HmacCore$HmacSHA512",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.MacProvider"
},
"name": "com.sun.crypto.provider.KeyGeneratorCore$HmacKG$SHA256",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.MacProvider"
},
"name": "com.sun.crypto.provider.KeyGeneratorCore$HmacKG$SHA384",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.MacProvider"
},
"name": "com.sun.crypto.provider.KeyGeneratorCore$HmacKG$SHA512",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.DefaultJwtBuilder"
},
"name": "io.jsonwebtoken.impl.DefaultClaims",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.DefaultJwtBuilder"
},
"name": "io.jsonwebtoken.impl.DefaultJwsHeader",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.Jwts"
},
"name": "io.jsonwebtoken.impl.DefaultJwtBuilder",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.Jwts"
},
"name": "io.jsonwebtoken.impl.DefaultJwtParserBuilder",
"methods": [
{
"name": "<init>",
"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": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.CompressionCodecs"
},
"name": "io.jsonwebtoken.impl.compression.GzipCompressionCodec",
"methods": [
{
"name": "<init>",
"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": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.MacProvider"
},
"name": "sun.security.provider.NativePRNG",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.SignatureProvider"
},
"name": "sun.security.provider.NativePRNG",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveSignatureValidator"
},
"name": "sun.security.provider.SHA2$SHA256",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveSigner"
},
"name": "sun.security.provider.SHA2$SHA256",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.MacSigner"
},
"name": "sun.security.provider.SHA2$SHA256",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.RsaSignatureValidator"
},
"name": "sun.security.provider.SHA2$SHA256",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.RsaSigner"
},
"name": "sun.security.provider.SHA2$SHA256",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.SignatureProvider"
},
"name": "sun.security.provider.SHA2$SHA256",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveSignatureValidator"
},
"name": "sun.security.provider.SHA5$SHA384",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveSigner"
},
"name": "sun.security.provider.SHA5$SHA384",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.MacSigner"
},
"name": "sun.security.provider.SHA5$SHA384",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.RsaSignatureValidator"
},
"name": "sun.security.provider.SHA5$SHA384",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.RsaSigner"
},
"name": "sun.security.provider.SHA5$SHA384",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.SignatureProvider"
},
"name": "sun.security.provider.SHA5$SHA384",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveSignatureValidator"
},
"name": "sun.security.provider.SHA5$SHA512",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.EllipticCurveSigner"
},
"name": "sun.security.provider.SHA5$SHA512",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.MacSigner"
},
"name": "sun.security.provider.SHA5$SHA512",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.RsaSignatureValidator"
},
"name": "sun.security.provider.SHA5$SHA512",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.RsaSigner"
},
"name": "sun.security.provider.SHA5$SHA512",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.SignatureProvider"
},
"name": "sun.security.provider.SHA5$SHA512",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.RsaProvider"
},
"name": "sun.security.rsa.RSAKeyPairGenerator$Legacy",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.RsaSignatureValidator"
},
"name": "sun.security.rsa.RSASignature$SHA256withRSA",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.RsaSigner"
},
"name": "sun.security.rsa.RSASignature$SHA256withRSA",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.RsaSignatureValidator"
},
"name": "sun.security.rsa.RSASignature$SHA384withRSA",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.RsaSigner"
},
"name": "sun.security.rsa.RSASignature$SHA384withRSA",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.RsaSignatureValidator"
},
"name": "sun.security.rsa.RSASignature$SHA512withRSA",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"condition": {
"typeReachable": "io.jsonwebtoken.impl.crypto.RsaSigner"
},
"name": "sun.security.rsa.RSASignature$SHA512withRSA",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"name": "io.jsonwebtoken.impl.DefaultJwtHeaderBuilder$Supplier",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"name": "io.jsonwebtoken.impl.DefaultClaimsBuilder$Supplier",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"name": "io.jsonwebtoken.impl.security.DefaultKeyOperationBuilder$Supplier",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"name": "io.jsonwebtoken.impl.security.DefaultKeyOperationPolicyBuilder$Supplier",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
}
]
@@ -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"
}
]
}
}
@@ -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
+31 -37
View File
@@ -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
@@ -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);
+20
View File
@@ -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