diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..b4a0214 Binary files /dev/null and b/.DS_Store differ diff --git a/.ebextensions-dev/00-makefiles.config b/.ebextensions-dev/00-makefiles.config new file mode 100644 index 0000000..8030404 --- /dev/null +++ b/.ebextensions-dev/00-makefiles.config @@ -0,0 +1,12 @@ +files: + "/sbin/appstart": + mode: "000755" + owner: webapp + group: webapp + content: | + #!/usr/bin/env bash + JAR_PATH=/var/app/current/application.jar + + # run app + killalljava + java -Dfile.encoding=UTF-8 -jar $JAR_PATH \ No newline at end of file diff --git a/.ebextensions-dev/01-set-timezone.config b/.ebextensions-dev/01-set-timezone.config new file mode 100644 index 0000000..869275c --- /dev/null +++ b/.ebextensions-dev/01-set-timezone.config @@ -0,0 +1,3 @@ +commands: + set_time_zone: + command: ln -f -s /usr/share/zoneinfo/Asia/Seoul /etc/localtime \ No newline at end of file diff --git "a/.github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\355\205\234\355\224\214\353\246\277.md" "b/.github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\355\205\234\355\224\214\353\246\277.md" new file mode 100644 index 0000000..45cf640 --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\355\205\234\355\224\214\353\246\277.md" @@ -0,0 +1,16 @@ +--- +name: 이슈 템플릿 +about: 이슈 생성시 해당 템플릿을 사용해주세요 +title: "[Feat/Refactor/Fix/Ci] + 제목" +labels: '' +assignees: '' + +--- + +*** +### 세부내용 + +*** +### 체크 리스트 +- [ ] 구현 +- [ ] 기능 테스트 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..6364938 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +## 개요 + +## 작업사항 + +## 변경로직 + +### 변경 전 + +### 변경 후 + +## 사용방법 + +## 기타 \ No newline at end of file diff --git a/.github/workflows/dev_deploy.yml b/.github/workflows/dev_deploy.yml new file mode 100644 index 0000000..94f1e4a --- /dev/null +++ b/.github/workflows/dev_deploy.yml @@ -0,0 +1,61 @@ +name: Solution-friend Dev CI/CD + +on: + pull_request: + types: [ closed ] + workflow_dispatch: # (2).수동 실행도 가능하도록 + +jobs: + build: + runs-on: ubuntu-latest # (3).OS환경 + if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'develop' + + steps: + - name: Checkout + uses: actions/checkout@v2 # (4).코드 check out + + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: 11 # (5).자바 설치 + distribution: 'adopt' + + - name: Grant execute permission for gradlew + run: chmod +x ./gradlew + shell: bash # (6).권한 부여 + + - name: Build with Gradle + run: ./gradlew clean build -x test + shell: bash # (7).build시작 + + - name: Get current time + uses: 1466587594/get-current-time@v2 + id: current-time + with: + format: YYYY-MM-DDTHH-mm-ss + utcOffset: "+09:00" # (8).build시점의 시간확보 + + - name: Show Current Time + run: echo "CurrentTime=$" + shell: bash # (9).확보한 시간 보여주기 + + - name: Generate deployment package + run: | + mkdir -p deploy + cp build/libs/*.jar deploy/application.jar + cp Procfile deploy/Procfile + cp -r .ebextensions-dev deploy/.ebextensions + cp -r .platform deploy/.platform + cd deploy && zip -r deploy.zip . + + - name: Beanstalk Deploy + uses: einaregilsson/beanstalk-deploy@v20 + with: + aws_access_key: ${{ secrets.AWS_ACTION_ACCESS_KEY_ID }} + aws_secret_key: ${{ secrets.AWS_ACTION_SECRET_ACCESS_KEY }} + application_name: solution-friend-dev + environment_name: Solution-friend-dev-env-1 + version_label: github-action-${{ steps.current-time.outputs.formattedTime }} + region: ap-northeast-2 + deployment_package: deploy/deploy.zip + wait_for_deployment: false 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/.platform/nginx/conf.d/client_max_body_size.conf b/.platform/nginx/conf.d/client_max_body_size.conf new file mode 100644 index 0000000..8e8277e --- /dev/null +++ b/.platform/nginx/conf.d/client_max_body_size.conf @@ -0,0 +1 @@ +client_max_body_size 200M; \ No newline at end of file diff --git a/.platform/nginx/nginx.conf b/.platform/nginx/nginx.conf new file mode 100644 index 0000000..0316306 --- /dev/null +++ b/.platform/nginx/nginx.conf @@ -0,0 +1,68 @@ +user nginx; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; +worker_processes auto; +worker_rlimit_nofile 33282; + +events { + use epoll; + worker_connections 1024; + multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + include conf.d/*.conf; + + map $http_upgrade $connection_upgrade { + default "upgrade"; + } + + upstream springboot { + server 127.0.0.1:8080; + keepalive 1024; + } + + server { + listen 80 default_server; + listen [::]:80 default_server; + + location / { + proxy_pass http://springboot; + proxy_connect_timeout 3600; + proxy_send_timeout 3600; + proxy_read_timeout 3600; + # CORS 관련 헤더 추가 +# add_header 'Access-Control-Allow-Origin' '*'; +# add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS'; +# add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type'; +# add_header 'Access-Control-Allow-Credential' 'true'; + + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + access_log /var/log/nginx/access.log main; + + client_header_timeout 60; + client_body_timeout 60; + keepalive_timeout 60; + gzip off; + gzip_comp_level 4; + + # Include the Elastic Beanstalk generated locations + include conf.d/elasticbeanstalk/healthd.conf; + } +} \ No newline at end of file diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..58dab8d --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: appstart \ No newline at end of file diff --git a/README.md b/README.md index caa8310..db72cef 100644 --- a/README.md +++ b/README.md @@ -1 +1,61 @@ -# BE \ No newline at end of file +# 🗳️ 고민친구 +
+ +고민친구 백엔드 리포지토리 + +
+ +## 🧑‍🤝‍🧑 프로젝트 개요 +투표 기반 의사결정 지원 웹 서비스 + +
+
+ + + +## 🔎 주요 기능 +- 로그인 / 회원가입 +- 고민 투표 & 고민 후기 글/댓글 관리 +- 마이페이지 내 정보 관리 +- 댓글 작성 및 투표 마감 실시간 알림 + +
+ +## 🔭 기술 스택 +- Java, Springboot +- AWS EB, ElastiCache(Redis), RDS, VPC +- Spring Security +- SSE 실시간 알림 + +
+ +## 👨‍👩‍👧‍👦 팀원 소개 +- PM 1명 +- Design 1명 +- Front-End(Web) 4명 +- Back-End 4명 + +
+ +## 📖 커밋 규칙 + +#{이슈번호} {type}: [작업한 내용] + +ex) #200 Feat: 사용자 뮤트 기능 추가 + + +| 커밋 유형 | 의미 | +|-------|-----------| +| Feat | 새로운 기능 추가 | +| Fix | 버그 수정 | +| Docs | 문서 수정 | +| Style | 코드 formatting, 세미콜론 누락, 코드 자체의 변경이 없는 경우 | +| Refactor | 코드 리팩토링 | +| Test | 테스트 코드, 리팩토링 테스트 코드 추가 | +| Chore | 패키지 매니저 수정, 그 외 기타 수정 ex) .gitignore | +| Design | CSS 등 사용자 UI 디자인 변경 | +| Comment | 필요한 주석 추가 및 변경 | +| Rename | 파일 또는 폴더 명을 수정하거나 옮기는 작업만인 경우 | +| Remove | 파일을 삭제하는 작업만 수행한 경우 | +| !BREAKING CHANGE | 커다란 API 변경의 경우 | +| !HOTFIX | 급하게 치명적인 버그를 고쳐야 하는 경우 | diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..a172b29 --- /dev/null +++ b/build.gradle @@ -0,0 +1,62 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '2.7.17' + id 'io.spring.dependency-management' version '1.0.15.RELEASE' +} + +group = 'friend' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '11' +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-web' + compileOnly 'org.projectlombok:lombok' + runtimeOnly 'com.mysql:mysql-connector-j' + annotationProcessor 'org.projectlombok:lombok' + implementation 'org.springframework.boot:spring-boot-starter-validation' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'org.springdoc:springdoc-openapi-ui:1.6.15' + implementation 'io.springfox:springfox-swagger2:2.9.2' + implementation 'io.springfox:springfox-swagger-ui:2.9.2' + + //user + implementation 'com.google.code.findbugs:jsr305:3.0.2' + //mail + implementation group: 'com.sun.mail', name: 'javax.mail', version: '1.6.2' + //jwt + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.4' + runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.4' + runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.4' +// redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + //kakao +// implementation 'org.springframework.boot:spring-boot-starter-webflux' + // S3 + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + implementation platform('software.amazon.awssdk:bom:2.20.56') + implementation 'software.amazon.awssdk:s3' + +} + +tasks.named('test') { + useJUnitPlatform() +} + +jar { + enabled = false +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..d64cd49 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1af9e09 --- /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.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1aa94a4 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/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. +# + +############################################################################## +# +# 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/subprojects/plugins/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 "${APP_HOME:-./}" > /dev/null && pwd -P ) || 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..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@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 + +@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. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +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..ebf1ef8 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'spring' diff --git a/src/main/java/friend/spring/Application.java b/src/main/java/friend/spring/Application.java new file mode 100644 index 0000000..2c83866 --- /dev/null +++ b/src/main/java/friend/spring/Application.java @@ -0,0 +1,27 @@ +package friend.spring; + +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; + +import javax.annotation.PostConstruct; +import java.time.LocalDateTime; +import java.util.TimeZone; + +@SpringBootApplication +@EnableJpaAuditing +@EnableScheduling +public class Application { + + @PostConstruct + public void started() { + TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); + } + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + System.out.println("현재시간 " + LocalDateTime.now()); + } + +} diff --git a/src/main/java/friend/spring/OAuth/KakaoProfile.java b/src/main/java/friend/spring/OAuth/KakaoProfile.java new file mode 100644 index 0000000..1ea73f6 --- /dev/null +++ b/src/main/java/friend/spring/OAuth/KakaoProfile.java @@ -0,0 +1,24 @@ +package friend.spring.OAuth; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Getter; + +@Getter +@JsonIgnoreProperties(ignoreUnknown = true) +public class KakaoProfile { + + private Properties properties; + private KakaoAccount kakao_account; + + @Getter + @JsonIgnoreProperties(ignoreUnknown = true) + public class Properties { + private String nickname; + } + + @Getter + @JsonIgnoreProperties(ignoreUnknown = true) + public class KakaoAccount { + private String email; + } +} diff --git a/src/main/java/friend/spring/OAuth/OAuthToken.java b/src/main/java/friend/spring/OAuth/OAuthToken.java new file mode 100644 index 0000000..1bfd95c --- /dev/null +++ b/src/main/java/friend/spring/OAuth/OAuthToken.java @@ -0,0 +1,15 @@ +package friend.spring.OAuth; + +import lombok.Data; + +@Data +public class OAuthToken { + + private String access_token; + private String token_type; + private String refresh_token; + private String id_token; + private int expires_in; + private String scope; + private int refresh_token_expires_in; +} diff --git a/src/main/java/friend/spring/OAuth/provider/KakaoAuthProvider.java b/src/main/java/friend/spring/OAuth/provider/KakaoAuthProvider.java new file mode 100644 index 0000000..1c6ee33 --- /dev/null +++ b/src/main/java/friend/spring/OAuth/provider/KakaoAuthProvider.java @@ -0,0 +1,104 @@ +package friend.spring.OAuth.provider; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import friend.spring.apiPayload.GeneralException; +import friend.spring.apiPayload.code.status.ErrorStatus; +import friend.spring.OAuth.KakaoProfile; +import friend.spring.OAuth.OAuthToken; +import friend.spring.security.PrincipalDetailService; +import org.springframework.beans.factory.annotation.Value; +import friend.spring.repository.UserRepository; +import friend.spring.security.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +@Component +@RequiredArgsConstructor +public class KakaoAuthProvider { + + private final PrincipalDetailService principalDetailService; + private final UserRepository userRepository; + private final JwtTokenProvider jwtTokenProvider; + + @Value("${kakao.auth.client}") + private String client; + + @Value("${kakao.auth.redirect_uri}") + private String redirect; + + @Value("${kakao.auth.secret_key}") + private String secretKey; + + // code로 access 토큰 요청하기 + public OAuthToken requestToken(String code) { + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + + headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8"); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type", "authorization_code"); + params.add("client_id", client); + params.add("redirect_uri", redirect); + params.add("secret_key", secretKey); + params.add("code", code); + + HttpEntity> kakaoTokenRequest = + new HttpEntity<>(params, headers); + + ResponseEntity response = + restTemplate.exchange( + "https://kauth.kakao.com/oauth/token", + HttpMethod.POST, + kakaoTokenRequest, + String.class); + + ObjectMapper objectMapper = new ObjectMapper(); + + OAuthToken oAuthToken = null; + + try { + oAuthToken = objectMapper.readValue(response.getBody(), OAuthToken.class); + } catch (JsonProcessingException e) { + throw new GeneralException(ErrorStatus.INVALID_REQUEST_INFO); + } + + return oAuthToken; + } + + // Token으로 정보 요청하기 + public KakaoProfile requestKakaoProfile(String token) { + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8"); + headers.add("Authorization", "Bearer " + token); + + HttpEntity> kakaoProfileRequest = new HttpEntity<>(headers); + + ResponseEntity response = + restTemplate.exchange( + "https://kapi.kakao.com/v2/user/me", + HttpMethod.POST, + kakaoProfileRequest, + String.class); + + ObjectMapper objectMapper = new ObjectMapper(); + KakaoProfile kakaoProfile = null; + + try { + kakaoProfile = objectMapper.readValue(response.getBody(), KakaoProfile.class); + } catch (JsonProcessingException e) { + throw new GeneralException(ErrorStatus.INVALID_REQUEST_INFO); + } + + return kakaoProfile; + } +} diff --git a/src/main/java/friend/spring/apiPayload/ApiResponse.java b/src/main/java/friend/spring/apiPayload/ApiResponse.java new file mode 100644 index 0000000..edea1b1 --- /dev/null +++ b/src/main/java/friend/spring/apiPayload/ApiResponse.java @@ -0,0 +1,39 @@ +package friend.spring.apiPayload; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import friend.spring.apiPayload.code.BaseCode; +import friend.spring.apiPayload.code.status.SuccessStatus; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@JsonPropertyOrder({"isSuccess", "code", "message", "result"}) +public class ApiResponse { + + @JsonProperty("isSuccess") + private final Boolean isSuccess; + private final String code; + private final String message; + @JsonInclude(JsonInclude.Include.NON_NULL) + private T result; + + + // 성공한 경우 응답 생성 + + public static ApiResponse onSuccess(T result) { + return new ApiResponse<>(true, SuccessStatus._OK.getCode(), SuccessStatus._OK.getMessage(), result); + } + + public static ApiResponse of(BaseCode code, T result) { + return new ApiResponse<>(true, code.getReasonHttpStatus().getCode(), code.getReasonHttpStatus().getMessage(), result); + } + + + // 실패한 경우 응답 생성 + public static ApiResponse onFailure(String code, String message, T data) { + return new ApiResponse<>(false, code, message, data); + } +} diff --git a/src/main/java/friend/spring/apiPayload/ExceptionAdvice.java b/src/main/java/friend/spring/apiPayload/ExceptionAdvice.java new file mode 100644 index 0000000..f9737d2 --- /dev/null +++ b/src/main/java/friend/spring/apiPayload/ExceptionAdvice.java @@ -0,0 +1,120 @@ +package friend.spring.apiPayload; + +import friend.spring.apiPayload.code.ErrorReasonDTO; +import friend.spring.apiPayload.code.status.ErrorStatus; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +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.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.ConstraintViolationException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@RestControllerAdvice(annotations = {RestController.class}) +public class ExceptionAdvice extends ResponseEntityExceptionHandler { + + + @org.springframework.web.bind.annotation.ExceptionHandler + public ResponseEntity validation(ConstraintViolationException e, WebRequest request) { + String errorMessage = e.getConstraintViolations().stream() + .map(constraintViolation -> constraintViolation.getMessage()) + .findFirst() + .orElseThrow(() -> new RuntimeException("ConstraintViolationException 추출 도중 에러 발생")); + + return handleExceptionInternalConstraint(e, ErrorStatus.valueOf(errorMessage), HttpHeaders.EMPTY, request); + } + + + @Override + public ResponseEntity handleMethodArgumentNotValid( + MethodArgumentNotValidException e, HttpHeaders headers, HttpStatus status, WebRequest request) { + + Map errors = new LinkedHashMap<>(); + + e.getBindingResult().getFieldErrors().stream() + .forEach(fieldError -> { + String fieldName = fieldError.getField(); + String errorMessage = Optional.ofNullable(fieldError.getDefaultMessage()).orElse(""); + errors.merge(fieldName, errorMessage, (existingErrorMessage, newErrorMessage) -> existingErrorMessage + ", " + newErrorMessage); + }); + + return handleExceptionInternalArgs(e, HttpHeaders.EMPTY, ErrorStatus.valueOf("_BAD_REQUEST"), request, errors); + } + + @org.springframework.web.bind.annotation.ExceptionHandler + public ResponseEntity exception(Exception e, WebRequest request) { + e.printStackTrace(); + + return handleExceptionInternalFalse(e, ErrorStatus._INTERNAL_SERVER_ERROR, HttpHeaders.EMPTY, ErrorStatus._INTERNAL_SERVER_ERROR.getHttpStatus(), request, e.getMessage()); + } + + @ExceptionHandler(value = GeneralException.class) + public ResponseEntity onThrowException(GeneralException generalException, HttpServletRequest request) { + ErrorReasonDTO errorReasonHttpStatus = generalException.getErrorReasonHttpStatus(); + return handleExceptionInternal(generalException, errorReasonHttpStatus, null, request); + } + + private ResponseEntity handleExceptionInternal(Exception e, ErrorReasonDTO reason, + HttpHeaders headers, HttpServletRequest request) { + + ApiResponse body = ApiResponse.onFailure(reason.getCode(), reason.getMessage(), null); +// e.printStackTrace(); + + WebRequest webRequest = new ServletWebRequest(request); + return super.handleExceptionInternal( + e, + body, + headers, + reason.getHttpStatus(), + webRequest + ); + } + + private ResponseEntity handleExceptionInternalFalse(Exception e, ErrorStatus errorCommonStatus, + HttpHeaders headers, HttpStatus status, WebRequest request, String errorPoint) { + ApiResponse body = ApiResponse.onFailure(errorCommonStatus.getCode(), errorCommonStatus.getMessage(), errorPoint); + return super.handleExceptionInternal( + e, + body, + headers, + status, + request + ); + } + + private ResponseEntity handleExceptionInternalArgs(Exception e, HttpHeaders headers, ErrorStatus errorCommonStatus, + WebRequest request, Map errorArgs) { + ApiResponse body = ApiResponse.onFailure(errorCommonStatus.getCode(), errorCommonStatus.getMessage(), errorArgs); + return super.handleExceptionInternal( + e, + body, + headers, + errorCommonStatus.getHttpStatus(), + request + ); + } + + private ResponseEntity handleExceptionInternalConstraint(Exception e, ErrorStatus errorCommonStatus, + HttpHeaders headers, WebRequest request) { + ApiResponse body = ApiResponse.onFailure(errorCommonStatus.getCode(), errorCommonStatus.getMessage(), null); + return super.handleExceptionInternal( + e, + body, + headers, + errorCommonStatus.getHttpStatus(), + request + ); + } +} \ No newline at end of file diff --git a/src/main/java/friend/spring/apiPayload/GeneralException.java b/src/main/java/friend/spring/apiPayload/GeneralException.java new file mode 100644 index 0000000..b67a1e4 --- /dev/null +++ b/src/main/java/friend/spring/apiPayload/GeneralException.java @@ -0,0 +1,21 @@ +package friend.spring.apiPayload; + +import friend.spring.apiPayload.code.BaseErrorCode; +import friend.spring.apiPayload.code.ErrorReasonDTO; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class GeneralException extends RuntimeException { + + private BaseErrorCode code; + + public ErrorReasonDTO getErrorReason() { + return this.code.getReason(); + } + + public ErrorReasonDTO getErrorReasonHttpStatus() { + return this.code.getReasonHttpStatus(); + } +} \ No newline at end of file diff --git a/src/main/java/friend/spring/apiPayload/code/BaseCode.java b/src/main/java/friend/spring/apiPayload/code/BaseCode.java new file mode 100644 index 0000000..064d788 --- /dev/null +++ b/src/main/java/friend/spring/apiPayload/code/BaseCode.java @@ -0,0 +1,8 @@ +package friend.spring.apiPayload.code; + +public interface BaseCode { + + public ReasonDTO getReason(); + + public ReasonDTO getReasonHttpStatus(); +} \ No newline at end of file diff --git a/src/main/java/friend/spring/apiPayload/code/BaseErrorCode.java b/src/main/java/friend/spring/apiPayload/code/BaseErrorCode.java new file mode 100644 index 0000000..3d8b1eb --- /dev/null +++ b/src/main/java/friend/spring/apiPayload/code/BaseErrorCode.java @@ -0,0 +1,8 @@ +package friend.spring.apiPayload.code; + +public interface BaseErrorCode { + + public ErrorReasonDTO getReason(); + + public ErrorReasonDTO getReasonHttpStatus(); +} \ No newline at end of file diff --git a/src/main/java/friend/spring/apiPayload/code/ErrorReasonDTO.java b/src/main/java/friend/spring/apiPayload/code/ErrorReasonDTO.java new file mode 100644 index 0000000..db731d0 --- /dev/null +++ b/src/main/java/friend/spring/apiPayload/code/ErrorReasonDTO.java @@ -0,0 +1,18 @@ +package friend.spring.apiPayload.code; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Builder +@Getter +@Data +@AllArgsConstructor +public class ErrorReasonDTO { + private final HttpStatus httpStatus; + private final String code; + private final String message; + private final boolean isSuccess; +} diff --git a/src/main/java/friend/spring/apiPayload/code/ReasonDTO.java b/src/main/java/friend/spring/apiPayload/code/ReasonDTO.java new file mode 100644 index 0000000..c48f905 --- /dev/null +++ b/src/main/java/friend/spring/apiPayload/code/ReasonDTO.java @@ -0,0 +1,14 @@ +package friend.spring.apiPayload.code; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Builder +@Getter +public class ReasonDTO { + private final HttpStatus httpStatus; + private final String code; + private final String message; + private final boolean isSuccess; +} \ No newline at end of file diff --git a/src/main/java/friend/spring/apiPayload/code/status/ErrorStatus.java b/src/main/java/friend/spring/apiPayload/code/status/ErrorStatus.java new file mode 100644 index 0000000..2daa04f --- /dev/null +++ b/src/main/java/friend/spring/apiPayload/code/status/ErrorStatus.java @@ -0,0 +1,109 @@ +package friend.spring.apiPayload.code.status; + +import friend.spring.apiPayload.code.BaseErrorCode; +import friend.spring.apiPayload.code.ErrorReasonDTO; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum ErrorStatus implements BaseErrorCode { + + // 가장 일반적인 응답 + _INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON5000", "서버 에러, 관리자에게 문의 바랍니다."), + _BAD_REQUEST(HttpStatus.BAD_REQUEST, "COMMON4000", "잘못된 요청입니다."), + _UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "COMMON4001", "인증이 필요합니다."), + _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON4003", "금지된 요청입니다."), + + // 멤버 관련 응답 + USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER4001", "회원정보가 존재하지 않습니다."), + USERS_NOT_FOUND_EMAIL(HttpStatus.NOT_FOUND, "USER4010", "가입 가능한 이메일입니다."), + USER_EXISTS_EMAIL(HttpStatus.NOT_ACCEPTABLE, "USER4002", "이미 존재하는 메일 주소입니다"), + UNABLE_TO_SEND_EMAIL(HttpStatus.BAD_REQUEST, "USER4003", "이메일을 발송하지 못했습니다."), + ERR_MAKE_CODE(HttpStatus.BAD_REQUEST, "USER4004", "인증 코드 생성에 오류가 있습니다."), + INCORRECT_CODE(HttpStatus.UNAUTHORIZED, "USER4005", "인증 코드가 일치하지 않습니다."), + EMPTY_JWT(HttpStatus.BAD_REQUEST, "USER4006", "JWT를 입력해주세요."), + INVALID_JWT(HttpStatus.UNAUTHORIZED, "USER4007", "유효하지 않은 JWT입니다."), + INVALID_PASSWORD_FORMAT(HttpStatus.NOT_ACCEPTABLE, "USER4077", "비밀번호 형식에 맞지 않습니다."), + PASSWORD_INCORRECT(HttpStatus.NOT_FOUND, "USER4008", "비밀번호가 틀렸습니다."), + PASSWORD_CHECK_INCORRECT(HttpStatus.NOT_FOUND, "USER4009", "확인 비밀번호가 일치하지 않습니다."), + RTK_INCORREXT(HttpStatus.UNAUTHORIZED, "USER4100", "RefreshToken값을 확인해주세요."), + NOT_ADMIN(HttpStatus.BAD_REQUEST, "USER4101", "관리자가 아닙니다."), + + // Auth 관련 + AUTH_EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH_4101", "토큰이 만료되었습니다."), + AUTH_INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH_4102", "토큰이 유효하지 않습니다."), + INVALID_LOGIN_REQUEST(HttpStatus.UNAUTHORIZED, "AUTH_4103", "올바른 이메일이나 패스워드가 아닙니다."), + INVALID_REQUEST_INFO(HttpStatus.UNAUTHORIZED, "AUTH_4106", "카카오 정보 불러오기에 실패하였습니다."), + NOT_EQUAL_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH_4107", "리프레시 토큰이 다릅니다."), + NOT_CONTAIN_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH_4108", "해당하는 토큰이 저장되어있지 않습니다."), + + // 글 관련 응답 + POST_NOT_FOUND(HttpStatus.NOT_FOUND, "POST4001", "글을 찾을 수 없습니다."), + POST_NOT_CORRECT_USER(HttpStatus.BAD_REQUEST, "POST4002", "올바른 사용자(글 작성자)가 아닙니다."), + POST_LIKE_NOT_FOUND(HttpStatus.NOT_FOUND, "POST4003", "글에 대한 좋아요 데이터를 찾을 수 없습니다."), + POST_CATGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "POST4004", "카테고리를 찾을 수 없습니다."), + POST_SAVED_NOT_FOUND(HttpStatus.NOT_FOUND, "POST4005", "저장한 글이 없습니다."), + POST_SCRAP_NOT_FOUND(HttpStatus.NOT_FOUND, "POST4004", "글에 대한 스크랩 데이터를 찾을 수 없습니다."), + POST_GENERAL_POLL_NOT_FOUND(HttpStatus.NOT_FOUND, "POST4005", "글에 대한 일반 투표 데이터를 찾을 수 없습니다."), + POST_CARD_POLL_NOT_FOUND(HttpStatus.NOT_FOUND, "POST4006", "글에 대한 카드 투표 데이터를 찾을 수 없습니다."), + TITLE_TEXT_LIMIT(HttpStatus.BAD_REQUEST, "POST4007", "최소 5자 이상, 30자 미만 입력해 주세요"), + CONTENT_TEXT_LIMIT(HttpStatus.BAD_REQUEST, "POST4008", "최소 5자 이상, 1000자 미만 입력해 주세요"), + CANDIDATE_TEXT_LIMIT(HttpStatus.BAD_REQUEST, "POST4009", "최소 1자 이상, 30자 미만 입력해 주세요"), + DEADLINE_LIMIT(HttpStatus.BAD_REQUEST, "POST4010", "최소 1분~최대30일로 입력해 주세요"), + CANDIDATE_NOT_FOUND(HttpStatus.NOT_FOUND, "POST4011", "후보를 찾을 수 없습니다"), + TOO_MUCH_FIXED(HttpStatus.NOT_FOUND, "POST4012", "이미 2회 이상 수정 했습니다"), + NOT_ENOUGH_POINT(HttpStatus.BAD_REQUEST, "POST4013", "해당 유저의 포인트가 부족 합니다"), + POST_LIKE_DUPLICATE(HttpStatus.BAD_REQUEST, "POST4014", "글에 대한 좋아요 데이터가 이미 존재합니다."), + POST_SCRAP_DUPLICATE(HttpStatus.BAD_REQUEST, "POST4015", "글에 대한 스크랩 데이터가 이미 존재합니다."), + DEADLINE_OVER(HttpStatus.BAD_REQUEST, "POST4016", "투표 마감 시간이 지났습니다"), + ALREADY_VOTE(HttpStatus.BAD_REQUEST, "POST4017", "이미 투표 하셨습니다."), +// USER_VOTE(HttpStatus.BAD_REQUEST,"POST4017","작성자는 투표가 불가능 합니다 하셨습니다."), // 이거 필요없으면 지워야 할 것 같습니다! + + POST_REPORT_DUPLICATE(HttpStatus.BAD_REQUEST, "POST4018", "이 유저가 해당 글을 신고한 신고 내역 데이터가 이미 존재합니다."), + + // 댓글 관련 응답 + COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "COMMENT4001", "댓글을 찾을 수 없습니다."), + COMMENT_LIKE_NOT_FOUND(HttpStatus.NOT_FOUND, "COMMENT4002", "댓글에 대한 좋아요 데이터를 찾을 수 없습니다."), + COMMENT_CHOICE_OVER_ONE(HttpStatus.BAD_REQUEST, "COMMENT4003", "댓글 채택은 1개 댓글에 대해서만 가능합니다."), + COMMENT_SELECT_MYSELF(HttpStatus.BAD_REQUEST, "COMMENT4004", "자기 자신은 채택할 수 없습니다."), + COMMENT_NOT_CORRECT_USER(HttpStatus.BAD_REQUEST, "COMMENT4005", "올바른 사용자(댓글 작성자)가 아닙니다."), + COMMENT_POST_NOT_MATCH(HttpStatus.BAD_REQUEST, "COMMENT4006", "해당 글에 작성된 댓글이 아닙니다."), + COMMENT_LIKE_DUPLICATE(HttpStatus.BAD_REQUEST, "COMMENT4007", "댓글에 대한 좋아요 데이터가 이미 존재합니다."), + COMMENT_REPORT_DUPLICATE(HttpStatus.BAD_REQUEST, "COMMENT4008", "이 유저가 해당 댓글을 신고한 신고 내역 데이터가 이미 존재합니다."), + + // 알림 관련 응답 + ALARM_NOT_FOUND(HttpStatus.NOT_FOUND, "ALARM4001", "알림이 없습니다"), + + + // 공지사항 관련 응답 + NOTICE_NOT_FOUND(HttpStatus.NOT_FOUND, "NOTICE4001", "공지사항이 없습니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ErrorReasonDTO getReason() { + return ErrorReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(false) + .build(); + } + + @Override + public ErrorReasonDTO getReasonHttpStatus() { + return ErrorReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(false) + .httpStatus(httpStatus) + .build() + ; + + + } +} + diff --git a/src/main/java/friend/spring/apiPayload/code/status/SuccessStatus.java b/src/main/java/friend/spring/apiPayload/code/status/SuccessStatus.java new file mode 100644 index 0000000..940cfcf --- /dev/null +++ b/src/main/java/friend/spring/apiPayload/code/status/SuccessStatus.java @@ -0,0 +1,44 @@ +package friend.spring.apiPayload.code.status; + +import friend.spring.apiPayload.code.BaseErrorCode; +import friend.spring.apiPayload.code.ErrorReasonDTO; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum SuccessStatus implements BaseErrorCode { + // 가장 일반적인 응답 + _OK(HttpStatus.OK, "COMMON200", "요청에 성공했습니다."), + ; + + // 멤버 관련 응답 + + // ~~~ 관련 응답 .... + + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ErrorReasonDTO getReason() { + return ErrorReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(false) + .build(); + } + + @Override + public ErrorReasonDTO getReasonHttpStatus() { + return ErrorReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(false) + .httpStatus(httpStatus) + .build() + ; + } +} \ No newline at end of file diff --git a/src/main/java/friend/spring/apiPayload/handler/AlarmHandler.java b/src/main/java/friend/spring/apiPayload/handler/AlarmHandler.java new file mode 100644 index 0000000..b0162b5 --- /dev/null +++ b/src/main/java/friend/spring/apiPayload/handler/AlarmHandler.java @@ -0,0 +1,10 @@ +package friend.spring.apiPayload.handler; + +import friend.spring.apiPayload.GeneralException; +import friend.spring.apiPayload.code.BaseErrorCode; + +public class AlarmHandler extends GeneralException { + public AlarmHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/friend/spring/apiPayload/handler/CommentHandler.java b/src/main/java/friend/spring/apiPayload/handler/CommentHandler.java new file mode 100644 index 0000000..f12198b --- /dev/null +++ b/src/main/java/friend/spring/apiPayload/handler/CommentHandler.java @@ -0,0 +1,10 @@ +package friend.spring.apiPayload.handler; + +import friend.spring.apiPayload.GeneralException; +import friend.spring.apiPayload.code.BaseErrorCode; + +public class CommentHandler extends GeneralException { + public CommentHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/friend/spring/apiPayload/handler/PostHandler.java b/src/main/java/friend/spring/apiPayload/handler/PostHandler.java new file mode 100644 index 0000000..66c4269 --- /dev/null +++ b/src/main/java/friend/spring/apiPayload/handler/PostHandler.java @@ -0,0 +1,10 @@ +package friend.spring.apiPayload.handler; + +import friend.spring.apiPayload.GeneralException; +import friend.spring.apiPayload.code.BaseErrorCode; + +public class PostHandler extends GeneralException { + public PostHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/friend/spring/apiPayload/handler/UserHandler.java b/src/main/java/friend/spring/apiPayload/handler/UserHandler.java new file mode 100644 index 0000000..06099f1 --- /dev/null +++ b/src/main/java/friend/spring/apiPayload/handler/UserHandler.java @@ -0,0 +1,10 @@ +package friend.spring.apiPayload.handler; + +import friend.spring.apiPayload.GeneralException; +import friend.spring.apiPayload.code.BaseErrorCode; + +public class UserHandler extends GeneralException { + public UserHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/friend/spring/aws/s3/AmazonS3Manager.java b/src/main/java/friend/spring/aws/s3/AmazonS3Manager.java new file mode 100644 index 0000000..89bac6c --- /dev/null +++ b/src/main/java/friend/spring/aws/s3/AmazonS3Manager.java @@ -0,0 +1,61 @@ +package friend.spring.aws.s3; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectMetadata; +import friend.spring.config.AmazonConfig; +import friend.spring.domain.Uuid; +import friend.spring.repository.UuidRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; + +import java.io.IOException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AmazonS3Manager { + + private final AmazonS3 amazonS3; + + private final S3Client s3Client; + + private final AmazonConfig amazonConfig; + + private final UuidRepository uuidRepository; + + public String uploadFile(String keyName, MultipartFile file) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(file.getSize()); + try { + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(amazonConfig.getBucket()) + .key(keyName) + .contentType(file.getContentType()) + .contentDisposition("inline") + .build(); + s3Client.putObject(putObjectRequest, RequestBody.fromInputStream(file.getInputStream(), file.getSize())); + + } catch (IOException e) { + log.error("error at AmazonS3Manager uploadFile : {}", (Object) e.getStackTrace()); + } + + return amazonS3.getUrl(amazonConfig.getBucket(), keyName).toString(); + } + + public String generateUserKeyName(Uuid uuid) { + return amazonConfig.getUserPath() + '/' + uuid.getUuid(); + } + + public String generatePostKeyName(Uuid uuid) { + return amazonConfig.getPostPath() + '/' + uuid.getUuid(); + } + + public String generateCandidateKeyName(Uuid uuid) { + return amazonConfig.getCandidatePath() + '/' + uuid.getUuid(); + } +} \ No newline at end of file diff --git a/src/main/java/friend/spring/config/AmazonConfig.java b/src/main/java/friend/spring/config/AmazonConfig.java new file mode 100644 index 0000000..0e90f23 --- /dev/null +++ b/src/main/java/friend/spring/config/AmazonConfig.java @@ -0,0 +1,63 @@ +package friend.spring.config; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +@Configuration +@Getter +public class AmazonConfig { + + + private AWSCredentials awsCredentials; + + @Value("${cloud.aws.credentials.accessKey}") + private String accessKey; + + @Value("${cloud.aws.credentials.secretKey}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + @Value("${cloud.aws.s3.path.user}") + private String userPath; + + @Value("${cloud.aws.s3.path.post}") + private String postPath; + + @Value("${cloud.aws.s3.path.candidate}") + private String candidatePath; + + + @PostConstruct + public void init() { + this.awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + } + + @Bean + public AmazonS3 amazonS3() { + AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + return AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } + + @Bean + public AWSCredentialsProvider awsCredentialsProvider() { + return new AWSStaticCredentialsProvider(awsCredentials); + } +} \ No newline at end of file diff --git a/src/main/java/friend/spring/config/CorsConfig.java b/src/main/java/friend/spring/config/CorsConfig.java new file mode 100644 index 0000000..2b56bf9 --- /dev/null +++ b/src/main/java/friend/spring/config/CorsConfig.java @@ -0,0 +1,19 @@ +package friend.spring.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 CorsConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOriginPatterns("*") + .allowedHeaders("*") + .allowedMethods("*") + .exposedHeaders("*") + .allowCredentials(true); + } +} \ No newline at end of file diff --git a/src/main/java/friend/spring/config/EmailConfig.java b/src/main/java/friend/spring/config/EmailConfig.java new file mode 100644 index 0000000..acb52d0 --- /dev/null +++ b/src/main/java/friend/spring/config/EmailConfig.java @@ -0,0 +1,57 @@ +package friend.spring.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import java.util.Properties; + +@Configuration +@PropertySource(value = {"/application.yml"}, factory = YmlSourceFactory.class) +public class EmailConfig { + + @Value("${mail.smtp.host}") + private String host; + @Value("${mail.smtp.port}") + private int port; + @Value("${mail.smtp.socketFactoryPort}") + private int socketPort; + @Value("${mail.smtp.auth}") + private boolean auth; + @Value("${mail.smtp.starttls.enable}") + private boolean starttls; + @Value("${mail.smtp.starttls.required}") + private boolean startlls_required; + @Value("${mail.smtp.socketFactory.fallback}") + private boolean fallback; + @Value("${mail.AdminMail.id}") + private String id; + @Value("${mail.AdminMail.password}") + private String password; + + @Bean + public JavaMailSender javaMailService() { + JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl(); + javaMailSender.setHost("smtp.gmail.com"); + javaMailSender.setUsername(id); + javaMailSender.setPassword(password); + javaMailSender.setPort(port); + javaMailSender.setJavaMailProperties(getMailProperties()); + javaMailSender.setDefaultEncoding("UTF-8"); + return javaMailSender; + } + + private Properties getMailProperties() { + Properties pt = new Properties(); + pt.put("mail.smtp.socketFactory.port", socketPort); + pt.put("mail.smtp.auth", auth); + pt.put("mail.smtp.starttls.enable", starttls); + pt.put("mail.smtp.starttls.required", startlls_required); + pt.put("mail.smtp.socketFactory.fallback", fallback); + //pt.put("mail.smtp.socketFactory.class", "javax.net.tls.SSLSocketFactory"); + return pt; + } +} diff --git a/src/main/java/friend/spring/config/RedisRepositoryConfig.java b/src/main/java/friend/spring/config/RedisRepositoryConfig.java new file mode 100644 index 0000000..9bb2b6a --- /dev/null +++ b/src/main/java/friend/spring/config/RedisRepositoryConfig.java @@ -0,0 +1,67 @@ +package friend.spring.config; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@RequiredArgsConstructor +@Configuration +@EnableRedisRepositories +public class RedisRepositoryConfig { + + @Value("${spring.redis.host}") + private String host; + + @Value("${spring.redis.port}") + private int port; + + // lettuce + // RedisConnectionFactory 인터페이스를 통해 LettuceConnectionFactory를 생성하여 반환한다. + // RedisProperties로 yaml에 저장한 host, post를 가지고 와서 연결한다. + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(host, port); + } + + // setKeySerializer, setValueSerializer 설정으로 redis-cli를 통해 직접 데이터를 보는게 가능하다. + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>();// redisTemplate를 받아와서 set, get, delete + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + return redisTemplate; + } + + @Bean + public RedisTemplate objectRedisTemplate(RedisConnectionFactory redisConnectionFactory){ + PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator + .builder() + .allowIfSubType(Object.class) + .build(); + ObjectMapper objectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL) + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS); + + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory()); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)); + return template; + } +} diff --git a/src/main/java/friend/spring/config/RedisUtil.java b/src/main/java/friend/spring/config/RedisUtil.java new file mode 100644 index 0000000..046d242 --- /dev/null +++ b/src/main/java/friend/spring/config/RedisUtil.java @@ -0,0 +1,35 @@ +package friend.spring.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Service; + +import java.time.Duration; + +@Service +@RequiredArgsConstructor +public class RedisUtil { + + private final StringRedisTemplate redisTemplate;//Redis에 접근하기 위한 Spring의 Redis 템플릿 클래스 + + public String getData(String key) {//지정된 키(key)에 해당하는 데이터를 Redis에서 가져오는 메서드 + ValueOperations valueOperations = redisTemplate.opsForValue(); + return valueOperations.get(key); + } + + public void setData(String key, String value) {//지정된 키(key)에 값을 저장하는 메서드 + ValueOperations valueOperations = redisTemplate.opsForValue(); + valueOperations.set(key, value); + } + + public void setDataExpire(String key, String value, long duration) {//지정된 키(key)에 값을 저장하고, 지정된 시간(duration) 후에 데이터가 만료되도록 설정하는 메서드 + ValueOperations valueOperations = redisTemplate.opsForValue(); + Duration expireDuration = Duration.ofSeconds(duration); + valueOperations.set(key, value, expireDuration); + } + + public void deleteData(String key) {//지정된 키(key)에 해당하는 데이터를 Redis에서 삭제하는 메서드 + redisTemplate.delete(key); + } +} \ No newline at end of file diff --git a/src/main/java/friend/spring/config/S3Config.java b/src/main/java/friend/spring/config/S3Config.java new file mode 100644 index 0000000..466cced --- /dev/null +++ b/src/main/java/friend/spring/config/S3Config.java @@ -0,0 +1,15 @@ +package friend.spring.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +@Configuration +public class S3Config { + + @Bean + public S3Client defaultS3Client() { + return S3Client.builder().region(Region.AP_NORTHEAST_2).build(); + } +} diff --git a/src/main/java/friend/spring/config/SwaggerConfig.java b/src/main/java/friend/spring/config/SwaggerConfig.java new file mode 100644 index 0000000..0f4e9cc --- /dev/null +++ b/src/main/java/friend/spring/config/SwaggerConfig.java @@ -0,0 +1,37 @@ +package friend.spring.config; + +import io.swagger.v3.oas.models.info.Info; +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 io.swagger.v3.oas.models.servers.Server; +import org.springframework.context.annotation.Bean; + +public class SwaggerConfig { + @Bean + public OpenAPI UMCstudyAPI() { + Info info = new Info() + .title("고민친구 Server API") + .description("고민친구 API 명세서") + .version("1.0.0"); + + String jwtSchemeName = "JWT TOKEN"; + // API 요청헤더에 인증정보 포함 + SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName); + // SecuritySchemes 등록 + Components components = new Components() + .addSecuritySchemes(jwtSchemeName, new SecurityScheme() + .name(jwtSchemeName) + .type(SecurityScheme.Type.HTTP) // HTTP 방식 + .scheme("bearer") + .bearerFormat("JWT")); + + return new OpenAPI() + .addServersItem(new Server().url("/")) + .info(info) + .addSecurityItem(securityRequirement) + .components(components); + } +} + diff --git a/src/main/java/friend/spring/config/WebSecurityConfig.java b/src/main/java/friend/spring/config/WebSecurityConfig.java new file mode 100644 index 0000000..7b69c45 --- /dev/null +++ b/src/main/java/friend/spring/config/WebSecurityConfig.java @@ -0,0 +1,55 @@ +package friend.spring.config; + +import friend.spring.security.JwtAuthenticationFilter; +import friend.spring.security.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.authentication.AuthenticationManager; +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.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +// Spring security 사용하는 경우 Postman 의 접근을 security 가 막기 때문에 설정해주어야 함. +@RequiredArgsConstructor +@Configuration +@EnableWebSecurity +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + private final JwtTokenProvider jwtTokenProvider; + private final RedisTemplate redisTemplate; + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.httpBasic().disable() // rest api 만을 고려하여 기본 설정은 해제 + .csrf().disable()// csrf 보안 토큰 disable + .formLogin().disable() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // session 사용 X + .and() + .authorizeRequests() + //.antMatchers("/users/test").hasRole("USER") + .antMatchers("/**").permitAll() +// .antMatchers("/health").authenticated() + .anyRequest().permitAll(); + + http.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, redisTemplate), UsernamePasswordAuthenticationFilter.class) + .authorizeRequests(); + } +} diff --git a/src/main/java/friend/spring/config/YmlSourceFactory.java b/src/main/java/friend/spring/config/YmlSourceFactory.java new file mode 100644 index 0000000..8151145 --- /dev/null +++ b/src/main/java/friend/spring/config/YmlSourceFactory.java @@ -0,0 +1,22 @@ +package friend.spring.config; + +import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.core.io.support.PropertySourceFactory; +import org.springframework.lang.Nullable; + +import java.io.IOException; +import java.util.Properties; + +public class YmlSourceFactory implements PropertySourceFactory { + + @Override + public PropertySource createPropertySource(@Nullable String name, EncodedResource resource) throws IOException { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResources(resource.getResource()); + Properties properties = factory.getObject(); + return new PropertiesPropertySource(resource.getResource().getFilename(), properties); + } +} diff --git a/src/main/java/friend/spring/converter/AlarmConverter.java b/src/main/java/friend/spring/converter/AlarmConverter.java new file mode 100644 index 0000000..c5e5ed7 --- /dev/null +++ b/src/main/java/friend/spring/converter/AlarmConverter.java @@ -0,0 +1,63 @@ +package friend.spring.converter; + +import friend.spring.domain.Alarm; +import friend.spring.domain.Comment; +import friend.spring.domain.Post; +import friend.spring.domain.User; +import friend.spring.domain.enums.AlarmType; +import friend.spring.web.dto.AlarmResponseDTO; +import org.springframework.data.domain.Page; + +import java.util.List; +import java.util.stream.Collectors; + +public class AlarmConverter { + + //알림 + public static AlarmResponseDTO.AlarmResDTO toAlarmResDTO(Alarm alarm) { + Long commentId = null; + String commentContent = null; + String userNickname = null; + String userPhoto = null; + if (alarm.getComment() != null) { + commentId = alarm.getComment().getId(); + commentContent = alarm.getComment().getContent(); + userNickname = alarm.getComment().getUser().getNickname(); + if (alarm.getComment().getUser().getFile() != null) { + userPhoto = alarm.getComment().getUser().getFile().getUrl(); + } + } + + return AlarmResponseDTO.AlarmResDTO.builder() + .alarmId(alarm.getId()) + .userNickname(userNickname) + .userPhoto(userPhoto) + .alarmType(alarm.getType().toString()) + .alarmContent(alarm.getContent()) + .postId(alarm.getPost().getId()) + .commentId(commentId) + .commentContent(commentContent) + .createdAt(alarm.getCreatedAt()) + .read(alarm.getRead()) + .build(); + } + + public static AlarmResponseDTO.AlarmListResDTO toAlarmListResDTO(Page alarmList) { + List alarmResDTOList = alarmList.stream() + .map(AlarmConverter::toAlarmResDTO).collect(Collectors.toList()); + return AlarmResponseDTO.AlarmListResDTO.builder() + .alarmList(alarmResDTOList) + .build(); + } + + public static Alarm toAlarm(String alarmContent, AlarmType alarmType, Post post, User user, Comment comment) { + return Alarm.builder() + .content(alarmContent) + .type(alarmType) + .post(post) + .user(user) + .comment(comment) + .read(false) + .build(); + } +} diff --git a/src/main/java/friend/spring/converter/Base64Decoder.java b/src/main/java/friend/spring/converter/Base64Decoder.java new file mode 100644 index 0000000..7d83801 --- /dev/null +++ b/src/main/java/friend/spring/converter/Base64Decoder.java @@ -0,0 +1,68 @@ +package friend.spring.converter; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.util.Base64; + +public class Base64Decoder { + + public static MultipartFile decodeBase64ToMultipartFile(String base64String) throws IOException { + // Base64 문자열을 디코딩하여 byte 배열로 변환 + byte[] decodedBytes = Base64.getDecoder().decode(base64String); + + // 디코딩된 byte 배열을 이용하여 MultipartFile 생성 + MultipartFile multipartFile = createMultipartFile(decodedBytes); + + return multipartFile; + } + + private static MultipartFile createMultipartFile(byte[] content) throws IOException { + // MultipartFile을 구현한 클래스를 사용하여 MultipartFile 생성 + return new MultipartFile() { + @Override + public String getName() { + return null; + } + + @Override + public String getOriginalFilename() { + // 임의의 파일 이름 또는 고정된 확장자를 사용할 수 있습니다. + return "example.jpg"; + } + + @Override + public String getContentType() { + // 이미지 타입에 맞게 Content-Type 설정 + return "image/jpeg"; // 예시로 JPEG 이미지로 설정 + } + + @Override + public boolean isEmpty() { + return content.length == 0; + } + + @Override + public long getSize() { + return content.length; + } + + @Override + public byte[] getBytes() throws IOException { + return content; + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(content); + } + + @Override + public void transferTo(File dest) throws IOException, IllegalStateException { + try (OutputStream os = new FileOutputStream(dest)) { + os.write(content); + } + } + }; + } +} diff --git a/src/main/java/friend/spring/converter/CandidateConverter.java b/src/main/java/friend/spring/converter/CandidateConverter.java new file mode 100644 index 0000000..f11e739 --- /dev/null +++ b/src/main/java/friend/spring/converter/CandidateConverter.java @@ -0,0 +1,13 @@ +package friend.spring.converter; + +import friend.spring.domain.Candidate; +import friend.spring.web.dto.CandidateResponseDTO; + +public class CandidateConverter { + public static CandidateResponseDTO.AddCandidateResultDTO toAddCandidateResultDTO(Candidate candidate) { + return CandidateResponseDTO.AddCandidateResultDTO.builder() + .candidateId(candidate.getId()) + .createdAt(candidate.getCreatedAt()) + .build(); + } +} diff --git a/src/main/java/friend/spring/converter/CommentConverter.java b/src/main/java/friend/spring/converter/CommentConverter.java new file mode 100644 index 0000000..40e5efe --- /dev/null +++ b/src/main/java/friend/spring/converter/CommentConverter.java @@ -0,0 +1,135 @@ +package friend.spring.converter; + +import friend.spring.domain.*; +import friend.spring.domain.enums.CommentState; +import friend.spring.domain.enums.ReportType; +import friend.spring.domain.mapping.Comment_choice; +import friend.spring.domain.mapping.Comment_like; +import friend.spring.web.dto.CommentRequestDTO; +import friend.spring.web.dto.CommentResponseDTO; +import friend.spring.web.dto.PostResponseDTO; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class CommentConverter { + public static Comment toComment(CommentRequestDTO.commentCreateReq request, Post post, User user, Comment parentComment) { + return Comment.builder() + .user(user) + .post(post) + .content(request.getContent()) + .parentComment(parentComment) + .state(CommentState.POSTING) + .build(); + } + + public static CommentResponseDTO.commentCreateRes toCommentCreateRes(Comment comment) { + return CommentResponseDTO.commentCreateRes.builder() + .commentId(comment.getId()) + .build(); + } + + public static Comment_like toCommentLike(Post post, Comment comment, User user) { + return Comment_like.builder() + .user(user) + .comment(comment) + .build(); + } + + public static CommentResponseDTO.commentLikeRes toCommentLikeRes(Comment_like comment_like) { + return CommentResponseDTO.commentLikeRes.builder() + .commentLikeId(comment_like.getId()) + .build(); + } + + public static CommentResponseDTO.commentGetRes toCommentGetRes(Comment comment, Long loginUserId, Boolean isPushedLike, Boolean isOwnerOfPost, Boolean isSelected, List subComments) { + Long parentCommentId = null; + if (comment.getParentComment() != null) { + parentCommentId = comment.getParentComment().getId(); + } + + // 유저 프로필 + String userPhoto = null; + if (comment.getUser().getFile() != null) { + userPhoto = comment.getUser().getFile().getUrl(); + } + + // 댓글 삭제 여부 + boolean isDeleted; + if (comment.getState().equals(CommentState.DELETED)) { + isDeleted = true; + } else { + isDeleted = false; + } + + // 내가 쓴 댓글인지 여부 + boolean isMyComment; + if (Objects.equals(comment.getUser().getId(), loginUserId)) { + isMyComment = true; + } else { + isMyComment = false; + } + + return CommentResponseDTO.commentGetRes.builder() + .commentId(comment.getId()) + .content(comment.getContent()) + .userId(comment.getUser().getId()) + .userNickname(comment.getUser().getNickname()) + .userImage(userPhoto) + .createdAt(comment.getCreatedAt()) + .updatedAt(comment.getUpdatedAt()) + .parentCommentId(parentCommentId) + .commentLike(comment.getCommentLikeList().size()) + .childrenComments(subComments) + .isDeleted(isDeleted) + .isMyComment(isMyComment) + .isPushedLike(isPushedLike) + .isOwnerOfPost(isOwnerOfPost) + .isSelected(isSelected) + .build(); + } + + public static Comment_choice toCommentChoice(Post post, Comment comment) { + return Comment_choice.builder() + .point(post.getPoint()) + .post(post) + .comment(comment) + .build(); + } + + public static CommentResponseDTO.commentSelectRes toCommentSelectRes(Comment_choice comment_choice) { + return CommentResponseDTO.commentSelectRes.builder() + .commentChoiceId(comment_choice.getId()) + .point(comment_choice.getPoint()) + .build(); + } + + public static CommentResponseDTO.myCommentRes toMyCommentResDTO(Comment comment) { + return CommentResponseDTO.myCommentRes.builder() + .postId(comment.getPost().getId()) + .nickName(comment.getUser().getNickname()) + .createdAt(comment.getCreatedAt()) + .content(comment.getContent()) + .commentLike(comment.getCommentLikeList().size()) + .reComment(comment.getSubCommentList().size()) + .build(); + } + + public static CommentResponseDTO.CommentReportRes toCommentReportRes(PostResponseDTO.ReportResult reportResult) { + return CommentResponseDTO.CommentReportRes.builder() + .reportId(reportResult.getReport().getId()) + .createdAt(reportResult.getReport().getCreatedAt()) + .duplicatedReport(reportResult.getDuplicatedReport()) + .build(); + } + + public static Report toReportComment(Comment comment, User user, ReportCategory reportCategory) { + return Report.builder() + .targetType(ReportType.COMMENT) + .targetId(comment.getId()) + .reportCategory(reportCategory) + .user(user) + .build(); + } +} diff --git a/src/main/java/friend/spring/converter/FileConverter.java b/src/main/java/friend/spring/converter/FileConverter.java new file mode 100644 index 0000000..dba886a --- /dev/null +++ b/src/main/java/friend/spring/converter/FileConverter.java @@ -0,0 +1,32 @@ +package friend.spring.converter; + +import friend.spring.domain.Candidate; +import friend.spring.domain.File; +import friend.spring.domain.Post; +import friend.spring.domain.User; +import friend.spring.web.dto.FileDTO; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class FileConverter { + + public static File toFile(String pictureUrl, User user, Post post, Candidate candidate) { + return File.builder() + .url(pictureUrl) + .user(user) + .post(post) + .candidate(candidate) + .build(); + } + + public static List toFileDTO(List fileList) { + return fileList.stream() + .map(file -> FileDTO.builder() + .imageId(file.getId()) + .imageUrl(file.getUrl()) + .build()) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/friend/spring/converter/MultipartJackson2HttpMessageConverter.java b/src/main/java/friend/spring/converter/MultipartJackson2HttpMessageConverter.java new file mode 100644 index 0000000..983139a --- /dev/null +++ b/src/main/java/friend/spring/converter/MultipartJackson2HttpMessageConverter.java @@ -0,0 +1,31 @@ +package friend.spring.converter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Type; + +@Component +public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter { + + public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) { + super(objectMapper, MediaType.APPLICATION_OCTET_STREAM); + } + + @Override + public boolean canWrite(Class clazz, MediaType mediaType) { + return false; + } + + @Override + public boolean canWrite(Type type, Class clazz, MediaType mediaType) { + return false; + } + + @Override + protected boolean canWrite(MediaType mediaType) { + return false; + } +} \ No newline at end of file diff --git a/src/main/java/friend/spring/converter/MyPageConverter.java b/src/main/java/friend/spring/converter/MyPageConverter.java new file mode 100644 index 0000000..383591e --- /dev/null +++ b/src/main/java/friend/spring/converter/MyPageConverter.java @@ -0,0 +1,185 @@ +package friend.spring.converter; + +import friend.spring.domain.*; +import friend.spring.domain.mapping.Post_scrap; +import friend.spring.web.dto.MyPageRequestDTO; +import friend.spring.web.dto.MyPageResponseDTO; +import org.springframework.data.domain.Page; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static java.util.Calendar.HOUR; +import static java.util.Calendar.SECOND; +import static java.util.Calendar.MINUTE; + +public class MyPageConverter { + public static MyPageResponseDTO.CategoryResDTO toCategoryResDTO(Category category) { + return MyPageResponseDTO.CategoryResDTO.builder() + .categoryId(category.getId()) + .category(category.getName()) + .build(); + } + + public static MyPageResponseDTO.SavedCategoryResDTO toSavedCategoryResDTO(List categoryList) { + List categoryNameList = categoryList.stream() + .map(MyPageConverter::toCategoryResDTO).collect(Collectors.toList()); + return MyPageResponseDTO.SavedCategoryResDTO.builder() + .postCategoryList(categoryNameList).build(); + } + + public static MyPageResponseDTO.SavedPostResDTO toSavedPostResDTO(Post post) { + long diffTime = post.getCreatedAt().until(LocalDateTime.now(), ChronoUnit.DAYS); // now보다 이후면 +, 전이면 - + + return MyPageResponseDTO.SavedPostResDTO.builder() + .postId(post.getId()) + .ago(diffTime) + .title(post.getTitle()) + .content(post.getContent()) + .postLike(post.getPostLikeList().size()) + .comment(post.getCommentList().size()).build(); + } + + public static MyPageResponseDTO.SavedAllPostResDTO toSavedAllPostResDTO(Page postList) { + List savedAllPostList = postList.stream().map(MyPageConverter::toSavedPostResDTO).collect(Collectors.toList()); + return MyPageResponseDTO.SavedAllPostResDTO.builder() + .postList(savedAllPostList).build(); + } + + public static MyPageResponseDTO.MyProfileResDTO toMyProfileResDTO(User user) { + String userPhoto = null; + if (user.getFile() != null) { + userPhoto = user.getFile().getUrl(); + } + return MyPageResponseDTO.MyProfileResDTO.builder() + .userImage(userPhoto) + .nickName(user.getNickname()) + .email(user.getEmail()) + .phone(user.getPhone()) + .build(); + } + + public static User toUserName(MyPageRequestDTO.ProfileEditNameReq profileEditNameReq) { + return User.builder() + .nickname(profileEditNameReq.getNickName()) + .build(); + } + + public static MyPageResponseDTO.ProfileEditNameRes toProfileEditNameResDTO(User user) { + return MyPageResponseDTO.ProfileEditNameRes.builder() + .nickName(user.getNickname()) + .build(); + } + + public static MyPageResponseDTO.ProfileEditEmailRes toProfileEditEmailResDTO(User user) { + return MyPageResponseDTO.ProfileEditEmailRes.builder() + .changeEmail(user.getEmail()) + .build(); + } + + public static User toUserEmail(MyPageRequestDTO.ProfileEditEmailReq profileEditEmailReq) { + return User.builder() + .email(profileEditEmailReq.getChangeEmail()) + .build(); + } + + public static MyPageResponseDTO.ProfileEditPhoneRes toProfileEditPhoneResDTO(User user) { + return MyPageResponseDTO.ProfileEditPhoneRes.builder() + .phone(user.getPhone()) + .build(); + } + + public static MyPageResponseDTO.ProfileEditPasswordRes toProfileEditPasswordResDTO(User user) { + return MyPageResponseDTO.ProfileEditPasswordRes.builder() + .changePassword(user.getPassword()) + .build(); + } + + public static Inquiry toInquiry(MyPageRequestDTO.MyInquiryReq myInquiryReq, User user) { + return Inquiry.builder() + .category(myInquiryReq.getInquiryCategory()) + .content(myInquiryReq.getContent()) + .user(user) + .build(); + } + + public static MyPageResponseDTO.MyInquiryRes toMyInquiryRes(Inquiry inquiry) { + return MyPageResponseDTO.MyInquiryRes.builder() + .inquiry_id(inquiry.getId()) + .build(); + } + + public static MyPageResponseDTO.SavedPostCategoryDetailRes toSavedPostCategoryDetailRes(Post post) { + long diffTime = post.getCreatedAt().until(LocalDateTime.now(), ChronoUnit.DAYS); // now보다 이후면 +, 전이면 - + + return MyPageResponseDTO.SavedPostCategoryDetailRes.builder() + .postId(post.getId()) + .ago(diffTime) + .title(post.getTitle()) + .content(post.getContent()) + .postLike(post.getPostLikeList().size()) + .comment(post.getCommentList().size()) + .build(); + } + + public static MyPageResponseDTO.SavedPostCategoryDetailListRes toSavedPostCategoryDetailListRes(Page postList, Category category) { + List postCategoryDetailResList = postList.stream().map(MyPageConverter::toSavedPostCategoryDetailRes).collect(Collectors.toList()); + return MyPageResponseDTO.SavedPostCategoryDetailListRes.builder() + .name(category.getName()) + .postList(postCategoryDetailResList) + .build(); + } + + public static MyPageResponseDTO.NoticeRes toNoticeRes(Notice notice) { + long diffTime = notice.getCreatedAt().until(LocalDateTime.now(), ChronoUnit.DAYS); // now보다 이후면 +, 전이면 - + + String adminImage = null; + if (notice.getUser().getFile() != null) { + adminImage = notice.getUser().getFile().getUrl(); + } + return MyPageResponseDTO.NoticeRes.builder() + .adminImage(adminImage) + .ago(diffTime) + .title(notice.getTitle()) + .content(notice.getContent()) + .build(); + } + + public static MyPageResponseDTO.NoticeListRes toNoticeListRes(Page noticeList) { + List noticeResList = noticeList.stream().map(MyPageConverter::toNoticeRes).collect(Collectors.toList()); + return MyPageResponseDTO.NoticeListRes.builder() + .noticeList(noticeResList) + .build(); + } + + public static MyPageResponseDTO.NoticeDetailRes toNoticeDetailRes(Notice notice) { + String adminImage = null; + if (notice.getUser().getFile() != null) { + adminImage = notice.getUser().getFile().getUrl(); + } + return MyPageResponseDTO.NoticeDetailRes.builder() + .adminImage(adminImage) + .adminName(notice.getUser().getNickname()) + .createdAt(notice.getCreatedAt()) + .content(notice.getContent()) + .view(notice.getView()) + .build(); + } + + public static MyPageResponseDTO.TermRes toTermRes(Term term) { + return MyPageResponseDTO.TermRes.builder() + .content(term.getTerm()) + .build(); + } + + public static MyPageResponseDTO.PrivacyRes toPrivacyRes(Term term) { + return MyPageResponseDTO.PrivacyRes.builder() + .content(term.getPrivacy()) + .build(); + } +} diff --git a/src/main/java/friend/spring/converter/PostConverter.java b/src/main/java/friend/spring/converter/PostConverter.java new file mode 100644 index 0000000..10b847b --- /dev/null +++ b/src/main/java/friend/spring/converter/PostConverter.java @@ -0,0 +1,1070 @@ +package friend.spring.converter; + +import friend.spring.domain.*; +import friend.spring.domain.Redis.SearchLog; +import friend.spring.domain.enums.PostState; +import friend.spring.domain.enums.PostType; +import friend.spring.domain.enums.PostVoteType; +import friend.spring.domain.enums.ReportType; +import friend.spring.service.PostQueryService; +import friend.spring.web.dto.*; +import org.springframework.data.domain.Page; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import friend.spring.domain.Post; +import friend.spring.domain.User; + +import friend.spring.domain.mapping.Post_like; +import friend.spring.domain.mapping.Post_scrap; +import friend.spring.web.dto.PostRequestDTO; +import friend.spring.web.dto.PostResponseDTO; + +import java.util.List; + +import static friend.spring.domain.enums.PostType.*; + +public class PostConverter { + + private static PostQueryService postQueryService; + + public static PostResponseDTO.AddPostResultDTO toAddPostResultDTO(Post post) { + return PostResponseDTO.AddPostResultDTO.builder() + .postId(post.getId()) + .createdAt(LocalDateTime.now()) + .build(); + } + + public static PollOptionDTO.PollOptionRes toPollOptionResDTO(Candidate candidate) { + String optionImgUrl = null; + if (candidate.getFile() != null) { + optionImgUrl = candidate.getFile().getUrl(); + } + return PollOptionDTO.PollOptionRes.builder() + .optionId(candidate.getId()) + .optionString(candidate.getName()) + .optionImgUrl(optionImgUrl).build(); + } + + public static ParentPostDTO toParentPostDTO(Post parentPost) { + ParentPollDTO parentPollDTO = null; + Integer parentLikes = parentPost.getPostLikeList().size(); + Integer parentComments = parentPost.getCommentList().size(); + File userFile = null; + String userImg = null; + Optional userFileOptional = Optional.ofNullable(parentPost.getUser().getFile()); + + if (userFileOptional.isPresent()) { + userFile = userFileOptional.get(); + userImg = userFile.getUrl(); + } + + if (parentPost.getVoteType() == PostVoteType.GAUGE) { + return ParentPostDTO.builder() + .postId(parentPost.getId()) + .postVoteType(parentPost.getVoteType()) + .nickname(parentPost.getUser().getNickname()) + .userImg(userImg) + .title(parentPost.getTitle()) + .content(parentPost.getContent()) + .gauge(parentPost.getGaugePoll().getGauge()) + .like(parentLikes) + .comment(parentComments) + .build(); + } + + if (parentPost.getVoteType() == PostVoteType.GENERAL) { + List pollOptionDTOList = parentPost.getGeneralPoll().getCandidateList().stream() + .map(PostConverter::toPollOptionResDTO).collect(Collectors.toList()); + + // 총 투표수 계산 + long totalVotes = parentPost.getGeneralPoll().getGeneralVoteList().stream() + .flatMap(vote -> vote.getSelect_list().stream()) + .count(); + + // 각 후보별 선택된 횟수 계산 + Map candidateSelectionCounts = parentPost.getGeneralPoll().getGeneralVoteList().stream() + .flatMap(vote -> vote.getSelect_list().stream()) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + + // 선택률 -> ParentPollDTO 객체 리스트로 변환 + List candidateInfos = candidateSelectionCounts.entrySet().stream() + .map(entry -> ParentPollDTO.builder() +// .candidateName(candidateRepository.findById(entry.getKey()).orElseThrow(()->new RuntimeException("없으요")).getName()) + .candidateId(entry.getKey()) + .rate((int) ((double) entry.getValue() / totalVotes * 100)) + .selection(entry.getValue()) + .build()) + .collect(Collectors.toList()); + + // 선택률이 가장 높은 후보 찾기 + Optional highestSelectionCandidate = candidateInfos.stream() + .max(Comparator.comparingInt(ParentPollDTO::getRate)); + + // 1등 후보의 정보를 ParentPollDTO 객체로 반환 + if (highestSelectionCandidate.isPresent()) { + parentPollDTO = highestSelectionCandidate.get(); + // 1등 후보 이름, 사진 반환 + Long id = parentPollDTO.getCandidateId(); + //1등이 있는 경우만 포함. + Optional name = parentPost.getGeneralPoll().getCandidateList().stream() + .filter(candidate -> candidate.getId().equals(id)) + .map(Candidate::getName) + .findFirst(); + String candidateName = name.get(); + String candidateImage = parentPollDTO.getCandidateImage(); + + + parentPollDTO.setCandidateName(candidateName); + parentPollDTO.setCandidateImage(candidateImage); + + } + + + return ParentPostDTO.builder() + .postId(parentPost.getId()) + .postVoteType(parentPost.getVoteType()) + .nickname(parentPost.getUser().getNickname()) + .userImg(userImg) + .title(parentPost.getTitle()) + .content(parentPost.getContent()) + .pollOption(pollOptionDTOList) + .pollContent(parentPollDTO) + .like(parentLikes) + .comment(parentComments) + .build(); + } + + List pollOptionDTOList = parentPost.getCardPoll().getCandidateList().stream() + .map(PostConverter::toPollOptionResDTO).collect(Collectors.toList()); + + // 총 투표수 계산 + long totalVotes = parentPost.getCardPoll().getCardVoteList().stream() + .flatMap(vote -> vote.getSelect_list().stream()) + .count(); + + // 각 후보별 선택된 횟수 계산 + Map candidateSelectionCounts = parentPost.getCardPoll().getCardVoteList().stream() + .flatMap(vote -> vote.getSelect_list().stream()) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + + // 선택률 -> ParentPollDTO 객체 리스트로 변환 + List candidateInfos = candidateSelectionCounts.entrySet().stream() + .map(entry -> ParentPollDTO.builder() + .candidateId(entry.getKey()) + .rate((int) ((double) entry.getValue() / totalVotes * 100)) + .selection(entry.getValue()) + .build()) + .collect(Collectors.toList()); + + // 선택률이 가장 높은 후보 찾기 + Optional highestSelectionCandidate = candidateInfos.stream() + .max(Comparator.comparingInt(ParentPollDTO::getRate)); + //1등이 있는 경우만 + if (highestSelectionCandidate.isPresent()) { + // 1등 후보의 정보를 ParentPollDTO 객체로 반환 + parentPollDTO = highestSelectionCandidate.get(); + + // 1등 후보 이름, 사진 반환 + Long id = parentPollDTO.getCandidateId(); + Optional name = parentPost.getCardPoll().getCandidateList().stream() + .filter(candidate -> candidate.getId().equals(id)) + .map(Candidate::getName) + .findFirst(); + String candidateName = name.get(); + + String candidateImage = parentPollDTO.getCandidateImage(); + parentPollDTO.setCandidateName(candidateName); + parentPollDTO.setCandidateImage(candidateImage); + } + + + return ParentPostDTO.builder() + .postId(parentPost.getId()) + .postVoteType(parentPost.getVoteType()) + .nickname(parentPost.getUser().getNickname()) + .userImg(userImg) + .title(parentPost.getTitle()) + .content(parentPost.getContent()) + .pollOption(pollOptionDTOList) + .pollContent(parentPollDTO) + .like(parentLikes) + .comment(parentComments) + .build(); + + } + + public static Post toPost(PostRequestDTO.AddPostDTO request) { + PostType postType = null; + PostVoteType postVoteType = null; + + switch (request.getPostType()) { + case 1: + postType = PostType.VOTE; + break; + case 2: + postType = PostType.REVIEW; + break; + default: + break; + } + + if (postType == VOTE) { + switch (request.getPostVoteType()) { + case 1: + postVoteType = PostVoteType.GENERAL; + break; + case 2: + postVoteType = PostVoteType.GAUGE; + break; + case 3: + postVoteType = PostVoteType.CARD; + break; + default: + break; + } + } + + + return Post.builder() + .title(request.getTitle()) + .content(request.getContent()) + .postType(postType) + .voteType(postVoteType) + .point(request.getPoint()) + .view(0) + .state(PostState.POSTING) + .build(); + } + + + //글 상세 보기 + public static PostResponseDTO.PostDetailResponse postDetailResponse(Post post, Boolean engage, Long userId, Post parentPost) { + Integer likeCount = post.getPostLikeList().size(); + Integer commentCount = post.getCommentList().size(); + Boolean myPost = false; + List userChoiceList = null; + List topCandidateList = null; + List userVotePercent = null; + List topCandidatePercent = null; + List allCandidatePercent = null; + List allCandidateResult=null; + List userVoteResult = null; + List topVoteResult = null; + Integer userGauge = null; + Integer totalGauge = null; + Boolean isVote = false; + Boolean isLike = !post.getPostLikeList().stream().filter(like -> like.getUser().getId().equals(userId)).collect(Collectors.toList()).isEmpty(); + Boolean isComment = !post.getCommentList().stream().filter(like -> like.getUser().getId().equals(userId)).collect(Collectors.toList()).isEmpty(); + if (post.getUser().getId().equals(userId)) { + myPost = true; + } + File userFile = null; + String userImg = null; + Optional userFileOptional = Optional.ofNullable(post.getUser().getFile()); + + if (userFileOptional.isPresent()) { + userFile = userFileOptional.get(); + userImg = userFile.getUrl(); + } +//리뷰글 + if (post.getPostType() == REVIEW) { + return PostResponseDTO.PostDetailResponse.builder() + .postType(REVIEW) + .postVoteType(null) + .nickname(post.getUser().getNickname()) + .userImg(userImg) + .createdAt(post.getCreatedAt()) + .title(post.getTitle()) + .content(post.getContent()) + .file(FileConverter.toFileDTO(post.getFileList())) + .parentPost(toParentPostDTO(parentPost)) + .view(post.getView()) + .like(likeCount) + .comment(commentCount) + .isLike(isLike) + .isComment(isComment) + .myPost(myPost) + .build(); + } +//게이지 투표 + if (post.getVoteType() == PostVoteType.GAUGE) { + //나노초 단위로 마감 여부 확인 + + LocalDateTime now = LocalDateTime.now(); + long nanosUntilDeadline = ChronoUnit.NANOS.between(now, post.getGaugePoll().getDeadline()); + Boolean voteOnGoing = nanosUntilDeadline > 0; + //투표에 참여 했을 경우 + if (engage) { + Optional userGaugeVoteOptional = post.getGaugePoll().getGaugeVoteList().stream() + .filter(gaugeVote -> gaugeVote.getUser().getId().equals(userId)) + .findFirst(); + if (userGaugeVoteOptional.isPresent()) { + userGauge = userGaugeVoteOptional.get().getValue(); + } + isVote = true; + totalGauge = post.getGaugePoll().getGauge(); + } + if (post.getGaugePoll().getDeadline().isBefore(LocalDateTime.now()) || myPost) { + totalGauge = post.getGaugePoll().getGauge(); + } + return PostResponseDTO.PostDetailResponse.builder() + .postType(VOTE) + .postVoteType(PostVoteType.GAUGE) + .nickname(post.getUser().getNickname()) + .userImg(userImg) + .createdAt(post.getCreatedAt()) + .title(post.getTitle()) + .content(post.getContent()) + .OnGoing(voteOnGoing) + .isVoted(isVote) + .file(FileConverter.toFileDTO(post.getFileList())) + .pollTitle(post.getGaugePoll().getPollTitle()) + .userGauge(userGauge) + .totalGauge(totalGauge) + .point(post.getPoint()) + .deadline(post.getGaugePoll().getDeadline()) + .view(post.getView()) + .like(likeCount) + .comment(commentCount) + .isLike(isLike) + .isComment(isComment) + .myPost(myPost) + .build(); + } +//일반 투표 + if (post.getVoteType() == PostVoteType.GENERAL) { + //나노초 단위로 마감 여부 확인 + LocalDateTime now = LocalDateTime.now(); + long nanosUntilDeadline = ChronoUnit.NANOS.between(now, post.getGeneralPoll().getDeadline()); + Boolean voteOnGoing = nanosUntilDeadline > 0; + + //투표 후보 리스트 + List pollOptionDTOList = post.getGeneralPoll().getCandidateList().stream() + .map(PostConverter::toPollOptionResDTO).collect(Collectors.toList()); + + // 총 투표수 계산 + long totalVotes = post.getGeneralPoll().getGeneralVoteList().stream() + .flatMap(vote -> vote.getSelect_list().stream()) + .count(); + + // 각 후보별 선택된 횟수 계산 + Map candidateSelectionCounts = post.getGeneralPoll().getGeneralVoteList().stream() + .flatMap(vote -> vote.getSelect_list().stream()) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + allCandidatePercent = post.getGeneralPoll().getCandidateList().stream() + .map(candidate -> { + Long candidateId = candidate.getId(); + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return (int) ((double) selectionCount / totalVotes * 100); + }) + .collect(Collectors.toList()); + allCandidateResult = post.getGeneralPoll().getCandidateList().stream() + .map(candidate -> { + Long candidateId = candidate.getId(); + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return String.format("%d/%d", selectionCount, totalVotes); + }) + .collect(Collectors.toList()); + if (engage) { + // 사용자가 투표한 후보의 ID + List userSelectedCandidateIds = post.getGeneralPoll().getGeneralVoteList().stream() + .filter(vote -> vote.getUser().getId().equals(userId)) + .flatMap(vote -> vote.getSelect_list().stream()) + .collect(Collectors.toList()); + + Set selectedOptionIds = post.getGeneralPoll().getGeneralVoteList().stream() + .filter(generalVote -> generalVote.getUser().getId().equals(userId)) + .flatMap(generalVote -> generalVote.getSelect_list().stream()) + .collect(Collectors.toSet()); + + userChoiceList = post.getGeneralPoll().getCandidateList().stream() + .filter(candidate -> selectedOptionIds.contains(candidate.getId())) + .map(PostConverter::toPollOptionResDTO) + .collect(Collectors.toList()); + // 사용자가 선택한 후보의 선택률 계산 + userVotePercent = userSelectedCandidateIds.stream() + .map(candidateId -> { + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return (int) ((double) selectionCount / totalVotes * 100); + }) + .collect(Collectors.toList()); + + // 사용자가 선택한 후보의 투표 결과 정보 계산 (선택 인원/총 인원) + userVoteResult = userSelectedCandidateIds.stream() + .map(candidateId -> { + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return String.format("%d/%d", selectionCount, totalVotes); + }) + .collect(Collectors.toList()); + isVote = true; + } + + // 투표수가 가장 많은 후보의 선택률 계산 + OptionalDouble hightestCandidate = candidateSelectionCounts.entrySet().stream() + .mapToDouble(aLong -> (double) aLong.getValue() / totalVotes) + .max(); + + // 선택률이 가장 높은 후보의 ID들 찾기 + List mostVotedCandidateIds = candidateSelectionCounts.entrySet().stream() + .filter(entry -> Double.compare(entry.getValue(), hightestCandidate.getAsDouble() * totalVotes) == 0) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + // 가장 높은 투표를 받은 후보들을 mostVotedCandidateIds에 담기 + topCandidateList = post.getGeneralPoll().getCandidateList().stream() + .filter(candidate -> mostVotedCandidateIds.contains(candidate.getId())) + .map(PostConverter::toPollOptionResDTO) + .collect(Collectors.toList()); + + topCandidatePercent = mostVotedCandidateIds.stream() + .map(candidateId -> { + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return (int) ((double) selectionCount / totalVotes * 100); + }) + .collect(Collectors.toList()); + + topVoteResult = mostVotedCandidateIds.stream() + .map(candidateId -> { + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return String.format("%d/%d", selectionCount, totalVotes); + }) + .collect(Collectors.toList()); + + return PostResponseDTO.PostDetailResponse.builder() + .OnGoing(voteOnGoing) + .isVoted(isVote) + .postType(VOTE) + .postVoteType(PostVoteType.GENERAL) + .nickname(post.getUser().getNickname()) + .userImg(userImg) + .createdAt(post.getCreatedAt()) + .title(post.getTitle()) + .content(post.getContent()) + .file(FileConverter.toFileDTO(post.getFileList())) + .pollTitle(post.getGeneralPoll().getPollTitle()) + .pollOption(pollOptionDTOList) + .topCandidate(topCandidateList) + .userVote(userChoiceList) + .userVotePercent(userVotePercent) + .userVoteResult(userVoteResult) + .allCandidateResult(allCandidateResult) + .topVoteResult(topVoteResult) + .topCandidatePercent(topCandidatePercent) + .allCandidatePercent(allCandidatePercent) + .point(post.getPoint()) + .deadline(post.getGeneralPoll().getDeadline()) + .view(post.getView()) + .like(likeCount) + .comment(commentCount) + .isLike(isLike) + .isComment(isComment) + .myPost(myPost) + .build(); + } +//카드 투표 상세 보기 + //나노초 단위로 마감 여부 확인 + LocalDateTime now = LocalDateTime.now(); + long nanosUntilDeadline = ChronoUnit.NANOS.between(now, post.getCardPoll().getDeadline()); + Boolean voteOnGoing = nanosUntilDeadline > 0; + + //투표 후보 리스트 + List pollOptionDTOList = post.getCardPoll().getCandidateList().stream() + .map(PostConverter::toPollOptionResDTO).collect(Collectors.toList()); + + // 총 투표수 계산 + long totalVotes = post.getCardPoll().getCardVoteList().stream() + .flatMap(vote -> vote.getSelect_list().stream()) + .count(); + + // 각 후보별 선택된 횟수 계산 + Map candidateSelectionCounts = post.getCardPoll().getCardVoteList().stream() + .flatMap(vote -> vote.getSelect_list().stream()) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + allCandidatePercent = post.getCardPoll().getCandidateList().stream() + .map(candidate -> { + Long candidateId = candidate.getId(); + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return (int) ((double) selectionCount / totalVotes * 100); + }) + .collect(Collectors.toList()); + allCandidateResult = post.getCardPoll().getCandidateList().stream() + .map(candidate -> { + Long candidateId = candidate.getId(); + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return String.format("%d/%d", selectionCount, totalVotes); + }) + .collect(Collectors.toList()); + if (engage) { + // 사용자가 투표한 후보의 ID + List userSelectedCandidateIds = post.getCardPoll().getCardVoteList().stream() + .filter(vote -> vote.getUser().getId().equals(userId)) + .flatMap(vote -> vote.getSelect_list().stream()) + .collect(Collectors.toList()); + + Set selectedOptionIds = post.getCardPoll().getCardVoteList().stream() + .filter(cardVote -> cardVote.getUser().getId().equals(userId)) + .flatMap(cardVote -> cardVote.getSelect_list().stream()) + .collect(Collectors.toSet()); + + userChoiceList = post.getCardPoll().getCandidateList().stream() + .filter(candidate -> selectedOptionIds.contains(candidate.getId())) + .map(PostConverter::toPollOptionResDTO) + .collect(Collectors.toList()); + // 사용자가 선택한 후보의 선택률 계산 + userVotePercent = userSelectedCandidateIds.stream() + .map(candidateId -> { + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return (int) ((double) selectionCount / totalVotes * 100); + }) + .collect(Collectors.toList()); + + // 사용자가 선택한 후보의 투표 결과 정보 계산 (선택 인원/총 인원) + userVoteResult = userSelectedCandidateIds.stream() + .map(candidateId -> { + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return String.format("%d/%d", selectionCount, totalVotes); + }) + .collect(Collectors.toList()); + isVote = true; + } + + // 투표수가 가장 많은 후보의 선택률 계산 + OptionalDouble hightestCandidate = candidateSelectionCounts.entrySet().stream() + .mapToDouble(aLong -> (double) aLong.getValue() / totalVotes) + .max(); + + // 선택률이 가장 높은 후보의 ID들 찾기 + List mostVotedCandidateIds = candidateSelectionCounts.entrySet().stream() + .filter(entry -> Double.compare(entry.getValue(), hightestCandidate.getAsDouble() * totalVotes) == 0) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + // 가장 높은 투표를 받은 후보들을 mostVotedCandidateIds에 담기 + topCandidateList = post.getCardPoll().getCandidateList().stream() + .filter(candidate -> mostVotedCandidateIds.contains(candidate.getId())) + .map(PostConverter::toPollOptionResDTO) + .collect(Collectors.toList()); + + topCandidatePercent = mostVotedCandidateIds.stream() + .map(candidateId -> { + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return (int) ((double) selectionCount / totalVotes * 100); + }) + .collect(Collectors.toList()); + + topVoteResult = mostVotedCandidateIds.stream() + .map(candidateId -> { + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return String.format("%d/%d", selectionCount, totalVotes); + }) + .collect(Collectors.toList()); + + return PostResponseDTO.PostDetailResponse.builder() + .OnGoing(voteOnGoing) + .isVoted(isVote) + .postType(VOTE) + .postVoteType(PostVoteType.CARD) + .nickname(post.getUser().getNickname()) + .userImg(userImg) + .createdAt(post.getCreatedAt()) + .title(post.getTitle()) + .content(post.getContent()) + .file(FileConverter.toFileDTO(post.getFileList())) + .pollTitle(post.getCardPoll().getPollTitle()) + .pollOption(pollOptionDTOList) + .topCandidate(topCandidateList) + .userVote(userChoiceList) + .userVotePercent(userVotePercent) + .userVoteResult(userVoteResult) + .allCandidateResult(allCandidateResult) + .topVoteResult(topVoteResult) + .topCandidatePercent(topCandidatePercent) + .allCandidatePercent(allCandidatePercent) + .point(post.getPoint()) + .deadline(post.getCardPoll().getDeadline()) + .view(post.getView()) + .like(likeCount) + .comment(commentCount) + .isLike(isLike) + .isComment(isComment) + .myPost(myPost) + .build(); + } + + //전체 보기 + public static PostResponseDTO.PollPostGetResponse pollPostGetResponse(Post post, Long userId) { + File userFile = null; + String userImg = null; + Optional userFileOptional = Optional.ofNullable(post.getUser().getFile()); + List userVotePercent = null; + List topCandidatePercent = null; + List allCandidatePercent = null; + List topVoteResult = null; + List allCandidateResult=null; + Boolean myPost=false; + if (post.getUser().getId().equals(userId)) { + myPost = true; + } + + + if (userFileOptional.isPresent()) { + userFile = userFileOptional.get(); + userImg = userFile.getUrl(); + } + + Integer likeCount = post.getPostLikeList().size(); + Integer commentCount = post.getCommentList().size(); + List userChoiceList = null; + List topCandidateList = null; + Boolean isLike = !post.getPostLikeList().stream().filter(like -> like.getUser().getId().equals(userId)).collect(Collectors.toList()).isEmpty(); + Boolean isComment = !post.getCommentList().stream().filter(like -> like.getUser().getId().equals(userId)).collect(Collectors.toList()).isEmpty(); + Boolean engage = false; + Boolean isVoted = false; + LocalDateTime now = LocalDateTime.now(); + //일반 투표 + if (post.getVoteType() == PostVoteType.GENERAL) { + if (!post.getGeneralPoll().getGeneralVoteList().stream().filter(cardVote -> cardVote.getUser().getId().equals(userId)).collect(Collectors.toList()).isEmpty()) { + engage = true; + } + long nanosUntilDeadline = ChronoUnit.NANOS.between(now, post.getGeneralPoll().getDeadline()); + Boolean voteOnGoing = nanosUntilDeadline > 0; + + List pollOptionDTOList = post.getGeneralPoll().getCandidateList().stream() + .map(PostConverter::toPollOptionResDTO).collect(Collectors.toList()); + //투표한 후보에 대한 정보 + // 총 투표수 계산 + long totalVotes = post.getGeneralPoll().getGeneralVoteList().stream() + .flatMap(vote -> vote.getSelect_list().stream()) + .count(); + // 각 후보별 선택된 횟수 계산 + Map candidateSelectionCounts = post.getGeneralPoll().getGeneralVoteList().stream() + .flatMap(vote -> vote.getSelect_list().stream()) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + //1등 투표 계산 + // 투표수가 가장 많은 후보의 선택률 계산 + OptionalDouble hightestCandidate = candidateSelectionCounts.entrySet().stream() + .mapToDouble(entry -> (double) entry.getValue() / totalVotes) + .max(); + + // 선택률이 가장 높은 후보의 ID들 찾기 + List mostVotedCandidateIds = candidateSelectionCounts.entrySet().stream() + .filter(entry -> entry.getValue() == hightestCandidate.getAsDouble() * totalVotes) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + // 가장 높은 투표를 받은 후보들을 topCandidateList에 담기 + topCandidateList = post.getGeneralPoll().getCandidateList().stream() + .filter(candidate -> mostVotedCandidateIds.contains(candidate.getId())) + .map(PostConverter::toPollOptionResDTO) + .collect(Collectors.toList()); + + topCandidatePercent = mostVotedCandidateIds.stream() + .map(candidateId -> { + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return (int) ((double) selectionCount / totalVotes * 100); + }) + .collect(Collectors.toList()); + allCandidatePercent = post.getGeneralPoll().getCandidateList().stream() + .map(candidate -> { + Long candidateId = candidate.getId(); + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return (int) ((double) selectionCount / totalVotes * 100); + }) + .collect(Collectors.toList()); + allCandidateResult = post.getGeneralPoll().getCandidateList().stream() + .map(candidate -> { + Long candidateId = candidate.getId(); + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return String.format("%d/%d", selectionCount, totalVotes); + }) + .collect(Collectors.toList()); + + topVoteResult = mostVotedCandidateIds.stream() + .map(candidateId -> { + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return String.format("%d/%d", selectionCount, totalVotes); + }) + .collect(Collectors.toList()); + + if (engage) { + // 사용자가 투표한 후보의 ID + List userSelectedCandidateIds = post.getGeneralPoll().getGeneralVoteList().stream() + .filter(vote -> vote.getUser().getId().equals(userId)) + .flatMap(vote -> vote.getSelect_list().stream()) + .collect(Collectors.toList()); + Set selectedOptionIds = post.getGeneralPoll().getGeneralVoteList().stream() + .filter(cardVote -> cardVote.getUser().getId().equals(userId)) + .flatMap(cardVote -> cardVote.getSelect_list().stream()) + .collect(Collectors.toSet()); + userChoiceList = post.getGeneralPoll().getCandidateList().stream() + .filter(candidate -> selectedOptionIds.contains(candidate.getId())) + .map(PostConverter::toPollOptionResDTO) + .collect(Collectors.toList()); + // 사용자가 선택한 후보의 선택률 계산 + userVotePercent = userSelectedCandidateIds.stream() + .map(candidateId -> { + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return (int) ((double) selectionCount / totalVotes * 100); + }) + .collect(Collectors.toList()); + isVoted = true; + } + + return PostResponseDTO.PollPostGetResponse.builder() + .myPost(myPost) + .onGoing(voteOnGoing) + .isVoted(isVoted) + .postId(post.getId()) + .postVoteType(PostVoteType.GENERAL) + .nickname(post.getUser().getNickname()) + .userImg(userImg) + .title(post.getTitle()) + .content(post.getContent()) + .uploadDate(post.getCreatedAt()) + .pollOption(pollOptionDTOList) + .topCandidate(topCandidateList) + .userVote(userChoiceList) + .userVotePercent(userVotePercent) + .topCandidatePercent(topCandidatePercent) + .allCandidatePercent(allCandidatePercent) + .allCandidateResult(allCandidateResult) + .topVoteResult(topVoteResult) + .like(likeCount) + .comment(commentCount) + .isLike(isLike) + .isComment(isComment) + .build(); + + } + //게이지 투표 + if (post.getVoteType() == PostVoteType.GAUGE) { + if (!post.getGaugePoll().getGaugeVoteList().stream().filter(cardVote -> cardVote.getUser().getId().equals(userId)).collect(Collectors.toList()).isEmpty()) { + engage = true; + } + long nanosUntilDeadline = ChronoUnit.NANOS.between(now, post.getGaugePoll().getDeadline()); + Boolean voteOnGoing = nanosUntilDeadline > 0; + Integer userGauge = null; + Optional userGaugeVoteOptional = post.getGaugePoll().getGaugeVoteList().stream() + .filter(gaugeVote -> gaugeVote.getUser().getId().equals(userId)) + .findFirst(); + if (userGaugeVoteOptional.isPresent()) { + userGauge = userGaugeVoteOptional.get().getValue(); + } + return PostResponseDTO.PollPostGetResponse.builder() + .myPost(myPost) + .onGoing(voteOnGoing) + .isVoted(engage) + .postId(post.getId()) + .postVoteType(PostVoteType.GAUGE) + .nickname(post.getUser().getNickname()) + .userImg(userImg) + .title(post.getTitle()) + .content(post.getContent()) + .uploadDate(post.getCreatedAt()) + .pollTitle(post.getGaugePoll().getPollTitle()) + .userGauge(userGauge) + .totalGauge(post.getGaugePoll().getGauge()) + .like(likeCount) + .comment(commentCount) + .isLike(isLike) + .isComment(isComment) + .build(); + } + +//카드 투표 + if (!post.getCardPoll().getCardVoteList().stream().filter(cardVote -> cardVote.getUser().getId().equals(userId)).collect(Collectors.toList()).isEmpty()) { + engage = true; + } + long nanosUntilDeadline = ChronoUnit.NANOS.between(now, post.getCardPoll().getDeadline()); + Boolean voteOnGoing = nanosUntilDeadline > 0; + + List pollOptionDTOList = post.getCardPoll().getCandidateList().stream() + .map(PostConverter::toPollOptionResDTO).collect(Collectors.toList()); + //투표한 후보에 대한 정보 + // 총 투표수 계산 + long totalVotes = post.getCardPoll().getCardVoteList().stream() + .flatMap(vote -> vote.getSelect_list().stream()) + .count(); + // 각 후보별 선택된 횟수 계산 + Map candidateSelectionCounts = post.getCardPoll().getCardVoteList().stream() + .flatMap(vote -> vote.getSelect_list().stream()) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + //1등 투표 계산 + // 투표수가 가장 많은 후보의 선택률 계산 + OptionalDouble hightestCandidate = candidateSelectionCounts.entrySet().stream() + .mapToDouble(entry -> (double) entry.getValue() / totalVotes) + .max(); + + // 선택률이 가장 높은 후보의 ID들 찾기 + List mostVotedCandidateIds = candidateSelectionCounts.entrySet().stream() + .filter(entry -> entry.getValue() == hightestCandidate.getAsDouble() * totalVotes) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + // 가장 높은 투표를 받은 후보들을 userChoiceList에 담기 + topCandidateList = post.getCardPoll().getCandidateList().stream() + .filter(candidate -> mostVotedCandidateIds.contains(candidate.getId())) + .map(PostConverter::toPollOptionResDTO) + .collect(Collectors.toList()); + + topCandidatePercent = mostVotedCandidateIds.stream() + .map(candidateId -> { + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return (int) ((double) selectionCount / totalVotes * 100); + }) + .collect(Collectors.toList()); + allCandidatePercent = post.getCardPoll().getCandidateList().stream() + .map(candidate -> { + Long candidateId = candidate.getId(); + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return (int) ((double) selectionCount / totalVotes * 100); + }) + .collect(Collectors.toList()); + allCandidateResult = post.getCardPoll().getCandidateList().stream() + .map(candidate -> { + Long candidateId = candidate.getId(); + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return String.format("%d/%d", selectionCount, totalVotes); + }) + .collect(Collectors.toList()); + topVoteResult = mostVotedCandidateIds.stream() + .map(candidateId -> { + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return String.format("%d/%d", selectionCount, totalVotes); + }) + .collect(Collectors.toList()); + + if (engage) { + // 사용자가 투표한 후보의 ID + List userSelectedCandidateIds = post.getCardPoll().getCardVoteList().stream() + .filter(vote -> vote.getUser().getId().equals(userId)) + .flatMap(vote -> vote.getSelect_list().stream()) + .collect(Collectors.toList()); + Set selectedOptionIds = post.getCardPoll().getCardVoteList().stream() + .filter(cardVote -> cardVote.getUser().getId().equals(userId)) + .flatMap(cardVote -> cardVote.getSelect_list().stream()) + .collect(Collectors.toSet()); + userChoiceList = post.getCardPoll().getCandidateList().stream() + .filter(candidate -> selectedOptionIds.contains(candidate.getId())) + .map(PostConverter::toPollOptionResDTO) + .collect(Collectors.toList()); + // 사용자가 선택한 후보의 선택률 계산 + userVotePercent = userSelectedCandidateIds.stream() + .map(candidateId -> { + long selectionCount = candidateSelectionCounts.getOrDefault(candidateId, 0L); + return (int) ((double) selectionCount / totalVotes * 100); + }) + .collect(Collectors.toList()); + + isVoted = true; + } + + return PostResponseDTO.PollPostGetResponse.builder() + .myPost(myPost) + .onGoing(voteOnGoing) + .isVoted(isVoted) + .postId(post.getId()) + .postVoteType(PostVoteType.CARD) + .nickname(post.getUser().getNickname()) + .userImg(userImg) + .title(post.getTitle()) + .content(post.getContent()) + .uploadDate(post.getCreatedAt()) + .pollOption(pollOptionDTOList) + .topCandidate(topCandidateList) + .userVote(userChoiceList) + .userVotePercent(userVotePercent) + .topCandidatePercent(topCandidatePercent) + .allCandidatePercent(allCandidatePercent) + .allCandidateResult(allCandidateResult) + .topVoteResult(topVoteResult) + .like(likeCount) + .comment(commentCount) + .isLike(isLike) + .isComment(isComment) + .build(); + + } + + public static PostResponseDTO.PollPostGetListDTO pollPostGetListDTO(Page postList, Long userId) { + List pollPostGetListDTO = postList.stream() + .map(post -> pollPostGetResponse(post, userId)).collect(Collectors.toList()); + return PostResponseDTO.PollPostGetListDTO.builder() + .pollPostList(pollPostGetListDTO) + .isEnd(postList.isLast()) + .build(); + } + + public static ParentPostDTO.CandidatePostDTO candidatePostDTO(Post post) { + Integer likeCount = post.getPostLikeList().size(); + Integer commentCount = post.getCommentList().size(); + return ParentPostDTO.CandidatePostDTO.builder() + .postId(post.getId()) + .title(post.getTitle()) + .content(post.getContent()) + .like(likeCount) + .comment(commentCount) + .createdAt(post.getCreatedAt()) + .build(); + } + + public static ParentPostDTO.ParentPostGetListDTO parentPostGetListDTO(Page postList, Long userId) { + List parentPostGetListDTO = postList.stream() + .map(post -> candidatePostDTO(post)).collect(Collectors.toList()); + return ParentPostDTO.ParentPostGetListDTO.builder() + .candidatePostDTOList(parentPostGetListDTO) + .isEnd(postList.isLast()) + .build(); + } + + public static PostResponseDTO.ReviewPostGetResponse reviewPostGetResponse(Post post, Long userId) { + Integer likeCount = post.getPostLikeList().size(); + Integer commentCount = post.getCommentList().size(); + Boolean isLike = !post.getPostLikeList().stream().filter(like -> like.getUser().getId().equals(userId)).collect(Collectors.toList()).isEmpty(); + Boolean isComment = !post.getCommentList().stream().filter(like -> like.getUser().getId().equals(userId)).collect(Collectors.toList()).isEmpty(); + File userFile = null; + String userImg = null; + Optional userFileOptional = Optional.ofNullable(post.getUser().getFile()); + Post parentPost = post.getParentPost(); + + if (userFileOptional.isPresent()) { + userFile = userFileOptional.get(); + userImg = userFile.getUrl(); + } + return PostResponseDTO.ReviewPostGetResponse.builder() + .postId(post.getId()) + .nickname(post.getUser().getNickname()) + .userImg(userImg) + .title(post.getTitle()) + .content(post.getContent()) + .ReviewPicList(FileConverter.toFileDTO(post.getFileList())) + .uploadDate(post.getCreatedAt()) + .like(likeCount) + .comment(commentCount) + .isLike(isLike) + .isComment(isComment) + .parentPostDTO(candidatePostDTO(parentPost)) + .build(); + } + + public static PostResponseDTO.ReviewPostGetListDTO reviewPostGetListDTO(Page postList, Long userId) { + List reviewPostGetListDTO = postList.stream() + .map(post -> reviewPostGetResponse(post, userId)).collect(Collectors.toList()); + return PostResponseDTO.ReviewPostGetListDTO.builder() + .reviewPostList(reviewPostGetListDTO) + .isEnd(postList.isLast()) + .build(); + } + + public static PostResponseDTO.MyPostDTO toMyPostResDTO(Post post) { + return PostResponseDTO.MyPostDTO.builder() + .postId(post.getId()) + .nickName(post.getUser().getNickname()) + .createdAt(post.getCreatedAt()) + .title(post.getTitle()) + .postLike(post.getPostLikeList().size()) + .comment(post.getCommentList().size()) + .build(); + } + + public static Post_like toPostLike(Post post, User user) { + return Post_like.builder() + .post(post) + .user(user) + .build(); + } + + public static PostResponseDTO.PostLikeRes toPostLikeRes(Post_like post_like) { + return PostResponseDTO.PostLikeRes.builder() + .post_like_id(post_like.getId()) + .build(); + } + + public static Post_scrap toPostScrap(Post post, User user) { + return Post_scrap.builder() + .post(post) + .user(user) + .build(); + } + + public static PostResponseDTO.ScrapCreateRes toScrapCreateRes(Post_scrap post_scrap) { + return PostResponseDTO.ScrapCreateRes.builder() + .post_scrap_id(post_scrap.getId()) + .build(); + } + + public static Report toReportPost(Post post, User user, ReportCategory reportCategory) { + return Report.builder() + .targetType(ReportType.POST) + .targetId(post.getId()) + .reportCategory(reportCategory) + .user(user) + .build(); + } + + public static PostResponseDTO.PostReportRes toPostReportRes(PostResponseDTO.ReportResult reportResult) { + return PostResponseDTO.PostReportRes.builder() + .reportId(reportResult.getReport().getId()) + .createdAt(reportResult.getReport().getCreatedAt()) + .duplicatedReport(reportResult.getDuplicatedReport()) + .build(); + } + + public static PostResponseDTO.PostSearchRes PostSearchResDTO(Post post,Long userId) { + Integer likeCount = post.getPostLikeList().size(); + Integer commentCount = post.getCommentList().size(); + Boolean isLike = !post.getPostLikeList().stream().filter(like -> like.getUser().getId().equals(userId)).collect(Collectors.toList()).isEmpty(); + Boolean isComment = !post.getCommentList().stream().filter(like -> like.getUser().getId().equals(userId)).collect(Collectors.toList()).isEmpty(); + File userFile = null; + String userImg = null; + Optional userFileOptional = Optional.ofNullable(post.getUser().getFile()); + + if (userFileOptional.isPresent()) { + userFile = userFileOptional.get(); + userImg = userFile.getUrl(); + } + return PostResponseDTO.PostSearchRes.builder() + .postId(post.getId()) + .nickname(post.getUser().getNickname()) + .userImg(userImg) + .title(post.getTitle()) + .content(post.getContent()) + .uploadDate(post.getCreatedAt()) + .like(likeCount) + .comment(commentCount) + .isLike(isLike) + .isComment(isComment) + .build(); + } + + public static PostResponseDTO.PostSearchList PostSearchListDTO(Page postList, Long userId) { + List postSearchLists = postList.stream() + .map(post -> PostSearchResDTO(post, userId)).collect(Collectors.toList()); + return PostResponseDTO.PostSearchList.builder() + .reviewPostList(postSearchLists) + .isEnd(postList.isLast()) + .build(); + } + + public static PostResponseDTO.RecentSearchRes toRecentSearchRes(List recentSearchLogs) { + List searchLogList = recentSearchLogs.stream() + .map(PostConverter::toSearchLog) // String을 SearchLog로 변환하였습니다. + .collect(Collectors.toList()); + + return PostResponseDTO.RecentSearchRes.builder() + .recentSearchList(searchLogList) + .build(); + } + + + public static PostResponseDTO.SearchLog toSearchLog(String searchLog){ + return PostResponseDTO.SearchLog.builder() + .name(searchLog) + .build(); + } +} + + diff --git a/src/main/java/friend/spring/converter/SseConverter.java b/src/main/java/friend/spring/converter/SseConverter.java new file mode 100644 index 0000000..2cba39f --- /dev/null +++ b/src/main/java/friend/spring/converter/SseConverter.java @@ -0,0 +1,42 @@ +package friend.spring.converter; + +import friend.spring.domain.Comment; +import friend.spring.domain.Post; +import friend.spring.domain.enums.AlarmType; +import friend.spring.web.dto.SseResponseDTO; + +public class SseConverter { + public static SseResponseDTO.CommentCreateResDTO toCommentCreateResDTO(Comment comment, AlarmType alarmType) { + String userPhoto = null; + if (comment.getUser().getFile() != null) { + userPhoto = comment.getUser().getFile().getUrl(); + } + + String alarmContent = comment.getUser().getNickname(); + if (alarmType.equals(AlarmType.COMMENT)) { + alarmContent += "님이 회원님의 게시물에 댓글을 남겼습니다."; + } else { + alarmContent += "님이 회원님의 댓글에 답글을 남겼습니다."; + } + + return SseResponseDTO.CommentCreateResDTO.builder() + .userNickname(comment.getUser().getNickname()) + .userPhoto(userPhoto) + .alarmContent(alarmContent) + .commentContent(comment.getContent()) + .createdAt(comment.getCreatedAt()) + .alarmType(alarmType.toString()) + .postId(comment.getPost().getId()) + .commentId(comment.getId()) + .build(); + } + + public static SseResponseDTO.VoteFinishResDTO toVoteFinishResDTO(Post post, AlarmType alarmType) { + return SseResponseDTO.VoteFinishResDTO.builder() + .postId(post.getId()) + .alarmContent(post.getContent()) + .alarmType(alarmType.toString()) + .createdAt(post.getCreatedAt()) + .build(); + } +} diff --git a/src/main/java/friend/spring/converter/UserConverter.java b/src/main/java/friend/spring/converter/UserConverter.java new file mode 100644 index 0000000..301404a --- /dev/null +++ b/src/main/java/friend/spring/converter/UserConverter.java @@ -0,0 +1,208 @@ +package friend.spring.converter; + +import friend.spring.domain.*; +import friend.spring.domain.enums.Gender; +import friend.spring.OAuth.KakaoProfile; +import friend.spring.web.dto.*; +import friend.spring.domain.enums.RoleType; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import friend.spring.web.dto.UserResponseDTO; +import org.springframework.data.domain.Page; + +import java.util.List; +import java.util.stream.Collectors; + + +public class UserConverter { + + + public static UserResponseDTO.MyPageResDTO toMypageResDTO(User user) { + String userPhoto = null; + if (user.getFile() != null) { + userPhoto = user.getFile().getUrl(); + } + + return UserResponseDTO.MyPageResDTO.builder() + .userPhoto(userPhoto) + .userName(user.getNickname()) + .userPoint(user.getPoint()) + .userLevelInt(user.getLevel().getLike() + 1) + .userLevelName(user.getLevel().getName()) + .userRecommend(user.getLike()) + .build(); + } + + + public static UserResponseDTO.PointViewDTO toPointViewResDTO(Integer point) { + return UserResponseDTO.PointViewDTO.builder() + .point(point) + .build(); + } + + + public static UserResponseDTO.EmailSendRes toEmailSendRes(String code) { + return UserResponseDTO.EmailSendRes.builder() + .code(code) + .build(); + } + + public static UserResponseDTO.JoinResultDTO joinResultDTO(User user) { + return UserResponseDTO.JoinResultDTO.builder() + .email(user.getEmail()) + .createAt(LocalDate.from(LocalDateTime.now())) + .build(); + + } + + + public static User toUser(UserRequestDTO.UserJoinRequest userJoinRequest, String pw, Level initLevel) { + + Gender gender = null; + + switch (userJoinRequest.getGender()) { + case 1: + gender = Gender.MALE; + break; + case 2: + gender = Gender.FEMALE; + break; + case 3: + gender = Gender.NONE; + break; + } + return User.builder() + .email(userJoinRequest.getEmail()) + .password(pw) + .nickname(userJoinRequest.getNickname()) + .gender(gender) + .phone(userJoinRequest.getPhone()) + .agree_info(userJoinRequest.isAgree_info()) + .agree_marketing(userJoinRequest.isAgree_marketing()) + .birth(userJoinRequest.getBirth()) + .kakao(userJoinRequest.getKakao()) + .is_deleted(userJoinRequest.is_deleted()) + .point(userJoinRequest.getPoint()) + .like(userJoinRequest.getLike()) + .role(RoleType.USER) + .level(initLevel) + .build(); + } + + //나의 프로필(Q&A질문) + public static UserResponseDTO.QuestionResDTO toQuestionResDTO(User user, Level nxtLevel, Page postList, Page commentList) { + //질문 목록 + List myPostDTOList = postList.stream() + .map(PostConverter::toMyPostResDTO).collect(Collectors.toList()); + + //답변 목록 + List myCommentDTOList = commentList.stream() + .map(CommentConverter::toMyCommentResDTO).collect(Collectors.toList()); + //총 답변수 + int all = myCommentDTOList.size(); + //채택 답변수 + List myCommentList = commentList.stream() + .filter(comment -> comment.getCommentChoiceList().isEmpty()).collect(Collectors.toList()); + //답변 채택률 + int aChoice = all - myCommentList.size(); + double aChoicePercent = ((double) aChoice / (double) all) * 100; + + //질문 채택률 + int Question = myPostDTOList.size(); + List myPostList = postList.stream() + .filter(post -> post.getCommentChoiceList().isEmpty()).collect(Collectors.toList()); + double pChoicePercent = ((double) (Question - myPostList.size()) / (double) Question) * 100; + + // 유저 프로필 + String userPhoto = null; + if (user.getFile() != null) { + userPhoto = user.getFile().getUrl(); + } + + //남은 다음 등급 + double nxtGrade = ((double) user.getLike() / (double) (nxtLevel.getLike() - user.getLevel().getLike())) * 100.0; + return UserResponseDTO.QuestionResDTO.builder() + .userPhoto(userPhoto) + .nickName(user.getNickname()) + .recommend(user.getLike()) + .grade(user.getLevel().getName()) + .nextGrade(nxtGrade) + .nextGradeName(nxtLevel.getName()) + .adoptComments(aChoice) + .adoptCommentPercent(aChoicePercent) + .postNum(myPostDTOList.size()) + .adoptPostPercent(pChoicePercent) + .postList(myPostDTOList) + .build(); + } + + //나의 프로필(Q&A답변) + public static UserResponseDTO.AnswerResDTO toAnswerResDTO(User user, Level nxtLevel, Page commentList) { + //답변 목록 + List myCommentDTOList = commentList.stream() + .map(CommentConverter::toMyCommentResDTO).collect(Collectors.toList()); + //총 답변수 + int all = myCommentDTOList.size(); + + //채택 답변수 + List myCommentList = commentList.stream() + .filter(comment -> comment.getCommentChoiceList().isEmpty()).collect(Collectors.toList()); + int choice = all - myCommentList.size(); + //채택 답변률 + double percent = ((double) choice / (double) all) * 100.0; + + // 유저 프로필 + String userPhoto = null; + if (user.getFile() != null) { + userPhoto = user.getFile().getUrl(); + } + + //남은 다음 등급 + double nxtGrade = ((double) user.getLike() / (double) (nxtLevel.getLike() - user.getLevel().getLike())) * 100.0; + return UserResponseDTO.AnswerResDTO.builder() + .userPhoto(userPhoto) + .nickName(user.getNickname()) + .recommend(user.getLike()) + .grade(user.getLevel().getName()) + .nextGrade(nxtGrade) + .nextGradeName(nxtLevel.getName()) + .adoptComments(choice) + .adoptCommentPercent(percent) + .commentList(myCommentDTOList) + .build(); + } + + public static UserResponseDTO.UserSummaryInfo toUserSummaryInfo(User user) { + // 유저 프로필 + String userPhoto = null; + if (user.getFile() != null) { + userPhoto = user.getFile().getUrl(); + } + + return UserResponseDTO.UserSummaryInfo.builder() + .user_id(user.getId()) + .nickname(user.getNickname()) + .image(userPhoto) + .build(); + } + + public static UserResponseDTO.OAuthResponse toOAuthResponse( + TokenDTO accessToken, TokenDTO refreshToken, Boolean isLogin, User user) { + return UserResponseDTO.OAuthResponse.builder() + .refreshToken(refreshToken) + .accessToken(accessToken) + .isLogin(isLogin) + .email(user.getEmail()) + .build(); + } + + public static User KakaoUser( + KakaoProfile kakaoProfile) { + return User.builder() + .email(kakaoProfile.getKakao_account().getEmail()).build(); + + } + +} diff --git a/src/main/java/friend/spring/converter/VoteConverter.java b/src/main/java/friend/spring/converter/VoteConverter.java new file mode 100644 index 0000000..7d2d667 --- /dev/null +++ b/src/main/java/friend/spring/converter/VoteConverter.java @@ -0,0 +1,59 @@ +package friend.spring.converter; + +import friend.spring.domain.Card_vote; +import friend.spring.domain.Gauge_vote; +import friend.spring.domain.General_vote; +import friend.spring.domain.Post; +import friend.spring.domain.enums.PostState; +import friend.spring.domain.enums.PostType; +import friend.spring.domain.enums.PostVoteType; +import friend.spring.web.dto.PostRequestDTO; +import friend.spring.web.dto.PostResponseDTO; +import friend.spring.web.dto.VoteRequestDTO; +import friend.spring.web.dto.VoteResponseDTO; + +import java.time.LocalDateTime; + +import static friend.spring.domain.enums.PostType.VOTE; + +public class VoteConverter { + public static VoteResponseDTO.GeneralVoteResponseDTO toAddGeneralVoteResultDTO(General_vote generalVote) { + return VoteResponseDTO.GeneralVoteResponseDTO.builder() + .generalVoteId(generalVote.getId()) + .createdAt(LocalDateTime.now()) + .build(); + } + + public static VoteResponseDTO.GaugeVoteResponseDTO toAddGaugelVoteResultDTO(Gauge_vote gaugeVote) { + return VoteResponseDTO.GaugeVoteResponseDTO.builder() + .gaugeVoteId(gaugeVote.getId()) + .createdAt(LocalDateTime.now()) + .build(); + } + + public static VoteResponseDTO.CardVoteResponseDTO toAddCardVoteResultDTO(Card_vote cardVote) { + return VoteResponseDTO.CardVoteResponseDTO.builder() + .cardVoteId(cardVote.getId()) + .createdAt(LocalDateTime.now()) + .build(); + } + + public static General_vote toGeneralVote(VoteRequestDTO.GeneralVoteRequestDTO request) { + + return General_vote.builder() + .build(); + } + + public static Gauge_vote toGaugeVote(VoteRequestDTO.GaugeVoteRequestDTO request) { + + return Gauge_vote.builder() + .value(request.getValue()) + .build(); + } + + public static Card_vote toCardVote(VoteRequestDTO.CardVoteRequestDTO request) { + + return Card_vote.builder() + .build(); + } +} diff --git a/src/main/java/friend/spring/domain/Alarm.java b/src/main/java/friend/spring/domain/Alarm.java new file mode 100644 index 0000000..883ca40 --- /dev/null +++ b/src/main/java/friend/spring/domain/Alarm.java @@ -0,0 +1,44 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import friend.spring.domain.enums.AlarmType; +import lombok.*; + +import javax.persistence.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Alarm extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(EnumType.STRING) + @Column + private AlarmType type; + + @Column(nullable = false, length = 100) + private String content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id") + private Post post; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "comment_id") + private Comment comment; + + @Column(nullable = true) + private Boolean read; // 해당 알림 읽었는지 여부 + + public void setRead(Boolean read) { + this.read = read; + } +} diff --git a/src/main/java/friend/spring/domain/Candidate.java b/src/main/java/friend/spring/domain/Candidate.java new file mode 100644 index 0000000..d2cab24 --- /dev/null +++ b/src/main/java/friend/spring/domain/Candidate.java @@ -0,0 +1,57 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import lombok.*; +import org.hibernate.Hibernate; + +import javax.persistence.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Candidate extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "generalPoll_id") + private General_poll generalPoll; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "cardPoll_id") + private Card_poll cardPoll; + + @OneToOne(mappedBy = "candidate") + @JoinColumn(name = "file_id") + private File file; + + public void setGeneralPoll(General_poll generalPoll) { + this.generalPoll = generalPoll; + if (this.generalPoll != null) { + Hibernate.initialize(this.generalPoll); + if (this.generalPoll.getCandidateList() != null) { + this.generalPoll.getCandidateList().add(this); + } + } + } + + public void setCardPoll(Card_poll cardPoll) { + this.cardPoll = cardPoll; + if (this.cardPoll != null) { + Hibernate.initialize(this.cardPoll); + if (this.cardPoll.getCandidateList() != null) { + this.cardPoll.getCandidateList().add(this); + } + } + } + + public void setFile(File file) { + this.file = file; + } +} diff --git a/src/main/java/friend/spring/domain/Card_poll.java b/src/main/java/friend/spring/domain/Card_poll.java new file mode 100644 index 0000000..d2e6d41 --- /dev/null +++ b/src/main/java/friend/spring/domain/Card_poll.java @@ -0,0 +1,64 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Card_poll extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 30) + private String pollTitle; + + @Column(nullable = true) + @Builder.Default + private Boolean multipleChoice = true; + + @Column(nullable = true) + @Builder.Default + private LocalDateTime deadline = LocalDateTime.now().plusHours(1); // 디폴트 시간 1시간 설정. + + @Column(nullable = false) + @Builder.Default + private Boolean VoteOnGoing = true; + + @Builder.Default + @OneToMany(mappedBy = "cardPoll", cascade = CascadeType.ALL) + private List cardVoteList = new ArrayList<>(); + + @OneToOne(mappedBy = "cardPoll", cascade = CascadeType.ALL) + @JoinColumn(name = "post_id") + private Post post; + + @Builder.Default + @OneToMany(mappedBy = "cardPoll", cascade = CascadeType.ALL) + private List candidateList = new ArrayList<>(); + + public void setPost(Post post) { + this.post = post; + } + + public void setDeadline(LocalDateTime deadline) { + this.deadline = deadline; + } + + public void setVoteOnGoing(Boolean voteOnGoing) { + this.VoteOnGoing = voteOnGoing; + } + + public void setMultipleChoice(Boolean multipleChoice) { + this.multipleChoice = multipleChoice; + } +} diff --git a/src/main/java/friend/spring/domain/Card_vote.java b/src/main/java/friend/spring/domain/Card_vote.java new file mode 100644 index 0000000..7c81807 --- /dev/null +++ b/src/main/java/friend/spring/domain/Card_vote.java @@ -0,0 +1,54 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Card_vote extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ElementCollection + @Builder.Default + @Column(nullable = true, length = 100) + private List select_list = new ArrayList(); + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "card_poll_id") + private Card_poll cardPoll; + + public void setSelect_list(List select_list) { + if (select_list != null) { + this.select_list = new ArrayList(select_list); + } else { + this.select_list.clear(); + } + } + + public void setCardPoll(Card_poll cardPoll) { + if (this.cardPoll != null) + cardPoll.getCardVoteList().remove(this); + this.cardPoll = cardPoll; + cardPoll.getCardVoteList().add(this); + } + + public void setUser(User user) { + if (this.user != null) + user.getCardVoteList().remove(this); + this.user = user; + user.getCardVoteList().add(this); + } +} diff --git a/src/main/java/friend/spring/domain/Category.java b/src/main/java/friend/spring/domain/Category.java new file mode 100644 index 0000000..f340f23 --- /dev/null +++ b/src/main/java/friend/spring/domain/Category.java @@ -0,0 +1,29 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Category extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + @Builder.Default + @OneToMany(mappedBy = "category") + private List postList = new ArrayList<>(); + +} + diff --git a/src/main/java/friend/spring/domain/Comment.java b/src/main/java/friend/spring/domain/Comment.java new file mode 100644 index 0000000..52d8ef9 --- /dev/null +++ b/src/main/java/friend/spring/domain/Comment.java @@ -0,0 +1,68 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import friend.spring.domain.enums.CommentState; +import friend.spring.domain.enums.PostState; +import friend.spring.domain.mapping.Comment_choice; +import friend.spring.domain.mapping.Comment_like; +import lombok.*; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Comment extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 500) + private String content; + + @Enumerated(EnumType.STRING) + @Column + private CommentState state; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id") + private Post post; + + // 부모 댓글 정의 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id") + private Comment parentComment; + + // 자식 댓글 정의 + @Builder.Default + @OneToMany(mappedBy = "parentComment") + private List subCommentList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "comment") + private List commentChoiceList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "comment") + private List commentLikeList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "comment") + private List alarmList = new ArrayList<>(); + + public void update(String content) { + this.content = content; + } + + public void updateStateToDeleted() { + this.state = CommentState.DELETED; + } +} diff --git a/src/main/java/friend/spring/domain/File.java b/src/main/java/friend/spring/domain/File.java new file mode 100644 index 0000000..6897136 --- /dev/null +++ b/src/main/java/friend/spring/domain/File.java @@ -0,0 +1,36 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; + +@Entity +@Builder +@Getter +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class File extends BaseEntity { // S3에 저장한 이미지 파일 링크들 관리 + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(unique = true) + private String url; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumn(name = "post_id") + private Post post; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "candidate_id") + private Candidate candidate; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + public void setUrl(String imageUrl) { + this.url = imageUrl; + } +} diff --git a/src/main/java/friend/spring/domain/Gauge_poll.java b/src/main/java/friend/spring/domain/Gauge_poll.java new file mode 100644 index 0000000..9132262 --- /dev/null +++ b/src/main/java/friend/spring/domain/Gauge_poll.java @@ -0,0 +1,60 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Gauge_poll extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 30) + private String pollTitle; + + @Builder.Default + @Column(nullable = false) + private Integer gauge = 0; + + @Builder.Default + @Column(nullable = true) + private LocalDateTime deadline = LocalDateTime.now().plusHours(1); // 디폴트 시간 1시간 설정. + + @Column(nullable = false) + @Builder.Default + private Boolean VoteOnGoing = true; + + @OneToOne(mappedBy = "gaugePoll", cascade = CascadeType.ALL) + @JoinColumn(name = "post_id") + private Post post; + + @Builder.Default + @OneToMany(mappedBy = "gaugePoll") + private List gaugeVoteList = new ArrayList<>(); + + public void setPost(Post post) { + this.post = post; + } + + public void setDeadline(LocalDateTime deadline) { + this.deadline = deadline; + } + + public void setVoteOnGoing(Boolean voteOnGoing) { + this.VoteOnGoing = voteOnGoing; + } + + public void setGauge(Integer gauge) { + this.gauge = gauge; + } +} diff --git a/src/main/java/friend/spring/domain/Gauge_vote.java b/src/main/java/friend/spring/domain/Gauge_vote.java new file mode 100644 index 0000000..c488cf7 --- /dev/null +++ b/src/main/java/friend/spring/domain/Gauge_vote.java @@ -0,0 +1,43 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Gauge_vote extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Integer value; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "gauge_poll_id") + private Gauge_poll gaugePoll; + + + public void setGaugePoll(Gauge_poll gaugePoll) { + if (this.gaugePoll != null) + gaugePoll.getGaugeVoteList().remove(this); + this.gaugePoll = gaugePoll; + gaugePoll.getGaugeVoteList().add(this); + } + + public void setUser(User user) { + if (this.user != null) + user.getGaugeVoteList().remove(this); + this.user = user; + user.getGaugeVoteList().add(this); + } +} diff --git a/src/main/java/friend/spring/domain/General_poll.java b/src/main/java/friend/spring/domain/General_poll.java new file mode 100644 index 0000000..21a83e1 --- /dev/null +++ b/src/main/java/friend/spring/domain/General_poll.java @@ -0,0 +1,65 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class General_poll extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 30) + private String pollTitle; + + @Column(nullable = true) + @Builder.Default + private Boolean multipleChoice = true; + + @Column(nullable = true) + @Builder.Default + private LocalDateTime deadline = LocalDateTime.now().plusHours(1); // 디폴트 시간 1시간 설정. + + @Column(nullable = false) + @Builder.Default + private Boolean VoteOnGoing = true; + + @Builder.Default + @OneToMany(mappedBy = "generalPoll", cascade = CascadeType.ALL) + private List generalVoteList = new ArrayList<>(); + + @OneToOne(mappedBy = "generalPoll", cascade = CascadeType.ALL) + @JoinColumn(name = "post_id") + private Post post; + + @Builder.Default + @OneToMany(mappedBy = "generalPoll", cascade = CascadeType.ALL) + private List candidateList = new ArrayList<>(); + + public void setPost(Post post) { + this.post = post; + } + + public void setDeadline(LocalDateTime deadline) { + this.deadline = deadline; + } + + public void setVoteOnGoing(Boolean voteOnGoing) { + this.VoteOnGoing = voteOnGoing; + } + + public void setMultipleChoice(Boolean multipleChoice) { + this.multipleChoice = multipleChoice; + } + +} diff --git a/src/main/java/friend/spring/domain/General_vote.java b/src/main/java/friend/spring/domain/General_vote.java new file mode 100644 index 0000000..e4866be --- /dev/null +++ b/src/main/java/friend/spring/domain/General_vote.java @@ -0,0 +1,54 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class General_vote extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ElementCollection + @Builder.Default + @Column(nullable = true, length = 100) + private List select_list = new ArrayList(); + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "general_poll_id") + private General_poll generalPoll; + + public void setSelect_list(List select_list) { + if (select_list != null) { + this.select_list = new ArrayList(select_list); + } else { + this.select_list.clear(); + } + } + + public void setGeneralPoll(General_poll generalPoll) { + if (this.generalPoll != null) + generalPoll.getGeneralVoteList().remove(this); + this.generalPoll = generalPoll; + generalPoll.getGeneralVoteList().add(this); + } + + public void setUser(User user) { + if (this.user != null) + user.getGeneralVoteList().remove(this); + this.user = user; + user.getGeneralVoteList().add(this); + } +} diff --git a/src/main/java/friend/spring/domain/Inquiry.java b/src/main/java/friend/spring/domain/Inquiry.java new file mode 100644 index 0000000..326aadf --- /dev/null +++ b/src/main/java/friend/spring/domain/Inquiry.java @@ -0,0 +1,31 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import friend.spring.domain.enums.InquiryCategory; +import lombok.*; + +import javax.persistence.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Inquiry extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private InquiryCategory category; + + @Column(nullable = false, length = 1000) + private String content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + +} diff --git a/src/main/java/friend/spring/domain/Level.java b/src/main/java/friend/spring/domain/Level.java new file mode 100644 index 0000000..064f27d --- /dev/null +++ b/src/main/java/friend/spring/domain/Level.java @@ -0,0 +1,29 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Level extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 10) + private String name; + + @Column(nullable = false) + private Integer like; + + @Builder.Default + @OneToMany(mappedBy = "level") + private List userList = new ArrayList<>(); +} diff --git a/src/main/java/friend/spring/domain/Notice.java b/src/main/java/friend/spring/domain/Notice.java new file mode 100644 index 0000000..d1b21c6 --- /dev/null +++ b/src/main/java/friend/spring/domain/Notice.java @@ -0,0 +1,27 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Notice extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String title; + + @Column(length = 1000) + private String content; + + private Integer view; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "admin_id") + private User user; +} diff --git a/src/main/java/friend/spring/domain/Payment.java b/src/main/java/friend/spring/domain/Payment.java new file mode 100644 index 0000000..2389dbd --- /dev/null +++ b/src/main/java/friend/spring/domain/Payment.java @@ -0,0 +1,35 @@ +//package friend.spring.domain; +// +//import friend.spring.domain.common.BaseEntity; +//import friend.spring.domain.enums.PaymentState; +//import lombok.*; +// +//import javax.persistence.*; +//import java.math.BigDecimal; +// +//@Entity +//@Getter +//@Builder +//@AllArgsConstructor +//@NoArgsConstructor(access = AccessLevel.PROTECTED) +//public class Payment extends BaseEntity { +// +// @Id +// @GeneratedValue(strategy = GenerationType.IDENTITY) +// private Long id; +// +// @Column(nullable = false) +// private BigDecimal amount; +// +// //주문 고유 번호 +// private String merchantUid; +// +// //결제 상태 +// @Enumerated(EnumType.STRING) +// +// private PaymentState paymentState; +// +// +// +// +//} diff --git a/src/main/java/friend/spring/domain/Point.java b/src/main/java/friend/spring/domain/Point.java new file mode 100644 index 0000000..14c058a --- /dev/null +++ b/src/main/java/friend/spring/domain/Point.java @@ -0,0 +1,34 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Point extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 100) + private String content; + + @Column(nullable = false) + private Integer amount; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + public void setUser(User user) { + if (this.user != null) + user.getPointList().remove(this); + this.user = user; + user.getPointList().add(this); + } +} diff --git a/src/main/java/friend/spring/domain/Post.java b/src/main/java/friend/spring/domain/Post.java new file mode 100644 index 0000000..99795c8 --- /dev/null +++ b/src/main/java/friend/spring/domain/Post.java @@ -0,0 +1,177 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import friend.spring.domain.enums.PostState; +import friend.spring.domain.enums.PostType; +import friend.spring.domain.enums.PostVoteType; +import friend.spring.domain.mapping.Comment_choice; +import friend.spring.domain.mapping.Post_like; +import friend.spring.domain.mapping.Post_scrap; +import lombok.*; + +import javax.persistence.*; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Post extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 30) + private String title; + + @Column(nullable = false, length = 1000) + private String content; + + @Enumerated(EnumType.STRING) + @Column + private PostType postType; + + @Enumerated(EnumType.STRING) + @Column + private PostVoteType voteType; + + + @Enumerated(EnumType.STRING) + @Column + private PostState state; + + @Builder.Default + @Column(nullable = false) + private Integer view = 0; + + + @Column(nullable = true) + private Integer point; + + @Builder.Default + @Column(nullable = false) + private Integer isFixed = 0; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "category_id") + private Category category; + + // 부모 글 정의 + // 고민후기 원글 아이디 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id") + private Post parentPost; + + @Builder.Default + // 자식 글 정의 + @OneToMany(mappedBy = "parentPost") + private List reviewPostList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "post") + private List alarmList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL) + private List postLikeList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL) + private List postScrapList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "post") + private List commentList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "post") + private List commentChoiceList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "post") + private List fileList = new ArrayList<>(); + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "cardPoll_id") + private Card_poll cardPoll; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "gaugePoll_id") + private Gauge_poll gaugePoll; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "generalPoll_id") + private General_poll generalPoll; + + + public void setUser(User user) { + if (this.user != null) + user.getPostList().remove(this); + this.user = user; + user.getPostList().add(this); + } + + public void setParentPost(Post parent) { + if (this.parentPost != null) + parent.getReviewPostList().remove(this); + this.parentPost = parent; + parent.getReviewPostList().add(this); + } + + public void setCategory(Category category) { + if (this.category != null) + category.getPostList().remove(this); + this.category = category; + category.getPostList().add(this); + } + + public void setView(Integer view) { + this.view = view; + } + + + public void setGeneralPoll(General_poll generalPoll) { + this.generalPoll = generalPoll; + if (generalPoll != null && generalPoll.getPost() != this) { + generalPoll.setPost(this); + } + } + + public void setGaugePoll(Gauge_poll gaugePoll) { + this.gaugePoll = gaugePoll; + if (gaugePoll != null && gaugePoll.getPost() != this) { + gaugePoll.setPost(this); + } + } + + public void setCardPoll(Card_poll cardPoll) { + this.cardPoll = cardPoll; + if (cardPoll != null && cardPoll.getPost() != this) { + cardPoll.setPost(this); + } + } + + public void setTitle(String title) { + this.title = title; + } + + public void setContent(String content) { + this.content = content; + } + + public void setIsFixed(Integer isFixed) { + this.isFixed = isFixed; + } + + public void setStateDel() { + this.state = PostState.DELETED; + } + +} diff --git a/src/main/java/friend/spring/domain/Redis/SearchLog.java b/src/main/java/friend/spring/domain/Redis/SearchLog.java new file mode 100644 index 0000000..ec276ad --- /dev/null +++ b/src/main/java/friend/spring/domain/Redis/SearchLog.java @@ -0,0 +1,15 @@ +package friend.spring.domain.Redis; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +public class SearchLog { + private String name; + private String createdAt; +} diff --git a/src/main/java/friend/spring/domain/Report.java b/src/main/java/friend/spring/domain/Report.java new file mode 100644 index 0000000..396f4fd --- /dev/null +++ b/src/main/java/friend/spring/domain/Report.java @@ -0,0 +1,33 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import friend.spring.domain.enums.ReportType; +import lombok.*; + +import javax.persistence.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Report extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(EnumType.STRING) + @Column + private ReportType targetType; + + @Column(nullable = false) + private Long targetId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "report_category_id") + private ReportCategory reportCategory; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; +} diff --git a/src/main/java/friend/spring/domain/ReportCategory.java b/src/main/java/friend/spring/domain/ReportCategory.java new file mode 100644 index 0000000..b382fd1 --- /dev/null +++ b/src/main/java/friend/spring/domain/ReportCategory.java @@ -0,0 +1,29 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class ReportCategory { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + @Builder.Default + @OneToMany(mappedBy = "reportCategory") + private List reportList = new ArrayList<>(); + +} + diff --git a/src/main/java/friend/spring/domain/Term.java b/src/main/java/friend/spring/domain/Term.java new file mode 100644 index 0000000..8b29f58 --- /dev/null +++ b/src/main/java/friend/spring/domain/Term.java @@ -0,0 +1,27 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Term extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(length = 50000) + private String term; + + @Column(length = 50000) + private String privacy; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "admin_id") + private User user; +} diff --git a/src/main/java/friend/spring/domain/User.java b/src/main/java/friend/spring/domain/User.java new file mode 100644 index 0000000..9a98def --- /dev/null +++ b/src/main/java/friend/spring/domain/User.java @@ -0,0 +1,201 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import friend.spring.domain.enums.Gender; +import friend.spring.domain.enums.RoleType; +import friend.spring.domain.mapping.Comment_like; +import friend.spring.domain.mapping.Post_like; +import friend.spring.domain.mapping.Post_scrap; +import lombok.*; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import javax.persistence.*; +import javax.validation.constraints.Email; +import javax.validation.constraints.Pattern; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class User extends BaseEntity implements UserDetails { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 68) + private String password; + + @Email////1)@기호를 포함해야 한다.2_@기호를 기준으로 이메일 주소를 이루는 로컬호스트와 도메인 파트가 존재해야 한다.3)도메인 파트는 최소하나의 점과 + //그 뒤에 최소한 2개의 알파벳을 가진다를 검증 + @Column(nullable = false, length = 50) + private String email; + + @Pattern(regexp = "^01([0|1|6|7|8|9]?)-?([0-9]{3,4})-?([0-9]{4})$", message = "전화번호 형식이 맞지 않습니다.") + @Column(nullable = false, length = 15) + private String phone; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(10)") + private Gender gender; + + @Column(nullable = false, length = 30) + private String nickname; + + @DateTimeFormat(pattern = "yyyy-MM-dd") + @Column(nullable = false) + private LocalDate birth; + + @Column(nullable = true) + private Boolean agree_marketing; + + @Column(nullable = true) + private Boolean agree_info; + + @Column(nullable = true) + private Boolean is_deleted; + + @Column(nullable = true) + @Builder.Default + private Integer point = 0; + + @Column(nullable = true) + private String kakao; + + @Column(nullable = true)//잠시 true + private Integer like; + + @Enumerated(value = EnumType.STRING) + @Column(nullable = false) + private RoleType role; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "level_id") + private Level level; + + @Builder.Default + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) + private List pointList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) + private List alarmList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "user") + private List reportList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "user") + private List postList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) + private List postLikeList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) + private List postScrapList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "user") + private List commentList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "user") + private List commentLikeList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "user") + private List generalVoteList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "user") + private List gaugeVoteList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "user") + private List InquiryList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "user") + private List noticeList = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "user") + private List termList = new ArrayList<>(); + + @OneToOne(mappedBy = "user") + @JoinColumn(name = "file_id") + private File file; + + // UserDetails 상속 + @Override + public Collection getAuthorities() { + return null; + } + + @Override + public String getUsername() { + return email; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Builder.Default + @OneToMany(mappedBy = "user") + private List cardVoteList = new ArrayList<>(); + + public void setPoint(Integer point) { + this.point = point; + } + + public void setFile(File file) { + this.file = file; + } + + public void setNickname(String nickname) { + this.nickname = nickname; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setIsDeleted(boolean deleted) { + this.is_deleted = deleted; + } +} + diff --git a/src/main/java/friend/spring/domain/Uuid.java b/src/main/java/friend/spring/domain/Uuid.java new file mode 100644 index 0000000..d4c13ee --- /dev/null +++ b/src/main/java/friend/spring/domain/Uuid.java @@ -0,0 +1,20 @@ +package friend.spring.domain; + +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; + +@Entity +@Builder +@Getter +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Uuid extends BaseEntity { // S3 업로드 시 사진 각각을 식별해주는 UUID + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(unique = true) + private String uuid; +} diff --git a/src/main/java/friend/spring/domain/common/BaseEntity.java b/src/main/java/friend/spring/domain/common/BaseEntity.java new file mode 100644 index 0000000..80f5ebc --- /dev/null +++ b/src/main/java/friend/spring/domain/common/BaseEntity.java @@ -0,0 +1,25 @@ +package friend.spring.domain.common; + +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.Column; +import javax.persistence.EntityListeners; +import javax.persistence.MappedSuperclass; +import java.time.LocalDateTime; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@Getter +public abstract class BaseEntity { + + @CreatedDate + @Column(nullable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(nullable = false) + private LocalDateTime updatedAt; +} diff --git a/src/main/java/friend/spring/domain/enums/AlarmType.java b/src/main/java/friend/spring/domain/enums/AlarmType.java new file mode 100644 index 0000000..c16f477 --- /dev/null +++ b/src/main/java/friend/spring/domain/enums/AlarmType.java @@ -0,0 +1,5 @@ +package friend.spring.domain.enums; + +public enum AlarmType { + COMMENT, REPLY_COMMENT, VOTE_FINISH +} diff --git a/src/main/java/friend/spring/domain/enums/CommentState.java b/src/main/java/friend/spring/domain/enums/CommentState.java new file mode 100644 index 0000000..a684c97 --- /dev/null +++ b/src/main/java/friend/spring/domain/enums/CommentState.java @@ -0,0 +1,5 @@ +package friend.spring.domain.enums; + +public enum CommentState { + DELETED, POSTING +} diff --git a/src/main/java/friend/spring/domain/enums/Gender.java b/src/main/java/friend/spring/domain/enums/Gender.java new file mode 100644 index 0000000..b9d2c44 --- /dev/null +++ b/src/main/java/friend/spring/domain/enums/Gender.java @@ -0,0 +1,5 @@ +package friend.spring.domain.enums; + +public enum Gender { + NONE, MALE, FEMALE +} diff --git a/src/main/java/friend/spring/domain/enums/InquiryCategory.java b/src/main/java/friend/spring/domain/enums/InquiryCategory.java new file mode 100644 index 0000000..118fb94 --- /dev/null +++ b/src/main/java/friend/spring/domain/enums/InquiryCategory.java @@ -0,0 +1,5 @@ +package friend.spring.domain.enums; + +public enum InquiryCategory { + ONE, TWO, THREE, FOUR +} diff --git a/src/main/java/friend/spring/domain/enums/PaymentState.java b/src/main/java/friend/spring/domain/enums/PaymentState.java new file mode 100644 index 0000000..f6be152 --- /dev/null +++ b/src/main/java/friend/spring/domain/enums/PaymentState.java @@ -0,0 +1,5 @@ +//package friend.spring.domain.enums; +// +//public enum PaymentState { +// READY, PAID, FAILED, CANCEL +//} diff --git a/src/main/java/friend/spring/domain/enums/PostState.java b/src/main/java/friend/spring/domain/enums/PostState.java new file mode 100644 index 0000000..e48bafe --- /dev/null +++ b/src/main/java/friend/spring/domain/enums/PostState.java @@ -0,0 +1,5 @@ +package friend.spring.domain.enums; + +public enum PostState { + DELETED, POSTING, TEMP +} diff --git a/src/main/java/friend/spring/domain/enums/PostType.java b/src/main/java/friend/spring/domain/enums/PostType.java new file mode 100644 index 0000000..f044ef6 --- /dev/null +++ b/src/main/java/friend/spring/domain/enums/PostType.java @@ -0,0 +1,5 @@ +package friend.spring.domain.enums; + +public enum PostType { + VOTE, REVIEW +} diff --git a/src/main/java/friend/spring/domain/enums/PostVoteType.java b/src/main/java/friend/spring/domain/enums/PostVoteType.java new file mode 100644 index 0000000..c9b43b4 --- /dev/null +++ b/src/main/java/friend/spring/domain/enums/PostVoteType.java @@ -0,0 +1,5 @@ +package friend.spring.domain.enums; + +public enum PostVoteType { + GENERAL, GAUGE, CARD +} diff --git a/src/main/java/friend/spring/domain/enums/ReportType.java b/src/main/java/friend/spring/domain/enums/ReportType.java new file mode 100644 index 0000000..04a2e0c --- /dev/null +++ b/src/main/java/friend/spring/domain/enums/ReportType.java @@ -0,0 +1,5 @@ +package friend.spring.domain.enums; + +public enum ReportType { + POST, COMMENT +} diff --git a/src/main/java/friend/spring/domain/enums/RoleType.java b/src/main/java/friend/spring/domain/enums/RoleType.java new file mode 100644 index 0000000..2c645e8 --- /dev/null +++ b/src/main/java/friend/spring/domain/enums/RoleType.java @@ -0,0 +1,5 @@ +package friend.spring.domain.enums; + +public enum RoleType { + USER, ADMIN +} diff --git a/src/main/java/friend/spring/domain/enums/S3ImageType.java b/src/main/java/friend/spring/domain/enums/S3ImageType.java new file mode 100644 index 0000000..5e6af23 --- /dev/null +++ b/src/main/java/friend/spring/domain/enums/S3ImageType.java @@ -0,0 +1,5 @@ +package friend.spring.domain.enums; + +public enum S3ImageType { + USER, POST, CANDIDATE +} diff --git a/src/main/java/friend/spring/domain/enums/SocialType.java b/src/main/java/friend/spring/domain/enums/SocialType.java new file mode 100644 index 0000000..f7de7d9 --- /dev/null +++ b/src/main/java/friend/spring/domain/enums/SocialType.java @@ -0,0 +1,5 @@ +package friend.spring.domain.enums; + +public enum SocialType { + KAKAO +} diff --git a/src/main/java/friend/spring/domain/mapping/Comment_choice.java b/src/main/java/friend/spring/domain/mapping/Comment_choice.java new file mode 100644 index 0000000..a676598 --- /dev/null +++ b/src/main/java/friend/spring/domain/mapping/Comment_choice.java @@ -0,0 +1,30 @@ +package friend.spring.domain.mapping; + +import friend.spring.domain.Comment; +import friend.spring.domain.Post; +import friend.spring.domain.User; +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Comment_choice extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private Integer point; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id") + private Post post; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "comment_id") + private Comment comment; +} diff --git a/src/main/java/friend/spring/domain/mapping/Comment_like.java b/src/main/java/friend/spring/domain/mapping/Comment_like.java new file mode 100644 index 0000000..0e09b98 --- /dev/null +++ b/src/main/java/friend/spring/domain/mapping/Comment_like.java @@ -0,0 +1,27 @@ +package friend.spring.domain.mapping; + +import friend.spring.domain.Comment; +import friend.spring.domain.User; +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Comment_like extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "comment_id") + private Comment comment; +} diff --git a/src/main/java/friend/spring/domain/mapping/Post_like.java b/src/main/java/friend/spring/domain/mapping/Post_like.java new file mode 100644 index 0000000..4fcdf7f --- /dev/null +++ b/src/main/java/friend/spring/domain/mapping/Post_like.java @@ -0,0 +1,27 @@ +package friend.spring.domain.mapping; + +import friend.spring.domain.Post; +import friend.spring.domain.User; +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Post_like extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id") + private Post post; +} diff --git a/src/main/java/friend/spring/domain/mapping/Post_scrap.java b/src/main/java/friend/spring/domain/mapping/Post_scrap.java new file mode 100644 index 0000000..6103a08 --- /dev/null +++ b/src/main/java/friend/spring/domain/mapping/Post_scrap.java @@ -0,0 +1,27 @@ +package friend.spring.domain.mapping; + +import friend.spring.domain.Post; +import friend.spring.domain.User; +import friend.spring.domain.common.BaseEntity; +import lombok.*; + +import javax.persistence.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Post_scrap extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id") + private Post post; +} diff --git a/src/main/java/friend/spring/repository/AlarmRepository.java b/src/main/java/friend/spring/repository/AlarmRepository.java new file mode 100644 index 0000000..4cff38b --- /dev/null +++ b/src/main/java/friend/spring/repository/AlarmRepository.java @@ -0,0 +1,13 @@ +package friend.spring.repository; + +import friend.spring.domain.Alarm; +import friend.spring.domain.User; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AlarmRepository extends JpaRepository { + Page findAllByUser(User user, PageRequest pageRequest); + + Boolean existsByUserIdAndReadIsFalse(Long userId); +} diff --git a/src/main/java/friend/spring/repository/CandidateRepository.java b/src/main/java/friend/spring/repository/CandidateRepository.java new file mode 100644 index 0000000..2560250 --- /dev/null +++ b/src/main/java/friend/spring/repository/CandidateRepository.java @@ -0,0 +1,12 @@ +package friend.spring.repository; + +import friend.spring.domain.Candidate; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface CandidateRepository extends JpaRepository { + List findAllByGeneralPollId(Long general_poll_id); + + List findAllByCardPollId(Long card_poll_id); +} diff --git a/src/main/java/friend/spring/repository/Card_PollRepository.java b/src/main/java/friend/spring/repository/Card_PollRepository.java new file mode 100644 index 0000000..9b39eb8 --- /dev/null +++ b/src/main/java/friend/spring/repository/Card_PollRepository.java @@ -0,0 +1,7 @@ +package friend.spring.repository; + +import friend.spring.domain.Card_poll; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface Card_PollRepository extends JpaRepository { +} diff --git a/src/main/java/friend/spring/repository/Card_VoteRepository.java b/src/main/java/friend/spring/repository/Card_VoteRepository.java new file mode 100644 index 0000000..dda4f9f --- /dev/null +++ b/src/main/java/friend/spring/repository/Card_VoteRepository.java @@ -0,0 +1,11 @@ +package friend.spring.repository; + +import friend.spring.domain.Card_vote; +import friend.spring.domain.Gauge_poll; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface Card_VoteRepository extends JpaRepository { + List findByUserId(Long user_id); +} diff --git a/src/main/java/friend/spring/repository/CategoryRepository.java b/src/main/java/friend/spring/repository/CategoryRepository.java new file mode 100644 index 0000000..d5cb5da --- /dev/null +++ b/src/main/java/friend/spring/repository/CategoryRepository.java @@ -0,0 +1,11 @@ +package friend.spring.repository; + +import friend.spring.domain.Card_vote; +import friend.spring.domain.Category; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface CategoryRepository extends JpaRepository { + Category findByName(String category); +} diff --git a/src/main/java/friend/spring/repository/CommentChoiceRepository.java b/src/main/java/friend/spring/repository/CommentChoiceRepository.java new file mode 100644 index 0000000..5b2fdc8 --- /dev/null +++ b/src/main/java/friend/spring/repository/CommentChoiceRepository.java @@ -0,0 +1,12 @@ +package friend.spring.repository; + +import friend.spring.domain.mapping.Comment_choice; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface CommentChoiceRepository extends JpaRepository { + Optional findByPostId(Long postId); + + Optional findByCommentIdAndPostId(Long commentId, Long postId); +} diff --git a/src/main/java/friend/spring/repository/CommentLikeRepository.java b/src/main/java/friend/spring/repository/CommentLikeRepository.java new file mode 100644 index 0000000..3965a0c --- /dev/null +++ b/src/main/java/friend/spring/repository/CommentLikeRepository.java @@ -0,0 +1,10 @@ +package friend.spring.repository; + +import friend.spring.domain.mapping.Comment_like; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface CommentLikeRepository extends JpaRepository { + Optional findByCommentIdAndUserId(Long commentId, Long userId); +} diff --git a/src/main/java/friend/spring/repository/CommentRepository.java b/src/main/java/friend/spring/repository/CommentRepository.java new file mode 100644 index 0000000..943aaa5 --- /dev/null +++ b/src/main/java/friend/spring/repository/CommentRepository.java @@ -0,0 +1,16 @@ +package friend.spring.repository; + +import friend.spring.domain.Comment; +import friend.spring.domain.User; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface CommentRepository extends JpaRepository { + List findByPostIdAndParentCommentIsNull(Long postId); + + Page findAllByUser(User user, PageRequest pageRequest); +} diff --git a/src/main/java/friend/spring/repository/EmitterRepository.java b/src/main/java/friend/spring/repository/EmitterRepository.java new file mode 100644 index 0000000..83b6255 --- /dev/null +++ b/src/main/java/friend/spring/repository/EmitterRepository.java @@ -0,0 +1,44 @@ +package friend.spring.repository; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Repository +@RequiredArgsConstructor +public class EmitterRepository { + // 모든 Emitters를 저장하는 ConcurrentHashMap + private final Map emitters = new ConcurrentHashMap<>(); + + /** + * 주어진 아이디와 이미터를 저장 + * + * @param id - 사용자 아이디. + * @param emitter - 이벤트 Emitter. + */ + public void save(Long id, SseEmitter emitter) { + emitters.put(id, emitter); + } + + /** + * 주어진 아이디의 Emitter를 제거 + * + * @param id - 사용자 아이디. + */ + public void deleteById(Long id) { + emitters.remove(id); + } + + /** + * 주어진 아이디의 Emitter를 가져옴. + * + * @param id - 사용자 아이디. + * @return SseEmitter - 이벤트 Emitter. + */ + public SseEmitter get(Long id) { + return emitters.get(id); + } +} \ No newline at end of file diff --git a/src/main/java/friend/spring/repository/FileRepository.java b/src/main/java/friend/spring/repository/FileRepository.java new file mode 100644 index 0000000..33cc7f7 --- /dev/null +++ b/src/main/java/friend/spring/repository/FileRepository.java @@ -0,0 +1,10 @@ +package friend.spring.repository; + +import friend.spring.domain.File; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface FileRepository extends JpaRepository { + Optional findByUserId(Long userId); +} diff --git a/src/main/java/friend/spring/repository/Gauge_PollRepository.java b/src/main/java/friend/spring/repository/Gauge_PollRepository.java new file mode 100644 index 0000000..c764b26 --- /dev/null +++ b/src/main/java/friend/spring/repository/Gauge_PollRepository.java @@ -0,0 +1,11 @@ +package friend.spring.repository; + +import friend.spring.domain.Gauge_poll; +import friend.spring.domain.Gauge_vote; +import friend.spring.domain.General_vote; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface Gauge_PollRepository extends JpaRepository { +} diff --git a/src/main/java/friend/spring/repository/Gauge_VoteRepository.java b/src/main/java/friend/spring/repository/Gauge_VoteRepository.java new file mode 100644 index 0000000..b5d4b8e --- /dev/null +++ b/src/main/java/friend/spring/repository/Gauge_VoteRepository.java @@ -0,0 +1,11 @@ +package friend.spring.repository; + +import friend.spring.domain.Gauge_poll; +import friend.spring.domain.Gauge_vote; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface Gauge_VoteRepository extends JpaRepository { + List findByUserId(Long user_id); +} diff --git a/src/main/java/friend/spring/repository/General_PollRepository.java b/src/main/java/friend/spring/repository/General_PollRepository.java new file mode 100644 index 0000000..5154ea1 --- /dev/null +++ b/src/main/java/friend/spring/repository/General_PollRepository.java @@ -0,0 +1,7 @@ +package friend.spring.repository; + +import friend.spring.domain.General_poll; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface General_PollRepository extends JpaRepository { +} diff --git a/src/main/java/friend/spring/repository/General_VoteRepository.java b/src/main/java/friend/spring/repository/General_VoteRepository.java new file mode 100644 index 0000000..f70153a --- /dev/null +++ b/src/main/java/friend/spring/repository/General_VoteRepository.java @@ -0,0 +1,10 @@ +package friend.spring.repository; + +import friend.spring.domain.General_vote; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface General_VoteRepository extends JpaRepository { + List findByUserId(Long user_id); +} diff --git a/src/main/java/friend/spring/repository/InquiryRepository.java b/src/main/java/friend/spring/repository/InquiryRepository.java new file mode 100644 index 0000000..8812c0f --- /dev/null +++ b/src/main/java/friend/spring/repository/InquiryRepository.java @@ -0,0 +1,7 @@ +package friend.spring.repository; + +import friend.spring.domain.Inquiry; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface InquiryRepository extends JpaRepository { +} diff --git a/src/main/java/friend/spring/repository/LevelRepository.java b/src/main/java/friend/spring/repository/LevelRepository.java new file mode 100644 index 0000000..fed06c6 --- /dev/null +++ b/src/main/java/friend/spring/repository/LevelRepository.java @@ -0,0 +1,9 @@ +package friend.spring.repository; + +import friend.spring.domain.Level; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface LevelRepository extends JpaRepository { +} diff --git a/src/main/java/friend/spring/repository/NoticeRepository.java b/src/main/java/friend/spring/repository/NoticeRepository.java new file mode 100644 index 0000000..54127cc --- /dev/null +++ b/src/main/java/friend/spring/repository/NoticeRepository.java @@ -0,0 +1,11 @@ +package friend.spring.repository; + +import friend.spring.domain.Notice; +import friend.spring.domain.User; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface NoticeRepository extends JpaRepository { + Page findAllByUser(User user, PageRequest pageRequest); +} diff --git a/src/main/java/friend/spring/repository/PointRepository.java b/src/main/java/friend/spring/repository/PointRepository.java new file mode 100644 index 0000000..8e4fd90 --- /dev/null +++ b/src/main/java/friend/spring/repository/PointRepository.java @@ -0,0 +1,7 @@ +package friend.spring.repository; + +import friend.spring.domain.Point; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PointRepository extends JpaRepository { +} diff --git a/src/main/java/friend/spring/repository/PostLikeRepository.java b/src/main/java/friend/spring/repository/PostLikeRepository.java new file mode 100644 index 0000000..2fdbacf --- /dev/null +++ b/src/main/java/friend/spring/repository/PostLikeRepository.java @@ -0,0 +1,13 @@ +package friend.spring.repository; + +import friend.spring.domain.mapping.Post_like; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface PostLikeRepository extends JpaRepository { + + Optional findByPostIdAndUserId(Long postId, Long userId); + + Integer countByPostId(Long postId); +} diff --git a/src/main/java/friend/spring/repository/PostRepository.java b/src/main/java/friend/spring/repository/PostRepository.java new file mode 100644 index 0000000..517dac2 --- /dev/null +++ b/src/main/java/friend/spring/repository/PostRepository.java @@ -0,0 +1,65 @@ +package friend.spring.repository; + +import friend.spring.domain.Category; +import friend.spring.domain.Post; +import friend.spring.domain.enums.PostState; +import friend.spring.domain.enums.PostType; +import io.lettuce.core.dynamic.annotation.Param; +import org.springframework.data.domain.Page; +import friend.spring.domain.User; +import org.springframework.data.domain.PageRequest; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.time.LocalDateTime; + +public interface PostRepository extends JpaRepository { + Page findByPostTypeAndState(PostType postType, PostState state, Pageable pageable); + + Page findByPostTypeAndStateAndCategory(PostType postType, PostState state, Category category, Pageable pageable); + + Page findByUserIdAndPostTypeAndState(Long userId, PostType postType, PostState state, Pageable pageable); + + Page findAllByUser(User user, PageRequest pageRequest); + + Page findByPostTypeAndStateAndCreatedAtAfter(PostType postType, PostState state, LocalDateTime sevenDaysAgo, Pageable pageable); + + @Query(value = "SELECT p FROM Post p JOIN p.postScrapList s JOIN p.category c WHERE s.user.id = :userId and c.id = :categoryId") + Page findCategoryDetail(Long userId, Long categoryId, PageRequest pageRequest); + + @Query(value = "SELECT p FROM Post p WHERE p.title LIKE CONCAT('%',:search,'%') OR p.content LIKE CONCAT('%',:search,'%')") + Page findByKeyWord(String search, Pageable pageable); + + @Query("SELECT p FROM Post p " + + "LEFT JOIN General_poll gp ON p.generalPoll.id = gp.id " + + "LEFT JOIN Gauge_poll ggp ON p.gaugePoll.id = ggp.id " + + "LEFT JOIN Card_poll cp ON p.cardPoll.id = cp.id " + + "WHERE p.postType = :vote " + + "AND p.state = :posting " + + "AND p.createdAt >= :minusDays " + + // 투표 마감 안 된 글들만 필터링 + "AND (" + + " (gp.VoteOnGoing = true) OR " + + " (ggp.VoteOnGoing = true) OR " + + " (cp.VoteOnGoing = true)" + + ") " + + // 로그인한 사용자가 투표 안 한 글들만 필터링 + "AND (" + + " gp.id NOT IN (SELECT gv.generalPoll.id FROM General_vote gv WHERE gv.user.id = :userId) OR " + + " ggp.id NOT IN (SELECT gv.gaugePoll.id FROM Gauge_vote gv WHERE gv.user.id = :userId) OR " + + " cp.id NOT IN (SELECT gv.cardPoll.id FROM Card_vote gv WHERE gv.user.id = :userId)" + + ") " + + //각 투표 유형에 대한 투표 수를 합산해(어차피 하나에만 값이 있음) 투표 수가 적은 순으로 정렬 + "ORDER BY (" + + " (SELECT COUNT(gv.id) FROM General_vote gv WHERE gv.generalPoll.id = gp.id) + " + + " (SELECT COUNT(gv.id) FROM Gauge_vote gv WHERE gv.gaugePoll.id = ggp.id) + " + + " (SELECT COUNT(gv.id) FROM Card_vote gv WHERE gv.cardPoll.id = cp.id)" + + ") ASC") + Page findPostsOrderByVoteCount(@Param("vote") PostType vote, + @Param("posting") PostState posting, + @Param("minusDays") LocalDateTime minusDays, + @Param("userId") Long userId, + Pageable pageable); +} diff --git a/src/main/java/friend/spring/repository/PostScrapRepository.java b/src/main/java/friend/spring/repository/PostScrapRepository.java new file mode 100644 index 0000000..960742f --- /dev/null +++ b/src/main/java/friend/spring/repository/PostScrapRepository.java @@ -0,0 +1,30 @@ +package friend.spring.repository; + +import friend.spring.domain.Category; +import friend.spring.domain.Post; +import friend.spring.domain.User; +import friend.spring.domain.mapping.Post_scrap; +import lombok.Value; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; +import java.util.Optional; + +public interface PostScrapRepository extends JpaRepository { + + Optional findByPostIdAndUserId(Long postId, Long userId); + + List findAllByUser(User user); + + // 사용자가 스크랩한 모든 글을 최신순으로 정렬하여 페이징하여 조회 + @Query("SELECT p FROM Post p JOIN p.postScrapList s WHERE s.user.id = :userId ORDER BY p.createdAt DESC") + Page findPostsByUserIdOrderByCreatedAtDesc(Long userId, Pageable pageable); + + // 사용자가 스크랩한 모든 글을 조회수 순으로 정렬하여 페이징하여 조회 + @Query("SELECT p FROM Post p JOIN p.postScrapList s WHERE s.user.id = :userId ORDER BY p.view DESC") + Page findPostsByUserIdOrderByPostViewDesc(Long userId, Pageable pageable); +} diff --git a/src/main/java/friend/spring/repository/ReportCategoryRepository.java b/src/main/java/friend/spring/repository/ReportCategoryRepository.java new file mode 100644 index 0000000..1487661 --- /dev/null +++ b/src/main/java/friend/spring/repository/ReportCategoryRepository.java @@ -0,0 +1,10 @@ +package friend.spring.repository; + +import friend.spring.domain.Category; +import friend.spring.domain.Report; +import friend.spring.domain.ReportCategory; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ReportCategoryRepository extends JpaRepository { + ReportCategory findByName(String category); +} diff --git a/src/main/java/friend/spring/repository/ReportRepository.java b/src/main/java/friend/spring/repository/ReportRepository.java new file mode 100644 index 0000000..7991e22 --- /dev/null +++ b/src/main/java/friend/spring/repository/ReportRepository.java @@ -0,0 +1,11 @@ +package friend.spring.repository; + +import friend.spring.domain.Report; +import friend.spring.domain.enums.ReportType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface ReportRepository extends JpaRepository { + Optional findByTargetTypeAndTargetIdAndUserId(ReportType post, Long postId, Long userId); +} diff --git a/src/main/java/friend/spring/repository/TermRepository.java b/src/main/java/friend/spring/repository/TermRepository.java new file mode 100644 index 0000000..a48426c --- /dev/null +++ b/src/main/java/friend/spring/repository/TermRepository.java @@ -0,0 +1,7 @@ +package friend.spring.repository; + +import friend.spring.domain.Term; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TermRepository extends JpaRepository { +} diff --git a/src/main/java/friend/spring/repository/UserRepository.java b/src/main/java/friend/spring/repository/UserRepository.java new file mode 100644 index 0000000..1ee2630 --- /dev/null +++ b/src/main/java/friend/spring/repository/UserRepository.java @@ -0,0 +1,21 @@ +package friend.spring.repository; + +import friend.spring.domain.User; +import friend.spring.domain.enums.RoleType; +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); + + Optional findByNickname(String nickname); +// Optional findByEmailAndRole(String email, RoleType roleType); + + Boolean existsByEmail(String email); + + Boolean existsByNickname(String nickname); +} diff --git a/src/main/java/friend/spring/repository/UuidRepository.java b/src/main/java/friend/spring/repository/UuidRepository.java new file mode 100644 index 0000000..fcbbd4c --- /dev/null +++ b/src/main/java/friend/spring/repository/UuidRepository.java @@ -0,0 +1,9 @@ +package friend.spring.repository; + +import friend.spring.domain.Uuid; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UuidRepository extends JpaRepository { +} diff --git a/src/main/java/friend/spring/security/JwtAuthenticationFilter.java b/src/main/java/friend/spring/security/JwtAuthenticationFilter.java new file mode 100644 index 0000000..3d1d0ef --- /dev/null +++ b/src/main/java/friend/spring/security/JwtAuthenticationFilter.java @@ -0,0 +1,50 @@ +package friend.spring.security; + +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + + private final RedisTemplate redisTemplate; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + + System.out.println("필터 실행"); + // Request Header에서 JWT 토큰 추출 + String token = jwtTokenProvider.resolveToken(request); + + // validateToken으로 토큰 유효성 검사 + if (token != null && jwtTokenProvider.validateToken(token)) { + + // Redis에 해당 access token logout여부를 확인 + String isLogout = (String) redisTemplate.opsForValue().get(token); + + // 로그아웃이 없는(되어 있지 않은) 경우 해당 토큰은 정상적으로 작동하기 + if (ObjectUtils.isEmpty(isLogout)) { + + // token이 유효하면 인증 객체 생성 + Authentication authentication = jwtTokenProvider.getAuthentication(token); + + // SecurityContextHolder.getContext() : 시큐리티의 session 공간 + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + } + filterChain.doFilter(request, response); + + } +} diff --git a/src/main/java/friend/spring/security/JwtTokenProvider.java b/src/main/java/friend/spring/security/JwtTokenProvider.java new file mode 100644 index 0000000..4e4229d --- /dev/null +++ b/src/main/java/friend/spring/security/JwtTokenProvider.java @@ -0,0 +1,176 @@ +package friend.spring.security; + +import ch.qos.logback.core.status.ErrorStatus; +import friend.spring.apiPayload.GeneralException; +import friend.spring.apiPayload.handler.UserHandler; +import friend.spring.domain.User; +import friend.spring.repository.UserRepository; +import friend.spring.web.dto.TokenDTO; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.PropertySource; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; +import java.util.Base64; +import java.util.Date; +import java.util.Optional; + +import static friend.spring.apiPayload.code.status.ErrorStatus.*; + +@Slf4j +@RequiredArgsConstructor +@Component +@PropertySource(value = {"/application.yml"}) +public class JwtTokenProvider { + + @Value("${spring.jwt.secret}") + private String secretKey; + + private final UserDetailsService userDetailsService; + + private final UserRepository userRepository; + + private final RedisTemplate redisTemplate; + + + // 객체 초기화, secretKey를 Base64로 인코딩 + @PostConstruct + protected void init() { + secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); + } + + // JWT Access 토큰 생성 + public TokenDTO createAccessToken(String email) { + // 토큰 유효시간 30분 + long tokenValidTime = 48 * 60 * 60 * 1000L; + + Optional user = userRepository.findByEmail(email); + + Claims claims = Jwts.claims().setSubject(email); // JWT payload에 저장되는 정보단위 + + user.ifPresent(value -> claims.put("id", value.getId())); + + + Date now = new Date(); + Date expiresTime = new Date(now.getTime() + tokenValidTime); + String token = Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + tokenValidTime)) + .signWith(SignatureAlgorithm.HS256, secretKey) + .claim("types", "atk") + //.claim("userIdx",user.get().getUserIdx()) + //.claim("role", user.get().getRole()) + .compact(); + + return new TokenDTO(String.valueOf(TokenType.atk), token, expiresTime); + } + + // JWT Refresh 토큰 생성 + public TokenDTO createRefreshToken(String email) { + // Refresh 토큰 유효시간 2주 + long tokenValidTime = 2 * 7 * 24 * 60 * 60 * 1000L; + Optional user = userRepository.findByEmail(email); + + Claims claims = Jwts.claims().setSubject(email); // JWT payload에 저장되는 정보단위 + + user.ifPresent(value -> claims.put("id", value.getId())); + + Date now = new Date(); + Date expiresTime = new Date(now.getTime() + tokenValidTime); // 토큰 만료 시간 + String token = Jwts.builder() + .setClaims(claims) // 정보 저장 + .setIssuedAt(now) // 토큰 발행 시간 정보 + .setExpiration(expiresTime) // Expire Time 설정 + .signWith(SignatureAlgorithm.HS256, secretKey) // 사용할 암호화 알고리즘과 signature 에 들어갈 secretkey 값 설정 + .claim("types", "rtk") + .compact(); + return new TokenDTO(String.valueOf(TokenType.rtk), token, expiresTime); + } + + // JWT 토큰에서 인증 정보 조회 + public Authentication getAuthentication(String token) { + UserDetails userDetails = userDetailsService.loadUserByUsername(this.getTokenSub(token)); + return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); + } + + // 토큰에서 회원정보 추출 - email (payload의 subject) + public String getTokenSub(String token) { + return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); + } + + // Request 의 Header 에서 access token 값 추출 "atk" : "token--" + public String resolveAccessToken(HttpServletRequest request) { + return request.getHeader("atk"); + } + + // 토큰 재발급 때 Header에 rtk를 넣어 요청, 나머지 경우 atk 사용 + public String resolveToken(HttpServletRequest request) { + if (request.getHeader("rtk") != null) { + return request.getHeader("rtk"); + } else { + return request.getHeader("atk"); + } + } + + // 토큰의 유효성 + 만료일자 확인 + public boolean validateToken(String jwtToken) { + try { + Jws claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken); // 토큰의 payload(claim) // Access 토큰의 경우 redis 까지 검사 + if (claims.getBody().get("types").equals("atk")) { + Object isLogOut = redisTemplate.opsForValue().get(jwtToken); // token 을 key 로 value 가져옴 (null 이면 유효 토큰, logout 이면 유효하지 않은 토큰) + // 로그인 시 redis 에 email : refreshtoken 형태로 저장 + // 로그아웃 시 redis 에 accesstoken : logout 형태로 저장 + if (isLogOut != null) { + return false; + } + return !claims.getBody().getExpiration().before(new Date());// 만료안됐으면 true, 만료됐으면 false + } else { + // Refresh 토큰 유효성 검사 + return !claims.getBody().getExpiration().before(new Date()); // 만료안됐으면 true, 만료됐으면 false + } + } catch (Exception e) { + return false; + } + } + + // 토큰 만료 시간 + public Date getExpireTime(String jwtToken) { + Jws claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken); + return claims.getBody().getExpiration(); + } + + // 토큰에서 회원정보 추출 - userIdx 추출 + public Long getCurrentUser(HttpServletRequest request) throws GeneralException { // userIdx 가져오기 + String jwtToken = resolveAccessToken(request); // Request의 header에서 Access 토큰 추출 + if (!validateToken(jwtToken)) { + throw new GeneralException(INVALID_JWT); + } + + Long userIdx = Long.valueOf(String.valueOf(Jwts.parser() + .setSigningKey(secretKey) + .parseClaimsJws(jwtToken) + .getBody() + .get("id"))); + + return userIdx; + } +// public String getemail(String token) { +// return getClaims(token).getBody().get("id", String.class); +// } +// private Jws getClaims(String token) { +// return Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token); +// } +} diff --git a/src/main/java/friend/spring/security/PrincipalDetailService.java b/src/main/java/friend/spring/security/PrincipalDetailService.java new file mode 100644 index 0000000..199a2fb --- /dev/null +++ b/src/main/java/friend/spring/security/PrincipalDetailService.java @@ -0,0 +1,20 @@ +package friend.spring.security; + +import friend.spring.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PrincipalDetailService implements UserDetailsService { + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + return userRepository.findByEmail(email) + .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다.")); + } +} diff --git a/src/main/java/friend/spring/security/TokenType.java b/src/main/java/friend/spring/security/TokenType.java new file mode 100644 index 0000000..1c315f0 --- /dev/null +++ b/src/main/java/friend/spring/security/TokenType.java @@ -0,0 +1,5 @@ +package friend.spring.security; + +public enum TokenType { + atk, rtk +} \ No newline at end of file diff --git a/src/main/java/friend/spring/service/AlarmService.java b/src/main/java/friend/spring/service/AlarmService.java new file mode 100644 index 0000000..7fde446 --- /dev/null +++ b/src/main/java/friend/spring/service/AlarmService.java @@ -0,0 +1,18 @@ +package friend.spring.service; + +import friend.spring.domain.Alarm; +import friend.spring.web.dto.AlarmResponseDTO; +import org.springframework.data.domain.Page; + +import javax.servlet.http.HttpServletRequest; + +public interface AlarmService { + + void checkAlarm(boolean flag); + + Page getAlarmList(Long userId, Integer page); + + AlarmResponseDTO.AlarmLeftResDTO getRemainingAlarm(HttpServletRequest request); + + void editAlarmRead(Long alarmId, HttpServletRequest request); +} diff --git a/src/main/java/friend/spring/service/AlarmServiceImpl.java b/src/main/java/friend/spring/service/AlarmServiceImpl.java new file mode 100644 index 0000000..c6962b3 --- /dev/null +++ b/src/main/java/friend/spring/service/AlarmServiceImpl.java @@ -0,0 +1,80 @@ +package friend.spring.service; + +import friend.spring.apiPayload.code.status.ErrorStatus; +import friend.spring.apiPayload.handler.AlarmHandler; +import friend.spring.domain.Alarm; +import friend.spring.domain.User; +import friend.spring.repository.AlarmRepository; +import friend.spring.repository.UserRepository; +import friend.spring.security.JwtTokenProvider; +import friend.spring.web.dto.AlarmResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletRequest; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Transactional +public class AlarmServiceImpl implements AlarmService { + + private final UserRepository userRepository; + private final AlarmRepository alarmRepository; + private final UserService userService; + private final JwtTokenProvider jwtTokenProvider; + + @Override + public void checkAlarm(boolean flag) { + if (!flag) { + throw new AlarmHandler(ErrorStatus.ALARM_NOT_FOUND); + } + } + + @Override + public Page getAlarmList(Long userId, Integer page) { + Optional optionalUser = userRepository.findById(userId); + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + User user = optionalUser.get(); + + Page alarmPage = alarmRepository.findAllByUser(user, PageRequest.of(page, 10)); + return alarmPage; + + } + + @Override + public AlarmResponseDTO.AlarmLeftResDTO getRemainingAlarm(HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + Optional optionalUser = userRepository.findById(userId); + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + + Boolean isAlarmLeft = alarmRepository.existsByUserIdAndReadIsFalse(userId); + return AlarmResponseDTO.AlarmLeftResDTO.builder() + .isAlarmLeft(isAlarmLeft).build(); + } + + @Override + @Transactional + public void editAlarmRead(Long alarmId, HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + Optional optionalUser = userRepository.findById(userId); + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + + Optional optionalAlarm = alarmRepository.findById(alarmId); + if (optionalAlarm.isEmpty()) { + checkAlarm(false); + } + + Alarm alarm = optionalAlarm.get(); + alarm.setRead(true); + } +} diff --git a/src/main/java/friend/spring/service/AuthService.java b/src/main/java/friend/spring/service/AuthService.java new file mode 100644 index 0000000..05cd6b3 --- /dev/null +++ b/src/main/java/friend/spring/service/AuthService.java @@ -0,0 +1,12 @@ +package friend.spring.service; + +import friend.spring.web.dto.TokenDTO; + +import java.util.List; + +public interface AuthService { + + List kakaoLogin(String code); + +// void logout(Long userId); +} diff --git a/src/main/java/friend/spring/service/AuthServiceImpl.java b/src/main/java/friend/spring/service/AuthServiceImpl.java new file mode 100644 index 0000000..c7b795b --- /dev/null +++ b/src/main/java/friend/spring/service/AuthServiceImpl.java @@ -0,0 +1,69 @@ +package friend.spring.service; + +import friend.spring.converter.UserConverter; +import friend.spring.domain.User; +import friend.spring.OAuth.KakaoProfile; +import friend.spring.OAuth.OAuthToken; +import friend.spring.OAuth.provider.KakaoAuthProvider; +import friend.spring.repository.UserRepository; +import friend.spring.security.JwtTokenProvider; +import friend.spring.web.dto.TokenDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +@Service +@RequiredArgsConstructor +public class AuthServiceImpl implements AuthService { + + private final KakaoAuthProvider kakaoAuthProvider; + private final UserRepository userRepository; + private final JwtTokenProvider jwtTokenProvider; + private final RedisTemplate redisTemplate; +// private final RefreshTokenService refreshTokenService; + + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + + @Override + @Transactional + public List kakaoLogin(String code) { + OAuthToken oAuthToken = kakaoAuthProvider.requestToken(code); + KakaoProfile kakaoProfile = + kakaoAuthProvider.requestKakaoProfile(oAuthToken.getAccess_token()); + + System.out.println(kakaoProfile.getKakao_account().getEmail()); + // 유저 정보 받기 + Optional queryUser = + userRepository.findByEmail( + kakaoProfile.getKakao_account().getEmail()); + System.out.println(queryUser.get().getEmail()); + System.out.println(queryUser.get().getBirth()); +// 가입자 혹은 비가입자 체크해서 로그인 처리 + if (queryUser.isPresent()) { + User user = queryUser.get(); + TokenDTO accessToken = jwtTokenProvider.createAccessToken(user.getEmail()); + TokenDTO refreshToken = jwtTokenProvider.createRefreshToken(user.getEmail()); + redisTemplate.opsForValue().set("RT:" + user.getEmail(), refreshToken.getToken(), refreshToken.getTokenExpriresTime().getTime(), TimeUnit.MILLISECONDS); + + List tokenDTOList = new ArrayList<>(); + tokenDTOList.add(refreshToken); + tokenDTOList.add(accessToken); + + return tokenDTOList; + } else { + User user = userRepository.save(UserConverter.KakaoUser(kakaoProfile)); + TokenDTO accessToken = jwtTokenProvider.createAccessToken(user.getEmail()); + TokenDTO refreshToken = jwtTokenProvider.createRefreshToken(user.getEmail()); + redisTemplate.opsForValue().set("RT:" + refreshToken, TimeUnit.MILLISECONDS); + + return (List) UserConverter.toOAuthResponse(accessToken, refreshToken, false, user); + } + } +} \ No newline at end of file diff --git a/src/main/java/friend/spring/service/CommentService.java b/src/main/java/friend/spring/service/CommentService.java new file mode 100644 index 0000000..f0699e9 --- /dev/null +++ b/src/main/java/friend/spring/service/CommentService.java @@ -0,0 +1,43 @@ +package friend.spring.service; + +import friend.spring.domain.Comment; +import friend.spring.domain.Report; +import friend.spring.domain.mapping.Comment_choice; +import friend.spring.domain.mapping.Comment_like; +import friend.spring.web.dto.CommentRequestDTO; +import friend.spring.web.dto.CommentResponseDTO; +import friend.spring.web.dto.PostResponseDTO; +import org.springframework.data.domain.Page; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +public interface CommentService { + void checkComment(Boolean flag); + + void checkCommentLike(Boolean flag); + + void checkCommentChoice(Boolean flag); + + void checkSelectCommentAnotherUser(Boolean flag); + + void checkCommentWriterUser(Boolean flag); + + public Comment createComment(Long postId, CommentRequestDTO.commentCreateReq requestBody, HttpServletRequest request); + + Comment_like likeComment(Long postId, Long commentId, HttpServletRequest request); + + List getComments(Long postId, HttpServletRequest request); + + void dislikeComment(Long postId, Long commentId, HttpServletRequest request); + + Comment_choice selectComment(Long postId, Long commentId, HttpServletRequest request); + + void editComment(Long postId, Long commentId, CommentRequestDTO.commentEditReq requestBody, HttpServletRequest request); + + Page getMyCommentList(Long userId, Integer page); + + void deleteComment(Long postId, Long commentId, HttpServletRequest request); + + PostResponseDTO.ReportResult createReportComment(Long commentId, CommentRequestDTO.CommentReportReq request, HttpServletRequest request2); +} diff --git a/src/main/java/friend/spring/service/CommentServiceImpl.java b/src/main/java/friend/spring/service/CommentServiceImpl.java new file mode 100644 index 0000000..7633b87 --- /dev/null +++ b/src/main/java/friend/spring/service/CommentServiceImpl.java @@ -0,0 +1,444 @@ +package friend.spring.service; + +import friend.spring.apiPayload.GeneralException; +import friend.spring.apiPayload.code.status.ErrorStatus; +import friend.spring.apiPayload.handler.CommentHandler; +import friend.spring.apiPayload.handler.PostHandler; +import friend.spring.converter.AlarmConverter; +import friend.spring.converter.CommentConverter; +import friend.spring.converter.PostConverter; +import friend.spring.converter.SseConverter; +import friend.spring.domain.*; +import friend.spring.domain.enums.AlarmType; +import friend.spring.domain.enums.ReportType; +import friend.spring.domain.mapping.Comment_choice; +import friend.spring.domain.mapping.Comment_like; +import friend.spring.repository.*; +import friend.spring.security.JwtTokenProvider; +import friend.spring.web.dto.CommentRequestDTO; +import friend.spring.web.dto.CommentResponseDTO; +import friend.spring.web.dto.PostResponseDTO; +import friend.spring.web.dto.SseResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletRequest; +import java.util.*; + +import static friend.spring.apiPayload.code.status.ErrorStatus.*; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = false) +public class CommentServiceImpl implements CommentService { + + private final CommentRepository commentRepository; + private final PostRepository postRepository; + private final UserRepository userRepository; + private final CommentLikeRepository commentLikeRepository; + private final CommentChoiceRepository commentChoiceRepository; + private final PointRepository pointRepository; + private final AlarmRepository alarmRepository; + private final ReportRepository reportRepository; + private final ReportCategoryRepository reportCategoryRepository; + private final UserService userService; + private final PostService postService; + private final JwtTokenProvider jwtTokenProvider; + private final SseService notificationService; + + @Override + public void checkComment(Boolean flag) { + if (!flag) { + throw new CommentHandler(COMMENT_NOT_FOUND); + } + } + + @Override + public void checkCommentLike(Boolean flag) { + if (!flag) { + throw new CommentHandler(COMMENT_LIKE_NOT_FOUND); + } else { + throw new CommentHandler(COMMENT_LIKE_DUPLICATE); + } + } + + @Override + public void checkCommentChoice(Boolean flag) { + if (!flag) { + throw new CommentHandler(COMMENT_CHOICE_OVER_ONE); + } + } + + @Override + public void checkSelectCommentAnotherUser(Boolean flag) { + if (!flag) { + throw new CommentHandler(COMMENT_SELECT_MYSELF); + } + } + + @Override + public void checkCommentWriterUser(Boolean flag) { + if (!flag) { + throw new CommentHandler(COMMENT_NOT_CORRECT_USER); + } + } + + @Override + @Transactional + public Comment createComment(Long postId, CommentRequestDTO.commentCreateReq requestBody, HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + + Optional optionalPost = postRepository.findById(postId); + if (optionalPost.isEmpty()) { + postService.checkPost(false); + } + + Optional optionalUser = userRepository.findById(userId); + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + + Comment parentComment = null; + Post post = optionalPost.get(); + User user = optionalUser.get(); + + // 대댓글인 경우 + boolean isReplyComment = false; + if (requestBody.getParentId() != null) { + isReplyComment = true; + Optional optionalParentComment = commentRepository.findById(requestBody.getParentId()); + if (optionalParentComment.isEmpty()) { + this.checkComment(false); + } + + // 댓글 소속이 글과 일치하는지 확인 + if (!Objects.equals(optionalParentComment.get().getPost().getId(), post.getId())) { + throw new CommentHandler(COMMENT_POST_NOT_MATCH); + } + + parentComment = optionalParentComment.get(); + } + + Comment comment = CommentConverter.toComment(requestBody, post, user, parentComment); + commentRepository.save(comment); + + // 알림 ResponseDTO 생성 + SseResponseDTO.CommentCreateResDTO commentCreateResDTO; + + // 필수) 해당 댓글이 있는 글 주인에게 알림 생성 + commentCreateResDTO = SseConverter.toCommentCreateResDTO(comment, AlarmType.COMMENT); + Alarm newAlarm = AlarmConverter.toAlarm(commentCreateResDTO.getAlarmContent(), AlarmType.COMMENT, post, comment.getPost().getUser(), comment); + alarmRepository.save(newAlarm); + notificationService.customNotify(comment.getPost().getUser().getId(), commentCreateResDTO, comment.getContent(), AlarmType.COMMENT.toString()); + + // 옵션) 해당 댓글이 대댓글인 경우, 루트 댓글 주인에게 알림 생성 + if (isReplyComment) { + commentCreateResDTO = SseConverter.toCommentCreateResDTO(comment, AlarmType.REPLY_COMMENT); + newAlarm = AlarmConverter.toAlarm(commentCreateResDTO.getAlarmContent(), AlarmType.REPLY_COMMENT, post, comment.getPost().getUser(), comment); + alarmRepository.save(newAlarm); + notificationService.customNotify(comment.getParentComment().getUser().getId(), commentCreateResDTO, comment.getContent(), AlarmType.REPLY_COMMENT.toString()); + } + + return comment; + } + + @Override + public Comment_like likeComment(Long postId, Long commentId, HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + + Optional optionalPost = postRepository.findById(postId); + if (optionalPost.isEmpty()) { + postService.checkPost(false); + } + + Optional optionalComment = commentRepository.findById(commentId); + if (optionalComment.isEmpty()) { + this.checkComment(false); + } + + Optional optionalUser = userRepository.findById(userId); + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + + Post post = optionalPost.get(); + Comment comment = optionalComment.get(); + User user = optionalUser.get(); + + // 댓글 소속이 글과 일치하는지 확인 + if (!Objects.equals(comment.getPost().getId(), post.getId())) { + throw new CommentHandler(COMMENT_POST_NOT_MATCH); + } + + Optional optionalComment_like = commentLikeRepository.findByCommentIdAndUserId(commentId, userId); + if (!optionalComment_like.isEmpty()) { + this.checkCommentLike(true); + } + + Comment_like comment_like = CommentConverter.toCommentLike(post, comment, user); + return commentLikeRepository.save(comment_like); + } + + @Override + public List getComments(Long postId, HttpServletRequest request) { + Long loginUserId = jwtTokenProvider.getCurrentUser(request); + Optional optionalUser = userRepository.findById(loginUserId); + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + + Optional optionalPost = postRepository.findById(postId); + if (optionalPost.isEmpty()) { + postService.checkPost(false); + } + + List commentList = commentRepository.findByPostIdAndParentCommentIsNull(postId); // 루트 댓글만 가져옴 + List commentGetResList = new ArrayList<>(); + for (Comment comment : commentList) { + // 대댓글 처리 + List subComments = new ArrayList<>(); + if (comment.getSubCommentList() != null) { + for (Comment c : comment.getSubCommentList()) { + Boolean isPushedLike_sub = checkIsPushedLike(c, loginUserId); + Boolean isOwnerOfPost_sub = checkIsOwnerOfPost(c, loginUserId); + Boolean isSelected = checkIsSelected(c); + CommentResponseDTO.commentGetRes subCommentGetRes = CommentConverter.toCommentGetRes(c, loginUserId, isPushedLike_sub, isOwnerOfPost_sub, isSelected, new ArrayList<>()); + subComments.add(subCommentGetRes); + } + } + + Boolean isPushedLike = checkIsPushedLike(comment, loginUserId); + Boolean isOwnerOfPost = checkIsOwnerOfPost(comment, loginUserId); + Boolean isSelected = checkIsSelected(comment); + + CommentResponseDTO.commentGetRes commentGetRes = CommentConverter.toCommentGetRes(comment, loginUserId, isPushedLike, isOwnerOfPost, isSelected, subComments); + commentGetResList.add(commentGetRes); + } + + return commentGetResList; + } + + public Boolean checkIsPushedLike(Comment comment, Long loginUserId) { + // 좋아요 이미 눌렀는지 여부 + Optional optionalComment_like = commentLikeRepository.findByCommentIdAndUserId(comment.getId(), loginUserId); + Boolean isPushedLike; + if (optionalComment_like.isEmpty()) { + isPushedLike = false; + } else { + isPushedLike = true; + } + return isPushedLike; + } + + public Boolean checkIsOwnerOfPost(Comment comment, Long loginUserId) { + // 내가 쓴 글인지 여부 + Boolean isOwnerOfPost; + if (Objects.equals(comment.getPost().getUser().getId(), loginUserId)) { + isOwnerOfPost = true; + } else { + isOwnerOfPost = false; + } + return isOwnerOfPost; + } + + public Boolean checkIsSelected(Comment comment) { + Optional optionalComment_choice = commentChoiceRepository.findByCommentIdAndPostId(comment.getId(), comment.getPost().getId()); + + // 이 댓글이 채택되었는지 여부 + Boolean isSelected; + if (optionalComment_choice.isEmpty()) { + isSelected = false; + } else { + isSelected = true; + } + + return isSelected; + } + + + @Override + public void dislikeComment(Long postId, Long commentId, HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + + Optional optionalPost = postRepository.findById(postId); + if (optionalPost.isEmpty()) { + postService.checkPost(false); + } + + Optional optionalComment = commentRepository.findById(commentId); + if (optionalComment.isEmpty()) { + this.checkComment(false); + } + + // 댓글 소속이 글과 일치하는지 확인 + if (!Objects.equals(optionalComment.get().getPost().getId(), optionalPost.get().getId())) { + throw new CommentHandler(COMMENT_POST_NOT_MATCH); + } + + Optional optionalUser = userRepository.findById(userId); + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + + Optional optionalComment_like = commentLikeRepository.findByCommentIdAndUserId(commentId, userId); + if (optionalComment_like.isEmpty()) { + this.checkCommentLike(false); + } + + Comment_like comment_like = optionalComment_like.get(); + commentLikeRepository.delete(comment_like); + } + + @Override + public Comment_choice selectComment(Long postId, Long commentId, HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + + Optional optionalPost = postRepository.findById(postId); + if (optionalPost.isEmpty()) { + postService.checkPost(false); + } + + Optional optionalComment = commentRepository.findById(commentId); + if (optionalComment.isEmpty()) { + this.checkComment(false); + } + + // 댓글 소속이 글과 일치하는지 확인 + if (!Objects.equals(optionalComment.get().getPost().getId(), optionalPost.get().getId())) { + throw new CommentHandler(COMMENT_POST_NOT_MATCH); + } + + Optional optionalUser = userRepository.findById(userId); + // 이 사용자가 존재하는지 확인 + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + + Post post = optionalPost.get(); + Comment comment = optionalComment.get(); + User user = optionalUser.get(); + + // 로그인한 사용자가 이 글의 작성자인지 확인 + if (!Objects.equals(user.getId(), post.getUser().getId())) { + // 작성자가 아닌 경우 -> 에러 반환 + postService.checkPostWriterUser(false); + } + + // 자기 자신을 채택했는지 확인 + if (Objects.equals(user.getId(), comment.getUser().getId())) { + this.checkSelectCommentAnotherUser(false); + } + + Optional optionalComment_choice = commentChoiceRepository.findByPostId(postId); + if (!optionalComment_choice.isEmpty()) { // 이미 1명 채택을 한 상태이므로 에러로 반환 + this.checkCommentChoice(false); + } + + Comment_choice comment_choice = CommentConverter.toCommentChoice(post, comment); + + // 채택된 사용자에게 포인트 적립 + comment.getUser().setPoint(comment.getUser().getPoint() + post.getPoint()); + Point newPoint = Point.builder() + .amount(post.getPoint()) + .content("채택된 댓글에 대한 " + post.getPoint() + " 포인트 적립") + .build(); + newPoint.setUser(user); + pointRepository.save(newPoint); + + return commentChoiceRepository.save(comment_choice); + } + + @Override + @Transactional + public void editComment(Long postId, Long commentId, CommentRequestDTO.commentEditReq requestBody, HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + Post post = postRepository.findById(postId).orElseThrow(() -> new GeneralException(ErrorStatus.POST_NOT_FOUND)); + Comment comment = commentRepository.findById(commentId).orElseThrow(() -> new GeneralException(ErrorStatus.COMMENT_NOT_FOUND)); + + // 댓글 소속이 글과 일치하는지 확인 + if (!Objects.equals(comment.getPost().getId(), post.getId())) { + throw new CommentHandler(COMMENT_POST_NOT_MATCH); + } + + // 로그인한 사용자가 이 댓글의 작성자인지 확인 + if (!Objects.equals(user.getId(), comment.getUser().getId())) { + // 작성자가 아닌 경우 -> 에러 반환 + this.checkCommentWriterUser(false); + } + comment.update(requestBody.getContent()); + } + + //한 유저의 모든 댓글 + @Override + public Page getMyCommentList(Long userId, Integer page) { + Optional optionalUser = userRepository.findById(userId); + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + User user = optionalUser.get(); + Page userPage = commentRepository.findAllByUser(user, PageRequest.of(page, 5)); + return userPage; + } + + @Override + @Transactional + public void deleteComment(Long postId, Long commentId, HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + Post post = postRepository.findById(postId).orElseThrow(() -> new GeneralException(ErrorStatus.POST_NOT_FOUND)); + Comment comment = commentRepository.findById(commentId).orElseThrow(() -> new GeneralException(ErrorStatus.COMMENT_NOT_FOUND)); + + // 댓글 소속이 글과 일치하는지 확인 + if (!Objects.equals(comment.getPost().getId(), post.getId())) { + throw new CommentHandler(COMMENT_POST_NOT_MATCH); + } + + // 로그인한 사용자가 이 댓글의 작성자인지 확인 + if (!Objects.equals(user.getId(), comment.getUser().getId())) { + // 작성자가 아닌 경우 -> 에러 반환 + this.checkCommentWriterUser(false); + } + comment.updateStateToDeleted(); + } + + @Override + public PostResponseDTO.ReportResult createReportComment(Long commentId, CommentRequestDTO.CommentReportReq request, HttpServletRequest request2) { + Long userId = jwtTokenProvider.getCurrentUser(request2); + + Optional optionalComment = commentRepository.findById(commentId); + if (optionalComment.isEmpty()) { + this.checkComment(false); + } + + Optional optionalUser = userRepository.findById(userId); + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + + Comment comment = optionalComment.get(); + User user = optionalUser.get(); + + // 이미 신고된 건인지 확인 + Optional optionalReport = reportRepository.findByTargetTypeAndTargetIdAndUserId(ReportType.COMMENT, commentId, userId); + if (!optionalReport.isEmpty()) { + return PostResponseDTO.ReportResult.builder() + .report(optionalReport.get()) + .duplicatedReport(true) + .build(); + } + + ReportCategory reportCategory = reportCategoryRepository.findByName(request.getReportCategory()); + Report report = CommentConverter.toReportComment(comment, user, reportCategory); + return PostResponseDTO.ReportResult.builder() + .report(reportRepository.save(report)) + .duplicatedReport(false) + .build(); + } +} + diff --git a/src/main/java/friend/spring/service/EmailService.java b/src/main/java/friend/spring/service/EmailService.java new file mode 100644 index 0000000..4cfcedf --- /dev/null +++ b/src/main/java/friend/spring/service/EmailService.java @@ -0,0 +1,116 @@ +package friend.spring.service; + +import friend.spring.apiPayload.code.status.ErrorStatus; +import friend.spring.apiPayload.handler.UserHandler; +import friend.spring.config.RedisUtil; +import friend.spring.domain.Post; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import java.util.Random; + +@Service +@RequiredArgsConstructor +public class EmailService { + + @Autowired + private final JavaMailSender emailSender; + @Autowired + private RedisUtil redisUtil; + private int authNumber; + + // 인증코드 체크 + public boolean CheckAuthNum(String email, String authNum) { + if (redisUtil.getData(authNum) == null) { + System.out.println("false1"); + throw new UserHandler(ErrorStatus.INCORRECT_CODE); + } else if (redisUtil.getData(authNum).equals(email)) { + System.out.println("true"); + return true; + } else { + System.out.println("false2"); + throw new UserHandler(ErrorStatus.INCORRECT_CODE); + } + } + + //임의의 6자리 인증 코드생성 + public void makeRandomNumber() { + Random r = new Random(); + String randomNumber = ""; + for (int i = 0; i < 6; i++) { + randomNumber += Integer.toString(r.nextInt(10)); + } + + authNumber = Integer.parseInt(randomNumber); + } + + //mail을 어디서 보내는지, 어디로 보내는지 , 인증 번호를 html 형식으로 어떻게 보내는지 작성 + public String joinEmail(String email) { + makeRandomNumber(); + String setFrom = "gominchingu@gmail.com"; // email-config에 설정한 자신의 이메일 주소를 입력 + String toMail = email; + String title = "고민친구 회원 가입 인증 이메일 입니다."; // 이메일 제목 + String content = + "고민친구를 방문해주셔서 감사합니다." + //html 형식으로 작성 ! + "

" + + "인증 번호는 " + authNumber + "입니다." + + "
" + + "인증 코드의 유효시간은 5분 입니다."; //이메일 내용 삽입 + mailSend(setFrom, toMail, title, content); + + return Integer.toString(authNumber); + } + + //이메일을 전송합니다. + public void mailSend(String setFrom, String toMail, String title, String content) { + MimeMessage message = emailSender.createMimeMessage();//JavaMailSender 객체를 사용하여 MimeMessage 객체를 생성 + try { + MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");//이메일 메시지와 관련된 설정을 수행합니다. + // true를 전달하여 multipart 형식의 메시지를 지원하고, "utf-8"을 전달하여 문자 인코딩을 설정 + helper.setFrom(setFrom);//이메일의 발신자 주소 설정 + helper.setTo(toMail);//이메일의 수신자 주소 설정 + helper.setSubject(title);//이메일의 제목을 설정 + helper.setText(content, true);//이메일의 내용 설정 두 번째 매개 변수에 true를 설정하여 html 설정으로한다. + emailSender.send(message); + } catch (MessagingException e) {//이메일 서버에 연결할 수 없거나, 잘못된 이메일 주소를 사용하거나, 인증 오류가 발생하는 등 오류 + // 이러한 경우 MessagingException이 발생 + e.printStackTrace();//e.printStackTrace()는 예외를 기본 오류 스트림에 출력하는 메서드 + } + redisUtil.setDataExpire(Integer.toString(authNumber), toMail, 60 * 5L); + + } + + //mail을 어디서 보내는지, 어디로 보내는지 , 인증 번호를 html 형식으로 어떻게 보내는지 작성 + public String passwordEmail(String email) { + makeRandomNumber(); + String setFrom = "gominchingu@gmail.com"; // email-config에 설정한 자신의 이메일 주소를 입력 + String toMail = email; + String title = "고민친구 비밀번호 재설정 인증 이메일 입니다."; // 이메일 제목 + String content = + "고민친구를 방문해주셔서 감사합니다." + //html 형식으로 작성 ! + "

" + + "인증 번호는 " + authNumber + "입니다." + + "
" + + "인증 코드의 유효시간은 5분 입니다."; //이메일 내용 삽입 + mailSend(setFrom, toMail, title, content); + + return Integer.toString(authNumber); + } + + + public void voteFinishEmail(Post post) { + String setFrom = "gominchingu@gmail.com"; // email-config에 설정한 자신의 이메일 주소를 입력 + String toMail = post.getUser().getEmail(); + String title = "고민친구 투표 마감 알림 이메일 입니다."; + String content = + "회원님의 고민투표" + post.getTitle() + "이 마감되었습니다." + + "
" + + "투표를 확인해보세요!"; + mailSend(setFrom, toMail, title, content); + } +} \ No newline at end of file diff --git a/src/main/java/friend/spring/service/JwtTokenService.java b/src/main/java/friend/spring/service/JwtTokenService.java new file mode 100644 index 0000000..1e34532 --- /dev/null +++ b/src/main/java/friend/spring/service/JwtTokenService.java @@ -0,0 +1,7 @@ +package friend.spring.service; + +import javax.servlet.http.HttpServletRequest; + +public interface JwtTokenService { + Long JwtToId(HttpServletRequest request); +} diff --git a/src/main/java/friend/spring/service/JwtTokenServiceImpl.java b/src/main/java/friend/spring/service/JwtTokenServiceImpl.java new file mode 100644 index 0000000..f215367 --- /dev/null +++ b/src/main/java/friend/spring/service/JwtTokenServiceImpl.java @@ -0,0 +1,21 @@ +package friend.spring.service; + +import friend.spring.security.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletRequest; + +@Service +@RequiredArgsConstructor +public class JwtTokenServiceImpl implements JwtTokenService { + private final JwtTokenProvider jwtTokenProvider; + + @Override + @Transactional + public Long JwtToId(HttpServletRequest request) { + return jwtTokenProvider.getCurrentUser(request); + } + +} diff --git a/src/main/java/friend/spring/service/MyPageService.java b/src/main/java/friend/spring/service/MyPageService.java new file mode 100644 index 0000000..e128fb1 --- /dev/null +++ b/src/main/java/friend/spring/service/MyPageService.java @@ -0,0 +1,50 @@ +package friend.spring.service; + +import friend.spring.domain.*; +import friend.spring.domain.mapping.Post_scrap; +import friend.spring.web.dto.MyPageRequestDTO; +import org.springframework.data.domain.Page; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +public interface MyPageService { + void checkPost(Boolean flag); + + List getCategoryList(Long userId); + + Page getAllPostList(Long userId, Integer page, Integer sort); + + void editUserImage(MultipartFile file, HttpServletRequest request); + + User getEditUserPage(Long userId); + + User editUserName(Long userId, MyPageRequestDTO.ProfileEditNameReq profileEditNameReq); + + User editUserEmail(Long userId, MyPageRequestDTO.ProfileEditEmailReq profileEditEmailReq); + + User editUserPhone(Long userId, MyPageRequestDTO.ProfileEditPhoneReq profileEditPhoneReq); + + User editUserPassword(Long userId, MyPageRequestDTO.ProfileEditPasswordReq profileEditPasswordReq); + + User editUserSecurity(Long userId, MyPageRequestDTO.ProfileEditSecurityReq profileEditSecurityReq); + + Inquiry createInquiry(Long userId, MyPageRequestDTO.MyInquiryReq myInquiryReq); + + Page getCategoryDetailList(Long userId, Long categoryId, Integer page); + + Category getCategory(Long categoryId); + + Page getNoticeList(Long userid, Integer page); + + User checkAdmin(Long adminId); + + Notice getNoticeDetail(Long noticeId); + + Term getTerm(Long userId); + + Term getPrivacy(Long userId); +} diff --git a/src/main/java/friend/spring/service/MyPageServiceImpl.java b/src/main/java/friend/spring/service/MyPageServiceImpl.java new file mode 100644 index 0000000..3ec22f6 --- /dev/null +++ b/src/main/java/friend/spring/service/MyPageServiceImpl.java @@ -0,0 +1,227 @@ +package friend.spring.service; + +import friend.spring.apiPayload.GeneralException; +import friend.spring.apiPayload.code.status.ErrorStatus; +import friend.spring.apiPayload.handler.PostHandler; +import friend.spring.converter.MyPageConverter; +import friend.spring.domain.*; +import friend.spring.domain.enums.RoleType; +import friend.spring.domain.enums.S3ImageType; +import friend.spring.domain.mapping.Post_scrap; +import friend.spring.repository.*; +import friend.spring.security.JwtTokenProvider; +import friend.spring.web.dto.MyPageRequestDTO; +import friend.spring.web.dto.MyPageResponseDTO; +import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Service +@RequiredArgsConstructor +@Transactional +@Slf4j +public class MyPageServiceImpl implements MyPageService { + + private final UserRepository userRepository; + private final PostService postService; + private final UserService userService; + private final PostRepository postRepository; + private final CategoryRepository categoryRepository; + private final PostScrapRepository postScrapRepository; + private final FileRepository fileRepository; + private final JwtTokenProvider jwtTokenProvider; + private final S3Service s3Service; + private final InquiryRepository inquiryRepository; + private final NoticeRepository noticeRepository; + private final TermRepository termRepository; + + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + + @Override + public void checkPost(Boolean flag) { + if (!flag) { + throw new PostHandler(ErrorStatus.POST_CATGORY_NOT_FOUND); + } + } + + //저장한 게시물 + @Override + public List getCategoryList(Long userId) { + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + List scrapList = user.getPostScrapList(); + if (scrapList.isEmpty()) { + postService.checkPostScrap(false); + } + List categoryList = scrapList.stream() + .map(Post_scrap::getPost).filter(Objects::nonNull) + .map(Post::getCategory).filter(Objects::nonNull).distinct().collect(Collectors.toList()); + return categoryList; + } + + @Override + public Page getAllPostList(Long userId, Integer page, Integer sort) { + if (sort == 0) { + Page scrapListByView = postScrapRepository.findPostsByUserIdOrderByPostViewDesc(userId, PageRequest.of(page, 10)); + return scrapListByView; + } else if (sort == 1) { + Page scrapListByRecent = postScrapRepository.findPostsByUserIdOrderByCreatedAtDesc(userId, PageRequest.of(page, 10)); + return scrapListByRecent; + } else return null; + } + + @Override + @Transactional + public void editUserImage(MultipartFile file, HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + + Optional optionalUser = userRepository.findById(userId); + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + + User user = optionalUser.get(); + + Optional optionalFile = fileRepository.findByUserId(userId); + File newFile = null; + if (optionalFile.isPresent()) { // 이미 유저 프로필사진이 있는 경우 -> 기존 데이터 변경 + newFile = s3Service.editSingleImage(file, user); + } else { // 유저 프로필 사진이 없는 경우 -> 새로 데이터 추가 + newFile = s3Service.uploadSingleImage(file, S3ImageType.USER, user, null); + } + user.setFile(newFile); + } + + @Override + public User getEditUserPage(Long userId) { + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + return user; + } + + @Override + public User editUserName(Long userId, MyPageRequestDTO.ProfileEditNameReq profileEditNameReq) { + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + user.setNickname(profileEditNameReq.getNickName()); + return user; + } + + @Override + public User editUserEmail(Long userId, MyPageRequestDTO.ProfileEditEmailReq profileEditEmailReq) { + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + List all = userRepository.findAll(); + all.forEach(eachUser -> { + if (eachUser.getEmail().equals(profileEditEmailReq.getChangeEmail())) { + throw new GeneralException(ErrorStatus.USER_EXISTS_EMAIL); + } + }); + user.setEmail(profileEditEmailReq.getChangeEmail()); + return user; + } + + @Override + public User editUserPhone(Long userId, MyPageRequestDTO.ProfileEditPhoneReq profileEditPhoneReq) { + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + user.setPhone(profileEditPhoneReq.getPhone()); + return user; + } + + @Override + public User editUserPassword(Long userId, MyPageRequestDTO.ProfileEditPasswordReq profileEditPasswordReq) { + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + if (!encoder.matches(profileEditPasswordReq.getCurPassword(), user.getPassword())) { + throw new GeneralException(ErrorStatus.PASSWORD_INCORRECT); + } + if (!profileEditPasswordReq.getChangePassword().equals(profileEditPasswordReq.getCheckPassword())) { + throw new GeneralException(ErrorStatus.PASSWORD_CHECK_INCORRECT); + } + String encode = encoder.encode(profileEditPasswordReq.getChangePassword()); + user.setPassword(encode); + return user; + } + + @Override + public User editUserSecurity(Long userId, MyPageRequestDTO.ProfileEditSecurityReq profileEditSecurityReq) { + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + List all = userRepository.findAll(); + all.forEach(eachUser -> { + if (eachUser.getEmail().equals(profileEditSecurityReq.getChangeEmail())) { + throw new GeneralException(ErrorStatus.USER_EXISTS_EMAIL); + } + }); + user.setEmail(profileEditSecurityReq.getChangeEmail()); + return user; + } + + @Override + public Inquiry createInquiry(Long userId, MyPageRequestDTO.MyInquiryReq myInquiryReq) { + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + Inquiry inquiry = MyPageConverter.toInquiry(myInquiryReq, user); + return inquiryRepository.save(inquiry); + } + + @Override + public Page getCategoryDetailList(Long userId, Long categoryId, Integer page) { + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + Category category = categoryRepository.findById(categoryId).orElseThrow(() -> new GeneralException(ErrorStatus.POST_CATGORY_NOT_FOUND)); + Page detailList = postRepository.findCategoryDetail(userId, categoryId, PageRequest.of(page, 10)); + return detailList; + } + + @Override + public Category getCategory(Long categoryId) { + Category category = categoryRepository.findById(categoryId).orElseThrow(() -> new GeneralException(ErrorStatus.POST_CATGORY_NOT_FOUND)); + return category; + } + + @Override + public Page getNoticeList(Long userId, Integer page) { + User admin = checkAdmin(userId); + Page noticeList = noticeRepository.findAllByUser(admin, PageRequest.of(page, 10)); + return noticeList; + } + + @Override + public User checkAdmin(Long adminId) { + User user = userRepository.findById(adminId).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + if (user.getRole() == RoleType.USER) { + throw new GeneralException(ErrorStatus.NOT_ADMIN); + } + return user; + } + + @Override + public Notice getNoticeDetail(Long noticeId) { + Notice notice = noticeRepository.findById(noticeId).orElseThrow(() -> new GeneralException(ErrorStatus.NOTICE_NOT_FOUND)); + return notice; + } + + @Override + public Term getTerm(Long userId) { + User admin = checkAdmin(userId); + Long termId = 1L; + Term term = termRepository.findById(termId).get(); + return term; + } + + @Override + public Term getPrivacy(Long userId) { + User admin = checkAdmin(userId); + Long privacyId = 1L; + Term privacy = termRepository.findById(privacyId).get(); + return privacy; + } + + +} diff --git a/src/main/java/friend/spring/service/PostQueryService.java b/src/main/java/friend/spring/service/PostQueryService.java new file mode 100644 index 0000000..1283fb6 --- /dev/null +++ b/src/main/java/friend/spring/service/PostQueryService.java @@ -0,0 +1,28 @@ +package friend.spring.service; + +import friend.spring.domain.Post; +import friend.spring.domain.Redis.SearchLog; +import org.springframework.data.domain.Page; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Optional; + +public interface PostQueryService { + Post getPostDetail(Long postId); + + Boolean checkEngage(Long userId, Long postId); + + Post ParentPost(Long parentid); + + Optional findPost(Long postId); + + Page getPostList(Integer page, Integer size, String category); + + Page getReviewList(Integer page, Integer size, Integer arrange); + + Page getParentPostList(Integer page, Integer size, HttpServletRequest request); + Page getPostSearch(Long userId,Integer page, Integer size, String search); + List getRecentSearchLogs(Long userId); + +} diff --git a/src/main/java/friend/spring/service/PostQueryServiceImpl.java b/src/main/java/friend/spring/service/PostQueryServiceImpl.java new file mode 100644 index 0000000..1cc9b6d --- /dev/null +++ b/src/main/java/friend/spring/service/PostQueryServiceImpl.java @@ -0,0 +1,190 @@ +package friend.spring.service; + +import friend.spring.apiPayload.GeneralException; +import friend.spring.apiPayload.code.status.ErrorStatus; +import friend.spring.domain.*; +import friend.spring.domain.Redis.SearchLog; +import friend.spring.domain.enums.PostState; +import friend.spring.domain.enums.PostType; +import friend.spring.domain.enums.PostVoteType; +import friend.spring.repository.*; +import friend.spring.security.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.*; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletRequest; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static friend.spring.apiPayload.code.status.ErrorStatus.USER_NOT_FOUND; + +@Service +@RequiredArgsConstructor +public class PostQueryServiceImpl implements PostQueryService { + private final PostRepository postRepository; + private final UserRepository userRepository; + private final General_VoteRepository generalVoteRepository; + private final Gauge_VoteRepository gaugeVoteRepository; + private final Card_VoteRepository cardVoteRepository; + private final CategoryRepository categoryRepository; + private final JwtTokenProvider jwtTokenProvider; + private final RedisTemplate objectRedisTemplate; + + @Override + @Transactional + public Post getPostDetail(Long postId) { + Post post = postRepository.findById(postId).orElseThrow(() -> new GeneralException(ErrorStatus.POST_NOT_FOUND)); + post.setView(post.getView() + 1); + return post; + } + + @Override + public Boolean checkEngage(Long postId, Long userId) { + Optional postOptional = postRepository.findById(postId); + Post post = postOptional.get(); + Boolean engage; + if (post.getUser().getId().equals(userId)) { + return true; + } + if (post.getPostType() == PostType.VOTE) { + if (post.getVoteType() == PostVoteType.GENERAL) { + List vote = generalVoteRepository.findByUserId(userId); + Boolean isIn = vote.stream().filter(pollPost -> pollPost.getGeneralPoll() == post.getGeneralPoll()).collect(Collectors.toList()).isEmpty(); + if (!isIn) { // 목록이 비어 있지 않으면 true를 반환 + return true; + } + } + } + if (post.getPostType() == PostType.VOTE) { + if (post.getVoteType() == PostVoteType.CARD) { + List vote = cardVoteRepository.findByUserId(userId); + Boolean isIn = vote.stream().filter(pollPost -> pollPost.getCardPoll() == post.getCardPoll()).collect(Collectors.toList()).isEmpty(); + if (!isIn) { // 목록이 비어 있지 않으면 true를 반환 + return true; + } + } + } + if (post.getPostType() == PostType.VOTE) { + if (post.getVoteType() == PostVoteType.GAUGE) { + List vote = gaugeVoteRepository.findByUserId(userId); + Boolean isIn = vote.stream().filter(pollPost -> pollPost.getGaugePoll() == post.getGaugePoll()).collect(Collectors.toList()).isEmpty(); + if (!isIn) { // 목록이 비어 있지 않으면 true를 반환 + return true; + } + } + } + return false; + } + + @Override + @Transactional + public Post ParentPost(Long parentid) { + Optional parentPostOptional = postRepository.findById(parentid); + return parentPostOptional.get().getParentPost(); + } + + @Override + public Optional findPost(Long postId) { + return Optional.empty(); + } + + @Override + @Transactional + public Page getPostList(Integer page, Integer size, String category) { + if (category.equals("모두")) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + return postRepository.findByPostTypeAndState(PostType.VOTE, PostState.POSTING, pageable); + } + Category category1 = categoryRepository.findByName(category); + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + return postRepository.findByPostTypeAndStateAndCategory(PostType.VOTE, PostState.POSTING, category1, pageable); + } + + @Override + @Transactional + public Page getReviewList(Integer page, Integer size, Integer arrange) { + if (arrange == 1) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + return postRepository.findByPostTypeAndState(PostType.REVIEW, PostState.POSTING, pageable); + } + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "view")); + return postRepository.findByPostTypeAndState(PostType.REVIEW, PostState.POSTING, pageable); + } + + @Override + @Transactional + public Page getParentPostList(Integer page, Integer size, HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); +// List userPost = postRepository.findByUserId(userId); + Page allPost = postRepository.findByUserIdAndPostTypeAndState(userId, PostType.VOTE, PostState.POSTING, pageable); + List filterEndPost = allPost.getContent().stream() + .filter(post -> { + LocalDateTime deadline = null; + switch (post.getVoteType()) { + case GENERAL: + deadline = post.getGeneralPoll().getDeadline(); + break; + case GAUGE: + deadline = post.getGaugePoll().getDeadline(); + break; + case CARD: + deadline = post.getCardPoll().getDeadline(); + break; + } + return deadline != null && deadline.isBefore(LocalDateTime.now()); + }).collect(Collectors.toList()); + return new PageImpl<>(filterEndPost, pageable, filterEndPost.size()); + + } + + @Override + @Transactional + public Page getPostSearch(Long userId,Integer page, Integer size, String search) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(USER_NOT_FOUND)); + + // 기존 큐 방식을 soreted set 방식으로 리팩토링 + + double score = System.currentTimeMillis(); // 시스템의 현재시간을 점수로 사용하였습니다. + String key = "CurrentSearch" + user.getId(); + + objectRedisTemplate.opsForZSet().add(key, search, score); // sorted set에 새로운 검색로그를 저장하였습니다. + + Long redisSize = objectRedisTemplate.opsForZSet().size(key); + + // 10개면 오래된 항목 제거 + if (redisSize != null && redisSize > 10) { + objectRedisTemplate.opsForZSet().removeRange(key, 0, 0); + } + + return postRepository.findByKeyWord(search, pageable); + } + + public List getRecentSearchLogs(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(USER_NOT_FOUND)); + + String key = "CurrentSearch" + user.getId(); + + // score 높은순으로 10개 가져오기 + Set> recentLogs = objectRedisTemplate.opsForZSet().reverseRangeWithScores(key, 0, 9); + + // 검색어 문자열로 변환 + List searchLogs = recentLogs.stream() + .map(ZSetOperations.TypedTuple::getValue) // 문자열 검색어 가져오기 + .map(Object::toString)// string으로 타입을 변환 + .collect(Collectors.toList()); + + return searchLogs; + } + + +} diff --git a/src/main/java/friend/spring/service/PostService.java b/src/main/java/friend/spring/service/PostService.java new file mode 100644 index 0000000..063bdb2 --- /dev/null +++ b/src/main/java/friend/spring/service/PostService.java @@ -0,0 +1,56 @@ +package friend.spring.service; + + +import friend.spring.domain.Candidate; +import friend.spring.domain.Post; +import friend.spring.domain.User; +import friend.spring.domain.mapping.Post_like; +import friend.spring.domain.mapping.Post_scrap; +import friend.spring.web.dto.CandidateRequestDTO; +import friend.spring.web.dto.PostRequestDTO; +import friend.spring.web.dto.PostResponseDTO; +import friend.spring.web.dto.UserRequestDTO; +import org.springframework.data.domain.Page; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +public interface PostService { + void checkPost(Boolean flag); + + + void checkPostWriterUser(Boolean flag); + + void checkPostLike(Boolean flag); + + Post joinPost(PostRequestDTO.AddPostDTO request, HttpServletRequest request2); + + Candidate createCandidate(Long postId, CandidateRequestDTO.AddCandidateRequestDTO request, HttpServletRequest request2) throws IOException; + + Boolean checkPoint(PostRequestDTO.AddPostDTO request, User user); + + + void editPost(Long postId, PostRequestDTO.PostEditReq request, Long userId); + + void deletePost(Long postId, Long userId); + + Page getMyPostList(Long userId, Integer page); + + Post_like likePost(Long postId, HttpServletRequest request); + + void dislikePost(Long postId, HttpServletRequest request); + + void checkPostScrap(Boolean flag); + + PostResponseDTO.PollPostGetListDTO getBestPosts(Integer page, Integer size, HttpServletRequest request); + + PostResponseDTO.PollPostGetListDTO getRecentPosts(Integer page, Integer size, HttpServletRequest request); + + Post_scrap createScrapPost(Long postId, HttpServletRequest request); + + void deleteScrapPost(Long postId, HttpServletRequest request); + + + PostResponseDTO.ReportResult createReportPost(Long postId, PostRequestDTO.PostReportReq request, HttpServletRequest request2); + +} diff --git a/src/main/java/friend/spring/service/PostServiceImpl.java b/src/main/java/friend/spring/service/PostServiceImpl.java new file mode 100644 index 0000000..325fa71 --- /dev/null +++ b/src/main/java/friend/spring/service/PostServiceImpl.java @@ -0,0 +1,516 @@ +package friend.spring.service; + +import friend.spring.apiPayload.GeneralException; +import friend.spring.apiPayload.code.status.ErrorStatus; +import friend.spring.apiPayload.handler.PostHandler; +import friend.spring.apiPayload.handler.UserHandler; +import friend.spring.converter.PostConverter; +import friend.spring.domain.*; +import friend.spring.domain.Redis.SearchLog; +import friend.spring.domain.enums.PostState; +import friend.spring.domain.enums.PostType; +import friend.spring.domain.enums.ReportType; +import friend.spring.domain.enums.S3ImageType; +import friend.spring.domain.mapping.Post_like; +import friend.spring.domain.mapping.Post_scrap; +import friend.spring.repository.*; +import friend.spring.security.JwtTokenProvider; +import friend.spring.web.dto.*; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.*; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import javax.servlet.http.HttpServletRequest; +import java.util.*; + +import static friend.spring.apiPayload.code.status.ErrorStatus.*; +import static friend.spring.domain.enums.PostType.*; +import static friend.spring.domain.enums.PostVoteType.*; + + +@Service +@RequiredArgsConstructor +public class PostServiceImpl implements PostService { + + private final PostRepository postRepository; + private final General_PollRepository generalPollRepository; + private final UserRepository userRepository; + private final CandidateRepository candidateRepository; + private final Gauge_PollRepository gaugePollRepository; + private final Card_PollRepository cardPollRepository; + private final PointRepository pointRepository; + private final CategoryRepository categoryRepository; + private final ReportCategoryRepository reportCategoryRepository; + private final ReportRepository reportRepository; + + private final PostLikeRepository postLikeRepository; + private final CommentRepository commentRepository; + private final PostScrapRepository postScrapRepository; + private final UserService userService; + private final JwtTokenProvider jwtTokenProvider; + + private final S3Service s3Service; + + @Override + public void checkPost(Boolean flag) { + if (!flag) { + throw new PostHandler(POST_NOT_FOUND); + } + } + + @Override + public void checkPostWriterUser(Boolean flag) { + if (!flag) { + throw new PostHandler(POST_NOT_CORRECT_USER); + } + } + + @Override + public Boolean checkPoint(PostRequestDTO.AddPostDTO request, User user) { + if (request.getPoint() > user.getPoint()) { + return Boolean.FALSE; + } + return Boolean.TRUE; + } + + @Override + public void checkPostLike(Boolean flag) { + if (!flag) { + throw new PostHandler(POST_LIKE_NOT_FOUND); + } else { + throw new PostHandler(POST_LIKE_DUPLICATE); + } + } + + @Override + public void checkPostScrap(Boolean flag) { + if (!flag) { + throw new PostHandler(POST_SCRAP_NOT_FOUND); + } else { + throw new PostHandler(POST_SCRAP_DUPLICATE); + } + } + + @Override + @Transactional + public Post joinPost(PostRequestDTO.AddPostDTO request, HttpServletRequest request2) { + Long userId = jwtTokenProvider.getCurrentUser(request2); + + Post newPost = PostConverter.toPost(request); + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(USER_NOT_FOUND)); + newPost.setUser(user); + + // 글 첨부파일 사진 저장 + if (request.getFileBase64List() != null) { + s3Service.uploadPostImagesBase64(request.getFileBase64List(), S3ImageType.POST, newPost); + } + LocalDateTime deadline = request.getDeadline(); + if (request.getDeadline() == null) { + deadline = LocalDateTime.now().plusHours(1); + } + + //일반 투표 api + if (newPost.getPostType() == VOTE) { + newPost.setCategory(categoryRepository.findByName(request.getCategory())); + + } + + if (newPost.getPostType() == VOTE && newPost.getVoteType() == GENERAL) { + //포인트 차감 관련 코드 + if (request.getPoint() != null) { + if (!checkPoint(request, user)) { + throw new GeneralException(NOT_ENOUGH_POINT); + } + user.setPoint(user.getPoint() - request.getPoint()); + Point newPoint = Point.builder() + .amount(request.getPoint() * -1) // 차감이므로 -1 곱해서 음수로 변환 + .content("일반 투표 작성에 대한 " + request.getPoint() + " 포인트 차감") + .build(); + newPoint.setUser(user); + pointRepository.save(newPoint); + } + + General_poll generalPoll = General_poll.builder() + .pollTitle(request.getPollTitle()) + .deadline(deadline) + .build(); + if (request.getMultipleChoice() != null) { + generalPoll.setMultipleChoice(request.getMultipleChoice()); + } + newPost.setGeneralPoll(generalPoll); + generalPollRepository.save(generalPoll); + + } +//카드 투표 api + if (newPost.getPostType() == VOTE && newPost.getVoteType() == CARD) { + //포인트 차감 관련 코드 + if (request.getPoint() != null) { + if (!checkPoint(request, user)) { + throw new GeneralException(NOT_ENOUGH_POINT); + } + user.setPoint(user.getPoint() - request.getPoint()); + Point newPoint = Point.builder() + .amount(request.getPoint() * -1) + .content("게이지 투표 등록에 대한 " + request.getPoint() + " 포인트 차감") + .build(); + newPoint.setUser(user); + pointRepository.save(newPoint); + } + + if (!checkPoint(request, user) && request.getPoint() != null) { + throw new GeneralException(NOT_ENOUGH_POINT); + } + + Card_poll cardPoll = Card_poll.builder() + .pollTitle(request.getPollTitle()) + .deadline(deadline) + .build(); + if (request.getMultipleChoice() != null) { + cardPoll.setMultipleChoice(request.getMultipleChoice()); + } + newPost.setCardPoll(cardPoll); + cardPollRepository.save(cardPoll); + } +//게이지 투표 api + if (newPost.getPostType() == VOTE && newPost.getVoteType() == GAUGE) { + //포인트 차감 관련 코드 + if (request.getPoint() != null) { + if (!checkPoint(request, user)) { + throw new GeneralException(NOT_ENOUGH_POINT); + } + user.setPoint(user.getPoint() - request.getPoint()); + Point newPoint = Point.builder() + .amount(request.getPoint() * -1) + .content("카드 투표 등록에 대한 " + request.getPoint() + " 포인트 차감") + .build(); + newPoint.setUser(user); + pointRepository.save(newPoint); + } + + if (!checkPoint(request, user) && request.getPoint() != null) { + throw new GeneralException(NOT_ENOUGH_POINT); + } + + Gauge_poll gaugePoll = Gauge_poll.builder() + .pollTitle(request.getPollTitle()) + .gauge(0) + .deadline(deadline) + .build(); + + newPost.setGaugePoll(gaugePoll); + gaugePollRepository.save(gaugePoll); + } + + if (newPost.getPostType() == REVIEW && request.getParent_id() != null) { + Post parent = postRepository.findById(request.getParent_id()) + .orElseThrow(() -> new GeneralException(POST_NOT_FOUND)); + if (!userId.equals(parent.getUser().getId())) { + throw new GeneralException(POST_NOT_CORRECT_USER); + } + newPost.setParentPost(parent); + } + + return postRepository.save(newPost); + + } + + @Override + @Transactional + public Candidate createCandidate(Long postId, CandidateRequestDTO.AddCandidateRequestDTO request, HttpServletRequest request2) throws IOException { + Post newPost = postRepository.findById(postId).orElseThrow(() -> new GeneralException(POST_NOT_FOUND)); + + Long userId = jwtTokenProvider.getCurrentUser(request2); + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(USER_NOT_FOUND)); + if (!newPost.getUser().equals(user)) { // 이 글을 쓴 사용자인지 검증 + this.checkPostWriterUser(false); + } + + if (!(!request.getOptionString().isEmpty() && request.getOptionString().length() < 30)) { + throw new GeneralException(CANDIDATE_TEXT_LIMIT); + } + + Candidate candidate = Candidate.builder() + .name(request.getOptionString()) + .build(); + + candidateRepository.save(candidate); + + if (request.getOptionImg() != null) { + File candidateFile = s3Service.uploadSingleImageBase64(request.getOptionImg(), S3ImageType.CANDIDATE, null, candidate); + candidate.setFile(candidateFile); + } + + // 일반 투표 + if (newPost.getPostType() == VOTE && newPost.getVoteType() == GENERAL) { + General_poll generalPoll = generalPollRepository.findById(newPost.getGeneralPoll().getId()).orElseThrow(() -> new GeneralException(POST_GENERAL_POLL_NOT_FOUND)); + candidate.setGeneralPoll(generalPoll); + } + // 카드 투표 + else { + Card_poll cardPoll = cardPollRepository.findById(newPost.getCardPoll().getId()).orElseThrow(() -> new GeneralException(POST_CARD_POLL_NOT_FOUND)); + candidate.setCardPoll(cardPoll); + } + + return candidate; + } + + @Override + @Transactional + public void editPost(Long postId, PostRequestDTO.PostEditReq request, Long userId) { + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + Post post = postRepository.findById(postId).orElseThrow(() -> new GeneralException(POST_NOT_FOUND)); + if (!user.getId().equals(post.getUser().getId())) { + throw new GeneralException(POST_NOT_CORRECT_USER); + } + if (post.getIsFixed() >= 2) { + throw new GeneralException(TOO_MUCH_FIXED); + } + if (!(request.getDeadline() == null)) { + LocalDateTime currentDeadLine; + if (post.getVoteType() == GENERAL) { + currentDeadLine = post.getGeneralPoll().getDeadline(); + long daysUntilDeadline = ChronoUnit.DAYS.between(currentDeadLine, request.getDeadline()); + if (request.getDeadline().isBefore(LocalDateTime.now())) { + throw new GeneralException(DEADLINE_LIMIT); + } + if (!(daysUntilDeadline <= 30)) { + throw new GeneralException(DEADLINE_LIMIT); + } + post.getGeneralPoll().setDeadline(request.getDeadline()); + } + if (post.getVoteType() == CARD) { + currentDeadLine = post.getCardPoll().getDeadline(); + long daysUntilDeadline = ChronoUnit.DAYS.between(currentDeadLine, request.getDeadline()); + if (request.getDeadline().isBefore(LocalDateTime.now())) { + throw new GeneralException(DEADLINE_LIMIT); + } + if (!(daysUntilDeadline <= 30)) { + throw new GeneralException(DEADLINE_LIMIT); + } + post.getCardPoll().setDeadline(request.getDeadline()); + } + if (post.getVoteType() == GAUGE) { + currentDeadLine = post.getGaugePoll().getDeadline(); + long daysUntilDeadline = ChronoUnit.DAYS.between(currentDeadLine, request.getDeadline()); + if (request.getDeadline().isBefore(LocalDateTime.now())) { + throw new GeneralException(DEADLINE_LIMIT); + } + if (!(daysUntilDeadline <= 30)) { + throw new GeneralException(DEADLINE_LIMIT); + } + post.getGaugePoll().setDeadline(request.getDeadline()); + } + } + if (!request.getVoteOnGoing()) { + if (post.getVoteType() == GENERAL) { + post.getGeneralPoll().setVoteOnGoing(false); + } + if (post.getVoteType() == CARD) { + post.getCardPoll().setVoteOnGoing(false); + } + if (post.getVoteType() == GAUGE) { + post.getGaugePoll().setVoteOnGoing(false); + } + } + post.setTitle(request.getTitle()); + post.setContent(request.getContent()); + post.setIsFixed(post.getIsFixed() + 1); + } + + @Override + @Transactional + public void deletePost(Long postId, Long userId) { + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + Post post = postRepository.findById(postId).orElseThrow(() -> new GeneralException(POST_NOT_FOUND)); + if (!user.getId().equals(post.getUser().getId())) { + throw new GeneralException(POST_NOT_CORRECT_USER); + } + post.setStateDel(); + } + + + //한 유저의 모든 질문글 + @Override + public Page getMyPostList(Long userId, Integer page) { + Optional user = userRepository.findById(userId); + if (user.isEmpty()) { + throw new UserHandler(ErrorStatus.USER_NOT_FOUND); + } + + User myUser = user.get(); + + Page userPage = postRepository.findAllByUser(myUser, PageRequest.of(page, 5)); + return userPage; + } + + @Override + public Post_like likePost(Long postId, HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + + Optional optionalPost = postRepository.findById(postId); + if (optionalPost.isEmpty()) { + this.checkPost(false); + } + + Optional optionalUser = userRepository.findById(userId); + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + + Post post = optionalPost.get(); + User user = optionalUser.get(); + + Post_like post_like = PostConverter.toPostLike(post, user); + Optional optionalPost_like = postLikeRepository.findByPostIdAndUserId(postId, userId); + if (!optionalPost_like.isEmpty()) { + this.checkPostLike(true); + } + + return postLikeRepository.save(post_like); + } + + @Override + public void dislikePost(Long postId, HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + + Optional optionalPost = postRepository.findById(postId); + if (optionalPost.isEmpty()) { + this.checkPost(false); + } + + Optional optionalUser = userRepository.findById(userId); + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + + Optional optionalPost_like = postLikeRepository.findByPostIdAndUserId(postId, userId); + if (optionalPost_like.isEmpty()) { + this.checkPostLike(false); + } + + Post_like post_like = optionalPost_like.get(); + postLikeRepository.delete(post_like); + } + + @Override + public PostResponseDTO.PollPostGetListDTO getBestPosts(Integer page, Integer size, HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + Optional optionalUser = userRepository.findById(userId); + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + + LocalDateTime minusDays = LocalDateTime.now().minusDays(7); // 7일 이내 + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "point")); + Page bestPostPage = postRepository.findByPostTypeAndStateAndCreatedAtAfter(PostType.VOTE, PostState.POSTING, minusDays, pageable); + + return PostConverter.pollPostGetListDTO(bestPostPage, userId); + } + + @Override + public PostResponseDTO.PollPostGetListDTO getRecentPosts(Integer page, Integer size, HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(USER_NOT_FOUND)); + + LocalDateTime minusDays = LocalDateTime.now().minusDays(7); // 7일 이내(임시) + Pageable pageable = PageRequest.of(page, size); + Page recentPostPage = postRepository.findPostsOrderByVoteCount(PostType.VOTE, PostState.POSTING, minusDays, user.getId(), pageable); + + // 랜덤으로 섞기 + List postList = new ArrayList<>(recentPostPage.getContent()); + // 리스트를 랜덤하게 섞기 + Collections.shuffle(postList); + // 섞인 리스트를 페이지로 다시 변환 + Page shuffledPage = new PageImpl<>(postList, recentPostPage.getPageable(), postList.size()); + + return PostConverter.pollPostGetListDTO(shuffledPage, userId); + } + + @Override + public Post_scrap createScrapPost(Long postId, HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + + Optional optionalPost = postRepository.findById(postId); + if (optionalPost.isEmpty()) { + this.checkPost(false); + } + + Optional optionalUser = userRepository.findById(userId); + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + + Post post = optionalPost.get(); + User user = optionalUser.get(); + + Post_scrap post_scrap = PostConverter.toPostScrap(post, user); + Optional optionalPost_scrap = postScrapRepository.findByPostIdAndUserId(postId, userId); + if (!optionalPost_scrap.isEmpty()) { + this.checkPostScrap(true); + } + return postScrapRepository.save(post_scrap); + } + + @Override + public void deleteScrapPost(Long postId, HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + Optional optionalPost = postRepository.findById(postId); + if (optionalPost.isEmpty()) { + this.checkPost(false); + } + + Optional optionalUser = userRepository.findById(userId); + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + + Optional optionalPost_scrap = postScrapRepository.findByPostIdAndUserId(postId, userId); + if (optionalPost_scrap.isEmpty()) { + this.checkPostScrap(false); + } + + Post_scrap post_scrap = optionalPost_scrap.get(); + postScrapRepository.delete(post_scrap); + } + + @Override + public PostResponseDTO.ReportResult createReportPost(Long postId, PostRequestDTO.PostReportReq request, HttpServletRequest request2) { + Long userId = jwtTokenProvider.getCurrentUser(request2); + + Optional optionalPost = postRepository.findById(postId); + if (optionalPost.isEmpty()) { + this.checkPost(false); + } + + Optional optionalUser = userRepository.findById(userId); + if (optionalUser.isEmpty()) { + userService.checkUser(false); + } + + Post post = optionalPost.get(); + User user = optionalUser.get(); + + // 이미 신고된 건인지 확인 + Optional optionalReport = reportRepository.findByTargetTypeAndTargetIdAndUserId(ReportType.POST, postId, userId); + if (!optionalReport.isEmpty()) { + return PostResponseDTO.ReportResult.builder() + .report(optionalReport.get()) + .duplicatedReport(true) + .build(); + } + + ReportCategory reportCategory = reportCategoryRepository.findByName(request.getReportCategory()); + Report report = PostConverter.toReportPost(post, user, reportCategory); + return PostResponseDTO.ReportResult.builder() + .report(reportRepository.save(report)) + .duplicatedReport(false) + .build(); + } +} diff --git a/src/main/java/friend/spring/service/S3Service.java b/src/main/java/friend/spring/service/S3Service.java new file mode 100644 index 0000000..2bcf864 --- /dev/null +++ b/src/main/java/friend/spring/service/S3Service.java @@ -0,0 +1,99 @@ +package friend.spring.service; + +import friend.spring.aws.s3.AmazonS3Manager; +import friend.spring.converter.Base64Decoder; +import friend.spring.converter.FileConverter; +import friend.spring.domain.*; +import friend.spring.domain.enums.S3ImageType; +import friend.spring.repository.FileRepository; +import friend.spring.repository.UuidRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Service +@Transactional +@RequiredArgsConstructor +public class S3Service { + private final UuidRepository uuidRepository; + private final AmazonS3Manager s3Manager; + + private final FileRepository fileRepository; + + public List uploadPostImages(List multipartFiles, S3ImageType type, Post post) { + List fileList = new ArrayList<>(); + // forEach 구문을 통해 multipartFiles 리스트로 넘어온 파일들을 순차적으로 fileNameList 에 추가 + multipartFiles.forEach(file -> { + String pictureUrl = s3Manager.uploadFile(s3Manager.generatePostKeyName(createFileName()), file); + File newFile = fileRepository.save(FileConverter.toFile(pictureUrl, null, post, null)); + fileList.add(newFile); + }); + return fileList; + } + + public List uploadPostImagesBase64(List fileBase64List, S3ImageType type, Post post) { + List fileList = new ArrayList<>(); + // forEach 구문을 통해 multipartFiles 리스트로 넘어온 파일들을 순차적으로 fileList 에 추가 + fileBase64List.forEach(base64String -> { + MultipartFile file = null; + try { + file = Base64Decoder.decodeBase64ToMultipartFile(base64String); + String pictureUrl = s3Manager.uploadFile(s3Manager.generatePostKeyName(createFileName()), file); + File newFile = fileRepository.save(FileConverter.toFile(pictureUrl, null, post, null)); + fileList.add(newFile); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + return fileList; + } + + public File uploadSingleImage(MultipartFile file, S3ImageType type, User user, Candidate candidate) { + File newFile; + if (type == S3ImageType.USER && user != null) { // 사용자 프로필 이미지인 경우 + String pictureUrl = s3Manager.uploadFile(s3Manager.generateUserKeyName(createFileName()), file); + newFile = fileRepository.save(FileConverter.toFile(pictureUrl, user, null, null)); + } else { // 후보 이미지인 경우 + String pictureUrl = s3Manager.uploadFile(s3Manager.generateCandidateKeyName(createFileName()), file); + newFile = fileRepository.save(FileConverter.toFile(pictureUrl, null, null, candidate)); + } + return newFile; + } + + public File uploadSingleImageBase64(String fileBase64, S3ImageType type, User user, Candidate candidate) throws IOException { + File newFile; + MultipartFile file = Base64Decoder.decodeBase64ToMultipartFile(fileBase64); + + if (type == S3ImageType.USER && user != null) { // 사용자 프로필 이미지인 경우 + String pictureUrl = s3Manager.uploadFile(s3Manager.generateUserKeyName(createFileName()), file); + newFile = fileRepository.save(FileConverter.toFile(pictureUrl, user, null, null)); + } else { // 후보 이미지인 경우 + String pictureUrl = s3Manager.uploadFile(s3Manager.generateCandidateKeyName(createFileName()), file); + newFile = fileRepository.save(FileConverter.toFile(pictureUrl, null, null, candidate)); + } + return newFile; + } + + // 유저 프로필 사진 변경(이미 데이터가 있는 경우) + @Transactional + public File editSingleImage(MultipartFile file, User user) { + File newFile; + String pictureUrl = s3Manager.uploadFile(s3Manager.generateUserKeyName(createFileName()), file); + newFile = fileRepository.findByUserId(user.getId()).get(); + newFile.setUrl(pictureUrl); + return newFile; + } + + + // 먼저 파일 업로드시, 파일명을 난수화하기 위해 UUID 를 활용하여 난수를 돌린다. + public Uuid createFileName() { + String uuid = UUID.randomUUID().toString(); + return uuidRepository.save(Uuid.builder().uuid(uuid).build()); + } +} diff --git a/src/main/java/friend/spring/service/SchedulerService.java b/src/main/java/friend/spring/service/SchedulerService.java new file mode 100644 index 0000000..5423177 --- /dev/null +++ b/src/main/java/friend/spring/service/SchedulerService.java @@ -0,0 +1,70 @@ +package friend.spring.service; + +import friend.spring.apiPayload.GeneralException; +import friend.spring.apiPayload.code.status.ErrorStatus; +import friend.spring.converter.AlarmConverter; +import friend.spring.converter.SseConverter; +import friend.spring.domain.*; +import friend.spring.domain.enums.AlarmType; +import friend.spring.repository.AlarmRepository; +import friend.spring.repository.PostRepository; +import friend.spring.web.dto.SseResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class SchedulerService { + + private final PostRepository postRepository; + private final EmailService emailService; + private final SseService notificationService; + private final AlarmRepository alarmRepository; + + // 투표 마감 + // (1시간마다 검사해서 투표 마감 날짜가 지나면 닫히도록 할 예정) + @Transactional + @Async + @Scheduled(cron = "0 0/1 * * * *", zone = "Asia/Seoul") // 매 분마다 실행 -> 매 시간마다 실행으로 변경 예정 + public void deleteVote() { + List postList = postRepository.findAll(); + if (postList.isEmpty()) { + throw new GeneralException(ErrorStatus.POST_NOT_FOUND); + } + LocalDateTime now = LocalDateTime.now(); + for (Post post : postList) { + Gauge_poll gaugePoll = post.getGaugePoll(); + Card_poll cardPoll = post.getCardPoll(); + General_poll generalPoll = post.getGeneralPoll(); + if (gaugePoll != null && gaugePoll.getDeadline().isBefore(now) && gaugePoll.getVoteOnGoing()) { + gaugePoll.setVoteOnGoing(false); + getData(post); + } + if (cardPoll != null && cardPoll.getDeadline().isBefore(now) && cardPoll.getVoteOnGoing()) { + cardPoll.setVoteOnGoing(false); + getData(post); + } + if (generalPoll != null && generalPoll.getDeadline().isBefore(now) && generalPoll.getVoteOnGoing()) { + generalPoll.setVoteOnGoing(false); + getData(post); + } + } + + } + + private void getData(Post post) { + SseResponseDTO.VoteFinishResDTO voteFinishResDTO = SseConverter.toVoteFinishResDTO(post, AlarmType.VOTE_FINISH); + Alarm newAlarm = AlarmConverter.toAlarm(voteFinishResDTO.getAlarmContent(), AlarmType.VOTE_FINISH, post, post.getUser(), null); + emailService.voteFinishEmail(post); + alarmRepository.saveAndFlush(newAlarm); + notificationService.customNotify(post.getUser().getId(), voteFinishResDTO, post.getTitle(), AlarmType.VOTE_FINISH.toString()); + } + + +} diff --git a/src/main/java/friend/spring/service/SchedulerService2.java b/src/main/java/friend/spring/service/SchedulerService2.java new file mode 100644 index 0000000..0df9f26 --- /dev/null +++ b/src/main/java/friend/spring/service/SchedulerService2.java @@ -0,0 +1,35 @@ +package friend.spring.service; + +import friend.spring.domain.Point; +import friend.spring.repository.PointRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Service +@Slf4j +@RequiredArgsConstructor +public class SchedulerService2 { + private final PointRepository pointRepository; + +// // Point 내역 합계 값과 User 엔티티의 point 값이 일치하는지 주기적으로 확인 +// @Transactional +// @Async +// @Scheduled(cron = "0 0 0 * * *", zone = "Asia/Seoul") // 매일 오전 0시에 실행 +// public void checkPoint() { +// List pointList = pointRepository.findAll(); +// for (Point point : pointList) { +// +// } +// +// +// chatRoomRepository.disabledSafeUpdates(); // safe update 임시 해제 (여러 열 수정 가능하도록) +// chatRoomRepository.deleteChatRoom(); +// } +} diff --git a/src/main/java/friend/spring/service/SseService.java b/src/main/java/friend/spring/service/SseService.java new file mode 100644 index 0000000..dcc330c --- /dev/null +++ b/src/main/java/friend/spring/service/SseService.java @@ -0,0 +1,104 @@ +package friend.spring.service; + +import friend.spring.repository.EmitterRepository; +import friend.spring.security.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +@Service +@RequiredArgsConstructor +public class SseService { + // 기본 타임아웃 설정 + private static final Long DEFAULT_TIMEOUT = 60L * 1000 * 60 * 48; // 1시간 * 48 -> 데모데이 까지만! + + private final EmitterRepository emitterRepository; + private final JwtTokenProvider jwtTokenProvider; + + /** + * 클라이언트가 구독을 위해 호출하는 메서드. + * + * @param request - 구독하는 클라이언트의 사용자 atk가 담긴 + * @return SseEmitter - 서버에서 보낸 이벤트 Emitter + */ + public SseEmitter subscribe(HttpServletRequest request) { + Long userId = jwtTokenProvider.getCurrentUser(request); + SseEmitter emitter = createEmitter(userId); + + sendToClient(userId, "EventStream Created. [userId=" + userId + "]"); + return emitter; + } + + /** + * 서버의 이벤트를 클라이언트에게 보내는 메서드 + * 다른 서비스 로직에서 이 메서드를 사용해 데이터를 Object event에 넣고 전송하면 된다. + * + * @param userId - 메세지를 전송할 사용자의 아이디. + * @param event - 전송할 이벤트 객체. + */ + public void notify(Long userId, Object event) { + sendToClient(userId, event); + } + + public void customNotify(Long userId, T data, String comment, String type) { + sendToClient(userId, data, comment, type); + } + + /** + * 클라이언트에게 데이터를 전송 + * + * @param id - 데이터를 받을 사용자의 아이디. + * @param data - 전송할 데이터. + */ + private void sendToClient(Long id, Object data) { + SseEmitter emitter = emitterRepository.get(id); + if (emitter != null) { + try { + emitter.send(SseEmitter.event() + .id(String.valueOf(id)) + .name("sse") + .data(data)); + } catch (IOException exception) { + emitterRepository.deleteById(id); + emitter.completeWithError(exception); + } + } + } + + private void sendToClient(Long userId, T data, String comment, String type) { + SseEmitter emitter = emitterRepository.get(userId); + if (emitter != null) { + try { + emitter.send(SseEmitter.event() + .id(String.valueOf(userId)) + .name(type) + .data(data) + .comment(comment)); + } catch (IOException e) { + emitterRepository.deleteById(userId); + emitter.completeWithError(e); + } + } + } + + /** + * 사용자 아이디를 기반으로 이벤트 Emitter를 생성 + * + * @param id - 사용자 아이디. + * @return SseEmitter - 생성된 이벤트 Emitter. + */ + private SseEmitter createEmitter(Long id) { + SseEmitter emitter = new SseEmitter(DEFAULT_TIMEOUT); + emitterRepository.save(id, emitter); + + // Emitter가 완료될 때(모든 데이터가 성공적으로 전송된 상태) Emitter를 삭제한다. + emitter.onCompletion(() -> emitterRepository.deleteById(id)); + // Emitter가 타임아웃 되었을 때(지정된 시간동안 어떠한 이벤트도 전송되지 않았을 때) Emitter를 삭제한다. + emitter.onTimeout(() -> emitterRepository.deleteById(id)); + + return emitter; + } +} diff --git a/src/main/java/friend/spring/service/UserService.java b/src/main/java/friend/spring/service/UserService.java new file mode 100644 index 0000000..cdca44f --- /dev/null +++ b/src/main/java/friend/spring/service/UserService.java @@ -0,0 +1,35 @@ +package friend.spring.service; + +import friend.spring.domain.Level; +import friend.spring.domain.User; +import friend.spring.web.dto.TokenDTO; +import friend.spring.web.dto.UserRequestDTO; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + + +public interface UserService { + + User joinUser(UserRequestDTO.UserJoinRequest userJoinRequest); + + List login(UserRequestDTO.UserLoginRequest userLoginRequest); + + User findMyPage(Long id); + + void checkUser(Boolean flag); + + Integer pointCheck(Long id); + + Level nextLevel(Long id); + + String logout(HttpServletRequest request); + + List reissue(HttpServletRequest request); + + void updatePassword(String email, UserRequestDTO.PasswordUpdateReq passwordUpdateReq); + + String getEmail(HttpServletRequest request); + + String deactivateUser(HttpServletRequest request); +} diff --git a/src/main/java/friend/spring/service/UserServiceImpl.java b/src/main/java/friend/spring/service/UserServiceImpl.java new file mode 100644 index 0000000..b81f7d7 --- /dev/null +++ b/src/main/java/friend/spring/service/UserServiceImpl.java @@ -0,0 +1,240 @@ +package friend.spring.service; + +import friend.spring.apiPayload.GeneralException; +import friend.spring.converter.UserConverter; +import friend.spring.apiPayload.code.status.ErrorStatus; +import friend.spring.domain.Level; +import friend.spring.domain.User; +import friend.spring.repository.LevelRepository; +import friend.spring.repository.UserRepository; +import friend.spring.apiPayload.handler.UserHandler; +import friend.spring.security.JwtTokenProvider; +import friend.spring.web.dto.TokenDTO; +import friend.spring.web.dto.UserRequestDTO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import static friend.spring.apiPayload.code.status.ErrorStatus.*; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + + private final UserRepository userRepository; + private final EmailService emailService; + private final LevelRepository levelRepository; + private final JwtTokenProvider jwtTokenProvider; + private final RedisTemplate redisTemplate; + + static final String PASSWORD_PATTERN = "^(?=.*[a-z])(?=.*[A-Z]+)(?=.*\\d)(?=.*[!@#$%^&*]).{8,64}$"; + + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + + @Override + public User findMyPage(Long id) { + Optional user = userRepository.findById(id); + if (user.isEmpty()) { + throw new UserHandler(USER_NOT_FOUND); + } + return user.get(); + } + + @Override + public void checkUser(Boolean flag) { + if (!flag) { + throw new UserHandler(USER_NOT_FOUND); + } + } + + public User joinUser(UserRequestDTO.UserJoinRequest userJoinRequest) {//회원가입 + Optional user = userRepository.findByEmail(userJoinRequest.getEmail()); + + if (user.isPresent()) { + throw new UserHandler(USER_EXISTS_EMAIL); + } + + + String encodedPw = encoder.encode(userJoinRequest.getPassword()); + + Level initLevel = levelRepository.findById(Long.valueOf(1)).get(); + User newUser = UserConverter.toUser(userJoinRequest, encodedPw, initLevel); + + return userRepository.saveAndFlush(newUser); + } + + @Override + public Level nextLevel(Long id) { + Optional user = userRepository.findById(id); + if (user.isEmpty()) { + throw new UserHandler(USER_NOT_FOUND); + } + Long curId = user.get().getLevel().getId(); + Long nxtId = curId + 1; + Level nxtLevel = levelRepository.findById(nxtId).get(); + return nxtLevel; + } + + @Override + public List reissue(HttpServletRequest request) { + String rtk = request.getHeader("rtk"); + System.out.println("reissue함수실행 rtk: " + rtk); + + // refresh token 유효성 검증 + if (!jwtTokenProvider.validateToken(rtk)) + throw new GeneralException(INVALID_JWT); + + String email = jwtTokenProvider.getTokenSub(rtk); + System.out.println("rtk subject인 email: " + email); + + // Redis에서 email 기반으로 저장된 refresh token 값 가져오기 + String refreshToken = (String) redisTemplate.opsForValue().get("RT:" + email); + + if (refreshToken == null) { + throw new GeneralException(RTK_INCORREXT); + } + + if (!refreshToken.equals(rtk)) { + throw new GeneralException(RTK_INCORREXT); + } + + // refresh token 유효할 경우 새로운 토큰 생성 + List tokenDTOList = new ArrayList<>(); + TokenDTO newRefreshToken = jwtTokenProvider.createRefreshToken(email); + TokenDTO newAccessToken = jwtTokenProvider.createAccessToken(email); + tokenDTOList.add(newRefreshToken); + tokenDTOList.add(newAccessToken); + System.out.println("Access Token, Refresh Token 재발행: " + tokenDTOList); + + // Redis에 refresh token 업데이트 + redisTemplate.opsForValue().set("RT:" + email, newRefreshToken.getToken(), newRefreshToken.getTokenExpriresTime().getTime(), TimeUnit.MILLISECONDS); + + return tokenDTOList; + } + + //로그인 + public List login(UserRequestDTO.UserLoginRequest userLoginRequest) throws GeneralException { + User user = userRepository.findByEmail(userLoginRequest.getEmail()) + .orElseThrow(() -> new GeneralException(USERS_NOT_FOUND_EMAIL));//가입 안된 이메일 + if (!encoder.matches(userLoginRequest.getPassword(), user.getPassword())) { + throw new GeneralException(PASSWORD_INCORRECT); //비밀번호 불일치 + } + //토큰발급 + TokenDTO accessToken = jwtTokenProvider.createAccessToken(user.getEmail()); + TokenDTO refreshToken = jwtTokenProvider.createRefreshToken(user.getEmail()); + + // login 시 Redis에 RT:gominchingy@gmail.com(key): --refresh token실제값--(value) 형태로 refresh 토큰 저장하기 + // opsForValue() : set을 통해 key,value값 저장하고 get(key)통해 value가져올 수 있음. + // refreshToken.getTokenExpriresTime().getTime() : 리프레시 토큰의 만료시간이 지나면 해당 값 자동 삭제 + redisTemplate.opsForValue().set("RT:" + user.getEmail(), refreshToken.getToken(), refreshToken.getTokenExpriresTime().getTime(), TimeUnit.MILLISECONDS); + + List tokenDTOList = new ArrayList<>(); + tokenDTOList.add(accessToken); + tokenDTOList.add(refreshToken); + System.out.println(tokenDTOList); + + return tokenDTOList; + } + + @Override + // 로그아웃 - userIdx + public String logout(HttpServletRequest request) { + + Long userIdx = jwtTokenProvider.getCurrentUser(request); + + System.out.println("getCurrentUser()로 가져온 userIdx : " + userIdx); + User user = userRepository.findById(userIdx) + .orElseThrow(() -> new GeneralException(USER_NOT_FOUND)); + + // Redis 에서 해당 User email 로 저장된 Refresh Token 이 있는지 여부를 확인 후 있을 경우 삭제 + if (redisTemplate.opsForValue().get("RT:" + user.getEmail()) != null) { + // Refresh Token 삭제 + redisTemplate.delete("RT:" + user.getEmail()); + } + // 해당 AccessToken 유효시간 가지고 와서 저장하기 + String accessToken = jwtTokenProvider.resolveAccessToken(request); + Long expiration = jwtTokenProvider.getExpireTime(accessToken).getTime(); + // Redis 에 --accesstoken--(key) : logout(value) 로 저장, token 만료시간 지나면 자동 삭제 + redisTemplate.opsForValue().set(accessToken, "logout", expiration, TimeUnit.MILLISECONDS); + + return "로그아웃 성공"; + } + + @Override + public Integer pointCheck(Long id) { + User user = userRepository.findById(id) + .orElseThrow(() -> new RuntimeException("\"" + id + "\"해당 유저가 없습니다")); + return user.getPoint(); + } + + private boolean isValidPassword(String password) { + return password.matches(PASSWORD_PATTERN); + } + + //비밀번호 변경 + public void updatePassword(String email, UserRequestDTO.PasswordUpdateReq passwordUpdateReq) throws GeneralException { + User user = userRepository.findByEmail(email).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + + String newPassword = passwordUpdateReq.getNewPassword(); + + if (!isValidPassword(newPassword)) { + throw new GeneralException(INVALID_PASSWORD_FORMAT); + } + if (!passwordUpdateReq.getNewPassword().equals(passwordUpdateReq.getNewPasswordCheck())) { + throw new GeneralException(ErrorStatus.PASSWORD_CHECK_INCORRECT); + } + + // 새로운 비밀번호가 null이 아닌 경우, 사용자의 비밀번호를 새로운 값으로 업데이트 + if (newPassword != null) { + user.setPassword(encoder.encode(newPassword)); + } + userRepository.save(user); + + } + + // Request 의 Header 에서 email 값 추출 "email" : "gominchingu@gmail.com" + public String getEmail(HttpServletRequest request) { + return request.getHeader("email"); + } + + // 회원 탈퇴 - userIdx + @Transactional + public String deactivateUser(HttpServletRequest request) throws GeneralException { + Long userIdx = jwtTokenProvider.getCurrentUser(request); + + System.out.println("getCurrentUser()로 가져온 userIdx : "+userIdx); + User user = userRepository.findById(userIdx) + .orElseThrow(() -> new GeneralException(USER_NOT_FOUND)); + + // Redis에 로그인되어있는 토큰 삭제 + // Redis 에서 해당 User email 로 저장된 Refresh Token 이 있는지 여부를 확인 후 있을 경우 삭제 + if (redisTemplate.opsForValue().get("RT:" + user.getEmail()) != null) { + // Refresh Token 삭제 + redisTemplate.delete("RT:" + user.getEmail()); + } + String accessToken = jwtTokenProvider.resolveAccessToken(request); + // 탈퇴한 토큰을 차단 (deactivateUser 토큰 블랙리스트) + Long expiration = jwtTokenProvider.getExpireTime(accessToken).getTime(); + // Redis 에 --accesstoken--(key) : logout(value) 로 저장, token 만료시간 지나면 자동 삭제 + redisTemplate.opsForValue().set(accessToken, "logout", expiration, TimeUnit.MILLISECONDS); + +// userRepository.deleteById(user.getId()); + user.setIsDeleted(true); + userRepository.save(user); + + return "회원 탈퇴 성공"; + } +} + + diff --git a/src/main/java/friend/spring/service/VoteService.java b/src/main/java/friend/spring/service/VoteService.java new file mode 100644 index 0000000..391a8d8 --- /dev/null +++ b/src/main/java/friend/spring/service/VoteService.java @@ -0,0 +1,16 @@ +package friend.spring.service; + +import friend.spring.domain.Card_vote; +import friend.spring.domain.Gauge_vote; +import friend.spring.domain.General_vote; +import friend.spring.web.dto.VoteRequestDTO; + +public interface VoteService { + + General_vote castGeneralVote(VoteRequestDTO.GeneralVoteRequestDTO request, Long PostId, Long userId); + + Gauge_vote castGaugeVote(VoteRequestDTO.GaugeVoteRequestDTO request, Long PostId, Long userId); + + Card_vote castCardVote(VoteRequestDTO.CardVoteRequestDTO request, Long PostId, Long userId); + +} diff --git a/src/main/java/friend/spring/service/VoteServiceImpl.java b/src/main/java/friend/spring/service/VoteServiceImpl.java new file mode 100644 index 0000000..faa2c6a --- /dev/null +++ b/src/main/java/friend/spring/service/VoteServiceImpl.java @@ -0,0 +1,176 @@ +package friend.spring.service; + +import friend.spring.apiPayload.GeneralException; +import friend.spring.apiPayload.code.status.ErrorStatus; +import friend.spring.converter.VoteConverter; +import friend.spring.domain.*; +import friend.spring.repository.*; +import friend.spring.web.dto.VoteRequestDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class VoteServiceImpl implements VoteService { + private final UserRepository userRepository; + private final General_VoteRepository generalVoteRepository; + private final Gauge_VoteRepository gaugeVoteRepository; + private final Card_VoteRepository cardVoteRepository; + private final PostRepository postRepository; + private final PointRepository pointRepository; + private final Gauge_PollRepository gaugePollRepository; + + @Override + @Transactional + public General_vote castGeneralVote(VoteRequestDTO.GeneralVoteRequestDTO request, Long PostId, Long userId) { + General_vote newGeneralVote = VoteConverter.toGeneralVote(request); + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + newGeneralVote.setUser(user); + + Post post = postRepository.findById(PostId) + .orElseThrow(() -> new GeneralException(ErrorStatus.POST_NOT_FOUND)); + + if (post.getGeneralPoll().getGeneralVoteList().stream().anyMatch(generalVote -> generalVote.getUser().getId().equals(userId))) { + throw new GeneralException(ErrorStatus.ALREADY_VOTE); + } + + General_poll generalPoll = post.getGeneralPoll(); + newGeneralVote.setGeneralPoll(generalPoll); + + List selectedCandidateIds = request.getSelectList(); + List validCandidateIds = generalPoll.getCandidateList().stream() + .map(Candidate::getId) + .collect(Collectors.toList()); + for (Long selectedId : selectedCandidateIds) { + if (!validCandidateIds.contains(selectedId)) { + throw new GeneralException(ErrorStatus.CANDIDATE_NOT_FOUND); + } + } + + if (request.getSelectList() != null) { + newGeneralVote.setSelect_list(request.getSelectList()); + } + //나노초 단위로 마감 여부 확인 + LocalDateTime now = LocalDateTime.now(); + long nanosUntilDeadline = ChronoUnit.NANOS.between(now, post.getGeneralPoll().getDeadline()); + Boolean voteOnGoing = nanosUntilDeadline > 0; + if (!voteOnGoing) { + throw new GeneralException(ErrorStatus.DEADLINE_OVER); + } + + user.setPoint(user.getPoint() + 5); + Point newPoint = Point.builder() + .amount(5) + .content("일반 투표에 대한 " + 5 + " 포인트 획득") + .build(); + newPoint.setUser(user); + pointRepository.save(newPoint); + + return generalVoteRepository.save(newGeneralVote); + } + + @Override + @Transactional + public Gauge_vote castGaugeVote(VoteRequestDTO.GaugeVoteRequestDTO request, Long PostId, Long userId) { + Gauge_vote newGaugeVote = VoteConverter.toGaugeVote(request); + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + newGaugeVote.setUser(user); + + Post post = postRepository.findById(PostId) + .orElseThrow(() -> new GeneralException(ErrorStatus.POST_NOT_FOUND)); + + if (post.getGaugePoll().getGaugeVoteList().stream().anyMatch(gaugeVote -> gaugeVote.getUser().getId().equals(userId))) { + throw new GeneralException(ErrorStatus.ALREADY_VOTE); + } + Gauge_poll gaugePoll = post.getGaugePoll(); + newGaugeVote.setGaugePoll(gaugePoll); + + + //나노초 단위로 마감 여부 확인 + LocalDateTime now = LocalDateTime.now(); + long nanosUntilDeadline = ChronoUnit.NANOS.between(now, post.getGaugePoll().getDeadline()); + Boolean voteOnGoing = nanosUntilDeadline > 0; + if (!voteOnGoing) { + throw new GeneralException(ErrorStatus.DEADLINE_OVER); + } + + user.setPoint(user.getPoint() + 5); + Point newPoint = Point.builder() + .amount(5) + .content("게이지 투표에 대한 " + 5 + " 포인트 획득") + .build(); + newPoint.setUser(user); + pointRepository.save(newPoint); + + Integer value = request.getValue(); + gaugePollRepository.findById(PostId); + Optional optionalPost = postRepository.findById(PostId); + Post gaugePost = optionalPost.get(); + Integer currentGauge = gaugePost.getGaugePoll().getGauge(); + Integer engagedUser = gaugePost.getGaugePoll().getGaugeVoteList().size(); + int result1 = (int) Math.round((currentGauge * (engagedUser - 1.0) + value) / (engagedUser)); + Integer result = (Integer) result1; + gaugePost.getGaugePoll().setGauge(result); + return gaugeVoteRepository.save(newGaugeVote); + } + + @Override + @Transactional + public Card_vote castCardVote(VoteRequestDTO.CardVoteRequestDTO request, Long PostId, Long userId) { + Card_vote newCardVote = VoteConverter.toCardVote(request); + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + newCardVote.setUser(user); + + Post post = postRepository.findById(PostId) + .orElseThrow(() -> new GeneralException(ErrorStatus.POST_NOT_FOUND)); + if (post.getCardPoll().getCardVoteList().stream().anyMatch(cardVote -> cardVote.getUser().getId().equals(userId))) { + throw new GeneralException(ErrorStatus.ALREADY_VOTE); + } + Card_poll cardPoll = post.getCardPoll(); + newCardVote.setCardPoll(cardPoll); + + List selectedCandidateIds = request.getSelectList(); + List validCandidateIds = cardPoll.getCandidateList().stream() + .map(Candidate::getId) + .collect(Collectors.toList()); + for (Long selectedId : selectedCandidateIds) { + if (!validCandidateIds.contains(selectedId)) { + throw new GeneralException(ErrorStatus.CANDIDATE_NOT_FOUND); + } + } + + if (request.getSelectList() != null) { + newCardVote.setSelect_list(request.getSelectList()); + } + + //나노초 단위로 마감 여부 확인 + LocalDateTime now = LocalDateTime.now(); + long nanosUntilDeadline = ChronoUnit.NANOS.between(now, post.getCardPoll().getDeadline()); + Boolean voteOnGoing = nanosUntilDeadline > 0; + if (!voteOnGoing) { + throw new GeneralException(ErrorStatus.DEADLINE_OVER); + } + + user.setPoint(user.getPoint() + 5); + Point newPoint = Point.builder() + .amount(5) + .content("카드 투표에 대한 " + 5 + " 포인트 획득") + .build(); + newPoint.setUser(user); + pointRepository.save(newPoint); + + return cardVoteRepository.save(newCardVote); + } + +} diff --git a/src/main/java/friend/spring/validation/annotation/ContentTextLimit.java b/src/main/java/friend/spring/validation/annotation/ContentTextLimit.java new file mode 100644 index 0000000..6b750c1 --- /dev/null +++ b/src/main/java/friend/spring/validation/annotation/ContentTextLimit.java @@ -0,0 +1,19 @@ +package friend.spring.validation.annotation; + +import friend.spring.validation.validator.ContentTextLimitValidator; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = ContentTextLimitValidator.class) +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ContentTextLimit { + String message() default "최소 5자 이상, 1000자 미만 입력해 주세요."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/main/java/friend/spring/validation/annotation/DeadlineLimit.java b/src/main/java/friend/spring/validation/annotation/DeadlineLimit.java new file mode 100644 index 0000000..9264f2c --- /dev/null +++ b/src/main/java/friend/spring/validation/annotation/DeadlineLimit.java @@ -0,0 +1,19 @@ +package friend.spring.validation.annotation; + +import friend.spring.validation.validator.DeadlineLimitValidator; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = DeadlineLimitValidator.class) +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DeadlineLimit { + String message() default "Deadline must be between 1 minute and 30 days from now."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/main/java/friend/spring/validation/annotation/TitleTextLimit.java b/src/main/java/friend/spring/validation/annotation/TitleTextLimit.java new file mode 100644 index 0000000..895eff9 --- /dev/null +++ b/src/main/java/friend/spring/validation/annotation/TitleTextLimit.java @@ -0,0 +1,19 @@ +package friend.spring.validation.annotation; + +import friend.spring.validation.validator.TitleTextLimitValidator; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = TitleTextLimitValidator.class) +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface TitleTextLimit { + String message() default "최소 5자 이상, 30자 미만 입력해 주세요."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/main/java/friend/spring/validation/validator/ContentTextLimitValidator.java b/src/main/java/friend/spring/validation/validator/ContentTextLimitValidator.java new file mode 100644 index 0000000..2acfae5 --- /dev/null +++ b/src/main/java/friend/spring/validation/validator/ContentTextLimitValidator.java @@ -0,0 +1,30 @@ +package friend.spring.validation.validator; + +import friend.spring.apiPayload.code.status.ErrorStatus; +import friend.spring.validation.annotation.ContentTextLimit; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +@Component +@RequiredArgsConstructor +public class ContentTextLimitValidator implements ConstraintValidator { + @Override + public void initialize(ContentTextLimit constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + boolean isValid = value.length() >= 5 && value.length() < 1000; + if (!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.CONTENT_TEXT_LIMIT.getMessage()).addConstraintViolation(); + } + + return isValid; + } + +} diff --git a/src/main/java/friend/spring/validation/validator/DeadlineLimitValidator.java b/src/main/java/friend/spring/validation/validator/DeadlineLimitValidator.java new file mode 100644 index 0000000..031abfd --- /dev/null +++ b/src/main/java/friend/spring/validation/validator/DeadlineLimitValidator.java @@ -0,0 +1,31 @@ +package friend.spring.validation.validator; + +import friend.spring.apiPayload.code.status.ErrorStatus; +import friend.spring.validation.annotation.DeadlineLimit; + + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; + +public class DeadlineLimitValidator implements ConstraintValidator { + + @Override + public boolean isValid(LocalDateTime deadline, ConstraintValidatorContext context) { + if (deadline == null) { + return true; + } + + LocalDateTime now = LocalDateTime.now(); + long minutesUntilDeadline = ChronoUnit.MINUTES.between(now, deadline); + long daysUntilDeadline = ChronoUnit.DAYS.between(now, deadline); + + boolean isValid = minutesUntilDeadline >= 1 && daysUntilDeadline <= 30; + if (!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.DEADLINE_LIMIT.getMessage()).addConstraintViolation(); + } + return isValid; + } +} diff --git a/src/main/java/friend/spring/validation/validator/TitleTextLimitValidator.java b/src/main/java/friend/spring/validation/validator/TitleTextLimitValidator.java new file mode 100644 index 0000000..81e99da --- /dev/null +++ b/src/main/java/friend/spring/validation/validator/TitleTextLimitValidator.java @@ -0,0 +1,30 @@ +package friend.spring.validation.validator; + +import friend.spring.apiPayload.code.status.ErrorStatus; +import friend.spring.validation.annotation.TitleTextLimit; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +@Component +@RequiredArgsConstructor +public class TitleTextLimitValidator implements ConstraintValidator { + + @Override + public void initialize(TitleTextLimit constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + boolean isValid = value.length() >= 5 && value.length() < 30; + if (!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.TITLE_TEXT_LIMIT.getMessage()).addConstraintViolation(); + } + + return isValid; + } +} diff --git a/src/main/java/friend/spring/web/controller/AlarmRestController.java b/src/main/java/friend/spring/web/controller/AlarmRestController.java new file mode 100644 index 0000000..ab0517b --- /dev/null +++ b/src/main/java/friend/spring/web/controller/AlarmRestController.java @@ -0,0 +1,81 @@ +package friend.spring.web.controller; + +import friend.spring.apiPayload.ApiResponse; +import friend.spring.converter.AlarmConverter; +import friend.spring.domain.Alarm; +import friend.spring.service.AlarmService; +import friend.spring.service.JwtTokenService; +import friend.spring.web.dto.AlarmResponseDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/user") +public class AlarmRestController { + + private final AlarmService alarmService; + private final JwtTokenService jwtTokenService; + + //알림 조회 + @GetMapping("/alarm") + @Operation(summary = "사용자 알림 조회 API", description = "사용자의 알림 목록을 조회하는 API이며, 페이징을 포함합니다. query String 으로 page 번호를 주세요") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "ALARM4001", description = "NOT_FOUND, 알림을 찾을 수 없습니다."), + + }) + @Parameters({ + @Parameter(name = "page", description = "query string(RequestParam) - 몇번째 페이지인지 가리키는 page 변수 (0부터 시작)") + }) + private ApiResponse getAlarm( + @RequestHeader(name = "atk") String atk, + HttpServletRequest request, + @RequestParam(name = "page") Integer page) { + Long userId = jwtTokenService.JwtToId(request); + Page alarmList = alarmService.getAlarmList(userId, page); + return ApiResponse.onSuccess(AlarmConverter.toAlarmListResDTO(alarmList)); + } + + // 홈 - 안 읽은 알림 존재 여부 조회 + @GetMapping("/alarm/notReadAlarm") + @Operation(summary = "홈 - 안 읽은 알림 존재 여부 조회 API", description = "사용자가 안 읽은 알림이 존재하는지 여부를 조회하는 API입니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공") + }) + @Parameters({}) + private ApiResponse getRemainingAlarm( + @RequestHeader(name = "atk") String atk, + HttpServletRequest request + ) { + return ApiResponse.onSuccess(alarmService.getRemainingAlarm(request)); + } + + // 알림 읽음 처리 + @PatchMapping("/alarm/{alarm-id}") + @Operation(summary = "사용자 알림 읽음 처리 API", description = "사용자의 알림을 읽음 처리하는 API입니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "ALARM4001", description = "NOT_FOUND, 알림을 찾을 수 없습니다."), + }) + @Parameters({ + @Parameter(name = "alarm-id", description = "path variable - 알람 아이디"), + }) + private ApiResponse editAlarmRead( + @PathVariable("alarm-id") Long alarmId, + @RequestHeader(name = "atk") String atk, + HttpServletRequest request + ) { + alarmService.editAlarmRead(alarmId, request); + return ApiResponse.onSuccess(null); + } +} diff --git a/src/main/java/friend/spring/web/controller/CommentRestController.java b/src/main/java/friend/spring/web/controller/CommentRestController.java new file mode 100644 index 0000000..ea05999 --- /dev/null +++ b/src/main/java/friend/spring/web/controller/CommentRestController.java @@ -0,0 +1,226 @@ +package friend.spring.web.controller; + +import friend.spring.apiPayload.ApiResponse; +import friend.spring.converter.CommentConverter; +import friend.spring.converter.PostConverter; +import friend.spring.domain.Comment; +import friend.spring.domain.Report; +import friend.spring.domain.mapping.Comment_choice; +import friend.spring.domain.mapping.Comment_like; +import friend.spring.service.CommentService; +import friend.spring.web.dto.CommentRequestDTO; +import friend.spring.web.dto.CommentResponseDTO; +import friend.spring.web.dto.PostResponseDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/posts") +public class CommentRestController { + + private final CommentService commentService; + + // 댓글 작성 + @PostMapping("/{post-id}/comment") + @Operation(summary = "댓글 작성 API", description = "댓글 작성하는 API입니다. ex) /posts/1/comment") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 사용자를 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4001", description = "NOT_FOUND, 글을 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMENT4001", description = "NOT_FOUND, (부모 루트) 댓글을 찾을 수 없습니다."), + }) + @Parameters({ + @Parameter(name = "post-id", description = "path variable - 글 아이디"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse createComment( + @PathVariable("post-id") Long postId, + @RequestBody @Valid CommentRequestDTO.commentCreateReq requestBody, + @RequestHeader(name = "atk") String atk, + HttpServletRequest request + ) { + Comment comment = commentService.createComment(postId, requestBody, request); + return ApiResponse.onSuccess(CommentConverter.toCommentCreateRes(comment)); + } + + // 댓글 추천(좋아요) + @PostMapping("/{post-id}/comment/{comment-id}/like") + @Operation(summary = "댓글 추천(좋아요) 생성 API", description = "댓글 추천(좋아요) 생성하는 API입니다. ex) /posts/1/comment/1/like") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 사용자를 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4001", description = "NOT_FOUND, 글을 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMENT4001", description = "NOT_FOUND, 댓글을 찾을 수 없습니다."), + }) + @Parameters({ + @Parameter(name = "post-id", description = "path variable - 글 아이디"), + @Parameter(name = "comment-id", description = "path variable - 댓글 아이디"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse likeComment( + @PathVariable("post-id") Long postId, + @PathVariable("comment-id") Long commentId, + @RequestHeader(name = "atk") String atk, + HttpServletRequest request + ) { + Comment_like comment_like = commentService.likeComment(postId, commentId, request); + return ApiResponse.onSuccess(CommentConverter.toCommentLikeRes(comment_like)); + } + + // 댓글 추천(좋아요) 해제 + @DeleteMapping("/{post-id}/comment/{comment-id}/like/del") + @Operation(summary = "댓글 추천(좋아요) 해제 API", description = "댓글 추천(좋아요) 해제하는 API입니다. ex) /posts/1/comment/1/like/del") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 사용자를 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4001", description = "NOT_FOUND, 글을 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMENT4001", description = "NOT_FOUND, 댓글을 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMENT4002", description = "NOT_FOUND, 댓글에 대한 좋아요 데이터를 찾을 수 없습니다."), + }) + @Parameters({ + @Parameter(name = "post-id", description = "path variable - 글 아이디"), + @Parameter(name = "comment-id", description = "path variable - 댓글 아이디"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse dislikeComment( + @PathVariable("post-id") Long postId, + @PathVariable("comment-id") Long commentId, + @RequestHeader(name = "atk") String atk, + HttpServletRequest request + ) { + commentService.dislikeComment(postId, commentId, request); + return ApiResponse.onSuccess(null); + } + + // 댓글 조회 + @GetMapping("/{post-id}/comments") + @Operation(summary = "댓글 조회 API", description = "댓글을 조회하는 API입니다. ex) /posts/111/comments") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4001", description = "NOT_FOUND, 글을 찾을 수 없습니다."), + }) + @Parameters({ + @Parameter(name = "post-id", description = "path variable - 글 아이디") + }) + public ApiResponse> getComments( + @PathVariable("post-id") Long postId, + @RequestHeader(name = "atk") String atk, + HttpServletRequest request + ) { + return ApiResponse.onSuccess(commentService.getComments(postId, request)); + } + + // 댓글 채택 + @PostMapping("/{post-id}/comment/{comment-id}/choice") + @Operation(summary = "댓글 채택 API", description = "댓글 채택 데이터를 생성하는 API입니다. ex) /posts/1/comment/1/choice") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 사용자를 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4001", description = "NOT_FOUND, 글을 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4002", description = "BAD_REQUEST, 올바른 사용자(글 작성자)가 아닙니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMENT4001", description = "NOT_FOUND, 댓글을 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMENT4003", description = "BAD_REQUEST, 댓글 채택은 1개 댓글에 대해서만 가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMENT4004", description = "BAD_REQUEST, 자기 자신은 채택할 수 없습니다."), + }) + @Parameters({ + @Parameter(name = "post-id", description = "path variable - 글 아이디"), + @Parameter(name = "comment-id", description = "path variable - 댓글 아이디"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse selectComment( + @PathVariable("post-id") Long postId, + @PathVariable("comment-id") Long commentId, + @RequestHeader(name = "atk") String atk, + HttpServletRequest request + ) { + Comment_choice comment_choice = commentService.selectComment(postId, commentId, request); + return ApiResponse.onSuccess(CommentConverter.toCommentSelectRes(comment_choice)); + } + + // 댓글 수정 + @PatchMapping("/{post-id}/comment/{comment-id}/edit") + @Operation(summary = "댓글 수정 API", description = "댓글 수정하는 API입니다. ex) /posts/1/comment/1/edit") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 사용자를 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4001", description = "NOT_FOUND, 글을 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMENT4001", description = "NOT_FOUND, 댓글을 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMENT4005", description = "올바른 사용자(댓글 작성자)가 아닙니다."), + }) + @Parameters({ + @Parameter(name = "post-id", description = "path variable - 글 아이디"), + @Parameter(name = "comment-id", description = "path variable - 댓글 아이디"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse editComment( + @PathVariable("post-id") Long postId, + @PathVariable("comment-id") Long commentId, + @RequestBody CommentRequestDTO.commentEditReq requestBody, + @RequestHeader(name = "atk") String atk, + HttpServletRequest request + ) { + commentService.editComment(postId, commentId, requestBody, request); + return ApiResponse.onSuccess(null); + } + + // 댓글 삭제 + @PatchMapping("/{post-id}/comment/{comment-id}/del") + @Operation(summary = "댓글 삭제 API", description = "댓글 삭제하는 API입니다. ex) /posts/1/comment/1/del") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 사용자를 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4001", description = "NOT_FOUND, 글을 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMENT4001", description = "NOT_FOUND, 댓글을 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMENT4005", description = "올바른 사용자(댓글 작성자)가 아닙니다."), + }) + @Parameters({ + @Parameter(name = "post-id", description = "path variable - 글 아이디"), + @Parameter(name = "comment-id", description = "path variable - 댓글 아이디"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse deleteComment( + @PathVariable("post-id") Long postId, + @PathVariable("comment-id") Long commentId, + @RequestHeader(name = "atk") String atk, + HttpServletRequest request + ) { + commentService.deleteComment(postId, commentId, request); + return ApiResponse.onSuccess(null); + } + + // 댓글 신고 + @PostMapping("/posts/{post-id}/comment/{comment-id}/report") + @Operation(summary = "댓글 신고 API", description = "댓글 신고하는 API입니다. ex) /posts/1/report") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 사용자를 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4001", description = "NOT_FOUND, 글을 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMENT4008", description = "BAD_REQUEST, 이 유저가 해당 댓글을 신고한 신고 내역 데이터가 이미 존재합니다."), + }) + @Parameters({ + @Parameter(name = "post-id", description = "path variable - 글 아이디"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse createReportComment( + @PathVariable("comment-id") Long commentId, + @RequestBody CommentRequestDTO.CommentReportReq request, + @RequestHeader("atk") String atk, + HttpServletRequest request2 + ) { + PostResponseDTO.ReportResult commentReport = commentService.createReportComment(commentId, request, request2); + return ApiResponse.onSuccess(CommentConverter.toCommentReportRes(commentReport)); + } +} diff --git a/src/main/java/friend/spring/web/controller/MyPageRestController.java b/src/main/java/friend/spring/web/controller/MyPageRestController.java new file mode 100644 index 0000000..0f58343 --- /dev/null +++ b/src/main/java/friend/spring/web/controller/MyPageRestController.java @@ -0,0 +1,316 @@ +package friend.spring.web.controller; + +import friend.spring.apiPayload.ApiResponse; +import friend.spring.converter.CommentConverter; +import friend.spring.converter.MyPageConverter; +import friend.spring.domain.*; +import friend.spring.domain.mapping.Comment_choice; +import friend.spring.service.*; +import friend.spring.web.dto.CommentResponseDTO; +import friend.spring.web.dto.MyPageRequestDTO; +import friend.spring.web.dto.MyPageResponseDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/user/my-page") +public class MyPageRestController { + private final UserService userService; + private final PostService postService; + private final MyPageService myPageService; + + private final JwtTokenService jwtTokenService; + private final EmailService emailService; + + //저장한 게시물(내 카테고리) 조회 + @GetMapping("/post") + @Operation(summary = "사용자 글 카테고리 조회 API", description = "사용자의 저장 글 카테고리 목록을 집합으로 조회하는 API입니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "회원정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4004", description = "카테고리를 찾을 수 없습니다.") + + }) + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse getCategorySet( + @RequestHeader(name = "atk") String atk, + HttpServletRequest request) { + Long userId = jwtTokenService.JwtToId(request); + List categoryList = myPageService.getCategoryList(userId); + return ApiResponse.onSuccess(MyPageConverter.toSavedCategoryResDTO(categoryList)); + } + + //저장한 게시물(모든게시물) + @GetMapping("/post/all") + @Operation(summary = "저장한 게시물(모든게시물) 조회 API", description = "사용자의 전체 저장 글을 조회하는 API입니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "회원정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4001", description = "글을 찾을 수 없습니다.."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4005", description = "저장한 글이 없습니다.") + + }) + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + @Parameter(name = "page", description = "query string(RequestParam) - 몇번째 페이지인지 가리키는 page 변수 (0부터 시작)"), + @Parameter(name = "sort", description = "query string(RequestParam) - 조회순(0)인지 최신순(1)인지 가리키는 sort 변수") + + }) + public ApiResponse getAllSavedPosts( + @RequestParam(name = "page", defaultValue = "0") Integer page, + @RequestParam(name = "sort", defaultValue = "0") Integer sort, + @RequestHeader(name = "atk") String atk, + HttpServletRequest request) { + Long userId = jwtTokenService.JwtToId(request); + Page allPostList = myPageService.getAllPostList(userId, page, sort); + return ApiResponse.onSuccess(MyPageConverter.toSavedAllPostResDTO(allPostList)); + } + + // 회원정보 사용자 프로필 사진 수정 + @PatchMapping(value = "/profile/modify/image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Operation(summary = "회원정보 사용자 프로필 사진 수정 API", description = "사용자 프로필 사진을 수정하는 API입니다. ex) /user/my-page/profile/modify/image") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 사용자를 찾을 수 없습니다."), + }) + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse editUserImage( + @RequestParam("file") MultipartFile file, + @RequestHeader(name = "atk") String atk, + HttpServletRequest request + ) { + myPageService.editUserImage(file, request); + return ApiResponse.onSuccess(null); + } + + // 회원정보 수정 페이지 + @GetMapping(value = "/profile/modify") + @Operation(summary = "회원정보 수정 페이지 API", description = "회원정보 수정 페이지를 조회하는 API입니다. ex) /user/my-page/profile/modify") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 사용자를 찾을 수 없습니다."), + }) + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse getEditUserPage( + @RequestHeader(name = "atk") String atk, + HttpServletRequest request + ) { + Long userId = jwtTokenService.JwtToId(request); + User editUserPage = myPageService.getEditUserPage(userId); + return ApiResponse.onSuccess(MyPageConverter.toMyProfileResDTO(editUserPage)); + } + + @PatchMapping(value = "/profile/modify/name") + @Operation(summary = "회원정보 이름 수정 API", description = "회원정보 이름을 수정하는 API입니다. ex) /user/my-page/profile/modify/name") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다. "), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4005", description = "UNAUTHORIZED, 인증 코드가 일치하지 않습니다."), + }) + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken") + }) + public ApiResponse editUserName( + @RequestHeader(name = "atk") String atk, + HttpServletRequest request, + @RequestBody @Valid MyPageRequestDTO.ProfileEditNameReq profileEditNameReq) { + Long userId = jwtTokenService.JwtToId(request); + emailService.CheckAuthNum(profileEditNameReq.getEmail(), profileEditNameReq.getCertification()); + User editUserName = myPageService.editUserName(userId, profileEditNameReq); + return ApiResponse.onSuccess(MyPageConverter.toProfileEditNameResDTO(editUserName)); + } + + @PatchMapping(value = "/profile/modify/email") + @Operation(summary = "회원정보 이메일 수정 API", description = "회원정보 이메일을 수정하는 API입니다. ex) /user/my-page/profile/modify/email") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다. "), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4005", description = "UNAUTHORIZED, 인증 코드가 일치하지 않습니다."), + }) + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken") + }) + public ApiResponse editUserEmail( + @RequestHeader(name = "atk") String atk, + HttpServletRequest request, + @RequestBody @Valid MyPageRequestDTO.ProfileEditEmailReq profileEditEmailReq) { + Long userId = jwtTokenService.JwtToId(request); + emailService.CheckAuthNum(profileEditEmailReq.getCurEmail(), profileEditEmailReq.getCertification()); + User editUserEmail = myPageService.editUserEmail(userId, profileEditEmailReq); + return ApiResponse.onSuccess(MyPageConverter.toProfileEditEmailResDTO(editUserEmail)); + } + + @PatchMapping(value = "/profile/modify/phone") + @Operation(summary = "회원정보 번호 수정 API", description = "회원정보 번호를 수정하는 API입니다. ex) /user/my-page/profile/modify/phone") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다. "), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4005", description = "UNAUTHORIZED, 인증 코드가 일치하지 않습니다."), + }) + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken") + }) + public ApiResponse editUserPhone( + @RequestHeader(name = "atk") String atk, + HttpServletRequest request, + @RequestBody @Valid MyPageRequestDTO.ProfileEditPhoneReq profileEditPhoneReq) { + Long userId = jwtTokenService.JwtToId(request); + emailService.CheckAuthNum(profileEditPhoneReq.getEmail(), profileEditPhoneReq.getCertification()); + User editUserPhone = myPageService.editUserPhone(userId, profileEditPhoneReq); + return ApiResponse.onSuccess(MyPageConverter.toProfileEditPhoneResDTO(editUserPhone)); + } + + @PatchMapping(value = "/profile/modify/password") + @Operation(summary = "회원정보 비밀번호 수정 API", description = "회원정보 비밀번호를 수정하는 API입니다. ex) /user/my-page/profile/modify/password") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다. "), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4008", description = "NOT_FOUND, 비밀번호가 틀렸습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4009", description = "NOT_FOUND, 확인 비밀번호가 일치하지 않습니다."), + }) + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken") + }) + public ApiResponse editUserPassword( + @RequestHeader(name = "atk") String atk, + HttpServletRequest request, + @RequestBody @Valid MyPageRequestDTO.ProfileEditPasswordReq profileEditPasswordReq) { + Long userId = jwtTokenService.JwtToId(request); + User editUserPassword = myPageService.editUserPassword(userId, profileEditPasswordReq); + return ApiResponse.onSuccess(MyPageConverter.toProfileEditPasswordResDTO(editUserPassword)); + } + + @PatchMapping(value = "/profile/modify/security") + @Operation(summary = "회원정보 보안 메일 수정 API", description = "회원정보 보안 메일을 수정하는 API입니다. ex) /user/my-page/profile/modify/security") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다. "), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4002", description = "이미 존재하는 메일 주소입니다.") + }) + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken") + }) + public ApiResponse editUserSecurity( + @RequestHeader(name = "atk") String atk, + HttpServletRequest request, + @RequestBody @Valid MyPageRequestDTO.ProfileEditSecurityReq profileEditSecurityReq) { + Long userId = jwtTokenService.JwtToId(request); + emailService.CheckAuthNum(profileEditSecurityReq.getCurEmail(), profileEditSecurityReq.getCertification()); + emailService.CheckAuthNum(profileEditSecurityReq.getChangeEmail(), profileEditSecurityReq.getNxtCertification()); + User editUserSecurity = myPageService.editUserSecurity(userId, profileEditSecurityReq); + return ApiResponse.onSuccess(MyPageConverter.toProfileEditEmailResDTO(editUserSecurity)); + } + + @PatchMapping(value = "/inquiry") + @Operation(summary = "설정 문의하기 API", description = "문의를 작성하는 API입니다. ex) /user/my-page/inquiry") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다. "), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4007", description = "최소 5자 이상, 30자 미만 입력해 주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4008", description = "최소 5자 이상, 1000자 미만 입력해 주세요.") + }) + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken") + }) + public ApiResponse editUserSecurity( + @RequestHeader(name = "atk") String atk, + HttpServletRequest request, + @RequestBody @Valid MyPageRequestDTO.MyInquiryReq myInquiryReq) { + Long userId = jwtTokenService.JwtToId(request); + Inquiry inquiry = myPageService.createInquiry(userId, myInquiryReq); + return ApiResponse.onSuccess(MyPageConverter.toMyInquiryRes(inquiry)); + } + + @GetMapping(value = "/post/{category-id}") + @Operation(summary = "사용자 글 카테고리 상세보기 조회 API", description = "카테고리별로 상세화면을 조회하는 API입니다. ex) /user/my-page/post/{category-id}") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다. "), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 사용자를 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4004", description = "NOT_FOUND, 글에 대한 스크랩 데이터를 찾을 수 없습니다."), + + }) + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + @Parameter(name = "page", description = "query string(RequestParam) - 몇번째 페이지인지 가리키는 page 변수 (0부터 시작)"), + @Parameter(name = "category-id", description = "path variable - 카테고리 아이디"), + }) + public ApiResponse GetUserCategoryDetail( + @RequestHeader(name = "atk") String atk, + @PathVariable("category-id") Long categoryId, + @RequestParam(name = "page", defaultValue = "0") Integer page, + HttpServletRequest request) { + Long userId = jwtTokenService.JwtToId(request); + Page categoryDetailList = myPageService.getCategoryDetailList(userId, categoryId, page); + Category category = myPageService.getCategory(categoryId); + return ApiResponse.onSuccess(MyPageConverter.toSavedPostCategoryDetailListRes(categoryDetailList, category)); + } + + @GetMapping(value = "/setting/notice") + @Operation(summary = "공지사항 리스트 조회 API", description = "전체 공지사항의 리스트를 조회하는 API입니다. ex) /user/my-page/setting/notice") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다. "), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "NOTICE4001", description = "NOT_FOUND, 공지사항이 없습니다."), + }) + @Parameters({ + @Parameter(name = "page", description = "query string(RequestParam) - 몇번째 페이지인지 가리키는 page 변수 (0부터 시작)"), + }) + public ApiResponse getNoticeList( + @RequestParam(name = "page", defaultValue = "0") Integer page) { + Page noticeList = myPageService.getNoticeList(30L, page); + return ApiResponse.onSuccess(MyPageConverter.toNoticeListRes(noticeList)); + } + + @GetMapping(value = "/setting/notice/{notice-id}") + @Operation(summary = "공지사항 상세 조회 API", description = "전체 공지사항 상세내용을 조회하는 API입니다. ex) /user/my-page/setting/notice/{notice-id}") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다. "), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "NOTICE4001", description = "NOT_FOUND, 공지사항이 없습니다."), + }) + @Parameters({ + @Parameter(name = "notice-id", description = "path variable - 공지사항 아이디"), + }) + public ApiResponse getNoticeDetail( + @PathVariable("notice-id") Long noticeId + ) { + Notice noticeDetail = myPageService.getNoticeDetail(noticeId); + return ApiResponse.onSuccess(MyPageConverter.toNoticeDetailRes(noticeDetail)); + } + + @GetMapping(value = "/setting/term") + @Operation(summary = "이용약관 조회 API", description = "이용약관을 조회하는 API입니다. ex) /user/my-page/setting/term") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다. "), + }) + @Parameters({ + }) + public ApiResponse getTerm( + ) { + Term term = myPageService.getTerm(30L); + return ApiResponse.onSuccess(MyPageConverter.toTermRes(term)); + } + + @GetMapping(value = "/setting/privacy") + @Operation(summary = "개인정보 처리방침 조회 API", description = "개인정보 처리방침을 조회하는 API입니다. ex) /user/my-page/setting/privacy") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다. "), + }) + @Parameters({ + }) + public ApiResponse getPrivacy( + ) { + Term term = myPageService.getPrivacy(30L); + return ApiResponse.onSuccess(MyPageConverter.toPrivacyRes(term)); + } +} diff --git a/src/main/java/friend/spring/web/controller/PostRestController.java b/src/main/java/friend/spring/web/controller/PostRestController.java new file mode 100644 index 0000000..2d01c30 --- /dev/null +++ b/src/main/java/friend/spring/web/controller/PostRestController.java @@ -0,0 +1,371 @@ +package friend.spring.web.controller; + +import friend.spring.apiPayload.ApiResponse; +import friend.spring.converter.CandidateConverter; +import friend.spring.converter.PostConverter; +import friend.spring.domain.Candidate; +import friend.spring.domain.Post; +import friend.spring.domain.Redis.SearchLog; +import friend.spring.repository.PostRepository; +import friend.spring.service.JwtTokenService; +import friend.spring.service.PostQueryService; +import friend.spring.service.PostService; +import friend.spring.web.dto.*; +import friend.spring.domain.mapping.Post_like; +import friend.spring.domain.mapping.Post_scrap; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequiredArgsConstructor +@Validated +@RequestMapping("/posts") +public class PostRestController { + private final PostService postService; + private final PostQueryService postQueryService; + private final PostRepository postRepository; + private final JwtTokenService jwtTokenService; + + @PostMapping(value = "/", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Operation(summary = "글 작성 API", description = "글을 추가 합니다.") + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + @Parameter(name = "title", description = " 글 제목"), + @Parameter(name = "content", description = " 글 내용"), + @Parameter(name = "category", description = "카테고리. 한글 입력(ex 교육)"), + @Parameter(name = "postType", description = " 글 종류
1 : VOTE
2 : REVIEW"), + @Parameter(name = "postVoteType", description = " 투표 종류
1 : GENERAL
2 : GAUGE
3 : CARD
해당 사항 없을시 null"), + @Parameter(name = "pollTitle", description = " 투표 제목"), + @Parameter(name = "multipleChoice", description = " 복수 선택 여부"), + @Parameter(name = "parent_id", description = " 원글(후기글 경우) id
해당 사항 없을시 null"), + @Parameter(name = "deadline", description = " 투표 마감 시간
해당 사항 없을시 null(기본값 1시간 이후)"), + @Parameter(name = "point", description = " 포인트
해당 사항 없을시 null") + + }) + public ApiResponse join(@RequestPart(value = "request") @Valid PostRequestDTO.AddPostDTO request, +// @RequestPart(value = "file", required = false) List file, + @RequestHeader("atk") String atk, + HttpServletRequest request2) { + Post post = postService.joinPost(request, request2); + return ApiResponse.onSuccess(PostConverter.toAddPostResultDTO(post)); + } + + @PostMapping(value = "/{post-id}") + @Operation(summary = "후보 생성 API", description = "후보를 생성합니다.(글 작성 API 호출 후 postId 응답 받으시면, 후보 개수 만큼 바로 호출해주세요!)") + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + @Parameter(name = "pollOption", description = " 투표 후보{optionString : string, optionImg : MultipartFile}
해당 사항 없을시 null"), + }) + public ApiResponse createCandidate( + @PathVariable(name = "post-id") Long postId, + @RequestBody CandidateRequestDTO.AddCandidateRequestDTO request, + @RequestHeader("atk") String atk, + HttpServletRequest request2) throws IOException { + Candidate candidate = postService.createCandidate(postId, request, request2); + return ApiResponse.onSuccess(CandidateConverter.toAddCandidateResultDTO(candidate)); + } + + + @GetMapping("/poll-postList") + @Operation(summary = "후기글 작성시 내투표 보기 API", description = "후기글 작성시 내투표 보기합니다.") + @Parameters({ + @Parameter(name = "page", description = "query string(RequestParam) - 몇번째 페이지인지 가리키는 page 변수 입니다! (0부터 시작)"), + @Parameter(name = "size", description = "query string(RequestParam) - 몇 개씩 불러올지 개수를 세는 변수입니다. (1 이상 자연수로 설정)"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken") + }) + public ApiResponse getParentPosts(@RequestParam(name = "page", defaultValue = "0") Integer page, + @RequestParam(name = "size", defaultValue = "15") Integer size, + @RequestHeader("atk") String atk, + HttpServletRequest request2) { + Page postPage = postQueryService.getParentPostList(page, size, request2); + Long userId = jwtTokenService.JwtToId(request2); + return ApiResponse.onSuccess(PostConverter.parentPostGetListDTO(postPage, userId)); + + } + + @GetMapping("/{post-id}") + @Operation(summary = "글 상세 보기 API", description = "글 상세 보기합니다
response로 나오는 isLike, isComment는 각각 조회하는 사용자의 좋아요 클릭 여부, " + + "댓글 작성 여부입니다..") + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse getPostDetail(@PathVariable(name = "post-id") Long PostId, + @RequestHeader("atk") String atk, + HttpServletRequest request2) { + Long userId = jwtTokenService.JwtToId(request2); + Post post = postQueryService.getPostDetail(PostId); + Post parentPost = postQueryService.ParentPost(PostId); + Boolean engage = postQueryService.checkEngage(PostId, userId); + return ApiResponse.onSuccess(PostConverter.postDetailResponse(post, engage, userId, parentPost)); + + } + + @GetMapping("/poll-post/{category}") + @Operation(summary = "고민글 전체 보기 API", description = "고민글 전체 보기합니다") + @Parameters({ + @Parameter(name = "page", description = "query string(RequestParam) - 몇번째 페이지인지 가리키는 page 변수 입니다! (0부터 시작)"), + @Parameter(name = "size", description = "query string(RequestParam) - 몇 개씩 불러올지 개수를 세는 변수입니다. (1 이상 자연수로 설정)"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + @Parameter(name = "category", description = "path variable - category(한글). 모두 보기는 '모두'라고 입력 하시면 됩니다.") + }) + public ApiResponse getPostAll(@RequestParam(name = "page", defaultValue = "0") Integer page, + @RequestParam(name = "size", defaultValue = "15") Integer size, + @PathVariable(name = "category") String category, + @RequestHeader("atk") String atk, + HttpServletRequest request2) { + Long userId = jwtTokenService.JwtToId(request2); + Page postPage = postQueryService.getPostList(page, size, category); + return ApiResponse.onSuccess(PostConverter.pollPostGetListDTO(postPage, userId)); + + } + + @GetMapping("/review-post") + @Operation(summary = "후기글 전체 보기 API", description = "후기글 전체 보기합니다") + @Parameters({ + @Parameter(name = "arrange", description = "query string(RequestParam) - 정렬 기준 변수입니다. (0: 조회순,1: 최신순) 디폴트값 0"), + @Parameter(name = "page", description = "query string(RequestParam) - 몇번째 페이지인지 가리키는 page 변수 입니다! (0부터 시작) 디폴트값 0"), + @Parameter(name = "size", description = "query string(RequestParam) - 몇 개씩 불러올지 개수를 세는 변수입니다. (1 이상 자연수로 설정) 디폴트값 15"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse getReviewAll(@RequestParam(name = "arrange", defaultValue = "0") Integer arrange, + @RequestParam(name = "page", defaultValue = "0") Integer page, + @RequestParam(name = "size", defaultValue = "15") Integer size, + @RequestHeader("atk") String atk, + HttpServletRequest request2) { + Long userId = jwtTokenService.JwtToId(request2); + Page postPage = postQueryService.getReviewList(page, size, arrange); + return ApiResponse.onSuccess(PostConverter.reviewPostGetListDTO(postPage, userId)); + } + + @PatchMapping("/{post-id}/edit") + @Operation(summary = "글 수정 API", description = "댓글 수정하는 API입니다. ex) /posts/1/comment/1/edit") + @Parameters({ + @Parameter(name = "post-id", description = "path variable - 글 아이디"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse editPost(@PathVariable("post-id") Long postId, + @RequestBody @Valid PostRequestDTO.PostEditReq request, + @RequestHeader("atk") String atk, + HttpServletRequest request2) { + Long userId = jwtTokenService.JwtToId(request2); + postService.editPost(postId, request, userId); + return ApiResponse.onSuccess(null); + } + + @PatchMapping("/{post-id}/del") + @Operation(summary = "글 삭제 API", description = "글 삭제하는 API입니다. ex) /posts/1/del") + @Parameters({ + @Parameter(name = "post-id", description = "path variable - 글 아이디"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse deleteComment( + @PathVariable("post-id") Long postId, + @RequestHeader("atk") String atk, + HttpServletRequest request2 + ) { + Long userId = jwtTokenService.JwtToId(request2); + postService.deletePost(postId, userId); + return ApiResponse.onSuccess(null); + } + + // 글 추천(좋아요) 생성 + @PostMapping("/{post-id}/like") + @Operation(summary = "글 추천(좋아요) 생성 API", description = "글 추천(좋아요) 생성하는 API입니다. ex) /posts/1/like") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 사용자를 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4001", description = "NOT_FOUND, 글을 찾을 수 없습니다."), + }) + @Parameters({ + @Parameter(name = "post-id", description = "path variable - 글 아이디"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse likePost( + @PathVariable("post-id") Long postId, + @RequestHeader(name = "atk") String atk, + HttpServletRequest request + ) { + Post_like post_like = postService.likePost(postId, request); + return ApiResponse.onSuccess(PostConverter.toPostLikeRes(post_like)); + } + + // 글 추천(좋아요) 해제 + @DeleteMapping("/{post-id}/like/del") + @Operation(summary = "글 추천(좋아요) 해제 API", description = "글 추천(좋아요) 해제하는 API입니다. ex) /posts/1/like/del") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 사용자를 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4001", description = "NOT_FOUND, 글을 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4003", description = "글에 대한 좋아요 데이터를 찾을 수 없습니다."), + }) + @Parameters({ + @Parameter(name = "post-id", description = "path variable - 글 아이디"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse dislikePost( + @PathVariable("post-id") Long postId, + @RequestHeader(name = "atk") String atk, + HttpServletRequest request + ) { + postService.dislikePost(postId, request); + return ApiResponse.onSuccess(null); + } + + // 홈 - 지금 가장 핫한 고민투표 + @GetMapping("/best") + @Operation(summary = "홈 - 지금 가장 핫한 고민투표 API", description = "홈 - 지금 가장 핫한 고민투표 조회 API입니다. ex) /posts/best") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + }) + @Parameters({ + @Parameter(name = "page", description = "query string(RequestParam) - 몇번째 페이지인지 가리키는 page 변수 (0부터 시작)"), + @Parameter(name = "size", description = "query string(RequestParam) - 한 번에 글 몇 개씩 불러올지 개수를 세는 변수 (1 이상 자연수로 설정)"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse getBestPosts( + @RequestParam(name = "page") Integer page, + @RequestParam(name = "size") Integer size, + @RequestHeader("atk") String atk, + HttpServletRequest request + ) { + return ApiResponse.onSuccess(postService.getBestPosts(page, size, request)); + } + + // 홈 - 답변을 기다리는 고민들 + @GetMapping("/recent") + @Operation(summary = "홈 - 답변을 기다리는 고민들 API", description = "홈 - 답변을 기다리는 고민들 조회 API입니다. ex) /posts/recent") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + }) + @Parameters({ + @Parameter(name = "page", description = "query string(RequestParam) - 몇번째 페이지인지 가리키는 page 변수 (0부터 시작)"), + @Parameter(name = "size", description = "query string(RequestParam) - 한 번에 글 몇 개씩 불러올지 개수를 세는 변수 (1 이상 자연수로 설정)"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse getRecentPosts( + @RequestParam(name = "page") Integer page, + @RequestParam(name = "size") Integer size, + @RequestHeader("atk") String atk, + HttpServletRequest request + ) { + return ApiResponse.onSuccess(postService.getRecentPosts(page, size, request)); + } + + // 글 스크랩 추가 + @PostMapping("/{post-id}/scrap") + @Operation(summary = "글 스크랩 추가 API", description = "글 스크랩 추가하는 API입니다. ex) /posts/1/scrap") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 사용자를 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4001", description = "NOT_FOUND, 글을 찾을 수 없습니다."), + }) + @Parameters({ + @Parameter(name = "post-id", description = "path variable - 글 아이디"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse createScrapPost( + @PathVariable("post-id") Long postId, + @RequestHeader(name = "atk") String atk, + HttpServletRequest request + ) { + Post_scrap post_scrap = postService.createScrapPost(postId, request); + return ApiResponse.onSuccess(PostConverter.toScrapCreateRes(post_scrap)); + } + + // 글 스크랩 해제 + @DeleteMapping("/{post-id}/scrap/del") + @Operation(summary = "글 스크랩 해제 API", description = "글 스크랩 해제하는 API입니다. ex) /posts/1/scrap/del") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 사용자를 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4001", description = "NOT_FOUND, 글을 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4004", description = "글에 대한 스크랩 데이터를 찾을 수 없습니다."), + }) + @Parameters({ + @Parameter(name = "post-id", description = "path variable - 글 아이디"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse deleteScrapPost( + @PathVariable("post-id") Long postId, + @RequestHeader(name = "atk") String atk, + HttpServletRequest request + ) { + postService.deleteScrapPost(postId, request); + return ApiResponse.onSuccess(null); + } + + // 글 신고 + @PostMapping("/posts/{post-id}/report") + @Operation(summary = "글 신고 API", description = "글 신고하는 API입니다. ex) /posts/1/report") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 사용자를 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4001", description = "NOT_FOUND, 글을 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4018", description = "BAD_REQUEST, 이 유저가 해당 글을 신고한 신고 내역 데이터가 이미 존재합니다."), + }) + @Parameters({ + @Parameter(name = "post-id", description = "path variable - 글 아이디"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse createReportPost( + @PathVariable("post-id") Long postId, + @RequestBody PostRequestDTO.PostReportReq request, + @RequestHeader("atk") String atk, + HttpServletRequest request2 + ) { + PostResponseDTO.ReportResult postReport = postService.createReportPost(postId, request, request2); + return ApiResponse.onSuccess(PostConverter.toPostReportRes(postReport)); + } + + // 글 검색 + @GetMapping("/post/search/") + @Operation(summary = "글 검색 API", description = "글을 검색합니다") + @Parameters({ + @Parameter(name = "page", description = "query string(RequestParam) - 몇번째 페이지인지 가리키는 page 변수 입니다! (0부터 시작)"), + @Parameter(name = "size", description = "query string(RequestParam) - 몇 개씩 불러올지 개수를 세는 변수입니다. (1 이상 자연수로 설정)"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + @Parameter(name = "keyword", description = "query string(RequestParam) - 검색어.") + }) + public ApiResponse getPostSearch(@RequestParam(name = "page", defaultValue = "0") Integer page, + @RequestParam(name = "size", defaultValue = "15") Integer size, + @RequestParam(name = "keyword") String keyword, + @RequestHeader("atk") String atk, + HttpServletRequest request2) { + Long userId = jwtTokenService.JwtToId(request2); + Page postPage = postQueryService.getPostSearch(userId,page, size, keyword); + return ApiResponse.onSuccess(PostConverter.PostSearchListDTO(postPage, userId)); + } + + @GetMapping("/post/search/recent-log") + @Operation(summary = "최근 검색어 목록 조회 API", description = "최신순으로 검색어 목록을 최대 10개까지 조회합니다.") + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse getRecentSearchLogs(@RequestHeader("atk") String atk, + HttpServletRequest request) { + Long userId = jwtTokenService.JwtToId(request); + List recentSearchLogs = postQueryService.getRecentSearchLogs(userId); + + +// List searchLogs = recentSearchLogs.stream() +// .map(search -> new SearchLog(search, LocalDateTime.now().toString())) +// .collect(Collectors.toList()); + + return ApiResponse.onSuccess(PostConverter.toRecentSearchRes(recentSearchLogs)); + } + +} diff --git a/src/main/java/friend/spring/web/controller/RootController.java b/src/main/java/friend/spring/web/controller/RootController.java new file mode 100644 index 0000000..f15b1a7 --- /dev/null +++ b/src/main/java/friend/spring/web/controller/RootController.java @@ -0,0 +1,13 @@ +package friend.spring.web.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class RootController { + @GetMapping("/health") + public String healthCheck() { + return "I'm healthy!"; + } + +} diff --git a/src/main/java/friend/spring/web/controller/S3Controller.java b/src/main/java/friend/spring/web/controller/S3Controller.java new file mode 100644 index 0000000..e9f9982 --- /dev/null +++ b/src/main/java/friend/spring/web/controller/S3Controller.java @@ -0,0 +1,24 @@ +package friend.spring.web.controller; + +import friend.spring.apiPayload.ApiResponse; +import friend.spring.domain.enums.S3ImageType; +import friend.spring.service.S3Service; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/file") +public class S3Controller { + private final S3Service s3Service; + + // 유저 프로필 이미지 올리기 +// @PostMapping(value = "/uploadImage", consumes = "multipart/form-data") +// public ApiResponse> uploadUserImage(@RequestParam("file") List file) { +// List url = s3Service.uploadImage(file, S3ImageType.USER); +// return ApiResponse.onSuccess(url); +// } +} diff --git a/src/main/java/friend/spring/web/controller/SseController.java b/src/main/java/friend/spring/web/controller/SseController.java new file mode 100644 index 0000000..3737ce8 --- /dev/null +++ b/src/main/java/friend/spring/web/controller/SseController.java @@ -0,0 +1,29 @@ +package friend.spring.web.controller; + +import friend.spring.service.SseService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import javax.servlet.http.HttpServletRequest; + +@RestController +@RequestMapping("/sse") +@RequiredArgsConstructor +public class SseController { + private final SseService notificationService; + + @GetMapping(value = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter subscribe( + @RequestHeader(name = "atk") String atk, + HttpServletRequest request + ) { + return notificationService.subscribe(request); + } + + @PostMapping("/send-data/{id}") + public void sendData(@PathVariable Long id) { + notificationService.notify(id, "data"); + } +} diff --git a/src/main/java/friend/spring/web/controller/UserRestController.java b/src/main/java/friend/spring/web/controller/UserRestController.java new file mode 100644 index 0000000..b762ec4 --- /dev/null +++ b/src/main/java/friend/spring/web/controller/UserRestController.java @@ -0,0 +1,262 @@ +package friend.spring.web.controller; + +import friend.spring.apiPayload.ApiResponse; +import friend.spring.apiPayload.GeneralException; +import friend.spring.converter.UserConverter; +import friend.spring.domain.Comment; +import friend.spring.domain.Level; +import friend.spring.domain.Post; +import friend.spring.domain.User; +import friend.spring.service.AuthService; +import friend.spring.repository.UserRepository; +import friend.spring.service.CommentService; +import friend.spring.service.EmailService; +import friend.spring.service.PostService; +import friend.spring.service.UserService; +import friend.spring.service.*; +import friend.spring.web.dto.*; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import java.util.List; + + +@RestController +@RequiredArgsConstructor +@RequestMapping("/user") +public class UserRestController { + + private final UserService userService; + private final UserRepository userRepository; + private final EmailService mailService; + private final PostService postService; + private final CommentService commentService; + private final JwtTokenService jwtTokenService; + private final AuthService authService; + + //마이 페이지 조회 + @GetMapping("/my-page") + public ApiResponse getMyPage( + @RequestHeader(name = "atk") String atk, + HttpServletRequest request) { + Long userId = jwtTokenService.JwtToId(request); + User myPage = userService.findMyPage(userId); + return ApiResponse.onSuccess(UserConverter.toMypageResDTO(myPage)); + } + + @PostMapping("/mailSend")//이메일 인증 코드 전송 + @Operation(summary = "이메일 인증 코드 전송 API", description = "이메일 인증 코드 전송하는 API입니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + }) + @Parameters({}) + public ApiResponse mailSend(@RequestBody @Valid UserRequestDTO.EmailSendReq emailDto) { + System.out.println("이메일 인증 요청이 들어옴"); + System.out.println("이메일 인증 이메일 :" + emailDto.getEmail()); + + String code = mailService.joinEmail(emailDto.getEmail()); + return ApiResponse.onSuccess(UserConverter.toEmailSendRes(code)); + } + + @PostMapping("/mailauthCheck")//이메일 코드 확인 + @Operation(summary = "이메일 코드 확인 API", description = "이메일 코드 확인하는 API입니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4005", description = "UNAUTHORIZED, 인증 코드가 일치하지 않습니다."), + }) + @Parameters({}) + public ApiResponse mailauthCheck(@RequestBody @Valid UserRequestDTO.EmailSendCheckReq emailSendCheckReq) { + mailService.CheckAuthNum(emailSendCheckReq.getEmail(), emailSendCheckReq.getAuthNum()); + return ApiResponse.onSuccess(null); + } + + //나의 Q&A 질문 조회 + @GetMapping("/my-page/profile/question") + @Operation(summary = "나의 Q&A 질문 조회 API", description = "사용자의 나의 Q&A 질문 조회 API 이며, 페이징을 포함합니다. query String 으로 page 번호를 주세요") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST4001", description = "NOT_FOUND, 글을 찾을 수 없습니다."), + + }) + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + @Parameter(name = "page", description = "query string(RequestParam) - 몇번째 페이지인지 가리키는 page 변수 (0부터 시작)") + }) + public ApiResponse getQuestion( + @RequestHeader(name = "atk") String atk, + HttpServletRequest request, + @RequestParam(name = "page") Integer page) { + Long userId = jwtTokenService.JwtToId(request); + User myPage = userService.findMyPage(userId); + Level nxtLevel = userService.nextLevel(userId); + Page myPostList = postService.getMyPostList(userId, page); + Page myCommentList = commentService.getMyCommentList(userId, page); + return ApiResponse.onSuccess(UserConverter.toQuestionResDTO(myPage, nxtLevel, myPostList, myCommentList)); + } + + //나의 Q&A 답변 조회 + @GetMapping("/my-page/profile/answer") + @Operation(summary = "나의 Q&A 답변 조회 API", description = "사용자의 Q&A 답변 조회 API 이며, 페이징을 포함합니다. query String 으로 page 번호를 주세요") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMENT4001", description = "NOT_FOUND, 댓글을 찾을 수 없습니다."), + + }) + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + @Parameter(name = "page", description = "query string(RequestParam) - 몇번째 페이지인지 가리키는 page 변수 (0부터 시작)") + }) + public ApiResponse getAnswer( + @RequestHeader(name = "atk") String atk, + HttpServletRequest request, + @RequestParam(name = "page") Integer page) { + Long userId = jwtTokenService.JwtToId(request); + User myPage = userService.findMyPage(userId); + Level nxtLevel = userService.nextLevel(userId); + Page myCommentList = commentService.getMyCommentList(userId, page); + return ApiResponse.onSuccess(UserConverter.toAnswerResDTO(myPage, nxtLevel, myCommentList)); + } + + @PostMapping("/join")//회원가입 + @Operation(summary = "회원가입 API", description = "회원가입하는 API입니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4002", description = "NOT_ACCEPTABLE, 이미 존재하는 메일 주소입니다."), + }) + @Parameters({}) + public ApiResponse join(@RequestBody @Valid UserRequestDTO.UserJoinRequest userJoinRequest) { + + User user = userService.joinUser(userJoinRequest); + return ApiResponse.onSuccess(UserConverter.joinResultDTO(user)); + + } + + @PostMapping("/passwordMailSend")//비밀번호 재설정 인증 코드 전송 + @Operation(summary = "비밀번호 재설정 인증 코드 전송 API", description = "비밀번호 재설정 인증 코드 전송하는 API입니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + }) + @Parameters({}) + public ApiResponse mailSend(@RequestBody @Valid UserRequestDTO.PasswordEmailSendReq emailDto) { + System.out.println("비밀번호 재설정 인증 요청이 들어옴"); + System.out.println("비밀번호 재설정 인증 이메일 :" + emailDto.getEmail()); + + String code = mailService.passwordEmail(emailDto.getEmail()); + return ApiResponse.onSuccess(UserConverter.toEmailSendRes(code)); + } + + @PostMapping("/passwordMailauthCheck")//이메일 코드 확인 + @Operation(summary = "비밀번호 재설정 코드 확인 API", description = "비밀번호 재설정 코드 확인하는 API입니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4005", description = "UNAUTHORIZED, 인증 코드가 일치하지 않습니다."), + }) + @Parameters({}) + public ApiResponse mailauthCheck(@RequestBody @Valid UserRequestDTO.PasswordEmailSendCheckReq emailSendCheckReq) { + mailService.CheckAuthNum(emailSendCheckReq.getEmail(), emailSendCheckReq.getAuthNum()); + return ApiResponse.onSuccess(null); + } + + //로그인 + @PostMapping("/login") + @Operation(summary = "로그인 API", description = "로그인하는 API입니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4010", description = "NOT_FOUND, 가입 가능한 이메일입니다.(이메일 정보가 존재하지 않습니다.)"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4008", description = "NOT_FOUND, 비밀번호가 틀렸습니다."), + }) + public ApiResponse> login(@RequestBody UserRequestDTO.UserLoginRequest userLoginRequest) throws GeneralException { + List tokenDTOList = userService.login(userLoginRequest); + return ApiResponse.onSuccess(tokenDTOList); + } + + // 토큰 재발급 + @PostMapping("/reissue") + @Operation(summary = "토큰 재발급 API", description = "토큰 재발급하는 API입니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4007", description = "UNAUTHORIZED, 유효하지 않은 JWT입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4100", description = "UNAUTHORIZED, RefreshToken값을 확인해주세요."), + }) + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse> reissue(@RequestHeader(name = "rtk") String rtk, HttpServletRequest request) { + System.out.println("controller: reissue 함수 실행"); + return ApiResponse.onSuccess(userService.reissue(request)); + } + + // 로그아웃 + @PostMapping("/logout") + @Operation(summary = "로그아웃 API", description = "로그아웃하는 API입니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 회원정보가 존재하지 않습니다."), + }) + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse logout(@RequestHeader(name = "atk") String atk, HttpServletRequest request) { + return ApiResponse.onSuccess(userService.logout(request)); + } + + @GetMapping("/point") + @Operation(summary = "포인트 조회 API", description = "유저의 포인트 내역을 조회하는 API입니다") + public ApiResponse myPoint(@RequestHeader("atk") String atk, + HttpServletRequest request2) { + + Long userId = jwtTokenService.JwtToId(request2); + Integer point = userService.pointCheck(userId); + return ApiResponse.onSuccess(UserConverter.toPointViewResDTO(point)); + } + + @Operation(summary = "카카오 로그인 API", description = "카카오 로그인 및 회원 가입을 진행") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + }) + @GetMapping("/login/kakao") + public ApiResponse> kakaoLogin(@RequestParam("code") String code) throws GeneralException { + + return ApiResponse.onSuccess(authService.kakaoLogin(code)); + } + + //비밀번호 재설정 + @PatchMapping(value = "/updatePassword") + @Operation(summary = "비밀번호 재설정 API", description = "(비밀번호 재설정 이메일 인증후) 비밀번호를 변경하는 API") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 요청에 성공했습니다. ") + }) + @Parameters({ + @Parameter(name = "email", description = "RequestHeader - 비밀번호를 바꾸고자 하는 사용자의 email") + }) + public ApiResponse updatePassword( + @RequestHeader(name = "email") String mail, + HttpServletRequest request, + @RequestBody @Valid UserRequestDTO.PasswordUpdateReq passwordUpdateReq) { + userService.updatePassword(userService.getEmail(request),passwordUpdateReq ); + + return ApiResponse.onSuccess(null); + + } + + // 회원 탈퇴 + @DeleteMapping("/delete") + @Operation(summary = "회원 탈퇴 API", description = "회원을 탈퇴시키는 API") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER4001", description = "NOT_FOUND, 회원정보가 존재하지 않습니다."), + }) + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 회원 탈퇴할 회원의 accessToken"), + }) + public ApiResponse deactivateUser(@RequestHeader(name = "atk") String atk, HttpServletRequest request) throws GeneralException { + return ApiResponse.onSuccess(userService.deactivateUser(request)); + } +} diff --git a/src/main/java/friend/spring/web/controller/VoteRestController.java b/src/main/java/friend/spring/web/controller/VoteRestController.java new file mode 100644 index 0000000..f4ca0b5 --- /dev/null +++ b/src/main/java/friend/spring/web/controller/VoteRestController.java @@ -0,0 +1,77 @@ +package friend.spring.web.controller; + +import friend.spring.apiPayload.ApiResponse; +import friend.spring.converter.VoteConverter; +import friend.spring.domain.Card_vote; +import friend.spring.domain.Gauge_vote; +import friend.spring.domain.General_vote; +import friend.spring.security.JwtTokenProvider; +import friend.spring.service.JwtTokenService; +import friend.spring.service.VoteService; +import friend.spring.web.dto.VoteRequestDTO; +import friend.spring.web.dto.VoteResponseDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; + +@RestController +@RequiredArgsConstructor +@Validated +@RequestMapping("/posts") +public class VoteRestController { + private final JwtTokenService jwtTokenService; + private final VoteService voteService; + + @PostMapping("/{post-id}/generalVote") + @Operation(summary = "일반 투표 API", description = "일반 투표에 참여합니다.") + @Parameters({ + @Parameter(name = "selectList", description = " 투표 후보 id 리스트"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken") + }) + public ApiResponse join(@RequestBody @Valid VoteRequestDTO.GeneralVoteRequestDTO request, + @PathVariable(name = "post-id") Long PostId, + @RequestHeader("atk") String atk, + HttpServletRequest request2) { + Long UserId = jwtTokenService.JwtToId(request2); + General_vote generalVote = voteService.castGeneralVote(request, PostId, UserId); + return ApiResponse.onSuccess(VoteConverter.toAddGeneralVoteResultDTO(generalVote)); + } + + @PostMapping("/{post-id}/gaugeVote") + @Operation(summary = "게이지 투표 API", description = "게이지 투표에 참여합니다") + @Parameters({ + @Parameter(name = "gauge", description = " 게이지"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken") + }) + public ApiResponse join(@RequestBody @Valid VoteRequestDTO.GaugeVoteRequestDTO request, + @PathVariable(name = "post-id") Long PostId, + @RequestHeader("atk") String atk, + HttpServletRequest request2 + ) { + Long UserId = jwtTokenService.JwtToId(request2); + Gauge_vote gaugeVote = voteService.castGaugeVote(request, PostId, UserId); + return ApiResponse.onSuccess(VoteConverter.toAddGaugelVoteResultDTO(gaugeVote)); + } + + @PostMapping("/{post-id}/cardVote") + @Operation(summary = "카드 투표 API", description = "카드 투표에 참여합니다.") + @Parameters({ + @Parameter(name = "selectList", description = " 투표 후보 id 리스트"), + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken") + }) + public ApiResponse join(@RequestBody @Valid VoteRequestDTO.CardVoteRequestDTO request, + @PathVariable(name = "post-id") Long PostId, + @RequestHeader("atk") String atk, + HttpServletRequest request2) { + Long UserId = jwtTokenService.JwtToId(request2); + Card_vote cardVote = voteService.castCardVote(request, PostId, UserId); + return ApiResponse.onSuccess(VoteConverter.toAddCardVoteResultDTO(cardVote)); + } +} + diff --git a/src/main/java/friend/spring/web/dto/AlarmResponseDTO.java b/src/main/java/friend/spring/web/dto/AlarmResponseDTO.java new file mode 100644 index 0000000..b01c071 --- /dev/null +++ b/src/main/java/friend/spring/web/dto/AlarmResponseDTO.java @@ -0,0 +1,45 @@ +package friend.spring.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +public class AlarmResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class AlarmListResDTO { + List alarmList; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class AlarmResDTO { + Long alarmId; + String userNickname; + String userPhoto; + String alarmType; + String alarmContent; + String commentContent; + Long postId; + Long commentId; + Boolean read; + LocalDateTime createdAt; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class AlarmLeftResDTO { + Boolean isAlarmLeft; + } +} diff --git a/src/main/java/friend/spring/web/dto/CandidateRequestDTO.java b/src/main/java/friend/spring/web/dto/CandidateRequestDTO.java new file mode 100644 index 0000000..260d147 --- /dev/null +++ b/src/main/java/friend/spring/web/dto/CandidateRequestDTO.java @@ -0,0 +1,15 @@ +package friend.spring.web.dto; + +import lombok.*; + +public class CandidateRequestDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class AddCandidateRequestDTO { + String optionString; + String optionImg; + } +} diff --git a/src/main/java/friend/spring/web/dto/CandidateResponseDTO.java b/src/main/java/friend/spring/web/dto/CandidateResponseDTO.java new file mode 100644 index 0000000..e7d730b --- /dev/null +++ b/src/main/java/friend/spring/web/dto/CandidateResponseDTO.java @@ -0,0 +1,28 @@ +package friend.spring.web.dto; + +import lombok.*; + +import java.time.LocalDateTime; + +public class CandidateResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class CandidateSummaryRes { + Long candidate_id; // 후보 아이디 + String candidate_name; // 후보 이름 + String candidate_image; // 후보 이미지 + Double ratio; // 비율 + Boolean mySelect; // 사용자가 투표했는지 여부 + } + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class AddCandidateResultDTO { + Long candidateId; + LocalDateTime createdAt; + } +} diff --git a/src/main/java/friend/spring/web/dto/CommentRequestDTO.java b/src/main/java/friend/spring/web/dto/CommentRequestDTO.java new file mode 100644 index 0000000..82e9559 --- /dev/null +++ b/src/main/java/friend/spring/web/dto/CommentRequestDTO.java @@ -0,0 +1,38 @@ +package friend.spring.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; + +public class CommentRequestDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class commentCreateReq { + @NotBlank + String content; + Long parentId; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class commentEditReq { + @NotBlank + String content; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class CommentReportReq { + String reportCategory; + } +} diff --git a/src/main/java/friend/spring/web/dto/CommentResponseDTO.java b/src/main/java/friend/spring/web/dto/CommentResponseDTO.java new file mode 100644 index 0000000..b97f840 --- /dev/null +++ b/src/main/java/friend/spring/web/dto/CommentResponseDTO.java @@ -0,0 +1,84 @@ +package friend.spring.web.dto; + +import friend.spring.domain.Report; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import software.amazon.awssdk.services.s3.endpoints.internal.Value; + +import java.time.LocalDateTime; +import java.util.List; + +public class CommentResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class commentCreateRes { + Long commentId; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class commentLikeRes { + Long commentLikeId; // 댓글-좋아요 아이디 + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class commentGetRes { + Long commentId; + String content; + Long userId; + String userNickname; + String userImage; + LocalDateTime createdAt; + LocalDateTime updatedAt; + Long parentCommentId; + Integer commentLike; + Boolean isDeleted; // 댓글 삭제 여부 + Boolean isMyComment; // 내가 쓴 댓글인지 여부 + Boolean isPushedLike; // 좋아요 이미 눌렀는지 여부 + Boolean isOwnerOfPost; // 내가 쓴 글인지 여부 + Boolean isSelected; // 채택 여부 + List childrenComments; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class commentSelectRes { + Long commentChoiceId; + Integer point; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class myCommentRes { + Long postId; + String nickName; + LocalDateTime createdAt; + String content; + Integer commentLike; + Integer reComment; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class CommentReportRes { + Long reportId; + LocalDateTime createdAt; + Boolean duplicatedReport; + } +} diff --git a/src/main/java/friend/spring/web/dto/FileDTO.java b/src/main/java/friend/spring/web/dto/FileDTO.java new file mode 100644 index 0000000..16ed195 --- /dev/null +++ b/src/main/java/friend/spring/web/dto/FileDTO.java @@ -0,0 +1,13 @@ +package friend.spring.web.dto; + +import lombok.*; + +@Getter +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FileDTO { + private Long imageId; + private String imageUrl; +} diff --git a/src/main/java/friend/spring/web/dto/MyPageRequestDTO.java b/src/main/java/friend/spring/web/dto/MyPageRequestDTO.java new file mode 100644 index 0000000..ca6e508 --- /dev/null +++ b/src/main/java/friend/spring/web/dto/MyPageRequestDTO.java @@ -0,0 +1,87 @@ +package friend.spring.web.dto; + +import friend.spring.domain.enums.InquiryCategory; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotEmpty; + +public class MyPageRequestDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ProfileEditNameReq { + @Email//1)@기호를 포함해야 한다.2_@기호를 기준으로 이메일 주소를 이루는 로컬호스트와 도메인 파트가 존재해야 한다.3)도메인 파트는 최소하나의 점과 + //그 뒤에 최소한 2개의 알파벳을 가진다를 검증 + @NotEmpty(message = "이메일을 입력해 주세요") + private String email; + private String certification; + String nickName; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ProfileEditEmailReq { + @Email//1)@기호를 포함해야 한다.2_@기호를 기준으로 이메일 주소를 이루는 로컬호스트와 도메인 파트가 존재해야 한다.3)도메인 파트는 최소하나의 점과 + //그 뒤에 최소한 2개의 알파벳을 가진다를 검증 + @NotEmpty(message = "이메일을 입력해 주세요") + private String curEmail; + private String certification; + String changeEmail; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ProfileEditPhoneReq { + @Email//1)@기호를 포함해야 한다.2_@기호를 기준으로 이메일 주소를 이루는 로컬호스트와 도메인 파트가 존재해야 한다.3)도메인 파트는 최소하나의 점과 + //그 뒤에 최소한 2개의 알파벳을 가진다를 검증 + @NotEmpty(message = "이메일을 입력해 주세요") + private String email; + private String certification; + String phone; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ProfileEditPasswordReq { + String curPassword; + String changePassword; + String checkPassword; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ProfileEditSecurityReq { + @Email//1)@기호를 포함해야 한다.2_@기호를 기준으로 이메일 주소를 이루는 로컬호스트와 도메인 파트가 존재해야 한다.3)도메인 파트는 최소하나의 점과 + //그 뒤에 최소한 2개의 알파벳을 가진다를 검증 + @NotEmpty(message = "이메일을 입력해 주세요") + private String curEmail; + private String certification; + @Email + @NotEmpty(message = "이메일을 입력해 주세요") + String changeEmail; + String nxtCertification; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class MyInquiryReq { + InquiryCategory inquiryCategory; + String content; + } +} \ No newline at end of file diff --git a/src/main/java/friend/spring/web/dto/MyPageResponseDTO.java b/src/main/java/friend/spring/web/dto/MyPageResponseDTO.java new file mode 100644 index 0000000..26b16b9 --- /dev/null +++ b/src/main/java/friend/spring/web/dto/MyPageResponseDTO.java @@ -0,0 +1,175 @@ +package friend.spring.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotEmpty; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; + +public class MyPageResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class CategoryResDTO { + Long categoryId; + String category; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class SavedCategoryResDTO { + List postCategoryList; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class SavedPostResDTO { + Long postId; + Long ago; + String title; + String content; + Integer postLike; + Integer comment; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class SavedAllPostResDTO { + List postList; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class MyProfileResDTO { + String userImage; + String nickName; + String email; + String phone; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ProfileEditNameRes { + String nickName; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ProfileEditEmailRes { + String changeEmail; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ProfileEditPhoneRes { + String phone; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ProfileEditPasswordRes { + String changePassword; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class MyInquiryRes { + Long inquiry_id; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class SavedPostCategoryDetailRes { + Long postId; + Long ago; + String title; + String content; + Integer postLike; + Integer comment; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class SavedPostCategoryDetailListRes { + String name; + List postList; + } + + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class NoticeListRes { + List noticeList; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class NoticeRes { + String adminImage; + Long ago; + String title; + String content; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class NoticeDetailRes { + String adminImage; + String adminName; + LocalDateTime createdAt; + String content; + Integer view; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class TermRes { + String content; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PrivacyRes { + String content; + } +} diff --git a/src/main/java/friend/spring/web/dto/ParentPollDTO.java b/src/main/java/friend/spring/web/dto/ParentPollDTO.java new file mode 100644 index 0000000..9e85f84 --- /dev/null +++ b/src/main/java/friend/spring/web/dto/ParentPollDTO.java @@ -0,0 +1,16 @@ +package friend.spring.web.dto; + +import lombok.*; + +@Getter +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ParentPollDTO { + String candidateName; + String candidateImage; + Long candidateId; + Integer rate; + Long selection; +} diff --git a/src/main/java/friend/spring/web/dto/ParentPostDTO.java b/src/main/java/friend/spring/web/dto/ParentPostDTO.java new file mode 100644 index 0000000..24c9388 --- /dev/null +++ b/src/main/java/friend/spring/web/dto/ParentPostDTO.java @@ -0,0 +1,51 @@ +package friend.spring.web.dto; + +import friend.spring.domain.enums.PostType; +import friend.spring.domain.enums.PostVoteType; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ParentPostDTO { + Long postId; + PostVoteType postVoteType; + String nickname; + String userImg; + String title; + String content; + List pollOption; + ParentPollDTO pollContent; + Integer gauge; + Integer like; + Integer comment; + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class CandidatePostDTO { + Long postId; + String title; + String content; + Integer like; + Integer comment; + LocalDateTime createdAt; + } + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class ParentPostGetListDTO { + List candidatePostDTOList; + private Boolean isEnd; + } +} + + diff --git a/src/main/java/friend/spring/web/dto/PollOptionDTO.java b/src/main/java/friend/spring/web/dto/PollOptionDTO.java new file mode 100644 index 0000000..8260c80 --- /dev/null +++ b/src/main/java/friend/spring/web/dto/PollOptionDTO.java @@ -0,0 +1,30 @@ +package friend.spring.web.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.*; +import org.springframework.web.multipart.MultipartFile; + +public class PollOptionDTO { + + @Getter + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class PollOptionReq { + private String optionString; + private MultipartFile optionImg; + } + + @Getter + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class PollOptionRes { + private Long optionId; + private String optionString; + private String optionImgUrl; + } +} diff --git a/src/main/java/friend/spring/web/dto/PostRequestDTO.java b/src/main/java/friend/spring/web/dto/PostRequestDTO.java new file mode 100644 index 0000000..d80a409 --- /dev/null +++ b/src/main/java/friend/spring/web/dto/PostRequestDTO.java @@ -0,0 +1,60 @@ +package friend.spring.web.dto; + +import friend.spring.validation.annotation.ContentTextLimit; +import friend.spring.validation.annotation.DeadlineLimit; +import friend.spring.validation.annotation.TitleTextLimit; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; +import java.util.List; + +public class PostRequestDTO { + @Getter + public static class AddPostDTO { + @TitleTextLimit + String title; + @ContentTextLimit + String content; + String category; + @NotNull + Integer postType; // 1: vote, 2: review + Integer postVoteType;// 1: general, 2: gauge + String pollTitle; + Boolean multipleChoice; + Long parent_id; + @DeadlineLimit + LocalDateTime deadline; + Integer point; + List fileBase64List; + } + + @Getter + public static class ReviewPostGetDTO { + Integer arrange; //조회순 :0, 최신순:1 + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PostEditReq { + @TitleTextLimit + String title; + @ContentTextLimit + String content; + LocalDateTime deadline; + Boolean voteOnGoing; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PostReportReq { + String reportCategory; + } +} diff --git a/src/main/java/friend/spring/web/dto/PostResponseDTO.java b/src/main/java/friend/spring/web/dto/PostResponseDTO.java new file mode 100644 index 0000000..6dda30e --- /dev/null +++ b/src/main/java/friend/spring/web/dto/PostResponseDTO.java @@ -0,0 +1,222 @@ +package friend.spring.web.dto; + +import friend.spring.domain.Report; +import friend.spring.domain.enums.PostType; +import friend.spring.domain.enums.PostVoteType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.List; + +public class PostResponseDTO { + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class AddPostResultDTO { + Long postId; + LocalDateTime createdAt; + } + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class PostDetailResponse { + Boolean myPost; + Boolean OnGoing; + Boolean isVoted; //투표글에서만 사용, 후기글에서는 null + PostType postType; + PostVoteType postVoteType; + String nickname; + String userImg; + LocalDateTime createdAt; + String title; + String content; + List file; // 첨부파일 이미지 리스트 + String pollTitle; // 투표글에서만 사용, 후기글에서는 null + List pollOption; // 투표글에서만 사용, 후기글에서는 null + List topCandidate; + List userVote;// 투표글에서 사용자가 투표완료시 투표한 후보 + List userVotePercent; // 투표글에서 사용자가 투표 완료시 투표한 후보 선택 퍼센트 + List topCandidatePercent; + List allCandidatePercent; + List userVoteResult; // 투표글에서 사용자가 투표 완료시 투표인원/총인원 + List allCandidateResult; + List topVoteResult; + Integer userGauge; // 게이지 투표글에서만 사용, 후기글에서는 null + Integer totalGauge; + Integer point; // 투표글에서만 사용, 후기글에서는 null + ParentPostDTO parentPost; // 후기글에서만 사용, 일반글에서는 null + LocalDateTime deadline; // 투표글에서만 사용, 후기글에서는 null + Integer view; + Integer like; + Integer comment; + Boolean isLike; + Boolean isComment; + + } + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class PollPostGetListDTO { + List pollPostList; + private Boolean isEnd; + } + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class PollPostGetResponse { + Boolean myPost; + Boolean onGoing; + Boolean isVoted; + Long postId; + PostVoteType postVoteType; + String nickname; + String userImg; + String title; + String content; + LocalDateTime uploadDate; + List pollOption; + List topCandidate; + List userVote; + List userVotePercent; + List topCandidatePercent; + List allCandidatePercent; + List allCandidateResult; + List topVoteResult; + String pollTitle; //게이지투표만 해당(게이지 투표 이름) + Integer userGauge; + Integer totalGauge; + Integer like; + Integer comment; + Boolean isLike; + Boolean isComment; + } + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class ReviewPostGetListDTO { + List reviewPostList; + private Boolean isEnd; + } + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class ReviewPostGetResponse { + Long postId; + String nickname; + String userImg; + String title; + String content; + LocalDateTime uploadDate; + List ReviewPicList; + Integer like; + Integer comment; + Boolean isLike; + Boolean isComment; + ParentPostDTO.CandidatePostDTO parentPostDTO; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class MyPostDTO { + Long postId; + String nickName; + LocalDateTime createdAt; + String title; + Integer postLike; + Integer comment; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PostLikeRes { + Long post_like_id; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ScrapCreateRes { + Long post_scrap_id; + } + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class PostSearchList { + List reviewPostList; + private Boolean isEnd; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PostSearchRes{ + Long postId; + String nickname; + String userImg; + String title; + String content; + LocalDateTime uploadDate; + Integer like; + Integer comment; + Boolean isLike; + Boolean isComment; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class RecentSearchRes { + List recentSearchList; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PostReportRes { + Long reportId; + LocalDateTime createdAt; + Boolean duplicatedReport; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class SearchLog{ + private String name; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ReportResult{ + Report report; + Boolean duplicatedReport; + } +} \ No newline at end of file diff --git a/src/main/java/friend/spring/web/dto/SseResponseDTO.java b/src/main/java/friend/spring/web/dto/SseResponseDTO.java new file mode 100644 index 0000000..86ca33d --- /dev/null +++ b/src/main/java/friend/spring/web/dto/SseResponseDTO.java @@ -0,0 +1,36 @@ +package friend.spring.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +public class SseResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class CommentCreateResDTO { + String userNickname; + String userPhoto; + String alarmType; + String alarmContent; + String commentContent; + Long postId; + Long commentId; + LocalDateTime createdAt; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class VoteFinishResDTO { + String alarmContent; + String alarmType; + Long postId; + LocalDateTime createdAt; + } +} diff --git a/src/main/java/friend/spring/web/dto/TokenDTO.java b/src/main/java/friend/spring/web/dto/TokenDTO.java new file mode 100644 index 0000000..9c67a31 --- /dev/null +++ b/src/main/java/friend/spring/web/dto/TokenDTO.java @@ -0,0 +1,20 @@ +package friend.spring.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; + +import java.util.Date; + +@Data +@AllArgsConstructor +public class TokenDTO { + + //1. 로그인 시 토큰을 응답 + // private String token; // jwt 토큰 + private String types; // atk, rtk + private String token; + private Date tokenExpriresTime; // 토큰 만료시간 + +} \ No newline at end of file diff --git a/src/main/java/friend/spring/web/dto/UserRequestDTO.java b/src/main/java/friend/spring/web/dto/UserRequestDTO.java new file mode 100644 index 0000000..acd92a4 --- /dev/null +++ b/src/main/java/friend/spring/web/dto/UserRequestDTO.java @@ -0,0 +1,153 @@ +package friend.spring.web.dto; + +import friend.spring.domain.enums.Gender; +import io.swagger.models.auth.In; +import lombok.Getter; + +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotEmpty; +import java.time.LocalDate; + +public class UserRequestDTO { + + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Setter + public static class UserJoinRequest { + + String email; + String password; + String nickname; + String phone; + Integer gender; + boolean agree_info; + boolean agree_marketing; + LocalDate birth; + boolean is_deleted; + Integer point; + String kakao; + Integer like; + + } + + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Setter + public static class UserLoginRequest {//로그인 요청 + String email; + String password; + } + + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Setter + public static class EmailCheckReq { + + @Email + @NotEmpty(message = "이메일을 입력해 주세요") + private String email; + + @NotEmpty(message = "인증 번호를 입력해 주세요") + private String authNum; + + } + + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Setter + public static class NicknameCheckReq { + private String nickname; + } + + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Setter + public static class UserDeleteReq { // 회원 탈퇴 요청 + Long userIdx; + } + + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Setter + public static class EmailSendReq { + @Email//1)@기호를 포함해야 한다.2_@기호를 기준으로 이메일 주소를 이루는 로컬호스트와 도메인 파트가 존재해야 한다.3)도메인 파트는 최소하나의 점과 + //그 뒤에 최소한 2개의 알파벳을 가진다를 검증 + @NotEmpty(message = "이메일을 입력해 주세요") + private String email; + } + + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Setter + public static class EmailSendCheckReq { + @Email//1)@기호를 포함해야 한다.2_@기호를 기준으로 이메일 주소를 이루는 로컬호스트와 도메인 파트가 존재해야 한다.3)도메인 파트는 최소하나의 점과 + //그 뒤에 최소한 2개의 알파벳을 가진다를 검증 + @NotEmpty(message = "이메일을 입력해 주세요") + private String email; + private String authNum; + } + + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Setter + public static class PasswordEmailSendReq { + @Email//1)@기호를 포함해야 한다.2_@기호를 기준으로 이메일 주소를 이루는 로컬호스트와 도메인 파트가 존재해야 한다.3)도메인 파트는 최소하나의 점과 + //그 뒤에 최소한 2개의 알파벳을 가진다를 검증 + @NotEmpty(message = "이메일을 입력해 주세요") + private String email; + private String name; + } + + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Setter + public static class PasswordEmailSendCheckReq { + @Email//1)@기호를 포함해야 한다.2_@기호를 기준으로 이메일 주소를 이루는 로컬호스트와 도메인 파트가 존재해야 한다.3)도메인 파트는 최소하나의 점과 + //그 뒤에 최소한 2개의 알파벳을 가진다를 검증 + @NotEmpty(message = "이메일을 입력해 주세요") + private String email; + private String name; + private String authNum; + } + + //비밀번호 변경 + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Setter + public static class PasswordUpdateReq { + + @NotEmpty(message = "변경할 비밀번호를 입력해 주세요") + private String newPassword; + private String newPasswordCheck; + } + //회원 탈퇴 + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Setter + public static class Delete { + private String email; + } + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Setter + public static class SearchReq{ + private String name; + private String createdAt; + } +} \ No newline at end of file diff --git a/src/main/java/friend/spring/web/dto/UserResponseDTO.java b/src/main/java/friend/spring/web/dto/UserResponseDTO.java new file mode 100644 index 0000000..a20cfa4 --- /dev/null +++ b/src/main/java/friend/spring/web/dto/UserResponseDTO.java @@ -0,0 +1,144 @@ +package friend.spring.web.dto; + +import friend.spring.domain.enums.Gender; +import lombok.*; + +import java.time.LocalDate; +import java.util.List; + +public class UserResponseDTO { + +// public static List LoginResDTO; + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class MyPageResDTO { + String userPhoto; + String userName; + Integer userPoint; + Integer userLevelInt; + String userLevelName; + Integer userRecommend; + + } + + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Builder + public static class JoinResultDTO { + + String email; + LocalDate createAt; + } + + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Builder + public static class UserInfo { + private String email; + private String nickname; + private Gender gender; + + } + + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class EmailSendRes { + private String code; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class QuestionResDTO { + String userPhoto; + String nickName; + Integer recommend; + String grade; + Double nextGrade; + String nextGradeName; + Integer adoptComments; + Double adoptCommentPercent; + Integer postNum; + Double adoptPostPercent; + List postList; + } + + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PointViewDTO { + Integer point; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class AnswerResDTO { + String userPhoto; + String nickName; + Integer recommend; + String grade; + Double nextGrade; + String nextGradeName; + Integer adoptComments; + Double adoptCommentPercent; + List commentList; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class LoginResDTO { + + String type; + String accessToken; + String refreshToken; + String tokenExpiresTime; + + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class OAuthResponse { + + Boolean isLogin; + TokenDTO accessToken; + TokenDTO refreshToken; + String email; + + } + + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Builder + public static class UserSummaryInfo { + + Long user_id; + String nickname; + String image; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PasswordUpdateRes { + String newPassword; + } +} diff --git a/src/main/java/friend/spring/web/dto/VoteRequestDTO.java b/src/main/java/friend/spring/web/dto/VoteRequestDTO.java new file mode 100644 index 0000000..49c3023 --- /dev/null +++ b/src/main/java/friend/spring/web/dto/VoteRequestDTO.java @@ -0,0 +1,24 @@ +package friend.spring.web.dto; + +import lombok.Getter; + +import javax.validation.constraints.NotBlank; +import java.sql.Timestamp; +import java.util.List; + +public class VoteRequestDTO { + @Getter + public static class GeneralVoteRequestDTO { + List selectList; + } + + @Getter + public static class GaugeVoteRequestDTO { + Integer value; + } + + @Getter + public static class CardVoteRequestDTO { + List selectList; + } +} diff --git a/src/main/java/friend/spring/web/dto/VoteResponseDTO.java b/src/main/java/friend/spring/web/dto/VoteResponseDTO.java new file mode 100644 index 0000000..501f604 --- /dev/null +++ b/src/main/java/friend/spring/web/dto/VoteResponseDTO.java @@ -0,0 +1,37 @@ +package friend.spring.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +public class VoteResponseDTO { + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class GeneralVoteResponseDTO { + Long generalVoteId; + LocalDateTime createdAt; + } + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class GaugeVoteResponseDTO { + Long gaugeVoteId; + LocalDateTime createdAt; + } + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class CardVoteResponseDTO { + Long cardVoteId; + LocalDateTime createdAt; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..4f9a80a --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,70 @@ +spring: + servlet: + multipart: + max-file-size: 200MB + max-request-size: 200MB + redis: + host: ${REDIS_HOSTNAME} + port: ${REDIS_PORT} + jwt: + secret: ${JWT_SECRET} + datasource: + url: ${aws.db.url} + username: ${aws.db.username} + password: ${aws.db.password} + driver-class-name: com.mysql.cj.jdbc.Driver + sql: + init: + mode: never + jpa: + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL8Dialect + show_sql: true + format_sql: true + use_sql_comments: true + hbm2ddl: + auto: update + default_batch_fetch_size: 1000 + globally_quoted_identifiers: true +cloud: + aws: + s3: + bucket: solution-friend-bucket + path: + user: users + post: posts + candidate: candidates + region: + static: ap-northeast-2 + stack: + auto: false + credentials: + accessKey: ${AWS_ACCESS_KEY_ID} + secretKey: ${AWS_SECRET_ACCESS_KEY} +logging: + level: + com: + amazonaws: + util: + EC2MetadataUtils: error +mail: + smtp: + host: smtp.gmail.com + auth: true + starttls: + required: true + enable: true + socketFactory: + fallback: false + port: 587 + socketFactoryPort: 587 + AdminMail: + id: ${gmail.username} + password: ${gmail.password} +kakao: + auth: + client: ${KAKAO_AUTH_CLIENT} + redirect_uri: ${KAKAO_AUTH_REDIRECT_URI} + secret_key: ${KAKAO_AUTH_SECRET_KEY} + diff --git a/src/test/java/friend/spring/ApplicationTests.java b/src/test/java/friend/spring/ApplicationTests.java new file mode 100644 index 0000000..9ba171d --- /dev/null +++ b/src/test/java/friend/spring/ApplicationTests.java @@ -0,0 +1,13 @@ +package friend.spring; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ApplicationTests { + + @Test + void contextLoads() { + } + +}