From 7117e0d33cadd4735d5f72878235b08d4ed89074 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Sun, 1 Dec 2024 00:43:31 +0900 Subject: [PATCH 01/73] git init --- .gitattributes | 3 + .gitignore | 37 +++ build.gradle | 28 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 252 ++++++++++++++++++ gradlew.bat | 94 +++++++ settings.gradle | 1 + .../com/ceos/vote/CeosVoteBeApplication.java | 13 + src/main/resources/application.properties | 1 + .../ceos/vote/CeosVoteBeApplicationTests.java | 13 + 11 files changed, 449 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/com/ceos/vote/CeosVoteBeApplication.java create mode 100644 src/main/resources/application.properties create mode 100644 src/test/java/com/ceos/vote/CeosVoteBeApplicationTests.java diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2065bc --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..e78c06c --- /dev/null +++ b/build.gradle @@ -0,0 +1,28 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.4.0' + id 'io.spring.dependency-management' version '1.1.6' +} + +group = 'com.ceos.vote' +version = '0.0.1-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..a4b76b9530d66f5e68d973ea569d8e19de379189 GIT binary patch literal 43583 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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 GZ~q4|mN%#X literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e2847c8 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..f5feea6 --- /dev/null +++ b/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e906f4a --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'ceos-vote-BE' diff --git a/src/main/java/com/ceos/vote/CeosVoteBeApplication.java b/src/main/java/com/ceos/vote/CeosVoteBeApplication.java new file mode 100644 index 0000000..b910fac --- /dev/null +++ b/src/main/java/com/ceos/vote/CeosVoteBeApplication.java @@ -0,0 +1,13 @@ +package com.ceos.vote; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class CeosVoteBeApplication { + + public static void main(String[] args) { + SpringApplication.run(CeosVoteBeApplication.class, args); + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..f94e9b3 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=ceos-vote-BE diff --git a/src/test/java/com/ceos/vote/CeosVoteBeApplicationTests.java b/src/test/java/com/ceos/vote/CeosVoteBeApplicationTests.java new file mode 100644 index 0000000..bd83ad5 --- /dev/null +++ b/src/test/java/com/ceos/vote/CeosVoteBeApplicationTests.java @@ -0,0 +1,13 @@ +package com.ceos.vote; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class CeosVoteBeApplicationTests { + + @Test + void contextLoads() { + } + +} From 295e6eab300926ec52b1718f7ed7ad194c2900be Mon Sep 17 00:00:00 2001 From: limgahyun Date: Sun, 1 Dec 2024 00:56:56 +0900 Subject: [PATCH 02/73] feat: add dependencies --- build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build.gradle b/build.gradle index e78c06c..447a5ec 100644 --- a/build.gradle +++ b/build.gradle @@ -19,6 +19,13 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.projectlombok:lombok:1.18.24' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.jetbrains:annotations:24.0.0' + annotationProcessor('org.projectlombok:lombok') + runtimeOnly 'com.mysql:mysql-connector-j' + testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } From 37c683e2003fbedb8cda81588e1b9ff162b03501 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Sun, 1 Dec 2024 00:58:00 +0900 Subject: [PATCH 03/73] feat: common response & exception code --- .../common/response/CommonResponse.java | 33 +++++++++++++ .../exception/ApplicationException.java | 11 +++++ .../vote/global/exception/ExceptionCode.java | 40 ++++++++++++++++ .../global/exception/ExceptionResponse.java | 24 ++++++++++ .../exception/GlobalExceptionHandler.java | 46 +++++++++++++++++++ 5 files changed, 154 insertions(+) create mode 100644 src/main/java/com/ceos/vote/global/common/response/CommonResponse.java create mode 100644 src/main/java/com/ceos/vote/global/exception/ApplicationException.java create mode 100644 src/main/java/com/ceos/vote/global/exception/ExceptionCode.java create mode 100644 src/main/java/com/ceos/vote/global/exception/ExceptionResponse.java create mode 100644 src/main/java/com/ceos/vote/global/exception/GlobalExceptionHandler.java diff --git a/src/main/java/com/ceos/vote/global/common/response/CommonResponse.java b/src/main/java/com/ceos/vote/global/common/response/CommonResponse.java new file mode 100644 index 0000000..34ef387 --- /dev/null +++ b/src/main/java/com/ceos/vote/global/common/response/CommonResponse.java @@ -0,0 +1,33 @@ +package com.ceos.vote.global.common.response; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +import java.time.LocalDateTime; + +@Getter +@AllArgsConstructor(access = AccessLevel.PROTECTED) +public class CommonResponse { + + private LocalDateTime timestamp; + private int code; + private String message; + private T result; + + // 반환값 있는 경우 + public CommonResponse(T result, String message) { + this.timestamp = LocalDateTime.now(); + this.code = HttpStatus.OK.value(); + this.message = message; + this.result = result; + } + + // 반환값 없는 경우 + public CommonResponse(String message) { + this.timestamp = LocalDateTime.now(); + this.code = HttpStatus.OK.value(); + this.message = message; + } +} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/global/exception/ApplicationException.java b/src/main/java/com/ceos/vote/global/exception/ApplicationException.java new file mode 100644 index 0000000..0b0ab98 --- /dev/null +++ b/src/main/java/com/ceos/vote/global/exception/ApplicationException.java @@ -0,0 +1,11 @@ +package com.ceos.vote.global.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ApplicationException extends RuntimeException { + + public ExceptionCode exceptionCode; +} diff --git a/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java b/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java new file mode 100644 index 0000000..79a0332 --- /dev/null +++ b/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java @@ -0,0 +1,40 @@ +package com.ceos.vote.global.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum ExceptionCode { + + // 1000: Success Case + + // 2000: Common Error + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 2000, "서버 에러가 발생하였습니다. 관리자에게 문의해 주세요."), + NOT_FOUND_EXCEPTION(HttpStatus.NOT_FOUND, 2001, "존재하지 않는 리소스입니다."), + INVALID_VALUE_EXCEPTION(HttpStatus.BAD_REQUEST, 2002, "올바르지 않은 요청 값입니다."), + UNAUTHORIZED_EXCEPTION(HttpStatus.UNAUTHORIZED, 2003, "권한이 없는 요청입니다."), + ALREADY_DELETE_EXCEPTION(HttpStatus.BAD_REQUEST, 2004, "이미 삭제된 리소스입니다."), + FORBIDDEN_EXCEPTION(HttpStatus.FORBIDDEN, 2005, "인가되지 않는 요청입니다."), + ALREADY_EXIST_EXCEPTION(HttpStatus.BAD_REQUEST, 2006, "이미 존재하는 리소스입니다."), + INVALID_SORT_EXCEPTION(HttpStatus.BAD_REQUEST, 2007, "올바르지 않은 정렬 값입니다."), + BAD_REQUEST_ERROR(HttpStatus.BAD_REQUEST, 2008, "잘못된 요청입니다."); + + // 3000: + + // 4000: + + // 5000: + + + // 6000: + + //7000: [임의] Error + + + private final HttpStatus httpStatus; + private final int code; + private final String message; + +} diff --git a/src/main/java/com/ceos/vote/global/exception/ExceptionResponse.java b/src/main/java/com/ceos/vote/global/exception/ExceptionResponse.java new file mode 100644 index 0000000..887cce2 --- /dev/null +++ b/src/main/java/com/ceos/vote/global/exception/ExceptionResponse.java @@ -0,0 +1,24 @@ +package com.ceos.vote.global.exception; + +import java.time.LocalDateTime; + +public record ExceptionResponse ( + LocalDateTime timestamp, + int code, + String message +) { + // 기본 생성자: exceptionCode만 사용하는 경우 + public ExceptionResponse(ExceptionCode exceptionCode) { + this(LocalDateTime.now(), exceptionCode.getCode(), exceptionCode.getMessage()); + } + + // 메시지를 직접 지정하는 경우 + public ExceptionResponse(String message) { + this(LocalDateTime.now(), ExceptionCode.INTERNAL_SERVER_ERROR.getCode(), message); + } + + // exceptionCode와 메시지를 동시에 사용하는 경우 + public ExceptionResponse(ExceptionCode exceptionCode, String message) { + this(LocalDateTime.now(), exceptionCode.getCode(), message); + } +} diff --git a/src/main/java/com/ceos/vote/global/exception/GlobalExceptionHandler.java b/src/main/java/com/ceos/vote/global/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..5dfb5b0 --- /dev/null +++ b/src/main/java/com/ceos/vote/global/exception/GlobalExceptionHandler.java @@ -0,0 +1,46 @@ +package com.ceos.vote.global.exception; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.Objects; + + +@Slf4j +@RestControllerAdvice +@RequiredArgsConstructor +public class GlobalExceptionHandler { + + @ExceptionHandler(ApplicationException.class) + protected ResponseEntity handleBadRequestException(ApplicationException e){ + + log.error("BadRequestException 발생: {}", e.getMessage(), e); + + return ResponseEntity.status(e.getExceptionCode().getHttpStatus()) + .body(new ExceptionResponse(e.getExceptionCode())); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity methodArgumentNotValidException(MethodArgumentNotValidException e) { + + log.error("MethodArgumentNotValidException 발생: {}", e.getMessage(), e); + + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(new ExceptionResponse(ExceptionCode.INVALID_VALUE_EXCEPTION, Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage())); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(final Exception e){ + log.error("UnhandledException 발생: {}", e.getMessage(), e); + + return ResponseEntity.internalServerError() + .body(new ExceptionResponse(ExceptionCode.INTERNAL_SERVER_ERROR, e.getMessage())); + } + +} \ No newline at end of file From a7ec842b6dab8ea7c6408ed602dfd603fdf2dad0 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Sun, 1 Dec 2024 00:58:26 +0900 Subject: [PATCH 04/73] feat: baseEntity --- .../java/com/ceos/vote/global/BaseEntity.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/main/java/com/ceos/vote/global/BaseEntity.java diff --git a/src/main/java/com/ceos/vote/global/BaseEntity.java b/src/main/java/com/ceos/vote/global/BaseEntity.java new file mode 100644 index 0000000..8bd834d --- /dev/null +++ b/src/main/java/com/ceos/vote/global/BaseEntity.java @@ -0,0 +1,30 @@ +package com.ceos.vote.global; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Getter +@Setter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseEntity { + + @CreatedDate + @Column(name = "created_at", nullable = false, columnDefinition = "TIMESTAMP") + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name = "updated_at", nullable = false, columnDefinition = "TIMESTAMP") + private LocalDateTime updatedAt; + + @Column(name = "deleted_at", columnDefinition = "TIMESTAMP") + private LocalDateTime deletedAt; +} \ No newline at end of file From ba6a96d504390bf17ccdae2cfadd0a118271f808 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Sun, 1 Dec 2024 01:04:12 +0900 Subject: [PATCH 05/73] feat: users entity, repository --- .../ceos/vote/domain/users/entity/Users.java | 33 +++++++++++++++++++ .../users/repository/UserRepository.java | 13 ++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/main/java/com/ceos/vote/domain/users/entity/Users.java create mode 100644 src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java diff --git a/src/main/java/com/ceos/vote/domain/users/entity/Users.java b/src/main/java/com/ceos/vote/domain/users/entity/Users.java new file mode 100644 index 0000000..d1fe932 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/users/entity/Users.java @@ -0,0 +1,33 @@ +package com.ceos.vote.domain.users.entity; + +import com.ceos.vote.global.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class Users extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Long id; + + @Column(length = 32) + private String username; + + @Column + private String email; + + @Builder + public Users(String username, String email) { + this.username = username; + this.email = email; + } +} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java new file mode 100644 index 0000000..c0971ba --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java @@ -0,0 +1,13 @@ +package com.ceos.vote.domain.users.repository; + +import com.ceos.vote.domain.users.entity.Users; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + + Optional findByWalletAddressAndDeletedAtIsNull(String walletAddress); +} \ No newline at end of file From 34c5b5a4c4d4d777d181bc669f10c7faa0c4e513 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Sun, 1 Dec 2024 01:08:01 +0900 Subject: [PATCH 06/73] feat: swagger setting --- build.gradle | 4 +++ .../vote/global/config/SwaggerConfig.java | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/main/java/com/ceos/vote/global/config/SwaggerConfig.java diff --git a/build.gradle b/build.gradle index 447a5ec..9b69b87 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,10 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + // Swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + } tasks.named('test') { diff --git a/src/main/java/com/ceos/vote/global/config/SwaggerConfig.java b/src/main/java/com/ceos/vote/global/config/SwaggerConfig.java new file mode 100644 index 0000000..e881c95 --- /dev/null +++ b/src/main/java/com/ceos/vote/global/config/SwaggerConfig.java @@ -0,0 +1,29 @@ +package com.ceos.vote.global.config; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.servers.Server; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@OpenAPIDefinition( + info = @Info( + title = "API Test", + description = "CEOS vote service API 명세서", + version = "v1" + ), + servers = {@Server(url = "http://localhost:8080", description = "local server")} +) + +public class SwaggerConfig { + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .components(new Components()); + } +} \ No newline at end of file From e19a7011866bdc427d0c5a5e8dd2895eff6009c3 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Sun, 1 Dec 2024 01:19:23 +0900 Subject: [PATCH 07/73] feat: mysql db setting --- .gitignore | 4 ++++ .../ceos/vote/domain/users/entity/Users.java | 1 - .../users/repository/UserRepository.java | 4 ---- src/main/resources/application.properties | 1 - src/main/resources/application.yml | 22 +++++++++++++++++++ 5 files changed, 26 insertions(+), 6 deletions(-) delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/application.yml diff --git a/.gitignore b/.gitignore index c2065bc..fb5710b 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,7 @@ out/ ### VS Code ### .vscode/ + +### 환경변후 ### +.env + diff --git a/src/main/java/com/ceos/vote/domain/users/entity/Users.java b/src/main/java/com/ceos/vote/domain/users/entity/Users.java index d1fe932..7c82f50 100644 --- a/src/main/java/com/ceos/vote/domain/users/entity/Users.java +++ b/src/main/java/com/ceos/vote/domain/users/entity/Users.java @@ -7,7 +7,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) diff --git a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java index c0971ba..6c7ebd2 100644 --- a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java +++ b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java @@ -4,10 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.Optional; - @Repository public interface UserRepository extends JpaRepository { - - Optional findByWalletAddressAndDeletedAtIsNull(String walletAddress); } \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index f94e9b3..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=ceos-vote-BE diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..d531d5f --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,22 @@ +spring: + config: + import: optional:file:.env[.properties] + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: ${DB_URL} + username: ${DB_USER} + password: ${DB_PASSWORD} + jpa: + database: mysql + database-platform: org.hibernate.dialect.MySQL8Dialect + hibernate: + ddl-auto: create + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL8Dialect + database-platform: org.hibernate.dialect.MySQL8Dialect + format_sql: true + show_sql: true +logging: + level: + org.hibernate.SQL: debug \ No newline at end of file From f54dff65411df2dc0d2a4e272661dfe7fc6e0261 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Sun, 1 Dec 2024 01:22:37 +0900 Subject: [PATCH 08/73] feat: file structure --- src/main/java/com/ceos/vote/domain/vote/entity/empty.java | 4 ++++ src/main/java/com/ceos/vote/domain/vote/repository/empty.java | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 src/main/java/com/ceos/vote/domain/vote/entity/empty.java create mode 100644 src/main/java/com/ceos/vote/domain/vote/repository/empty.java diff --git a/src/main/java/com/ceos/vote/domain/vote/entity/empty.java b/src/main/java/com/ceos/vote/domain/vote/entity/empty.java new file mode 100644 index 0000000..3df1b2f --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/vote/entity/empty.java @@ -0,0 +1,4 @@ +package com.ceos.vote.domain.vote.entity; + +public class empty { +} diff --git a/src/main/java/com/ceos/vote/domain/vote/repository/empty.java b/src/main/java/com/ceos/vote/domain/vote/repository/empty.java new file mode 100644 index 0000000..3a68451 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/vote/repository/empty.java @@ -0,0 +1,4 @@ +package com.ceos.vote.domain.vote.repository; + +public class empty { +} From 1001bb923178598c4cbc9e39c24741cf33a190d6 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Wed, 25 Dec 2024 03:40:56 +0900 Subject: [PATCH 09/73] feat: deploy setting --- .github/workflows/CICD.yml | 119 ++++++++++++++++++ .gitignore | 3 +- Dockerfile | 8 ++ build.gradle | 20 +++ .../vote/global/config/SecurityConfig.java | 56 +++++++++ .../healthCheck/HealthCheckController.java | 38 ++++++ src/main/resources/application.yml | 78 +++++++++--- 7 files changed, 304 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/CICD.yml create mode 100644 Dockerfile create mode 100644 src/main/java/com/ceos/vote/global/config/SecurityConfig.java create mode 100644 src/main/java/com/ceos/vote/global/healthCheck/HealthCheckController.java diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml new file mode 100644 index 0000000..7840930 --- /dev/null +++ b/.github/workflows/CICD.yml @@ -0,0 +1,119 @@ +# github repository actions 페이지에 나타날 이름 +name: CI/CD using github actions & docker + +# event trigger +# main브랜치에 pull request가 발생 되었을 때 실행 +on: + pull_request: + branches: [ "main" ] + push: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v3 + + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + + - name: Gradle Caching + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Grant execute permission for gradlew + run: chmod +x ./gradlew + + - name: Build Project with Gradle + run: | + echo ${{ secrets.APPLICATION_SECRET }} | base64 --decode > ./src/main/resources/application-secret.yml + ./gradlew bootJar + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Build docker image + run: docker build --platform linux/amd64 -t ${{ secrets.DOCKER_USERNAME }}/vote . + + - name: Publish image to docker hub + run: docker push ${{ secrets.DOCKER_USERNAME }}/vote:latest + + deploy: + needs: build + runs-on: ubuntu-latest + steps: + - name: Set Target IP + run: | + STATUS=$(curl -o /dev/null -w "%{http_code}" "${{ secrets.SERVER_ADDRESS }}/env") + echo $STATUS + if [ $STATUS = 200 ]; then + CURRENT_UPSTREAM=$(curl -s "${{ secrets.SERVER_ADDRESS }}/env") + else + CURRENT_UPSTREAM=green + fi + echo CURRENT_UPSTREAM=$CURRENT_UPSTREAM >> $GITHUB_ENV + if [ $CURRENT_UPSTREAM = blue ]; then + echo "CURRENT_PORT=8080" >> $GITHUB_ENV + echo "STOPPED_PORT=8081" >> $GITHUB_ENV + echo "TARGET_UPSTREAM=green" >> $GITHUB_ENV + else + echo "CURRENT_PORT=8081" >> $GITHUB_ENV + echo "STOPPED_PORT=8080" >> $GITHUB_ENV + echo "TARGET_UPSTREAM=blue" >> $GITHUB_ENV + fi + + - name: Docker Compose + uses: appleboy/ssh-action@master + with: + username: ubuntu + host: ${{ secrets.SERVER_IP }} + key: ${{ secrets.EC2_SSH_KEY }} + script_stop: true + script: | + sudo docker pull ${{ secrets.DOCKER_USERNAME }}/vote:latest + sudo docker-compose -f docker-compose-${{env.TARGET_UPSTREAM}}.yml up -d + + - name: Check deploy server URL + uses: jtalk/url-health-check-action@v3 + with: + url: http://${{ secrets.SERVER_IP }}:${{ env.STOPPED_PORT }}/env + max-attempts: 5 + retry-delay: 10s + + - name: Change nginx upstream + uses: appleboy/ssh-action@master + with: + username: ubuntu + host: ${{ secrets.SERVER_IP }} + key: ${{ secrets.EC2_SSH_KEY }} + script_stop: true + script: | + sudo docker exec -i nginxserver bash -c 'echo "set \$service_url ${{env.TARGET_UPSTREAM}};" > etc/nginx/conf.d/service-env.inc && nginx -s reload' + + - name: Stop current server + uses: appleboy/ssh-action@master + with: + username: ubuntu + host: ${{ secrets.SERVER_IP }} + key: ${{ secrets.EC2_SSH_KEY }} + script_stop: true + script: | + sudo docker stop ${{ env.CURRENT_UPSTREAM }} + sudo docker rm ${{ env.CURRENT_UPSTREAM }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index fb5710b..549350d 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ out/ ### VS Code ### .vscode/ -### 환경변후 ### +### 환경변수 ### .env +application-secret.yml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4d25e0b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM openjdk:17-jdk-slim + +ARG JAR_FILE=/build/libs/*.jar +ARG PROFILES +ARG ENV + +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java", "-Dspring.profiles.active=${PROFILES}", "-Dserver.env=${ENV}", "-jar", "app.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9b69b87..a052021 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,26 @@ dependencies { // Swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + // security + implementation 'org.springframework.boot:spring-boot-starter-security' + + // env + implementation 'io.github.cdimascio:dotenv-java:3.0.0' +} + +tasks.named('compileJava') { + doLast { + def generated = layout.buildDirectory.dir("generated/source/apt/main").get().asFile + sourceSets.main.java.srcDirs += generated + } +} + +sourceSets { + main { + java { + srcDirs += layout.buildDirectory.dir("generated/source/apt/main").get().asFile + } + } } tasks.named('test') { diff --git a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java new file mode 100644 index 0000000..cce256b --- /dev/null +++ b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java @@ -0,0 +1,56 @@ +package com.ceos.vote.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +import lombok.RequiredArgsConstructor; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final AuthenticationConfiguration authenticationConfiguration; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .formLogin(AbstractHttpConfigurer::disable) + .httpBasic(AbstractHttpConfigurer::disable); + + http + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/hc", "/env","/**","/login/**", "/auth/join/**").permitAll() + .requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**").permitAll() + .requestMatchers("/reissue").permitAll() + .anyRequest().authenticated()); + http + .logout((logout) -> logout + .logoutSuccessUrl("/login") + .invalidateHttpSession(true)); + http + .sessionManagement(session -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + + return http.build(); + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { + return configuration.getAuthenticationManager(); + } + + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/global/healthCheck/HealthCheckController.java b/src/main/java/com/ceos/vote/global/healthCheck/HealthCheckController.java new file mode 100644 index 0000000..895925c --- /dev/null +++ b/src/main/java/com/ceos/vote/global/healthCheck/HealthCheckController.java @@ -0,0 +1,38 @@ +package com.ceos.vote.global.healthCheck; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +@RestController +public class HealthCheckController { + + @Value("${server.env}") + private String env; + + @Value("${server.port}") + private String serverPort; + + @Value("${server.serverAddress}") + private String serverAddress; + + @GetMapping("/hc") + public ResponseEntity healthCheck() { + Map response = new HashMap<>(); + response.put("serverAddress", serverAddress); + response.put("serverPort", serverPort); + response.put("env", env); + + return ResponseEntity.ok(response); + } + + @GetMapping("/env") + public ResponseEntity getEnv() { + + return ResponseEntity.ok(env); + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d531d5f..9ae61ef 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,22 +1,66 @@ +spring: + profiles: + active: local + group: + local: local, common, secret + blue: blue, common, secret + green: green, common, secret + +server: + env: blue + +--- + +# local +spring: + config: + activate: + on-profile: local + +server: + port: 8080 + serverAddress: localhost + +serverName: local_server + +--- + +# blue server +spring: + config: + activate: + on-profile: blue + +server: + port: 8080 + serverAddress: 52.79.122.106 + +serverName: blue_server + +--- + +# green server +spring: + config: + activate: + on-profile: green + +server: + port: 8081 + serverAddress: 52.79.122.106 + +serverName: green_server + +--- + spring: config: + activate: + on-profile: common import: optional:file:.env[.properties] - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: ${DB_URL} - username: ${DB_USER} - password: ${DB_PASSWORD} - jpa: - database: mysql - database-platform: org.hibernate.dialect.MySQL8Dialect - hibernate: - ddl-auto: create - properties: - hibernate: - dialect: org.hibernate.dialect.MySQL8Dialect - database-platform: org.hibernate.dialect.MySQL8Dialect - format_sql: true - show_sql: true logging: level: - org.hibernate.SQL: debug \ No newline at end of file + org.hibernate.SQL: debug +springdoc: + swagger-ui: + path: /swagger \ No newline at end of file From 0f8a9e87fe4f4b11e1f1889305fe2185bcfbe411 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Wed, 25 Dec 2024 03:41:57 +0900 Subject: [PATCH 10/73] feat: git action branch setting --- .github/workflows/CICD.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 7840930..c694120 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -5,9 +5,9 @@ name: CI/CD using github actions & docker # main브랜치에 pull request가 발생 되었을 때 실행 on: pull_request: - branches: [ "main" ] + branches: [ "master" ] push: - branches: [ "main" ] + branches: [ "master" ] permissions: contents: read From 08e71d9fdce918d34bf8505cd1108a810d94fdec Mon Sep 17 00:00:00 2001 From: limgahyun Date: Wed, 25 Dec 2024 03:53:43 +0900 Subject: [PATCH 11/73] =?UTF-8?q?feat:=20git=20action=20trigger=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/CICD.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index c694120..96f49dd 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -5,7 +5,7 @@ name: CI/CD using github actions & docker # main브랜치에 pull request가 발생 되었을 때 실행 on: pull_request: - branches: [ "master" ] + branches: [ "dev" ] push: branches: [ "master" ] From f3bbdcb1a7507d348fc926d1b56c83cb0f42e00c Mon Sep 17 00:00:00 2001 From: limgahyun Date: Wed, 25 Dec 2024 03:57:10 +0900 Subject: [PATCH 12/73] =?UTF-8?q?test:=20git=20action=20trigger=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/CICD.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 96f49dd..266fa31 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -7,7 +7,7 @@ on: pull_request: branches: [ "dev" ] push: - branches: [ "master" ] + branches: [ "dev" ] permissions: contents: read From ed3f238eed69a19ea9aad58842ea0bd134f45804 Mon Sep 17 00:00:00 2001 From: limgahyun <114561842+limgahyun@users.noreply.github.com> Date: Wed, 25 Dec 2024 03:57:49 +0900 Subject: [PATCH 13/73] Create CICD.yml --- .github/workflows/CICD.yml | 119 +++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 .github/workflows/CICD.yml diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml new file mode 100644 index 0000000..ceebb38 --- /dev/null +++ b/.github/workflows/CICD.yml @@ -0,0 +1,119 @@ +# github repository actions 페이지에 나타날 이름 +name: CI/CD using github actions & docker + +# event trigger +# main브랜치에 pull request가 발생 되었을 때 실행 +on: + pull_request: + branches: [ "dev" ] + push: + branches: [ "dev" ] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v3 + + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + + - name: Gradle Caching + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Grant execute permission for gradlew + run: chmod +x ./gradlew + + - name: Build Project with Gradle + run: | + echo ${{ secrets.APPLICATION_SECRET }} | base64 --decode > ./src/main/resources/application-secret.yml + ./gradlew bootJar + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Build docker image + run: docker build --platform linux/amd64 -t ${{ secrets.DOCKER_USERNAME }}/vote . + + - name: Publish image to docker hub + run: docker push ${{ secrets.DOCKER_USERNAME }}/vote:latest + + deploy: + needs: build + runs-on: ubuntu-latest + steps: + - name: Set Target IP + run: | + STATUS=$(curl -o /dev/null -w "%{http_code}" "${{ secrets.SERVER_ADDRESS }}/env") + echo $STATUS + if [ $STATUS = 200 ]; then + CURRENT_UPSTREAM=$(curl -s "${{ secrets.SERVER_ADDRESS }}/env") + else + CURRENT_UPSTREAM=green + fi + echo CURRENT_UPSTREAM=$CURRENT_UPSTREAM >> $GITHUB_ENV + if [ $CURRENT_UPSTREAM = blue ]; then + echo "CURRENT_PORT=8080" >> $GITHUB_ENV + echo "STOPPED_PORT=8081" >> $GITHUB_ENV + echo "TARGET_UPSTREAM=green" >> $GITHUB_ENV + else + echo "CURRENT_PORT=8081" >> $GITHUB_ENV + echo "STOPPED_PORT=8080" >> $GITHUB_ENV + echo "TARGET_UPSTREAM=blue" >> $GITHUB_ENV + fi + + - name: Docker Compose + uses: appleboy/ssh-action@master + with: + username: ubuntu + host: ${{ secrets.SERVER_IP }} + key: ${{ secrets.EC2_SSH_KEY }} + script_stop: true + script: | + sudo docker pull ${{ secrets.DOCKER_USERNAME }}/vote:latest + sudo docker-compose -f docker-compose-${{env.TARGET_UPSTREAM}}.yml up -d + + - name: Check deploy server URL + uses: jtalk/url-health-check-action@v3 + with: + url: http://${{ secrets.SERVER_IP }}:${{ env.STOPPED_PORT }}/env + max-attempts: 5 + retry-delay: 10s + + - name: Change nginx upstream + uses: appleboy/ssh-action@master + with: + username: ubuntu + host: ${{ secrets.SERVER_IP }} + key: ${{ secrets.EC2_SSH_KEY }} + script_stop: true + script: | + sudo docker exec -i nginxserver bash -c 'echo "set \$service_url ${{env.TARGET_UPSTREAM}};" > etc/nginx/conf.d/service-env.inc && nginx -s reload' + + - name: Stop current server + uses: appleboy/ssh-action@master + with: + username: ubuntu + host: ${{ secrets.SERVER_IP }} + key: ${{ secrets.EC2_SSH_KEY }} + script_stop: true + script: | + sudo docker stop ${{ env.CURRENT_UPSTREAM }} + sudo docker rm ${{ env.CURRENT_UPSTREAM }} From 69a8b3e03abddb572afb4f2290ffd400deb705bb Mon Sep 17 00:00:00 2001 From: limgahyun Date: Wed, 25 Dec 2024 03:58:19 +0900 Subject: [PATCH 14/73] =?UTF-8?q?test:=20git=20action=20trigger=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/CICD.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 266fa31..c694120 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -5,9 +5,9 @@ name: CI/CD using github actions & docker # main브랜치에 pull request가 발생 되었을 때 실행 on: pull_request: - branches: [ "dev" ] + branches: [ "master" ] push: - branches: [ "dev" ] + branches: [ "master" ] permissions: contents: read From b0be3fc97f570d85aafc94edf613790daac4e43e Mon Sep 17 00:00:00 2001 From: limgahyun <114561842+limgahyun@users.noreply.github.com> Date: Wed, 25 Dec 2024 03:59:04 +0900 Subject: [PATCH 15/73] Update CICD.yml --- .github/workflows/CICD.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index ceebb38..bbef94c 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -5,9 +5,9 @@ name: CI/CD using github actions & docker # main브랜치에 pull request가 발생 되었을 때 실행 on: pull_request: - branches: [ "dev" ] + branches: [ "master" ] push: - branches: [ "dev" ] + branches: [ "master" ] permissions: contents: read From 0e996c5b925f84143b10d899e72aa682b5a3c99e Mon Sep 17 00:00:00 2001 From: limgahyun Date: Wed, 25 Dec 2024 04:12:03 +0900 Subject: [PATCH 16/73] =?UTF-8?q?fix:=20server=20url=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/CICD.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 9f74429..61d8f51 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -61,10 +61,10 @@ jobs: steps: - name: Set Target IP run: | - STATUS=$(curl -o /dev/null -w "%{http_code}" "${{ secrets.SERVER_ADDRESS }}/env") + STATUS=$(curl -o /dev/null -w "%{http_code}" "${{ secrets.SERVER_IP }}/env") echo $STATUS if [ $STATUS = 200 ]; then - CURRENT_UPSTREAM=$(curl -s "${{ secrets.SERVER_ADDRESS }}/env") + CURRENT_UPSTREAM=$(curl -s "${{ secrets.SERVER_IP }}/env") else CURRENT_UPSTREAM=green fi From 563ce26255132eae04db4b60fac830253a4abe5f Mon Sep 17 00:00:00 2001 From: Seoah <0325sandy@ewhain.net> Date: Thu, 26 Dec 2024 14:52:18 +0900 Subject: [PATCH 17/73] =?UTF-8?q?feat:=20login=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 9 +++ .../users/controller/UserApiController.java | 23 +++++++ .../users/controller/UserViewController.java | 18 ++++++ .../vote/domain/users/dto/AddUserRequest.java | 11 ++++ .../ceos/vote/domain/users/entity/User.java | 60 +++++++++++++++++++ .../ceos/vote/domain/users/entity/Users.java | 32 ---------- .../users/repository/UserRepository.java | 7 ++- .../users/service/UserDetailService.java | 20 +++++++ .../domain/users/service/UserService.java | 23 +++++++ .../vote/global/config/SecurityConfig.java | 18 ++++++ 10 files changed, 187 insertions(+), 34 deletions(-) create mode 100644 src/main/java/com/ceos/vote/domain/users/controller/UserApiController.java create mode 100644 src/main/java/com/ceos/vote/domain/users/controller/UserViewController.java create mode 100644 src/main/java/com/ceos/vote/domain/users/dto/AddUserRequest.java create mode 100644 src/main/java/com/ceos/vote/domain/users/entity/User.java delete mode 100644 src/main/java/com/ceos/vote/domain/users/entity/Users.java create mode 100644 src/main/java/com/ceos/vote/domain/users/service/UserDetailService.java create mode 100644 src/main/java/com/ceos/vote/domain/users/service/UserService.java diff --git a/build.gradle b/build.gradle index a052021..0916177 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,10 @@ dependencies { annotationProcessor('org.projectlombok:lombok') runtimeOnly 'com.mysql:mysql-connector-j' + implementation 'org.projectlombok:lombok:1.18.26' + annotationProcessor 'org.projectlombok:lombok:1.18.26' + + testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' @@ -37,6 +41,11 @@ dependencies { // env implementation 'io.github.cdimascio:dotenv-java:3.0.0' + + // spring security + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' + testImplementation 'org.springframework.security:spring-security-test' } tasks.named('compileJava') { diff --git a/src/main/java/com/ceos/vote/domain/users/controller/UserApiController.java b/src/main/java/com/ceos/vote/domain/users/controller/UserApiController.java new file mode 100644 index 0000000..705d597 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/users/controller/UserApiController.java @@ -0,0 +1,23 @@ +package com.ceos.vote.domain.users.controller; + +import com.ceos.vote.domain.users.dto.AddUserRequest; +import com.ceos.vote.domain.users.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@Controller +public class UserApiController { + + private final UserService userService; + + @PostMapping("/user") + public String signup(AddUserRequest request){ + userService.save(request); + return "redirect:/login"; + } + +} diff --git a/src/main/java/com/ceos/vote/domain/users/controller/UserViewController.java b/src/main/java/com/ceos/vote/domain/users/controller/UserViewController.java new file mode 100644 index 0000000..eeb3860 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/users/controller/UserViewController.java @@ -0,0 +1,18 @@ +package com.ceos.vote.domain.users.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class UserViewController { + + @GetMapping("/login") + public String login(){ + return "login"; + } + + @GetMapping("/signup") + public String signup(){ + return "signup"; + } +} diff --git a/src/main/java/com/ceos/vote/domain/users/dto/AddUserRequest.java b/src/main/java/com/ceos/vote/domain/users/dto/AddUserRequest.java new file mode 100644 index 0000000..4eb268f --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/users/dto/AddUserRequest.java @@ -0,0 +1,11 @@ +package com.ceos.vote.domain.users.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AddUserRequest { + private String email; + private String password; +} diff --git a/src/main/java/com/ceos/vote/domain/users/entity/User.java b/src/main/java/com/ceos/vote/domain/users/entity/User.java new file mode 100644 index 0000000..adc1f3c --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/users/entity/User.java @@ -0,0 +1,60 @@ +package com.ceos.vote.domain.users.entity; + +import com.ceos.vote.global.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; + + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Table(name = "user") +public class User extends BaseEntity implements UserDetails { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id", updatable = false) + private Long id; + + @Column(name = "email", nullable = false, unique = true) + private String email; + + @Column(name = "password", nullable = false) + private String password; + + @Builder + public User(String email, String password, String auth) { + this.email = email; + this.password = password; + } + + @Override + public Collection getAuthorities() { + return List.of(new SimpleGrantedAuthority("user")); + } + + @Override + public String getUsername() { + return email; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public boolean isEnabled() { + return true; + } + +} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/users/entity/Users.java b/src/main/java/com/ceos/vote/domain/users/entity/Users.java deleted file mode 100644 index 7c82f50..0000000 --- a/src/main/java/com/ceos/vote/domain/users/entity/Users.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.ceos.vote.domain.users.entity; - -import com.ceos.vote.global.BaseEntity; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - - -@Entity -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -public class Users extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "user_id") - private Long id; - - @Column(length = 32) - private String username; - - @Column - private String email; - - @Builder - public Users(String username, String email) { - this.username = username; - this.email = email; - } -} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java index 6c7ebd2..2f97029 100644 --- a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java +++ b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java @@ -1,9 +1,12 @@ package com.ceos.vote.domain.users.repository; -import com.ceos.vote.domain.users.entity.Users; +import com.ceos.vote.domain.users.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository -public interface UserRepository extends JpaRepository { +public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); } \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/users/service/UserDetailService.java b/src/main/java/com/ceos/vote/domain/users/service/UserDetailService.java new file mode 100644 index 0000000..590c9e3 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/users/service/UserDetailService.java @@ -0,0 +1,20 @@ +package com.ceos.vote.domain.users.service; + +import com.ceos.vote.domain.users.entity.User; +import com.ceos.vote.domain.users.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class UserDetailService implements UserDetailsService { + + private final UserRepository userRepository; + + @Override + public User loadUserByUsername(String email){ + return userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException((email))); + } +} diff --git a/src/main/java/com/ceos/vote/domain/users/service/UserService.java b/src/main/java/com/ceos/vote/domain/users/service/UserService.java new file mode 100644 index 0000000..054d723 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/users/service/UserService.java @@ -0,0 +1,23 @@ +package com.ceos.vote.domain.users.service; + +import com.ceos.vote.domain.users.dto.AddUserRequest; +import com.ceos.vote.domain.users.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class UserService { + + private final UserRepository userRepository; + private final BCryptPasswordEncoder bCryptPasswordEncoder; + + public Long save(AddUserRequest dto){ + return userRepository.save(User.builder() + .email(dto.getEmail()) + .password(bCryptPasswordEncoder.encode(dto.getPassword())) + .build()).getId(); + } +} diff --git a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java index cce256b..4b9635e 100644 --- a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java +++ b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java @@ -1,17 +1,23 @@ package com.ceos.vote.global.config; +import com.ceos.vote.domain.users.service.UserDetailService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import lombok.RequiredArgsConstructor; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toH2Console; @Configuration @EnableWebSecurity @@ -19,6 +25,7 @@ public class SecurityConfig { private final AuthenticationConfiguration authenticationConfiguration; + private final UserDetailService userService; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -31,6 +38,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests(authorize -> authorize .requestMatchers("/hc", "/env","/**","/login/**", "/auth/join/**").permitAll() .requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**").permitAll() + .requestMatchers("/login/**", "/auth/join/**", "/signup", "/user").permitAll() .requestMatchers("/reissue").permitAll() .anyRequest().authenticated()); http @@ -46,6 +54,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(userService); // 사용자 정보 서비스 설정 + authProvider.setPasswordEncoder(bCryptPasswordEncoder()); // 비밀번호 인코더 설정 return configuration.getAuthenticationManager(); } @@ -53,4 +64,11 @@ public AuthenticationManager authenticationManager(AuthenticationConfiguration c public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } + + @Bean + public WebSecurityCustomizer configure(){ + return (web) -> web.ignoring() + .requestMatchers(toH2Console()) + .requestMatchers(new AntPathRequestMatcher("/static/**")); + } } \ No newline at end of file From 54699e92f12ee7b2226a48e472d8ccca28e56750 Mon Sep 17 00:00:00 2001 From: limgahyun <114561842+limgahyun@users.noreply.github.com> Date: Thu, 26 Dec 2024 18:14:09 +0900 Subject: [PATCH 18/73] =?UTF-8?q?Revert=20"Feat:=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85,=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 9 --- .../users/controller/UserApiController.java | 23 ------- .../users/controller/UserViewController.java | 18 ------ .../vote/domain/users/dto/AddUserRequest.java | 11 ---- .../ceos/vote/domain/users/entity/User.java | 60 ------------------- .../ceos/vote/domain/users/entity/Users.java | 32 ++++++++++ .../users/repository/UserRepository.java | 7 +-- .../users/service/UserDetailService.java | 20 ------- .../domain/users/service/UserService.java | 23 ------- .../vote/global/config/SecurityConfig.java | 18 ------ 10 files changed, 34 insertions(+), 187 deletions(-) delete mode 100644 src/main/java/com/ceos/vote/domain/users/controller/UserApiController.java delete mode 100644 src/main/java/com/ceos/vote/domain/users/controller/UserViewController.java delete mode 100644 src/main/java/com/ceos/vote/domain/users/dto/AddUserRequest.java delete mode 100644 src/main/java/com/ceos/vote/domain/users/entity/User.java create mode 100644 src/main/java/com/ceos/vote/domain/users/entity/Users.java delete mode 100644 src/main/java/com/ceos/vote/domain/users/service/UserDetailService.java delete mode 100644 src/main/java/com/ceos/vote/domain/users/service/UserService.java diff --git a/build.gradle b/build.gradle index 0916177..a052021 100644 --- a/build.gradle +++ b/build.gradle @@ -26,10 +26,6 @@ dependencies { annotationProcessor('org.projectlombok:lombok') runtimeOnly 'com.mysql:mysql-connector-j' - implementation 'org.projectlombok:lombok:1.18.26' - annotationProcessor 'org.projectlombok:lombok:1.18.26' - - testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' @@ -41,11 +37,6 @@ dependencies { // env implementation 'io.github.cdimascio:dotenv-java:3.0.0' - - // spring security - implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' - testImplementation 'org.springframework.security:spring-security-test' } tasks.named('compileJava') { diff --git a/src/main/java/com/ceos/vote/domain/users/controller/UserApiController.java b/src/main/java/com/ceos/vote/domain/users/controller/UserApiController.java deleted file mode 100644 index 705d597..0000000 --- a/src/main/java/com/ceos/vote/domain/users/controller/UserApiController.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.ceos.vote.domain.users.controller; - -import com.ceos.vote.domain.users.dto.AddUserRequest; -import com.ceos.vote.domain.users.service.UserService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RestController; - -@RequiredArgsConstructor -@Controller -public class UserApiController { - - private final UserService userService; - - @PostMapping("/user") - public String signup(AddUserRequest request){ - userService.save(request); - return "redirect:/login"; - } - -} diff --git a/src/main/java/com/ceos/vote/domain/users/controller/UserViewController.java b/src/main/java/com/ceos/vote/domain/users/controller/UserViewController.java deleted file mode 100644 index eeb3860..0000000 --- a/src/main/java/com/ceos/vote/domain/users/controller/UserViewController.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.ceos.vote.domain.users.controller; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; - -@Controller -public class UserViewController { - - @GetMapping("/login") - public String login(){ - return "login"; - } - - @GetMapping("/signup") - public String signup(){ - return "signup"; - } -} diff --git a/src/main/java/com/ceos/vote/domain/users/dto/AddUserRequest.java b/src/main/java/com/ceos/vote/domain/users/dto/AddUserRequest.java deleted file mode 100644 index 4eb268f..0000000 --- a/src/main/java/com/ceos/vote/domain/users/dto/AddUserRequest.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ceos.vote.domain.users.dto; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class AddUserRequest { - private String email; - private String password; -} diff --git a/src/main/java/com/ceos/vote/domain/users/entity/User.java b/src/main/java/com/ceos/vote/domain/users/entity/User.java deleted file mode 100644 index adc1f3c..0000000 --- a/src/main/java/com/ceos/vote/domain/users/entity/User.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.ceos.vote.domain.users.entity; - -import com.ceos.vote.global.BaseEntity; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; - -import java.util.Collection; -import java.util.List; - - -@Entity -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -@Table(name = "user") -public class User extends BaseEntity implements UserDetails { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "user_id", updatable = false) - private Long id; - - @Column(name = "email", nullable = false, unique = true) - private String email; - - @Column(name = "password", nullable = false) - private String password; - - @Builder - public User(String email, String password, String auth) { - this.email = email; - this.password = password; - } - - @Override - public Collection getAuthorities() { - return List.of(new SimpleGrantedAuthority("user")); - } - - @Override - public String getUsername() { - return email; - } - - @Override - public String getPassword() { - return password; - } - - @Override - public boolean isEnabled() { - return true; - } - -} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/users/entity/Users.java b/src/main/java/com/ceos/vote/domain/users/entity/Users.java new file mode 100644 index 0000000..7c82f50 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/users/entity/Users.java @@ -0,0 +1,32 @@ +package com.ceos.vote.domain.users.entity; + +import com.ceos.vote.global.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class Users extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Long id; + + @Column(length = 32) + private String username; + + @Column + private String email; + + @Builder + public Users(String username, String email) { + this.username = username; + this.email = email; + } +} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java index 2f97029..6c7ebd2 100644 --- a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java +++ b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java @@ -1,12 +1,9 @@ package com.ceos.vote.domain.users.repository; -import com.ceos.vote.domain.users.entity.User; +import com.ceos.vote.domain.users.entity.Users; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.Optional; - @Repository -public interface UserRepository extends JpaRepository { - Optional findByEmail(String email); +public interface UserRepository extends JpaRepository { } \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/users/service/UserDetailService.java b/src/main/java/com/ceos/vote/domain/users/service/UserDetailService.java deleted file mode 100644 index 590c9e3..0000000 --- a/src/main/java/com/ceos/vote/domain/users/service/UserDetailService.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.ceos.vote.domain.users.service; - -import com.ceos.vote.domain.users.entity.User; -import com.ceos.vote.domain.users.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.stereotype.Service; - -@RequiredArgsConstructor -@Service -public class UserDetailService implements UserDetailsService { - - private final UserRepository userRepository; - - @Override - public User loadUserByUsername(String email){ - return userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException((email))); - } -} diff --git a/src/main/java/com/ceos/vote/domain/users/service/UserService.java b/src/main/java/com/ceos/vote/domain/users/service/UserService.java deleted file mode 100644 index 054d723..0000000 --- a/src/main/java/com/ceos/vote/domain/users/service/UserService.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.ceos.vote.domain.users.service; - -import com.ceos.vote.domain.users.dto.AddUserRequest; -import com.ceos.vote.domain.users.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.stereotype.Service; - -@RequiredArgsConstructor -@Service -public class UserService { - - private final UserRepository userRepository; - private final BCryptPasswordEncoder bCryptPasswordEncoder; - - public Long save(AddUserRequest dto){ - return userRepository.save(User.builder() - .email(dto.getEmail()) - .password(bCryptPasswordEncoder.encode(dto.getPassword())) - .build()).getId(); - } -} diff --git a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java index 4b9635e..cce256b 100644 --- a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java +++ b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java @@ -1,23 +1,17 @@ package com.ceos.vote.global.config; -import com.ceos.vote.domain.users.service.UserDetailService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import lombok.RequiredArgsConstructor; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; - -import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toH2Console; @Configuration @EnableWebSecurity @@ -25,7 +19,6 @@ public class SecurityConfig { private final AuthenticationConfiguration authenticationConfiguration; - private final UserDetailService userService; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -38,7 +31,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests(authorize -> authorize .requestMatchers("/hc", "/env","/**","/login/**", "/auth/join/**").permitAll() .requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**").permitAll() - .requestMatchers("/login/**", "/auth/join/**", "/signup", "/user").permitAll() .requestMatchers("/reissue").permitAll() .anyRequest().authenticated()); http @@ -54,9 +46,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { - DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); - authProvider.setUserDetailsService(userService); // 사용자 정보 서비스 설정 - authProvider.setPasswordEncoder(bCryptPasswordEncoder()); // 비밀번호 인코더 설정 return configuration.getAuthenticationManager(); } @@ -64,11 +53,4 @@ public AuthenticationManager authenticationManager(AuthenticationConfiguration c public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } - - @Bean - public WebSecurityCustomizer configure(){ - return (web) -> web.ignoring() - .requestMatchers(toH2Console()) - .requestMatchers(new AntPathRequestMatcher("/static/**")); - } } \ No newline at end of file From 2ba2279055c228dbe0ad0716fcea1ebe33d69bcb Mon Sep 17 00:00:00 2001 From: Seoah <0325sandy@ewhain.net> Date: Fri, 27 Dec 2024 07:22:15 +0900 Subject: [PATCH 19/73] =?UTF-8?q?feat:=20auth=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 + .../domain/auth/JwtAuthenticationFilter.java | 44 +++++++ .../com/ceos/vote/domain/auth/JwtToken.java | 14 ++ .../vote/domain/auth/JwtTokenProvider.java | 121 ++++++++++++++++++ .../ceos/vote/domain/users/entity/User.java | 21 ++- .../vote/global/config/SecurityConfig.java | 25 ++++ src/main/resources/application.yml | 5 +- 7 files changed, 227 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/ceos/vote/domain/auth/JwtAuthenticationFilter.java create mode 100644 src/main/java/com/ceos/vote/domain/auth/JwtToken.java create mode 100644 src/main/java/com/ceos/vote/domain/auth/JwtTokenProvider.java diff --git a/build.gradle b/build.gradle index 0916177..eecaefa 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,11 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' testImplementation 'org.springframework.security:spring-security-test' + + //JWT + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' } tasks.named('compileJava') { diff --git a/src/main/java/com/ceos/vote/domain/auth/JwtAuthenticationFilter.java b/src/main/java/com/ceos/vote/domain/auth/JwtAuthenticationFilter.java new file mode 100644 index 0000000..93c9621 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/auth/JwtAuthenticationFilter.java @@ -0,0 +1,44 @@ +package com.ceos.vote.domain.auth; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.GenericFilterBean; + +import java.io.IOException; + +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends GenericFilterBean { + private final JwtTokenProvider jwtTokenProvider; + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + + // 1. Request Header에서 JWT 토큰 추출 + String token = resolveToken((HttpServletRequest) servletRequest); + + // 2. validateToken으로 토큰 유효성 검사 + if (token != null && jwtTokenProvider.validateToken(token)) { + // 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext에 저장 + Authentication authentication = jwtTokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + filterChain.doFilter(servletRequest, servletResponse); + } + + // Request Header에서 토큰 정보 추출 + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer")) { + return bearerToken.substring(7); + } + return null; + } + +} diff --git a/src/main/java/com/ceos/vote/domain/auth/JwtToken.java b/src/main/java/com/ceos/vote/domain/auth/JwtToken.java new file mode 100644 index 0000000..911a2f8 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/auth/JwtToken.java @@ -0,0 +1,14 @@ +package com.ceos.vote.domain.auth; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +@AllArgsConstructor +public class JwtToken { + private String grantType; + private String accessToken; + private String refreshToken; +} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/auth/JwtTokenProvider.java b/src/main/java/com/ceos/vote/domain/auth/JwtTokenProvider.java new file mode 100644 index 0000000..ac72545 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/auth/JwtTokenProvider.java @@ -0,0 +1,121 @@ +package com.ceos.vote.domain.auth; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import javax.swing.*; +import java.security.Key; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class JwtTokenProvider { + + private final Key key; + + //application.yml에서 secret 값 가져와서 key에 저장 + public JwtTokenProvider(@Value("${jwt.secret}") String secretKey){ + byte[] keyBytes = Decoders.BASE64.decode(secretKey); + this.key = Keys.hmacShaKeyFor(keyBytes); + } + + //AccessToken, RefreshToken을 생성하는 메서드 + public JwtToken generateToken(Authentication authentication){ + + //권한 가져오기 + @NotNull String authorities = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(",")); + + long now = (new Date()).getTime(); + + Date accessTokenExpiresIn = new Date(now+86400000); + String accessToken = Jwts.builder() + .setSubject(authentication.getName()) + .claim("auth", authorities) + .setExpiration(accessTokenExpiresIn) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + + //Refresh Token 생성 + String refreshToken = Jwts.builder() + .setExpiration(new Date(now + 86400000)) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + + return JwtToken.builder() + .grantType("Bearer") + .accessToken(accessToken) + .refreshToken(refreshToken) + .build(); + } + + // Jwt 토큰을 복호화하여 토큰에 들어있는 정보를 꺼내는 메서드 + public Authentication getAuthentication(String accessToken) { + + // Jwt 토큰 복호화 + Claims claims = parseClaims(accessToken); + + if (claims.get("auth") == null) { + throw new RuntimeException("권한 정보가 없는 토큰입니다."); + } + + // 클레임에서 권한 정보 가져오기 + Collection authorities = Arrays.stream(claims.get("auth").toString().split(",")) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + + // UserDetails 객체를 만들어서 Authentication return + // UserDetails: interface, User: UserDetails를 구현한 class + UserDetails principal = new User(claims.getSubject(), "", authorities); + + return new UsernamePasswordAuthenticationToken(principal, "", authorities); + } + + public boolean validateToken(String token) { + try { + Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token); + return true; + } catch (SecurityException | MalformedJwtException e) { + log.info("Invalid JWT Token", e); + } catch (ExpiredJwtException e) { + log.info("Expired JWT Token", e); + } catch (UnsupportedJwtException e) { + log.info("Unsupported JWT Token", e); + } catch (IllegalArgumentException e) { + log.info("JWT claims string is empty.", e); + } + return false; + } + + private Claims parseClaims(String accessToken) { + try { + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(accessToken) + .getBody(); + } catch (ExpiredJwtException e) { + return e.getClaims(); + } + } + + +} diff --git a/src/main/java/com/ceos/vote/domain/users/entity/User.java b/src/main/java/com/ceos/vote/domain/users/entity/User.java index adc1f3c..f1551ab 100644 --- a/src/main/java/com/ceos/vote/domain/users/entity/User.java +++ b/src/main/java/com/ceos/vote/domain/users/entity/User.java @@ -2,10 +2,7 @@ import com.ceos.vote.global.BaseEntity; import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @@ -16,8 +13,10 @@ @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor @Getter -@Table(name = "user") +@Table(name = "USER") +@Builder public class User extends BaseEntity implements UserDetails { @Id @@ -25,13 +24,21 @@ public class User extends BaseEntity implements UserDetails { @Column(name = "user_id", updatable = false) private Long id; - @Column(name = "email", nullable = false, unique = true) + @Column(name="name") + private String name; + + @Column(name = "email", nullable = false, unique = true) private String email; @Column(name = "password", nullable = false) private String password; - @Builder + @Column(name="user_team") + private String userTeam; + + @Column(name="user_part") + private String userPart; + public User(String email, String password, String auth) { this.email = email; this.password = password; diff --git a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java index 4b9635e..5b3488b 100644 --- a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java +++ b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java @@ -1,20 +1,26 @@ package com.ceos.vote.global.config; +import com.ceos.vote.domain.auth.JwtAuthenticationFilter; +import com.ceos.vote.domain.auth.JwtTokenProvider; import com.ceos.vote.domain.users.service.UserDetailService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.HttpSecurityDsl; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import lombok.RequiredArgsConstructor; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toH2Console; @@ -26,6 +32,7 @@ public class SecurityConfig { private final AuthenticationConfiguration authenticationConfiguration; private final UserDetailService userService; + private final JwtTokenProvider jwtTokenProvider; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -48,6 +55,21 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + http + // REST API이므로 basic auth 및 csrf 보안을 사용하지 않음 + .httpBasic(AbstractHttpConfigurer::disable) + .csrf(AbstractHttpConfigurer::disable) + // JWT를 사용하기 때문에 세션을 사용하지 않음 + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(authorize -> authorize + // 해당 API에 대해서는 모든 요청을 허가 + .requestMatchers("/members/sign-in").permitAll() + // USER 권한이 있어야 요청할 수 있음 + .requestMatchers("/members/test").hasRole("USER") + // 이 밖에 모든 요청에 대해서 인증을 필요로 한다는 설정 + .anyRequest().authenticated()) + // JWT 인증을 위하여 직접 구현한 필터를 UsernamePasswordAuthenticationFilter 전에 실행 + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); return http.build(); } @@ -71,4 +93,7 @@ public WebSecurityCustomizer configure(){ .requestMatchers(toH2Console()) .requestMatchers(new AntPathRequestMatcher("/static/**")); } + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } } \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9ae61ef..11fd10c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -63,4 +63,7 @@ logging: org.hibernate.SQL: debug springdoc: swagger-ui: - path: /swagger \ No newline at end of file + path: /swagger + +jwt: + secret:161dd0a2af20a9c25c2d0044222d46a1ba6b0f2641e240a50f1694a7da9a5b73 \ No newline at end of file From fa93694cc4273d3aaac6fd52cecf4c1e7cb30d44 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Sun, 29 Dec 2024 23:49:25 +0900 Subject: [PATCH 20/73] =?UTF-8?q?feat:=20leader=20voting=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20entity,=20repository?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/LeaderCandidate.java | 32 +++++++++++++++++ .../repository/LeaderCandidateRepository.java | 9 +++++ .../domain/leaderVote/entity/LeaderVote.java | 36 +++++++++++++++++++ .../repository/LeaderVoteRepository.java | 9 +++++ .../ceos/vote/domain/vote/entity/empty.java | 4 --- .../vote/domain/vote/repository/empty.java | 4 --- 6 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/ceos/vote/domain/leaderCandidate/entity/LeaderCandidate.java create mode 100644 src/main/java/com/ceos/vote/domain/leaderCandidate/repository/LeaderCandidateRepository.java create mode 100644 src/main/java/com/ceos/vote/domain/leaderVote/entity/LeaderVote.java create mode 100644 src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java delete mode 100644 src/main/java/com/ceos/vote/domain/vote/entity/empty.java delete mode 100644 src/main/java/com/ceos/vote/domain/vote/repository/empty.java diff --git a/src/main/java/com/ceos/vote/domain/leaderCandidate/entity/LeaderCandidate.java b/src/main/java/com/ceos/vote/domain/leaderCandidate/entity/LeaderCandidate.java new file mode 100644 index 0000000..f995033 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/leaderCandidate/entity/LeaderCandidate.java @@ -0,0 +1,32 @@ +package com.ceos.vote.domain.leaderCandidate.entity; + +import com.ceos.vote.global.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class LeaderCandidate extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "leader_candidate_id") + private Long id; + + @Column(length = 32) + private String part; + + @Column + private String name; + + @Builder + public LeaderCandidate(String part, String name) { + this.part = part; + this.name = name; + } +} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/leaderCandidate/repository/LeaderCandidateRepository.java b/src/main/java/com/ceos/vote/domain/leaderCandidate/repository/LeaderCandidateRepository.java new file mode 100644 index 0000000..5f2d244 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/leaderCandidate/repository/LeaderCandidateRepository.java @@ -0,0 +1,9 @@ +package com.ceos.vote.domain.leaderCandidate.repository; + +import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface LeaderCandidateRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/entity/LeaderVote.java b/src/main/java/com/ceos/vote/domain/leaderVote/entity/LeaderVote.java new file mode 100644 index 0000000..7fb0ff2 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/leaderVote/entity/LeaderVote.java @@ -0,0 +1,36 @@ +package com.ceos.vote.domain.leaderVote.entity; + +import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; +import com.ceos.vote.domain.users.entity.Users; +import com.ceos.vote.global.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class LeaderVote extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "leader_candidate_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private Users user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "leader_candidate_id") + private LeaderCandidate leaderCandidate; + + @Builder + public LeaderVote(Users user, LeaderCandidate leaderCandidate) { + this.user = user; + this.leaderCandidate = leaderCandidate; + } +} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java b/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java new file mode 100644 index 0000000..57d8451 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java @@ -0,0 +1,9 @@ +package com.ceos.vote.domain.leaderVote.repository; + +import com.ceos.vote.domain.leaderVote.entity.LeaderVote; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface LeaderVoteRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/vote/entity/empty.java b/src/main/java/com/ceos/vote/domain/vote/entity/empty.java deleted file mode 100644 index 3df1b2f..0000000 --- a/src/main/java/com/ceos/vote/domain/vote/entity/empty.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.ceos.vote.domain.vote.entity; - -public class empty { -} diff --git a/src/main/java/com/ceos/vote/domain/vote/repository/empty.java b/src/main/java/com/ceos/vote/domain/vote/repository/empty.java deleted file mode 100644 index 3a68451..0000000 --- a/src/main/java/com/ceos/vote/domain/vote/repository/empty.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.ceos.vote.domain.vote.repository; - -public class empty { -} From 32559c17e42f94acd6a757ba790b69f57c569646 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Mon, 30 Dec 2024 00:43:55 +0900 Subject: [PATCH 21/73] feat: leader vote create service --- .../controller/LeaderVoteController.java | 23 ++++++++++++++++++ .../request/LeaderVoteCreateRequestDto.java | 17 +++++++++++++ .../leaderVote/service/LeaderVoteService.java | 24 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java create mode 100644 src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteCreateRequestDto.java create mode 100644 src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java b/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java new file mode 100644 index 0000000..d17824a --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java @@ -0,0 +1,23 @@ +package com.ceos.vote.domain.leaderVote.controller; + +import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteCreateRequestDto; +import com.ceos.vote.domain.leaderVote.service.LeaderVoteService; +import com.ceos.vote.global.common.response.CommonResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "Leader Vote", description = "leader vote api") +@RestController +@RequiredArgsConstructor +@RequestMapping("api/v1/leader") +public class LeaderVoteController { + private final LeaderVoteService leaderVoteService; + + public CommonResponse createLeaderVote(@RequestBody LeaderVoteCreateRequestDto requestDto){ + leaderVoteService.createLeaderVote(requestDto); + return new CommonResponse<>("new leader vote created"); + } +} diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteCreateRequestDto.java b/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteCreateRequestDto.java new file mode 100644 index 0000000..607d799 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteCreateRequestDto.java @@ -0,0 +1,17 @@ +package com.ceos.vote.domain.leaderVote.dto.request; + +import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; +import com.ceos.vote.domain.leaderVote.entity.LeaderVote; +import com.ceos.vote.domain.users.entity.Users; + +public record LeaderVoteCreateRequestDto ( + Users user, + LeaderCandidate leaderCandidate +){ + public LeaderVote toEntity(){ + return LeaderVote.builder() + .user(user) + .leaderCandidate(leaderCandidate) + .build(); + } +} diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java new file mode 100644 index 0000000..9802f3a --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java @@ -0,0 +1,24 @@ +package com.ceos.vote.domain.leaderVote.service; + +import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteCreateRequestDto; +import com.ceos.vote.domain.leaderVote.entity.LeaderVote; +import com.ceos.vote.domain.leaderVote.repository.LeaderVoteRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class LeaderVoteService { + private LeaderVoteRepository leaderVoteRepository; + + /* + leaderVote 생성 + */ + @Transactional + public void createLeaderVote(LeaderVoteCreateRequestDto requestDto) { + final LeaderVote leaderVote = requestDto.toEntity(); + leaderVoteRepository.save(leaderVote); + } +} From d670aec0826054d997c56ddd8628d4717ef3d84c Mon Sep 17 00:00:00 2001 From: limgahyun Date: Mon, 30 Dec 2024 01:01:52 +0900 Subject: [PATCH 22/73] =?UTF-8?q?feat:=20leader=20vote=20by=20user=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/LeaderVoteController.java | 10 +++++++++- .../request/LeaderVoteCreateRequestDto.java | 3 +++ .../response/LeaderVoteByUserResponseDto.java | 17 +++++++++++++++++ .../leaderVote/service/LeaderVoteService.java | 19 +++++++++++++++++++ .../vote/global/exception/ExceptionCode.java | 5 +++-- 5 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderVoteByUserResponseDto.java diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java b/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java index d17824a..05b06fe 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java @@ -1,10 +1,12 @@ package com.ceos.vote.domain.leaderVote.controller; import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteCreateRequestDto; +import com.ceos.vote.domain.leaderVote.dto.response.LeaderVoteByUserResponseDto; import com.ceos.vote.domain.leaderVote.service.LeaderVoteService; import com.ceos.vote.global.common.response.CommonResponse; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -18,6 +20,12 @@ public class LeaderVoteController { public CommonResponse createLeaderVote(@RequestBody LeaderVoteCreateRequestDto requestDto){ leaderVoteService.createLeaderVote(requestDto); - return new CommonResponse<>("new leader vote created"); + return new CommonResponse<>("new leader vote를 생성하였습니다."); } + + public CommonResponse getLeaderVoteByUser(@PathVariable Long userId){ + final LeaderVoteByUserResponseDto leaderVoteByUser = leaderVoteService.getLeaderVoteByUser(userId); + return new CommonResponse<>(leaderVoteByUser, "해당 user의 투표 결과 조회에 성공하였습니다."); + } + } diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteCreateRequestDto.java b/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteCreateRequestDto.java index 607d799..e3bf890 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteCreateRequestDto.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteCreateRequestDto.java @@ -3,9 +3,12 @@ import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; import com.ceos.vote.domain.leaderVote.entity.LeaderVote; import com.ceos.vote.domain.users.entity.Users; +import jakarta.validation.constraints.NotNull; public record LeaderVoteCreateRequestDto ( + @NotNull Users user, + @NotNull LeaderCandidate leaderCandidate ){ public LeaderVote toEntity(){ diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderVoteByUserResponseDto.java b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderVoteByUserResponseDto.java new file mode 100644 index 0000000..420b7a8 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderVoteByUserResponseDto.java @@ -0,0 +1,17 @@ +package com.ceos.vote.domain.leaderVote.dto.response; + +import com.ceos.vote.domain.leaderVote.entity.LeaderVote; +import jakarta.validation.constraints.NotNull; + +public record LeaderVoteByUserResponseDto ( + @NotNull + Boolean is_voting, + Long leader_Candidate_id +){ + public static LeaderVoteByUserResponseDto from(LeaderVote leaderVote, Boolean is_voting){ + return new LeaderVoteByUserResponseDto( + is_voting, + leaderVote.getLeaderCandidate().getId() + ); + } +} diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java index 9802f3a..c96a8db 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java @@ -1,8 +1,11 @@ package com.ceos.vote.domain.leaderVote.service; import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteCreateRequestDto; +import com.ceos.vote.domain.leaderVote.dto.response.LeaderVoteByUserResponseDto; import com.ceos.vote.domain.leaderVote.entity.LeaderVote; import com.ceos.vote.domain.leaderVote.repository.LeaderVoteRepository; +import com.ceos.vote.global.exception.ApplicationException; +import com.ceos.vote.global.exception.ExceptionCode; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -13,6 +16,10 @@ public class LeaderVoteService { private LeaderVoteRepository leaderVoteRepository; + public LeaderVote findLeaderVoteById(Long id) { + return leaderVoteRepository.findById(id) + .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_LEADER_VOTE)); + } /* leaderVote 생성 */ @@ -21,4 +28,16 @@ public void createLeaderVote(LeaderVoteCreateRequestDto requestDto) { final LeaderVote leaderVote = requestDto.toEntity(); leaderVoteRepository.save(leaderVote); } + + /* + leaderVote 전체 결과 조회 + */ + public LeaderVoteByUserResponseDto getLeaderVoteByUser(final Long userId) { + try { + final LeaderVote leaderVote = findLeaderVoteById(userId); + return LeaderVoteByUserResponseDto.from(leaderVote, true); + } catch (Exception e) { + return LeaderVoteByUserResponseDto.from(null, false); + } + } } diff --git a/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java b/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java index 79a0332..8ffa7b9 100644 --- a/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java +++ b/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java @@ -19,11 +19,12 @@ public enum ExceptionCode { FORBIDDEN_EXCEPTION(HttpStatus.FORBIDDEN, 2005, "인가되지 않는 요청입니다."), ALREADY_EXIST_EXCEPTION(HttpStatus.BAD_REQUEST, 2006, "이미 존재하는 리소스입니다."), INVALID_SORT_EXCEPTION(HttpStatus.BAD_REQUEST, 2007, "올바르지 않은 정렬 값입니다."), - BAD_REQUEST_ERROR(HttpStatus.BAD_REQUEST, 2008, "잘못된 요청입니다."); + BAD_REQUEST_ERROR(HttpStatus.BAD_REQUEST, 2008, "잘못된 요청입니다."), // 3000: - // 4000: + // 4000: Leader Vote Error + NOT_FOUND_LEADER_VOTE(HttpStatus.NOT_FOUND, 4001, "해당 leader vote가 존재하지 않습니다."); // 5000: From fcf76ab8dcf05e9da8c03591e4050858d0fe8b89 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Mon, 30 Dec 2024 02:24:39 +0900 Subject: [PATCH 23/73] feat: leader vote update service --- .../service/LeaderCandidateService.java | 23 +++++++++++ .../controller/LeaderVoteController.java | 18 +++++++-- .../request/LeaderVoteCreateRequestDto.java | 9 ++--- .../request/LeaderVoteUpdateRequestDto.java | 10 +++++ .../domain/leaderVote/entity/LeaderVote.java | 4 ++ .../repository/LeaderVoteRepository.java | 3 ++ .../leaderVote/service/LeaderVoteService.java | 39 ++++++++++++++++--- .../vote/global/exception/ExceptionCode.java | 3 +- 8 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/ceos/vote/domain/leaderCandidate/service/LeaderCandidateService.java create mode 100644 src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteUpdateRequestDto.java diff --git a/src/main/java/com/ceos/vote/domain/leaderCandidate/service/LeaderCandidateService.java b/src/main/java/com/ceos/vote/domain/leaderCandidate/service/LeaderCandidateService.java new file mode 100644 index 0000000..5cfe72d --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/leaderCandidate/service/LeaderCandidateService.java @@ -0,0 +1,23 @@ +package com.ceos.vote.domain.leaderCandidate.service; + +import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; +import com.ceos.vote.domain.leaderCandidate.repository.LeaderCandidateRepository; +import com.ceos.vote.global.exception.ApplicationException; +import com.ceos.vote.global.exception.ExceptionCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class LeaderCandidateService { + + private final LeaderCandidateRepository leaderCandidateRepository; + + public LeaderCandidate findLeaderCandidateById(Long id) { + return leaderCandidateRepository.findById(id) + .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_LEADER_CANDIDATE)); + } + +} diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java b/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java index 05b06fe..3c35681 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java @@ -1,15 +1,14 @@ package com.ceos.vote.domain.leaderVote.controller; import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteCreateRequestDto; +import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteUpdateRequestDto; import com.ceos.vote.domain.leaderVote.dto.response.LeaderVoteByUserResponseDto; import com.ceos.vote.domain.leaderVote.service.LeaderVoteService; import com.ceos.vote.global.common.response.CommonResponse; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @Tag(name = "Leader Vote", description = "leader vote api") @RestController @@ -18,14 +17,25 @@ public class LeaderVoteController { private final LeaderVoteService leaderVoteService; + @Operation(summary = "leader vote 생성") + @PostMapping public CommonResponse createLeaderVote(@RequestBody LeaderVoteCreateRequestDto requestDto){ leaderVoteService.createLeaderVote(requestDto); return new CommonResponse<>("new leader vote를 생성하였습니다."); } + @Operation(summary = "user의 leader vote 조회") + @GetMapping("/{userId}") public CommonResponse getLeaderVoteByUser(@PathVariable Long userId){ final LeaderVoteByUserResponseDto leaderVoteByUser = leaderVoteService.getLeaderVoteByUser(userId); return new CommonResponse<>(leaderVoteByUser, "해당 user의 투표 결과 조회에 성공하였습니다."); } + @Operation(summary = "user의 leader vote 수정") + @PutMapping("/{userId}") + public CommonResponse updateLeaderVote(@PathVariable Long userId, @RequestBody LeaderVoteUpdateRequestDto requestDto){ + leaderVoteService.updateLeaderVoteByUser(userId, requestDto); + return new CommonResponse<>("leader vote 수정을 성공하였습니다."); + } + } diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteCreateRequestDto.java b/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteCreateRequestDto.java index e3bf890..e9a5bbd 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteCreateRequestDto.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteCreateRequestDto.java @@ -3,15 +3,12 @@ import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; import com.ceos.vote.domain.leaderVote.entity.LeaderVote; import com.ceos.vote.domain.users.entity.Users; -import jakarta.validation.constraints.NotNull; public record LeaderVoteCreateRequestDto ( - @NotNull - Users user, - @NotNull - LeaderCandidate leaderCandidate + Long user_id, + Long leader_candidate_id ){ - public LeaderVote toEntity(){ + public LeaderVote toEntity(Users user, LeaderCandidate leaderCandidate) { return LeaderVote.builder() .user(user) .leaderCandidate(leaderCandidate) diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteUpdateRequestDto.java b/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteUpdateRequestDto.java new file mode 100644 index 0000000..5009f5e --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteUpdateRequestDto.java @@ -0,0 +1,10 @@ +package com.ceos.vote.domain.leaderVote.dto.request; + +import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; +import jakarta.validation.constraints.NotNull; + +public record LeaderVoteUpdateRequestDto( + @NotNull + Long leader_candidate_id +){ +} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/entity/LeaderVote.java b/src/main/java/com/ceos/vote/domain/leaderVote/entity/LeaderVote.java index 7fb0ff2..ef808ae 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/entity/LeaderVote.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/entity/LeaderVote.java @@ -28,6 +28,10 @@ public class LeaderVote extends BaseEntity { @JoinColumn(name = "leader_candidate_id") private LeaderCandidate leaderCandidate; + public void setLeaderCandidate(LeaderCandidate leaderCandidate) { + this.leaderCandidate = leaderCandidate; + } + @Builder public LeaderVote(Users user, LeaderCandidate leaderCandidate) { this.user = user; diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java b/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java index 57d8451..928121b 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java @@ -4,6 +4,9 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface LeaderVoteRepository extends JpaRepository { + Optional findByUserId(Long userId); } \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java index c96a8db..8fa3af1 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java @@ -1,9 +1,15 @@ package com.ceos.vote.domain.leaderVote.service; +import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; +import com.ceos.vote.domain.leaderCandidate.repository.LeaderCandidateRepository; +import com.ceos.vote.domain.leaderCandidate.service.LeaderCandidateService; import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteCreateRequestDto; +import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteUpdateRequestDto; import com.ceos.vote.domain.leaderVote.dto.response.LeaderVoteByUserResponseDto; import com.ceos.vote.domain.leaderVote.entity.LeaderVote; import com.ceos.vote.domain.leaderVote.repository.LeaderVoteRepository; +import com.ceos.vote.domain.users.entity.Users; +import com.ceos.vote.domain.users.repository.UserRepository; import com.ceos.vote.global.exception.ApplicationException; import com.ceos.vote.global.exception.ExceptionCode; import lombok.RequiredArgsConstructor; @@ -14,30 +20,53 @@ @Transactional(readOnly = true) @RequiredArgsConstructor public class LeaderVoteService { + private final UserRepository userRepository; + private final LeaderCandidateRepository leaderCandidateRepository; + private final LeaderCandidateService leaderCandidateService; private LeaderVoteRepository leaderVoteRepository; - public LeaderVote findLeaderVoteById(Long id) { - return leaderVoteRepository.findById(id) + public LeaderVote findLeaderVoteByUserId(Long id) { + return leaderVoteRepository.findByUserId(id) .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_LEADER_VOTE)); } + /* leaderVote 생성 */ @Transactional public void createLeaderVote(LeaderVoteCreateRequestDto requestDto) { - final LeaderVote leaderVote = requestDto.toEntity(); + Users user = userRepository.findById(requestDto.user_id()) + .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_EXCEPTION)); + + LeaderCandidate leaderCandidate = leaderCandidateService.findLeaderCandidateById(requestDto.leader_candidate_id()); + + final LeaderVote leaderVote = requestDto.toEntity(user, leaderCandidate); leaderVoteRepository.save(leaderVote); } /* - leaderVote 전체 결과 조회 + user의 leaderVote 결과 조회 */ public LeaderVoteByUserResponseDto getLeaderVoteByUser(final Long userId) { try { - final LeaderVote leaderVote = findLeaderVoteById(userId); + final LeaderVote leaderVote = findLeaderVoteByUserId(userId); return LeaderVoteByUserResponseDto.from(leaderVote, true); } catch (Exception e) { return LeaderVoteByUserResponseDto.from(null, false); } } + + /* + user의 leaderVote 수정 + */ + @Transactional + public void updateLeaderVoteByUser(final Long userId, final LeaderVoteUpdateRequestDto requestDto) { + LeaderVote leaderVote = findLeaderVoteByUserId(userId); + + LeaderCandidate leaderCandidate = leaderCandidateService.findLeaderCandidateById(requestDto.leader_candidate_id()); + + leaderVote.setLeaderCandidate(leaderCandidate); + leaderVoteRepository.save(leaderVote); + + } } diff --git a/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java b/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java index 8ffa7b9..03356d7 100644 --- a/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java +++ b/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java @@ -24,7 +24,8 @@ public enum ExceptionCode { // 3000: // 4000: Leader Vote Error - NOT_FOUND_LEADER_VOTE(HttpStatus.NOT_FOUND, 4001, "해당 leader vote가 존재하지 않습니다."); + NOT_FOUND_LEADER_CANDIDATE(HttpStatus.NOT_FOUND, 4001, "해당 leader candidate가 존재하지 않습니다."), + NOT_FOUND_LEADER_VOTE(HttpStatus.NOT_FOUND, 4002, "해당 leader vote가 존재하지 않습니다."); // 5000: From e07b1f3a8d76a6fd1ebfab9cd04d45133b2b0bcd Mon Sep 17 00:00:00 2001 From: limgahyun Date: Mon, 30 Dec 2024 02:27:30 +0900 Subject: [PATCH 24/73] refactor: findUserById service --- .../domain/leaderVote/service/LeaderVoteService.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java index 8fa3af1..0e2f6ad 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java @@ -30,14 +30,18 @@ public LeaderVote findLeaderVoteByUserId(Long id) { .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_LEADER_VOTE)); } + // 추후 UserService에 통합 후 삭제 + public Users findUserById(Long id) { + return userRepository.findById(id) + .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_EXCEPTION)); + } + /* leaderVote 생성 */ @Transactional public void createLeaderVote(LeaderVoteCreateRequestDto requestDto) { - Users user = userRepository.findById(requestDto.user_id()) - .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_EXCEPTION)); - + Users user = findUserById(requestDto.user_id()); LeaderCandidate leaderCandidate = leaderCandidateService.findLeaderCandidateById(requestDto.leader_candidate_id()); final LeaderVote leaderVote = requestDto.toEntity(user, leaderCandidate); From 6f17311753d5c9f25e6bca4b919cea329de97b8b Mon Sep 17 00:00:00 2001 From: limgahyun Date: Mon, 30 Dec 2024 03:00:50 +0900 Subject: [PATCH 25/73] feat: get leader vote result service --- .../controller/LeaderVoteController.java | 6 ++++ .../dto/response/LeaderResultResponseDto.java | 13 ++++++++ .../LeaderVoteFinalResultResponseDto.java | 15 +++++++++ .../repository/LeaderVoteRepository.java | 3 ++ .../leaderVote/service/LeaderVoteService.java | 31 +++++++++++++++++++ 5 files changed, 68 insertions(+) create mode 100644 src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderResultResponseDto.java create mode 100644 src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderVoteFinalResultResponseDto.java diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java b/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java index 3c35681..59b0233 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java @@ -3,6 +3,7 @@ import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteCreateRequestDto; import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteUpdateRequestDto; import com.ceos.vote.domain.leaderVote.dto.response.LeaderVoteByUserResponseDto; +import com.ceos.vote.domain.leaderVote.dto.response.LeaderVoteFinalResultResponseDto; import com.ceos.vote.domain.leaderVote.service.LeaderVoteService; import com.ceos.vote.global.common.response.CommonResponse; import io.swagger.v3.oas.annotations.Operation; @@ -24,6 +25,11 @@ public CommonResponse createLeaderVote(@RequestBody LeaderVoteCreateReques return new CommonResponse<>("new leader vote를 생성하였습니다."); } + public CommonResponse getLeaderVoteResult(){ + final LeaderVoteFinalResultResponseDto resultResponseDto = leaderVoteService.getLeaderVoteFinalResult(); + return new CommonResponse<>(resultResponseDto, "전체 투표 결과 조회를 성공하였습니다."); + } + @Operation(summary = "user의 leader vote 조회") @GetMapping("/{userId}") public CommonResponse getLeaderVoteByUser(@PathVariable Long userId){ diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderResultResponseDto.java b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderResultResponseDto.java new file mode 100644 index 0000000..d5f9af7 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderResultResponseDto.java @@ -0,0 +1,13 @@ +package com.ceos.vote.domain.leaderVote.dto.response; + +public record LeaderResultResponseDto ( + String leader_name, + Long count +){ + public static LeaderResultResponseDto from(String leader_name, Long count){ + return new LeaderResultResponseDto( + leader_name, + count + ); + } +} diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderVoteFinalResultResponseDto.java b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderVoteFinalResultResponseDto.java new file mode 100644 index 0000000..ca37ef6 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderVoteFinalResultResponseDto.java @@ -0,0 +1,15 @@ +package com.ceos.vote.domain.leaderVote.dto.response; + +import java.util.List; + +public record LeaderVoteFinalResultResponseDto ( + Long total_count, + List results +){ + public static LeaderVoteFinalResultResponseDto from(Long total_count, List results){ + return new LeaderVoteFinalResultResponseDto( + total_count, + results + ); + } +} diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java b/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java index 928121b..63c9c4c 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java @@ -1,5 +1,6 @@ package com.ceos.vote.domain.leaderVote.repository; +import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; import com.ceos.vote.domain.leaderVote.entity.LeaderVote; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -9,4 +10,6 @@ @Repository public interface LeaderVoteRepository extends JpaRepository { Optional findByUserId(Long userId); + + Long countByLeaderCandidate(LeaderCandidate leaderCandidate); } \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java index 0e2f6ad..cecfb77 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java @@ -5,7 +5,9 @@ import com.ceos.vote.domain.leaderCandidate.service.LeaderCandidateService; import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteCreateRequestDto; import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteUpdateRequestDto; +import com.ceos.vote.domain.leaderVote.dto.response.LeaderResultResponseDto; import com.ceos.vote.domain.leaderVote.dto.response.LeaderVoteByUserResponseDto; +import com.ceos.vote.domain.leaderVote.dto.response.LeaderVoteFinalResultResponseDto; import com.ceos.vote.domain.leaderVote.entity.LeaderVote; import com.ceos.vote.domain.leaderVote.repository.LeaderVoteRepository; import com.ceos.vote.domain.users.entity.Users; @@ -16,6 +18,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.List; + @Service @Transactional(readOnly = true) @RequiredArgsConstructor @@ -73,4 +78,30 @@ public void updateLeaderVoteByUser(final Long userId, final LeaderVoteUpdateRequ leaderVoteRepository.save(leaderVote); } + + /* + user의 leaderVote 결과 조회 + */ + public LeaderResultResponseDto getLeaderResult(LeaderCandidate leaderCandidate) { + final String name = leaderCandidate.getName(); + final Long count = leaderVoteRepository.countByLeaderCandidate(leaderCandidate); + + return LeaderResultResponseDto.from(name, count); + } + + /* + leaderVote 전체 결과 조회 + */ + public LeaderVoteFinalResultResponseDto getLeaderVoteFinalResult() { + List leaderCandidates = leaderCandidateRepository.findAll(); + List resultList = new ArrayList<>(); + + // 각 leader candidate의 결과를 LeaderResultResponseDto로 변환 + leaderCandidates.forEach(leaderCandidate -> resultList.add(getLeaderResult(leaderCandidate))); + + // 총 투표수 조회 + final Long count = leaderVoteRepository.count(); + + return LeaderVoteFinalResultResponseDto.from(count, resultList); + } } From 43bf47c5a0d7333e5e5db2abdb07b58833d83123 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Mon, 30 Dec 2024 03:32:29 +0900 Subject: [PATCH 26/73] feat: get leader candidates service --- .../entity/LeaderCandidate.java | 5 +-- .../repository/LeaderCandidateRepository.java | 4 +++ .../controller/LeaderVoteController.java | 13 ++++++++ .../response/LeaderCandidateResponseDto.java | 15 +++++++++ .../response/PartCandidateResponseDto.java | 17 ++++++++++ .../leaderVote/service/LeaderVoteService.java | 31 +++++++++++++++++-- .../vote/domain/users/enumerate/Part.java | 20 ++++++++++++ 7 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderCandidateResponseDto.java create mode 100644 src/main/java/com/ceos/vote/domain/leaderVote/dto/response/PartCandidateResponseDto.java create mode 100644 src/main/java/com/ceos/vote/domain/users/enumerate/Part.java diff --git a/src/main/java/com/ceos/vote/domain/leaderCandidate/entity/LeaderCandidate.java b/src/main/java/com/ceos/vote/domain/leaderCandidate/entity/LeaderCandidate.java index f995033..6b16322 100644 --- a/src/main/java/com/ceos/vote/domain/leaderCandidate/entity/LeaderCandidate.java +++ b/src/main/java/com/ceos/vote/domain/leaderCandidate/entity/LeaderCandidate.java @@ -1,5 +1,6 @@ package com.ceos.vote.domain.leaderCandidate.entity; +import com.ceos.vote.domain.users.enumerate.Part; import com.ceos.vote.global.BaseEntity; import jakarta.persistence.*; import lombok.AccessLevel; @@ -19,13 +20,13 @@ public class LeaderCandidate extends BaseEntity { private Long id; @Column(length = 32) - private String part; + private Part part; @Column private String name; @Builder - public LeaderCandidate(String part, String name) { + public LeaderCandidate(Part part, String name) { this.part = part; this.name = name; } diff --git a/src/main/java/com/ceos/vote/domain/leaderCandidate/repository/LeaderCandidateRepository.java b/src/main/java/com/ceos/vote/domain/leaderCandidate/repository/LeaderCandidateRepository.java index 5f2d244..de67b3a 100644 --- a/src/main/java/com/ceos/vote/domain/leaderCandidate/repository/LeaderCandidateRepository.java +++ b/src/main/java/com/ceos/vote/domain/leaderCandidate/repository/LeaderCandidateRepository.java @@ -1,9 +1,13 @@ package com.ceos.vote.domain.leaderCandidate.repository; import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; +import com.ceos.vote.domain.users.enumerate.Part; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface LeaderCandidateRepository extends JpaRepository { + List findByPart(Part part); } \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java b/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java index 59b0233..407a78e 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java @@ -2,8 +2,10 @@ import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteCreateRequestDto; import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteUpdateRequestDto; +import com.ceos.vote.domain.leaderVote.dto.response.LeaderCandidateResponseDto; import com.ceos.vote.domain.leaderVote.dto.response.LeaderVoteByUserResponseDto; import com.ceos.vote.domain.leaderVote.dto.response.LeaderVoteFinalResultResponseDto; +import com.ceos.vote.domain.leaderVote.dto.response.PartCandidateResponseDto; import com.ceos.vote.domain.leaderVote.service.LeaderVoteService; import com.ceos.vote.global.common.response.CommonResponse; import io.swagger.v3.oas.annotations.Operation; @@ -11,6 +13,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; +import java.util.List; + @Tag(name = "Leader Vote", description = "leader vote api") @RestController @RequiredArgsConstructor @@ -18,6 +22,13 @@ public class LeaderVoteController { private final LeaderVoteService leaderVoteService; + @Operation(summary = "leader candidate 전체 조회") + @GetMapping("/candidate") + public CommonResponse> getLeaderCandidates(){ + final List leaderCandidates = leaderVoteService.findLeaderCandidates(); + return new CommonResponse<>(leaderCandidates, "전체 파트장 후보 조회를 성공하였습니다."); + } + @Operation(summary = "leader vote 생성") @PostMapping public CommonResponse createLeaderVote(@RequestBody LeaderVoteCreateRequestDto requestDto){ @@ -25,6 +36,8 @@ public CommonResponse createLeaderVote(@RequestBody LeaderVoteCreateReques return new CommonResponse<>("new leader vote를 생성하였습니다."); } + @Operation(summary = "leader vote 전체 결과 조회") + @GetMapping public CommonResponse getLeaderVoteResult(){ final LeaderVoteFinalResultResponseDto resultResponseDto = leaderVoteService.getLeaderVoteFinalResult(); return new CommonResponse<>(resultResponseDto, "전체 투표 결과 조회를 성공하였습니다."); diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderCandidateResponseDto.java b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderCandidateResponseDto.java new file mode 100644 index 0000000..b470d2e --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderCandidateResponseDto.java @@ -0,0 +1,15 @@ +package com.ceos.vote.domain.leaderVote.dto.response; + +import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; + +public record LeaderCandidateResponseDto ( + Long id, + String name +){ + public static LeaderCandidateResponseDto from(LeaderCandidate leaderCandidate) { + return new LeaderCandidateResponseDto( + leaderCandidate.getId(), + leaderCandidate.getName() + ); + } +} diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/PartCandidateResponseDto.java b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/PartCandidateResponseDto.java new file mode 100644 index 0000000..37a3e93 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/PartCandidateResponseDto.java @@ -0,0 +1,17 @@ +package com.ceos.vote.domain.leaderVote.dto.response; + +import com.ceos.vote.domain.users.enumerate.Part; + +import java.util.List; + +public record PartCandidateResponseDto ( + String part, + List leaderCandidates +){ + public static PartCandidateResponseDto from(String part, List leaderCandidates) { + return new PartCandidateResponseDto( + part, + leaderCandidates + ); + } +} diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java index cecfb77..31ba473 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java @@ -5,12 +5,11 @@ import com.ceos.vote.domain.leaderCandidate.service.LeaderCandidateService; import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteCreateRequestDto; import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteUpdateRequestDto; -import com.ceos.vote.domain.leaderVote.dto.response.LeaderResultResponseDto; -import com.ceos.vote.domain.leaderVote.dto.response.LeaderVoteByUserResponseDto; -import com.ceos.vote.domain.leaderVote.dto.response.LeaderVoteFinalResultResponseDto; +import com.ceos.vote.domain.leaderVote.dto.response.*; import com.ceos.vote.domain.leaderVote.entity.LeaderVote; import com.ceos.vote.domain.leaderVote.repository.LeaderVoteRepository; import com.ceos.vote.domain.users.entity.Users; +import com.ceos.vote.domain.users.enumerate.Part; import com.ceos.vote.domain.users.repository.UserRepository; import com.ceos.vote.global.exception.ApplicationException; import com.ceos.vote.global.exception.ExceptionCode; @@ -19,7 +18,12 @@ import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; + +import static com.ceos.vote.domain.users.enumerate.Part.*; +import static com.ceos.vote.domain.users.enumerate.Part.DESIGN; @Service @Transactional(readOnly = true) @@ -41,6 +45,27 @@ public Users findUserById(Long id) { .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_EXCEPTION)); } + /* + 파트별 leader candidate 조회 + */ + public List findLeaderCandidatesByPart(Part part) { + List leaderCandidates = leaderCandidateRepository.findByPart(part); + + return leaderCandidates.stream() + .map(LeaderCandidateResponseDto::from) + .toList(); + } + + /* + 전체 leader candidate 조회 + */ + public List findLeaderCandidates() { + // part별 leader candidate을 조회하여 PartCandidateResponseDto로 반환 + return Arrays.stream(Part.values()) + .map(part -> PartCandidateResponseDto.from(part.getDescription(), findLeaderCandidatesByPart(part))) + .collect(Collectors.toList()); + } + /* leaderVote 생성 */ diff --git a/src/main/java/com/ceos/vote/domain/users/enumerate/Part.java b/src/main/java/com/ceos/vote/domain/users/enumerate/Part.java new file mode 100644 index 0000000..b4d23c6 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/users/enumerate/Part.java @@ -0,0 +1,20 @@ +package com.ceos.vote.domain.users.enumerate; + +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public enum Part { + + BE("BE"), + FE("FE"), + DESIGN("DESIGN"), + PLANNING("PLANNING"); + + private final String description; + + Part(String description) { + this.description = description; + } +} From ce0bb28cdc1bc3b2e57a768e3baa181580370066 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Mon, 30 Dec 2024 04:00:33 +0900 Subject: [PATCH 27/73] =?UTF-8?q?feat:=20=ED=8C=8C=ED=8A=B8=EB=B3=84=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20=EA=B2=B0=EA=B3=BC=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/LeaderVoteController.java | 6 +-- .../LeaderVoteFinalResultResponseDto.java | 6 ++- .../response/PartCandidateResponseDto.java | 4 +- .../repository/LeaderVoteRepository.java | 1 + .../leaderVote/service/LeaderVoteService.java | 46 +++++++++++-------- 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java b/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java index 407a78e..3138cb4 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java @@ -38,9 +38,9 @@ public CommonResponse createLeaderVote(@RequestBody LeaderVoteCreateReques @Operation(summary = "leader vote 전체 결과 조회") @GetMapping - public CommonResponse getLeaderVoteResult(){ - final LeaderVoteFinalResultResponseDto resultResponseDto = leaderVoteService.getLeaderVoteFinalResult(); - return new CommonResponse<>(resultResponseDto, "전체 투표 결과 조회를 성공하였습니다."); + public CommonResponse> getLeaderVoteResult(){ + final List results = leaderVoteService.getLeaderVoteFinalResult(); + return new CommonResponse<>(results, "전체 투표 결과 조회를 성공하였습니다."); } @Operation(summary = "user의 leader vote 조회") diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderVoteFinalResultResponseDto.java b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderVoteFinalResultResponseDto.java index ca37ef6..ecc3977 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderVoteFinalResultResponseDto.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderVoteFinalResultResponseDto.java @@ -1,13 +1,17 @@ package com.ceos.vote.domain.leaderVote.dto.response; +import com.ceos.vote.domain.users.enumerate.Part; + import java.util.List; public record LeaderVoteFinalResultResponseDto ( + String part, Long total_count, List results ){ - public static LeaderVoteFinalResultResponseDto from(Long total_count, List results){ + public static LeaderVoteFinalResultResponseDto from(Part part, Long total_count, List results){ return new LeaderVoteFinalResultResponseDto( + part.getDescription(), total_count, results ); diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/PartCandidateResponseDto.java b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/PartCandidateResponseDto.java index 37a3e93..a148aae 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/PartCandidateResponseDto.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/PartCandidateResponseDto.java @@ -8,9 +8,9 @@ public record PartCandidateResponseDto ( String part, List leaderCandidates ){ - public static PartCandidateResponseDto from(String part, List leaderCandidates) { + public static PartCandidateResponseDto from(Part part, List leaderCandidates) { return new PartCandidateResponseDto( - part, + part.getDescription(), leaderCandidates ); } diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java b/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java index 63c9c4c..d4c595a 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java @@ -2,6 +2,7 @@ import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; import com.ceos.vote.domain.leaderVote.entity.LeaderVote; +import com.ceos.vote.domain.users.enumerate.Part; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java index 31ba473..52f72ea 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java @@ -22,9 +22,6 @@ import java.util.List; import java.util.stream.Collectors; -import static com.ceos.vote.domain.users.enumerate.Part.*; -import static com.ceos.vote.domain.users.enumerate.Part.DESIGN; - @Service @Transactional(readOnly = true) @RequiredArgsConstructor @@ -62,7 +59,7 @@ public List findLeaderCandidatesByPart(Part part) { public List findLeaderCandidates() { // part별 leader candidate을 조회하여 PartCandidateResponseDto로 반환 return Arrays.stream(Part.values()) - .map(part -> PartCandidateResponseDto.from(part.getDescription(), findLeaderCandidatesByPart(part))) + .map(part -> PartCandidateResponseDto.from(part, findLeaderCandidatesByPart(part))) .collect(Collectors.toList()); } @@ -105,28 +102,39 @@ public void updateLeaderVoteByUser(final Long userId, final LeaderVoteUpdateRequ } /* - user의 leaderVote 결과 조회 + 파트별 leaderVote 결과 조회 */ - public LeaderResultResponseDto getLeaderResult(LeaderCandidate leaderCandidate) { - final String name = leaderCandidate.getName(); - final Long count = leaderVoteRepository.countByLeaderCandidate(leaderCandidate); + public List getLeaderResult(Part part) { + // 파트에 속한 모든 리더 후보 조회 + List leaderCandidates = leaderCandidateRepository.findByPart(part); - return LeaderResultResponseDto.from(name, count); + // 각 후보의 투표 결과 반환 + return leaderCandidates.stream() + .map(candidate -> LeaderResultResponseDto.from(candidate.getName(), leaderVoteRepository.countByLeaderCandidate(candidate))) + .toList(); } /* - leaderVote 전체 결과 조회 - */ - public LeaderVoteFinalResultResponseDto getLeaderVoteFinalResult() { - List leaderCandidates = leaderCandidateRepository.findAll(); - List resultList = new ArrayList<>(); + 파트별 leaderVote 총 투표수 조회 + */ + public long getTotalVotesByPart(Part part) { + // 파트에 속한 모든 리더 후보 조회 + List leaderCandidates = leaderCandidateRepository.findByPart(part); - // 각 leader candidate의 결과를 LeaderResultResponseDto로 변환 - leaderCandidates.forEach(leaderCandidate -> resultList.add(getLeaderResult(leaderCandidate))); + // 각 후보의 투표수를 합산 + return leaderCandidates.stream() + .mapToLong(candidate -> leaderVoteRepository.countByLeaderCandidate(candidate)) + .sum(); + } - // 총 투표수 조회 - final Long count = leaderVoteRepository.count(); + /* + leaderVote 전체 결과 조회 + */ + public List getLeaderVoteFinalResult() { - return LeaderVoteFinalResultResponseDto.from(count, resultList); + // part별 투표 결과를 LeaderVoteFinalResultResponseDto로 반환 + return Arrays.stream(Part.values()) + .map(part -> LeaderVoteFinalResultResponseDto.from(part, getTotalVotesByPart(part), getLeaderResult(part))) + .collect(Collectors.toList()); } } From b66f45549b9399b8e065b30f58cff2a3567836a6 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Mon, 30 Dec 2024 04:01:44 +0900 Subject: [PATCH 28/73] fix: remove unused import --- .../vote/domain/leaderVote/controller/LeaderVoteController.java | 1 - .../vote/domain/leaderVote/repository/LeaderVoteRepository.java | 1 - .../ceos/vote/domain/leaderVote/service/LeaderVoteService.java | 1 - 3 files changed, 3 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java b/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java index 3138cb4..4c7e22f 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java @@ -2,7 +2,6 @@ import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteCreateRequestDto; import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteUpdateRequestDto; -import com.ceos.vote.domain.leaderVote.dto.response.LeaderCandidateResponseDto; import com.ceos.vote.domain.leaderVote.dto.response.LeaderVoteByUserResponseDto; import com.ceos.vote.domain.leaderVote.dto.response.LeaderVoteFinalResultResponseDto; import com.ceos.vote.domain.leaderVote.dto.response.PartCandidateResponseDto; diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java b/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java index d4c595a..63c9c4c 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java @@ -2,7 +2,6 @@ import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; import com.ceos.vote.domain.leaderVote.entity.LeaderVote; -import com.ceos.vote.domain.users.enumerate.Part; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java index 52f72ea..2c406b9 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java @@ -17,7 +17,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; From 55840a9e0f0941c0e21d849442a67ac28cdfb771 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Mon, 30 Dec 2024 04:12:21 +0900 Subject: [PATCH 29/73] =?UTF-8?q?feat:=20=EB=93=9D=ED=91=9C=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EB=82=B4=EB=A6=BC=EC=B0=A8=EC=88=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../leaderVote/dto/response/LeaderResultResponseDto.java | 4 ++++ .../vote/domain/leaderVote/service/LeaderVoteService.java | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderResultResponseDto.java b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderResultResponseDto.java index d5f9af7..6ab11d7 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderResultResponseDto.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderResultResponseDto.java @@ -4,6 +4,10 @@ public record LeaderResultResponseDto ( String leader_name, Long count ){ + public Long getVoteCount() { + return count; + } + public static LeaderResultResponseDto from(String leader_name, Long count){ return new LeaderResultResponseDto( leader_name, diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java index 2c406b9..5b435d5 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java @@ -18,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -107,9 +108,10 @@ public List getLeaderResult(Part part) { // 파트에 속한 모든 리더 후보 조회 List leaderCandidates = leaderCandidateRepository.findByPart(part); - // 각 후보의 투표 결과 반환 + // 각 후보의 투표 결과 반환 및 득표수 내림차순 정렬 return leaderCandidates.stream() .map(candidate -> LeaderResultResponseDto.from(candidate.getName(), leaderVoteRepository.countByLeaderCandidate(candidate))) + .sorted(Comparator.comparingLong(LeaderResultResponseDto::getVoteCount).reversed()) // 내림차순 정렬 .toList(); } From a69112fce2e3bcbc75982c6081a53e99a956082a Mon Sep 17 00:00:00 2001 From: limgahyun Date: Mon, 30 Dec 2024 04:18:48 +0900 Subject: [PATCH 30/73] =?UTF-8?q?feat:=20team=20votin=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20entity=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/leaderVote/entity/LeaderVote.java | 2 +- .../teamCandidate/entity/TeamCandidate.java | 25 ++++++++++++ .../repository/TeamCandidateRepository.java | 9 +++++ .../vote/domain/teamVote/entity/TeamVote.java | 39 +++++++++++++++++++ .../repository/TeamVoteRepository.java | 9 +++++ 5 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/ceos/vote/domain/teamCandidate/entity/TeamCandidate.java create mode 100644 src/main/java/com/ceos/vote/domain/teamCandidate/repository/TeamCandidateRepository.java create mode 100644 src/main/java/com/ceos/vote/domain/teamVote/entity/TeamVote.java create mode 100644 src/main/java/com/ceos/vote/domain/teamVote/repository/TeamVoteRepository.java diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/entity/LeaderVote.java b/src/main/java/com/ceos/vote/domain/leaderVote/entity/LeaderVote.java index ef808ae..995ef0f 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/entity/LeaderVote.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/entity/LeaderVote.java @@ -17,7 +17,7 @@ public class LeaderVote extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "leader_candidate_id") + @Column(name = "leader_vote_id") private Long id; @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/java/com/ceos/vote/domain/teamCandidate/entity/TeamCandidate.java b/src/main/java/com/ceos/vote/domain/teamCandidate/entity/TeamCandidate.java new file mode 100644 index 0000000..2c389a2 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/teamCandidate/entity/TeamCandidate.java @@ -0,0 +1,25 @@ +package com.ceos.vote.domain.teamCandidate.entity; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class TeamCandidate { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "leader_candidate_id") + private Long id; + + @Column + private String name; + + @Builder + public TeamCandidate(String name) { + this.name = name; + } +} diff --git a/src/main/java/com/ceos/vote/domain/teamCandidate/repository/TeamCandidateRepository.java b/src/main/java/com/ceos/vote/domain/teamCandidate/repository/TeamCandidateRepository.java new file mode 100644 index 0000000..3c5ed94 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/teamCandidate/repository/TeamCandidateRepository.java @@ -0,0 +1,9 @@ +package com.ceos.vote.domain.teamCandidate.repository; + +import com.ceos.vote.domain.teamCandidate.entity.TeamCandidate; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TeamCandidateRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/teamVote/entity/TeamVote.java b/src/main/java/com/ceos/vote/domain/teamVote/entity/TeamVote.java new file mode 100644 index 0000000..eedaa2e --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/teamVote/entity/TeamVote.java @@ -0,0 +1,39 @@ +package com.ceos.vote.domain.teamVote.entity; + +import com.ceos.vote.domain.teamCandidate.entity.TeamCandidate; +import com.ceos.vote.domain.users.entity.Users; +import com.ceos.vote.global.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class TeamVote extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "team_vote_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private Users user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "team_candidate_id") + private TeamCandidate teamCandidate; + + public void setTeamCandidate(TeamCandidate teamCandidate) { + this.teamCandidate = teamCandidate; + } + + @Builder + public TeamVote(Users user, TeamCandidate teamCandidate) { + this.user = user; + this.teamCandidate = teamCandidate; + } +} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/teamVote/repository/TeamVoteRepository.java b/src/main/java/com/ceos/vote/domain/teamVote/repository/TeamVoteRepository.java new file mode 100644 index 0000000..96b194d --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/teamVote/repository/TeamVoteRepository.java @@ -0,0 +1,9 @@ +package com.ceos.vote.domain.teamVote.repository; + +import com.ceos.vote.domain.teamVote.entity.TeamVote; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TeamVoteRepository extends JpaRepository { +} \ No newline at end of file From 6de9a2964b6c98cea542e98a0f6f445abed35231 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Mon, 30 Dec 2024 04:27:52 +0900 Subject: [PATCH 31/73] feat: get team candidates service --- .../request/LeaderVoteCreateRequestDto.java | 2 ++ .../controller/TeamVoteController.java | 30 ++++++++++++++++++ .../dto/request/TeamVoteCreateRequestDto.java | 19 ++++++++++++ .../dto/request/TeamVoteUpdateRequestDto.java | 9 ++++++ .../response/TeamCandidateResponseDto.java | 15 +++++++++ .../teamVote/service/TeamVoteService.java | 31 +++++++++++++++++++ 6 files changed, 106 insertions(+) create mode 100644 src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java create mode 100644 src/main/java/com/ceos/vote/domain/teamVote/dto/request/TeamVoteCreateRequestDto.java create mode 100644 src/main/java/com/ceos/vote/domain/teamVote/dto/request/TeamVoteUpdateRequestDto.java create mode 100644 src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamCandidateResponseDto.java create mode 100644 src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteCreateRequestDto.java b/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteCreateRequestDto.java index e9a5bbd..956bdeb 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteCreateRequestDto.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/dto/request/LeaderVoteCreateRequestDto.java @@ -3,9 +3,11 @@ import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; import com.ceos.vote.domain.leaderVote.entity.LeaderVote; import com.ceos.vote.domain.users.entity.Users; +import jakarta.validation.constraints.NotNull; public record LeaderVoteCreateRequestDto ( Long user_id, + @NotNull Long leader_candidate_id ){ public LeaderVote toEntity(Users user, LeaderCandidate leaderCandidate) { diff --git a/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java b/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java new file mode 100644 index 0000000..5be2dee --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java @@ -0,0 +1,30 @@ +package com.ceos.vote.domain.teamVote.controller; + +import com.ceos.vote.domain.leaderVote.dto.response.PartCandidateResponseDto; +import com.ceos.vote.domain.leaderVote.service.LeaderVoteService; +import com.ceos.vote.domain.teamVote.dto.response.TeamCandidateResponseDto; +import com.ceos.vote.domain.teamVote.service.TeamVoteService; +import com.ceos.vote.global.common.response.CommonResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Tag(name = "Team Vote", description = "team vote api") +@RestController +@RequiredArgsConstructor +@RequestMapping("api/v1/team") +public class TeamVoteController { + private final TeamVoteService teamVoteService; + + @Operation(summary = "team candidate 전체 조회") + @GetMapping("/candidate") + public CommonResponse> getTeamCandidates(){ + final List teamCandidates = teamVoteService.findTeamCandidates(); + return new CommonResponse<>(teamCandidates, "전체 후보팀 조회를 성공하였습니다."); + } +} diff --git a/src/main/java/com/ceos/vote/domain/teamVote/dto/request/TeamVoteCreateRequestDto.java b/src/main/java/com/ceos/vote/domain/teamVote/dto/request/TeamVoteCreateRequestDto.java new file mode 100644 index 0000000..902e03b --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/teamVote/dto/request/TeamVoteCreateRequestDto.java @@ -0,0 +1,19 @@ +package com.ceos.vote.domain.teamVote.dto.request; + +import com.ceos.vote.domain.teamCandidate.entity.TeamCandidate; +import com.ceos.vote.domain.teamVote.entity.TeamVote; +import com.ceos.vote.domain.users.entity.Users; +import jakarta.validation.constraints.NotNull; + +public record TeamVoteCreateRequestDto( + Long user_id, + @NotNull + Long team_candidate_id +){ + public TeamVote toEntity(Users user, TeamCandidate teamCandidate) { + return TeamVote.builder() + .user(user) + .teamCandidate(teamCandidate) + .build(); + } +} diff --git a/src/main/java/com/ceos/vote/domain/teamVote/dto/request/TeamVoteUpdateRequestDto.java b/src/main/java/com/ceos/vote/domain/teamVote/dto/request/TeamVoteUpdateRequestDto.java new file mode 100644 index 0000000..4623712 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/teamVote/dto/request/TeamVoteUpdateRequestDto.java @@ -0,0 +1,9 @@ +package com.ceos.vote.domain.teamVote.dto.request; + +import jakarta.validation.constraints.NotNull; + +public record TeamVoteUpdateRequestDto( + @NotNull + Long team_candidate_id +){ +} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamCandidateResponseDto.java b/src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamCandidateResponseDto.java new file mode 100644 index 0000000..17d12df --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamCandidateResponseDto.java @@ -0,0 +1,15 @@ +package com.ceos.vote.domain.teamVote.dto.response; + +import com.ceos.vote.domain.teamCandidate.entity.TeamCandidate; + +public record TeamCandidateResponseDto ( + Long id, + String name +){ + public static TeamCandidateResponseDto from(TeamCandidate teamCandidate) { + return new TeamCandidateResponseDto( + teamCandidate.getId(), + teamCandidate.getName() + ); + } +} diff --git a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java new file mode 100644 index 0000000..7ecc9ef --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java @@ -0,0 +1,31 @@ +package com.ceos.vote.domain.teamVote.service; + +import com.ceos.vote.domain.teamCandidate.entity.TeamCandidate; +import com.ceos.vote.domain.teamCandidate.repository.TeamCandidateRepository; +import com.ceos.vote.domain.teamVote.dto.response.TeamCandidateResponseDto; +import com.ceos.vote.domain.teamVote.repository.TeamVoteRepository; +import com.ceos.vote.domain.users.enumerate.Part; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class TeamVoteService { + private final TeamVoteRepository teamVoteRepository; + private final TeamCandidateRepository teamCandidateRepository; + + /* + 전체 team candidate 조회 + */ + public List findTeamCandidates() { + List teamCandidates = teamCandidateRepository.findAll(); + + return teamCandidates.stream() + .map(TeamCandidateResponseDto::from) + .toList(); + } +} From d5184d42f0db2e45d3e3f71999717faeaeaf2f31 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Mon, 30 Dec 2024 04:34:03 +0900 Subject: [PATCH 32/73] feat: create team vote service --- .../service/TeamCandidateService.java | 24 +++++++++++++++++++ .../controller/TeamVoteController.java | 16 ++++++++----- .../teamVote/service/TeamVoteService.java | 20 ++++++++++++++++ .../vote/global/exception/ExceptionCode.java | 6 +++-- 4 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/ceos/vote/domain/teamCandidate/service/TeamCandidateService.java diff --git a/src/main/java/com/ceos/vote/domain/teamCandidate/service/TeamCandidateService.java b/src/main/java/com/ceos/vote/domain/teamCandidate/service/TeamCandidateService.java new file mode 100644 index 0000000..5eb1b28 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/teamCandidate/service/TeamCandidateService.java @@ -0,0 +1,24 @@ +package com.ceos.vote.domain.teamCandidate.service; + +import com.ceos.vote.domain.teamCandidate.entity.TeamCandidate; +import com.ceos.vote.domain.teamCandidate.repository.TeamCandidateRepository; +import com.ceos.vote.global.exception.ApplicationException; +import com.ceos.vote.global.exception.ExceptionCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class TeamCandidateService { + + + private final TeamCandidateRepository teamCandidateRepository; + + public TeamCandidate findTeamCandidateById(Long id) { + return teamCandidateRepository.findById(id) + .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_TEAM_CANDIDATE)); + } + +} diff --git a/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java b/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java index 5be2dee..835b2f3 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java @@ -1,16 +1,13 @@ package com.ceos.vote.domain.teamVote.controller; -import com.ceos.vote.domain.leaderVote.dto.response.PartCandidateResponseDto; -import com.ceos.vote.domain.leaderVote.service.LeaderVoteService; -import com.ceos.vote.domain.teamVote.dto.response.TeamCandidateResponseDto; +import com.ceos.vote.domain.teamVote.dto.request.TeamVoteCreateRequestDto; import com.ceos.vote.domain.teamVote.service.TeamVoteService; +import com.ceos.vote.domain.teamVote.dto.response.TeamCandidateResponseDto; import com.ceos.vote.global.common.response.CommonResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.List; @@ -27,4 +24,11 @@ public CommonResponse> getTeamCandidates(){ final List teamCandidates = teamVoteService.findTeamCandidates(); return new CommonResponse<>(teamCandidates, "전체 후보팀 조회를 성공하였습니다."); } + + @Operation(summary = "team vote 생성") + @PostMapping + public CommonResponse createTeamVote(@RequestBody TeamVoteCreateRequestDto requestDto){ + teamVoteService.createTeamVote(requestDto); + return new CommonResponse<>("new team vote를 생성하였습니다."); + } } diff --git a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java index 7ecc9ef..b1a7f6c 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java @@ -1,9 +1,15 @@ package com.ceos.vote.domain.teamVote.service; +import com.ceos.vote.domain.leaderVote.service.LeaderVoteService; +import com.ceos.vote.domain.teamCandidate.entity.TeamCandidate; +import com.ceos.vote.domain.teamCandidate.service.TeamCandidateService; +import com.ceos.vote.domain.teamVote.dto.request.TeamVoteCreateRequestDto; +import com.ceos.vote.domain.teamVote.entity.TeamVote; import com.ceos.vote.domain.teamCandidate.entity.TeamCandidate; import com.ceos.vote.domain.teamCandidate.repository.TeamCandidateRepository; import com.ceos.vote.domain.teamVote.dto.response.TeamCandidateResponseDto; import com.ceos.vote.domain.teamVote.repository.TeamVoteRepository; +import com.ceos.vote.domain.users.entity.Users; import com.ceos.vote.domain.users.enumerate.Part; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -17,6 +23,8 @@ public class TeamVoteService { private final TeamVoteRepository teamVoteRepository; private final TeamCandidateRepository teamCandidateRepository; + private final LeaderVoteService leaderVoteService; + private final TeamCandidateService teamCandidateService; /* 전체 team candidate 조회 @@ -28,4 +36,16 @@ public List findTeamCandidates() { .map(TeamCandidateResponseDto::from) .toList(); } + + /* + teamVote 생성 + */ + @Transactional + public void createTeamVote(TeamVoteCreateRequestDto requestDto) { + Users user = leaderVoteService.findUserById(requestDto.user_id()); + TeamCandidate teamCandidate = teamCandidateService.findTeamCandidateById(requestDto.team_candidate_id()); + + final TeamVote teamVote = requestDto.toEntity(user, teamCandidate); + teamVoteRepository.save(teamVote); + } } diff --git a/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java b/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java index 03356d7..82b4131 100644 --- a/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java +++ b/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java @@ -25,9 +25,11 @@ public enum ExceptionCode { // 4000: Leader Vote Error NOT_FOUND_LEADER_CANDIDATE(HttpStatus.NOT_FOUND, 4001, "해당 leader candidate가 존재하지 않습니다."), - NOT_FOUND_LEADER_VOTE(HttpStatus.NOT_FOUND, 4002, "해당 leader vote가 존재하지 않습니다."); + NOT_FOUND_LEADER_VOTE(HttpStatus.NOT_FOUND, 4002, "해당 leader vote가 존재하지 않습니다."), - // 5000: + // 5000: Team Vote Error + NOT_FOUND_TEAM_CANDIDATE(HttpStatus.NOT_FOUND, 4001, "해당 team candidate가 존재하지 않습니다."), + NOT_FOUND_TEAM_VOTE(HttpStatus.NOT_FOUND, 4002, "해당 team vote가 존재하지 않습니다."); // 6000: From 7e1c9a059638356be83d636cc26a621aec68df89 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Mon, 30 Dec 2024 04:43:09 +0900 Subject: [PATCH 33/73] feat: get team vote by user service --- .../controller/LeaderVoteController.java | 2 +- .../response/LeaderVoteByUserResponseDto.java | 4 ++-- .../controller/TeamVoteController.java | 8 +++++++ .../response/TeamVoteByUserResponseDto.java | 17 ++++++++++++++ .../repository/TeamVoteRepository.java | 4 ++++ .../teamVote/service/TeamVoteService.java | 23 +++++++++++++++++-- 6 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamVoteByUserResponseDto.java diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java b/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java index 4c7e22f..13f7475 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/controller/LeaderVoteController.java @@ -42,7 +42,7 @@ public CommonResponse> getLeaderVoteResul return new CommonResponse<>(results, "전체 투표 결과 조회를 성공하였습니다."); } - @Operation(summary = "user의 leader vote 조회") + @Operation(summary = "user의 leader vote 결과 조회") @GetMapping("/{userId}") public CommonResponse getLeaderVoteByUser(@PathVariable Long userId){ final LeaderVoteByUserResponseDto leaderVoteByUser = leaderVoteService.getLeaderVoteByUser(userId); diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderVoteByUserResponseDto.java b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderVoteByUserResponseDto.java index 420b7a8..59a1db4 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderVoteByUserResponseDto.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/dto/response/LeaderVoteByUserResponseDto.java @@ -10,8 +10,8 @@ public record LeaderVoteByUserResponseDto ( ){ public static LeaderVoteByUserResponseDto from(LeaderVote leaderVote, Boolean is_voting){ return new LeaderVoteByUserResponseDto( - is_voting, - leaderVote.getLeaderCandidate().getId() + is_voting, + leaderVote != null ? leaderVote.getLeaderCandidate().getId() : null ); } } diff --git a/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java b/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java index 835b2f3..4f03717 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java @@ -1,5 +1,6 @@ package com.ceos.vote.domain.teamVote.controller; +import com.ceos.vote.domain.teamVote.dto.response.TeamVoteByUserResponseDto; import com.ceos.vote.domain.teamVote.dto.request.TeamVoteCreateRequestDto; import com.ceos.vote.domain.teamVote.service.TeamVoteService; import com.ceos.vote.domain.teamVote.dto.response.TeamCandidateResponseDto; @@ -31,4 +32,11 @@ public CommonResponse createTeamVote(@RequestBody TeamVoteCreateRequestDto teamVoteService.createTeamVote(requestDto); return new CommonResponse<>("new team vote를 생성하였습니다."); } + + @Operation(summary = "user의 team vote 결과 조회") + @GetMapping("/{userId}") + public CommonResponse getTeamVoteByUser(@PathVariable Long userId){ + final TeamVoteByUserResponseDto teamVoteByUser = teamVoteService.getTeamVoteByUser(userId); + return new CommonResponse<>(teamVoteByUser, "해당 user의 투표 결과 조회에 성공하였습니다."); + } } diff --git a/src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamVoteByUserResponseDto.java b/src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamVoteByUserResponseDto.java new file mode 100644 index 0000000..168f631 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamVoteByUserResponseDto.java @@ -0,0 +1,17 @@ +package com.ceos.vote.domain.teamVote.dto.response; + +import com.ceos.vote.domain.teamVote.entity.TeamVote; +import jakarta.validation.constraints.NotNull; + +public record TeamVoteByUserResponseDto( + @NotNull + Boolean is_voting, + Long team_Candidate_id +){ + public static TeamVoteByUserResponseDto from(TeamVote teamVote, Boolean is_voting){ + return new TeamVoteByUserResponseDto( + is_voting, + teamVote != null ? teamVote.getTeamCandidate().getId() : null + ); + } +} diff --git a/src/main/java/com/ceos/vote/domain/teamVote/repository/TeamVoteRepository.java b/src/main/java/com/ceos/vote/domain/teamVote/repository/TeamVoteRepository.java index 96b194d..2d66f61 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/repository/TeamVoteRepository.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/repository/TeamVoteRepository.java @@ -4,6 +4,10 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface TeamVoteRepository extends JpaRepository { + Optional findByUserId(Long userId); + } \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java index b1a7f6c..caae8c5 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java @@ -1,16 +1,18 @@ package com.ceos.vote.domain.teamVote.service; +import com.ceos.vote.domain.leaderVote.entity.LeaderVote; import com.ceos.vote.domain.leaderVote.service.LeaderVoteService; import com.ceos.vote.domain.teamCandidate.entity.TeamCandidate; import com.ceos.vote.domain.teamCandidate.service.TeamCandidateService; import com.ceos.vote.domain.teamVote.dto.request.TeamVoteCreateRequestDto; +import com.ceos.vote.domain.teamVote.dto.response.TeamVoteByUserResponseDto; import com.ceos.vote.domain.teamVote.entity.TeamVote; -import com.ceos.vote.domain.teamCandidate.entity.TeamCandidate; import com.ceos.vote.domain.teamCandidate.repository.TeamCandidateRepository; import com.ceos.vote.domain.teamVote.dto.response.TeamCandidateResponseDto; import com.ceos.vote.domain.teamVote.repository.TeamVoteRepository; import com.ceos.vote.domain.users.entity.Users; -import com.ceos.vote.domain.users.enumerate.Part; +import com.ceos.vote.global.exception.ApplicationException; +import com.ceos.vote.global.exception.ExceptionCode; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,6 +28,11 @@ public class TeamVoteService { private final LeaderVoteService leaderVoteService; private final TeamCandidateService teamCandidateService; + public TeamVote findTeamVoteByUserId(Long id) { + return teamVoteRepository.findByUserId(id) + .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_TEAM_VOTE)); + } + /* 전체 team candidate 조회 */ @@ -48,4 +55,16 @@ public void createTeamVote(TeamVoteCreateRequestDto requestDto) { final TeamVote teamVote = requestDto.toEntity(user, teamCandidate); teamVoteRepository.save(teamVote); } + + /* + user의 teamVote 결과 조회 + */ + public TeamVoteByUserResponseDto getTeamVoteByUser(final Long userId) { + try { + final TeamVote teamVote = findTeamVoteByUserId(userId); + return TeamVoteByUserResponseDto.from(teamVote, true); + } catch (Exception e) { + return TeamVoteByUserResponseDto.from(null, false); + } + } } From 0d54290e2eaf88520b914e5de90a016b4ac730b1 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Mon, 30 Dec 2024 04:45:41 +0900 Subject: [PATCH 34/73] feat: update team vote service --- .../teamVote/controller/TeamVoteController.java | 8 ++++++++ .../teamVote/service/TeamVoteService.java | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java b/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java index 4f03717..8334792 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java @@ -1,5 +1,6 @@ package com.ceos.vote.domain.teamVote.controller; +import com.ceos.vote.domain.teamVote.dto.request.TeamVoteUpdateRequestDto; import com.ceos.vote.domain.teamVote.dto.response.TeamVoteByUserResponseDto; import com.ceos.vote.domain.teamVote.dto.request.TeamVoteCreateRequestDto; import com.ceos.vote.domain.teamVote.service.TeamVoteService; @@ -39,4 +40,11 @@ public CommonResponse getTeamVoteByUser(@PathVariable final TeamVoteByUserResponseDto teamVoteByUser = teamVoteService.getTeamVoteByUser(userId); return new CommonResponse<>(teamVoteByUser, "해당 user의 투표 결과 조회에 성공하였습니다."); } + + @Operation(summary = "user의 team vote 수정") + @PutMapping("/{userId}") + public CommonResponse updateTeamVote(@PathVariable Long userId, @RequestBody TeamVoteUpdateRequestDto requestDto){ + teamVoteService.updateTeamVoteByUser(userId, requestDto); + return new CommonResponse<>("team vote 수정을 성공하였습니다."); + } } diff --git a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java index caae8c5..48e9ee5 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java @@ -1,10 +1,13 @@ package com.ceos.vote.domain.teamVote.service; +import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; +import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteUpdateRequestDto; import com.ceos.vote.domain.leaderVote.entity.LeaderVote; import com.ceos.vote.domain.leaderVote.service.LeaderVoteService; import com.ceos.vote.domain.teamCandidate.entity.TeamCandidate; import com.ceos.vote.domain.teamCandidate.service.TeamCandidateService; import com.ceos.vote.domain.teamVote.dto.request.TeamVoteCreateRequestDto; +import com.ceos.vote.domain.teamVote.dto.request.TeamVoteUpdateRequestDto; import com.ceos.vote.domain.teamVote.dto.response.TeamVoteByUserResponseDto; import com.ceos.vote.domain.teamVote.entity.TeamVote; import com.ceos.vote.domain.teamCandidate.repository.TeamCandidateRepository; @@ -67,4 +70,18 @@ public TeamVoteByUserResponseDto getTeamVoteByUser(final Long userId) { return TeamVoteByUserResponseDto.from(null, false); } } + + /* + user의 teamVote 수정 + */ + @Transactional + public void updateTeamVoteByUser(final Long userId, final TeamVoteUpdateRequestDto requestDto) { + TeamVote teamVote = findTeamVoteByUserId(userId); + + TeamCandidate teamCandidate = teamCandidateService.findTeamCandidateById(requestDto.team_candidate_id()); + + teamVote.setTeamCandidate(teamCandidate); + teamVoteRepository.save(teamVote); + + } } From b353c9ffcea20f67605a140f0aeb1fb8977118e9 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Mon, 30 Dec 2024 05:03:36 +0900 Subject: [PATCH 35/73] feat: get team vote result service --- .../controller/TeamVoteController.java | 9 ++++++ .../dto/response/TeamResultResponseDto.java | 17 +++++++++++ .../TeamVoteFinalResultResponseDto.java | 15 ++++++++++ .../repository/TeamVoteRepository.java | 3 +- .../teamVote/service/TeamVoteService.java | 29 +++++++++++++++++++ 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamResultResponseDto.java create mode 100644 src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamVoteFinalResultResponseDto.java diff --git a/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java b/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java index 8334792..eea21fd 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java @@ -1,8 +1,10 @@ package com.ceos.vote.domain.teamVote.controller; +import com.ceos.vote.domain.leaderVote.dto.response.LeaderVoteFinalResultResponseDto; import com.ceos.vote.domain.teamVote.dto.request.TeamVoteUpdateRequestDto; import com.ceos.vote.domain.teamVote.dto.response.TeamVoteByUserResponseDto; import com.ceos.vote.domain.teamVote.dto.request.TeamVoteCreateRequestDto; +import com.ceos.vote.domain.teamVote.dto.response.TeamVoteFinalResultResponseDto; import com.ceos.vote.domain.teamVote.service.TeamVoteService; import com.ceos.vote.domain.teamVote.dto.response.TeamCandidateResponseDto; import com.ceos.vote.global.common.response.CommonResponse; @@ -34,6 +36,13 @@ public CommonResponse createTeamVote(@RequestBody TeamVoteCreateRequestDto return new CommonResponse<>("new team vote를 생성하였습니다."); } + @Operation(summary = "team vote 전체 결과 조회") + @GetMapping + public CommonResponse getTeamVoteResult(){ + final TeamVoteFinalResultResponseDto result = teamVoteService.getTeamVoteFinalResult(); + return new CommonResponse<>(result, "전체 투표 결과 조회를 성공하였습니다."); + } + @Operation(summary = "user의 team vote 결과 조회") @GetMapping("/{userId}") public CommonResponse getTeamVoteByUser(@PathVariable Long userId){ diff --git a/src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamResultResponseDto.java b/src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamResultResponseDto.java new file mode 100644 index 0000000..ab48556 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamResultResponseDto.java @@ -0,0 +1,17 @@ +package com.ceos.vote.domain.teamVote.dto.response; + +public record TeamResultResponseDto( + String team_name, + Long count +){ + public Long getVoteCount() { + return count; + } + + public static TeamResultResponseDto from(String team_name, Long count){ + return new TeamResultResponseDto( + team_name, + count + ); + } +} diff --git a/src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamVoteFinalResultResponseDto.java b/src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamVoteFinalResultResponseDto.java new file mode 100644 index 0000000..ae63581 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamVoteFinalResultResponseDto.java @@ -0,0 +1,15 @@ +package com.ceos.vote.domain.teamVote.dto.response; + +import java.util.List; + +public record TeamVoteFinalResultResponseDto( + Long total_count, + List results +){ + public static TeamVoteFinalResultResponseDto from(Long total_count, List results){ + return new TeamVoteFinalResultResponseDto( + total_count, + results + ); + } +} diff --git a/src/main/java/com/ceos/vote/domain/teamVote/repository/TeamVoteRepository.java b/src/main/java/com/ceos/vote/domain/teamVote/repository/TeamVoteRepository.java index 2d66f61..c8077d9 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/repository/TeamVoteRepository.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/repository/TeamVoteRepository.java @@ -1,5 +1,6 @@ package com.ceos.vote.domain.teamVote.repository; +import com.ceos.vote.domain.teamCandidate.entity.TeamCandidate; import com.ceos.vote.domain.teamVote.entity.TeamVote; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -9,5 +10,5 @@ @Repository public interface TeamVoteRepository extends JpaRepository { Optional findByUserId(Long userId); - + Long countByTeamCandidate(TeamCandidate teamCandidate); } \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java index 48e9ee5..769fc43 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java @@ -2,24 +2,29 @@ import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteUpdateRequestDto; +import com.ceos.vote.domain.leaderVote.dto.response.LeaderResultResponseDto; import com.ceos.vote.domain.leaderVote.entity.LeaderVote; import com.ceos.vote.domain.leaderVote.service.LeaderVoteService; import com.ceos.vote.domain.teamCandidate.entity.TeamCandidate; import com.ceos.vote.domain.teamCandidate.service.TeamCandidateService; import com.ceos.vote.domain.teamVote.dto.request.TeamVoteCreateRequestDto; import com.ceos.vote.domain.teamVote.dto.request.TeamVoteUpdateRequestDto; +import com.ceos.vote.domain.teamVote.dto.response.TeamResultResponseDto; import com.ceos.vote.domain.teamVote.dto.response.TeamVoteByUserResponseDto; +import com.ceos.vote.domain.teamVote.dto.response.TeamVoteFinalResultResponseDto; import com.ceos.vote.domain.teamVote.entity.TeamVote; import com.ceos.vote.domain.teamCandidate.repository.TeamCandidateRepository; import com.ceos.vote.domain.teamVote.dto.response.TeamCandidateResponseDto; import com.ceos.vote.domain.teamVote.repository.TeamVoteRepository; import com.ceos.vote.domain.users.entity.Users; +import com.ceos.vote.domain.users.enumerate.Part; import com.ceos.vote.global.exception.ApplicationException; import com.ceos.vote.global.exception.ExceptionCode; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Comparator; import java.util.List; @Service @@ -84,4 +89,28 @@ public void updateTeamVoteByUser(final Long userId, final TeamVoteUpdateRequestD teamVoteRepository.save(teamVote); } + + /* + 팀별 teamVote 결과 조회 + */ + public List getTeamResult() { + // 모든 후보팀 조회 + List teamCandidates = teamCandidateRepository.findAll(); + + // 각 후보의 투표 결과 반환 및 득표수 내림차순 정렬 + return teamCandidates.stream() + .map(candidate -> TeamResultResponseDto.from(candidate.getName(), teamVoteRepository.countByTeamCandidate(candidate))) + .sorted(Comparator.comparingLong(TeamResultResponseDto::getVoteCount).reversed()) // 내림차순 정렬 + .toList(); + } + + /* + teamVote 전체 결과 조회 + */ + public TeamVoteFinalResultResponseDto getTeamVoteFinalResult(){ + List results= getTeamResult(); + Long total_count = teamVoteRepository.count(); + + return TeamVoteFinalResultResponseDto.from(total_count, results); + } } From 5462b63481aaff962fcf0d99c5971b063c1a5ae0 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Mon, 30 Dec 2024 05:05:12 +0900 Subject: [PATCH 36/73] chore: remove unused import --- .../vote/domain/teamVote/controller/TeamVoteController.java | 1 - .../ceos/vote/domain/teamVote/service/TeamVoteService.java | 5 ----- 2 files changed, 6 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java b/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java index eea21fd..fe29821 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/controller/TeamVoteController.java @@ -1,6 +1,5 @@ package com.ceos.vote.domain.teamVote.controller; -import com.ceos.vote.domain.leaderVote.dto.response.LeaderVoteFinalResultResponseDto; import com.ceos.vote.domain.teamVote.dto.request.TeamVoteUpdateRequestDto; import com.ceos.vote.domain.teamVote.dto.response.TeamVoteByUserResponseDto; import com.ceos.vote.domain.teamVote.dto.request.TeamVoteCreateRequestDto; diff --git a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java index 769fc43..774b097 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java @@ -1,9 +1,5 @@ package com.ceos.vote.domain.teamVote.service; -import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; -import com.ceos.vote.domain.leaderVote.dto.request.LeaderVoteUpdateRequestDto; -import com.ceos.vote.domain.leaderVote.dto.response.LeaderResultResponseDto; -import com.ceos.vote.domain.leaderVote.entity.LeaderVote; import com.ceos.vote.domain.leaderVote.service.LeaderVoteService; import com.ceos.vote.domain.teamCandidate.entity.TeamCandidate; import com.ceos.vote.domain.teamCandidate.service.TeamCandidateService; @@ -17,7 +13,6 @@ import com.ceos.vote.domain.teamVote.dto.response.TeamCandidateResponseDto; import com.ceos.vote.domain.teamVote.repository.TeamVoteRepository; import com.ceos.vote.domain.users.entity.Users; -import com.ceos.vote.domain.users.enumerate.Part; import com.ceos.vote.global.exception.ApplicationException; import com.ceos.vote.global.exception.ExceptionCode; import lombok.RequiredArgsConstructor; From 9433316b35c43073b018510caa892495f07ee8bb Mon Sep 17 00:00:00 2001 From: Seoah <0325sandy@ewhain.net> Date: Mon, 30 Dec 2024 11:11:27 +0900 Subject: [PATCH 37/73] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=B3=B4=EC=95=88=20=EB=B0=8F=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 ++ .../vote/domain/auth/JwtTokenProvider.java | 3 +- .../users/controller/UserApiController.java | 23 -------- .../users/controller/UserController.java | 48 ++++++++++++++++ .../users/controller/UserViewController.java | 18 ------ .../vote/domain/users/dto/AddUserRequest.java | 11 ---- .../ceos/vote/domain/users/dto/SignInDto.java | 15 +++++ .../ceos/vote/domain/users/dto/SignUpDto.java | 37 ++++++++++++ .../ceos/vote/domain/users/dto/UserDto.java | 39 +++++++++++++ .../ceos/vote/domain/users/entity/User.java | 37 ++++++++---- .../users/repository/UserRepository.java | 5 +- .../users/service/UserDetailService.java | 22 ++++++- .../domain/users/service/UserService.java | 57 +++++++++++++++---- .../ceos/vote/domain/utils/SecurityUtil.java | 15 +++++ .../vote/global/config/SecurityConfig.java | 48 +++++++--------- .../vote/utils/PasswordEncoderGenerator.java | 11 ++++ src/main/resources/application.yml | 3 - 17 files changed, 286 insertions(+), 111 deletions(-) delete mode 100644 src/main/java/com/ceos/vote/domain/users/controller/UserApiController.java create mode 100644 src/main/java/com/ceos/vote/domain/users/controller/UserController.java delete mode 100644 src/main/java/com/ceos/vote/domain/users/controller/UserViewController.java delete mode 100644 src/main/java/com/ceos/vote/domain/users/dto/AddUserRequest.java create mode 100644 src/main/java/com/ceos/vote/domain/users/dto/SignInDto.java create mode 100644 src/main/java/com/ceos/vote/domain/users/dto/SignUpDto.java create mode 100644 src/main/java/com/ceos/vote/domain/users/dto/UserDto.java create mode 100644 src/main/java/com/ceos/vote/domain/utils/SecurityUtil.java create mode 100644 src/main/java/com/ceos/vote/utils/PasswordEncoderGenerator.java diff --git a/build.gradle b/build.gradle index eecaefa..8d2f7ca 100644 --- a/build.gradle +++ b/build.gradle @@ -51,6 +51,11 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.11.5' implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + //mysql + implementation 'com.mysql:mysql-connector-j' + + } tasks.named('compileJava') { diff --git a/src/main/java/com/ceos/vote/domain/auth/JwtTokenProvider.java b/src/main/java/com/ceos/vote/domain/auth/JwtTokenProvider.java index ac72545..85b82a5 100644 --- a/src/main/java/com/ceos/vote/domain/auth/JwtTokenProvider.java +++ b/src/main/java/com/ceos/vote/domain/auth/JwtTokenProvider.java @@ -28,7 +28,7 @@ public class JwtTokenProvider { private final Key key; //application.yml에서 secret 값 가져와서 key에 저장 - public JwtTokenProvider(@Value("${jwt.secret}") String secretKey){ + public JwtTokenProvider(@Value("${JWT_SECRET_KEY}") String secretKey){ byte[] keyBytes = Decoders.BASE64.decode(secretKey); this.key = Keys.hmacShaKeyFor(keyBytes); } @@ -105,6 +105,7 @@ public boolean validateToken(String token) { return false; } + //accessToken private Claims parseClaims(String accessToken) { try { return Jwts.parserBuilder() diff --git a/src/main/java/com/ceos/vote/domain/users/controller/UserApiController.java b/src/main/java/com/ceos/vote/domain/users/controller/UserApiController.java deleted file mode 100644 index 705d597..0000000 --- a/src/main/java/com/ceos/vote/domain/users/controller/UserApiController.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.ceos.vote.domain.users.controller; - -import com.ceos.vote.domain.users.dto.AddUserRequest; -import com.ceos.vote.domain.users.service.UserService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RestController; - -@RequiredArgsConstructor -@Controller -public class UserApiController { - - private final UserService userService; - - @PostMapping("/user") - public String signup(AddUserRequest request){ - userService.save(request); - return "redirect:/login"; - } - -} diff --git a/src/main/java/com/ceos/vote/domain/users/controller/UserController.java b/src/main/java/com/ceos/vote/domain/users/controller/UserController.java new file mode 100644 index 0000000..cc03eaa --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/users/controller/UserController.java @@ -0,0 +1,48 @@ +package com.ceos.vote.domain.users.controller; + +import com.ceos.vote.domain.auth.JwtToken; +import com.ceos.vote.domain.users.dto.SignInDto; +import com.ceos.vote.domain.users.dto.SignUpDto; +import com.ceos.vote.domain.users.dto.UserDto; +import com.ceos.vote.domain.users.service.UserService; +import com.ceos.vote.domain.utils.SecurityUtil; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("/api/v1/auth") +@RequiredArgsConstructor +public class UserController { + + private final UserService userService; + + @PostMapping("/sign-up") + public ResponseEntity signUp(@RequestBody SignUpDto signUpDto) { + UserDto userDto = userService.signUp(signUpDto); + return ResponseEntity.ok(userDto); + } + + @PostMapping("/sign-in") + public JwtToken signIn(@RequestBody SignInDto signinDto){ + + String username = signinDto.getUsername(); + String password = signinDto.getPassword(); + JwtToken jwtToken = userService.signIn(username, password); + + log.info("request username = {}, password = {}", username, password); + log.info("jwtToken accessToken = {}, refreshToken = {}", jwtToken.getAccessToken(), jwtToken.getRefreshToken()); + + return jwtToken; + } + + @PostMapping("/test") + public String test(){ + return SecurityUtil.getCurrentUsername(); + } +} diff --git a/src/main/java/com/ceos/vote/domain/users/controller/UserViewController.java b/src/main/java/com/ceos/vote/domain/users/controller/UserViewController.java deleted file mode 100644 index eeb3860..0000000 --- a/src/main/java/com/ceos/vote/domain/users/controller/UserViewController.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.ceos.vote.domain.users.controller; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; - -@Controller -public class UserViewController { - - @GetMapping("/login") - public String login(){ - return "login"; - } - - @GetMapping("/signup") - public String signup(){ - return "signup"; - } -} diff --git a/src/main/java/com/ceos/vote/domain/users/dto/AddUserRequest.java b/src/main/java/com/ceos/vote/domain/users/dto/AddUserRequest.java deleted file mode 100644 index 4eb268f..0000000 --- a/src/main/java/com/ceos/vote/domain/users/dto/AddUserRequest.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ceos.vote.domain.users.dto; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class AddUserRequest { - private String email; - private String password; -} diff --git a/src/main/java/com/ceos/vote/domain/users/dto/SignInDto.java b/src/main/java/com/ceos/vote/domain/users/dto/SignInDto.java new file mode 100644 index 0000000..d9b7a2d --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/users/dto/SignInDto.java @@ -0,0 +1,15 @@ +package com.ceos.vote.domain.users.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class SignInDto { + private String username; + private String password; +} diff --git a/src/main/java/com/ceos/vote/domain/users/dto/SignUpDto.java b/src/main/java/com/ceos/vote/domain/users/dto/SignUpDto.java new file mode 100644 index 0000000..1ebd4d2 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/users/dto/SignUpDto.java @@ -0,0 +1,37 @@ +package com.ceos.vote.domain.users.dto; + +import com.ceos.vote.domain.users.entity.User; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class SignUpDto { + + private String username; + private String email; + private String password; + private String userTeam; + private String userPart; + private List roles = new ArrayList<>(); + + public User toEntity(String encodedPassword, List roles){ + return User.builder() + .username(username) + .email(email) + .password(encodedPassword) + .userTeam(userTeam) + .userPart(userPart) + .roles(roles) + .build(); + } + + +} diff --git a/src/main/java/com/ceos/vote/domain/users/dto/UserDto.java b/src/main/java/com/ceos/vote/domain/users/dto/UserDto.java new file mode 100644 index 0000000..2d9a6eb --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/users/dto/UserDto.java @@ -0,0 +1,39 @@ +package com.ceos.vote.domain.users.dto; + +import com.ceos.vote.domain.users.entity.User; +import lombok.*; + +@Getter +@ToString +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class UserDto { + + private Long id; + private String username; + private String email; + private String password; + private String userTeam; + private String userPart; + + static public UserDto toDto(User user) { + return UserDto.builder() + .id(user.getId()) + .username(user.getUsername()) + .email(user.getEmail()) + .userTeam(user.getUserTeam()) + .userPart(user.getUserPart()) + .build(); + } + + public User toEntity() { + return User.builder() + .id(id) + .username(username) + .email(email) + .userTeam(userTeam) + .userPart(userPart) + .build(); + } +} diff --git a/src/main/java/com/ceos/vote/domain/users/entity/User.java b/src/main/java/com/ceos/vote/domain/users/entity/User.java index f1551ab..d3ac29e 100644 --- a/src/main/java/com/ceos/vote/domain/users/entity/User.java +++ b/src/main/java/com/ceos/vote/domain/users/entity/User.java @@ -7,8 +7,10 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; @Entity @@ -17,15 +19,16 @@ @Getter @Table(name = "USER") @Builder -public class User extends BaseEntity implements UserDetails { +@EqualsAndHashCode(of = "id") +public class User implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "user_id", updatable = false) + @Column(name = "user_id", updatable = false, nullable = false) private Long id; @Column(name="name") - private String name; + private String username; @Column(name = "email", nullable = false, unique = true) private String email; @@ -39,24 +42,34 @@ public class User extends BaseEntity implements UserDetails { @Column(name="user_part") private String userPart; - public User(String email, String password, String auth) { - this.email = email; - this.password = password; + @ElementCollection(fetch = FetchType.EAGER) + @Builder.Default + private List roles = new ArrayList<>(); + @Override + public Collection getAuthorities() { + return this.roles.stream() + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); } @Override - public Collection getAuthorities() { - return List.of(new SimpleGrantedAuthority("user")); + public String getUsername() { + return ""; } @Override - public String getUsername() { - return email; + public boolean isAccountNonExpired() { + return true; } @Override - public String getPassword() { - return password; + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; } @Override diff --git a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java index 2f97029..fcaeec3 100644 --- a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java +++ b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java @@ -8,5 +8,8 @@ @Repository public interface UserRepository extends JpaRepository { - Optional findByEmail(String email); + Optional findByUsername(String username); + + String username(String username); + boolean existsByUsername(String username); } \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/users/service/UserDetailService.java b/src/main/java/com/ceos/vote/domain/users/service/UserDetailService.java index 590c9e3..f41f1de 100644 --- a/src/main/java/com/ceos/vote/domain/users/service/UserDetailService.java +++ b/src/main/java/com/ceos/vote/domain/users/service/UserDetailService.java @@ -3,18 +3,34 @@ import com.ceos.vote.domain.users.entity.User; import com.ceos.vote.domain.users.repository.UserRepository; import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.util.List; + @RequiredArgsConstructor @Service public class UserDetailService implements UserDetailsService { private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder = null; @Override - public User loadUserByUsername(String email){ - return userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException((email))); + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return userRepository.findByUsername(username) + .map(this::createUserDetails) + .orElseThrow(() -> new UsernameNotFoundException("해당하는 회원을 찾을 수 없습니다.")); + } + + private UserDetails createUserDetails(@NotNull User user) { + return User.builder() + .username(user.getUsername()) + .password(passwordEncoder.encode(user.getPassword())) + .roles(List.of(user.getRoles().toArray(new String[0]))) + .build(); } } diff --git a/src/main/java/com/ceos/vote/domain/users/service/UserService.java b/src/main/java/com/ceos/vote/domain/users/service/UserService.java index 054d723..d27fd0c 100644 --- a/src/main/java/com/ceos/vote/domain/users/service/UserService.java +++ b/src/main/java/com/ceos/vote/domain/users/service/UserService.java @@ -1,23 +1,60 @@ package com.ceos.vote.domain.users.service; -import com.ceos.vote.domain.users.dto.AddUserRequest; +import ch.qos.logback.classic.encoder.JsonEncoder; +import com.ceos.vote.domain.auth.JwtToken; +import com.ceos.vote.domain.auth.JwtTokenProvider; +import com.ceos.vote.domain.users.dto.SignUpDto; +import com.ceos.vote.domain.users.dto.UserDto; import com.ceos.vote.domain.users.repository.UserRepository; import lombok.RequiredArgsConstructor; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.core.Authentication; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.ceos.vote.global.config.SecurityConfig; + +import java.util.ArrayList; +import java.util.List; @RequiredArgsConstructor @Service +@Transactional(readOnly = true) +@Slf4j public class UserService { private final UserRepository userRepository; - private final BCryptPasswordEncoder bCryptPasswordEncoder; - - public Long save(AddUserRequest dto){ - return userRepository.save(User.builder() - .email(dto.getEmail()) - .password(bCryptPasswordEncoder.encode(dto.getPassword())) - .build()).getId(); + private final AuthenticationManagerBuilder authenticationManagerBuilder; + private final JwtTokenProvider jwtTokenProvider; + private final PasswordEncoder passwordEncoder; + + @Transactional + //@Override + public UserDto signUp(SignUpDto signUpDto){ + if(userRepository.existsByUsername(signUpDto.getUsername())){ + throw new IllegalArgumentException("이미 사용 중인 사용자 이름입니다."); + } + + //password 암호화 + String encodedPassword = passwordEncoder.encode(signUpDto.getPassword()); + List roles = new ArrayList<>(); + roles.add("USER"); //user 권한 부여 + return UserDto.toDto(userRepository.save(signUpDto.toEntity(encodedPassword, roles))); + } + + @Transactional + public JwtToken signIn(String username, String password){ + // 1. username + password 를 기반으로 Authentication 객체 생성 + // 이때 authentication 은 인증 여부를 확인하는 authenticated 값이 false + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); + // 2. 실제 검증. authenticate() 메서드를 통해 요청된 Member 에 대한 검증 진행 + // authenticate 메서드가 실행될 때 CustomUserDetailsService 에서 만든 loadUserByUsername 메서드 실행 + Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); + // 3. 인증 정보를 기반으로 JWT 토큰 생성 + JwtToken jwtToken = jwtTokenProvider.generateToken(authentication); + + return jwtToken; } } diff --git a/src/main/java/com/ceos/vote/domain/utils/SecurityUtil.java b/src/main/java/com/ceos/vote/domain/utils/SecurityUtil.java new file mode 100644 index 0000000..3b3592e --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/utils/SecurityUtil.java @@ -0,0 +1,15 @@ +package com.ceos.vote.domain.utils; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +public class SecurityUtil { + + public static String getCurrentUsername() { + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || authentication.getName()==null) { + throw new RuntimeException("No authentication found"); + } + return authentication.getName(); + } +} diff --git a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java index 5b3488b..52c1fb5 100644 --- a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java +++ b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java @@ -16,6 +16,7 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @@ -23,7 +24,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toH2Console; +//import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toH2Console; @Configuration @EnableWebSecurity @@ -39,36 +40,23 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .formLogin(AbstractHttpConfigurer::disable) - .httpBasic(AbstractHttpConfigurer::disable); - - http + .httpBasic(AbstractHttpConfigurer::disable) + .sessionManagement(session -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authorize -> authorize .requestMatchers("/hc", "/env","/**","/login/**", "/auth/join/**").permitAll() .requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**").permitAll() .requestMatchers("/login/**", "/auth/join/**", "/signup", "/user").permitAll() + .requestMatchers("/api/v1/auth/**").permitAll() .requestMatchers("/reissue").permitAll() - .anyRequest().authenticated()); - http + .requestMatchers("/user/sign-up").permitAll() + .requestMatchers("/user/sign-in").permitAll() + .requestMatchers("/static/**", "/css/**", "/js/**", "/images/**").permitAll() + .requestMatchers("/user/test").hasRole("user") + .anyRequest().authenticated()) .logout((logout) -> logout .logoutSuccessUrl("/login") - .invalidateHttpSession(true)); - http - .sessionManagement(session -> session - .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); - http - // REST API이므로 basic auth 및 csrf 보안을 사용하지 않음 - .httpBasic(AbstractHttpConfigurer::disable) - .csrf(AbstractHttpConfigurer::disable) - // JWT를 사용하기 때문에 세션을 사용하지 않음 - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(authorize -> authorize - // 해당 API에 대해서는 모든 요청을 허가 - .requestMatchers("/members/sign-in").permitAll() - // USER 권한이 있어야 요청할 수 있음 - .requestMatchers("/members/test").hasRole("USER") - // 이 밖에 모든 요청에 대해서 인증을 필요로 한다는 설정 - .anyRequest().authenticated()) - // JWT 인증을 위하여 직접 구현한 필터를 UsernamePasswordAuthenticationFilter 전에 실행 + .invalidateHttpSession(true)) .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); return http.build(); @@ -87,13 +75,15 @@ public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } +// @Bean +// public WebSecurityCustomizer configure(){ +// return (web) -> web.ignoring() +// .requestMatchers(toH2Console()) +// .requestMatchers(new AntPathRequestMatcher("/static/**")); +// } @Bean - public WebSecurityCustomizer configure(){ - return (web) -> web.ignoring() - .requestMatchers(toH2Console()) - .requestMatchers(new AntPathRequestMatcher("/static/**")); - } public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); +// return NoOpPasswordEncoder.getInstance(); } } \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/utils/PasswordEncoderGenerator.java b/src/main/java/com/ceos/vote/utils/PasswordEncoderGenerator.java new file mode 100644 index 0000000..4b4451c --- /dev/null +++ b/src/main/java/com/ceos/vote/utils/PasswordEncoderGenerator.java @@ -0,0 +1,11 @@ +package com.ceos.vote.utils; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +public class PasswordEncoderGenerator { + public static void main(String[] args) { + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + String encodedPassword = encoder.encode("5678"); + System.out.println(encodedPassword); + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 11fd10c..a5010a6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -64,6 +64,3 @@ logging: springdoc: swagger-ui: path: /swagger - -jwt: - secret:161dd0a2af20a9c25c2d0044222d46a1ba6b0f2641e240a50f1694a7da9a5b73 \ No newline at end of file From ccefdb0e1176e8babebce91e70cb5c75b91eb9f9 Mon Sep 17 00:00:00 2001 From: Seoah <0325sandy@ewhain.net> Date: Mon, 30 Dec 2024 12:52:26 +0900 Subject: [PATCH 38/73] =?UTF-8?q?feat:=20user=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/UserController.java | 48 +++++++++++++++ .../domain/{users => auth}/dto/SignInDto.java | 2 +- .../domain/{users => auth}/dto/SignUpDto.java | 2 +- .../domain/{users => auth}/dto/UserDto.java | 2 +- .../service/UserDetailService.java | 2 +- .../vote/domain/auth/service/UserService.java | 58 +++++++++++++++++++ .../users/controller/UserController.java | 43 +++----------- .../users/dto/response/UserResponseDto.java | 12 ++++ .../domain/users/service/UserService.java | 56 ++++-------------- .../vote/global/config/SecurityConfig.java | 6 +- 10 files changed, 143 insertions(+), 88 deletions(-) create mode 100644 src/main/java/com/ceos/vote/domain/auth/controller/UserController.java rename src/main/java/com/ceos/vote/domain/{users => auth}/dto/SignInDto.java (85%) rename src/main/java/com/ceos/vote/domain/{users => auth}/dto/SignUpDto.java (95%) rename src/main/java/com/ceos/vote/domain/{users => auth}/dto/UserDto.java (95%) rename src/main/java/com/ceos/vote/domain/{users => auth}/service/UserDetailService.java (96%) create mode 100644 src/main/java/com/ceos/vote/domain/auth/service/UserService.java create mode 100644 src/main/java/com/ceos/vote/domain/users/dto/response/UserResponseDto.java diff --git a/src/main/java/com/ceos/vote/domain/auth/controller/UserController.java b/src/main/java/com/ceos/vote/domain/auth/controller/UserController.java new file mode 100644 index 0000000..631bced --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/auth/controller/UserController.java @@ -0,0 +1,48 @@ +package com.ceos.vote.domain.auth.controller; + +import com.ceos.vote.domain.auth.JwtToken; +import com.ceos.vote.domain.auth.dto.SignInDto; +import com.ceos.vote.domain.auth.dto.SignUpDto; +import com.ceos.vote.domain.auth.dto.UserDto; +import com.ceos.vote.domain.auth.service.UserService; +import com.ceos.vote.domain.utils.SecurityUtil; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("/api/v1/auth") +@RequiredArgsConstructor +public class UserController { + + private final UserService userService; + + @PostMapping("/sign-up") + public ResponseEntity signUp(@RequestBody SignUpDto signUpDto) { + UserDto userDto = userService.signUp(signUpDto); + return ResponseEntity.ok(userDto); + } + + @PostMapping("/sign-in") + public JwtToken signIn(@RequestBody SignInDto signinDto){ + + String username = signinDto.getUsername(); + String password = signinDto.getPassword(); + JwtToken jwtToken = userService.signIn(username, password); + + log.info("request username = {}, password = {}", username, password); + log.info("jwtToken accessToken = {}, refreshToken = {}", jwtToken.getAccessToken(), jwtToken.getRefreshToken()); + + return jwtToken; + } + + @PostMapping("/test") + public String test(){ + return SecurityUtil.getCurrentUsername(); + } +} diff --git a/src/main/java/com/ceos/vote/domain/users/dto/SignInDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/SignInDto.java similarity index 85% rename from src/main/java/com/ceos/vote/domain/users/dto/SignInDto.java rename to src/main/java/com/ceos/vote/domain/auth/dto/SignInDto.java index d9b7a2d..8bc613d 100644 --- a/src/main/java/com/ceos/vote/domain/users/dto/SignInDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/SignInDto.java @@ -1,4 +1,4 @@ -package com.ceos.vote.domain.users.dto; +package com.ceos.vote.domain.auth.dto; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/ceos/vote/domain/users/dto/SignUpDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/SignUpDto.java similarity index 95% rename from src/main/java/com/ceos/vote/domain/users/dto/SignUpDto.java rename to src/main/java/com/ceos/vote/domain/auth/dto/SignUpDto.java index 1ebd4d2..4a503b3 100644 --- a/src/main/java/com/ceos/vote/domain/users/dto/SignUpDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/SignUpDto.java @@ -1,4 +1,4 @@ -package com.ceos.vote.domain.users.dto; +package com.ceos.vote.domain.auth.dto; import com.ceos.vote.domain.users.entity.User; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/ceos/vote/domain/users/dto/UserDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/UserDto.java similarity index 95% rename from src/main/java/com/ceos/vote/domain/users/dto/UserDto.java rename to src/main/java/com/ceos/vote/domain/auth/dto/UserDto.java index 2d9a6eb..22ccbda 100644 --- a/src/main/java/com/ceos/vote/domain/users/dto/UserDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/UserDto.java @@ -1,4 +1,4 @@ -package com.ceos.vote.domain.users.dto; +package com.ceos.vote.domain.auth.dto; import com.ceos.vote.domain.users.entity.User; import lombok.*; diff --git a/src/main/java/com/ceos/vote/domain/users/service/UserDetailService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java similarity index 96% rename from src/main/java/com/ceos/vote/domain/users/service/UserDetailService.java rename to src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java index f41f1de..b3da2e5 100644 --- a/src/main/java/com/ceos/vote/domain/users/service/UserDetailService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java @@ -1,4 +1,4 @@ -package com.ceos.vote.domain.users.service; +package com.ceos.vote.domain.auth.service; import com.ceos.vote.domain.users.entity.User; import com.ceos.vote.domain.users.repository.UserRepository; diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java new file mode 100644 index 0000000..a25cbd2 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java @@ -0,0 +1,58 @@ +package com.ceos.vote.domain.auth.service; + +import com.ceos.vote.domain.auth.JwtToken; +import com.ceos.vote.domain.auth.JwtTokenProvider; +import com.ceos.vote.domain.auth.dto.SignUpDto; +import com.ceos.vote.domain.auth.dto.UserDto; +import com.ceos.vote.domain.users.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.core.Authentication; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor +@Service +@Transactional(readOnly = true) +@Slf4j +public class UserService { + + private final UserRepository userRepository; + private final AuthenticationManagerBuilder authenticationManagerBuilder; + private final JwtTokenProvider jwtTokenProvider; + private final PasswordEncoder passwordEncoder; + + @Transactional + //@Override + public UserDto signUp(SignUpDto signUpDto){ + if(userRepository.existsByUsername(signUpDto.getUsername())){ + throw new IllegalArgumentException("이미 사용 중인 사용자 이름입니다."); + } + + //password 암호화 + String encodedPassword = passwordEncoder.encode(signUpDto.getPassword()); + List roles = new ArrayList<>(); + roles.add("USER"); //user 권한 부여 + return UserDto.toDto(userRepository.save(signUpDto.toEntity(encodedPassword, roles))); + } + + @Transactional + public JwtToken signIn(String username, String password){ + // 1. username + password 를 기반으로 Authentication 객체 생성 + // 이때 authentication 은 인증 여부를 확인하는 authenticated 값이 false + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); + // 2. 실제 검증. authenticate() 메서드를 통해 요청된 Member 에 대한 검증 진행 + // authenticate 메서드가 실행될 때 CustomUserDetailsService 에서 만든 loadUserByUsername 메서드 실행 + Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); + // 3. 인증 정보를 기반으로 JWT 토큰 생성 + JwtToken jwtToken = jwtTokenProvider.generateToken(authentication); + + return jwtToken; + } +} diff --git a/src/main/java/com/ceos/vote/domain/users/controller/UserController.java b/src/main/java/com/ceos/vote/domain/users/controller/UserController.java index cc03eaa..47cd278 100644 --- a/src/main/java/com/ceos/vote/domain/users/controller/UserController.java +++ b/src/main/java/com/ceos/vote/domain/users/controller/UserController.java @@ -1,48 +1,23 @@ package com.ceos.vote.domain.users.controller; -import com.ceos.vote.domain.auth.JwtToken; -import com.ceos.vote.domain.users.dto.SignInDto; -import com.ceos.vote.domain.users.dto.SignUpDto; -import com.ceos.vote.domain.users.dto.UserDto; -import com.ceos.vote.domain.users.service.UserService; -import com.ceos.vote.domain.utils.SecurityUtil; -import io.swagger.v3.oas.annotations.parameters.RequestBody; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; +import com.ceos.vote.domain.auth.service.UserService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@Slf4j @RestController -@RequestMapping("/api/v1/auth") -@RequiredArgsConstructor +@RequestMapping("api/v1/users") public class UserController { private final UserService userService; - @PostMapping("/sign-up") - public ResponseEntity signUp(@RequestBody SignUpDto signUpDto) { - UserDto userDto = userService.signUp(signUpDto); - return ResponseEntity.ok(userDto); + public UserController(UserService userService) { + this.userService = userService; } - @PostMapping("/sign-in") - public JwtToken signIn(@RequestBody SignInDto signinDto){ - - String username = signinDto.getUsername(); - String password = signinDto.getPassword(); - JwtToken jwtToken = userService.signIn(username, password); - - log.info("request username = {}, password = {}", username, password); - log.info("jwtToken accessToken = {}, refreshToken = {}", jwtToken.getAccessToken(), jwtToken.getRefreshToken()); - - return jwtToken; - } - - @PostMapping("/test") - public String test(){ - return SecurityUtil.getCurrentUsername(); + @GetMapping("/{userId}") + public UserResponseDto getUserInfo(@PathVariable Long userId){ + return userService.getUserInfo(userId); } } diff --git a/src/main/java/com/ceos/vote/domain/users/dto/response/UserResponseDto.java b/src/main/java/com/ceos/vote/domain/users/dto/response/UserResponseDto.java new file mode 100644 index 0000000..5d6cb54 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/users/dto/response/UserResponseDto.java @@ -0,0 +1,12 @@ +package com.ceos.vote.domain.users.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class UserResponseDto { + private String username; + private String userPart; + private String userTeam; +} diff --git a/src/main/java/com/ceos/vote/domain/users/service/UserService.java b/src/main/java/com/ceos/vote/domain/users/service/UserService.java index d27fd0c..dedfe87 100644 --- a/src/main/java/com/ceos/vote/domain/users/service/UserService.java +++ b/src/main/java/com/ceos/vote/domain/users/service/UserService.java @@ -1,60 +1,26 @@ package com.ceos.vote.domain.users.service; -import ch.qos.logback.classic.encoder.JsonEncoder; -import com.ceos.vote.domain.auth.JwtToken; -import com.ceos.vote.domain.auth.JwtTokenProvider; -import com.ceos.vote.domain.users.dto.SignUpDto; -import com.ceos.vote.domain.users.dto.UserDto; +import com.ceos.vote.domain.users.entity.User; import com.ceos.vote.domain.users.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.core.Authentication; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import com.ceos.vote.global.config.SecurityConfig; -import java.util.ArrayList; -import java.util.List; - -@RequiredArgsConstructor @Service -@Transactional(readOnly = true) -@Slf4j public class UserService { - - private final UserRepository userRepository; - private final AuthenticationManagerBuilder authenticationManagerBuilder; - private final JwtTokenProvider jwtTokenProvider; - private final PasswordEncoder passwordEncoder; - @Transactional - //@Override - public UserDto signUp(SignUpDto signUpDto){ - if(userRepository.existsByUsername(signUpDto.getUsername())){ - throw new IllegalArgumentException("이미 사용 중인 사용자 이름입니다."); - } + private final UserRepository userRepository; - //password 암호화 - String encodedPassword = passwordEncoder.encode(signUpDto.getPassword()); - List roles = new ArrayList<>(); - roles.add("USER"); //user 권한 부여 - return UserDto.toDto(userRepository.save(signUpDto.toEntity(encodedPassword, roles))); + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; } - @Transactional - public JwtToken signIn(String username, String password){ - // 1. username + password 를 기반으로 Authentication 객체 생성 - // 이때 authentication 은 인증 여부를 확인하는 authenticated 값이 false - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); - // 2. 실제 검증. authenticate() 메서드를 통해 요청된 Member 에 대한 검증 진행 - // authenticate 메서드가 실행될 때 CustomUserDetailsService 에서 만든 loadUserByUsername 메서드 실행 - Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); - // 3. 인증 정보를 기반으로 JWT 토큰 생성 - JwtToken jwtToken = jwtTokenProvider.generateToken(authentication); + public UserResponseDto getUserInfo(Long userId){ + User user = userRepository.findById(userId).orElse(null); - return jwtToken; + return new UserResponseDto( + user.getUserPart(), + user.getUserTeam(), + user.getUsername() + ); } } diff --git a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java index 52c1fb5..cd0e386 100644 --- a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java +++ b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java @@ -2,27 +2,23 @@ import com.ceos.vote.domain.auth.JwtAuthenticationFilter; import com.ceos.vote.domain.auth.JwtTokenProvider; -import com.ceos.vote.domain.users.service.UserDetailService; +import com.ceos.vote.domain.auth.service.UserDetailService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; -import org.springframework.security.config.annotation.web.HttpSecurityDsl; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import lombok.RequiredArgsConstructor; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; //import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toH2Console; From cab62f37532a74b3d5b90eb6f5f93e8ca8a9c423 Mon Sep 17 00:00:00 2001 From: Seoah <0325sandy@ewhain.net> Date: Mon, 30 Dec 2024 13:21:54 +0900 Subject: [PATCH 39/73] =?UTF-8?q?update:=20auth=EC=99=80=20user=20?= =?UTF-8?q?=EC=82=AC=EC=9D=B4=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...serController.java => AuthController.java} | 2 +- .../auth/service/UserDetailService.java | 2 +- .../vote/domain/auth/service/UserService.java | 12 +++++++++ .../users/controller/UserController.java | 5 ++-- .../users/repository/UserRepository.java | 4 ++- .../domain/users/service/UserService.java | 26 ------------------- 6 files changed, 20 insertions(+), 31 deletions(-) rename src/main/java/com/ceos/vote/domain/auth/controller/{UserController.java => AuthController.java} (98%) delete mode 100644 src/main/java/com/ceos/vote/domain/users/service/UserService.java diff --git a/src/main/java/com/ceos/vote/domain/auth/controller/UserController.java b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java similarity index 98% rename from src/main/java/com/ceos/vote/domain/auth/controller/UserController.java rename to src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java index 631bced..136f00d 100644 --- a/src/main/java/com/ceos/vote/domain/auth/controller/UserController.java +++ b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java @@ -18,7 +18,7 @@ @RestController @RequestMapping("/api/v1/auth") @RequiredArgsConstructor -public class UserController { +public class AuthController { private final UserService userService; diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java index b3da2e5..b4d2dbe 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java @@ -21,7 +21,7 @@ public class UserDetailService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - return userRepository.findByUsername(username) + return UserRepository.findByUsername(username) .map(this::createUserDetails) .orElseThrow(() -> new UsernameNotFoundException("해당하는 회원을 찾을 수 없습니다.")); } diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java index a25cbd2..dcb3e5f 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java @@ -4,6 +4,8 @@ import com.ceos.vote.domain.auth.JwtTokenProvider; import com.ceos.vote.domain.auth.dto.SignUpDto; import com.ceos.vote.domain.auth.dto.UserDto; +import com.ceos.vote.domain.users.dto.response.UserResponseDto; +import com.ceos.vote.domain.users.entity.User; import com.ceos.vote.domain.users.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -28,6 +30,16 @@ public class UserService { private final JwtTokenProvider jwtTokenProvider; private final PasswordEncoder passwordEncoder; + public static UserResponseDto getUserInfo(String username){ + User user = UserRepository.findByUsername(username).orElse(null); + + return new UserResponseDto( + user.getUserPart(), + user.getUserTeam(), + user.getUsername() + ); + } + @Transactional //@Override public UserDto signUp(SignUpDto signUpDto){ diff --git a/src/main/java/com/ceos/vote/domain/users/controller/UserController.java b/src/main/java/com/ceos/vote/domain/users/controller/UserController.java index 47cd278..afeb1d1 100644 --- a/src/main/java/com/ceos/vote/domain/users/controller/UserController.java +++ b/src/main/java/com/ceos/vote/domain/users/controller/UserController.java @@ -1,6 +1,7 @@ package com.ceos.vote.domain.users.controller; import com.ceos.vote.domain.auth.service.UserService; +import com.ceos.vote.domain.users.dto.response.UserResponseDto; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -17,7 +18,7 @@ public UserController(UserService userService) { } @GetMapping("/{userId}") - public UserResponseDto getUserInfo(@PathVariable Long userId){ - return userService.getUserInfo(userId); + public UserResponseDto getUserInfo(@PathVariable String username){ + return UserService.getUserInfo(username); } } diff --git a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java index fcaeec3..faffa92 100644 --- a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java +++ b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java @@ -8,7 +8,9 @@ @Repository public interface UserRepository extends JpaRepository { - Optional findByUsername(String username); + static Optional findByUsername(String username) { + return null; + } String username(String username); boolean existsByUsername(String username); diff --git a/src/main/java/com/ceos/vote/domain/users/service/UserService.java b/src/main/java/com/ceos/vote/domain/users/service/UserService.java deleted file mode 100644 index dedfe87..0000000 --- a/src/main/java/com/ceos/vote/domain/users/service/UserService.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.ceos.vote.domain.users.service; - -import com.ceos.vote.domain.users.entity.User; -import com.ceos.vote.domain.users.repository.UserRepository; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; - -@Service -public class UserService { - - private final UserRepository userRepository; - - public UserService(UserRepository userRepository) { - this.userRepository = userRepository; - } - - public UserResponseDto getUserInfo(Long userId){ - User user = userRepository.findById(userId).orElse(null); - - return new UserResponseDto( - user.getUserPart(), - user.getUserTeam(), - user.getUsername() - ); - } -} From 4382ab07ae9b5b36dedcd9f666d21b1d09d75231 Mon Sep 17 00:00:00 2001 From: Seoah <0325sandy@ewhain.net> Date: Mon, 30 Dec 2024 15:29:25 +0900 Subject: [PATCH 40/73] =?UTF-8?q?update:=20user=EC=97=94=ED=8B=B0=ED=8B=B0?= =?UTF-8?q?=EB=AA=85=20users=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ceos/vote/domain/auth/dto/SignUpDto.java | 6 +++--- .../com/ceos/vote/domain/auth/dto/UserDto.java | 18 +++++++++--------- .../domain/auth/service/UserDetailService.java | 12 ++++++------ .../vote/domain/auth/service/UserService.java | 10 +++++----- .../users/entity/{User.java => Users.java} | 3 +-- .../users/repository/UserRepository.java | 6 +++--- 6 files changed, 27 insertions(+), 28 deletions(-) rename src/main/java/com/ceos/vote/domain/users/entity/{User.java => Users.java} (95%) diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/SignUpDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/SignUpDto.java index 4a503b3..8911b39 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/SignUpDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/SignUpDto.java @@ -1,6 +1,6 @@ package com.ceos.vote.domain.auth.dto; -import com.ceos.vote.domain.users.entity.User; +import com.ceos.vote.domain.users.entity.Users; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -22,8 +22,8 @@ public class SignUpDto { private String userPart; private List roles = new ArrayList<>(); - public User toEntity(String encodedPassword, List roles){ - return User.builder() + public Users toEntity(String encodedPassword, List roles){ + return Users.builder() .username(username) .email(email) .password(encodedPassword) diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/UserDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/UserDto.java index 22ccbda..25bdc74 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/UserDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/UserDto.java @@ -1,6 +1,6 @@ package com.ceos.vote.domain.auth.dto; -import com.ceos.vote.domain.users.entity.User; +import com.ceos.vote.domain.users.entity.Users; import lombok.*; @Getter @@ -17,18 +17,18 @@ public class UserDto { private String userTeam; private String userPart; - static public UserDto toDto(User user) { + static public UserDto toDto(Users users) { return UserDto.builder() - .id(user.getId()) - .username(user.getUsername()) - .email(user.getEmail()) - .userTeam(user.getUserTeam()) - .userPart(user.getUserPart()) + .id(users.getId()) + .username(users.getUsername()) + .email(users.getEmail()) + .userTeam(users.getUserTeam()) + .userPart(users.getUserPart()) .build(); } - public User toEntity() { - return User.builder() + public Users toEntity() { + return Users.builder() .id(id) .username(username) .email(email) diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java index b4d2dbe..befd46c 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java @@ -1,6 +1,6 @@ package com.ceos.vote.domain.auth.service; -import com.ceos.vote.domain.users.entity.User; +import com.ceos.vote.domain.users.entity.Users; import com.ceos.vote.domain.users.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.NotNull; @@ -26,11 +26,11 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx .orElseThrow(() -> new UsernameNotFoundException("해당하는 회원을 찾을 수 없습니다.")); } - private UserDetails createUserDetails(@NotNull User user) { - return User.builder() - .username(user.getUsername()) - .password(passwordEncoder.encode(user.getPassword())) - .roles(List.of(user.getRoles().toArray(new String[0]))) + private UserDetails createUserDetails(@NotNull Users users) { + return Users.builder() + .username(users.getUsername()) + .password(passwordEncoder.encode(users.getPassword())) + .roles(List.of(users.getRoles().toArray(new String[0]))) .build(); } } diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java index dcb3e5f..0ede7f6 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java @@ -5,7 +5,7 @@ import com.ceos.vote.domain.auth.dto.SignUpDto; import com.ceos.vote.domain.auth.dto.UserDto; import com.ceos.vote.domain.users.dto.response.UserResponseDto; -import com.ceos.vote.domain.users.entity.User; +import com.ceos.vote.domain.users.entity.Users; import com.ceos.vote.domain.users.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -31,12 +31,12 @@ public class UserService { private final PasswordEncoder passwordEncoder; public static UserResponseDto getUserInfo(String username){ - User user = UserRepository.findByUsername(username).orElse(null); + Users users = UserRepository.findByUsername(username).orElse(null); return new UserResponseDto( - user.getUserPart(), - user.getUserTeam(), - user.getUsername() + users.getUserPart(), + users.getUserTeam(), + users.getUsername() ); } diff --git a/src/main/java/com/ceos/vote/domain/users/entity/User.java b/src/main/java/com/ceos/vote/domain/users/entity/Users.java similarity index 95% rename from src/main/java/com/ceos/vote/domain/users/entity/User.java rename to src/main/java/com/ceos/vote/domain/users/entity/Users.java index d3ac29e..5c978fd 100644 --- a/src/main/java/com/ceos/vote/domain/users/entity/User.java +++ b/src/main/java/com/ceos/vote/domain/users/entity/Users.java @@ -1,6 +1,5 @@ package com.ceos.vote.domain.users.entity; -import com.ceos.vote.global.BaseEntity; import jakarta.persistence.*; import lombok.*; import org.springframework.security.core.GrantedAuthority; @@ -20,7 +19,7 @@ @Table(name = "USER") @Builder @EqualsAndHashCode(of = "id") -public class User implements UserDetails { +public class Users implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java index faffa92..bf05634 100644 --- a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java +++ b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java @@ -1,14 +1,14 @@ package com.ceos.vote.domain.users.repository; -import com.ceos.vote.domain.users.entity.User; +import com.ceos.vote.domain.users.entity.Users; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository -public interface UserRepository extends JpaRepository { - static Optional findByUsername(String username) { +public interface UserRepository extends JpaRepository { + static Optional findByUsername(String username) { return null; } From 00f340124139436aee12d6d2f74547561c357b8d Mon Sep 17 00:00:00 2001 From: limgahyun Date: Tue, 31 Dec 2024 03:00:28 +0900 Subject: [PATCH 41/73] =?UTF-8?q?fix:=20column=20name=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ceos/vote/domain/teamCandidate/entity/TeamCandidate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/ceos/vote/domain/teamCandidate/entity/TeamCandidate.java b/src/main/java/com/ceos/vote/domain/teamCandidate/entity/TeamCandidate.java index 2c389a2..67e8635 100644 --- a/src/main/java/com/ceos/vote/domain/teamCandidate/entity/TeamCandidate.java +++ b/src/main/java/com/ceos/vote/domain/teamCandidate/entity/TeamCandidate.java @@ -12,7 +12,7 @@ public class TeamCandidate { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "leader_candidate_id") + @Column(name = "team_candidate_id") private Long id; @Column From d16c06c10c7e4cf728fd263f56da58ea1291914e Mon Sep 17 00:00:00 2001 From: Seoah <0325sandy@ewhain.net> Date: Wed, 1 Jan 2025 19:22:59 +0900 Subject: [PATCH 42/73] =?UTF-8?q?update:=20=EC=BD=94=EB=A9=98=ED=8A=B8=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vote/domain/auth/JwtTokenProvider.java | 4 +-- .../auth/controller/AuthController.java | 25 +++++++++------- .../auth/dto/{ => request}/SignInDto.java | 2 +- .../auth/dto/{ => request}/SignUpDto.java | 5 ++-- .../UserInfoDto.java} | 20 ++++--------- .../auth/service/UserDetailService.java | 4 ++- .../vote/domain/auth/service/UserService.java | 10 +++---- .../users/controller/UserController.java | 6 ++-- .../ceos/vote/domain/users/entity/Users.java | 30 ++----------------- .../users/repository/UserRepository.java | 2 ++ .../ceos/vote/domain/utils/SecurityUtil.java | 4 ++- .../vote/global/config/SecurityConfig.java | 7 ++--- .../vote/global/exception/ExceptionCode.java | 5 +++- src/main/resources/application.yml | 2 +- 14 files changed, 54 insertions(+), 72 deletions(-) rename src/main/java/com/ceos/vote/domain/auth/dto/{ => request}/SignInDto.java (83%) rename src/main/java/com/ceos/vote/domain/auth/dto/{ => request}/SignUpDto.java (84%) rename src/main/java/com/ceos/vote/domain/auth/dto/{UserDto.java => response/UserInfoDto.java} (55%) diff --git a/src/main/java/com/ceos/vote/domain/auth/JwtTokenProvider.java b/src/main/java/com/ceos/vote/domain/auth/JwtTokenProvider.java index 85b82a5..c6c4286 100644 --- a/src/main/java/com/ceos/vote/domain/auth/JwtTokenProvider.java +++ b/src/main/java/com/ceos/vote/domain/auth/JwtTokenProvider.java @@ -43,7 +43,7 @@ public JwtToken generateToken(Authentication authentication){ long now = (new Date()).getTime(); - Date accessTokenExpiresIn = new Date(now+86400000); + Date accessTokenExpiresIn = new Date(now + 1000L * 60 * 60 * 24); String accessToken = Jwts.builder() .setSubject(authentication.getName()) .claim("auth", authorities) @@ -53,7 +53,7 @@ public JwtToken generateToken(Authentication authentication){ //Refresh Token 생성 String refreshToken = Jwts.builder() - .setExpiration(new Date(now + 86400000)) + .setExpiration(new Date(now + 1000L * 60 * 60 * 24)) .signWith(key, SignatureAlgorithm.HS256) .compact(); diff --git a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java index 136f00d..f55fde8 100644 --- a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java +++ b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java @@ -1,11 +1,12 @@ package com.ceos.vote.domain.auth.controller; import com.ceos.vote.domain.auth.JwtToken; -import com.ceos.vote.domain.auth.dto.SignInDto; -import com.ceos.vote.domain.auth.dto.SignUpDto; -import com.ceos.vote.domain.auth.dto.UserDto; +import com.ceos.vote.domain.auth.dto.request.SignInDto; +import com.ceos.vote.domain.auth.dto.request.SignUpDto; +import com.ceos.vote.domain.auth.dto.response.UserInfoDto; import com.ceos.vote.domain.auth.service.UserService; import com.ceos.vote.domain.utils.SecurityUtil; +import com.ceos.vote.global.common.response.CommonResponse; import io.swagger.v3.oas.annotations.parameters.RequestBody; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -23,13 +24,14 @@ public class AuthController { private final UserService userService; @PostMapping("/sign-up") - public ResponseEntity signUp(@RequestBody SignUpDto signUpDto) { - UserDto userDto = userService.signUp(signUpDto); - return ResponseEntity.ok(userDto); + public ResponseEntity> signUp(@RequestBody SignUpDto signUpDto) { + UserInfoDto userInfoDto = userService.signUp(signUpDto); + CommonResponse response = new CommonResponse<>(userInfoDto, "회원가입에 성공했습니다."); + return ResponseEntity.ok(response); } @PostMapping("/sign-in") - public JwtToken signIn(@RequestBody SignInDto signinDto){ + public CommonResponse signIn(@RequestBody SignInDto signinDto){ String username = signinDto.getUsername(); String password = signinDto.getPassword(); @@ -38,11 +40,14 @@ public JwtToken signIn(@RequestBody SignInDto signinDto){ log.info("request username = {}, password = {}", username, password); log.info("jwtToken accessToken = {}, refreshToken = {}", jwtToken.getAccessToken(), jwtToken.getRefreshToken()); - return jwtToken; + return new CommonResponse<>(jwtToken, "로그인에 성공헀습니다."); } @PostMapping("/test") - public String test(){ - return SecurityUtil.getCurrentUsername(); + public CommonResponse test(){ + + String currentUsername = SecurityUtil.getCurrentUsername(); + + return new CommonResponse<>(currentUsername, "현재 사용자 정보를 반환합니다."); } } diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/SignInDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignInDto.java similarity index 83% rename from src/main/java/com/ceos/vote/domain/auth/dto/SignInDto.java rename to src/main/java/com/ceos/vote/domain/auth/dto/request/SignInDto.java index 8bc613d..077ae80 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/SignInDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignInDto.java @@ -1,4 +1,4 @@ -package com.ceos.vote.domain.auth.dto; +package com.ceos.vote.domain.auth.dto.request; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/SignUpDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java similarity index 84% rename from src/main/java/com/ceos/vote/domain/auth/dto/SignUpDto.java rename to src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java index 8911b39..76940a3 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/SignUpDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java @@ -1,6 +1,7 @@ -package com.ceos.vote.domain.auth.dto; +package com.ceos.vote.domain.auth.dto.request; import com.ceos.vote.domain.users.entity.Users; +import com.ceos.vote.domain.users.enumerate.Part; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -28,7 +29,7 @@ public Users toEntity(String encodedPassword, List roles){ .email(email) .password(encodedPassword) .userTeam(userTeam) - .userPart(userPart) + .userPart(Part.valueOf(userPart)) .roles(roles) .build(); } diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/UserDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/response/UserInfoDto.java similarity index 55% rename from src/main/java/com/ceos/vote/domain/auth/dto/UserDto.java rename to src/main/java/com/ceos/vote/domain/auth/dto/response/UserInfoDto.java index 25bdc74..edc0c2f 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/UserDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/response/UserInfoDto.java @@ -1,6 +1,7 @@ -package com.ceos.vote.domain.auth.dto; +package com.ceos.vote.domain.auth.dto.response; import com.ceos.vote.domain.users.entity.Users; +import com.ceos.vote.domain.users.enumerate.Part; import lombok.*; @Getter @@ -8,17 +9,17 @@ @AllArgsConstructor @NoArgsConstructor @Builder -public class UserDto { +public class UserInfoDto { private Long id; private String username; private String email; private String password; private String userTeam; - private String userPart; + private Part userPart; - static public UserDto toDto(Users users) { - return UserDto.builder() + static public UserInfoDto from(Users users) { + return UserInfoDto.builder() .id(users.getId()) .username(users.getUsername()) .email(users.getEmail()) @@ -27,13 +28,4 @@ static public UserDto toDto(Users users) { .build(); } - public Users toEntity() { - return Users.builder() - .id(id) - .username(username) - .email(email) - .userTeam(userTeam) - .userPart(userPart) - .build(); - } } diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java index befd46c..b452ed8 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java @@ -2,6 +2,8 @@ import com.ceos.vote.domain.users.entity.Users; import com.ceos.vote.domain.users.repository.UserRepository; +import com.ceos.vote.global.exception.ApplicationException; +import com.ceos.vote.global.exception.ExceptionCode; import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.NotNull; import org.springframework.security.core.userdetails.UserDetails; @@ -23,7 +25,7 @@ public class UserDetailService implements UserDetailsService { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return UserRepository.findByUsername(username) .map(this::createUserDetails) - .orElseThrow(() -> new UsernameNotFoundException("해당하는 회원을 찾을 수 없습니다.")); + .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_USER)); } private UserDetails createUserDetails(@NotNull Users users) { diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java index 0ede7f6..45660b8 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java @@ -2,8 +2,8 @@ import com.ceos.vote.domain.auth.JwtToken; import com.ceos.vote.domain.auth.JwtTokenProvider; -import com.ceos.vote.domain.auth.dto.SignUpDto; -import com.ceos.vote.domain.auth.dto.UserDto; +import com.ceos.vote.domain.auth.dto.request.SignUpDto; +import com.ceos.vote.domain.auth.dto.response.UserInfoDto; import com.ceos.vote.domain.users.dto.response.UserResponseDto; import com.ceos.vote.domain.users.entity.Users; import com.ceos.vote.domain.users.repository.UserRepository; @@ -34,7 +34,7 @@ public static UserResponseDto getUserInfo(String username){ Users users = UserRepository.findByUsername(username).orElse(null); return new UserResponseDto( - users.getUserPart(), + users.getUserPart().name(), users.getUserTeam(), users.getUsername() ); @@ -42,7 +42,7 @@ public static UserResponseDto getUserInfo(String username){ @Transactional //@Override - public UserDto signUp(SignUpDto signUpDto){ + public UserInfoDto signUp(SignUpDto signUpDto){ if(userRepository.existsByUsername(signUpDto.getUsername())){ throw new IllegalArgumentException("이미 사용 중인 사용자 이름입니다."); } @@ -51,7 +51,7 @@ public UserDto signUp(SignUpDto signUpDto){ String encodedPassword = passwordEncoder.encode(signUpDto.getPassword()); List roles = new ArrayList<>(); roles.add("USER"); //user 권한 부여 - return UserDto.toDto(userRepository.save(signUpDto.toEntity(encodedPassword, roles))); + return UserInfoDto.from(userRepository.save(signUpDto.toEntity(encodedPassword, roles))); } @Transactional diff --git a/src/main/java/com/ceos/vote/domain/users/controller/UserController.java b/src/main/java/com/ceos/vote/domain/users/controller/UserController.java index afeb1d1..414c4ff 100644 --- a/src/main/java/com/ceos/vote/domain/users/controller/UserController.java +++ b/src/main/java/com/ceos/vote/domain/users/controller/UserController.java @@ -2,6 +2,7 @@ import com.ceos.vote.domain.auth.service.UserService; import com.ceos.vote.domain.users.dto.response.UserResponseDto; +import com.ceos.vote.global.common.response.CommonResponse; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -18,7 +19,8 @@ public UserController(UserService userService) { } @GetMapping("/{userId}") - public UserResponseDto getUserInfo(@PathVariable String username){ - return UserService.getUserInfo(username); + public CommonResponse getUserInfo(@PathVariable String username){ + UserResponseDto userResponseDto = userService.getUserInfo(username); + return new CommonResponse<>(userResponseDto, "사용자 정보 조회에 성공했습니다."); } } diff --git a/src/main/java/com/ceos/vote/domain/users/entity/Users.java b/src/main/java/com/ceos/vote/domain/users/entity/Users.java index bf0c2ef..b9c6550 100644 --- a/src/main/java/com/ceos/vote/domain/users/entity/Users.java +++ b/src/main/java/com/ceos/vote/domain/users/entity/Users.java @@ -1,6 +1,7 @@ package com.ceos.vote.domain.users.entity; +import com.ceos.vote.domain.users.enumerate.Part; import jakarta.persistence.*; import lombok.*; import org.springframework.security.core.GrantedAuthority; @@ -40,8 +41,9 @@ public class Users implements UserDetails { @Column(name="user_team") private String userTeam; + @Enumerated(EnumType.STRING) @Column(name="user_part") - private String userPart; + private Part userPart; @ElementCollection(fetch = FetchType.EAGER) @Builder.Default @@ -52,30 +54,4 @@ public Collection getAuthorities() { .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); } - - @Override - public String getUsername() { - return ""; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } - } \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java index 76bb81a..dee39e0 100644 --- a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java +++ b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java @@ -4,6 +4,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface UserRepository extends JpaRepository { diff --git a/src/main/java/com/ceos/vote/domain/utils/SecurityUtil.java b/src/main/java/com/ceos/vote/domain/utils/SecurityUtil.java index 3b3592e..6a2ed6a 100644 --- a/src/main/java/com/ceos/vote/domain/utils/SecurityUtil.java +++ b/src/main/java/com/ceos/vote/domain/utils/SecurityUtil.java @@ -1,5 +1,7 @@ package com.ceos.vote.domain.utils; +import com.ceos.vote.global.exception.ApplicationException; +import com.ceos.vote.global.exception.ExceptionCode; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -8,7 +10,7 @@ public class SecurityUtil { public static String getCurrentUsername() { final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || authentication.getName()==null) { - throw new RuntimeException("No authentication found"); + throw new ApplicationException(ExceptionCode.INVALID_USER_CREDENTIALS); } return authentication.getName(); } diff --git a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java index 7be6ca4..40a1e74 100644 --- a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java +++ b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java @@ -43,13 +43,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/hc", "/env","/**","/login/**", "/auth/join/**").permitAll() + .requestMatchers("/hc", "/env","/**").permitAll() .requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**").permitAll() - .requestMatchers("/login/**", "/auth/join/**", "/signup", "/user").permitAll() - .requestMatchers("/api/v1/auth/**").permitAll() + .requestMatchers("/login/**", "/auth/join/**", "/signup", "/user", "/user/sign-in").permitAll() .requestMatchers("/reissue").permitAll() - .requestMatchers("/user/sign-up").permitAll() - .requestMatchers("/user/sign-in").permitAll() .requestMatchers("/static/**", "/css/**", "/js/**", "/images/**").permitAll() .requestMatchers("/user/test").hasRole("user") .anyRequest().authenticated()) diff --git a/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java b/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java index 82b4131..ed9ce7a 100644 --- a/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java +++ b/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java @@ -21,7 +21,10 @@ public enum ExceptionCode { INVALID_SORT_EXCEPTION(HttpStatus.BAD_REQUEST, 2007, "올바르지 않은 정렬 값입니다."), BAD_REQUEST_ERROR(HttpStatus.BAD_REQUEST, 2008, "잘못된 요청입니다."), - // 3000: + // 3000: USER + NOT_FOUND_USER(HttpStatus.NOT_FOUND, 3001, "해당 회원을 찾을 수 없습니다."), + INVALID_USER_CREDENTIALS(HttpStatus.UNAUTHORIZED, 3002, "잘못된 사용자 인증 정보입니다."), + // 4000: Leader Vote Error NOT_FOUND_LEADER_CANDIDATE(HttpStatus.NOT_FOUND, 4001, "해당 leader candidate가 존재하지 않습니다."), diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index a5010a6..850e361 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -18,7 +18,7 @@ spring: on-profile: local server: - port: 8080 + port: 8086 serverAddress: localhost serverName: local_server From 2b7aea154ad97f20ece6073f96b1faf6ccc1337c Mon Sep 17 00:00:00 2001 From: Seoah <0325sandy@ewhain.net> Date: Wed, 1 Jan 2025 19:23:49 +0900 Subject: [PATCH 43/73] =?UTF-8?q?update:=20port=20=EB=B2=88=ED=98=B8=20808?= =?UTF-8?q?0=EC=9C=BC=EB=A1=9C=20=EC=A0=95=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 850e361..a5010a6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -18,7 +18,7 @@ spring: on-profile: local server: - port: 8086 + port: 8080 serverAddress: localhost serverName: local_server From c6f4b108c9f5a4cd63392f8229fdb2912177431f Mon Sep 17 00:00:00 2001 From: Seoah <0325sandy@ewhain.net> Date: Wed, 1 Jan 2025 21:05:00 +0900 Subject: [PATCH 44/73] =?UTF-8?q?update:=20Team=20enum=20=EA=B0=92?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20CommonRes?= =?UTF-8?q?ponse=EB=A1=9C=20=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 6 ++--- .../domain/auth/dto/request/SignUpDto.java | 3 ++- .../domain/auth/dto/response/UserInfoDto.java | 3 ++- .../vote/domain/auth/service/UserService.java | 2 +- .../ceos/vote/domain/users/entity/Users.java | 3 ++- .../vote/domain/users/enumerate/Team.java | 22 +++++++++++++++++++ 6 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/ceos/vote/domain/users/enumerate/Team.java diff --git a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java index f55fde8..612093a 100644 --- a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java +++ b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java @@ -10,7 +10,6 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -24,10 +23,9 @@ public class AuthController { private final UserService userService; @PostMapping("/sign-up") - public ResponseEntity> signUp(@RequestBody SignUpDto signUpDto) { + public CommonResponse signUp(@RequestBody SignUpDto signUpDto) { UserInfoDto userInfoDto = userService.signUp(signUpDto); - CommonResponse response = new CommonResponse<>(userInfoDto, "회원가입에 성공했습니다."); - return ResponseEntity.ok(response); + return new CommonResponse<>(userInfoDto, "회원가입에 성공했습니다."); } @PostMapping("/sign-in") diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java index 76940a3..a6c5bf1 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java @@ -2,6 +2,7 @@ import com.ceos.vote.domain.users.entity.Users; import com.ceos.vote.domain.users.enumerate.Part; +import com.ceos.vote.domain.users.enumerate.Team; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -28,7 +29,7 @@ public Users toEntity(String encodedPassword, List roles){ .username(username) .email(email) .password(encodedPassword) - .userTeam(userTeam) + .userTeam(Team.valueOf(userTeam)) .userPart(Part.valueOf(userPart)) .roles(roles) .build(); diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/response/UserInfoDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/response/UserInfoDto.java index edc0c2f..7e4fd74 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/response/UserInfoDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/response/UserInfoDto.java @@ -2,6 +2,7 @@ import com.ceos.vote.domain.users.entity.Users; import com.ceos.vote.domain.users.enumerate.Part; +import com.ceos.vote.domain.users.enumerate.Team; import lombok.*; @Getter @@ -15,7 +16,7 @@ public class UserInfoDto { private String username; private String email; private String password; - private String userTeam; + private Team userTeam; private Part userPart; static public UserInfoDto from(Users users) { diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java index 45660b8..78dbc61 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java @@ -35,7 +35,7 @@ public static UserResponseDto getUserInfo(String username){ return new UserResponseDto( users.getUserPart().name(), - users.getUserTeam(), + users.getUserTeam().name(), users.getUsername() ); } diff --git a/src/main/java/com/ceos/vote/domain/users/entity/Users.java b/src/main/java/com/ceos/vote/domain/users/entity/Users.java index b9c6550..ca8d77f 100644 --- a/src/main/java/com/ceos/vote/domain/users/entity/Users.java +++ b/src/main/java/com/ceos/vote/domain/users/entity/Users.java @@ -2,6 +2,7 @@ import com.ceos.vote.domain.users.enumerate.Part; +import com.ceos.vote.domain.users.enumerate.Team; import jakarta.persistence.*; import lombok.*; import org.springframework.security.core.GrantedAuthority; @@ -39,7 +40,7 @@ public class Users implements UserDetails { private String password; @Column(name="user_team") - private String userTeam; + private Team userTeam; @Enumerated(EnumType.STRING) @Column(name="user_part") diff --git a/src/main/java/com/ceos/vote/domain/users/enumerate/Team.java b/src/main/java/com/ceos/vote/domain/users/enumerate/Team.java new file mode 100644 index 0000000..449fc28 --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/users/enumerate/Team.java @@ -0,0 +1,22 @@ +package com.ceos.vote.domain.users.enumerate; + +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public enum Team { + + CUPFEEDEAL("CUPFEEDEAL"), + MUSAI("MUSAI"), + CAKEWAY("CAKEWAY"), + PHOTOGROUND("PHOTOGROUND"), + ANGELBRIDGE("ANGELBRIDGE"); ; + + private final String description; + + Team(String description) { + this.description = description; + } + +} From 6ae98ff9f9db0afd9364123f501c65fc8511a830 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Wed, 1 Jan 2025 21:31:56 +0900 Subject: [PATCH 45/73] refactor: findUserById refactoring --- .../vote/domain/auth/service/UserDetailService.java | 5 +++++ .../leaderVote/service/LeaderVoteService.java | 13 +++++-------- .../domain/teamVote/service/TeamVoteService.java | 5 +++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java index b452ed8..7b0f1d3 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java @@ -28,6 +28,11 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_USER)); } + public Users findUserById(Long id) { + return userRepository.findById(id) + .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_EXCEPTION)); + } + private UserDetails createUserDetails(@NotNull Users users) { return Users.builder() .username(users.getUsername()) diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java index 5b435d5..6447ea0 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java @@ -1,5 +1,7 @@ package com.ceos.vote.domain.leaderVote.service; +import com.ceos.vote.domain.auth.service.UserDetailService; +import com.ceos.vote.domain.auth.service.UserService; import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; import com.ceos.vote.domain.leaderCandidate.repository.LeaderCandidateRepository; import com.ceos.vote.domain.leaderCandidate.service.LeaderCandidateService; @@ -29,19 +31,14 @@ public class LeaderVoteService { private final UserRepository userRepository; private final LeaderCandidateRepository leaderCandidateRepository; private final LeaderCandidateService leaderCandidateService; - private LeaderVoteRepository leaderVoteRepository; + private final UserDetailService userDetailService; + private final LeaderVoteRepository leaderVoteRepository; public LeaderVote findLeaderVoteByUserId(Long id) { return leaderVoteRepository.findByUserId(id) .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_LEADER_VOTE)); } - // 추후 UserService에 통합 후 삭제 - public Users findUserById(Long id) { - return userRepository.findById(id) - .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_EXCEPTION)); - } - /* 파트별 leader candidate 조회 */ @@ -68,7 +65,7 @@ public List findLeaderCandidates() { */ @Transactional public void createLeaderVote(LeaderVoteCreateRequestDto requestDto) { - Users user = findUserById(requestDto.user_id()); + Users user = userDetailService.findUserById(requestDto.user_id()); LeaderCandidate leaderCandidate = leaderCandidateService.findLeaderCandidateById(requestDto.leader_candidate_id()); final LeaderVote leaderVote = requestDto.toEntity(user, leaderCandidate); diff --git a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java index 774b097..1dc74b5 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java @@ -1,5 +1,6 @@ package com.ceos.vote.domain.teamVote.service; +import com.ceos.vote.domain.auth.service.UserDetailService; import com.ceos.vote.domain.leaderVote.service.LeaderVoteService; import com.ceos.vote.domain.teamCandidate.entity.TeamCandidate; import com.ceos.vote.domain.teamCandidate.service.TeamCandidateService; @@ -28,8 +29,8 @@ public class TeamVoteService { private final TeamVoteRepository teamVoteRepository; private final TeamCandidateRepository teamCandidateRepository; - private final LeaderVoteService leaderVoteService; private final TeamCandidateService teamCandidateService; + private final UserDetailService userDetailService; public TeamVote findTeamVoteByUserId(Long id) { return teamVoteRepository.findByUserId(id) @@ -52,7 +53,7 @@ public List findTeamCandidates() { */ @Transactional public void createTeamVote(TeamVoteCreateRequestDto requestDto) { - Users user = leaderVoteService.findUserById(requestDto.user_id()); + Users user = userDetailService.findUserById(requestDto.user_id()); TeamCandidate teamCandidate = teamCandidateService.findTeamCandidateById(requestDto.team_candidate_id()); final TeamVote teamVote = requestDto.toEntity(user, teamCandidate); From 60b70005cdcde650af3424ed641ef496d651e28e Mon Sep 17 00:00:00 2001 From: limgahyun Date: Wed, 1 Jan 2025 22:31:52 +0900 Subject: [PATCH 46/73] fix: swagger, cors setting --- build.gradle | 2 +- .../domain/auth/dto/request/SignUpDto.java | 2 +- .../vote/global/config/CorsMvcConfig.java | 24 ++++++++++++ .../vote/global/config/SecurityConfig.java | 39 +++++++++++++++---- .../vote/global/config/SwaggerConfig.java | 15 ++++++- 5 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/ceos/vote/global/config/CorsMvcConfig.java diff --git a/build.gradle b/build.gradle index c027f94..4a9c3b0 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ dependencies { testRuntimeOnly 'org.junit.platform:junit-platform-launcher' // Swagger - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' // security implementation 'org.springframework.boot:spring-boot-starter-security' diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java index a6c5bf1..9e643af 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java @@ -22,7 +22,7 @@ public class SignUpDto { private String password; private String userTeam; private String userPart; - private List roles = new ArrayList<>(); + private final List roles = new ArrayList<>(); public Users toEntity(String encodedPassword, List roles){ return Users.builder() diff --git a/src/main/java/com/ceos/vote/global/config/CorsMvcConfig.java b/src/main/java/com/ceos/vote/global/config/CorsMvcConfig.java new file mode 100644 index 0000000..6eafcf0 --- /dev/null +++ b/src/main/java/com/ceos/vote/global/config/CorsMvcConfig.java @@ -0,0 +1,24 @@ +package com.ceos.vote.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class CorsMvcConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry corsRegistry) { + + corsRegistry.addMapping("/**") + .allowedOrigins( + "http://localhost:3000", // 로컬 개발 환경 + "http://localhost:8080", + "http://52.79.122.106", // EC2 서버 + "http://52.79.122.106:8081", + "http://52.79.122.106:8080" + ) + .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH") + .allowedHeaders("*"); + } +} diff --git a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java index 40a1e74..87a95d6 100644 --- a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java +++ b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java @@ -3,7 +3,7 @@ import com.ceos.vote.domain.auth.JwtAuthenticationFilter; import com.ceos.vote.domain.auth.JwtTokenProvider; -import com.ceos.vote.domain.auth.service.UserDetailService; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; @@ -20,22 +20,47 @@ import lombok.RequiredArgsConstructor; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; -//import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toH2Console; - +import java.util.Collections; +import java.util.List; @Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig { - - private final AuthenticationConfiguration authenticationConfiguration; - - private final UserDetailService userService; private final JwtTokenProvider jwtTokenProvider; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .cors((corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() { + + @Override + public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { + + CorsConfiguration configuration = new CorsConfiguration(); + /* + configuration.setAllowedOrigins(List.of( + "http://localhost:3000", // 로컬 개발 환경 + "http://localhost:8080", + "http://52.79.122.106", // EC2 서버 + "http://52.79.122.106:8081", + "http://52.79.122.106:8080" + )); + */ + configuration.addAllowedOriginPattern("*"); // 테스트용 전체 허용 + configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); + configuration.setAllowCredentials(true); + configuration.setAllowedHeaders(Collections.singletonList("*")); + configuration.setMaxAge(3600L); + + configuration.setExposedHeaders(Collections.singletonList("Authorization")); + + return configuration; + } + }))); http .csrf(AbstractHttpConfigurer::disable) .formLogin(AbstractHttpConfigurer::disable) diff --git a/src/main/java/com/ceos/vote/global/config/SwaggerConfig.java b/src/main/java/com/ceos/vote/global/config/SwaggerConfig.java index e881c95..589b4ce 100644 --- a/src/main/java/com/ceos/vote/global/config/SwaggerConfig.java +++ b/src/main/java/com/ceos/vote/global/config/SwaggerConfig.java @@ -17,13 +17,24 @@ description = "CEOS vote service API 명세서", version = "v1" ), - servers = {@Server(url = "http://localhost:8080", description = "local server")} + servers = {@Server(url = "http://localhost:8080", description = "local server"), + @Server(url = "http://52.79.122.106", description = "ec2 server")} ) public class SwaggerConfig { @Bean public OpenAPI openAPI() { + String jwt = "JWT"; + SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwt); + Components components = new Components().addSecuritySchemes(jwt, new SecurityScheme() + .name(jwt) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat(jwt) + ); + return new OpenAPI() - .components(new Components()); + .addSecurityItem(securityRequirement) + .components(components); } } \ No newline at end of file From ad512e6b918603dc3afebb0be615601ac4c3eb5c Mon Sep 17 00:00:00 2001 From: limgahyun Date: Wed, 1 Jan 2025 22:53:34 +0900 Subject: [PATCH 47/73] fix: change cors setting for test --- .../java/com/ceos/vote/global/config/CorsMvcConfig.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/java/com/ceos/vote/global/config/CorsMvcConfig.java b/src/main/java/com/ceos/vote/global/config/CorsMvcConfig.java index 6eafcf0..75eb7f9 100644 --- a/src/main/java/com/ceos/vote/global/config/CorsMvcConfig.java +++ b/src/main/java/com/ceos/vote/global/config/CorsMvcConfig.java @@ -11,13 +11,7 @@ public class CorsMvcConfig implements WebMvcConfigurer { public void addCorsMappings(CorsRegistry corsRegistry) { corsRegistry.addMapping("/**") - .allowedOrigins( - "http://localhost:3000", // 로컬 개발 환경 - "http://localhost:8080", - "http://52.79.122.106", // EC2 서버 - "http://52.79.122.106:8081", - "http://52.79.122.106:8080" - ) + .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH") .allowedHeaders("*"); } From ae4e5aeb2fe977a3d6553ade517267ad90830235 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Wed, 1 Jan 2025 22:57:00 +0900 Subject: [PATCH 48/73] =?UTF-8?q?feat:=20security=20role=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=20=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/ceos/vote/global/config/SecurityConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java index 87a95d6..83a345f 100644 --- a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java +++ b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java @@ -71,9 +71,9 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { .requestMatchers("/hc", "/env","/**").permitAll() .requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**").permitAll() .requestMatchers("/login/**", "/auth/join/**", "/signup", "/user", "/user/sign-in").permitAll() - .requestMatchers("/reissue").permitAll() + .requestMatchers("api/v1/auth/**", "/reissue").permitAll() .requestMatchers("/static/**", "/css/**", "/js/**", "/images/**").permitAll() - .requestMatchers("/user/test").hasRole("user") + .requestMatchers("/user/test", "/api/v1/**").hasRole("user") .anyRequest().authenticated()) .logout((logout) -> logout .logoutSuccessUrl("/login") From 427c625a2d297de4f1a43f80eb919aa124d86179 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Wed, 1 Jan 2025 23:04:20 +0900 Subject: [PATCH 49/73] =?UTF-8?q?fix:=20security=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=EC=A0=9C=ED=95=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ceos/vote/global/config/SecurityConfig.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java index 83a345f..c99ac72 100644 --- a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java +++ b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java @@ -68,12 +68,11 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/hc", "/env","/**").permitAll() + .requestMatchers("/hc", "/env", "/").permitAll() .requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**").permitAll() - .requestMatchers("/login/**", "/auth/join/**", "/signup", "/user", "/user/sign-in").permitAll() - .requestMatchers("api/v1/auth/**", "/reissue").permitAll() .requestMatchers("/static/**", "/css/**", "/js/**", "/images/**").permitAll() - .requestMatchers("/user/test", "/api/v1/**").hasRole("user") + .requestMatchers("api/v1/auth/**", "/reissue").permitAll() + .requestMatchers("/api/v1/**").hasRole("user") .anyRequest().authenticated()) .logout((logout) -> logout .logoutSuccessUrl("/login") From fea17f3cd4b478a7d6a4209e05ecd1fcb36338e9 Mon Sep 17 00:00:00 2001 From: Seoah <0325sandy@ewhain.net> Date: Thu, 2 Jan 2025 01:17:31 +0900 Subject: [PATCH 50/73] =?UTF-8?q?update:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=88=98=EC=A0=95=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 17 +++++++++---- .../domain/auth/dto/request/SignInDto.java | 2 +- .../domain/auth/dto/request/SignUpDto.java | 21 ++++++++-------- .../domain/auth/dto/response/UserInfoDto.java | 1 + .../auth/service/UserDetailService.java | 2 +- .../vote/domain/auth/service/UserService.java | 24 ++++++++++++++++--- .../ceos/vote/domain/users/entity/Users.java | 10 +++----- .../users/repository/UserRepository.java | 1 + .../vote/global/exception/ExceptionCode.java | 2 +- 9 files changed, 52 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java index 612093a..e7cda76 100644 --- a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java +++ b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java @@ -8,11 +8,17 @@ import com.ceos.vote.domain.utils.SecurityUtil; import com.ceos.vote.global.common.response.CommonResponse; import io.swagger.v3.oas.annotations.parameters.RequestBody; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.Map; +import java.util.HashMap; + +import static java.rmi.server.LogStream.log; +import static org.springframework.data.jpa.domain.AbstractPersistable_.id; @Slf4j @RestController @@ -23,7 +29,8 @@ public class AuthController { private final UserService userService; @PostMapping("/sign-up") - public CommonResponse signUp(@RequestBody SignUpDto signUpDto) { + public CommonResponse signUp(@Valid @RequestBody SignUpDto signUpDto) { + log.debug("Request body: ", signUpDto); UserInfoDto userInfoDto = userService.signUp(signUpDto); return new CommonResponse<>(userInfoDto, "회원가입에 성공했습니다."); } @@ -31,12 +38,12 @@ public CommonResponse signUp(@RequestBody SignUpDto signUpDto) { @PostMapping("/sign-in") public CommonResponse signIn(@RequestBody SignInDto signinDto){ - String username = signinDto.getUsername(); + String id = signinDto.getId(); String password = signinDto.getPassword(); - JwtToken jwtToken = userService.signIn(username, password); + JwtToken jwtToken = userService.signIn(id, password); - log.info("request username = {}, password = {}", username, password); - log.info("jwtToken accessToken = {}, refreshToken = {}", jwtToken.getAccessToken(), jwtToken.getRefreshToken()); + AuthController.log.info("request id = {}, password = {}", id, password); + AuthController.log.info("jwtToken accessToken = {}, refreshToken = {}", jwtToken.getAccessToken(), jwtToken.getRefreshToken()); return new CommonResponse<>(jwtToken, "로그인에 성공헀습니다."); } diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/request/SignInDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignInDto.java index 077ae80..1c750a2 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/request/SignInDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignInDto.java @@ -10,6 +10,6 @@ @ToString @NoArgsConstructor public class SignInDto { - private String username; + private String id; private String password; } diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java index a6c5bf1..559f420 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java @@ -3,14 +3,12 @@ import com.ceos.vote.domain.users.entity.Users; import com.ceos.vote.domain.users.enumerate.Part; import com.ceos.vote.domain.users.enumerate.Team; +import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.ArrayList; -import java.util.List; - @Getter @NoArgsConstructor @AllArgsConstructor @@ -19,21 +17,24 @@ public class SignUpDto { private String username; private String email; + @NotBlank(message = "Password is mandatory") private String password; - private String userTeam; - private String userPart; - private List roles = new ArrayList<>(); + private Team userTeam; + private Part userPart; - public Users toEntity(String encodedPassword, List roles){ + public Users toEntity(String encodedPassword){ return Users.builder() .username(username) .email(email) .password(encodedPassword) - .userTeam(Team.valueOf(userTeam)) - .userPart(Part.valueOf(userPart)) - .roles(roles) + .userTeam(userTeam) + .userPart(userPart) .build(); } + public String getPassword() { + System.out.println("Getter called for password: " + password); + return password; + } } diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/response/UserInfoDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/response/UserInfoDto.java index 7e4fd74..1b3519c 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/response/UserInfoDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/response/UserInfoDto.java @@ -22,6 +22,7 @@ public class UserInfoDto { static public UserInfoDto from(Users users) { return UserInfoDto.builder() .id(users.getId()) + .password(users.getPassword()) .username(users.getUsername()) .email(users.getEmail()) .userTeam(users.getUserTeam()) diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java index b452ed8..af7ac66 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java @@ -9,6 +9,7 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -32,7 +33,6 @@ private UserDetails createUserDetails(@NotNull Users users) { return Users.builder() .username(users.getUsername()) .password(passwordEncoder.encode(users.getPassword())) - .roles(List.of(users.getRoles().toArray(new String[0]))) .build(); } } diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java index 78dbc61..793e15c 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java @@ -7,6 +7,8 @@ import com.ceos.vote.domain.users.dto.response.UserResponseDto; import com.ceos.vote.domain.users.entity.Users; import com.ceos.vote.domain.users.repository.UserRepository; +import com.ceos.vote.global.exception.ApplicationException; +import com.ceos.vote.global.exception.ExceptionCode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -43,15 +45,31 @@ public static UserResponseDto getUserInfo(String username){ @Transactional //@Override public UserInfoDto signUp(SignUpDto signUpDto){ + + // 사용자 이름 중복 확인 if(userRepository.existsByUsername(signUpDto.getUsername())){ throw new IllegalArgumentException("이미 사용 중인 사용자 이름입니다."); } + // 사용자 이메일 중복 확인 + if(userRepository.existsByEmail(signUpDto.getEmail())){ + throw new IllegalArgumentException("이미 존재하는 이메일입니다."); + } + + log.debug("PASSWORD{}", signUpDto.getPassword()); + + //비밀번호 유효성 검사 + if (signUpDto.getPassword() == null || signUpDto.getPassword().isEmpty()) { + throw new ApplicationException(ExceptionCode.INVALID_PASSWORD); + } + //password 암호화 String encodedPassword = passwordEncoder.encode(signUpDto.getPassword()); - List roles = new ArrayList<>(); - roles.add("USER"); //user 권한 부여 - return UserInfoDto.from(userRepository.save(signUpDto.toEntity(encodedPassword, roles))); + log.debug("encodedPassword{}", encodedPassword); + + //사용자 저장 + Users newUser = userRepository.save(signUpDto.toEntity(encodedPassword)); + return UserInfoDto.from(newUser); } @Transactional diff --git a/src/main/java/com/ceos/vote/domain/users/entity/Users.java b/src/main/java/com/ceos/vote/domain/users/entity/Users.java index ca8d77f..5a60440 100644 --- a/src/main/java/com/ceos/vote/domain/users/entity/Users.java +++ b/src/main/java/com/ceos/vote/domain/users/entity/Users.java @@ -27,7 +27,7 @@ public class Users implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "user_id", updatable = false, nullable = false) + @Column(name = "user_id", nullable = false) private Long id; @Column(name="name") @@ -39,6 +39,7 @@ public class Users implements UserDetails { @Column(name = "password", nullable = false) private String password; + @Enumerated(EnumType.STRING) @Column(name="user_team") private Team userTeam; @@ -46,13 +47,8 @@ public class Users implements UserDetails { @Column(name="user_part") private Part userPart; - @ElementCollection(fetch = FetchType.EAGER) - @Builder.Default - private List roles = new ArrayList<>(); @Override public Collection getAuthorities() { - return this.roles.stream() - .map(SimpleGrantedAuthority::new) - .collect(Collectors.toList()); + return List.of(new SimpleGrantedAuthority("ROLE_USER")); } } \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java index dee39e0..7944a8b 100644 --- a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java +++ b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java @@ -16,4 +16,5 @@ static Optional findByUsername(String username) { String username(String username); boolean existsByUsername(String username); + boolean existsByEmail(String email); } \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java b/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java index ed9ce7a..1a1c0dc 100644 --- a/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java +++ b/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java @@ -24,7 +24,7 @@ public enum ExceptionCode { // 3000: USER NOT_FOUND_USER(HttpStatus.NOT_FOUND, 3001, "해당 회원을 찾을 수 없습니다."), INVALID_USER_CREDENTIALS(HttpStatus.UNAUTHORIZED, 3002, "잘못된 사용자 인증 정보입니다."), - + INVALID_PASSWORD(HttpStatus.BAD_REQUEST, 3003, "비밀번호를 찾을 수 없습니다."), // 4000: Leader Vote Error NOT_FOUND_LEADER_CANDIDATE(HttpStatus.NOT_FOUND, 4001, "해당 leader candidate가 존재하지 않습니다."), From 416a7f882aca1cb61b927a41dab37cbb390c27fc Mon Sep 17 00:00:00 2001 From: Seoah <0325sandy@ewhain.net> Date: Thu, 2 Jan 2025 02:59:20 +0900 Subject: [PATCH 51/73] =?UTF-8?q?update:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=84=B1=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ceos/vote/domain/auth/controller/AuthController.java | 8 ++++---- .../com/ceos/vote/domain/auth/dto/request/SignInDto.java | 2 +- .../com/ceos/vote/domain/auth/dto/request/SignUpDto.java | 1 - .../ceos/vote/domain/auth/service/UserDetailService.java | 4 ++-- .../com/ceos/vote/domain/auth/service/UserService.java | 4 ++-- .../java/com/ceos/vote/domain/users/entity/Users.java | 2 +- .../ceos/vote/domain/users/repository/UserRepository.java | 4 +--- .../java/com/ceos/vote/global/config/SecurityConfig.java | 7 ------- 8 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java index e7cda76..ed39743 100644 --- a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java +++ b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java @@ -7,11 +7,11 @@ import com.ceos.vote.domain.auth.service.UserService; import com.ceos.vote.domain.utils.SecurityUtil; import com.ceos.vote.global.common.response.CommonResponse; -import io.swagger.v3.oas.annotations.parameters.RequestBody; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Map; @@ -38,11 +38,11 @@ public CommonResponse signUp(@Valid @RequestBody SignUpDto signUpDt @PostMapping("/sign-in") public CommonResponse signIn(@RequestBody SignInDto signinDto){ - String id = signinDto.getId(); + String username = signinDto.getUsername(); String password = signinDto.getPassword(); - JwtToken jwtToken = userService.signIn(id, password); + JwtToken jwtToken = userService.signIn(username, password); - AuthController.log.info("request id = {}, password = {}", id, password); + AuthController.log.info("request name = {}, password = {}", username, password); AuthController.log.info("jwtToken accessToken = {}, refreshToken = {}", jwtToken.getAccessToken(), jwtToken.getRefreshToken()); return new CommonResponse<>(jwtToken, "로그인에 성공헀습니다."); diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/request/SignInDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignInDto.java index 1c750a2..077ae80 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/request/SignInDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignInDto.java @@ -10,6 +10,6 @@ @ToString @NoArgsConstructor public class SignInDto { - private String id; + private String username; private String password; } diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java index 559f420..b6dfd92 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java @@ -17,7 +17,6 @@ public class SignUpDto { private String username; private String email; - @NotBlank(message = "Password is mandatory") private String password; private Team userTeam; private Part userPart; diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java index af7ac66..59921a4 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java @@ -20,11 +20,11 @@ public class UserDetailService implements UserDetailsService { private final UserRepository userRepository; - private final PasswordEncoder passwordEncoder = null; + private final BCryptPasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - return UserRepository.findByUsername(username) + return userRepository.findByUsername(username) .map(this::createUserDetails) .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_USER)); } diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java index 793e15c..0d0d383 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java @@ -32,8 +32,8 @@ public class UserService { private final JwtTokenProvider jwtTokenProvider; private final PasswordEncoder passwordEncoder; - public static UserResponseDto getUserInfo(String username){ - Users users = UserRepository.findByUsername(username).orElse(null); + public UserResponseDto getUserInfo(String username){ + Users users = userRepository.findByUsername(username).orElse(null); return new UserResponseDto( users.getUserPart().name(), diff --git a/src/main/java/com/ceos/vote/domain/users/entity/Users.java b/src/main/java/com/ceos/vote/domain/users/entity/Users.java index 5a60440..871ed41 100644 --- a/src/main/java/com/ceos/vote/domain/users/entity/Users.java +++ b/src/main/java/com/ceos/vote/domain/users/entity/Users.java @@ -27,7 +27,7 @@ public class Users implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "user_id", nullable = false) + @Column(name = "user_id", nullable = false, unique = true) private Long id; @Column(name="name") diff --git a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java index 7944a8b..88133d5 100644 --- a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java +++ b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java @@ -9,9 +9,7 @@ @Repository public interface UserRepository extends JpaRepository { - static Optional findByUsername(String username) { - return null; - } + Optional findByUsername(String username); String username(String username); boolean existsByUsername(String username); diff --git a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java index 40a1e74..d3687ed 100644 --- a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java +++ b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java @@ -68,13 +68,6 @@ public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } - -// @Bean -// public WebSecurityCustomizer configure(){ -// return (web) -> web.ignoring() -// .requestMatchers(toH2Console()) -// .requestMatchers(new AntPathRequestMatcher("/static/**")); -// } @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); From 383296e878ebc7a55ac69937afed33ca65425886 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Thu, 2 Jan 2025 03:12:21 +0900 Subject: [PATCH 52/73] =?UTF-8?q?feat:=20jwt=20secret=20key=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EB=B3=80=EC=88=98=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/CICD.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 61d8f51..2f86c5b 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -87,6 +87,7 @@ jobs: key: ${{ secrets.EC2_SSH_KEY }} script_stop: true script: | + export JWT_SECRET_KEY=${{ secrets.JWT_SECRET_KEY }} sudo docker pull ${{ secrets.DOCKER_USERNAME }}/vote:latest sudo docker-compose -f docker-compose-${{env.TARGET_UPSTREAM}}.yml up -d From 094c7809205ed6074ebc3e3d8cc93493b2c265aa Mon Sep 17 00:00:00 2001 From: limgahyun Date: Thu, 2 Jan 2025 03:21:01 +0900 Subject: [PATCH 53/73] =?UTF-8?q?fix:=20swagger=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=ED=97=88=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/ceos/vote/global/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java index c99ac72..78e31f3 100644 --- a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java +++ b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java @@ -69,7 +69,7 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authorize -> authorize .requestMatchers("/hc", "/env", "/").permitAll() - .requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**").permitAll() + .requestMatchers("/swagger/**", "/swagger-ui.html/**", "/swagger-ui/**", "/v3/api-docs/**").permitAll() .requestMatchers("/static/**", "/css/**", "/js/**", "/images/**").permitAll() .requestMatchers("api/v1/auth/**", "/reissue").permitAll() .requestMatchers("/api/v1/**").hasRole("user") From 3fc0312ec965f472f6eb1aa3274b7e0a3605803d Mon Sep 17 00:00:00 2001 From: Seoah <0325sandy@ewhain.net> Date: Thu, 2 Jan 2025 03:48:56 +0900 Subject: [PATCH 54/73] =?UTF-8?q?update:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ceos/vote/domain/auth/service/UserDetailService.java | 4 ++-- src/main/java/com/ceos/vote/global/config/SecurityConfig.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java index d92af41..f9a06dc 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java @@ -20,7 +20,7 @@ public class UserDetailService implements UserDetailsService { private final UserRepository userRepository; - private final BCryptPasswordEncoder passwordEncoder; + private final PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { @@ -37,7 +37,7 @@ public Users findUserById(Long id) { private UserDetails createUserDetails(@NotNull Users users) { return Users.builder() .username(users.getUsername()) - .password(passwordEncoder.encode(users.getPassword())) + .password(users.getPassword()) .build(); } } diff --git a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java index bc8d3f7..452a310 100644 --- a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java +++ b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java @@ -93,7 +93,7 @@ public BCryptPasswordEncoder bCryptPasswordEncoder() { } @Bean - public PasswordEncoder passwordEncoder() { + public static PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); // return NoOpPasswordEncoder.getInstance(); } From 28080ad4afa83bdc479e41f7579e08282c1d01aa Mon Sep 17 00:00:00 2001 From: Seoah <0325sandy@ewhain.net> Date: Thu, 2 Jan 2025 04:04:09 +0900 Subject: [PATCH 55/73] =?UTF-8?q?update:=20=ED=9A=8C=EC=9B=90=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ceos/vote/domain/users/controller/UserController.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/users/controller/UserController.java b/src/main/java/com/ceos/vote/domain/users/controller/UserController.java index 414c4ff..c7617fc 100644 --- a/src/main/java/com/ceos/vote/domain/users/controller/UserController.java +++ b/src/main/java/com/ceos/vote/domain/users/controller/UserController.java @@ -3,6 +3,8 @@ import com.ceos.vote.domain.auth.service.UserService; import com.ceos.vote.domain.users.dto.response.UserResponseDto; import com.ceos.vote.global.common.response.CommonResponse; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -18,9 +20,9 @@ public UserController(UserService userService) { this.userService = userService; } - @GetMapping("/{userId}") - public CommonResponse getUserInfo(@PathVariable String username){ - UserResponseDto userResponseDto = userService.getUserInfo(username); + @GetMapping + public CommonResponse getUserInfo(@AuthenticationPrincipal User user) { + UserResponseDto userResponseDto = userService.getUserInfo(user.getUsername()); return new CommonResponse<>(userResponseDto, "사용자 정보 조회에 성공했습니다."); } } From 923407369359b1583e0ac961b4adb1e9cc6a4be1 Mon Sep 17 00:00:00 2001 From: Seoah <0325sandy@ewhain.net> Date: Thu, 2 Jan 2025 04:12:41 +0900 Subject: [PATCH 56/73] =?UTF-8?q?update:=20=EC=A3=BC=EC=84=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ceos/vote/domain/users/controller/UserController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/ceos/vote/domain/users/controller/UserController.java b/src/main/java/com/ceos/vote/domain/users/controller/UserController.java index c7617fc..b3b5247 100644 --- a/src/main/java/com/ceos/vote/domain/users/controller/UserController.java +++ b/src/main/java/com/ceos/vote/domain/users/controller/UserController.java @@ -20,6 +20,7 @@ public UserController(UserService userService) { this.userService = userService; } + //현재 가지고 있는(로그인된) 정보로 회원 정보 조회 @GetMapping public CommonResponse getUserInfo(@AuthenticationPrincipal User user) { UserResponseDto userResponseDto = userService.getUserInfo(user.getUsername()); From c1e560100c2f33c741f07bb097f9c4a2290815e0 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Fri, 3 Jan 2025 15:36:44 +0900 Subject: [PATCH 57/73] hotfix: api access permitAll --- src/main/java/com/ceos/vote/global/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java index 452a310..702cbe0 100644 --- a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java +++ b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java @@ -71,7 +71,7 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { .requestMatchers("/hc", "/env", "/").permitAll() .requestMatchers("/swagger/**", "/swagger-ui.html/**", "/swagger-ui/**", "/v3/api-docs/**").permitAll() .requestMatchers("/static/**", "/css/**", "/js/**", "/images/**").permitAll() - .requestMatchers("api/v1/auth/**", "/reissue").permitAll() + .requestMatchers("api/v1/auth/**", "/api/v1/**", "/reissue").permitAll() .requestMatchers("/api/v1/**").hasRole("user") .anyRequest().authenticated()) .logout((logout) -> logout From 951c2b628325419e23427e9b2c119049a16031d0 Mon Sep 17 00:00:00 2001 From: Seoah Hwang Date: Fri, 3 Jan 2025 00:49:07 -0800 Subject: [PATCH 58/73] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20resp?= =?UTF-8?q?onse=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 19 +++++++------ .../auth/dto/response/SignInResponseDto.java | 28 +++++++++++++++++++ .../vote/domain/auth/service/UserService.java | 6 ++++ 3 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/ceos/vote/domain/auth/dto/response/SignInResponseDto.java diff --git a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java index ed39743..36aab68 100644 --- a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java +++ b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java @@ -29,23 +29,26 @@ public class AuthController { private final UserService userService; @PostMapping("/sign-up") - public CommonResponse signUp(@Valid @RequestBody SignUpDto signUpDto) { - log.debug("Request body: ", signUpDto); - UserInfoDto userInfoDto = userService.signUp(signUpDto); - return new CommonResponse<>(userInfoDto, "회원가입에 성공했습니다."); + public CommonResponse signUp(@Valid @RequestBody SignUpDto signUpDto) { + //log.debug("Request body: ", signUpDto); + userService.signUp(signUpRequestDto); + return new CommonResponse<>("회원가입에 성공했습니다."); } @PostMapping("/sign-in") - public CommonResponse signIn(@RequestBody SignInDto signinDto){ + public CommonResponse signIn(@RequestBody SignInDto signinDto){ String username = signinDto.getUsername(); String password = signinDto.getPassword(); JwtToken jwtToken = userService.signIn(username, password); - AuthController.log.info("request name = {}, password = {}", username, password); - AuthController.log.info("jwtToken accessToken = {}, refreshToken = {}", jwtToken.getAccessToken(), jwtToken.getRefreshToken()); + Long userId = userService.findUserIdByUsername(username); + Boolean isVotingLeader = leaderVoteService.findLeaderVoteByUserId(userId) != null; + Boolean isVotingTeam = teamVoteService.findTeamVoteByUserId(userId) != null; - return new CommonResponse<>(jwtToken, "로그인에 성공헀습니다."); + SignInResponseDto signInResponseDto = SignInResponseDto.from(jwtToken, isVotingLeader, isVotingTeam); + + return new CommonResponse<>(signInResponseDto, "로그인에 성공헀습니다."); } @PostMapping("/test") diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/response/SignInResponseDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/response/SignInResponseDto.java new file mode 100644 index 0000000..1a153ff --- /dev/null +++ b/src/main/java/com/ceos/vote/domain/auth/dto/response/SignInResponseDto.java @@ -0,0 +1,28 @@ +package com.ceos.vote.domain.auth.dto.response; + +import com.ceos.vote.domain.users.entity.Users; +import com.ceos.vote.domain.users.enumerate.Part; +import com.ceos.vote.domain.users.enumerate.Team; +import lombok.*; + +@Builder +@Data +@AllArgsConstructor +public class SignInResponseDto { + private String grantType; + private String accessToken; + private String refreshToken; + private Part part; + private Boolean is_voting_leader; + private Boolean is_voting_team; + + public static SignInResponseDto from(JwtToken jwtToken, Boolean is_voting_leader, Boolean is_voting_team) { + return SignInResponseDto.builder() + .grantType(jwtToken.getGrantType()) + .accessToken(jwtToken.getAccessToken()) + .refreshToken(jwtToken.getRefreshToken()) + .userPart(users.getUserPart()) + .isVotingLeader(isVotingLeader) + .isVotingTeam(is_voting_team) + .build(); +} \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java index 0d0d383..87aefb7 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java @@ -42,6 +42,12 @@ public UserResponseDto getUserInfo(String username){ ); } + public Long findUserIdByUsername(String username){ + return userRepository.findByUsername(username) + .map(User::getId) + .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_USER)); + } + @Transactional //@Override public UserInfoDto signUp(SignUpDto signUpDto){ From d3b001fc96f35a6023490d3487cb7cd2be47b86e Mon Sep 17 00:00:00 2001 From: limgahyun Date: Fri, 3 Jan 2025 18:42:27 +0900 Subject: [PATCH 59/73] =?UTF-8?q?fix:=20team=20enum=20type=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/leaderCandidate/entity/LeaderCandidate.java | 1 + .../vote/domain/teamCandidate/entity/TeamCandidate.java | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/leaderCandidate/entity/LeaderCandidate.java b/src/main/java/com/ceos/vote/domain/leaderCandidate/entity/LeaderCandidate.java index 6b16322..0ee9e00 100644 --- a/src/main/java/com/ceos/vote/domain/leaderCandidate/entity/LeaderCandidate.java +++ b/src/main/java/com/ceos/vote/domain/leaderCandidate/entity/LeaderCandidate.java @@ -19,6 +19,7 @@ public class LeaderCandidate extends BaseEntity { @Column(name = "leader_candidate_id") private Long id; + @Enumerated(EnumType.STRING) @Column(length = 32) private Part part; diff --git a/src/main/java/com/ceos/vote/domain/teamCandidate/entity/TeamCandidate.java b/src/main/java/com/ceos/vote/domain/teamCandidate/entity/TeamCandidate.java index 67e8635..49bda6e 100644 --- a/src/main/java/com/ceos/vote/domain/teamCandidate/entity/TeamCandidate.java +++ b/src/main/java/com/ceos/vote/domain/teamCandidate/entity/TeamCandidate.java @@ -1,5 +1,6 @@ package com.ceos.vote.domain.teamCandidate.entity; +import com.ceos.vote.domain.users.enumerate.Team; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; @@ -15,11 +16,12 @@ public class TeamCandidate { @Column(name = "team_candidate_id") private Long id; + @Enumerated(EnumType.STRING) @Column - private String name; + private Team teamName; @Builder - public TeamCandidate(String name) { - this.name = name; + public TeamCandidate(Team name) { + this.teamName = name; } } From 3f982912af0513b71f4885e89c32a73d4fd75b3a Mon Sep 17 00:00:00 2001 From: limgahyun Date: Fri, 3 Jan 2025 18:45:00 +0900 Subject: [PATCH 60/73] =?UTF-8?q?fix:=20enum=20type=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/leaderCandidate/entity/LeaderCandidate.java | 1 + .../vote/domain/teamCandidate/entity/TeamCandidate.java | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/leaderCandidate/entity/LeaderCandidate.java b/src/main/java/com/ceos/vote/domain/leaderCandidate/entity/LeaderCandidate.java index 6b16322..0ee9e00 100644 --- a/src/main/java/com/ceos/vote/domain/leaderCandidate/entity/LeaderCandidate.java +++ b/src/main/java/com/ceos/vote/domain/leaderCandidate/entity/LeaderCandidate.java @@ -19,6 +19,7 @@ public class LeaderCandidate extends BaseEntity { @Column(name = "leader_candidate_id") private Long id; + @Enumerated(EnumType.STRING) @Column(length = 32) private Part part; diff --git a/src/main/java/com/ceos/vote/domain/teamCandidate/entity/TeamCandidate.java b/src/main/java/com/ceos/vote/domain/teamCandidate/entity/TeamCandidate.java index 67e8635..a6a48cb 100644 --- a/src/main/java/com/ceos/vote/domain/teamCandidate/entity/TeamCandidate.java +++ b/src/main/java/com/ceos/vote/domain/teamCandidate/entity/TeamCandidate.java @@ -1,5 +1,6 @@ package com.ceos.vote.domain.teamCandidate.entity; +import com.ceos.vote.domain.users.enumerate.Team; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; @@ -15,11 +16,12 @@ public class TeamCandidate { @Column(name = "team_candidate_id") private Long id; + @Enumerated(EnumType.STRING) @Column - private String name; + private Team teamName; @Builder - public TeamCandidate(String name) { - this.name = name; + public TeamCandidate(Team team) { + this.teamName = team; } } From 71563b3209568941d2da097e2fbc606bd262faf1 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Fri, 3 Jan 2025 18:47:33 +0900 Subject: [PATCH 61/73] =?UTF-8?q?fix:=20team=20enum=20type=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/teamVote/dto/response/TeamCandidateResponseDto.java | 2 +- .../com/ceos/vote/domain/teamVote/service/TeamVoteService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamCandidateResponseDto.java b/src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamCandidateResponseDto.java index 17d12df..00ae42d 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamCandidateResponseDto.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/dto/response/TeamCandidateResponseDto.java @@ -9,7 +9,7 @@ public record TeamCandidateResponseDto ( public static TeamCandidateResponseDto from(TeamCandidate teamCandidate) { return new TeamCandidateResponseDto( teamCandidate.getId(), - teamCandidate.getName() + teamCandidate.getTeamName().getDescription() ); } } diff --git a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java index 1dc74b5..9634459 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java @@ -95,7 +95,7 @@ public List getTeamResult() { // 각 후보의 투표 결과 반환 및 득표수 내림차순 정렬 return teamCandidates.stream() - .map(candidate -> TeamResultResponseDto.from(candidate.getName(), teamVoteRepository.countByTeamCandidate(candidate))) + .map(candidate -> TeamResultResponseDto.from(candidate.getTeamName().getDescription(), teamVoteRepository.countByTeamCandidate(candidate))) .sorted(Comparator.comparingLong(TeamResultResponseDto::getVoteCount).reversed()) // 내림차순 정렬 .toList(); } From 7ef1db966ff61641890006ba0dc0369fdd9e3cac Mon Sep 17 00:00:00 2001 From: Seoah Hwang Date: Fri, 3 Jan 2025 02:48:51 -0800 Subject: [PATCH 62/73] =?UTF-8?q?fix:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20response=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 30 +++++++------------ .../{SignInDto.java => SignInRequestDto.java} | 2 +- .../{SignUpDto.java => SignUpRequestDto.java} | 3 +- .../auth/dto/response/SignInResponseDto.java | 26 +--------------- .../vote/domain/auth/service/UserService.java | 25 +++++----------- 5 files changed, 22 insertions(+), 64 deletions(-) rename src/main/java/com/ceos/vote/domain/auth/dto/request/{SignInDto.java => SignInRequestDto.java} (88%) rename src/main/java/com/ceos/vote/domain/auth/dto/request/{SignUpDto.java => SignUpRequestDto.java} (92%) diff --git a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java index 36aab68..5d2ed9b 100644 --- a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java +++ b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java @@ -1,9 +1,8 @@ package com.ceos.vote.domain.auth.controller; import com.ceos.vote.domain.auth.JwtToken; -import com.ceos.vote.domain.auth.dto.request.SignInDto; -import com.ceos.vote.domain.auth.dto.request.SignUpDto; -import com.ceos.vote.domain.auth.dto.response.UserInfoDto; +import com.ceos.vote.domain.auth.dto.request.SignInRequestDto; +import com.ceos.vote.domain.auth.dto.request.SignUpRequestDto; import com.ceos.vote.domain.auth.service.UserService; import com.ceos.vote.domain.utils.SecurityUtil; import com.ceos.vote.global.common.response.CommonResponse; @@ -14,11 +13,6 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.Map; -import java.util.HashMap; - -import static java.rmi.server.LogStream.log; -import static org.springframework.data.jpa.domain.AbstractPersistable_.id; @Slf4j @RestController @@ -28,27 +22,25 @@ public class AuthController { private final UserService userService; + // 회원가입 @PostMapping("/sign-up") - public CommonResponse signUp(@Valid @RequestBody SignUpDto signUpDto) { - //log.debug("Request body: ", signUpDto); + public CommonResponse signUp(@Valid @RequestBody SignUpRequestDto signUpRequestDto) { + log.debug("Request body: ", signUpRequestDto); userService.signUp(signUpRequestDto); return new CommonResponse<>("회원가입에 성공했습니다."); } @PostMapping("/sign-in") - public CommonResponse signIn(@RequestBody SignInDto signinDto){ + public CommonResponse signIn(@RequestBody SignInRequestDto signinRequestDto){ - String username = signinDto.getUsername(); - String password = signinDto.getPassword(); + String username = signinRequestDto.getUsername(); + String password = signinRequestDto.getPassword(); JwtToken jwtToken = userService.signIn(username, password); - Long userId = userService.findUserIdByUsername(username); - Boolean isVotingLeader = leaderVoteService.findLeaderVoteByUserId(userId) != null; - Boolean isVotingTeam = teamVoteService.findTeamVoteByUserId(userId) != null; - - SignInResponseDto signInResponseDto = SignInResponseDto.from(jwtToken, isVotingLeader, isVotingTeam); + AuthController.log.info("request name = {}, password = {}", username, password); + AuthController.log.info("jwtToken accessToken = {}, refreshToken = {}", jwtToken.getAccessToken(), jwtToken.getRefreshToken()); - return new CommonResponse<>(signInResponseDto, "로그인에 성공헀습니다."); + return new CommonResponse<>(jwtToken, "로그인에 성공헀습니다."); } @PostMapping("/test") diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/request/SignInDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignInRequestDto.java similarity index 88% rename from src/main/java/com/ceos/vote/domain/auth/dto/request/SignInDto.java rename to src/main/java/com/ceos/vote/domain/auth/dto/request/SignInRequestDto.java index 077ae80..ed998dc 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/request/SignInDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignInRequestDto.java @@ -9,7 +9,7 @@ @Setter @ToString @NoArgsConstructor -public class SignInDto { +public class SignInRequestDto { private String username; private String password; } diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpRequestDto.java similarity index 92% rename from src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java rename to src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpRequestDto.java index 496f718..d7e2d6b 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/request/SignUpRequestDto.java @@ -3,7 +3,6 @@ import com.ceos.vote.domain.users.entity.Users; import com.ceos.vote.domain.users.enumerate.Part; import com.ceos.vote.domain.users.enumerate.Team; -import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -13,7 +12,7 @@ @NoArgsConstructor @AllArgsConstructor @Builder -public class SignUpDto { +public class SignUpRequestDto { private String username; private String email; diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/response/SignInResponseDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/response/SignInResponseDto.java index 1a153ff..608549d 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/response/SignInResponseDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/response/SignInResponseDto.java @@ -1,28 +1,4 @@ package com.ceos.vote.domain.auth.dto.response; -import com.ceos.vote.domain.users.entity.Users; -import com.ceos.vote.domain.users.enumerate.Part; -import com.ceos.vote.domain.users.enumerate.Team; -import lombok.*; - -@Builder -@Data -@AllArgsConstructor public class SignInResponseDto { - private String grantType; - private String accessToken; - private String refreshToken; - private Part part; - private Boolean is_voting_leader; - private Boolean is_voting_team; - - public static SignInResponseDto from(JwtToken jwtToken, Boolean is_voting_leader, Boolean is_voting_team) { - return SignInResponseDto.builder() - .grantType(jwtToken.getGrantType()) - .accessToken(jwtToken.getAccessToken()) - .refreshToken(jwtToken.getRefreshToken()) - .userPart(users.getUserPart()) - .isVotingLeader(isVotingLeader) - .isVotingTeam(is_voting_team) - .build(); -} \ No newline at end of file +} diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java index 87aefb7..6617209 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java @@ -2,7 +2,7 @@ import com.ceos.vote.domain.auth.JwtToken; import com.ceos.vote.domain.auth.JwtTokenProvider; -import com.ceos.vote.domain.auth.dto.request.SignUpDto; +import com.ceos.vote.domain.auth.dto.request.SignUpRequestDto; import com.ceos.vote.domain.auth.dto.response.UserInfoDto; import com.ceos.vote.domain.users.dto.response.UserResponseDto; import com.ceos.vote.domain.users.entity.Users; @@ -18,9 +18,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; - @RequiredArgsConstructor @Service @Transactional(readOnly = true) @@ -42,39 +39,33 @@ public UserResponseDto getUserInfo(String username){ ); } - public Long findUserIdByUsername(String username){ - return userRepository.findByUsername(username) - .map(User::getId) - .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_USER)); - } - @Transactional //@Override - public UserInfoDto signUp(SignUpDto signUpDto){ + public UserInfoDto signUp(SignUpRequestDto signUpRequestDto){ // 사용자 이름 중복 확인 - if(userRepository.existsByUsername(signUpDto.getUsername())){ + if(userRepository.existsByUsername(signUpRequestDto.getUsername())){ throw new IllegalArgumentException("이미 사용 중인 사용자 이름입니다."); } // 사용자 이메일 중복 확인 - if(userRepository.existsByEmail(signUpDto.getEmail())){ + if(userRepository.existsByEmail(signUpRequestDto.getEmail())){ throw new IllegalArgumentException("이미 존재하는 이메일입니다."); } - log.debug("PASSWORD{}", signUpDto.getPassword()); + log.debug("PASSWORD{}", signUpRequestDto.getPassword()); //비밀번호 유효성 검사 - if (signUpDto.getPassword() == null || signUpDto.getPassword().isEmpty()) { + if (signUpRequestDto.getPassword() == null || signUpRequestDto.getPassword().isEmpty()) { throw new ApplicationException(ExceptionCode.INVALID_PASSWORD); } //password 암호화 - String encodedPassword = passwordEncoder.encode(signUpDto.getPassword()); + String encodedPassword = passwordEncoder.encode(signUpRequestDto.getPassword()); log.debug("encodedPassword{}", encodedPassword); //사용자 저장 - Users newUser = userRepository.save(signUpDto.toEntity(encodedPassword)); + Users newUser = userRepository.save(signUpRequestDto.toEntity(encodedPassword)); return UserInfoDto.from(newUser); } From 49a3fe69cbb68687b32cfba47766128bb0a7ff9a Mon Sep 17 00:00:00 2001 From: Seoah Hwang Date: Fri, 3 Jan 2025 03:12:16 -0800 Subject: [PATCH 63/73] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20resp?= =?UTF-8?q?onse=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 26 ++++++++++++----- .../auth/dto/response/SignInResponseDto.java | 29 +++++++++++++++++++ .../vote/domain/auth/service/UserService.java | 13 +++++++++ .../users/repository/UserRepository.java | 1 - 4 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java index 5d2ed9b..2d63483 100644 --- a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java +++ b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java @@ -3,7 +3,12 @@ import com.ceos.vote.domain.auth.JwtToken; import com.ceos.vote.domain.auth.dto.request.SignInRequestDto; import com.ceos.vote.domain.auth.dto.request.SignUpRequestDto; +import com.ceos.vote.domain.auth.dto.response.SignInResponseDto; +import com.ceos.vote.domain.auth.dto.response.UserInfoDto; import com.ceos.vote.domain.auth.service.UserService; +import com.ceos.vote.domain.leaderVote.service.LeaderVoteService; +import com.ceos.vote.domain.teamVote.service.TeamVoteService; +import com.ceos.vote.domain.users.enumerate.Part; import com.ceos.vote.domain.utils.SecurityUtil; import com.ceos.vote.global.common.response.CommonResponse; import jakarta.validation.Valid; @@ -14,6 +19,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; + @Slf4j @RestController @RequestMapping("/api/v1/auth") @@ -21,26 +27,30 @@ public class AuthController { private final UserService userService; + private final LeaderVoteService leaderVoteService; + private final TeamVoteService teamVoteService; // 회원가입 @PostMapping("/sign-up") - public CommonResponse signUp(@Valid @RequestBody SignUpRequestDto signUpRequestDto) { + public CommonResponse signUp(@Valid @RequestBody SignUpRequestDto signUpRequestDto) { log.debug("Request body: ", signUpRequestDto); - userService.signUp(signUpRequestDto); - return new CommonResponse<>("회원가입에 성공했습니다."); + UserInfoDto userInfoDto = userService.signUp(signUpRequestDto); + return new CommonResponse<>(userInfoDto, "회원가입에 성공했습니다."); } @PostMapping("/sign-in") - public CommonResponse signIn(@RequestBody SignInRequestDto signinRequestDto){ + public CommonResponse signIn(@RequestBody SignInRequestDto signinRequestDto){ String username = signinRequestDto.getUsername(); String password = signinRequestDto.getPassword(); JwtToken jwtToken = userService.signIn(username, password); - AuthController.log.info("request name = {}, password = {}", username, password); - AuthController.log.info("jwtToken accessToken = {}, refreshToken = {}", jwtToken.getAccessToken(), jwtToken.getRefreshToken()); - - return new CommonResponse<>(jwtToken, "로그인에 성공헀습니다."); + Long userId = userService.findUserIdByUsername(username); + Boolean isVotingLeader = leaderVoteService.findLeaderVoteByUserId(userId) != null; + Boolean isVotingTeam = teamVoteService.findTeamVoteByUserId(userId) != null; + Part userpart = userService.findUserPartByUsername(username); + SignInResponseDto responseDto = SignInResponseDto.from(jwtToken, userpart, isVotingLeader, isVotingTeam); + return new CommonResponse<>(responseDto, "로그인에 성공헀습니다."); } @PostMapping("/test") diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/response/SignInResponseDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/response/SignInResponseDto.java index 608549d..24e67a0 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/response/SignInResponseDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/response/SignInResponseDto.java @@ -1,4 +1,33 @@ package com.ceos.vote.domain.auth.dto.response; + +import com.ceos.vote.domain.auth.JwtToken; +import com.ceos.vote.domain.users.entity.Users; +import com.ceos.vote.domain.users.enumerate.Part; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +@AllArgsConstructor public class SignInResponseDto { + + private String grantType; + private String accessToken; + private String refreshToken; + private Part userPart; + private Boolean isVotingLeader; + private Boolean isVotingTeam; + + public static SignInResponseDto from(JwtToken jwtToken, Part userPart, Boolean isVotingLeader, Boolean isVotingTeam) { + return SignInResponseDto.builder() + .grantType(jwtToken.getGrantType()) + .accessToken(jwtToken.getAccessToken()) + .refreshToken(jwtToken.getRefreshToken()) + .userPart(userPart) + .isVotingLeader(isVotingLeader) + .isVotingTeam(isVotingTeam) + .build(); + } } diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java index 6617209..383c889 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java @@ -6,6 +6,7 @@ import com.ceos.vote.domain.auth.dto.response.UserInfoDto; import com.ceos.vote.domain.users.dto.response.UserResponseDto; import com.ceos.vote.domain.users.entity.Users; +import com.ceos.vote.domain.users.enumerate.Part; import com.ceos.vote.domain.users.repository.UserRepository; import com.ceos.vote.global.exception.ApplicationException; import com.ceos.vote.global.exception.ExceptionCode; @@ -39,6 +40,12 @@ public UserResponseDto getUserInfo(String username){ ); } + public Long findUserIdByUsername(String username) { + return userRepository.findByUsername(username) + .map(Users::getId) + .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_USER)); + } + @Transactional //@Override public UserInfoDto signUp(SignUpRequestDto signUpRequestDto){ @@ -82,4 +89,10 @@ public JwtToken signIn(String username, String password){ return jwtToken; } + + public Part findUserPartByUsername(String username) { + return userRepository.findByUsername(username) + .map(Users::getUserPart) // User 엔티티에서 Part 필드 가져오기 + .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_USER)); + } } diff --git a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java index 88133d5..f7e6ac1 100644 --- a/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java +++ b/src/main/java/com/ceos/vote/domain/users/repository/UserRepository.java @@ -13,6 +13,5 @@ public interface UserRepository extends JpaRepository { String username(String username); boolean existsByUsername(String username); - boolean existsByEmail(String email); } \ No newline at end of file From 10c56e959f02920e02a7f753973a638fc8ae4987 Mon Sep 17 00:00:00 2001 From: Seoah Hwang Date: Fri, 3 Jan 2025 03:31:23 -0800 Subject: [PATCH 64/73] =?UTF-8?q?fix:=20=ED=88=AC=ED=91=9C=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=EC=9E=98=EB=AA=BB=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/controller/AuthController.java | 17 +++++++++++++++-- .../leaderVote/service/LeaderVoteService.java | 5 ++++- .../teamVote/service/TeamVoteService.java | 4 ++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java index 2d63483..f952f5e 100644 --- a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java +++ b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java @@ -11,6 +11,7 @@ import com.ceos.vote.domain.users.enumerate.Part; import com.ceos.vote.domain.utils.SecurityUtil; import com.ceos.vote.global.common.response.CommonResponse; +import com.ceos.vote.global.exception.ApplicationException; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -46,8 +47,20 @@ public CommonResponse signIn(@RequestBody SignInRequestDto si JwtToken jwtToken = userService.signIn(username, password); Long userId = userService.findUserIdByUsername(username); - Boolean isVotingLeader = leaderVoteService.findLeaderVoteByUserId(userId) != null; - Boolean isVotingTeam = teamVoteService.findTeamVoteByUserId(userId) != null; + Boolean isVotingLeader = false; + try { + leaderVoteService.checkLeaderVoteByUserId(userId); + isVotingLeader = true; + } catch (ApplicationException e) { + isVotingLeader = false; + } + Boolean isVotingTeam = false; + try { + teamVoteService.checkTeamVoteByUserId(userId); + isVotingTeam = true; + } catch (ApplicationException e) { + isVotingTeam = false; + } Part userpart = userService.findUserPartByUsername(username); SignInResponseDto responseDto = SignInResponseDto.from(jwtToken, userpart, isVotingLeader, isVotingTeam); return new CommonResponse<>(responseDto, "로그인에 성공헀습니다."); diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java index 6447ea0..67ccb16 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java @@ -1,7 +1,6 @@ package com.ceos.vote.domain.leaderVote.service; import com.ceos.vote.domain.auth.service.UserDetailService; -import com.ceos.vote.domain.auth.service.UserService; import com.ceos.vote.domain.leaderCandidate.entity.LeaderCandidate; import com.ceos.vote.domain.leaderCandidate.repository.LeaderCandidateRepository; import com.ceos.vote.domain.leaderCandidate.service.LeaderCandidateService; @@ -34,6 +33,10 @@ public class LeaderVoteService { private final UserDetailService userDetailService; private final LeaderVoteRepository leaderVoteRepository; + public boolean checkLeaderVoteByUserId(Long id) { + return leaderVoteRepository.findByUserId(id).isPresent(); + } + public LeaderVote findLeaderVoteByUserId(Long id) { return leaderVoteRepository.findByUserId(id) .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_LEADER_VOTE)); diff --git a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java index 1dc74b5..5792e8e 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java @@ -37,6 +37,10 @@ public TeamVote findTeamVoteByUserId(Long id) { .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_TEAM_VOTE)); } + public Boolean checkTeamVoteByUserId(Long id) { + return teamVoteRepository.findByUserId(id).isPresent(); + } + /* 전체 team candidate 조회 */ From fdf6f7e10b33c4c789a0f812e0958a6738964621 Mon Sep 17 00:00:00 2001 From: Seoah Hwang Date: Fri, 3 Jan 2025 03:39:09 -0800 Subject: [PATCH 65/73] =?UTF-8?q?fix:=20=ED=88=AC=ED=91=9C=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=EC=A7=84=EC=A7=9C=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/controller/AuthController.java | 16 ++-------------- .../repository/LeaderVoteRepository.java | 2 ++ .../leaderVote/service/LeaderVoteService.java | 2 +- .../teamVote/repository/TeamVoteRepository.java | 2 ++ .../domain/teamVote/service/TeamVoteService.java | 2 +- 5 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java index f952f5e..eebb83f 100644 --- a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java +++ b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java @@ -47,20 +47,8 @@ public CommonResponse signIn(@RequestBody SignInRequestDto si JwtToken jwtToken = userService.signIn(username, password); Long userId = userService.findUserIdByUsername(username); - Boolean isVotingLeader = false; - try { - leaderVoteService.checkLeaderVoteByUserId(userId); - isVotingLeader = true; - } catch (ApplicationException e) { - isVotingLeader = false; - } - Boolean isVotingTeam = false; - try { - teamVoteService.checkTeamVoteByUserId(userId); - isVotingTeam = true; - } catch (ApplicationException e) { - isVotingTeam = false; - } + Boolean isVotingLeader = leaderVoteService.checkLeaderVoteByUserId(userId); + Boolean isVotingTeam = teamVoteService.checkTeamVoteByUserId(userId); Part userpart = userService.findUserPartByUsername(username); SignInResponseDto responseDto = SignInResponseDto.from(jwtToken, userpart, isVotingLeader, isVotingTeam); return new CommonResponse<>(responseDto, "로그인에 성공헀습니다."); diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java b/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java index 63c9c4c..70beb6a 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/repository/LeaderVoteRepository.java @@ -12,4 +12,6 @@ public interface LeaderVoteRepository extends JpaRepository { Optional findByUserId(Long userId); Long countByLeaderCandidate(LeaderCandidate leaderCandidate); + + boolean existsByUserId(Long userId); } \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java index 67ccb16..f32ab6b 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/service/LeaderVoteService.java @@ -34,7 +34,7 @@ public class LeaderVoteService { private final LeaderVoteRepository leaderVoteRepository; public boolean checkLeaderVoteByUserId(Long id) { - return leaderVoteRepository.findByUserId(id).isPresent(); + return leaderVoteRepository.existsByUserId(id); } public LeaderVote findLeaderVoteByUserId(Long id) { diff --git a/src/main/java/com/ceos/vote/domain/teamVote/repository/TeamVoteRepository.java b/src/main/java/com/ceos/vote/domain/teamVote/repository/TeamVoteRepository.java index c8077d9..7ccd1ce 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/repository/TeamVoteRepository.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/repository/TeamVoteRepository.java @@ -11,4 +11,6 @@ public interface TeamVoteRepository extends JpaRepository { Optional findByUserId(Long userId); Long countByTeamCandidate(TeamCandidate teamCandidate); + + Boolean existsByUserId(Long userId); } \ No newline at end of file diff --git a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java index 5792e8e..be5627f 100644 --- a/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java +++ b/src/main/java/com/ceos/vote/domain/teamVote/service/TeamVoteService.java @@ -38,7 +38,7 @@ public TeamVote findTeamVoteByUserId(Long id) { } public Boolean checkTeamVoteByUserId(Long id) { - return teamVoteRepository.findByUserId(id).isPresent(); + return teamVoteRepository.existsByUserId(id); } /* From b0b36ee0ea5ee838ce539ddff416fbb25127524e Mon Sep 17 00:00:00 2001 From: limgahyun Date: Fri, 3 Jan 2025 21:00:29 +0900 Subject: [PATCH 66/73] fix: enableJpaAuditing --- src/main/java/com/ceos/vote/CeosVoteBeApplication.java | 4 ++++ .../com/ceos/vote/domain/leaderVote/entity/LeaderVote.java | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/main/java/com/ceos/vote/CeosVoteBeApplication.java b/src/main/java/com/ceos/vote/CeosVoteBeApplication.java index b910fac..37428c6 100644 --- a/src/main/java/com/ceos/vote/CeosVoteBeApplication.java +++ b/src/main/java/com/ceos/vote/CeosVoteBeApplication.java @@ -2,8 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableJpaAuditing +@EnableScheduling public class CeosVoteBeApplication { public static void main(String[] args) { diff --git a/src/main/java/com/ceos/vote/domain/leaderVote/entity/LeaderVote.java b/src/main/java/com/ceos/vote/domain/leaderVote/entity/LeaderVote.java index 995ef0f..fee5ff8 100644 --- a/src/main/java/com/ceos/vote/domain/leaderVote/entity/LeaderVote.java +++ b/src/main/java/com/ceos/vote/domain/leaderVote/entity/LeaderVote.java @@ -8,11 +8,13 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter +@EntityListeners(AuditingEntityListener.class) public class LeaderVote extends BaseEntity { @Id From 2a1ba71c4eb9ef72b0913288977dd222202d1161 Mon Sep 17 00:00:00 2001 From: Seoah Hwang Date: Fri, 3 Jan 2025 04:30:12 -0800 Subject: [PATCH 67/73] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=20=EC=95=84=EC=9D=B4=EB=94=94/=EB=B9=84=EB=B0=80?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=9E=98=EB=AA=BB=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EC=8B=9C=20=EC=97=90=EB=9F=AC=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vote/domain/auth/service/UserService.java | 18 +++++++++++++++--- .../vote/global/exception/ExceptionCode.java | 3 ++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java index 383c889..2bd4ad5 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserService.java @@ -78,13 +78,25 @@ public UserInfoDto signUp(SignUpRequestDto signUpRequestDto){ @Transactional public JwtToken signIn(String username, String password){ - // 1. username + password 를 기반으로 Authentication 객체 생성 + + // username 존재여부 확인 + Users users = userRepository.findByUsername(username) + .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_USER)); + + // password 일치 여부 확인 + if (!passwordEncoder.matches(password, users.getPassword())) { + throw new ApplicationException(ExceptionCode.INVALID_PASSWORD); + } + + // username + password 를 기반으로 Authentication 객체 생성 // 이때 authentication 은 인증 여부를 확인하는 authenticated 값이 false UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); - // 2. 실제 검증. authenticate() 메서드를 통해 요청된 Member 에 대한 검증 진행 + + // 실제 검증. authenticate() 메서드를 통해 요청된 Member 에 대한 검증 진행 // authenticate 메서드가 실행될 때 CustomUserDetailsService 에서 만든 loadUserByUsername 메서드 실행 Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); - // 3. 인증 정보를 기반으로 JWT 토큰 생성 + + // 인증 정보를 기반으로 JWT 토큰 생성 JwtToken jwtToken = jwtTokenProvider.generateToken(authentication); return jwtToken; diff --git a/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java b/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java index 1a1c0dc..202b234 100644 --- a/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java +++ b/src/main/java/com/ceos/vote/global/exception/ExceptionCode.java @@ -21,10 +21,11 @@ public enum ExceptionCode { INVALID_SORT_EXCEPTION(HttpStatus.BAD_REQUEST, 2007, "올바르지 않은 정렬 값입니다."), BAD_REQUEST_ERROR(HttpStatus.BAD_REQUEST, 2008, "잘못된 요청입니다."), - // 3000: USER + // 3000: USER/Auth NOT_FOUND_USER(HttpStatus.NOT_FOUND, 3001, "해당 회원을 찾을 수 없습니다."), INVALID_USER_CREDENTIALS(HttpStatus.UNAUTHORIZED, 3002, "잘못된 사용자 인증 정보입니다."), INVALID_PASSWORD(HttpStatus.BAD_REQUEST, 3003, "비밀번호를 찾을 수 없습니다."), + NOT_FOUND_USERNAME(HttpStatus.NOT_FOUND, 3004, "해당 아이디를 찾을 수 없습니다."), // 4000: Leader Vote Error NOT_FOUND_LEADER_CANDIDATE(HttpStatus.NOT_FOUND, 4001, "해당 leader candidate가 존재하지 않습니다."), From b7bbc2cc9f2e8901606af2bc618d9e57ed390487 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Sat, 4 Jan 2025 00:34:55 +0900 Subject: [PATCH 68/73] =?UTF-8?q?feat:=20signInResponseDto=20userId=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ceos/vote/domain/auth/controller/AuthController.java | 9 +++++++-- .../vote/domain/auth/dto/response/SignInResponseDto.java | 6 ++++-- .../ceos/vote/domain/auth/service/UserDetailService.java | 7 ++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java index eebb83f..203a341 100644 --- a/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java +++ b/src/main/java/com/ceos/vote/domain/auth/controller/AuthController.java @@ -5,9 +5,11 @@ import com.ceos.vote.domain.auth.dto.request.SignUpRequestDto; import com.ceos.vote.domain.auth.dto.response.SignInResponseDto; import com.ceos.vote.domain.auth.dto.response.UserInfoDto; +import com.ceos.vote.domain.auth.service.UserDetailService; import com.ceos.vote.domain.auth.service.UserService; import com.ceos.vote.domain.leaderVote.service.LeaderVoteService; import com.ceos.vote.domain.teamVote.service.TeamVoteService; +import com.ceos.vote.domain.users.entity.Users; import com.ceos.vote.domain.users.enumerate.Part; import com.ceos.vote.domain.utils.SecurityUtil; import com.ceos.vote.global.common.response.CommonResponse; @@ -30,6 +32,7 @@ public class AuthController { private final UserService userService; private final LeaderVoteService leaderVoteService; private final TeamVoteService teamVoteService; + private final UserDetailService userDetailService; // 회원가입 @PostMapping("/sign-up") @@ -49,8 +52,10 @@ public CommonResponse signIn(@RequestBody SignInRequestDto si Long userId = userService.findUserIdByUsername(username); Boolean isVotingLeader = leaderVoteService.checkLeaderVoteByUserId(userId); Boolean isVotingTeam = teamVoteService.checkTeamVoteByUserId(userId); - Part userpart = userService.findUserPartByUsername(username); - SignInResponseDto responseDto = SignInResponseDto.from(jwtToken, userpart, isVotingLeader, isVotingTeam); + + Users user = userDetailService.findUserByUsername(username); + + SignInResponseDto responseDto = SignInResponseDto.from(jwtToken, user, isVotingLeader, isVotingTeam); return new CommonResponse<>(responseDto, "로그인에 성공헀습니다."); } diff --git a/src/main/java/com/ceos/vote/domain/auth/dto/response/SignInResponseDto.java b/src/main/java/com/ceos/vote/domain/auth/dto/response/SignInResponseDto.java index 24e67a0..8b2153a 100644 --- a/src/main/java/com/ceos/vote/domain/auth/dto/response/SignInResponseDto.java +++ b/src/main/java/com/ceos/vote/domain/auth/dto/response/SignInResponseDto.java @@ -16,16 +16,18 @@ public class SignInResponseDto { private String grantType; private String accessToken; private String refreshToken; + private Long userId; private Part userPart; private Boolean isVotingLeader; private Boolean isVotingTeam; - public static SignInResponseDto from(JwtToken jwtToken, Part userPart, Boolean isVotingLeader, Boolean isVotingTeam) { + public static SignInResponseDto from(JwtToken jwtToken, Users user, Boolean isVotingLeader, Boolean isVotingTeam) { return SignInResponseDto.builder() .grantType(jwtToken.getGrantType()) .accessToken(jwtToken.getAccessToken()) .refreshToken(jwtToken.getRefreshToken()) - .userPart(userPart) + .userId(user.getId()) + .userPart(user.getUserPart()) .isVotingLeader(isVotingLeader) .isVotingTeam(isVotingTeam) .build(); diff --git a/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java index f9a06dc..1beda79 100644 --- a/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java +++ b/src/main/java/com/ceos/vote/domain/auth/service/UserDetailService.java @@ -31,7 +31,12 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx public Users findUserById(Long id) { return userRepository.findById(id) - .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_EXCEPTION)); + .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_USER)); + } + + public Users findUserByUsername(String username) { + return userRepository.findByUsername(username) + .orElseThrow(() -> new ApplicationException(ExceptionCode.NOT_FOUND_USER)); } private UserDetails createUserDetails(@NotNull Users users) { From e43e6e955e13b9ecf72600de92a08a3373d4811e Mon Sep 17 00:00:00 2001 From: Seoah Hwang Date: Sat, 4 Jan 2025 02:10:36 +0900 Subject: [PATCH 69/73] =?UTF-8?q?fix:=20=EC=9D=B8=EA=B0=80=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/JwtAuthenticationFilter.java | 21 +++++++++++++---- .../vote/domain/auth/JwtTokenProvider.java | 23 ++++++++++++------- .../vote/global/config/SecurityConfig.java | 5 ++-- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/ceos/vote/domain/auth/JwtAuthenticationFilter.java b/src/main/java/com/ceos/vote/domain/auth/JwtAuthenticationFilter.java index 93c9621..c9d3321 100644 --- a/src/main/java/com/ceos/vote/domain/auth/JwtAuthenticationFilter.java +++ b/src/main/java/com/ceos/vote/domain/auth/JwtAuthenticationFilter.java @@ -6,6 +6,7 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.StringUtils; @@ -13,6 +14,7 @@ import java.io.IOException; +@Slf4j @RequiredArgsConstructor public class JwtAuthenticationFilter extends GenericFilterBean { private final JwtTokenProvider jwtTokenProvider; @@ -23,22 +25,31 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo // 1. Request Header에서 JWT 토큰 추출 String token = resolveToken((HttpServletRequest) servletRequest); - // 2. validateToken으로 토큰 유효성 검사 if (token != null && jwtTokenProvider.validateToken(token)) { - // 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext에 저장 Authentication authentication = jwtTokenProvider.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); + log.info("User authenticated: {}", authentication.getName()); + log.info("Authorities: {}", authentication.getAuthorities()); + } else { + log.warn("Invalid or missing JWT token."); } + filterChain.doFilter(servletRequest, servletResponse); } // Request Header에서 토큰 정보 추출 private String resolveToken(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer")) { - return bearerToken.substring(7); + if (bearerToken == null) { + log.warn("Authorization header is completely missing in the request."); + } else if (!bearerToken.startsWith("Bearer ")) { + log.warn("Authorization header is malformed. Value: {}", bearerToken); + } else { + log.info("Authorization header found and valid."); } - return null; + return StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ") + ? bearerToken.substring(7) + : null; } } diff --git a/src/main/java/com/ceos/vote/domain/auth/JwtTokenProvider.java b/src/main/java/com/ceos/vote/domain/auth/JwtTokenProvider.java index c6c4286..4339994 100644 --- a/src/main/java/com/ceos/vote/domain/auth/JwtTokenProvider.java +++ b/src/main/java/com/ceos/vote/domain/auth/JwtTokenProvider.java @@ -1,5 +1,6 @@ package com.ceos.vote.domain.auth; +import com.ceos.vote.domain.users.entity.Users; import io.jsonwebtoken.*; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; @@ -27,13 +28,14 @@ public class JwtTokenProvider { private final Key key; - //application.yml에서 secret 값 가져와서 key에 저장 + //application-secret.yml에서 secret 값 가져와서 key에 저장 public JwtTokenProvider(@Value("${JWT_SECRET_KEY}") String secretKey){ + log.info("Loaded secret key: {}", secretKey); byte[] keyBytes = Decoders.BASE64.decode(secretKey); this.key = Keys.hmacShaKeyFor(keyBytes); } - //AccessToken, RefreshToken을 생성하는 메서드 + // 인증(authentication) 객체를 기반으로 AccessToken, RefreshToken을 생성하는 메서드 public JwtToken generateToken(Authentication authentication){ //권한 가져오기 @@ -43,7 +45,7 @@ public JwtToken generateToken(Authentication authentication){ long now = (new Date()).getTime(); - Date accessTokenExpiresIn = new Date(now + 1000L * 60 * 60 * 24); + Date accessTokenExpiresIn = new Date(now + 1000L * 60 * 60); String accessToken = Jwts.builder() .setSubject(authentication.getName()) .claim("auth", authorities) @@ -71,6 +73,7 @@ public Authentication getAuthentication(String accessToken) { Claims claims = parseClaims(accessToken); if (claims.get("auth") == null) { + log.error("No authority information found in the token."); throw new RuntimeException("권한 정보가 없는 토큰입니다."); } @@ -94,13 +97,13 @@ public boolean validateToken(String token) { .parseClaimsJws(token); return true; } catch (SecurityException | MalformedJwtException e) { - log.info("Invalid JWT Token", e); + log.error("Invalid JWT signature or malformed token: {}", e.getMessage()); } catch (ExpiredJwtException e) { - log.info("Expired JWT Token", e); + log.error("JWT token expired: {}", e.getMessage()); } catch (UnsupportedJwtException e) { - log.info("Unsupported JWT Token", e); + log.error("Unsupported JWT token: {}", e.getMessage()); } catch (IllegalArgumentException e) { - log.info("JWT claims string is empty.", e); + log.error("JWT claims string is empty or invalid: {}", e.getMessage()); } return false; } @@ -114,7 +117,11 @@ private Claims parseClaims(String accessToken) { .parseClaimsJws(accessToken) .getBody(); } catch (ExpiredJwtException e) { - return e.getClaims(); + log.error("Token expired: {}", e.getMessage()); + throw new RuntimeException("Token expired"); + } catch (Exception e) { + log.error("Failed to parse token: {}", e.getMessage()); + throw new RuntimeException("Failed to parse token"); } } diff --git a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java index 702cbe0..c378c1c 100644 --- a/src/main/java/com/ceos/vote/global/config/SecurityConfig.java +++ b/src/main/java/com/ceos/vote/global/config/SecurityConfig.java @@ -6,6 +6,7 @@ import jakarta.servlet.http.HttpServletRequest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -71,8 +72,8 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { .requestMatchers("/hc", "/env", "/").permitAll() .requestMatchers("/swagger/**", "/swagger-ui.html/**", "/swagger-ui/**", "/v3/api-docs/**").permitAll() .requestMatchers("/static/**", "/css/**", "/js/**", "/images/**").permitAll() - .requestMatchers("api/v1/auth/**", "/api/v1/**", "/reissue").permitAll() - .requestMatchers("/api/v1/**").hasRole("user") + .requestMatchers("/api/v1/auth/**", "/reissue").permitAll() + .requestMatchers("/api/v1/**").hasRole("USER") .anyRequest().authenticated()) .logout((logout) -> logout .logoutSuccessUrl("/login") From e49eda42344a9b6332c84b143e1f40440e8e2137 Mon Sep 17 00:00:00 2001 From: limgahyun Date: Sat, 4 Jan 2025 05:24:27 +0900 Subject: [PATCH 70/73] docs: deploy readme --- README.md | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 76efd88..cbb23ad 100644 --- a/README.md +++ b/README.md @@ -1 +1,142 @@ -# spring_vote_20th \ No newline at end of file +# spring_vote_20th + +## 배포 - blue green 무중단 배포 +### blue green 배포란? +https://github.com/user-attachments/assets/d603aaa1-c8fc-4ef2-829a-629b2a35b09e + +```yaml +spring: + profiles: + active: local + group: + local: local, common, secret + blue: blue, common, secret + green: green, common, secret + +--- + +spring: + config: + activate: + on-profile: blue + +server: + port: 8080 + serverAddress: 13.124.171.94 + +serverName: blue_server + +--- + +spring: + config: + activate: + on-profile: common + import: optional:file:.env[.properties] +``` +application.yml + + +### nginx load balancing +- blue server - 8080 port +- green server - 8081 port + +http://52.79.122.106 → nginx로 8080 or 8081 port 중 실행 중인 port로 연결 + + +### github action +```yaml + + +# event trigger +# main브랜치에 pull request가 발생 되었을 때 실행 +on: + pull_request: + branches: [ "master" ] + push: + branches: [ "master" ] + +jobs: + build: + steps: + - name: Checkout source + + - name: Setup Java + + - name: Gradle Caching + + - name: Grant execute permission for gradlew + + - name: Build Project with Gradle + + - name: Login to DockerHub + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Build docker image + run: docker build --platform linux/amd64 -t ${{ secrets.DOCKER_USERNAME }}/vote . + + - name: Publish image to docker hub + run: docker push ${{ secrets.DOCKER_USERNAME }}/vote:latest + + deploy: + steps: + - name: Set Target IP + run: | + STATUS=$(curl -o /dev/null -w "%{http_code}" "${{ secrets.SERVER_IP }}/env") + echo $STATUS + if [ $STATUS = 200 ]; then + CURRENT_UPSTREAM=$(curl -s "${{ secrets.SERVER_IP }}/env") + else + CURRENT_UPSTREAM=green + fi + echo CURRENT_UPSTREAM=$CURRENT_UPSTREAM >> $GITHUB_ENV + if [ $CURRENT_UPSTREAM = blue ]; then + echo "CURRENT_PORT=8080" >> $GITHUB_ENV + echo "STOPPED_PORT=8081" >> $GITHUB_ENV + echo "TARGET_UPSTREAM=green" >> $GITHUB_ENV + else + echo "CURRENT_PORT=8081" >> $GITHUB_ENV + echo "STOPPED_PORT=8080" >> $GITHUB_ENV + echo "TARGET_UPSTREAM=blue" >> $GITHUB_ENV + fi + + - name: Docker Compose + with: + script: | + export JWT_SECRET_KEY=${{ secrets.JWT_SECRET_KEY }} + sudo docker pull ${{ secrets.DOCKER_USERNAME }}/vote:latest + sudo docker-compose -f docker-compose-${{env.TARGET_UPSTREAM}}.yml up -d + + - name: Check deploy server URL + with: + url: http://${{ secrets.SERVER_IP }}:${{ env.STOPPED_PORT }}/env + + - name: Change nginx upstream + with: + script: | + sudo docker exec -i nginxserver bash -c 'echo "set \$service_url ${{env.TARGET_UPSTREAM}};" > etc/nginx/conf.d/service-env.inc && nginx -s reload' + + - name: Stop current server + with: + script: | + sudo docker stop ${{ env.CURRENT_UPSTREAM }} + sudo docker rm ${{ env.CURRENT_UPSTREAM }} + +``` + +### trouble shooting +**JWT_SECRET_KEY를 읽지 못해서 발생하는 빌드 에러** + +1. git secrets에 JWT_SECRET_KEY 변수를 추가 → docker-compose 파일을 실행할 때 넣어주려고 했으나 인식하지 못하는 문제가 발생함. +2. CICD.yml 파일 내에서 $GITHUB_ENV에 JWT_SECRET_KEY값을 저장 → 키가 외부에 노출되면 안되기 때문에 보안 문제 발생 +3. docker-compse.yml 파일에서 직접 추가 → 성공 + ```yaml + environment: + - JWT_SECRET_KEY= + ``` + + +**보안 규칙** +1. 8080 port, 8081 port를 번갈아가며 사용하기 때문에 두 port에 대한 inbound 규칙 추가 \ No newline at end of file From ada2c7b69b10b4089c27d61547ceeaae80b0a3a9 Mon Sep 17 00:00:00 2001 From: limgahyun <114561842+limgahyun@users.noreply.github.com> Date: Sun, 5 Jan 2025 04:44:56 +0900 Subject: [PATCH 71/73] Update CICD.yml --- .github/workflows/CICD.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 2f86c5b..67688c6 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -61,10 +61,10 @@ jobs: steps: - name: Set Target IP run: | - STATUS=$(curl -o /dev/null -w "%{http_code}" "${{ secrets.SERVER_IP }}/env") + STATUS=$(curl -o /dev/null -w "%{http_code}" "${{ secrets.SERVER_ADDRESS }}/env") echo $STATUS if [ $STATUS = 200 ]; then - CURRENT_UPSTREAM=$(curl -s "${{ secrets.SERVER_IP }}/env") + CURRENT_UPSTREAM=$(curl -s "${{ secrets.SERVER_ADDRESS }}/env") else CURRENT_UPSTREAM=green fi From be01f7ad1c079c1a2ef2234d9a72c4c7a85354d8 Mon Sep 17 00:00:00 2001 From: limgahyun <114561842+limgahyun@users.noreply.github.com> Date: Sun, 5 Jan 2025 13:12:05 +0900 Subject: [PATCH 72/73] Update CICD.yml --- .github/workflows/CICD.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 67688c6..2f86c5b 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -61,10 +61,10 @@ jobs: steps: - name: Set Target IP run: | - STATUS=$(curl -o /dev/null -w "%{http_code}" "${{ secrets.SERVER_ADDRESS }}/env") + STATUS=$(curl -o /dev/null -w "%{http_code}" "${{ secrets.SERVER_IP }}/env") echo $STATUS if [ $STATUS = 200 ]; then - CURRENT_UPSTREAM=$(curl -s "${{ secrets.SERVER_ADDRESS }}/env") + CURRENT_UPSTREAM=$(curl -s "${{ secrets.SERVER_IP }}/env") else CURRENT_UPSTREAM=green fi From 1165a941cd75be08a267acf3e3a527a3f5ed1131 Mon Sep 17 00:00:00 2001 From: seoah_s01 <121560146+seoahS01@users.noreply.github.com> Date: Sat, 4 Jan 2025 23:23:01 -0800 Subject: [PATCH 73/73] Update README.md --- README.md | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cbb23ad..38a2d6e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,37 @@ # spring_vote_20th +## ERD +전체 ERD + +스크린샷 2025-01-05 오후 2 29 35 + +- 유저, 파트장 투표, 팀 투표를 분리시켜 구성하였다. + +### User +사용자에 관한 테이블 + +스크린샷 2025-01-05 오후 2 57 58 + +- 사용자는 name과 password를 통해 로그인을 할 수 있다. + +### Leader Vote +파트장 투표에 관한 테이블 + +스크린샷 2025-01-05 오후 4 07 38 + +- Leader Candidate에는 후보자의 이름과 파트 그리고 후보자 id가 있다. +- Leader Vote는 투표한 사용자의 id와 사용자가 투표한 후보자 id 그리고 투표 고유 id가 있다. +- 사용자 id와 후보자 id는 외래키로 받아왔다. + +### Team Vote +팀 투표에 관한 테이블 + +스크린샷 2025-01-05 오후 4 12 14 + +- Team Candidate는 팀명과 팀 고유 id로 구성되어 있다. +- Team Vote는 투표한 사용자 id, 사용자가 투표한 팀 id 그리고 투표 고유 id가 있다. +- 사용자 id와 사용자 id는 외래키로 받아왔다. + ## 배포 - blue green 무중단 배포 ### blue green 배포란? https://github.com/user-attachments/assets/d603aaa1-c8fc-4ef2-829a-629b2a35b09e @@ -139,4 +171,10 @@ jobs: **보안 규칙** -1. 8080 port, 8081 port를 번갈아가며 사용하기 때문에 두 port에 대한 inbound 규칙 추가 \ No newline at end of file +1. 8080 port, 8081 port를 번갈아가며 사용하기 때문에 두 port에 대한 inbound 규칙 추가 + +**비밀번호를 인식하지 못하는 문제** +1. 로그인 기능을 먼저 구현한 뒤, 테이블에 직접 데이터를 넣고 로그인 테스트를 해보았으나 비밀번호를 인식하지 못하여 실패 +2. 비밀번호가 암호화되어 입력되어야 하는데 직접 데이터를 넣었기 때문에 암호화되지 않은 채로 들어가 문제가 생겼음을 인식 +3. 회원가입 구현 후에 다시 로그인을 시도했으나 여전히 비밀번호를 인식하지 못함 +4. import문을 잘못 가지고 왔다는 것을 깨달음... → 해결..^_^