diff --git a/.env b/.env new file mode 100644 index 0000000..1133522 --- /dev/null +++ b/.env @@ -0,0 +1,10 @@ +POSTGRES_DB=no_copy_db +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_PORT=5432 +POSTGRES_HOST=postgres + +REDIS_HOST=redis +REDIS_PORT=6379 + +SERVER_PORT=8080 diff --git a/Dockerfile b/Dockerfile index e8b06f0..8242e14 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,17 @@ -FROM mcr.microsoft.com/openjdk/jdk:17.0.13-ubuntu +FROM gradle:8.14.2-jdk21 AS build WORKDIR /app -COPY build/libs/app.jar /app/app.jar +COPY . . -ENV SERVER_PORT=8080 -ENV DB_HOST=mysql -ENV DB_PORT=3306 -ENV DB_NAME=no-copy-db -ENV DB_USERNAME=mysql_user -ENV DB_PASSWORD=mysql_pass -ENV REDIS_HOST=redis -ENV REDIS_PORT=6379 +RUN gradle --no-daemon clean build -x test + +FROM eclipse-temurin:21-jre + +WORKDIR /app + +COPY --from=build /app/build/libs/*.jar app.jar EXPOSE 8080 -ENTRYPOINT ["java", "-jar", "/app/app.jar"] +CMD ["java", "-jar", "app.jar"] diff --git a/build.gradle b/build.gradle index 6af1b68..4e63b11 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,11 @@ plugins { id 'java' - id 'org.springframework.boot' version '4.0.0' + id 'org.springframework.boot' version '3.5.6' id 'io.spring.dependency-management' version '1.1.7' } group = 'ru.soune' version = '0.0.1-SNAPSHOT' -description = 'Demo project for Spring Boot' java { toolchain { @@ -27,16 +26,16 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-webmvc' + implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.security:spring-security-crypto:6.5.3' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.microsoft.sqlserver:mssql-jdbc' runtimeOnly 'com.mysql:mysql-connector-j' + runtimeOnly 'org.postgresql:postgresql' annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test' - testImplementation 'org.springframework.boot:spring-boot-starter-validation-test' - testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.mockito:mockito-core:5.3.1' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } diff --git a/docker-compose.yaml b/docker-compose.yaml index c875d80..69f9587 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,52 +1,41 @@ -version: "3.8" +version: '3.9' services: - mysql: - image: mysql:8.1 - container_name: no-copy-mysql-db + db: + image: postgres:17 + restart: always environment: - MYSQL_ROOT_PASSWORD: root_pass - MYSQL_DATABASE: no-copy-db - MYSQL_USER: mysql_user - MYSQL_PASSWORD: mysql_pass + POSTGRES_DB: no_copy_db + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_PORT: 5432 + POSTGRES_HOST: postgres + PGDATA: /data/postgres ports: - - "3306:3306" + - "5432:5432" volumes: - - mysql_data:/var/lib/mysql - networks: - - app-network - command: --default-authentication-plugin=mysql_native_password + - pgdata:/var/lib/postgresql/data + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U postgres -p 5432" ] + interval: 10s + timeout: 5s + retries: 20 + container_name: postgres - redis: - image: redis:7 - container_name: no-copy-redis - ports: - - "6379:6379" - networks: - - app-network - - no-copy: + app: build: . - container_name: no-copy-service + container_name: no_copy_app + environment: + POSTGRES_DB: no_copy_db + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_PORT: 5432 + POSTGRES_HOST: postgres + depends_on: + db: + condition: service_healthy ports: - "8080:8080" - environment: - DB_HOST: mysql - DB_PORT: 3306 - DB_NAME: no-copy-db - DB_USERNAME: mysql_user - DB_PASSWORD: mysql_pass - SERVER_PORT: 8080 - REDIS_HOST: redis - REDIS_PORT: 6379 - depends_on: - - mysql - - redis - networks: - - app-network volumes: - mysql_data: - -networks: - app-network: + pgdata: diff --git a/src/main/java/ru/soune/no_copy/controller/AuthController.java b/src/main/java/ru/soune/no_copy/controller/AuthController.java index 143659f..c88cfb6 100644 --- a/src/main/java/ru/soune/no_copy/controller/AuthController.java +++ b/src/main/java/ru/soune/no_copy/controller/AuthController.java @@ -13,7 +13,7 @@ import ru.soune.no_copy.entity.AuthToken; import ru.soune.no_copy.service.AuthService; @RestController -@RequestMapping("/v1/api/auth") +@RequestMapping("/api/auth") @RequiredArgsConstructor public class AuthController { diff --git a/src/main/java/ru/soune/no_copy/handler/GlobalExceptionHandler.java b/src/main/java/ru/soune/no_copy/handler/GlobalExceptionHandler.java index 0380ccc..daa36ee 100644 --- a/src/main/java/ru/soune/no_copy/handler/GlobalExceptionHandler.java +++ b/src/main/java/ru/soune/no_copy/handler/GlobalExceptionHandler.java @@ -1,7 +1,6 @@ package ru.soune.no_copy.handler; import lombok.AllArgsConstructor; -import org.springframework.context.MessageSource; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/ru/soune/no_copy/service/AuthService.java b/src/main/java/ru/soune/no_copy/service/AuthService.java index 968a777..431be40 100644 --- a/src/main/java/ru/soune/no_copy/service/AuthService.java +++ b/src/main/java/ru/soune/no_copy/service/AuthService.java @@ -13,7 +13,6 @@ import ru.soune.no_copy.repository.AuthTokenRepository; import ru.soune.no_copy.repository.UserRepository; import java.security.SecureRandom; -import java.time.LocalDateTime; import java.util.HexFormat; import java.util.Locale; diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index bb04d87..2b84ef5 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,9 +1,9 @@ spring: datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:no-copy-db}?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC - username: ${DB_USERNAME:mysql_user} - password: ${DB_PASSWORD:mysql_pass} + url: jdbc:postgresql://${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} + username: ${POSTGRES_USER} + password: ${POSTGRES_PASSWORD} + driver-class-name: org.postgresql.Driver jpa: hibernate: diff --git a/src/test/java/ru/soune/no_copy/NoCopyApplicationTests.java b/src/test/java/ru/soune/no_copy/NoCopyApplicationTests.java deleted file mode 100644 index 0fe0bc6..0000000 --- a/src/test/java/ru/soune/no_copy/NoCopyApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.soune.no_copy; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class NoCopyApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/src/test/java/ru/soune/no_copy/service/AuthTestService.java b/src/test/java/ru/soune/no_copy/service/AuthServiceTest.java similarity index 98% rename from src/test/java/ru/soune/no_copy/service/AuthTestService.java rename to src/test/java/ru/soune/no_copy/service/AuthServiceTest.java index e5109c4..995af88 100644 --- a/src/test/java/ru/soune/no_copy/service/AuthTestService.java +++ b/src/test/java/ru/soune/no_copy/service/AuthServiceTest.java @@ -21,7 +21,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; -public class AuthTestService { +public class AuthServiceTest { @Mock private UserRepository userRepository; diff --git a/wait-for-it.sh b/wait-for-it.sh new file mode 100644 index 0000000..d990e0d --- /dev/null +++ b/wait-for-it.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi