diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..3014572
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,14 @@
+# Copy this file to .env and fill in real values.
+# The .env file is excluded from version control.
+
+# Database
+DB_NAME=jobtracker
+DB_USERNAME=jobtracker
+DB_PASSWORD=change_me
+DB_ROOT_PASSWORD=change_me_root
+
+# JWT
+JWT_SECRET=replace_with_a_256_bit_random_string
+
+# Tracing (1.0 = 100 %; lower for production)
+TRACING_SAMPLING_PROBABILITY=1.0
diff --git a/.gitignore b/.gitignore
index e6e4424..021fc5d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,6 @@ target/
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
+
+# Environment variables – never commit secrets
+.env
diff --git a/Dockerfile b/Dockerfile
index 00c5015..994a491 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,21 +1,18 @@
# ============================================================
# Stage 1: Build
# ============================================================
-FROM eclipse-temurin:21-jdk-alpine AS build
-
-# Install Maven
-RUN apk add --no-cache maven
+FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /workspace
# Copy POM first to cache dependency layer
-COPY backend/pom.xml .
+COPY pom.xml .
# Download dependencies (cached unless pom.xml changes)
RUN mvn dependency:go-offline -B --no-transfer-progress
# Copy source code and build the fat JAR, skipping tests
-COPY backend/src src
+COPY src src
RUN mvn package -DskipTests -B --no-transfer-progress
# ============================================================
@@ -29,10 +26,7 @@ RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# Copy the built JAR from the build stage
-COPY --from=build /workspace/target/*.jar app.jar
-
-# Ensure the app directory is owned by the non-root user
-RUN chown -R appuser:appgroup /app
+COPY --from=build --chown=appuser:appgroup /workspace/target/*.jar app.jar
USER appuser
diff --git a/Dockerfile.orig b/Dockerfile.orig
deleted file mode 100644
index 06d3ddf..0000000
--- a/Dockerfile.orig
+++ /dev/null
@@ -1,52 +0,0 @@
-# ============================================================
-# Stage 1: Build
-# ============================================================
-FROM eclipse-temurin:21-jdk-alpine AS build
-
-# Install Maven
-RUN apk add --no-cache maven
-
-WORKDIR /workspace
-
-# Copy POM first to cache dependency layer
-COPY backend/pom.xml .
-
-# Download dependencies (cached unless pom.xml changes)
-RUN mvn dependency:go-offline -B --no-transfer-progress
-
-# Copy source code and build the fat JAR, skipping tests
-COPY backend/src src
-RUN mvn package -DskipTests -B --no-transfer-progress
-
-# ============================================================
-# Stage 2: Runtime
-# ============================================================
-FROM eclipse-temurin:21-jre-alpine AS runtime
-
-# Create a non-root user for security
-RUN addgroup -S appgroup && adduser -S appuser -G appgroup
-
-WORKDIR /app
-
-# Copy the built JAR from the build stage
-COPY --from=build /workspace/target/*.jar app.jar
-
-# Ensure the app directory is owned by the non-root user
-RUN chown -R appuser:appgroup /app
-
-USER appuser
-
-# Expose application port
-EXPOSE 8080
-
-# Health check – relies on Spring Actuator
-HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
- CMD wget -qO- http://localhost:8080/actuator/health || exit 1
-
-# JVM tuning flags for containers (respects cgroup memory limits)
-ENV JAVA_OPTS="-XX:+UseContainerSupport \
- -XX:MaxRAMPercentage=75.0 \
- -XX:+ExitOnOutOfMemoryError \
- -Djava.security.egd=file:/dev/./urandom"
-
-ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar app.jar"]
diff --git a/backend/src/test/java/com/jobtracker/JobTrackerApplicationTests.java b/backend/src/test/java/com/jobtracker/JobTrackerApplicationTests.java
new file mode 100644
index 0000000..2f16b81
--- /dev/null
+++ b/backend/src/test/java/com/jobtracker/JobTrackerApplicationTests.java
@@ -0,0 +1,26 @@
+package com.jobtracker;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.TestPropertySource;
+
+@SpringBootTest
+@TestPropertySource(properties = {
+ "spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1",
+ "spring.datasource.driver-class-name=org.h2.Driver",
+ "spring.datasource.username=sa",
+ "spring.datasource.password=",
+ "spring.jpa.hibernate.ddl-auto=create-drop",
+ "spring.flyway.enabled=false",
+ "jwt.secret=TestSecretKeyThatIsAtLeast256BitsLongForTestingPurposesOnly",
+ "jwt.access-token-expiration-ms=900000",
+ "jwt.refresh-token-expiration-ms=604800000",
+ "cors.allowed-origins=http://localhost:3000",
+ "management.tracing.sampling.probability=0.0"
+})
+class JobTrackerApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index 037aefc..abb7187 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -11,15 +11,18 @@ services:
ports:
- "8080:8080"
environment:
- SPRING_DATASOURCE_URL: jdbc:mariadb://mariadb:3306/jobtracker?createDatabaseIfNotExist=true&characterEncoding=UTF-8&useUnicode=true
- SPRING_DATASOURCE_USERNAME: root
- SPRING_DATASOURCE_PASSWORD: root
- # WARNING: Replace this secret before any production use. This value is for development only.
- JWT_SECRET: ChangeThisToAVeryLongSecretKeyForJWTTokensInDevelopment
- CORS_ALLOWED_ORIGINS: "http://localhost:3000,http://localhost:5173"
+ DB_URL: jdbc:mariadb://mariadb:3306/${DB_NAME:-jobtracker}?createDatabaseIfNotExist=true&characterEncoding=UTF-8&useUnicode=true
+ DB_USERNAME: ${DB_USERNAME:-jobtracker}
+ DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required}
+ JWT_SECRET: ${JWT_SECRET:?JWT_SECRET is required}
+ CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-"http://localhost:3000,http://localhost:5173"}
+ OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4317
+ TRACING_SAMPLING_PROBABILITY: ${TRACING_SAMPLING_PROBABILITY:-1.0}
depends_on:
mariadb:
condition: service_healthy
+ jaeger:
+ condition: service_started
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8081/actuator/health"]
interval: 30s
@@ -37,8 +40,10 @@ services:
ports:
- "3306:3306"
environment:
- MARIADB_ROOT_PASSWORD: root
- MARIADB_DATABASE: jobtracker
+ MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:?DB_ROOT_PASSWORD is required}
+ MARIADB_DATABASE: ${DB_NAME:-jobtracker}
+ MARIADB_USER: ${DB_USERNAME:-jobtracker}
+ MARIADB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required}
volumes:
- mariadb_data:/var/lib/mysql
healthcheck:
@@ -51,6 +56,20 @@ services:
- job-tracker-net
restart: unless-stopped
+ # ── Jaeger (distributed tracing) ─────────────────────────
+ jaeger:
+ image: jaegertracing/all-in-one:1.57.0
+ container_name: job-tracker-jaeger
+ ports:
+ - "16686:16686" # Jaeger UI
+ - "4317:4317" # OTLP gRPC receiver
+ - "4318:4318" # OTLP HTTP receiver
+ environment:
+ - COLLECTOR_OTLP_ENABLED=true
+ networks:
+ - job-tracker-net
+ restart: unless-stopped
+
# ── Prometheus ────────────────────────────────────────────
prometheus:
image: prom/prometheus:v2.54.1
@@ -82,8 +101,8 @@ services:
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_USER: admin
- GF_SECURITY_ADMIN_PASSWORD: admin
+ GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
+ GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin}
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
@@ -104,3 +123,4 @@ volumes:
networks:
job-tracker-net:
driver: bridge
+
diff --git a/docker-compose.yml.orig b/docker-compose.yml.orig
deleted file mode 100644
index 5600e15..0000000
--- a/docker-compose.yml.orig
+++ /dev/null
@@ -1,99 +0,0 @@
-version: "3.9"
-
-services:
-
- # ── Spring Boot application ───────────────────────────────
- app:
- build:
- context: .
- dockerfile: Dockerfile
- container_name: job-tracker-app
- ports:
- - "8080:8080"
- environment:
- SPRING_DATASOURCE_URL: jdbc:mariadb://mariadb:3306/jobtracker?createDatabaseIfNotExist=true&characterEncoding=UTF-8&useUnicode=true
- SPRING_DATASOURCE_USERNAME: root
- SPRING_DATASOURCE_PASSWORD: root
- # WARNING: Replace this secret before any production use. This value is for development only.
- JWT_SECRET: ChangeThisToAVeryLongSecretKeyForJWTTokensInDevelopment
- CORS_ALLOWED_ORIGINS: "http://localhost:3000,http://localhost:5173"
- depends_on:
- mariadb:
- condition: service_healthy
- healthcheck:
- test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
- interval: 30s
- timeout: 10s
- retries: 5
- start_period: 60s
- networks:
- - job-tracker-net
- restart: unless-stopped
-
- # ── MariaDB ───────────────────────────────────────────────
- mariadb:
- image: mariadb:11
- container_name: job-tracker-mariadb
- ports:
- - "3306:3306"
- environment:
- MARIADB_ROOT_PASSWORD: root
- MARIADB_DATABASE: jobtracker
- volumes:
- - mariadb_data:/var/lib/mysql
- healthcheck:
- test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
- interval: 10s
- timeout: 5s
- retries: 5
- start_period: 30s
- networks:
- - job-tracker-net
- restart: unless-stopped
-
- # ── Prometheus ────────────────────────────────────────────
- prometheus:
- image: prom/prometheus:latest
- container_name: job-tracker-prometheus
- ports:
- - "9090:9090"
- volumes:
- - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- - prometheus_data:/prometheus
- command:
- - "--config.file=/etc/prometheus/prometheus.yml"
- - "--storage.tsdb.path=/prometheus"
- - "--web.console.libraries=/usr/share/prometheus/console_libraries"
- - "--web.console.templates=/usr/share/prometheus/consoles"
- networks:
- - job-tracker-net
- restart: unless-stopped
-
- # ── Grafana ───────────────────────────────────────────────
- grafana:
- image: grafana/grafana:latest
- container_name: job-tracker-grafana
- ports:
- - "3000:3000"
- environment:
- GF_SECURITY_ADMIN_USER: admin
- GF_SECURITY_ADMIN_PASSWORD: admin
- volumes:
- - grafana_data:/var/lib/grafana
- - ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
- - ./grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards:ro
- - ./grafana/dashboards:/etc/grafana/dashboards:ro
- depends_on:
- - prometheus
- networks:
- - job-tracker-net
- restart: unless-stopped
-
-volumes:
- mariadb_data:
- prometheus_data:
- grafana_data:
-
-networks:
- job-tracker-net:
- driver: bridge
diff --git a/docker-compose_BACKUP_20088.yml b/docker-compose_BACKUP_20088.yml
deleted file mode 100644
index 5600e15..0000000
--- a/docker-compose_BACKUP_20088.yml
+++ /dev/null
@@ -1,99 +0,0 @@
-version: "3.9"
-
-services:
-
- # ── Spring Boot application ───────────────────────────────
- app:
- build:
- context: .
- dockerfile: Dockerfile
- container_name: job-tracker-app
- ports:
- - "8080:8080"
- environment:
- SPRING_DATASOURCE_URL: jdbc:mariadb://mariadb:3306/jobtracker?createDatabaseIfNotExist=true&characterEncoding=UTF-8&useUnicode=true
- SPRING_DATASOURCE_USERNAME: root
- SPRING_DATASOURCE_PASSWORD: root
- # WARNING: Replace this secret before any production use. This value is for development only.
- JWT_SECRET: ChangeThisToAVeryLongSecretKeyForJWTTokensInDevelopment
- CORS_ALLOWED_ORIGINS: "http://localhost:3000,http://localhost:5173"
- depends_on:
- mariadb:
- condition: service_healthy
- healthcheck:
- test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
- interval: 30s
- timeout: 10s
- retries: 5
- start_period: 60s
- networks:
- - job-tracker-net
- restart: unless-stopped
-
- # ── MariaDB ───────────────────────────────────────────────
- mariadb:
- image: mariadb:11
- container_name: job-tracker-mariadb
- ports:
- - "3306:3306"
- environment:
- MARIADB_ROOT_PASSWORD: root
- MARIADB_DATABASE: jobtracker
- volumes:
- - mariadb_data:/var/lib/mysql
- healthcheck:
- test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
- interval: 10s
- timeout: 5s
- retries: 5
- start_period: 30s
- networks:
- - job-tracker-net
- restart: unless-stopped
-
- # ── Prometheus ────────────────────────────────────────────
- prometheus:
- image: prom/prometheus:latest
- container_name: job-tracker-prometheus
- ports:
- - "9090:9090"
- volumes:
- - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- - prometheus_data:/prometheus
- command:
- - "--config.file=/etc/prometheus/prometheus.yml"
- - "--storage.tsdb.path=/prometheus"
- - "--web.console.libraries=/usr/share/prometheus/console_libraries"
- - "--web.console.templates=/usr/share/prometheus/consoles"
- networks:
- - job-tracker-net
- restart: unless-stopped
-
- # ── Grafana ───────────────────────────────────────────────
- grafana:
- image: grafana/grafana:latest
- container_name: job-tracker-grafana
- ports:
- - "3000:3000"
- environment:
- GF_SECURITY_ADMIN_USER: admin
- GF_SECURITY_ADMIN_PASSWORD: admin
- volumes:
- - grafana_data:/var/lib/grafana
- - ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
- - ./grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards:ro
- - ./grafana/dashboards:/etc/grafana/dashboards:ro
- depends_on:
- - prometheus
- networks:
- - job-tracker-net
- restart: unless-stopped
-
-volumes:
- mariadb_data:
- prometheus_data:
- grafana_data:
-
-networks:
- job-tracker-net:
- driver: bridge
diff --git a/docker-compose_BASE_20088.yml b/docker-compose_BASE_20088.yml
deleted file mode 100644
index e69de29..0000000
diff --git a/docker-compose_LOCAL_20088.yml b/docker-compose_LOCAL_20088.yml
deleted file mode 100644
index 5600e15..0000000
--- a/docker-compose_LOCAL_20088.yml
+++ /dev/null
@@ -1,99 +0,0 @@
-version: "3.9"
-
-services:
-
- # ── Spring Boot application ───────────────────────────────
- app:
- build:
- context: .
- dockerfile: Dockerfile
- container_name: job-tracker-app
- ports:
- - "8080:8080"
- environment:
- SPRING_DATASOURCE_URL: jdbc:mariadb://mariadb:3306/jobtracker?createDatabaseIfNotExist=true&characterEncoding=UTF-8&useUnicode=true
- SPRING_DATASOURCE_USERNAME: root
- SPRING_DATASOURCE_PASSWORD: root
- # WARNING: Replace this secret before any production use. This value is for development only.
- JWT_SECRET: ChangeThisToAVeryLongSecretKeyForJWTTokensInDevelopment
- CORS_ALLOWED_ORIGINS: "http://localhost:3000,http://localhost:5173"
- depends_on:
- mariadb:
- condition: service_healthy
- healthcheck:
- test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
- interval: 30s
- timeout: 10s
- retries: 5
- start_period: 60s
- networks:
- - job-tracker-net
- restart: unless-stopped
-
- # ── MariaDB ───────────────────────────────────────────────
- mariadb:
- image: mariadb:11
- container_name: job-tracker-mariadb
- ports:
- - "3306:3306"
- environment:
- MARIADB_ROOT_PASSWORD: root
- MARIADB_DATABASE: jobtracker
- volumes:
- - mariadb_data:/var/lib/mysql
- healthcheck:
- test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
- interval: 10s
- timeout: 5s
- retries: 5
- start_period: 30s
- networks:
- - job-tracker-net
- restart: unless-stopped
-
- # ── Prometheus ────────────────────────────────────────────
- prometheus:
- image: prom/prometheus:latest
- container_name: job-tracker-prometheus
- ports:
- - "9090:9090"
- volumes:
- - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- - prometheus_data:/prometheus
- command:
- - "--config.file=/etc/prometheus/prometheus.yml"
- - "--storage.tsdb.path=/prometheus"
- - "--web.console.libraries=/usr/share/prometheus/console_libraries"
- - "--web.console.templates=/usr/share/prometheus/consoles"
- networks:
- - job-tracker-net
- restart: unless-stopped
-
- # ── Grafana ───────────────────────────────────────────────
- grafana:
- image: grafana/grafana:latest
- container_name: job-tracker-grafana
- ports:
- - "3000:3000"
- environment:
- GF_SECURITY_ADMIN_USER: admin
- GF_SECURITY_ADMIN_PASSWORD: admin
- volumes:
- - grafana_data:/var/lib/grafana
- - ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
- - ./grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards:ro
- - ./grafana/dashboards:/etc/grafana/dashboards:ro
- depends_on:
- - prometheus
- networks:
- - job-tracker-net
- restart: unless-stopped
-
-volumes:
- mariadb_data:
- prometheus_data:
- grafana_data:
-
-networks:
- job-tracker-net:
- driver: bridge
diff --git a/docker-compose_REMOTE_20088.yml b/docker-compose_REMOTE_20088.yml
deleted file mode 100644
index bdf6d43..0000000
--- a/docker-compose_REMOTE_20088.yml
+++ /dev/null
@@ -1,41 +0,0 @@
-version: '3.8'
-
-services:
- db:
- image: mariadb:11.2
- container_name: jobtracker-db
- environment:
- MYSQL_ROOT_PASSWORD: root
- MYSQL_DATABASE: jobtracker
- MYSQL_USER: jobtracker
- MYSQL_PASSWORD: jobtracker
- ports:
- - "3306:3306"
- volumes:
- - mariadb_data:/var/lib/mysql
- healthcheck:
- test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- app:
- build: .
- container_name: jobtracker-app
- ports:
- - "8080:8080"
- environment:
- DB_URL: jdbc:mariadb://db:3306/jobtracker?createDatabaseIfNotExist=true&characterEncoding=UTF-8&useUnicode=true
- DB_USERNAME: jobtracker
- DB_PASSWORD: jobtracker
- JWT_SECRET: ${JWT_SECRET:-ChangeThisToASecureRandomSecretKeyInProductionAtLeast256BitsLong}
- JWT_ACCESS_TOKEN_EXPIRATION_MS: 900000
- JWT_REFRESH_TOKEN_EXPIRATION_MS: 604800000
- CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost:3000}
- depends_on:
- db:
- condition: service_healthy
- restart: unless-stopped
-
-volumes:
- mariadb_data:
diff --git a/pom.xml b/pom.xml
index cb7c049..cd5b9bb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -92,6 +92,18 @@
spring-boot-starter-actuator
+
+
+ io.micrometer
+ micrometer-tracing-bridge-otel
+
+
+
+
+ io.opentelemetry
+ opentelemetry-exporter-otlp
+
+
io.micrometer
@@ -104,7 +116,6 @@
logstash-logback-encoder
7.4
-
org.springframework.boot
diff --git a/src/main/java/com/jobtracker/config/RequestLoggingFilter.java.orig b/src/main/java/com/jobtracker/config/RequestLoggingFilter.java.orig
deleted file mode 100644
index d270a3a..0000000
--- a/src/main/java/com/jobtracker/config/RequestLoggingFilter.java.orig
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.jobtracker.config;
-
-import jakarta.servlet.FilterChain;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.lang.NonNull;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.stereotype.Component;
-import org.springframework.web.filter.OncePerRequestFilter;
-
-import java.io.IOException;
-
-@Component
-public class RequestLoggingFilter extends OncePerRequestFilter {
-
- private static final Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class);
-
- @Override
- protected void doFilterInternal(@NonNull HttpServletRequest request,
- @NonNull HttpServletResponse response,
- @NonNull FilterChain filterChain) throws ServletException, IOException {
- long start = System.currentTimeMillis();
- try {
- filterChain.doFilter(request, response);
- } finally {
- long duration = System.currentTimeMillis() - start;
- String userId = resolveUserId();
- log.info("method={} path={} status={} duration={}ms userId={}",
- request.getMethod(),
- request.getRequestURI(),
- response.getStatus(),
- duration,
- userId);
- }
- }
-
- private String resolveUserId() {
- Authentication auth = SecurityContextHolder.getContext().getAuthentication();
- if (auth != null && auth.isAuthenticated() && !"anonymousUser".equals(auth.getPrincipal())) {
- return auth.getName();
- }
- return "anonymous";
- }
-}
diff --git a/src/main/java/com/jobtracker/exception/GlobalExceptionHandler.java.orig b/src/main/java/com/jobtracker/exception/GlobalExceptionHandler.java.orig
deleted file mode 100644
index 0d4b520..0000000
--- a/src/main/java/com/jobtracker/exception/GlobalExceptionHandler.java.orig
+++ /dev/null
@@ -1,81 +0,0 @@
-package com.jobtracker.exception;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.validation.FieldError;
-import org.springframework.web.bind.MethodArgumentNotValidException;
-import org.springframework.web.bind.annotation.ExceptionHandler;
-import org.springframework.web.bind.annotation.RestControllerAdvice;
-
-import java.time.LocalDateTime;
-import java.util.HashMap;
-import java.util.Map;
-
-@RestControllerAdvice
-public class GlobalExceptionHandler {
-
- private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
-
- @ExceptionHandler(ResourceNotFoundException.class)
- public ResponseEntity