From 427ed8ee66b3eaacae6b722e93f66d3d762eb293 Mon Sep 17 00:00:00 2001 From: sammool Date: Sun, 6 Apr 2025 22:48:51 +0900 Subject: [PATCH 01/16] practice/#4 --- .gitattributes | 3 + .gitignore | 37 +++ build.gradle | 38 +++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43705 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 251 ++++++++++++++++++ gradlew.bat | 94 +++++++ settings.gradle | 1 + src/main/java/umc/spring/Application.java | 13 + src/main/resources/application.yml | 10 + .../java/umc/spring/ApplicationTests.java | 13 + 11 files changed, 467 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/umc/spring/Application.java create mode 100644 src/main/resources/application.yml create mode 100644 src/test/java/umc/spring/ApplicationTests.java diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2065bc --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..4022d03 --- /dev/null +++ b/build.gradle @@ -0,0 +1,38 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.4.4' + id 'io.spring.dependency-management' version '1.1.7' +} + +group = 'umc' +version = '0.0.1-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(19) + } +} + +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' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..9bbc975c742b298b441bfb90dbc124400a3751b9 GIT binary patch literal 43705 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 DHx0+F literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..37f853b --- /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.13-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..faf9300 --- /dev/null +++ b/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..ebf1ef8 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'spring' diff --git a/src/main/java/umc/spring/Application.java b/src/main/java/umc/spring/Application.java new file mode 100644 index 0000000..ff807df --- /dev/null +++ b/src/main/java/umc/spring/Application.java @@ -0,0 +1,13 @@ +package umc.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..1a76dec --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,10 @@ + + +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/mission_db + username: root + password: 3mirsammoolsom* + application: + name:sping \ No newline at end of file diff --git a/src/test/java/umc/spring/ApplicationTests.java b/src/test/java/umc/spring/ApplicationTests.java new file mode 100644 index 0000000..dc36e6c --- /dev/null +++ b/src/test/java/umc/spring/ApplicationTests.java @@ -0,0 +1,13 @@ +package umc.spring; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ApplicationTests { + + @Test + void contextLoads() { + } + +} From 87a0dc49aaaf5a7cc9425758f392e00f7d7f34ee Mon Sep 17 00:00:00 2001 From: sammool Date: Mon, 28 Apr 2025 21:38:25 +0900 Subject: [PATCH 02/16] mission/#5 --- src/main/java/umc/spring/Application.java | 2 + .../java/umc/spring/domain/FoodCategory.java | 19 +++++ src/main/java/umc/spring/domain/Member.java | 69 +++++++++++++++++++ src/main/java/umc/spring/domain/Mission.java | 34 +++++++++ src/main/java/umc/spring/domain/Region.java | 24 +++++++ src/main/java/umc/spring/domain/Review.java | 36 ++++++++++ .../java/umc/spring/domain/ReviewImage.java | 22 ++++++ .../java/umc/spring/domain/SalesTime.java | 30 ++++++++ src/main/java/umc/spring/domain/Store.java | 41 +++++++++++ src/main/java/umc/spring/domain/Terms.java | 34 +++++++++ .../umc/spring/domain/common/BaseEntity.java | 23 +++++++ .../java/umc/spring/domain/enums/Gender.java | 5 ++ .../umc/spring/domain/enums/MemberStatus.java | 5 ++ .../spring/domain/enums/MissionStatus.java | 5 ++ .../umc/spring/domain/enums/SocialType.java | 5 ++ .../umc/spring/domain/enums/TermType.java | 5 ++ .../umc/spring/domain/mapping/MemberFood.java | 27 ++++++++ .../spring/domain/mapping/MemberMission.java | 39 +++++++++++ .../spring/domain/mapping/MemberTerms.java | 32 +++++++++ src/main/resources/application.yml | 16 ++++- 20 files changed, 471 insertions(+), 2 deletions(-) create mode 100644 src/main/java/umc/spring/domain/FoodCategory.java create mode 100644 src/main/java/umc/spring/domain/Member.java create mode 100644 src/main/java/umc/spring/domain/Mission.java create mode 100644 src/main/java/umc/spring/domain/Region.java create mode 100644 src/main/java/umc/spring/domain/Review.java create mode 100644 src/main/java/umc/spring/domain/ReviewImage.java create mode 100644 src/main/java/umc/spring/domain/SalesTime.java create mode 100644 src/main/java/umc/spring/domain/Store.java create mode 100644 src/main/java/umc/spring/domain/Terms.java create mode 100644 src/main/java/umc/spring/domain/common/BaseEntity.java create mode 100644 src/main/java/umc/spring/domain/enums/Gender.java create mode 100644 src/main/java/umc/spring/domain/enums/MemberStatus.java create mode 100644 src/main/java/umc/spring/domain/enums/MissionStatus.java create mode 100644 src/main/java/umc/spring/domain/enums/SocialType.java create mode 100644 src/main/java/umc/spring/domain/enums/TermType.java create mode 100644 src/main/java/umc/spring/domain/mapping/MemberFood.java create mode 100644 src/main/java/umc/spring/domain/mapping/MemberMission.java create mode 100644 src/main/java/umc/spring/domain/mapping/MemberTerms.java diff --git a/src/main/java/umc/spring/Application.java b/src/main/java/umc/spring/Application.java index ff807df..1712ec5 100644 --- a/src/main/java/umc/spring/Application.java +++ b/src/main/java/umc/spring/Application.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing public class Application { public static void main(String[] args) { diff --git a/src/main/java/umc/spring/domain/FoodCategory.java b/src/main/java/umc/spring/domain/FoodCategory.java new file mode 100644 index 0000000..5a818c3 --- /dev/null +++ b/src/main/java/umc/spring/domain/FoodCategory.java @@ -0,0 +1,19 @@ +package umc.spring.domain; + +import jakarta.persistence.*; +import lombok.*; +import umc.spring.domain.common.BaseEntity; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class FoodCategory extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; +} diff --git a/src/main/java/umc/spring/domain/Member.java b/src/main/java/umc/spring/domain/Member.java new file mode 100644 index 0000000..63ca6d4 --- /dev/null +++ b/src/main/java/umc/spring/domain/Member.java @@ -0,0 +1,69 @@ +package umc.spring.domain; + +import jakarta.persistence.*; +import lombok.*; +import umc.spring.domain.common.BaseEntity; +import umc.spring.domain.enums.Gender; +import umc.spring.domain.enums.SocialType; +import umc.spring.domain.enums.MemberStatus; +import umc.spring.domain.mapping.MemberFood; +import umc.spring.domain.mapping.MemberMission; +import umc.spring.domain.mapping.MemberTerms; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) //기본 생성자 +@AllArgsConstructor(access = AccessLevel.PUBLIC) //default가 public +public class Member extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(nullable = false, length = 50) + private String name; + + @Column(nullable = false, length = 50) + private String address; + + @Column(nullable = false, length = 20) + private String phoneNumber; + + @Column(nullable = false, length = 50) + private String email; + + @Column(nullable = false) + private LocalDate birth; + + //private String specAddress; + + @Enumerated(EnumType.STRING) + private Gender gender; + + @Enumerated(EnumType.STRING) + private SocialType socialType; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(15) DEFAULT 'ACTIVE'") + private MemberStatus status; + + private LocalDate inactiveDate; + + private Integer point; + + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) // Member의 변화에 따라 매핑한 엔티티가 영향을 받음 + private List memberFoodList = new ArrayList<>(); + + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) + private List memberTermsList = new ArrayList<>(); + + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) + private List reviewList = new ArrayList<>(); + + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) + private List memberMissionList = new ArrayList<>(); +} diff --git a/src/main/java/umc/spring/domain/Mission.java b/src/main/java/umc/spring/domain/Mission.java new file mode 100644 index 0000000..c53972a --- /dev/null +++ b/src/main/java/umc/spring/domain/Mission.java @@ -0,0 +1,34 @@ +package umc.spring.domain; +import jakarta.persistence.*; +import lombok.*; +import umc.spring.domain.common.BaseEntity; +import umc.spring.domain.mapping.MemberMission; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Mission extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private Integer point; + + private LocalDate deadline; + + private String description; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; + + @OneToMany(mappedBy = "mission", cascade = CascadeType.ALL) + private List memberMissionList = new ArrayList<>(); +} diff --git a/src/main/java/umc/spring/domain/Region.java b/src/main/java/umc/spring/domain/Region.java new file mode 100644 index 0000000..fb40e20 --- /dev/null +++ b/src/main/java/umc/spring/domain/Region.java @@ -0,0 +1,24 @@ +package umc.spring.domain; +import jakarta.persistence.*; +import lombok.*; +import umc.spring.domain.common.BaseEntity; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Region extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 20) + private String name; + + @OneToMany(mappedBy = "region", cascade = CascadeType.ALL) + private List storeList = new ArrayList<>(); +} diff --git a/src/main/java/umc/spring/domain/Review.java b/src/main/java/umc/spring/domain/Review.java new file mode 100644 index 0000000..2ffb97a --- /dev/null +++ b/src/main/java/umc/spring/domain/Review.java @@ -0,0 +1,36 @@ +package umc.spring.domain; +import jakarta.persistence.*; +import lombok.*; +import umc.spring.domain.common.BaseEntity; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Review extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String content; + + @Column(nullable = false) + private Float score; + + @OneToMany(mappedBy = "review", cascade = CascadeType.ALL) + private List reviewImageList = new ArrayList<>(); + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + +} diff --git a/src/main/java/umc/spring/domain/ReviewImage.java b/src/main/java/umc/spring/domain/ReviewImage.java new file mode 100644 index 0000000..4b1529a --- /dev/null +++ b/src/main/java/umc/spring/domain/ReviewImage.java @@ -0,0 +1,22 @@ +package umc.spring.domain; +import jakarta.persistence.*; +import lombok.*; +import umc.spring.domain.common.BaseEntity; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class ReviewImage extends BaseEntity{ + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String imageUrl; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; +} diff --git a/src/main/java/umc/spring/domain/SalesTime.java b/src/main/java/umc/spring/domain/SalesTime.java new file mode 100644 index 0000000..eb7b0a9 --- /dev/null +++ b/src/main/java/umc/spring/domain/SalesTime.java @@ -0,0 +1,30 @@ +package umc.spring.domain; +import jakarta.persistence.*; +import lombok.*; +import umc.spring.domain.common.BaseEntity; + +import java.sql.Timestamp; +import java.time.LocalDateTime; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class SalesTime extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private int dayOfWeek; //(범위 1~7, validation) + + private LocalDateTime openTime; + + private LocalDateTime closeTime; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; + +} diff --git a/src/main/java/umc/spring/domain/Store.java b/src/main/java/umc/spring/domain/Store.java new file mode 100644 index 0000000..fb14a70 --- /dev/null +++ b/src/main/java/umc/spring/domain/Store.java @@ -0,0 +1,41 @@ +package umc.spring.domain; + +import jakarta.persistence.*; +import lombok.*; +import umc.spring.domain.common.BaseEntity; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Store extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String name; + + @Column(nullable = false, length = 50) + private String address; + + @Column(nullable = false) + private Float score; + + private String category; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "region_id") + private Region region; + + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL) + private List missionList = new ArrayList<>(); + + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL) + private List salesTimeList = new ArrayList<>(); +} diff --git a/src/main/java/umc/spring/domain/Terms.java b/src/main/java/umc/spring/domain/Terms.java new file mode 100644 index 0000000..b31df3c --- /dev/null +++ b/src/main/java/umc/spring/domain/Terms.java @@ -0,0 +1,34 @@ +package umc.spring.domain; + +import jakarta.persistence.*; +import lombok.*; +import umc.spring.domain.common.BaseEntity; +import umc.spring.domain.enums.TermType; +import umc.spring.domain.mapping.MemberTerms; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Terms extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 100) + private String name; + + @Column(length = 225) + private String description; + + @Enumerated(EnumType.STRING) + private TermType type; + + @OneToMany(mappedBy = "terms", cascade = CascadeType.ALL) + private List memberTermsList = new ArrayList<>(); +} diff --git a/src/main/java/umc/spring/domain/common/BaseEntity.java b/src/main/java/umc/spring/domain/common/BaseEntity.java new file mode 100644 index 0000000..f83a2a8 --- /dev/null +++ b/src/main/java/umc/spring/domain/common/BaseEntity.java @@ -0,0 +1,23 @@ +package umc.spring.domain.common; + + +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@MappedSuperclass // 직접 테이블로 매핑되지 않고, 상속받는 자식 엔티티에 필드만 물려줌 +@EntityListeners(AuditingEntityListener.class) //엔티티의 생명주기를 감지해서 자동으로 날찌를 채워주는 리스너 +@Getter +public abstract class BaseEntity { + + @CreatedDate + private LocalDateTime createdAt; + + @LastModifiedDate + private LocalDateTime updatedAt; +} diff --git a/src/main/java/umc/spring/domain/enums/Gender.java b/src/main/java/umc/spring/domain/enums/Gender.java new file mode 100644 index 0000000..b74c14e --- /dev/null +++ b/src/main/java/umc/spring/domain/enums/Gender.java @@ -0,0 +1,5 @@ +package umc.spring.domain.enums; + +public enum Gender { + MALE, FEMALE +} diff --git a/src/main/java/umc/spring/domain/enums/MemberStatus.java b/src/main/java/umc/spring/domain/enums/MemberStatus.java new file mode 100644 index 0000000..b416298 --- /dev/null +++ b/src/main/java/umc/spring/domain/enums/MemberStatus.java @@ -0,0 +1,5 @@ +package umc.spring.domain.enums; + +public enum MemberStatus { + ACTIVE, INACTIVE +} diff --git a/src/main/java/umc/spring/domain/enums/MissionStatus.java b/src/main/java/umc/spring/domain/enums/MissionStatus.java new file mode 100644 index 0000000..ac47724 --- /dev/null +++ b/src/main/java/umc/spring/domain/enums/MissionStatus.java @@ -0,0 +1,5 @@ +package umc.spring.domain.enums; + +public enum MissionStatus { + COMPLETE, WAIT, ONGOING +} diff --git a/src/main/java/umc/spring/domain/enums/SocialType.java b/src/main/java/umc/spring/domain/enums/SocialType.java new file mode 100644 index 0000000..6206309 --- /dev/null +++ b/src/main/java/umc/spring/domain/enums/SocialType.java @@ -0,0 +1,5 @@ +package umc.spring.domain.enums; + +public enum SocialType { + KAKAO,GOOGLE,NAVER,APPLE +} diff --git a/src/main/java/umc/spring/domain/enums/TermType.java b/src/main/java/umc/spring/domain/enums/TermType.java new file mode 100644 index 0000000..72e8028 --- /dev/null +++ b/src/main/java/umc/spring/domain/enums/TermType.java @@ -0,0 +1,5 @@ +package umc.spring.domain.enums; + +public enum TermType { + OPTIONAL,ESSENTIAL +} diff --git a/src/main/java/umc/spring/domain/mapping/MemberFood.java b/src/main/java/umc/spring/domain/mapping/MemberFood.java new file mode 100644 index 0000000..28cc582 --- /dev/null +++ b/src/main/java/umc/spring/domain/mapping/MemberFood.java @@ -0,0 +1,27 @@ +package umc.spring.domain.mapping; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import umc.spring.domain.FoodCategory; +import umc.spring.domain.Member; +import umc.spring.domain.common.BaseEntity; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class MemberFood extends BaseEntity{ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "category_id") + private FoodCategory foodCategory; +} diff --git a/src/main/java/umc/spring/domain/mapping/MemberMission.java b/src/main/java/umc/spring/domain/mapping/MemberMission.java new file mode 100644 index 0000000..303d7b3 --- /dev/null +++ b/src/main/java/umc/spring/domain/mapping/MemberMission.java @@ -0,0 +1,39 @@ +package umc.spring.domain.mapping; +import jakarta.persistence.*; +import lombok.*; +import umc.spring.domain.Member; +import umc.spring.domain.Mission; +import umc.spring.domain.common.BaseEntity; +import umc.spring.domain.enums.MissionStatus; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class MemberMission extends BaseEntity{ + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(columnDefinition = "VARCHAR(10) DEFAULT 'WAIT'") + private MissionStatus status; + + private String certificationNumber; + + private LocalDateTime completedAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name="mission_id") + private Mission mission; + + +} diff --git a/src/main/java/umc/spring/domain/mapping/MemberTerms.java b/src/main/java/umc/spring/domain/mapping/MemberTerms.java new file mode 100644 index 0000000..7b99e19 --- /dev/null +++ b/src/main/java/umc/spring/domain/mapping/MemberTerms.java @@ -0,0 +1,32 @@ +package umc.spring.domain.mapping; +import jakarta.persistence.*; +import lombok.*; +import umc.spring.domain.Member; +import umc.spring.domain.Terms; +import umc.spring.domain.common.BaseEntity; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class MemberTerms extends BaseEntity{ + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "terms_id") + private Terms terms; + + private Boolean isAgreed; + + private LocalDateTime agreedAt; +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 1a76dec..076f5e6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,6 +5,18 @@ spring: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mission_db username: root - password: 3mirsammoolsom* + password: 0000 application: - name:sping \ No newline at end of file + name:sping + sql: + init: + mode:always + 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 From 9589954beca6ee8955ad2209868aeae54f251549 Mon Sep 17 00:00:00 2001 From: sammool Date: Mon, 5 May 2025 00:04:28 +0900 Subject: [PATCH 03/16] practice/#6 --- build.gradle | 30 ++++++++ .../umc/spring/domain/QFoodCategory.java | 47 ++++++++++++ .../generated/umc/spring/domain/QMember.java | 74 +++++++++++++++++++ .../generated/umc/spring/domain/QMission.java | 67 +++++++++++++++++ .../generated/umc/spring/domain/QRegion.java | 50 +++++++++++++ .../generated/umc/spring/domain/QReview.java | 68 +++++++++++++++++ .../umc/spring/domain/QReviewImage.java | 61 +++++++++++++++ .../umc/spring/domain/QSalesTime.java | 65 ++++++++++++++++ .../generated/umc/spring/domain/QStore.java | 71 ++++++++++++++++++ .../generated/umc/spring/domain/QTerms.java | 54 ++++++++++++++ .../umc/spring/domain/common/QBaseEntity.java | 39 ++++++++++ .../spring/domain/mapping/QMemberFood.java | 62 ++++++++++++++++ .../spring/domain/mapping/QMemberMission.java | 68 +++++++++++++++++ .../spring/domain/mapping/QMemberTerms.java | 66 +++++++++++++++++ src/main/java/umc/spring/Application.java | 21 ++++++ .../umc/spring/config/QueryDSLConfig.java | 20 +++++ src/main/java/umc/spring/domain/Store.java | 11 +++ .../spring/repository/MemberRepository.java | 15 ++++ .../storeRepository/StoreRepository.java | 7 ++ .../StoreRepositoryCustom.java | 9 +++ .../storeRepository/StoreRepositoryImpl.java | 36 +++++++++ .../storeService/StoreQueryService.java | 11 +++ .../storeService/StoreQueryServiceImpl.java | 30 ++++++++ src/main/resources/application.yml | 2 +- 24 files changed, 983 insertions(+), 1 deletion(-) create mode 100644 src/main/generated/umc/spring/domain/QFoodCategory.java create mode 100644 src/main/generated/umc/spring/domain/QMember.java create mode 100644 src/main/generated/umc/spring/domain/QMission.java create mode 100644 src/main/generated/umc/spring/domain/QRegion.java create mode 100644 src/main/generated/umc/spring/domain/QReview.java create mode 100644 src/main/generated/umc/spring/domain/QReviewImage.java create mode 100644 src/main/generated/umc/spring/domain/QSalesTime.java create mode 100644 src/main/generated/umc/spring/domain/QStore.java create mode 100644 src/main/generated/umc/spring/domain/QTerms.java create mode 100644 src/main/generated/umc/spring/domain/common/QBaseEntity.java create mode 100644 src/main/generated/umc/spring/domain/mapping/QMemberFood.java create mode 100644 src/main/generated/umc/spring/domain/mapping/QMemberMission.java create mode 100644 src/main/generated/umc/spring/domain/mapping/QMemberTerms.java create mode 100644 src/main/java/umc/spring/config/QueryDSLConfig.java create mode 100644 src/main/java/umc/spring/repository/MemberRepository.java create mode 100644 src/main/java/umc/spring/repository/storeRepository/StoreRepository.java create mode 100644 src/main/java/umc/spring/repository/storeRepository/StoreRepositoryCustom.java create mode 100644 src/main/java/umc/spring/repository/storeRepository/StoreRepositoryImpl.java create mode 100644 src/main/java/umc/spring/service/storeService/StoreQueryService.java create mode 100644 src/main/java/umc/spring/service/storeService/StoreQueryServiceImpl.java diff --git a/build.gradle b/build.gradle index 4022d03..16022e1 100644 --- a/build.gradle +++ b/build.gradle @@ -26,13 +26,43 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.hibernate.orm:hibernate-core:6.0.2.Final' // Hibernate 6.0.2 이상 + implementation 'mysql:mysql-connector-java:8.0.33' // MySQL 드라이버 추가 + compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + // queryDSL + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" } tasks.named('test') { useJUnitPlatform() } + + +// Querydsl 설정부 +def generated = 'src/main/generated' + + +sourceSets { + main { + java { + srcDirs += generated + } + } +} + +configurations { + querydsl.extendsFrom compileClasspath +} + +tasks.withType(JavaCompile).configureEach { + options.annotationProcessorGeneratedSourcesDirectory = file(generated) +} \ No newline at end of file diff --git a/src/main/generated/umc/spring/domain/QFoodCategory.java b/src/main/generated/umc/spring/domain/QFoodCategory.java new file mode 100644 index 0000000..6fa7e8a --- /dev/null +++ b/src/main/generated/umc/spring/domain/QFoodCategory.java @@ -0,0 +1,47 @@ +package umc.spring.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QFoodCategory is a Querydsl query type for FoodCategory + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QFoodCategory extends EntityPathBase { + + private static final long serialVersionUID = -811463128L; + + public static final QFoodCategory foodCategory = new QFoodCategory("foodCategory"); + + public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final NumberPath id = createNumber("id", Long.class); + + public final StringPath name = createString("name"); + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public QFoodCategory(String variable) { + super(FoodCategory.class, forVariable(variable)); + } + + public QFoodCategory(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QFoodCategory(PathMetadata metadata) { + super(FoodCategory.class, metadata); + } + +} + diff --git a/src/main/generated/umc/spring/domain/QMember.java b/src/main/generated/umc/spring/domain/QMember.java new file mode 100644 index 0000000..c56e438 --- /dev/null +++ b/src/main/generated/umc/spring/domain/QMember.java @@ -0,0 +1,74 @@ +package umc.spring.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QMember is a Querydsl query type for Member + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QMember extends EntityPathBase { + + private static final long serialVersionUID = 1366956614L; + + public static final QMember member = new QMember("member1"); + + public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); + + public final StringPath address = createString("address"); + + public final DatePath birth = createDate("birth", java.time.LocalDate.class); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final StringPath email = createString("email"); + + public final EnumPath gender = createEnum("gender", umc.spring.domain.enums.Gender.class); + + public final NumberPath id = createNumber("id", Long.class); + + public final DatePath inactiveDate = createDate("inactiveDate", java.time.LocalDate.class); + + public final ListPath memberFoodList = this.createList("memberFoodList", umc.spring.domain.mapping.MemberFood.class, umc.spring.domain.mapping.QMemberFood.class, PathInits.DIRECT2); + + public final ListPath memberMissionList = this.createList("memberMissionList", umc.spring.domain.mapping.MemberMission.class, umc.spring.domain.mapping.QMemberMission.class, PathInits.DIRECT2); + + public final ListPath memberTermsList = this.createList("memberTermsList", umc.spring.domain.mapping.MemberTerms.class, umc.spring.domain.mapping.QMemberTerms.class, PathInits.DIRECT2); + + public final StringPath name = createString("name"); + + public final StringPath phoneNumber = createString("phoneNumber"); + + public final NumberPath point = createNumber("point", Integer.class); + + public final ListPath reviewList = this.createList("reviewList", Review.class, QReview.class, PathInits.DIRECT2); + + public final EnumPath socialType = createEnum("socialType", umc.spring.domain.enums.SocialType.class); + + public final EnumPath status = createEnum("status", umc.spring.domain.enums.MemberStatus.class); + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public QMember(String variable) { + super(Member.class, forVariable(variable)); + } + + public QMember(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QMember(PathMetadata metadata) { + super(Member.class, metadata); + } + +} + diff --git a/src/main/generated/umc/spring/domain/QMission.java b/src/main/generated/umc/spring/domain/QMission.java new file mode 100644 index 0000000..611a7d0 --- /dev/null +++ b/src/main/generated/umc/spring/domain/QMission.java @@ -0,0 +1,67 @@ +package umc.spring.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QMission is a Querydsl query type for Mission + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QMission extends EntityPathBase { + + private static final long serialVersionUID = -453449888L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QMission mission = new QMission("mission"); + + public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final DatePath deadline = createDate("deadline", java.time.LocalDate.class); + + public final StringPath description = createString("description"); + + public final NumberPath id = createNumber("id", Long.class); + + public final ListPath memberMissionList = this.createList("memberMissionList", umc.spring.domain.mapping.MemberMission.class, umc.spring.domain.mapping.QMemberMission.class, PathInits.DIRECT2); + + public final NumberPath point = createNumber("point", Integer.class); + + public final QStore store; + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public QMission(String variable) { + this(Mission.class, forVariable(variable), INITS); + } + + public QMission(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QMission(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QMission(PathMetadata metadata, PathInits inits) { + this(Mission.class, metadata, inits); + } + + public QMission(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.store = inits.isInitialized("store") ? new QStore(forProperty("store"), inits.get("store")) : null; + } + +} + diff --git a/src/main/generated/umc/spring/domain/QRegion.java b/src/main/generated/umc/spring/domain/QRegion.java new file mode 100644 index 0000000..1ca2dee --- /dev/null +++ b/src/main/generated/umc/spring/domain/QRegion.java @@ -0,0 +1,50 @@ +package umc.spring.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QRegion is a Querydsl query type for Region + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QRegion extends EntityPathBase { + + private static final long serialVersionUID = 1509930656L; + + public static final QRegion region = new QRegion("region"); + + public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final NumberPath id = createNumber("id", Long.class); + + public final StringPath name = createString("name"); + + public final ListPath storeList = this.createList("storeList", Store.class, QStore.class, PathInits.DIRECT2); + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public QRegion(String variable) { + super(Region.class, forVariable(variable)); + } + + public QRegion(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QRegion(PathMetadata metadata) { + super(Region.class, metadata); + } + +} + diff --git a/src/main/generated/umc/spring/domain/QReview.java b/src/main/generated/umc/spring/domain/QReview.java new file mode 100644 index 0000000..c749fb2 --- /dev/null +++ b/src/main/generated/umc/spring/domain/QReview.java @@ -0,0 +1,68 @@ +package umc.spring.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QReview is a Querydsl query type for Review + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QReview extends EntityPathBase { + + private static final long serialVersionUID = 1510377220L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QReview review = new QReview("review"); + + public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); + + public final StringPath content = createString("content"); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final NumberPath id = createNumber("id", Long.class); + + public final QMember member; + + public final ListPath reviewImageList = this.createList("reviewImageList", ReviewImage.class, QReviewImage.class, PathInits.DIRECT2); + + public final NumberPath score = createNumber("score", Float.class); + + public final QStore store; + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public QReview(String variable) { + this(Review.class, forVariable(variable), INITS); + } + + public QReview(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QReview(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QReview(PathMetadata metadata, PathInits inits) { + this(Review.class, metadata, inits); + } + + public QReview(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.member = inits.isInitialized("member") ? new QMember(forProperty("member")) : null; + this.store = inits.isInitialized("store") ? new QStore(forProperty("store"), inits.get("store")) : null; + } + +} + diff --git a/src/main/generated/umc/spring/domain/QReviewImage.java b/src/main/generated/umc/spring/domain/QReviewImage.java new file mode 100644 index 0000000..ab300af --- /dev/null +++ b/src/main/generated/umc/spring/domain/QReviewImage.java @@ -0,0 +1,61 @@ +package umc.spring.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QReviewImage is a Querydsl query type for ReviewImage + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QReviewImage extends EntityPathBase { + + private static final long serialVersionUID = 1661007031L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QReviewImage reviewImage = new QReviewImage("reviewImage"); + + public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final NumberPath id = createNumber("id", Long.class); + + public final StringPath imageUrl = createString("imageUrl"); + + public final QReview review; + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public QReviewImage(String variable) { + this(ReviewImage.class, forVariable(variable), INITS); + } + + public QReviewImage(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QReviewImage(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QReviewImage(PathMetadata metadata, PathInits inits) { + this(ReviewImage.class, metadata, inits); + } + + public QReviewImage(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.review = inits.isInitialized("review") ? new QReview(forProperty("review"), inits.get("review")) : null; + } + +} + diff --git a/src/main/generated/umc/spring/domain/QSalesTime.java b/src/main/generated/umc/spring/domain/QSalesTime.java new file mode 100644 index 0000000..3fdc77e --- /dev/null +++ b/src/main/generated/umc/spring/domain/QSalesTime.java @@ -0,0 +1,65 @@ +package umc.spring.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QSalesTime is a Querydsl query type for SalesTime + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QSalesTime extends EntityPathBase { + + private static final long serialVersionUID = 993979085L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QSalesTime salesTime = new QSalesTime("salesTime"); + + public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); + + public final DateTimePath closeTime = createDateTime("closeTime", java.time.LocalDateTime.class); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final NumberPath dayOfWeek = createNumber("dayOfWeek", Integer.class); + + public final NumberPath id = createNumber("id", Long.class); + + public final DateTimePath openTime = createDateTime("openTime", java.time.LocalDateTime.class); + + public final QStore store; + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public QSalesTime(String variable) { + this(SalesTime.class, forVariable(variable), INITS); + } + + public QSalesTime(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QSalesTime(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QSalesTime(PathMetadata metadata, PathInits inits) { + this(SalesTime.class, metadata, inits); + } + + public QSalesTime(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.store = inits.isInitialized("store") ? new QStore(forProperty("store"), inits.get("store")) : null; + } + +} + diff --git a/src/main/generated/umc/spring/domain/QStore.java b/src/main/generated/umc/spring/domain/QStore.java new file mode 100644 index 0000000..632bdc0 --- /dev/null +++ b/src/main/generated/umc/spring/domain/QStore.java @@ -0,0 +1,71 @@ +package umc.spring.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QStore is a Querydsl query type for Store + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QStore extends EntityPathBase { + + private static final long serialVersionUID = 1158464437L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QStore store = new QStore("store"); + + public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); + + public final StringPath address = createString("address"); + + public final StringPath category = createString("category"); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final NumberPath id = createNumber("id", Long.class); + + public final ListPath missionList = this.createList("missionList", Mission.class, QMission.class, PathInits.DIRECT2); + + public final StringPath name = createString("name"); + + public final QRegion region; + + public final ListPath salesTimeList = this.createList("salesTimeList", SalesTime.class, QSalesTime.class, PathInits.DIRECT2); + + public final NumberPath score = createNumber("score", Float.class); + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public QStore(String variable) { + this(Store.class, forVariable(variable), INITS); + } + + public QStore(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QStore(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QStore(PathMetadata metadata, PathInits inits) { + this(Store.class, metadata, inits); + } + + public QStore(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.region = inits.isInitialized("region") ? new QRegion(forProperty("region")) : null; + } + +} + diff --git a/src/main/generated/umc/spring/domain/QTerms.java b/src/main/generated/umc/spring/domain/QTerms.java new file mode 100644 index 0000000..fd34f3a --- /dev/null +++ b/src/main/generated/umc/spring/domain/QTerms.java @@ -0,0 +1,54 @@ +package umc.spring.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QTerms is a Querydsl query type for Terms + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QTerms extends EntityPathBase { + + private static final long serialVersionUID = 1158943835L; + + public static final QTerms terms = new QTerms("terms"); + + public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final StringPath description = createString("description"); + + public final NumberPath id = createNumber("id", Long.class); + + public final ListPath memberTermsList = this.createList("memberTermsList", umc.spring.domain.mapping.MemberTerms.class, umc.spring.domain.mapping.QMemberTerms.class, PathInits.DIRECT2); + + public final StringPath name = createString("name"); + + public final EnumPath type = createEnum("type", umc.spring.domain.enums.TermType.class); + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public QTerms(String variable) { + super(Terms.class, forVariable(variable)); + } + + public QTerms(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QTerms(PathMetadata metadata) { + super(Terms.class, metadata); + } + +} + diff --git a/src/main/generated/umc/spring/domain/common/QBaseEntity.java b/src/main/generated/umc/spring/domain/common/QBaseEntity.java new file mode 100644 index 0000000..0bafae2 --- /dev/null +++ b/src/main/generated/umc/spring/domain/common/QBaseEntity.java @@ -0,0 +1,39 @@ +package umc.spring.domain.common; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QBaseEntity is a Querydsl query type for BaseEntity + */ +@Generated("com.querydsl.codegen.DefaultSupertypeSerializer") +public class QBaseEntity extends EntityPathBase { + + private static final long serialVersionUID = -1748720885L; + + public static final QBaseEntity baseEntity = new QBaseEntity("baseEntity"); + + public final DateTimePath createdAt = createDateTime("createdAt", java.time.LocalDateTime.class); + + public final DateTimePath updatedAt = createDateTime("updatedAt", java.time.LocalDateTime.class); + + public QBaseEntity(String variable) { + super(BaseEntity.class, forVariable(variable)); + } + + public QBaseEntity(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QBaseEntity(PathMetadata metadata) { + super(BaseEntity.class, metadata); + } + +} + diff --git a/src/main/generated/umc/spring/domain/mapping/QMemberFood.java b/src/main/generated/umc/spring/domain/mapping/QMemberFood.java new file mode 100644 index 0000000..5b5c261 --- /dev/null +++ b/src/main/generated/umc/spring/domain/mapping/QMemberFood.java @@ -0,0 +1,62 @@ +package umc.spring.domain.mapping; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QMemberFood is a Querydsl query type for MemberFood + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QMemberFood extends EntityPathBase { + + private static final long serialVersionUID = 488166628L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QMemberFood memberFood = new QMemberFood("memberFood"); + + public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final umc.spring.domain.QFoodCategory foodCategory; + + public final NumberPath id = createNumber("id", Long.class); + + public final umc.spring.domain.QMember member; + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public QMemberFood(String variable) { + this(MemberFood.class, forVariable(variable), INITS); + } + + public QMemberFood(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QMemberFood(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QMemberFood(PathMetadata metadata, PathInits inits) { + this(MemberFood.class, metadata, inits); + } + + public QMemberFood(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.foodCategory = inits.isInitialized("foodCategory") ? new umc.spring.domain.QFoodCategory(forProperty("foodCategory")) : null; + this.member = inits.isInitialized("member") ? new umc.spring.domain.QMember(forProperty("member")) : null; + } + +} + diff --git a/src/main/generated/umc/spring/domain/mapping/QMemberMission.java b/src/main/generated/umc/spring/domain/mapping/QMemberMission.java new file mode 100644 index 0000000..5055c06 --- /dev/null +++ b/src/main/generated/umc/spring/domain/mapping/QMemberMission.java @@ -0,0 +1,68 @@ +package umc.spring.domain.mapping; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QMemberMission is a Querydsl query type for MemberMission + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QMemberMission extends EntityPathBase { + + private static final long serialVersionUID = 1962779462L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QMemberMission memberMission = new QMemberMission("memberMission"); + + public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); + + public final StringPath certificationNumber = createString("certificationNumber"); + + public final DateTimePath completedAt = createDateTime("completedAt", java.time.LocalDateTime.class); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final NumberPath id = createNumber("id", Long.class); + + public final umc.spring.domain.QMember member; + + public final umc.spring.domain.QMission mission; + + public final EnumPath status = createEnum("status", umc.spring.domain.enums.MissionStatus.class); + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public QMemberMission(String variable) { + this(MemberMission.class, forVariable(variable), INITS); + } + + public QMemberMission(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QMemberMission(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QMemberMission(PathMetadata metadata, PathInits inits) { + this(MemberMission.class, metadata, inits); + } + + public QMemberMission(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.member = inits.isInitialized("member") ? new umc.spring.domain.QMember(forProperty("member")) : null; + this.mission = inits.isInitialized("mission") ? new umc.spring.domain.QMission(forProperty("mission"), inits.get("mission")) : null; + } + +} + diff --git a/src/main/generated/umc/spring/domain/mapping/QMemberTerms.java b/src/main/generated/umc/spring/domain/mapping/QMemberTerms.java new file mode 100644 index 0000000..f39ca03 --- /dev/null +++ b/src/main/generated/umc/spring/domain/mapping/QMemberTerms.java @@ -0,0 +1,66 @@ +package umc.spring.domain.mapping; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QMemberTerms is a Querydsl query type for MemberTerms + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QMemberTerms extends EntityPathBase { + + private static final long serialVersionUID = -2034069055L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QMemberTerms memberTerms = new QMemberTerms("memberTerms"); + + public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); + + public final DateTimePath agreedAt = createDateTime("agreedAt", java.time.LocalDateTime.class); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final NumberPath id = createNumber("id", Long.class); + + public final BooleanPath isAgreed = createBoolean("isAgreed"); + + public final umc.spring.domain.QMember member; + + public final umc.spring.domain.QTerms terms; + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public QMemberTerms(String variable) { + this(MemberTerms.class, forVariable(variable), INITS); + } + + public QMemberTerms(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QMemberTerms(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QMemberTerms(PathMetadata metadata, PathInits inits) { + this(MemberTerms.class, metadata, inits); + } + + public QMemberTerms(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.member = inits.isInitialized("member") ? new umc.spring.domain.QMember(forProperty("member")) : null; + this.terms = inits.isInitialized("terms") ? new umc.spring.domain.QTerms(forProperty("terms")) : null; + } + +} + diff --git a/src/main/java/umc/spring/Application.java b/src/main/java/umc/spring/Application.java index 1712ec5..9d0cd43 100644 --- a/src/main/java/umc/spring/Application.java +++ b/src/main/java/umc/spring/Application.java @@ -1,8 +1,12 @@ package umc.spring; +import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import umc.spring.service.storeService.StoreQueryService; @SpringBootApplication @EnableJpaAuditing @@ -12,4 +16,21 @@ public static void main(String[] args) { SpringApplication.run(Application.class, args); } + @Bean + public CommandLineRunner run(ApplicationContext context){ + return args -> { + StoreQueryService storeService = context.getBean(StoreQueryService.class); + + String name = "요아정"; + Float score = 4.0f; + + System.out.println("Executing findStoresByNameAndScore with parameters:"); + System.out.println("Name: " + name); + System.out.println("Score: " + score); + + storeService.findStoreByNameAndScore(name,score) + .forEach(System.out::println); + + }; + } } diff --git a/src/main/java/umc/spring/config/QueryDSLConfig.java b/src/main/java/umc/spring/config/QueryDSLConfig.java new file mode 100644 index 0000000..9948c3f --- /dev/null +++ b/src/main/java/umc/spring/config/QueryDSLConfig.java @@ -0,0 +1,20 @@ +package umc.spring.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import umc.spring.domain.Store; + +@Configuration +@RequiredArgsConstructor +public class QueryDSLConfig { + private final EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory(){ + return new JPAQueryFactory(entityManager); + } + +} diff --git a/src/main/java/umc/spring/domain/Store.java b/src/main/java/umc/spring/domain/Store.java index fb14a70..898944e 100644 --- a/src/main/java/umc/spring/domain/Store.java +++ b/src/main/java/umc/spring/domain/Store.java @@ -38,4 +38,15 @@ public class Store extends BaseEntity { @OneToMany(mappedBy = "store", cascade = CascadeType.ALL) private List salesTimeList = new ArrayList<>(); + + @Override + public String toString() { + return "Store{" + + "id=" + id + + ", name='" + name + '\'' + + ", address='" + address + '\'' + + ", score=" + score + + ", region=" + (region != null ? region.getName() : "N/A") + // region의 이름 출력 + '}'; + } } diff --git a/src/main/java/umc/spring/repository/MemberRepository.java b/src/main/java/umc/spring/repository/MemberRepository.java new file mode 100644 index 0000000..3139bb4 --- /dev/null +++ b/src/main/java/umc/spring/repository/MemberRepository.java @@ -0,0 +1,15 @@ +package umc.spring.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import umc.spring.domain.Member; +import umc.spring.domain.enums.MemberStatus; + +import java.util.List; + +public interface MemberRepository extends JpaRepository { + + @Query("SELECT m FROM Member m WHERE m.name = :name AND m.status = :status") + List findByNameAndStatus(@Param("name") String name, @Param("status") MemberStatus status); +} diff --git a/src/main/java/umc/spring/repository/storeRepository/StoreRepository.java b/src/main/java/umc/spring/repository/storeRepository/StoreRepository.java new file mode 100644 index 0000000..4c0be6e --- /dev/null +++ b/src/main/java/umc/spring/repository/storeRepository/StoreRepository.java @@ -0,0 +1,7 @@ +package umc.spring.repository.storeRepository; + +import org.springframework.data.jpa.repository.JpaRepository; +import umc.spring.domain.Store; + +public interface StoreRepository extends JpaRepository, StoreRepositoryCustom { +} diff --git a/src/main/java/umc/spring/repository/storeRepository/StoreRepositoryCustom.java b/src/main/java/umc/spring/repository/storeRepository/StoreRepositoryCustom.java new file mode 100644 index 0000000..dcf2f0d --- /dev/null +++ b/src/main/java/umc/spring/repository/storeRepository/StoreRepositoryCustom.java @@ -0,0 +1,9 @@ +package umc.spring.repository.storeRepository; + +import umc.spring.domain.Store; + +import java.util.List; + +public interface StoreRepositoryCustom { + List dynamicQueryWithBooleanBuilder(String name, Float score); +} diff --git a/src/main/java/umc/spring/repository/storeRepository/StoreRepositoryImpl.java b/src/main/java/umc/spring/repository/storeRepository/StoreRepositoryImpl.java new file mode 100644 index 0000000..61c21b4 --- /dev/null +++ b/src/main/java/umc/spring/repository/storeRepository/StoreRepositoryImpl.java @@ -0,0 +1,36 @@ +package umc.spring.repository.storeRepository; + +import com.querydsl.core.BooleanBuilder; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import umc.spring.domain.QStore; +import umc.spring.domain.Store; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class StoreRepositoryImpl implements StoreRepositoryCustom{ + + private final JPAQueryFactory jpaQueryFactory; + private final QStore store = QStore.store; + + @Override + public List dynamicQueryWithBooleanBuilder(String name, Float score){ + BooleanBuilder predicate = new BooleanBuilder(); + + if(name!=null){ + predicate.and(store.name.eq(name)); + } + + if(score!=null){ + predicate.and(store.score.goe(4.0f)); + } + + return jpaQueryFactory + .selectFrom(store) + .where(predicate) + .fetch(); + } +} diff --git a/src/main/java/umc/spring/service/storeService/StoreQueryService.java b/src/main/java/umc/spring/service/storeService/StoreQueryService.java new file mode 100644 index 0000000..9162058 --- /dev/null +++ b/src/main/java/umc/spring/service/storeService/StoreQueryService.java @@ -0,0 +1,11 @@ +package umc.spring.service.storeService; + +import umc.spring.domain.Store; + +import java.util.List; +import java.util.Optional; + +public interface StoreQueryService { + Optional findStore(Long id); + List findStoreByNameAndScore(String name, Float score); +} diff --git a/src/main/java/umc/spring/service/storeService/StoreQueryServiceImpl.java b/src/main/java/umc/spring/service/storeService/StoreQueryServiceImpl.java new file mode 100644 index 0000000..a9c4fd6 --- /dev/null +++ b/src/main/java/umc/spring/service/storeService/StoreQueryServiceImpl.java @@ -0,0 +1,30 @@ +package umc.spring.service.storeService; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import umc.spring.repository.storeRepository.StoreRepository; +import umc.spring.domain.Store; + +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class StoreQueryServiceImpl implements StoreQueryService{ + private final StoreRepository storeRepository; + + @Override + public Optional findStore(Long id){ + return storeRepository.findById(id); + } + + @Override + public List findStoreByNameAndScore(String name, Float score){ + List filteredStores = storeRepository.dynamicQueryWithBooleanBuilder(name, score); + filteredStores.forEach(s-> System.out.println("Store: " + s)); + + return filteredStores; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 076f5e6..9635924 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,7 +5,7 @@ spring: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mission_db username: root - password: 0000 + password: 3mirsammoolsom* application: name:sping sql: From f7882498bcd075b3f549f2b63fb148539f935fb8 Mon Sep 17 00:00:00 2001 From: sammool Date: Mon, 12 May 2025 16:58:14 +0900 Subject: [PATCH 04/16] mission/#07 --- .../umc/spring/apiPayload/ApiResponse.java | 33 +++++ .../java/umc/spring/apiPayload/BaseCode.java | 6 + .../umc/spring/apiPayload/BaseErrorCode.java | 6 + .../umc/spring/apiPayload/ErrorReasonDTO.java | 16 +++ .../java/umc/spring/apiPayload/ReasonDTO.java | 17 +++ .../spring/apiPayload/code/ErrorStatus.java | 46 +++++++ .../spring/apiPayload/code/SuccessStatus.java | 37 ++++++ .../apiPayload/exception/ExceptionAdvice.java | 119 ++++++++++++++++++ .../exception/GeneralException.java | 21 ++++ .../exception/handler/TempHandler.java | 10 ++ .../umc/spring/converter/TempConverter.java | 17 +++ .../MemberRepository.java | 0 .../storeService/StoreQueryService.java | 11 -- .../storeService/StoreQueryServiceImpl.java | 30 ----- .../service/tempService/TempQueryService.java | 5 + .../tempService/TempQueryServiceImpl.java | 16 +++ .../web/controller/TempRestController.java | 30 +++++ .../java/umc/spring/web/dto/TempRequest.java | 4 + .../java/umc/spring/web/dto/TempResponse.java | 24 ++++ 19 files changed, 407 insertions(+), 41 deletions(-) create mode 100644 src/main/java/umc/spring/apiPayload/ApiResponse.java create mode 100644 src/main/java/umc/spring/apiPayload/BaseCode.java create mode 100644 src/main/java/umc/spring/apiPayload/BaseErrorCode.java create mode 100644 src/main/java/umc/spring/apiPayload/ErrorReasonDTO.java create mode 100644 src/main/java/umc/spring/apiPayload/ReasonDTO.java create mode 100644 src/main/java/umc/spring/apiPayload/code/ErrorStatus.java create mode 100644 src/main/java/umc/spring/apiPayload/code/SuccessStatus.java create mode 100644 src/main/java/umc/spring/apiPayload/exception/ExceptionAdvice.java create mode 100644 src/main/java/umc/spring/apiPayload/exception/GeneralException.java create mode 100644 src/main/java/umc/spring/apiPayload/exception/handler/TempHandler.java create mode 100644 src/main/java/umc/spring/converter/TempConverter.java rename src/main/java/umc/spring/repository/{ => memberRepository}/MemberRepository.java (100%) delete mode 100644 src/main/java/umc/spring/service/storeService/StoreQueryService.java delete mode 100644 src/main/java/umc/spring/service/storeService/StoreQueryServiceImpl.java create mode 100644 src/main/java/umc/spring/service/tempService/TempQueryService.java create mode 100644 src/main/java/umc/spring/service/tempService/TempQueryServiceImpl.java create mode 100644 src/main/java/umc/spring/web/controller/TempRestController.java create mode 100644 src/main/java/umc/spring/web/dto/TempRequest.java create mode 100644 src/main/java/umc/spring/web/dto/TempResponse.java diff --git a/src/main/java/umc/spring/apiPayload/ApiResponse.java b/src/main/java/umc/spring/apiPayload/ApiResponse.java new file mode 100644 index 0000000..46f00e2 --- /dev/null +++ b/src/main/java/umc/spring/apiPayload/ApiResponse.java @@ -0,0 +1,33 @@ +package umc.spring.apiPayload; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.AllArgsConstructor; +import lombok.Getter; +import umc.spring.apiPayload.code.SuccessStatus; + +@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/umc/spring/apiPayload/BaseCode.java b/src/main/java/umc/spring/apiPayload/BaseCode.java new file mode 100644 index 0000000..194df53 --- /dev/null +++ b/src/main/java/umc/spring/apiPayload/BaseCode.java @@ -0,0 +1,6 @@ +package umc.spring.apiPayload; + +public interface BaseCode { + ReasonDTO getReason(); + ReasonDTO getReasonHttpStatus(); +} diff --git a/src/main/java/umc/spring/apiPayload/BaseErrorCode.java b/src/main/java/umc/spring/apiPayload/BaseErrorCode.java new file mode 100644 index 0000000..86769d1 --- /dev/null +++ b/src/main/java/umc/spring/apiPayload/BaseErrorCode.java @@ -0,0 +1,6 @@ +package umc.spring.apiPayload; + +public interface BaseErrorCode { + ErrorReasonDTO getReason(); + ErrorReasonDTO getReasonHttpStatus(); +} diff --git a/src/main/java/umc/spring/apiPayload/ErrorReasonDTO.java b/src/main/java/umc/spring/apiPayload/ErrorReasonDTO.java new file mode 100644 index 0000000..289ee83 --- /dev/null +++ b/src/main/java/umc/spring/apiPayload/ErrorReasonDTO.java @@ -0,0 +1,16 @@ +package umc.spring.apiPayload; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@Builder +public class ErrorReasonDTO { + private HttpStatus httpStatus; + private final boolean isSuccess; + private final String code; + private final String message; + + public boolean getIsSuccess(){return isSuccess;} +} diff --git a/src/main/java/umc/spring/apiPayload/ReasonDTO.java b/src/main/java/umc/spring/apiPayload/ReasonDTO.java new file mode 100644 index 0000000..bc8c8b8 --- /dev/null +++ b/src/main/java/umc/spring/apiPayload/ReasonDTO.java @@ -0,0 +1,17 @@ +package umc.spring.apiPayload; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@Builder +public class ReasonDTO { + private HttpStatus httpStatus; + + private final boolean isSuccess; + private final String code; + private final String message; + + public boolean getIsSuccess(){return isSuccess;} +} diff --git a/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java b/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java new file mode 100644 index 0000000..38d75bc --- /dev/null +++ b/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java @@ -0,0 +1,46 @@ +package umc.spring.apiPayload.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import umc.spring.apiPayload.BaseErrorCode; +import umc.spring.apiPayload.ErrorReasonDTO; + +@Getter +@AllArgsConstructor +public enum ErrorStatus implements BaseErrorCode { + + _INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON500", "서버 에러, 관리자에게 문의 바랍니다."), + _BAD_REQUEST(HttpStatus.BAD_REQUEST,"COMMON400","잘못된 요청입니다."), + _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"COMMON401","인증이 필요합니다."), + _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."), + + // 멤버 관려 에러 + MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER4001", "사용자가 없습니다."), + NICKNAME_NOT_EXIST(HttpStatus.BAD_REQUEST, "MEMBER4002", "닉네임은 필수 입니다."), + + TEMP_EXCEPTION(HttpStatus.BAD_REQUEST, "TEMP4001", "test exception"); + + 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/umc/spring/apiPayload/code/SuccessStatus.java b/src/main/java/umc/spring/apiPayload/code/SuccessStatus.java new file mode 100644 index 0000000..2afa035 --- /dev/null +++ b/src/main/java/umc/spring/apiPayload/code/SuccessStatus.java @@ -0,0 +1,37 @@ +package umc.spring.apiPayload.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import umc.spring.apiPayload.BaseCode; +import umc.spring.apiPayload.ReasonDTO; + +@Getter +@AllArgsConstructor +public enum SuccessStatus implements BaseCode { + + _OK(HttpStatus.OK, "COMMON200", "성공입니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ReasonDTO getReason(){ + return ReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(true) + .build(); + } + + @Override + public ReasonDTO getReasonHttpStatus(){ + return ReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(true) + .httpStatus(httpStatus) + .build(); + } +} diff --git a/src/main/java/umc/spring/apiPayload/exception/ExceptionAdvice.java b/src/main/java/umc/spring/apiPayload/exception/ExceptionAdvice.java new file mode 100644 index 0000000..9e13bee --- /dev/null +++ b/src/main/java/umc/spring/apiPayload/exception/ExceptionAdvice.java @@ -0,0 +1,119 @@ +package umc.spring.apiPayload.exception; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +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 umc.spring.apiPayload.ApiResponse; +import umc.spring.apiPayload.ErrorReasonDTO; +import umc.spring.apiPayload.code.ErrorStatus; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@RestControllerAdvice(annotations = {RestController.class}) +public class ExceptionAdvice extends ResponseEntityExceptionHandler { + + + @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, HttpStatusCode 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); + } + + @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) //GeneralException에 대한 오류를 처리 + 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 + ); + } +} diff --git a/src/main/java/umc/spring/apiPayload/exception/GeneralException.java b/src/main/java/umc/spring/apiPayload/exception/GeneralException.java new file mode 100644 index 0000000..55da271 --- /dev/null +++ b/src/main/java/umc/spring/apiPayload/exception/GeneralException.java @@ -0,0 +1,21 @@ +package umc.spring.apiPayload.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import umc.spring.apiPayload.BaseCode; +import umc.spring.apiPayload.BaseErrorCode; +import umc.spring.apiPayload.ErrorReasonDTO; + +@Getter +@AllArgsConstructor +public class GeneralException extends RuntimeException { + private BaseErrorCode code; + + public ErrorReasonDTO getErrorReason(){ + return this.code.getReason(); + } + + public ErrorReasonDTO getErrorReasonHttpStatus(){ + return this.code.getReasonHttpStatus(); + } +} diff --git a/src/main/java/umc/spring/apiPayload/exception/handler/TempHandler.java b/src/main/java/umc/spring/apiPayload/exception/handler/TempHandler.java new file mode 100644 index 0000000..af39a60 --- /dev/null +++ b/src/main/java/umc/spring/apiPayload/exception/handler/TempHandler.java @@ -0,0 +1,10 @@ +package umc.spring.apiPayload.exception.handler; + +import umc.spring.apiPayload.BaseErrorCode; +import umc.spring.apiPayload.exception.GeneralException; + +public class TempHandler extends GeneralException { + public TempHandler(BaseErrorCode errorCode){ + super(errorCode); + } +} diff --git a/src/main/java/umc/spring/converter/TempConverter.java b/src/main/java/umc/spring/converter/TempConverter.java new file mode 100644 index 0000000..cfba835 --- /dev/null +++ b/src/main/java/umc/spring/converter/TempConverter.java @@ -0,0 +1,17 @@ +package umc.spring.converter; + +import umc.spring.web.dto.TempResponse; + +public class TempConverter { + public static TempResponse.TempTestDTO toTempTestDTO(){ + return TempResponse.TempTestDTO.builder() + .testString("This is Test!") + .build(); + } + + public static TempResponse.TempExceptionDTO toTempExceptionDTO(Integer flag){ + return TempResponse.TempExceptionDTO.builder() + .flag(flag) + .build(); + } +} diff --git a/src/main/java/umc/spring/repository/MemberRepository.java b/src/main/java/umc/spring/repository/memberRepository/MemberRepository.java similarity index 100% rename from src/main/java/umc/spring/repository/MemberRepository.java rename to src/main/java/umc/spring/repository/memberRepository/MemberRepository.java diff --git a/src/main/java/umc/spring/service/storeService/StoreQueryService.java b/src/main/java/umc/spring/service/storeService/StoreQueryService.java deleted file mode 100644 index 9162058..0000000 --- a/src/main/java/umc/spring/service/storeService/StoreQueryService.java +++ /dev/null @@ -1,11 +0,0 @@ -package umc.spring.service.storeService; - -import umc.spring.domain.Store; - -import java.util.List; -import java.util.Optional; - -public interface StoreQueryService { - Optional findStore(Long id); - List findStoreByNameAndScore(String name, Float score); -} diff --git a/src/main/java/umc/spring/service/storeService/StoreQueryServiceImpl.java b/src/main/java/umc/spring/service/storeService/StoreQueryServiceImpl.java deleted file mode 100644 index a9c4fd6..0000000 --- a/src/main/java/umc/spring/service/storeService/StoreQueryServiceImpl.java +++ /dev/null @@ -1,30 +0,0 @@ -package umc.spring.service.storeService; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import umc.spring.repository.storeRepository.StoreRepository; -import umc.spring.domain.Store; - -import java.util.List; -import java.util.Optional; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class StoreQueryServiceImpl implements StoreQueryService{ - private final StoreRepository storeRepository; - - @Override - public Optional findStore(Long id){ - return storeRepository.findById(id); - } - - @Override - public List findStoreByNameAndScore(String name, Float score){ - List filteredStores = storeRepository.dynamicQueryWithBooleanBuilder(name, score); - filteredStores.forEach(s-> System.out.println("Store: " + s)); - - return filteredStores; - } -} diff --git a/src/main/java/umc/spring/service/tempService/TempQueryService.java b/src/main/java/umc/spring/service/tempService/TempQueryService.java new file mode 100644 index 0000000..08e6f22 --- /dev/null +++ b/src/main/java/umc/spring/service/tempService/TempQueryService.java @@ -0,0 +1,5 @@ +package umc.spring.service.tempService; + +public interface TempQueryService { + void checkFlag(Integer flag); +} diff --git a/src/main/java/umc/spring/service/tempService/TempQueryServiceImpl.java b/src/main/java/umc/spring/service/tempService/TempQueryServiceImpl.java new file mode 100644 index 0000000..eabed36 --- /dev/null +++ b/src/main/java/umc/spring/service/tempService/TempQueryServiceImpl.java @@ -0,0 +1,16 @@ +package umc.spring.service.tempService; + +import org.springframework.stereotype.Service; +import umc.spring.apiPayload.code.ErrorStatus; +import umc.spring.apiPayload.exception.handler.TempHandler; + +@Service +public class TempQueryServiceImpl implements TempQueryService{ + + @Override + public void checkFlag(Integer flag){ + if(flag==1){ + throw new TempHandler(ErrorStatus.TEMP_EXCEPTION); + } + } +} diff --git a/src/main/java/umc/spring/web/controller/TempRestController.java b/src/main/java/umc/spring/web/controller/TempRestController.java new file mode 100644 index 0000000..5c62be9 --- /dev/null +++ b/src/main/java/umc/spring/web/controller/TempRestController.java @@ -0,0 +1,30 @@ +package umc.spring.web.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import umc.spring.apiPayload.ApiResponse; +import umc.spring.converter.TempConverter; +import umc.spring.service.tempService.TempQueryService; +import umc.spring.web.dto.TempResponse; + +@RestController +@RequestMapping("/temp") +@RequiredArgsConstructor +public class TempRestController { + + private final TempQueryService tempQueryService; + + @GetMapping("/test") + public ApiResponse testAPI(){ + return ApiResponse.onSuccess(TempConverter.toTempTestDTO()); + } + + @GetMapping("/exception") + public ApiResponse exceptionAPI(@RequestParam("flag") Integer flag){ + tempQueryService.checkFlag(flag); + return ApiResponse.onSuccess(TempConverter.toTempExceptionDTO(flag)); + } +} diff --git a/src/main/java/umc/spring/web/dto/TempRequest.java b/src/main/java/umc/spring/web/dto/TempRequest.java new file mode 100644 index 0000000..6978f93 --- /dev/null +++ b/src/main/java/umc/spring/web/dto/TempRequest.java @@ -0,0 +1,4 @@ +package umc.spring.web.dto; + +public class TempRequest { +} diff --git a/src/main/java/umc/spring/web/dto/TempResponse.java b/src/main/java/umc/spring/web/dto/TempResponse.java new file mode 100644 index 0000000..563770d --- /dev/null +++ b/src/main/java/umc/spring/web/dto/TempResponse.java @@ -0,0 +1,24 @@ +package umc.spring.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class TempResponse { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class TempTestDTO{ + String testString; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class TempExceptionDTO{ + Integer flag; + } +} From 8a49dba5ec35e204b59760fe0b9ba9978ead7ba9 Mon Sep 17 00:00:00 2001 From: sammool Date: Mon, 19 May 2025 01:18:45 +0900 Subject: [PATCH 05/16] =?UTF-8?q?[mission/#8]=20=EA=B0=80=EA=B2=8C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../umc/spring/domain/QFoodCategory.java | 47 ------------ .../generated/umc/spring/domain/QMember.java | 74 ------------------- .../generated/umc/spring/domain/QMission.java | 67 ----------------- .../generated/umc/spring/domain/QRegion.java | 50 ------------- .../generated/umc/spring/domain/QReview.java | 68 ----------------- .../umc/spring/domain/QReviewImage.java | 61 --------------- .../umc/spring/domain/QSalesTime.java | 65 ---------------- .../generated/umc/spring/domain/QStore.java | 71 ------------------ .../generated/umc/spring/domain/QTerms.java | 54 -------------- .../umc/spring/domain/common/QBaseEntity.java | 39 ---------- .../spring/domain/mapping/QMemberFood.java | 62 ---------------- .../spring/domain/mapping/QMemberMission.java | 68 ----------------- .../spring/domain/mapping/QMemberTerms.java | 66 ----------------- .../spring/apiPayload/code/ErrorStatus.java | 3 + .../handler/FoodCategoryHandler.java | 8 ++ .../exception/handler/RegionHandler.java | 8 ++ .../umc/spring/converter/StoreConverter.java | 23 ++++++ .../MemberRepository.java | 4 +- .../spring/repository/RegionRepository.java | 7 ++ .../storeService/StoreCommandService.java | 8 ++ .../storeService/StoreCommandServiceImpl.java | 31 ++++++++ .../web/controller/StoreRestController.java | 32 ++++++++ .../umc/spring/web/dto/StoreRequestDto.java | 17 +++++ .../umc/spring/web/dto/StoreResponseDto.java | 17 +++++ 24 files changed, 155 insertions(+), 795 deletions(-) delete mode 100644 src/main/generated/umc/spring/domain/QFoodCategory.java delete mode 100644 src/main/generated/umc/spring/domain/QMember.java delete mode 100644 src/main/generated/umc/spring/domain/QMission.java delete mode 100644 src/main/generated/umc/spring/domain/QRegion.java delete mode 100644 src/main/generated/umc/spring/domain/QReview.java delete mode 100644 src/main/generated/umc/spring/domain/QReviewImage.java delete mode 100644 src/main/generated/umc/spring/domain/QSalesTime.java delete mode 100644 src/main/generated/umc/spring/domain/QStore.java delete mode 100644 src/main/generated/umc/spring/domain/QTerms.java delete mode 100644 src/main/generated/umc/spring/domain/common/QBaseEntity.java delete mode 100644 src/main/generated/umc/spring/domain/mapping/QMemberFood.java delete mode 100644 src/main/generated/umc/spring/domain/mapping/QMemberMission.java delete mode 100644 src/main/generated/umc/spring/domain/mapping/QMemberTerms.java create mode 100644 src/main/java/umc/spring/apiPayload/exception/handler/FoodCategoryHandler.java create mode 100644 src/main/java/umc/spring/apiPayload/exception/handler/RegionHandler.java create mode 100644 src/main/java/umc/spring/converter/StoreConverter.java rename src/main/java/umc/spring/repository/{memberRepository => }/MemberRepository.java (61%) create mode 100644 src/main/java/umc/spring/repository/RegionRepository.java create mode 100644 src/main/java/umc/spring/service/storeService/StoreCommandService.java create mode 100644 src/main/java/umc/spring/service/storeService/StoreCommandServiceImpl.java create mode 100644 src/main/java/umc/spring/web/controller/StoreRestController.java create mode 100644 src/main/java/umc/spring/web/dto/StoreRequestDto.java create mode 100644 src/main/java/umc/spring/web/dto/StoreResponseDto.java diff --git a/src/main/generated/umc/spring/domain/QFoodCategory.java b/src/main/generated/umc/spring/domain/QFoodCategory.java deleted file mode 100644 index 6fa7e8a..0000000 --- a/src/main/generated/umc/spring/domain/QFoodCategory.java +++ /dev/null @@ -1,47 +0,0 @@ -package umc.spring.domain; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; - - -/** - * QFoodCategory is a Querydsl query type for FoodCategory - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QFoodCategory extends EntityPathBase { - - private static final long serialVersionUID = -811463128L; - - public static final QFoodCategory foodCategory = new QFoodCategory("foodCategory"); - - public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final NumberPath id = createNumber("id", Long.class); - - public final StringPath name = createString("name"); - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public QFoodCategory(String variable) { - super(FoodCategory.class, forVariable(variable)); - } - - public QFoodCategory(Path path) { - super(path.getType(), path.getMetadata()); - } - - public QFoodCategory(PathMetadata metadata) { - super(FoodCategory.class, metadata); - } - -} - diff --git a/src/main/generated/umc/spring/domain/QMember.java b/src/main/generated/umc/spring/domain/QMember.java deleted file mode 100644 index c56e438..0000000 --- a/src/main/generated/umc/spring/domain/QMember.java +++ /dev/null @@ -1,74 +0,0 @@ -package umc.spring.domain; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QMember is a Querydsl query type for Member - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QMember extends EntityPathBase { - - private static final long serialVersionUID = 1366956614L; - - public static final QMember member = new QMember("member1"); - - public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); - - public final StringPath address = createString("address"); - - public final DatePath birth = createDate("birth", java.time.LocalDate.class); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final StringPath email = createString("email"); - - public final EnumPath gender = createEnum("gender", umc.spring.domain.enums.Gender.class); - - public final NumberPath id = createNumber("id", Long.class); - - public final DatePath inactiveDate = createDate("inactiveDate", java.time.LocalDate.class); - - public final ListPath memberFoodList = this.createList("memberFoodList", umc.spring.domain.mapping.MemberFood.class, umc.spring.domain.mapping.QMemberFood.class, PathInits.DIRECT2); - - public final ListPath memberMissionList = this.createList("memberMissionList", umc.spring.domain.mapping.MemberMission.class, umc.spring.domain.mapping.QMemberMission.class, PathInits.DIRECT2); - - public final ListPath memberTermsList = this.createList("memberTermsList", umc.spring.domain.mapping.MemberTerms.class, umc.spring.domain.mapping.QMemberTerms.class, PathInits.DIRECT2); - - public final StringPath name = createString("name"); - - public final StringPath phoneNumber = createString("phoneNumber"); - - public final NumberPath point = createNumber("point", Integer.class); - - public final ListPath reviewList = this.createList("reviewList", Review.class, QReview.class, PathInits.DIRECT2); - - public final EnumPath socialType = createEnum("socialType", umc.spring.domain.enums.SocialType.class); - - public final EnumPath status = createEnum("status", umc.spring.domain.enums.MemberStatus.class); - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public QMember(String variable) { - super(Member.class, forVariable(variable)); - } - - public QMember(Path path) { - super(path.getType(), path.getMetadata()); - } - - public QMember(PathMetadata metadata) { - super(Member.class, metadata); - } - -} - diff --git a/src/main/generated/umc/spring/domain/QMission.java b/src/main/generated/umc/spring/domain/QMission.java deleted file mode 100644 index 611a7d0..0000000 --- a/src/main/generated/umc/spring/domain/QMission.java +++ /dev/null @@ -1,67 +0,0 @@ -package umc.spring.domain; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QMission is a Querydsl query type for Mission - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QMission extends EntityPathBase { - - private static final long serialVersionUID = -453449888L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QMission mission = new QMission("mission"); - - public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final DatePath deadline = createDate("deadline", java.time.LocalDate.class); - - public final StringPath description = createString("description"); - - public final NumberPath id = createNumber("id", Long.class); - - public final ListPath memberMissionList = this.createList("memberMissionList", umc.spring.domain.mapping.MemberMission.class, umc.spring.domain.mapping.QMemberMission.class, PathInits.DIRECT2); - - public final NumberPath point = createNumber("point", Integer.class); - - public final QStore store; - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public QMission(String variable) { - this(Mission.class, forVariable(variable), INITS); - } - - public QMission(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QMission(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QMission(PathMetadata metadata, PathInits inits) { - this(Mission.class, metadata, inits); - } - - public QMission(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.store = inits.isInitialized("store") ? new QStore(forProperty("store"), inits.get("store")) : null; - } - -} - diff --git a/src/main/generated/umc/spring/domain/QRegion.java b/src/main/generated/umc/spring/domain/QRegion.java deleted file mode 100644 index 1ca2dee..0000000 --- a/src/main/generated/umc/spring/domain/QRegion.java +++ /dev/null @@ -1,50 +0,0 @@ -package umc.spring.domain; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QRegion is a Querydsl query type for Region - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QRegion extends EntityPathBase { - - private static final long serialVersionUID = 1509930656L; - - public static final QRegion region = new QRegion("region"); - - public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final NumberPath id = createNumber("id", Long.class); - - public final StringPath name = createString("name"); - - public final ListPath storeList = this.createList("storeList", Store.class, QStore.class, PathInits.DIRECT2); - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public QRegion(String variable) { - super(Region.class, forVariable(variable)); - } - - public QRegion(Path path) { - super(path.getType(), path.getMetadata()); - } - - public QRegion(PathMetadata metadata) { - super(Region.class, metadata); - } - -} - diff --git a/src/main/generated/umc/spring/domain/QReview.java b/src/main/generated/umc/spring/domain/QReview.java deleted file mode 100644 index c749fb2..0000000 --- a/src/main/generated/umc/spring/domain/QReview.java +++ /dev/null @@ -1,68 +0,0 @@ -package umc.spring.domain; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QReview is a Querydsl query type for Review - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QReview extends EntityPathBase { - - private static final long serialVersionUID = 1510377220L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QReview review = new QReview("review"); - - public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); - - public final StringPath content = createString("content"); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final NumberPath id = createNumber("id", Long.class); - - public final QMember member; - - public final ListPath reviewImageList = this.createList("reviewImageList", ReviewImage.class, QReviewImage.class, PathInits.DIRECT2); - - public final NumberPath score = createNumber("score", Float.class); - - public final QStore store; - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public QReview(String variable) { - this(Review.class, forVariable(variable), INITS); - } - - public QReview(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QReview(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QReview(PathMetadata metadata, PathInits inits) { - this(Review.class, metadata, inits); - } - - public QReview(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.member = inits.isInitialized("member") ? new QMember(forProperty("member")) : null; - this.store = inits.isInitialized("store") ? new QStore(forProperty("store"), inits.get("store")) : null; - } - -} - diff --git a/src/main/generated/umc/spring/domain/QReviewImage.java b/src/main/generated/umc/spring/domain/QReviewImage.java deleted file mode 100644 index ab300af..0000000 --- a/src/main/generated/umc/spring/domain/QReviewImage.java +++ /dev/null @@ -1,61 +0,0 @@ -package umc.spring.domain; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QReviewImage is a Querydsl query type for ReviewImage - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QReviewImage extends EntityPathBase { - - private static final long serialVersionUID = 1661007031L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QReviewImage reviewImage = new QReviewImage("reviewImage"); - - public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final NumberPath id = createNumber("id", Long.class); - - public final StringPath imageUrl = createString("imageUrl"); - - public final QReview review; - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public QReviewImage(String variable) { - this(ReviewImage.class, forVariable(variable), INITS); - } - - public QReviewImage(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QReviewImage(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QReviewImage(PathMetadata metadata, PathInits inits) { - this(ReviewImage.class, metadata, inits); - } - - public QReviewImage(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.review = inits.isInitialized("review") ? new QReview(forProperty("review"), inits.get("review")) : null; - } - -} - diff --git a/src/main/generated/umc/spring/domain/QSalesTime.java b/src/main/generated/umc/spring/domain/QSalesTime.java deleted file mode 100644 index 3fdc77e..0000000 --- a/src/main/generated/umc/spring/domain/QSalesTime.java +++ /dev/null @@ -1,65 +0,0 @@ -package umc.spring.domain; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QSalesTime is a Querydsl query type for SalesTime - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QSalesTime extends EntityPathBase { - - private static final long serialVersionUID = 993979085L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QSalesTime salesTime = new QSalesTime("salesTime"); - - public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); - - public final DateTimePath closeTime = createDateTime("closeTime", java.time.LocalDateTime.class); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final NumberPath dayOfWeek = createNumber("dayOfWeek", Integer.class); - - public final NumberPath id = createNumber("id", Long.class); - - public final DateTimePath openTime = createDateTime("openTime", java.time.LocalDateTime.class); - - public final QStore store; - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public QSalesTime(String variable) { - this(SalesTime.class, forVariable(variable), INITS); - } - - public QSalesTime(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QSalesTime(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QSalesTime(PathMetadata metadata, PathInits inits) { - this(SalesTime.class, metadata, inits); - } - - public QSalesTime(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.store = inits.isInitialized("store") ? new QStore(forProperty("store"), inits.get("store")) : null; - } - -} - diff --git a/src/main/generated/umc/spring/domain/QStore.java b/src/main/generated/umc/spring/domain/QStore.java deleted file mode 100644 index 632bdc0..0000000 --- a/src/main/generated/umc/spring/domain/QStore.java +++ /dev/null @@ -1,71 +0,0 @@ -package umc.spring.domain; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QStore is a Querydsl query type for Store - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QStore extends EntityPathBase { - - private static final long serialVersionUID = 1158464437L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QStore store = new QStore("store"); - - public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); - - public final StringPath address = createString("address"); - - public final StringPath category = createString("category"); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final NumberPath id = createNumber("id", Long.class); - - public final ListPath missionList = this.createList("missionList", Mission.class, QMission.class, PathInits.DIRECT2); - - public final StringPath name = createString("name"); - - public final QRegion region; - - public final ListPath salesTimeList = this.createList("salesTimeList", SalesTime.class, QSalesTime.class, PathInits.DIRECT2); - - public final NumberPath score = createNumber("score", Float.class); - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public QStore(String variable) { - this(Store.class, forVariable(variable), INITS); - } - - public QStore(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QStore(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QStore(PathMetadata metadata, PathInits inits) { - this(Store.class, metadata, inits); - } - - public QStore(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.region = inits.isInitialized("region") ? new QRegion(forProperty("region")) : null; - } - -} - diff --git a/src/main/generated/umc/spring/domain/QTerms.java b/src/main/generated/umc/spring/domain/QTerms.java deleted file mode 100644 index fd34f3a..0000000 --- a/src/main/generated/umc/spring/domain/QTerms.java +++ /dev/null @@ -1,54 +0,0 @@ -package umc.spring.domain; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QTerms is a Querydsl query type for Terms - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QTerms extends EntityPathBase { - - private static final long serialVersionUID = 1158943835L; - - public static final QTerms terms = new QTerms("terms"); - - public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final StringPath description = createString("description"); - - public final NumberPath id = createNumber("id", Long.class); - - public final ListPath memberTermsList = this.createList("memberTermsList", umc.spring.domain.mapping.MemberTerms.class, umc.spring.domain.mapping.QMemberTerms.class, PathInits.DIRECT2); - - public final StringPath name = createString("name"); - - public final EnumPath type = createEnum("type", umc.spring.domain.enums.TermType.class); - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public QTerms(String variable) { - super(Terms.class, forVariable(variable)); - } - - public QTerms(Path path) { - super(path.getType(), path.getMetadata()); - } - - public QTerms(PathMetadata metadata) { - super(Terms.class, metadata); - } - -} - diff --git a/src/main/generated/umc/spring/domain/common/QBaseEntity.java b/src/main/generated/umc/spring/domain/common/QBaseEntity.java deleted file mode 100644 index 0bafae2..0000000 --- a/src/main/generated/umc/spring/domain/common/QBaseEntity.java +++ /dev/null @@ -1,39 +0,0 @@ -package umc.spring.domain.common; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; - - -/** - * QBaseEntity is a Querydsl query type for BaseEntity - */ -@Generated("com.querydsl.codegen.DefaultSupertypeSerializer") -public class QBaseEntity extends EntityPathBase { - - private static final long serialVersionUID = -1748720885L; - - public static final QBaseEntity baseEntity = new QBaseEntity("baseEntity"); - - public final DateTimePath createdAt = createDateTime("createdAt", java.time.LocalDateTime.class); - - public final DateTimePath updatedAt = createDateTime("updatedAt", java.time.LocalDateTime.class); - - public QBaseEntity(String variable) { - super(BaseEntity.class, forVariable(variable)); - } - - public QBaseEntity(Path path) { - super(path.getType(), path.getMetadata()); - } - - public QBaseEntity(PathMetadata metadata) { - super(BaseEntity.class, metadata); - } - -} - diff --git a/src/main/generated/umc/spring/domain/mapping/QMemberFood.java b/src/main/generated/umc/spring/domain/mapping/QMemberFood.java deleted file mode 100644 index 5b5c261..0000000 --- a/src/main/generated/umc/spring/domain/mapping/QMemberFood.java +++ /dev/null @@ -1,62 +0,0 @@ -package umc.spring.domain.mapping; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QMemberFood is a Querydsl query type for MemberFood - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QMemberFood extends EntityPathBase { - - private static final long serialVersionUID = 488166628L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QMemberFood memberFood = new QMemberFood("memberFood"); - - public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final umc.spring.domain.QFoodCategory foodCategory; - - public final NumberPath id = createNumber("id", Long.class); - - public final umc.spring.domain.QMember member; - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public QMemberFood(String variable) { - this(MemberFood.class, forVariable(variable), INITS); - } - - public QMemberFood(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QMemberFood(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QMemberFood(PathMetadata metadata, PathInits inits) { - this(MemberFood.class, metadata, inits); - } - - public QMemberFood(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.foodCategory = inits.isInitialized("foodCategory") ? new umc.spring.domain.QFoodCategory(forProperty("foodCategory")) : null; - this.member = inits.isInitialized("member") ? new umc.spring.domain.QMember(forProperty("member")) : null; - } - -} - diff --git a/src/main/generated/umc/spring/domain/mapping/QMemberMission.java b/src/main/generated/umc/spring/domain/mapping/QMemberMission.java deleted file mode 100644 index 5055c06..0000000 --- a/src/main/generated/umc/spring/domain/mapping/QMemberMission.java +++ /dev/null @@ -1,68 +0,0 @@ -package umc.spring.domain.mapping; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QMemberMission is a Querydsl query type for MemberMission - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QMemberMission extends EntityPathBase { - - private static final long serialVersionUID = 1962779462L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QMemberMission memberMission = new QMemberMission("memberMission"); - - public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); - - public final StringPath certificationNumber = createString("certificationNumber"); - - public final DateTimePath completedAt = createDateTime("completedAt", java.time.LocalDateTime.class); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final NumberPath id = createNumber("id", Long.class); - - public final umc.spring.domain.QMember member; - - public final umc.spring.domain.QMission mission; - - public final EnumPath status = createEnum("status", umc.spring.domain.enums.MissionStatus.class); - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public QMemberMission(String variable) { - this(MemberMission.class, forVariable(variable), INITS); - } - - public QMemberMission(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QMemberMission(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QMemberMission(PathMetadata metadata, PathInits inits) { - this(MemberMission.class, metadata, inits); - } - - public QMemberMission(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.member = inits.isInitialized("member") ? new umc.spring.domain.QMember(forProperty("member")) : null; - this.mission = inits.isInitialized("mission") ? new umc.spring.domain.QMission(forProperty("mission"), inits.get("mission")) : null; - } - -} - diff --git a/src/main/generated/umc/spring/domain/mapping/QMemberTerms.java b/src/main/generated/umc/spring/domain/mapping/QMemberTerms.java deleted file mode 100644 index f39ca03..0000000 --- a/src/main/generated/umc/spring/domain/mapping/QMemberTerms.java +++ /dev/null @@ -1,66 +0,0 @@ -package umc.spring.domain.mapping; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QMemberTerms is a Querydsl query type for MemberTerms - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QMemberTerms extends EntityPathBase { - - private static final long serialVersionUID = -2034069055L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QMemberTerms memberTerms = new QMemberTerms("memberTerms"); - - public final umc.spring.domain.common.QBaseEntity _super = new umc.spring.domain.common.QBaseEntity(this); - - public final DateTimePath agreedAt = createDateTime("agreedAt", java.time.LocalDateTime.class); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final NumberPath id = createNumber("id", Long.class); - - public final BooleanPath isAgreed = createBoolean("isAgreed"); - - public final umc.spring.domain.QMember member; - - public final umc.spring.domain.QTerms terms; - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public QMemberTerms(String variable) { - this(MemberTerms.class, forVariable(variable), INITS); - } - - public QMemberTerms(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QMemberTerms(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QMemberTerms(PathMetadata metadata, PathInits inits) { - this(MemberTerms.class, metadata, inits); - } - - public QMemberTerms(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.member = inits.isInitialized("member") ? new umc.spring.domain.QMember(forProperty("member")) : null; - this.terms = inits.isInitialized("terms") ? new umc.spring.domain.QTerms(forProperty("terms")) : null; - } - -} - diff --git a/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java b/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java index 38d75bc..167ed10 100644 --- a/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java +++ b/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java @@ -18,7 +18,10 @@ public enum ErrorStatus implements BaseErrorCode { // 멤버 관려 에러 MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER4001", "사용자가 없습니다."), NICKNAME_NOT_EXIST(HttpStatus.BAD_REQUEST, "MEMBER4002", "닉네임은 필수 입니다."), + FOOD_CATEGORY_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER4003", "선호 음식을 선택해주세요"), + //Region 관련 에러 + REGION_NOT_EXIST(HttpStatus.BAD_REQUEST, "REGION4001", "지역이 존재하지 않습니다."), TEMP_EXCEPTION(HttpStatus.BAD_REQUEST, "TEMP4001", "test exception"); private final HttpStatus httpStatus; diff --git a/src/main/java/umc/spring/apiPayload/exception/handler/FoodCategoryHandler.java b/src/main/java/umc/spring/apiPayload/exception/handler/FoodCategoryHandler.java new file mode 100644 index 0000000..9d92569 --- /dev/null +++ b/src/main/java/umc/spring/apiPayload/exception/handler/FoodCategoryHandler.java @@ -0,0 +1,8 @@ +package umc.spring.apiPayload.exception.handler; + +import umc.spring.apiPayload.BaseErrorCode; +import umc.spring.apiPayload.exception.GeneralException; + +public class FoodCategoryHandler extends GeneralException { + public FoodCategoryHandler(BaseErrorCode code){super(code);} +} diff --git a/src/main/java/umc/spring/apiPayload/exception/handler/RegionHandler.java b/src/main/java/umc/spring/apiPayload/exception/handler/RegionHandler.java new file mode 100644 index 0000000..f40d62b --- /dev/null +++ b/src/main/java/umc/spring/apiPayload/exception/handler/RegionHandler.java @@ -0,0 +1,8 @@ +package umc.spring.apiPayload.exception.handler; + +import umc.spring.apiPayload.BaseErrorCode; +import umc.spring.apiPayload.exception.GeneralException; + +public class RegionHandler extends GeneralException { + public RegionHandler(BaseErrorCode code){super(code);} +} diff --git a/src/main/java/umc/spring/converter/StoreConverter.java b/src/main/java/umc/spring/converter/StoreConverter.java new file mode 100644 index 0000000..4b21918 --- /dev/null +++ b/src/main/java/umc/spring/converter/StoreConverter.java @@ -0,0 +1,23 @@ +package umc.spring.converter; + +import umc.spring.domain.Store; +import umc.spring.web.dto.StoreRequestDto; +import umc.spring.web.dto.StoreResponseDto; + +import java.time.LocalDateTime; + +public class StoreConverter { + public static StoreResponseDto.AddStoreToRegionResultDto toAddStoreToRegionResultDto(Store store){ + return StoreResponseDto.AddStoreToRegionResultDto.builder() + .storeId(store.getId()) + .createdAt(LocalDateTime.now()) + .build(); + } + + public static Store toStore(StoreRequestDto.AddStoreToRegionDto request){ + return Store.builder() + .name(request.getName()) + .address(request.getAddress()) + .build(); + } +} diff --git a/src/main/java/umc/spring/repository/memberRepository/MemberRepository.java b/src/main/java/umc/spring/repository/MemberRepository.java similarity index 61% rename from src/main/java/umc/spring/repository/memberRepository/MemberRepository.java rename to src/main/java/umc/spring/repository/MemberRepository.java index 3139bb4..21a4961 100644 --- a/src/main/java/umc/spring/repository/memberRepository/MemberRepository.java +++ b/src/main/java/umc/spring/repository/MemberRepository.java @@ -1,4 +1,4 @@ -package umc.spring.repository; +package umc.spring.repository.memberRepository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -10,6 +10,4 @@ public interface MemberRepository extends JpaRepository { - @Query("SELECT m FROM Member m WHERE m.name = :name AND m.status = :status") - List findByNameAndStatus(@Param("name") String name, @Param("status") MemberStatus status); } diff --git a/src/main/java/umc/spring/repository/RegionRepository.java b/src/main/java/umc/spring/repository/RegionRepository.java new file mode 100644 index 0000000..d9b66a5 --- /dev/null +++ b/src/main/java/umc/spring/repository/RegionRepository.java @@ -0,0 +1,7 @@ +package umc.spring.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import umc.spring.domain.Region; + +public interface RegionRepository extends JpaRepository { +} diff --git a/src/main/java/umc/spring/service/storeService/StoreCommandService.java b/src/main/java/umc/spring/service/storeService/StoreCommandService.java new file mode 100644 index 0000000..ca544cd --- /dev/null +++ b/src/main/java/umc/spring/service/storeService/StoreCommandService.java @@ -0,0 +1,8 @@ +package umc.spring.service.storeService; + +import umc.spring.domain.Store; +import umc.spring.web.dto.StoreRequestDto; + +public interface StoreCommandService { + public Store addStore(StoreRequestDto.AddStoreToRegionDto request); +} diff --git a/src/main/java/umc/spring/service/storeService/StoreCommandServiceImpl.java b/src/main/java/umc/spring/service/storeService/StoreCommandServiceImpl.java new file mode 100644 index 0000000..604f586 --- /dev/null +++ b/src/main/java/umc/spring/service/storeService/StoreCommandServiceImpl.java @@ -0,0 +1,31 @@ +package umc.spring.service.storeService; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import umc.spring.apiPayload.code.ErrorStatus; +import umc.spring.apiPayload.exception.handler.RegionHandler; +import umc.spring.converter.StoreConverter; +import umc.spring.domain.Region; +import umc.spring.domain.Store; +import umc.spring.repository.RegionRepository; +import umc.spring.repository.storeRepository.StoreRepository; +import umc.spring.web.dto.StoreRequestDto; + +@Service +@RequiredArgsConstructor +public class StoreCommandServiceImpl implements StoreCommandService { + private final StoreRepository storeRepository; + private final RegionRepository regionRepository; + + @Override + @Transactional + public Store addStore(StoreRequestDto.AddStoreToRegionDto request){ + Store store = StoreConverter.toStore(request); + Region region = regionRepository.findById(request.getRegionId()) + .orElseThrow(()->new RegionHandler(ErrorStatus.REGION_NOT_EXIST)); + region.addStore(store); + return storeRepository.save(store); + } + +} diff --git a/src/main/java/umc/spring/web/controller/StoreRestController.java b/src/main/java/umc/spring/web/controller/StoreRestController.java new file mode 100644 index 0000000..0a44f46 --- /dev/null +++ b/src/main/java/umc/spring/web/controller/StoreRestController.java @@ -0,0 +1,32 @@ +package umc.spring.web.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import umc.spring.apiPayload.ApiResponse; +import umc.spring.converter.MemberConverter; +import umc.spring.converter.StoreConverter; +import umc.spring.domain.Member; +import umc.spring.domain.Store; +import umc.spring.service.storeService.StoreCommandService; +import umc.spring.web.dto.MemberRequestDto; +import umc.spring.web.dto.MemberResponseDto; +import umc.spring.web.dto.StoreRequestDto; +import umc.spring.web.dto.StoreResponseDto; + +@RestController +@RequestMapping("/stores") +@RequiredArgsConstructor +public class StoreRestController { + + private final StoreCommandService storeCommandService; + + @PostMapping("/add") + public ApiResponse join(@RequestBody @Valid StoreRequestDto.AddStoreToRegionDto request){ + Store store = storeCommandService.addStore(request); + return ApiResponse.onSuccess(StoreConverter.toAddStoreToRegionResultDto(store)); + } +} diff --git a/src/main/java/umc/spring/web/dto/StoreRequestDto.java b/src/main/java/umc/spring/web/dto/StoreRequestDto.java new file mode 100644 index 0000000..52effe5 --- /dev/null +++ b/src/main/java/umc/spring/web/dto/StoreRequestDto.java @@ -0,0 +1,17 @@ +package umc.spring.web.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +public class StoreRequestDto { + @Getter + public static class AddStoreToRegionDto{ + @NotNull + String name; + @NotBlank + String address; + @NotNull + Long regionId; + } +} diff --git a/src/main/java/umc/spring/web/dto/StoreResponseDto.java b/src/main/java/umc/spring/web/dto/StoreResponseDto.java new file mode 100644 index 0000000..43503ba --- /dev/null +++ b/src/main/java/umc/spring/web/dto/StoreResponseDto.java @@ -0,0 +1,17 @@ +package umc.spring.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +public class StoreResponseDto { + @Getter + @Builder + @AllArgsConstructor + public static class AddStoreToRegionResultDto{ + Long storeId; + LocalDateTime createdAt; + } +} From fb299b5d500d59d5e3236540e4d035344c4a4bf8 Mon Sep 17 00:00:00 2001 From: sammool Date: Mon, 19 May 2025 02:23:43 +0900 Subject: [PATCH 06/16] =?UTF-8?q?[missioin/#8]=20=EA=B0=80=EA=B2=8C?= =?UTF-8?q?=EC=97=90=20=EB=A6=AC=EB=B7=B0=20=EC=B6=94=EA=B0=80=20API=20(2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../umc/spring/converter/ReviewConverter.java | 28 ++++++++++ src/main/java/umc/spring/domain/Review.java | 20 +++++++ .../spring/repository/ReviewRepository.java | 8 +++ .../reviewService/ReviewCommandService.java | 8 +++ .../ReviewCommandServiceImpl.java | 55 +++++++++++++++++++ .../web/controller/ReviewRestController.java | 26 +++++++++ .../umc/spring/web/dto/ReviewRequestDto.java | 23 ++++++++ .../umc/spring/web/dto/ReviewResponseDto.java | 19 +++++++ 8 files changed, 187 insertions(+) create mode 100644 src/main/java/umc/spring/converter/ReviewConverter.java create mode 100644 src/main/java/umc/spring/repository/ReviewRepository.java create mode 100644 src/main/java/umc/spring/service/reviewService/ReviewCommandService.java create mode 100644 src/main/java/umc/spring/service/reviewService/ReviewCommandServiceImpl.java create mode 100644 src/main/java/umc/spring/web/controller/ReviewRestController.java create mode 100644 src/main/java/umc/spring/web/dto/ReviewRequestDto.java create mode 100644 src/main/java/umc/spring/web/dto/ReviewResponseDto.java diff --git a/src/main/java/umc/spring/converter/ReviewConverter.java b/src/main/java/umc/spring/converter/ReviewConverter.java new file mode 100644 index 0000000..81d50a7 --- /dev/null +++ b/src/main/java/umc/spring/converter/ReviewConverter.java @@ -0,0 +1,28 @@ +package umc.spring.converter; + +import umc.spring.domain.Member; +import umc.spring.domain.Review; +import umc.spring.domain.Store; +import umc.spring.web.dto.ReviewRequestDto; +import umc.spring.web.dto.ReviewResponseDto; + +import java.time.LocalDateTime; +import java.util.ArrayList; + +public class ReviewConverter { + public static ReviewResponseDto.ReviewAddResultDto toReviewAddResultDto(Review review){ + return ReviewResponseDto.ReviewAddResultDto.builder() + .reviewId(review.getId()) + .createdAt(LocalDateTime.now()) + .build(); + } + + public static Review toReview(ReviewRequestDto.ReviewAddDto request){ + return Review.builder() + .content(request.getContent()) + .score(request.getScore()) + .reviewImageList(new ArrayList<>()) + .build(); + + } +} diff --git a/src/main/java/umc/spring/domain/Review.java b/src/main/java/umc/spring/domain/Review.java index 2ffb97a..9eb0c7d 100644 --- a/src/main/java/umc/spring/domain/Review.java +++ b/src/main/java/umc/spring/domain/Review.java @@ -8,6 +8,7 @@ @Entity @Getter +@Setter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @@ -33,4 +34,23 @@ public class Review extends BaseEntity { @JoinColumn(name = "member_id") private Member member; + public void setMember(Member member) { + this.member = member; + if (!member.getReviewList().contains(this)) { + member.getReviewList().add(this); + } + } + public void setStore(Store store) { + this.store = store; + if (!store.getReviewList().contains(this)) { + store.getReviewList().add(this); + } + } + public void addReviewImage(ReviewImage image) { + this.reviewImageList.add(image); + image.setReview(this); // 연관관계 주인 쪽 설정 + } + + + } diff --git a/src/main/java/umc/spring/repository/ReviewRepository.java b/src/main/java/umc/spring/repository/ReviewRepository.java new file mode 100644 index 0000000..62fd5c3 --- /dev/null +++ b/src/main/java/umc/spring/repository/ReviewRepository.java @@ -0,0 +1,8 @@ +package umc.spring.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import umc.spring.domain.Review; + +public interface ReviewRepository extends JpaRepository { + +} diff --git a/src/main/java/umc/spring/service/reviewService/ReviewCommandService.java b/src/main/java/umc/spring/service/reviewService/ReviewCommandService.java new file mode 100644 index 0000000..9838efe --- /dev/null +++ b/src/main/java/umc/spring/service/reviewService/ReviewCommandService.java @@ -0,0 +1,8 @@ +package umc.spring.service.reviewService; + +import umc.spring.domain.Review; +import umc.spring.web.dto.ReviewRequestDto; + +public interface ReviewCommandService { + Review addReview(ReviewRequestDto.ReviewAddDto request); +} diff --git a/src/main/java/umc/spring/service/reviewService/ReviewCommandServiceImpl.java b/src/main/java/umc/spring/service/reviewService/ReviewCommandServiceImpl.java new file mode 100644 index 0000000..958bb30 --- /dev/null +++ b/src/main/java/umc/spring/service/reviewService/ReviewCommandServiceImpl.java @@ -0,0 +1,55 @@ +package umc.spring.service.reviewService; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import umc.spring.apiPayload.code.ErrorStatus; +import umc.spring.apiPayload.exception.handler.MemberHandler; +import umc.spring.apiPayload.exception.handler.StoreHandler; +import umc.spring.apiPayload.exception.handler.TempHandler; +import umc.spring.converter.ReviewConverter; +import umc.spring.domain.Member; +import umc.spring.domain.Review; +import umc.spring.domain.ReviewImage; +import umc.spring.domain.Store; +import umc.spring.repository.MemberRepository; +import umc.spring.repository.ReviewRepository; +import umc.spring.repository.storeRepository.StoreRepository; +import umc.spring.web.dto.ReviewRequestDto; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ReviewCommandServiceImpl implements ReviewCommandService{ + + private final StoreRepository storeRepository; + private final MemberRepository memberRepository; + private final ReviewRepository reviewRepository; + + @Override + @Transactional + public Review addReview(ReviewRequestDto.ReviewAddDto request){ + Review review = ReviewConverter.toReview(request); + List urls = request.getImageUrl(); + if (urls != null && !urls.isEmpty()) { + for (String url : urls) { + review.addReviewImage( + ReviewImage.builder() + .imageUrl(url) + .build() + ); + } + } + + Store store = storeRepository.findById(request.getStoreId()) + .orElseThrow(()->new StoreHandler(ErrorStatus.STORE_NOT_FOUND)); + Member member = memberRepository.findById(1L) //하드코딩 + .orElseThrow(()-> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); + + review.setStore(store); + review.setMember(member); + return reviewRepository.save(review); + + } +} diff --git a/src/main/java/umc/spring/web/controller/ReviewRestController.java b/src/main/java/umc/spring/web/controller/ReviewRestController.java new file mode 100644 index 0000000..ce74a3d --- /dev/null +++ b/src/main/java/umc/spring/web/controller/ReviewRestController.java @@ -0,0 +1,26 @@ +package umc.spring.web.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import umc.spring.apiPayload.ApiResponse; +import umc.spring.converter.ReviewConverter; +import umc.spring.domain.Review; +import umc.spring.service.reviewService.ReviewCommandService; +import umc.spring.web.dto.ReviewRequestDto; +import umc.spring.web.dto.ReviewResponseDto; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/reviews") +public class ReviewRestController { + private final ReviewCommandService reviewCommandService; + @PostMapping("/") + public ApiResponse ReviewAdd(@RequestBody @Valid ReviewRequestDto.ReviewAddDto request){ + Review review = reviewCommandService.addReview(request); + return ApiResponse.onSuccess(ReviewConverter.toReviewAddResultDto(review)); + } +} diff --git a/src/main/java/umc/spring/web/dto/ReviewRequestDto.java b/src/main/java/umc/spring/web/dto/ReviewRequestDto.java new file mode 100644 index 0000000..5f42f0c --- /dev/null +++ b/src/main/java/umc/spring/web/dto/ReviewRequestDto.java @@ -0,0 +1,23 @@ +package umc.spring.web.dto; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +import java.util.List; + +public class ReviewRequestDto { + @Getter + public static class ReviewAddDto{ + @NotBlank + String content; + @Max(5) @Min(0) + Float score; + @NotNull + Long storeId; + + List imageUrl; + } +} diff --git a/src/main/java/umc/spring/web/dto/ReviewResponseDto.java b/src/main/java/umc/spring/web/dto/ReviewResponseDto.java new file mode 100644 index 0000000..f70adf6 --- /dev/null +++ b/src/main/java/umc/spring/web/dto/ReviewResponseDto.java @@ -0,0 +1,19 @@ +package umc.spring.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.time.LocalDateTime; + +public class ReviewResponseDto { + + @Getter + @Builder + @AllArgsConstructor + public static class ReviewAddResultDto{ + Long reviewId; + LocalDateTime createdAt; + } +} From a7c3581d4106699767e61f0ab72b5e100a7953f9 Mon Sep 17 00:00:00 2001 From: sammool Date: Mon, 19 May 2025 15:41:29 +0900 Subject: [PATCH 07/16] =?UTF-8?q?[mission/#8]=20=EA=B0=80=EA=B2=8C?= =?UTF-8?q?=EC=97=90=20=EB=AF=B8=EC=85=98=20=EC=B6=94=EA=B0=80=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring/converter/MissionConverter.java | 29 +++++++++++++++ src/main/java/umc/spring/domain/Mission.java | 7 ++++ .../spring/repository/MissionRepository.java | 7 ++++ .../missionService/MissionCommandService.java | 9 +++++ .../MissionCommandServiceImpl.java | 35 +++++++++++++++++++ .../web/controller/MissionRestController.java | 29 +++++++++++++++ .../umc/spring/web/dto/MissionRequestDto.java | 23 ++++++++++++ .../spring/web/dto/MissionResponseDto.java | 19 ++++++++++ 8 files changed, 158 insertions(+) create mode 100644 src/main/java/umc/spring/converter/MissionConverter.java create mode 100644 src/main/java/umc/spring/repository/MissionRepository.java create mode 100644 src/main/java/umc/spring/service/missionService/MissionCommandService.java create mode 100644 src/main/java/umc/spring/service/missionService/MissionCommandServiceImpl.java create mode 100644 src/main/java/umc/spring/web/controller/MissionRestController.java create mode 100644 src/main/java/umc/spring/web/dto/MissionRequestDto.java create mode 100644 src/main/java/umc/spring/web/dto/MissionResponseDto.java diff --git a/src/main/java/umc/spring/converter/MissionConverter.java b/src/main/java/umc/spring/converter/MissionConverter.java new file mode 100644 index 0000000..4adedd7 --- /dev/null +++ b/src/main/java/umc/spring/converter/MissionConverter.java @@ -0,0 +1,29 @@ +package umc.spring.converter; + +import umc.spring.apiPayload.ApiResponse; +import umc.spring.domain.Mission; +import umc.spring.web.dto.MissionRequestDto; +import umc.spring.web.dto.MissionResponseDto; + +import java.time.LocalDateTime; +import java.util.ArrayList; + +public class MissionConverter { + + public static MissionResponseDto.AddMissionToStoreResultDto toAddMissionToStoreResultDto(Mission mission){ + return MissionResponseDto.AddMissionToStoreResultDto.builder() + .missionId(mission.getId()) + .storeId(mission.getStore().getId()) + .createdAt(LocalDateTime.now()) + .build(); + } + + public static Mission toMission(MissionRequestDto.AddMissionToStoreDto request){ + return Mission.builder() + .point(request.getPoint()) + .description(request.getDescription()) + .memberMissionList(new ArrayList<>()) + .deadline(request.getDeadline()) + .build(); + } +} diff --git a/src/main/java/umc/spring/domain/Mission.java b/src/main/java/umc/spring/domain/Mission.java index c53972a..c42afe5 100644 --- a/src/main/java/umc/spring/domain/Mission.java +++ b/src/main/java/umc/spring/domain/Mission.java @@ -31,4 +31,11 @@ public class Mission extends BaseEntity { @OneToMany(mappedBy = "mission", cascade = CascadeType.ALL) private List memberMissionList = new ArrayList<>(); + + public void setStore(Store store){ + this.store = store; + if(!store.getMissionList().contains(this)){ + store.getMissionList().add(this); + } + } } diff --git a/src/main/java/umc/spring/repository/MissionRepository.java b/src/main/java/umc/spring/repository/MissionRepository.java new file mode 100644 index 0000000..51b0c2e --- /dev/null +++ b/src/main/java/umc/spring/repository/MissionRepository.java @@ -0,0 +1,7 @@ +package umc.spring.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import umc.spring.domain.Mission; + +public interface MissionRepository extends JpaRepository { +} diff --git a/src/main/java/umc/spring/service/missionService/MissionCommandService.java b/src/main/java/umc/spring/service/missionService/MissionCommandService.java new file mode 100644 index 0000000..ab42300 --- /dev/null +++ b/src/main/java/umc/spring/service/missionService/MissionCommandService.java @@ -0,0 +1,9 @@ +package umc.spring.service.missionService; + +import umc.spring.domain.Mission; +import umc.spring.domain.Review; +import umc.spring.web.dto.MissionRequestDto; + +public interface MissionCommandService { + Mission addMission(MissionRequestDto.AddMissionToStoreDto request); +} diff --git a/src/main/java/umc/spring/service/missionService/MissionCommandServiceImpl.java b/src/main/java/umc/spring/service/missionService/MissionCommandServiceImpl.java new file mode 100644 index 0000000..8ff4479 --- /dev/null +++ b/src/main/java/umc/spring/service/missionService/MissionCommandServiceImpl.java @@ -0,0 +1,35 @@ +package umc.spring.service.missionService; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import umc.spring.apiPayload.code.ErrorStatus; +import umc.spring.apiPayload.exception.handler.StoreHandler; +import umc.spring.converter.MissionConverter; +import umc.spring.domain.Mission; +import umc.spring.domain.Review; +import umc.spring.domain.Store; +import umc.spring.repository.MissionRepository; +import umc.spring.repository.storeRepository.StoreRepository; +import umc.spring.web.dto.MissionRequestDto; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class MissionCommandServiceImpl implements MissionCommandService{ + + private final MissionRepository missionRepository; + private final StoreRepository storeRepository; + + @Override + @Transactional + public Mission addMission(MissionRequestDto.AddMissionToStoreDto request){ + Mission mission = MissionConverter.toMission(request); + Store store = storeRepository.findById(request.getStoreId()) + .orElseThrow(()->new StoreHandler(ErrorStatus.STORE_NOT_FOUND)); + + mission.setStore(store); + return missionRepository.save(mission); + } +} diff --git a/src/main/java/umc/spring/web/controller/MissionRestController.java b/src/main/java/umc/spring/web/controller/MissionRestController.java new file mode 100644 index 0000000..468941e --- /dev/null +++ b/src/main/java/umc/spring/web/controller/MissionRestController.java @@ -0,0 +1,29 @@ +package umc.spring.web.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import umc.spring.apiPayload.ApiResponse; +import umc.spring.converter.MissionConverter; +import umc.spring.domain.Mission; +import umc.spring.service.missionService.MissionCommandService; +import umc.spring.web.dto.MissionRequestDto; +import umc.spring.web.dto.MissionResponseDto; +import umc.spring.web.dto.ReviewRequestDto; + +@RestController +@RequestMapping("/missions") +@RequiredArgsConstructor +public class MissionRestController { + + private final MissionCommandService missionCommandService; + + @PostMapping("/add") + public ApiResponse AddMission(@RequestBody @Valid MissionRequestDto.AddMissionToStoreDto request){ + Mission mission = missionCommandService.addMission(request); + return ApiResponse.onSuccess(MissionConverter.toAddMissionToStoreResultDto(mission)); + } +} diff --git a/src/main/java/umc/spring/web/dto/MissionRequestDto.java b/src/main/java/umc/spring/web/dto/MissionRequestDto.java new file mode 100644 index 0000000..d8444b0 --- /dev/null +++ b/src/main/java/umc/spring/web/dto/MissionRequestDto.java @@ -0,0 +1,23 @@ +package umc.spring.web.dto; + +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +import java.time.LocalDate; + +public class MissionRequestDto { + + @Getter + public static class AddMissionToStoreDto{ + @NotNull + Long storeId; + @NotBlank(message = "설명을 입력해주세요.") + String description; + @NotNull(message = "포인트를 입력해주세요") + Integer point; + @Future + LocalDate deadline; + } +} diff --git a/src/main/java/umc/spring/web/dto/MissionResponseDto.java b/src/main/java/umc/spring/web/dto/MissionResponseDto.java new file mode 100644 index 0000000..231ba40 --- /dev/null +++ b/src/main/java/umc/spring/web/dto/MissionResponseDto.java @@ -0,0 +1,19 @@ +package umc.spring.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +public class MissionResponseDto { + + @AllArgsConstructor + @Getter + @Builder + public static class AddMissionToStoreResultDto{ + Long missionId; + Long storeId; + LocalDateTime createdAt; + } +} From e0ef3c938e49cb28b298a9bedca55bf89b337d7a Mon Sep 17 00:00:00 2001 From: sammool Date: Mon, 19 May 2025 18:54:18 +0900 Subject: [PATCH 08/16] =?UTF-8?q?[mission/#8]=20=EB=8F=84=EC=A0=84=20?= =?UTF-8?q?=EC=A4=91=EC=9D=B8=20=EB=AF=B8=EC=85=98=EC=97=90=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring/apiPayload/code/ErrorStatus.java | 6 ++- .../apiPayload/exception/ExceptionAdvice.java | 10 ++++- .../exception/handler/MemberHandler.java | 10 +++++ .../exception/handler/MissionHandler.java | 10 +++++ .../exception/handler/StoreHandler.java | 10 +++++ .../converter/MemberMissionConverter.java | 26 +++++++++++ .../spring/domain/mapping/MemberMission.java | 13 ++++++ .../repository/MemberMissionRepository.java | 8 ++++ .../MemberMissionCommandService.java | 10 +++++ .../MemberMissionCommandServiceImpl.java | 43 +++++++++++++++++++ .../annotation/NotAlreadyChallenged.java | 17 ++++++++ .../validator/AlreadyChallengedValidator.java | 37 ++++++++++++++++ .../web/controller/MissionRestController.java | 14 +++++- .../umc/spring/web/dto/MissionRequestDto.java | 10 +++++ .../spring/web/dto/MissionResponseDto.java | 11 +++++ 15 files changed, 231 insertions(+), 4 deletions(-) create mode 100644 src/main/java/umc/spring/apiPayload/exception/handler/MemberHandler.java create mode 100644 src/main/java/umc/spring/apiPayload/exception/handler/MissionHandler.java create mode 100644 src/main/java/umc/spring/apiPayload/exception/handler/StoreHandler.java create mode 100644 src/main/java/umc/spring/converter/MemberMissionConverter.java create mode 100644 src/main/java/umc/spring/repository/MemberMissionRepository.java create mode 100644 src/main/java/umc/spring/service/memberMissionService/MemberMissionCommandService.java create mode 100644 src/main/java/umc/spring/service/memberMissionService/MemberMissionCommandServiceImpl.java create mode 100644 src/main/java/umc/spring/validation/annotation/NotAlreadyChallenged.java create mode 100644 src/main/java/umc/spring/validation/validator/AlreadyChallengedValidator.java diff --git a/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java b/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java index 167ed10..8cf7f1d 100644 --- a/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java +++ b/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java @@ -19,7 +19,11 @@ public enum ErrorStatus implements BaseErrorCode { MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER4001", "사용자가 없습니다."), NICKNAME_NOT_EXIST(HttpStatus.BAD_REQUEST, "MEMBER4002", "닉네임은 필수 입니다."), FOOD_CATEGORY_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER4003", "선호 음식을 선택해주세요"), - + //Mission 관련 에러 + MISSION_NOT_FOUND(HttpStatus.BAD_REQUEST, "MISSION4001", "해당 미션이 존재하지 않습니다"), + MISSION_ALREADY_CHALLENGED(HttpStatus.BAD_REQUEST, "MISSION4002", "해당 미션은 이미 진행중인 미션입니다."), + //Store 관련 에러 + STORE_NOT_FOUND(HttpStatus.BAD_REQUEST, "STORE4001", "가게가 존재하지 않습니다."), //Region 관련 에러 REGION_NOT_EXIST(HttpStatus.BAD_REQUEST, "REGION4001", "지역이 존재하지 않습니다."), TEMP_EXCEPTION(HttpStatus.BAD_REQUEST, "TEMP4001", "test exception"); diff --git a/src/main/java/umc/spring/apiPayload/exception/ExceptionAdvice.java b/src/main/java/umc/spring/apiPayload/exception/ExceptionAdvice.java index 9e13bee..e4019e6 100644 --- a/src/main/java/umc/spring/apiPayload/exception/ExceptionAdvice.java +++ b/src/main/java/umc/spring/apiPayload/exception/ExceptionAdvice.java @@ -18,6 +18,7 @@ import umc.spring.apiPayload.ErrorReasonDTO; import umc.spring.apiPayload.code.ErrorStatus; +import javax.swing.text.html.Option; import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; @@ -33,7 +34,6 @@ public ResponseEntity validation(ConstraintViolationException e, WebRequ .map(constraintViolation -> constraintViolation.getMessage()) .findFirst() .orElseThrow(() -> new RuntimeException("ConstraintViolationException 추출 도중 에러 발생")); - return handleExceptionInternalConstraint(e, ErrorStatus.valueOf(errorMessage), HttpHeaders.EMPTY,request); } @@ -49,9 +49,17 @@ public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotVali errors.merge(fieldName, errorMessage, (existingErrorMessage, newErrorMessage) -> existingErrorMessage + ", " + newErrorMessage); }); + e.getBindingResult().getGlobalErrors().stream() + .forEach(objectError->{ + String objectName = objectError.getObjectName(); + String errorMessage = Optional.ofNullable(objectError.getDefaultMessage()).orElse(""); + errors.put(objectName,errorMessage); + }); + return handleExceptionInternalArgs(e, HttpHeaders.EMPTY,ErrorStatus.valueOf("_BAD_REQUEST"),request,errors); } + @ExceptionHandler public ResponseEntity exception(Exception e, WebRequest request) { e.printStackTrace(); diff --git a/src/main/java/umc/spring/apiPayload/exception/handler/MemberHandler.java b/src/main/java/umc/spring/apiPayload/exception/handler/MemberHandler.java new file mode 100644 index 0000000..d99a65a --- /dev/null +++ b/src/main/java/umc/spring/apiPayload/exception/handler/MemberHandler.java @@ -0,0 +1,10 @@ +package umc.spring.apiPayload.exception.handler; + +import umc.spring.apiPayload.BaseErrorCode; +import umc.spring.apiPayload.exception.GeneralException; + +public class MemberHandler extends GeneralException { + public MemberHandler(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/umc/spring/apiPayload/exception/handler/MissionHandler.java b/src/main/java/umc/spring/apiPayload/exception/handler/MissionHandler.java new file mode 100644 index 0000000..c298aa6 --- /dev/null +++ b/src/main/java/umc/spring/apiPayload/exception/handler/MissionHandler.java @@ -0,0 +1,10 @@ +package umc.spring.apiPayload.exception.handler; + +import umc.spring.apiPayload.BaseErrorCode; +import umc.spring.apiPayload.exception.GeneralException; + +public class MissionHandler extends GeneralException { + public MissionHandler(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/umc/spring/apiPayload/exception/handler/StoreHandler.java b/src/main/java/umc/spring/apiPayload/exception/handler/StoreHandler.java new file mode 100644 index 0000000..946e05f --- /dev/null +++ b/src/main/java/umc/spring/apiPayload/exception/handler/StoreHandler.java @@ -0,0 +1,10 @@ +package umc.spring.apiPayload.exception.handler; + +import umc.spring.apiPayload.BaseErrorCode; +import umc.spring.apiPayload.exception.GeneralException; + +public class StoreHandler extends GeneralException { + public StoreHandler(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/umc/spring/converter/MemberMissionConverter.java b/src/main/java/umc/spring/converter/MemberMissionConverter.java new file mode 100644 index 0000000..c4ea8b3 --- /dev/null +++ b/src/main/java/umc/spring/converter/MemberMissionConverter.java @@ -0,0 +1,26 @@ +package umc.spring.converter; + +import umc.spring.domain.enums.MissionStatus; +import umc.spring.domain.mapping.MemberMission; +import umc.spring.web.dto.MissionRequestDto; +import umc.spring.web.dto.MissionResponseDto; + +import java.time.LocalDateTime; + +public class MemberMissionConverter { + + public static MissionResponseDto.ChallengeMissionResultDto toChallengeMissionResultDto(MemberMission memberMission){ + return MissionResponseDto.ChallengeMissionResultDto.builder() + .memberId(memberMission.getMember().getId()) + .missionId(memberMission.getMission().getId()) + .status(memberMission.getStatus()) + .createdAt(LocalDateTime.now()) + .build(); + } + + public static MemberMission toMemberMission(MissionRequestDto.ChallengeMissionDto request){ + return MemberMission.builder() + .status(MissionStatus.ONGOING) + .build(); + } +} diff --git a/src/main/java/umc/spring/domain/mapping/MemberMission.java b/src/main/java/umc/spring/domain/mapping/MemberMission.java index 303d7b3..948c024 100644 --- a/src/main/java/umc/spring/domain/mapping/MemberMission.java +++ b/src/main/java/umc/spring/domain/mapping/MemberMission.java @@ -20,6 +20,7 @@ public class MemberMission extends BaseEntity{ @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Enumerated(EnumType.STRING) @Column(columnDefinition = "VARCHAR(10) DEFAULT 'WAIT'") private MissionStatus status; @@ -35,5 +36,17 @@ public class MemberMission extends BaseEntity{ @JoinColumn(name="mission_id") private Mission mission; + public void setMember(Member member){ + this.member = member; + if(!member.getMemberMissionList().contains(this)){ + member.getMemberMissionList().add(this); + } + } + public void setMission(Mission mission){ + this.mission = mission; + if(!mission.getMemberMissionList().contains(this)){ + mission.getMemberMissionList().add(this); + } + } } diff --git a/src/main/java/umc/spring/repository/MemberMissionRepository.java b/src/main/java/umc/spring/repository/MemberMissionRepository.java new file mode 100644 index 0000000..214bf54 --- /dev/null +++ b/src/main/java/umc/spring/repository/MemberMissionRepository.java @@ -0,0 +1,8 @@ +package umc.spring.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import umc.spring.domain.mapping.MemberMission; + +public interface MemberMissionRepository extends JpaRepository { + boolean existsByMemberIdAndMissionId(Long memberId, Long MissionId); +} diff --git a/src/main/java/umc/spring/service/memberMissionService/MemberMissionCommandService.java b/src/main/java/umc/spring/service/memberMissionService/MemberMissionCommandService.java new file mode 100644 index 0000000..6ebf00b --- /dev/null +++ b/src/main/java/umc/spring/service/memberMissionService/MemberMissionCommandService.java @@ -0,0 +1,10 @@ +package umc.spring.service.memberMissionService; + +import umc.spring.converter.MemberMissionConverter; +import umc.spring.domain.mapping.MemberMission; +import umc.spring.web.dto.MissionRequestDto; + +public interface MemberMissionCommandService { + MemberMission challengeMission(MissionRequestDto.ChallengeMissionDto request); + boolean existsByMemberIdAndMissionId(Long memberId, Long missionId); +} diff --git a/src/main/java/umc/spring/service/memberMissionService/MemberMissionCommandServiceImpl.java b/src/main/java/umc/spring/service/memberMissionService/MemberMissionCommandServiceImpl.java new file mode 100644 index 0000000..87143ba --- /dev/null +++ b/src/main/java/umc/spring/service/memberMissionService/MemberMissionCommandServiceImpl.java @@ -0,0 +1,43 @@ +package umc.spring.service.memberMissionService; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import umc.spring.apiPayload.code.ErrorStatus; +import umc.spring.apiPayload.exception.handler.MemberHandler; +import umc.spring.apiPayload.exception.handler.MissionHandler; +import umc.spring.converter.MemberMissionConverter; +import umc.spring.domain.Member; +import umc.spring.domain.Mission; +import umc.spring.domain.mapping.MemberMission; +import umc.spring.repository.MemberMissionRepository; +import umc.spring.repository.MemberRepository; +import umc.spring.repository.MissionRepository; +import umc.spring.web.dto.MissionRequestDto; + +@Service +@RequiredArgsConstructor +public class MemberMissionCommandServiceImpl implements MemberMissionCommandService{ + + private final MemberMissionRepository memberMissionRepository; + private final MemberRepository memberRepository; + private final MissionRepository missionRepository; + + @Override + public MemberMission challengeMission(MissionRequestDto.ChallengeMissionDto request){ + MemberMission memberMission = MemberMissionConverter.toMemberMission(request); + + Member member = memberRepository.findById(request.getMemberId()) + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); + Mission mission = missionRepository.findById(request.getMissionId()) + .orElseThrow(() -> new MissionHandler(ErrorStatus.MISSION_NOT_FOUND)); + memberMission.setMember(member); + memberMission.setMission(mission); + + return memberMissionRepository.save(memberMission); + } + @Override + public boolean existsByMemberIdAndMissionId(Long memberId, Long missionId){ + return memberMissionRepository.existsByMemberIdAndMissionId(memberId, missionId); + } + +} diff --git a/src/main/java/umc/spring/validation/annotation/NotAlreadyChallenged.java b/src/main/java/umc/spring/validation/annotation/NotAlreadyChallenged.java new file mode 100644 index 0000000..a971009 --- /dev/null +++ b/src/main/java/umc/spring/validation/annotation/NotAlreadyChallenged.java @@ -0,0 +1,17 @@ +package umc.spring.validation.annotation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import umc.spring.validation.validator.AlreadyChallengedValidator; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = AlreadyChallengedValidator.class) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE}) +@Retention(value = RetentionPolicy.RUNTIME) +public @interface NotAlreadyChallenged { + String message() default "이미 진행중인 미션입니다."; + Class[] groups() default {}; + Class[] payload() default {}; //메타데이터를 전달할 때 사용 +} diff --git a/src/main/java/umc/spring/validation/validator/AlreadyChallengedValidator.java b/src/main/java/umc/spring/validation/validator/AlreadyChallengedValidator.java new file mode 100644 index 0000000..6f98bb6 --- /dev/null +++ b/src/main/java/umc/spring/validation/validator/AlreadyChallengedValidator.java @@ -0,0 +1,37 @@ +package umc.spring.validation.validator; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import umc.spring.apiPayload.code.ErrorStatus; +import umc.spring.domain.mapping.MemberMission; +import umc.spring.repository.MemberMissionRepository; +import umc.spring.service.memberMissionService.MemberMissionCommandService; +import umc.spring.validation.annotation.NotAlreadyChallenged; +import umc.spring.web.dto.MissionRequestDto; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AlreadyChallengedValidator implements ConstraintValidator { + + private final MemberMissionCommandService memberMissionCommandService; + + @Override + public void initialize(NotAlreadyChallenged constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(MissionRequestDto.ChallengeMissionDto request, ConstraintValidatorContext context) { + + boolean isValid = !(memberMissionCommandService.existsByMemberIdAndMissionId(request.getMemberId(), request.getMissionId())); + if(!isValid){ + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.MISSION_ALREADY_CHALLENGED.toString()).addConstraintViolation(); + } + return isValid; + } +} diff --git a/src/main/java/umc/spring/web/controller/MissionRestController.java b/src/main/java/umc/spring/web/controller/MissionRestController.java index 468941e..e37d1f2 100644 --- a/src/main/java/umc/spring/web/controller/MissionRestController.java +++ b/src/main/java/umc/spring/web/controller/MissionRestController.java @@ -7,12 +7,14 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import umc.spring.apiPayload.ApiResponse; +import umc.spring.converter.MemberMissionConverter; import umc.spring.converter.MissionConverter; import umc.spring.domain.Mission; +import umc.spring.domain.mapping.MemberMission; +import umc.spring.service.memberMissionService.MemberMissionCommandService; import umc.spring.service.missionService.MissionCommandService; import umc.spring.web.dto.MissionRequestDto; import umc.spring.web.dto.MissionResponseDto; -import umc.spring.web.dto.ReviewRequestDto; @RestController @RequestMapping("/missions") @@ -20,10 +22,18 @@ public class MissionRestController { private final MissionCommandService missionCommandService; + private final MemberMissionCommandService memberMissionCommandService; @PostMapping("/add") - public ApiResponse AddMission(@RequestBody @Valid MissionRequestDto.AddMissionToStoreDto request){ + public ApiResponse addMission(@RequestBody @Valid MissionRequestDto.AddMissionToStoreDto request){ Mission mission = missionCommandService.addMission(request); return ApiResponse.onSuccess(MissionConverter.toAddMissionToStoreResultDto(mission)); } + + @PostMapping("/challenge") + public ApiResponse + challangeMission(@RequestBody @Valid MissionRequestDto.ChallengeMissionDto request){ + MemberMission memberMission = memberMissionCommandService.challengeMission(request); + return ApiResponse.onSuccess(MemberMissionConverter.toChallengeMissionResultDto(memberMission)); + } } diff --git a/src/main/java/umc/spring/web/dto/MissionRequestDto.java b/src/main/java/umc/spring/web/dto/MissionRequestDto.java index d8444b0..595c0ae 100644 --- a/src/main/java/umc/spring/web/dto/MissionRequestDto.java +++ b/src/main/java/umc/spring/web/dto/MissionRequestDto.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Getter; +import umc.spring.validation.annotation.NotAlreadyChallenged; import java.time.LocalDate; @@ -20,4 +21,13 @@ public static class AddMissionToStoreDto{ @Future LocalDate deadline; } + + @Getter + @NotAlreadyChallenged + public static class ChallengeMissionDto{ + @NotNull + Long missionId; + @NotNull + Long memberId; + } } diff --git a/src/main/java/umc/spring/web/dto/MissionResponseDto.java b/src/main/java/umc/spring/web/dto/MissionResponseDto.java index 231ba40..3cbfc9f 100644 --- a/src/main/java/umc/spring/web/dto/MissionResponseDto.java +++ b/src/main/java/umc/spring/web/dto/MissionResponseDto.java @@ -3,6 +3,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import umc.spring.domain.enums.MissionStatus; import java.time.LocalDateTime; @@ -16,4 +17,14 @@ public static class AddMissionToStoreResultDto{ Long storeId; LocalDateTime createdAt; } + + @AllArgsConstructor + @Getter + @Builder + public static class ChallengeMissionResultDto{ + Long missionId; + Long memberId; + MissionStatus status; + LocalDateTime createdAt; + } } From 7b4a6abd56f53cfbeeab2b9f614b4ffaac818823 Mon Sep 17 00:00:00 2001 From: sammool Date: Sun, 25 May 2025 20:46:20 +0900 Subject: [PATCH 09/16] practice/#9 (v2) --- .../umc/spring/converter/ReviewConverter.java | 28 ++++++++++++- .../spring/repository/ReviewRepository.java | 5 ++- .../storeService/StoreQueryService.java | 8 ++++ .../storeService/StoreQueryServiceImpl.java | 31 ++++++++++++++ .../web/controller/StoreRestController.java | 42 +++++++++++++++---- .../umc/spring/web/dto/ReviewResponseDto.java | 8 +++- .../umc/spring/web/dto/StoreResponseDto.java | 24 +++++++++++ 7 files changed, 133 insertions(+), 13 deletions(-) create mode 100644 src/main/java/umc/spring/service/storeService/StoreQueryService.java create mode 100644 src/main/java/umc/spring/service/storeService/StoreQueryServiceImpl.java diff --git a/src/main/java/umc/spring/converter/ReviewConverter.java b/src/main/java/umc/spring/converter/ReviewConverter.java index 81d50a7..bafb31f 100644 --- a/src/main/java/umc/spring/converter/ReviewConverter.java +++ b/src/main/java/umc/spring/converter/ReviewConverter.java @@ -1,13 +1,15 @@ package umc.spring.converter; -import umc.spring.domain.Member; +import org.springframework.data.domain.Page; import umc.spring.domain.Review; -import umc.spring.domain.Store; import umc.spring.web.dto.ReviewRequestDto; import umc.spring.web.dto.ReviewResponseDto; +import umc.spring.web.dto.StoreResponseDto; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; public class ReviewConverter { public static ReviewResponseDto.ReviewAddResultDto toReviewAddResultDto(Review review){ @@ -25,4 +27,26 @@ public static Review toReview(ReviewRequestDto.ReviewAddDto request){ .build(); } + + public static StoreResponseDto.ReviewPreviewDto reviewPreviewDto(Review review){ + return StoreResponseDto.ReviewPreviewDto.builder() + .ownerNickname(review.getMember().getName()) + .score(review.getScore()) + .createdAt(review.getCreatedAt().toLocalDate()) + .content(review.getContent()) + .build(); + } + public static StoreResponseDto.ReviewPreviewListDto reviewPreviewListDto(Page reviewList){ + List reviewPreviewDtoList = reviewList.stream(). + map(ReviewConverter::reviewPreviewDto).collect(Collectors.toList()); + + return StoreResponseDto.ReviewPreviewListDto.builder() + .isLast(reviewList.isLast()) + .isFirst(reviewList.isFirst()) + .totalPage(reviewList.getTotalPages()) + .totalElements(reviewList.getTotalElements()) + .listSize(reviewPreviewDtoList.size()) + .reviewList(reviewPreviewDtoList) + .build(); + } } diff --git a/src/main/java/umc/spring/repository/ReviewRepository.java b/src/main/java/umc/spring/repository/ReviewRepository.java index 62fd5c3..668c1a4 100644 --- a/src/main/java/umc/spring/repository/ReviewRepository.java +++ b/src/main/java/umc/spring/repository/ReviewRepository.java @@ -1,8 +1,11 @@ package umc.spring.repository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; import umc.spring.domain.Review; +import umc.spring.domain.Store; public interface ReviewRepository extends JpaRepository { - + Page findAllByStore(Store store, PageRequest pageRequest); } diff --git a/src/main/java/umc/spring/service/storeService/StoreQueryService.java b/src/main/java/umc/spring/service/storeService/StoreQueryService.java new file mode 100644 index 0000000..e6cc53a --- /dev/null +++ b/src/main/java/umc/spring/service/storeService/StoreQueryService.java @@ -0,0 +1,8 @@ +package umc.spring.service.storeService; + +import org.springframework.data.domain.Page; +import umc.spring.domain.Review; + +public interface StoreQueryService { + Page getReviewList(Long storeId, Integer page); +} diff --git a/src/main/java/umc/spring/service/storeService/StoreQueryServiceImpl.java b/src/main/java/umc/spring/service/storeService/StoreQueryServiceImpl.java new file mode 100644 index 0000000..7c91156 --- /dev/null +++ b/src/main/java/umc/spring/service/storeService/StoreQueryServiceImpl.java @@ -0,0 +1,31 @@ +package umc.spring.service.storeService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import umc.spring.domain.Review; +import umc.spring.domain.Store; +import umc.spring.repository.ReviewRepository; +import umc.spring.repository.storeRepository.StoreRepository; + +@Slf4j +@Service +@RequiredArgsConstructor +public class StoreQueryServiceImpl implements StoreQueryService{ + + private final StoreRepository storeRepository; + private final ReviewRepository reviewRepository; + + @Override + public Page getReviewList(Long storeId, Integer page){ + Store store = storeRepository.findById(storeId).get(); + log.info("store:{}",store.getId()); + Page storePage = reviewRepository.findAllByStore(store, PageRequest.of(page,10)); + for (Review review : storePage) { + log.info("review:{}", review); + } + return storePage; + } +} diff --git a/src/main/java/umc/spring/web/controller/StoreRestController.java b/src/main/java/umc/spring/web/controller/StoreRestController.java index 0a44f46..c47199b 100644 --- a/src/main/java/umc/spring/web/controller/StoreRestController.java +++ b/src/main/java/umc/spring/web/controller/StoreRestController.java @@ -1,21 +1,25 @@ package umc.spring.web.controller; +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 jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.*; import umc.spring.apiPayload.ApiResponse; import umc.spring.converter.MemberConverter; +import umc.spring.converter.ReviewConverter; import umc.spring.converter.StoreConverter; import umc.spring.domain.Member; +import umc.spring.domain.Review; import umc.spring.domain.Store; import umc.spring.service.storeService.StoreCommandService; -import umc.spring.web.dto.MemberRequestDto; -import umc.spring.web.dto.MemberResponseDto; -import umc.spring.web.dto.StoreRequestDto; -import umc.spring.web.dto.StoreResponseDto; +import umc.spring.service.storeService.StoreQueryService; +import umc.spring.web.dto.*; @RestController @RequestMapping("/stores") @@ -23,10 +27,30 @@ public class StoreRestController { private final StoreCommandService storeCommandService; + private final StoreQueryService storeQueryService; @PostMapping("/add") - public ApiResponse join(@RequestBody @Valid StoreRequestDto.AddStoreToRegionDto request){ + public ApiResponse AddStore(@RequestBody @Valid StoreRequestDto.AddStoreToRegionDto request){ Store store = storeCommandService.addStore(request); return ApiResponse.onSuccess(StoreConverter.toAddStoreToRegionResultDto(store)); } + + @GetMapping("/{storeId}/reviews") + @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 = "AUTH003", description = "access 토큰을 주세요", content = @Content(schema = @Schema(implementation = ApiResponse.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "AUTH004", description = "access 토큰 만료", content = @Content(schema = @Schema(implementation = ApiResponse.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "AUTH006", description = "access 토큰 모양이 이상함", content = @Content(schema = @Schema(implementation = ApiResponse.class))) + }) + @Parameters({ + @Parameter(name = "storeId", description = "가게의 아이디, path variable입니다!") + }) + public ApiResponse getReviewList( + @PathVariable(name = "storeId") Long storeId, @RequestParam(name = "page") Integer page){ + + Page reviewList = storeQueryService.getReviewList(storeId, page); + return ApiResponse.onSuccess(ReviewConverter.reviewPreviewListDto(reviewList)); + } } diff --git a/src/main/java/umc/spring/web/dto/ReviewResponseDto.java b/src/main/java/umc/spring/web/dto/ReviewResponseDto.java index f70adf6..1b31a19 100644 --- a/src/main/java/umc/spring/web/dto/ReviewResponseDto.java +++ b/src/main/java/umc/spring/web/dto/ReviewResponseDto.java @@ -3,9 +3,10 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import lombok.RequiredArgsConstructor; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; public class ReviewResponseDto { @@ -16,4 +17,9 @@ public static class ReviewAddResultDto{ Long reviewId; LocalDateTime createdAt; } + + + + + } diff --git a/src/main/java/umc/spring/web/dto/StoreResponseDto.java b/src/main/java/umc/spring/web/dto/StoreResponseDto.java index 43503ba..a8c829f 100644 --- a/src/main/java/umc/spring/web/dto/StoreResponseDto.java +++ b/src/main/java/umc/spring/web/dto/StoreResponseDto.java @@ -4,7 +4,9 @@ import lombok.Builder; import lombok.Getter; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; public class StoreResponseDto { @Getter @@ -14,4 +16,26 @@ public static class AddStoreToRegionResultDto{ Long storeId; LocalDateTime createdAt; } + + @Builder + @Getter + @AllArgsConstructor + public static class ReviewPreviewListDto{ + List reviewList; + Integer listSize; + Integer totalPage; + Long totalElements; + Boolean isFirst; + Boolean isLast; + } + + @Builder + @Getter + @AllArgsConstructor + public static class ReviewPreviewDto{ + String ownerNickname; + Float score; + String content; + LocalDate createdAt; + } } From 59ea37f90f23479a03c0080d469775e4d8c7d527 Mon Sep 17 00:00:00 2001 From: sammool Date: Sun, 25 May 2025 23:47:06 +0900 Subject: [PATCH 10/16] =?UTF-8?q?[mission/#9]=20=EB=82=B4=EA=B0=80=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=ED=95=9C=20=EB=A6=AC=EB=B7=B0=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring/apiPayload/code/ErrorStatus.java | 5 +- .../apiPayload/exception/ExceptionAdvice.java | 38 +++++++++--- .../umc/spring/converter/MemberConverter.java | 61 +++++++++++++++++++ .../spring/repository/ReviewRepository.java | 2 + .../memberService/FoodCategoryService.java | 5 ++ .../FoodCategoryServiceImpl.java | 16 +++++ .../memberService/MemberCommandService.java | 8 +++ .../MemberCommandServiceImpl.java | 42 +++++++++++++ .../memberService/MemberQueryService.java | 8 +++ .../memberService/MemberQueryServiceImpl.java | 28 +++++++++ .../annotation/ExistCategories.java | 17 ++++++ .../validation/annotation/ValidPage.java | 19 ++++++ .../validator/CategoriesExistValidator.java | 34 +++++++++++ .../validator/ValidPageValidator.java | 24 ++++++++ .../web/controller/MemberRestController.java | 56 +++++++++++++++++ .../umc/spring/web/dto/MemberResponseDto.java | 44 +++++++++++++ 16 files changed, 396 insertions(+), 11 deletions(-) create mode 100644 src/main/java/umc/spring/converter/MemberConverter.java create mode 100644 src/main/java/umc/spring/service/memberService/FoodCategoryService.java create mode 100644 src/main/java/umc/spring/service/memberService/FoodCategoryServiceImpl.java create mode 100644 src/main/java/umc/spring/service/memberService/MemberCommandService.java create mode 100644 src/main/java/umc/spring/service/memberService/MemberCommandServiceImpl.java create mode 100644 src/main/java/umc/spring/service/memberService/MemberQueryService.java create mode 100644 src/main/java/umc/spring/service/memberService/MemberQueryServiceImpl.java create mode 100644 src/main/java/umc/spring/validation/annotation/ExistCategories.java create mode 100644 src/main/java/umc/spring/validation/annotation/ValidPage.java create mode 100644 src/main/java/umc/spring/validation/validator/CategoriesExistValidator.java create mode 100644 src/main/java/umc/spring/validation/validator/ValidPageValidator.java create mode 100644 src/main/java/umc/spring/web/controller/MemberRestController.java create mode 100644 src/main/java/umc/spring/web/dto/MemberResponseDto.java diff --git a/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java b/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java index 8cf7f1d..db5953c 100644 --- a/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java +++ b/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java @@ -26,8 +26,9 @@ public enum ErrorStatus implements BaseErrorCode { STORE_NOT_FOUND(HttpStatus.BAD_REQUEST, "STORE4001", "가게가 존재하지 않습니다."), //Region 관련 에러 REGION_NOT_EXIST(HttpStatus.BAD_REQUEST, "REGION4001", "지역이 존재하지 않습니다."), - TEMP_EXCEPTION(HttpStatus.BAD_REQUEST, "TEMP4001", "test exception"); - + TEMP_EXCEPTION(HttpStatus.BAD_REQUEST, "TEMP4001", "test exception"), + //Page 관련 에러 + PAGE_NOT_EXIST(HttpStatus.BAD_REQUEST, "PAGE4001", "존재하지 않는 페이지입니다."); private final HttpStatus httpStatus; private final String code; private final String message; diff --git a/src/main/java/umc/spring/apiPayload/exception/ExceptionAdvice.java b/src/main/java/umc/spring/apiPayload/exception/ExceptionAdvice.java index e4019e6..996830c 100644 --- a/src/main/java/umc/spring/apiPayload/exception/ExceptionAdvice.java +++ b/src/main/java/umc/spring/apiPayload/exception/ExceptionAdvice.java @@ -1,6 +1,7 @@ package umc.spring.apiPayload.exception; import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; @@ -28,14 +29,14 @@ public class ExceptionAdvice extends ResponseEntityExceptionHandler { - @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); - } +// @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, HttpStatusCode status, WebRequest request) { @@ -55,7 +56,7 @@ public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotVali String errorMessage = Optional.ofNullable(objectError.getDefaultMessage()).orElse(""); errors.put(objectName,errorMessage); }); - + log.info("에러 발생"); return handleExceptionInternalArgs(e, HttpHeaders.EMPTY,ErrorStatus.valueOf("_BAD_REQUEST"),request,errors); } @@ -73,6 +74,25 @@ public ResponseEntity onThrowException(GeneralException generalException, HttpSe return handleExceptionInternal(generalException,errorReasonHttpStatus,null,request); } + @ExceptionHandler(value = ConstraintViolationException.class) + public ResponseEntity handleConstraintViolation(ConstraintViolationException ex, WebRequest request) { + String errorKey = ex.getConstraintViolations().stream() + .map(v -> v.getMessage()) + .findFirst() + .orElse("INVALID_REQUEST"); + + ErrorStatus status; + + switch (errorKey) { + case "STORE_NOT_FOUND" -> status = ErrorStatus.STORE_NOT_FOUND; + case "PAGE_NOT_EXIST" -> status = ErrorStatus.PAGE_NOT_EXIST; + default -> status = ErrorStatus._BAD_REQUEST; + } + + ApiResponse body = ApiResponse.onFailure(status.getCode(), status.getMessage(), null); + return ResponseEntity.status(status.getHttpStatus()).body(body); + } + private ResponseEntity handleExceptionInternal(Exception e, ErrorReasonDTO reason, HttpHeaders headers, HttpServletRequest request) { diff --git a/src/main/java/umc/spring/converter/MemberConverter.java b/src/main/java/umc/spring/converter/MemberConverter.java new file mode 100644 index 0000000..feb93c7 --- /dev/null +++ b/src/main/java/umc/spring/converter/MemberConverter.java @@ -0,0 +1,61 @@ +package umc.spring.converter; + +import org.springframework.data.domain.Page; +import umc.spring.domain.Member; +import umc.spring.domain.Review; +import umc.spring.domain.enums.Gender; +import umc.spring.web.dto.MemberRequestDto; +import umc.spring.web.dto.MemberResponseDto; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class MemberConverter { + public static MemberResponseDto.JoinResultDTO toJoinResultDTO(Member member){ + return MemberResponseDto.JoinResultDTO.builder() + .memberId(member.getId()) + .createdAt(LocalDateTime.now()) + .build(); + } + + public static Member toMember(MemberRequestDto.JoinDto request){ + Gender gender = switch (request.getGender()){ + case 1 -> Gender.MALE; + case 2 -> Gender.FEMALE; + default -> null; + }; + return Member.builder() + .address(request.getAddress()) + .gender(gender) + .name(request.getName()) + .birth(request.getBirth()) + .email(request.getEmail()) + .phoneNumber(request.getPhoneNumber()) + .memberFoodList(new ArrayList<>()) + .build(); + } + + public static MemberResponseDto.ReviewPreviewDto reviewPreviewDto(Review review){ + return MemberResponseDto.ReviewPreviewDto.builder() + .username(review.getMember().getName()) + .score(review.getScore()) + .createdAt(review.getCreatedAt().toLocalDate()) + .content(review.getContent()) + .build(); + } + public static MemberResponseDto.ReviewPreviewListDto reviewPreviewListDto(Page reviewList){ + List reviewPreviewDtoList = reviewList.stream(). + map(MemberConverter::reviewPreviewDto).collect(Collectors.toList()); + + return MemberResponseDto.ReviewPreviewListDto.builder() + .isLast(reviewList.isLast()) + .isFirst(reviewList.isFirst()) + .totalPage(reviewList.getTotalPages()) + .totalElements(reviewList.getTotalElements()) + .listSize(reviewPreviewDtoList.size()) + .reviewList(reviewPreviewDtoList) + .build(); + } +} diff --git a/src/main/java/umc/spring/repository/ReviewRepository.java b/src/main/java/umc/spring/repository/ReviewRepository.java index 668c1a4..4b8bd91 100644 --- a/src/main/java/umc/spring/repository/ReviewRepository.java +++ b/src/main/java/umc/spring/repository/ReviewRepository.java @@ -3,9 +3,11 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; +import umc.spring.domain.Member; import umc.spring.domain.Review; import umc.spring.domain.Store; public interface ReviewRepository extends JpaRepository { Page findAllByStore(Store store, PageRequest pageRequest); + Page findAllByMember(Member member, PageRequest pageRequest); } diff --git a/src/main/java/umc/spring/service/memberService/FoodCategoryService.java b/src/main/java/umc/spring/service/memberService/FoodCategoryService.java new file mode 100644 index 0000000..04e9f4d --- /dev/null +++ b/src/main/java/umc/spring/service/memberService/FoodCategoryService.java @@ -0,0 +1,5 @@ +package umc.spring.service.memberService; + +public interface FoodCategoryService { + boolean existById(Long id); +} diff --git a/src/main/java/umc/spring/service/memberService/FoodCategoryServiceImpl.java b/src/main/java/umc/spring/service/memberService/FoodCategoryServiceImpl.java new file mode 100644 index 0000000..b9f25cd --- /dev/null +++ b/src/main/java/umc/spring/service/memberService/FoodCategoryServiceImpl.java @@ -0,0 +1,16 @@ +package umc.spring.service.memberService; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import umc.spring.repository.FoodCategoryRepository; + +@Service +@RequiredArgsConstructor +public class FoodCategoryServiceImpl implements FoodCategoryService { + private final FoodCategoryRepository foodCategoryRepository; + + @Override + public boolean existById(Long id) { + return foodCategoryRepository.existsById(id); + } +} diff --git a/src/main/java/umc/spring/service/memberService/MemberCommandService.java b/src/main/java/umc/spring/service/memberService/MemberCommandService.java new file mode 100644 index 0000000..02a051b --- /dev/null +++ b/src/main/java/umc/spring/service/memberService/MemberCommandService.java @@ -0,0 +1,8 @@ +package umc.spring.service.memberService; + +import umc.spring.domain.Member; +import umc.spring.web.dto.MemberRequestDto; + +public interface MemberCommandService { + public Member joinMember(MemberRequestDto.JoinDto request); +} diff --git a/src/main/java/umc/spring/service/memberService/MemberCommandServiceImpl.java b/src/main/java/umc/spring/service/memberService/MemberCommandServiceImpl.java new file mode 100644 index 0000000..728a5f1 --- /dev/null +++ b/src/main/java/umc/spring/service/memberService/MemberCommandServiceImpl.java @@ -0,0 +1,42 @@ +package umc.spring.service.memberService; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import umc.spring.apiPayload.code.ErrorStatus; +import umc.spring.apiPayload.exception.handler.FoodCategoryHandler; +import umc.spring.converter.MemberConverter; +import umc.spring.converter.MemberFoodConverter; +import umc.spring.domain.FoodCategory; +import umc.spring.domain.Member; +import umc.spring.domain.mapping.MemberFood; +import umc.spring.repository.FoodCategoryRepository; +import umc.spring.repository.MemberRepository; +import umc.spring.web.dto.MemberRequestDto; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class MemberCommandServiceImpl implements MemberCommandService { + private final MemberRepository memberRepository; + private final FoodCategoryRepository foodCategoryRepository; + + @Override + @Transactional + public Member joinMember(MemberRequestDto.JoinDto request){ + Member newMember = MemberConverter.toMember(request); + List foodCategoryList = request.getPreferCategory().stream(). + map(category->{ + return foodCategoryRepository.findById(category).orElseThrow(() -> new FoodCategoryHandler(ErrorStatus.FOOD_CATEGORY_NOT_FOUND)); + }).collect(Collectors.toList()); + + List memberFoodList = MemberFoodConverter.toMemberFoodList(foodCategoryList); + memberFoodList.forEach(memberFood -> { + memberFood.setMember(newMember); + }); + return memberRepository.save(newMember); + } + +} diff --git a/src/main/java/umc/spring/service/memberService/MemberQueryService.java b/src/main/java/umc/spring/service/memberService/MemberQueryService.java new file mode 100644 index 0000000..aeb27ac --- /dev/null +++ b/src/main/java/umc/spring/service/memberService/MemberQueryService.java @@ -0,0 +1,8 @@ +package umc.spring.service.memberService; + +import org.springframework.data.domain.Page; +import umc.spring.domain.Review; + +public interface MemberQueryService { + Page getReviewList(Long memberId, Integer page); +} diff --git a/src/main/java/umc/spring/service/memberService/MemberQueryServiceImpl.java b/src/main/java/umc/spring/service/memberService/MemberQueryServiceImpl.java new file mode 100644 index 0000000..588b055 --- /dev/null +++ b/src/main/java/umc/spring/service/memberService/MemberQueryServiceImpl.java @@ -0,0 +1,28 @@ +package umc.spring.service.memberService; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import umc.spring.apiPayload.code.ErrorStatus; +import umc.spring.apiPayload.exception.handler.MemberHandler; +import umc.spring.domain.Member; +import umc.spring.domain.Review; +import umc.spring.repository.MemberRepository; +import umc.spring.repository.ReviewRepository; + +@Service +@RequiredArgsConstructor +public class MemberQueryServiceImpl implements MemberQueryService { + + private final MemberRepository memberRepository; + private final ReviewRepository reviewRepository; + + @Override + public Page getReviewList(Long memberId, Integer page){ + Member member = memberRepository.findById(memberId).orElseThrow(()-> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); + Page memberPage = reviewRepository.findAllByMember(member, PageRequest.of(page, 10)); + return memberPage; + } + +} diff --git a/src/main/java/umc/spring/validation/annotation/ExistCategories.java b/src/main/java/umc/spring/validation/annotation/ExistCategories.java new file mode 100644 index 0000000..9eff722 --- /dev/null +++ b/src/main/java/umc/spring/validation/annotation/ExistCategories.java @@ -0,0 +1,17 @@ +package umc.spring.validation.annotation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import umc.spring.validation.validator.CategoriesExistValidator; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = CategoriesExistValidator.class) //사용자가 validation 커스텀 +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistCategories { + String message() default "해당하는 카테고리가 존재하지 않습니다."; + Class[] groups() default {}; + Class[] payload() default {}; //메타데이터를 전달할 때 사용 +} diff --git a/src/main/java/umc/spring/validation/annotation/ValidPage.java b/src/main/java/umc/spring/validation/annotation/ValidPage.java new file mode 100644 index 0000000..6cef110 --- /dev/null +++ b/src/main/java/umc/spring/validation/annotation/ValidPage.java @@ -0,0 +1,19 @@ +package umc.spring.validation.annotation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import umc.spring.validation.validator.AlreadyChallengedValidator; +import umc.spring.validation.validator.ValidPageValidator; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = ValidPageValidator.class) +@Target({ElementType.METHOD, ElementType.PARAMETER}) +@Retention(value = RetentionPolicy.RUNTIME) +public @interface ValidPage { + String message() default "유효하지 않은 페이지입니다."; + Class[] groups() default {}; + Class[] payload() default {}; + +} diff --git a/src/main/java/umc/spring/validation/validator/CategoriesExistValidator.java b/src/main/java/umc/spring/validation/validator/CategoriesExistValidator.java new file mode 100644 index 0000000..414a7ac --- /dev/null +++ b/src/main/java/umc/spring/validation/validator/CategoriesExistValidator.java @@ -0,0 +1,34 @@ +package umc.spring.validation.validator; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import umc.spring.apiPayload.code.ErrorStatus; +import umc.spring.service.memberService.FoodCategoryService; +import umc.spring.validation.annotation.ExistCategories; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class CategoriesExistValidator implements ConstraintValidator> { + + private final FoodCategoryService foodCategoryService; + + @Override + public void initialize(ExistCategories constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(List values, ConstraintValidatorContext context) { + boolean isValid = values.stream().allMatch(value -> + foodCategoryService.existById(value)); + if(!isValid){ + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.FOOD_CATEGORY_NOT_FOUND.toString()).addConstraintViolation(); + } + return isValid; + } +} diff --git a/src/main/java/umc/spring/validation/validator/ValidPageValidator.java b/src/main/java/umc/spring/validation/validator/ValidPageValidator.java new file mode 100644 index 0000000..18145cc --- /dev/null +++ b/src/main/java/umc/spring/validation/validator/ValidPageValidator.java @@ -0,0 +1,24 @@ +package umc.spring.validation.validator; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import umc.spring.apiPayload.code.ErrorStatus; +import umc.spring.validation.annotation.ValidPage; + +public class ValidPageValidator implements ConstraintValidator { + @Override + public void initialize(ValidPage constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(Integer page, ConstraintValidatorContext context) { + if(page > 0) + return true; + else{ + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.PAGE_NOT_EXIST.toString()).addConstraintViolation(); + } + return false; + } +} diff --git a/src/main/java/umc/spring/web/controller/MemberRestController.java b/src/main/java/umc/spring/web/controller/MemberRestController.java new file mode 100644 index 0000000..22a4960 --- /dev/null +++ b/src/main/java/umc/spring/web/controller/MemberRestController.java @@ -0,0 +1,56 @@ +package umc.spring.web.controller; + +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 jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import umc.spring.apiPayload.ApiResponse; +import umc.spring.apiPayload.code.ErrorStatus; +import umc.spring.converter.MemberConverter; +import umc.spring.domain.Member; +import umc.spring.domain.Review; +import umc.spring.service.memberService.MemberCommandService; +import umc.spring.service.memberService.MemberQueryService; +import umc.spring.validation.annotation.ValidPage; +import umc.spring.web.dto.MemberRequestDto; +import umc.spring.web.dto.MemberResponseDto; + +@RestController +@Validated +@RequiredArgsConstructor +@RequestMapping("/members") +public class MemberRestController { + private final MemberCommandService memberCommandService; + private final MemberQueryService memberQueryService; + + @PostMapping("/") + public ApiResponse join(@RequestBody @Valid MemberRequestDto.JoinDto request){ + Member member = memberCommandService.joinMember(request); + return ApiResponse.onSuccess(MemberConverter.toJoinResultDTO(member)); + } + + @GetMapping("{memberId}/reviews") + @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 = "AUTH003", description = "access 토큰을 주세요", content = @Content(schema = @Schema(implementation = ApiResponse.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "AUTH004", description = "access 토큰 만료", content = @Content(schema = @Schema(implementation = ApiResponse.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "MEMBER4001", description = "사용자가 존재하지 않습니다", content = @Content(schema = @Schema(implementation = ApiResponse.class))) + }) + @Parameters({ + @Parameter(name = "memberId", description = "멤버의 아이디, path variable") + }) + public ApiResponse getReviewList(@PathVariable(name = "memberId") Long memberId, + @ValidPage @RequestParam(name = "page") Integer page){ + Page reviewList = memberQueryService.getReviewList(memberId, --page); + return ApiResponse.onSuccess(MemberConverter.reviewPreviewListDto(reviewList)); + } + +} diff --git a/src/main/java/umc/spring/web/dto/MemberResponseDto.java b/src/main/java/umc/spring/web/dto/MemberResponseDto.java new file mode 100644 index 0000000..8bf5b07 --- /dev/null +++ b/src/main/java/umc/spring/web/dto/MemberResponseDto.java @@ -0,0 +1,44 @@ +package umc.spring.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +public class MemberResponseDto { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class JoinResultDTO{ + Long memberId; + LocalDateTime createdAt; + } + + @Builder + @Getter + @AllArgsConstructor + public static class ReviewPreviewListDto{ + List reviewList; + Integer listSize; + Integer totalPage; + Long totalElements; + Boolean isFirst; + Boolean isLast; + } + + @Builder + @Getter + @AllArgsConstructor + public static class ReviewPreviewDto{ + String username; + String content; + Float score; + LocalDate createdAt; + } + +} From 87f8f60789e873b3ed935f424e81f1870f72a931 Mon Sep 17 00:00:00 2001 From: sammool Date: Mon, 26 May 2025 00:44:26 +0900 Subject: [PATCH 11/16] =?UTF-8?q?[mission/#9]=20=ED=8A=B9=EC=A0=95=20?= =?UTF-8?q?=EA=B0=80=EA=B2=8C=20=EB=AF=B8=EC=85=98=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EA=B8=B0=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring/converter/MissionConverter.java | 26 +++++++++++++ .../spring/repository/MissionRepository.java | 4 ++ .../storeService/StoreCommandService.java | 1 + .../storeService/StoreQueryService.java | 3 ++ .../storeService/StoreQueryServiceImpl.java | 20 ++++++++++ .../validation/annotation/ExistStore.java | 18 +++++++++ .../validator/StoreExistValidator.java | 38 +++++++++++++++++++ .../web/controller/StoreRestController.java | 23 +++++++++++ .../umc/spring/web/dto/StoreResponseDto.java | 21 ++++++++++ 9 files changed, 154 insertions(+) create mode 100644 src/main/java/umc/spring/validation/annotation/ExistStore.java create mode 100644 src/main/java/umc/spring/validation/validator/StoreExistValidator.java diff --git a/src/main/java/umc/spring/converter/MissionConverter.java b/src/main/java/umc/spring/converter/MissionConverter.java index 4adedd7..aa24961 100644 --- a/src/main/java/umc/spring/converter/MissionConverter.java +++ b/src/main/java/umc/spring/converter/MissionConverter.java @@ -1,12 +1,17 @@ package umc.spring.converter; +import org.springframework.data.domain.Page; import umc.spring.apiPayload.ApiResponse; import umc.spring.domain.Mission; +import umc.spring.domain.Store; import umc.spring.web.dto.MissionRequestDto; import umc.spring.web.dto.MissionResponseDto; +import umc.spring.web.dto.StoreResponseDto; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; public class MissionConverter { @@ -26,4 +31,25 @@ public static Mission toMission(MissionRequestDto.AddMissionToStoreDto request){ .deadline(request.getDeadline()) .build(); } + + public static StoreResponseDto.MissionPreviewDto missionPreviewDto(Mission mission){ + return StoreResponseDto.MissionPreviewDto.builder() + .description(mission.getDescription()) + .point(mission.getPoint()) + .deadline(mission.getDeadline()) + .build(); + } + + public static StoreResponseDto.MissionPreviewListDto missionPreviewListDto(Page missionList){ + List missionPreviewList = missionList.stream() + .map(MissionConverter::missionPreviewDto).collect(Collectors.toList()); + return StoreResponseDto.MissionPreviewListDto.builder() + .isLast(missionList.isLast()) + .isFirst(missionList.isFirst()) + .totalPage(missionList.getTotalPages()) + .totalElements(missionList.getTotalElements()) + .listSize(missionPreviewList.size()) + .missionList(missionPreviewList) + .build(); + } } diff --git a/src/main/java/umc/spring/repository/MissionRepository.java b/src/main/java/umc/spring/repository/MissionRepository.java index 51b0c2e..6380aa9 100644 --- a/src/main/java/umc/spring/repository/MissionRepository.java +++ b/src/main/java/umc/spring/repository/MissionRepository.java @@ -1,7 +1,11 @@ package umc.spring.repository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; import umc.spring.domain.Mission; +import umc.spring.domain.Store; public interface MissionRepository extends JpaRepository { + Page findAllByStore(Store store, PageRequest pageRequest); } diff --git a/src/main/java/umc/spring/service/storeService/StoreCommandService.java b/src/main/java/umc/spring/service/storeService/StoreCommandService.java index ca544cd..0e1a13a 100644 --- a/src/main/java/umc/spring/service/storeService/StoreCommandService.java +++ b/src/main/java/umc/spring/service/storeService/StoreCommandService.java @@ -5,4 +5,5 @@ public interface StoreCommandService { public Store addStore(StoreRequestDto.AddStoreToRegionDto request); + } diff --git a/src/main/java/umc/spring/service/storeService/StoreQueryService.java b/src/main/java/umc/spring/service/storeService/StoreQueryService.java index e6cc53a..2e8ce03 100644 --- a/src/main/java/umc/spring/service/storeService/StoreQueryService.java +++ b/src/main/java/umc/spring/service/storeService/StoreQueryService.java @@ -1,8 +1,11 @@ package umc.spring.service.storeService; import org.springframework.data.domain.Page; +import umc.spring.domain.Mission; import umc.spring.domain.Review; public interface StoreQueryService { Page getReviewList(Long storeId, Integer page); + Page getMissionList(Long storeId, Integer page); + Boolean existById(Long storeId); } diff --git a/src/main/java/umc/spring/service/storeService/StoreQueryServiceImpl.java b/src/main/java/umc/spring/service/storeService/StoreQueryServiceImpl.java index 7c91156..b54fdd3 100644 --- a/src/main/java/umc/spring/service/storeService/StoreQueryServiceImpl.java +++ b/src/main/java/umc/spring/service/storeService/StoreQueryServiceImpl.java @@ -5,8 +5,12 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; +import umc.spring.apiPayload.code.ErrorStatus; +import umc.spring.apiPayload.exception.handler.StoreHandler; +import umc.spring.domain.Mission; import umc.spring.domain.Review; import umc.spring.domain.Store; +import umc.spring.repository.MissionRepository; import umc.spring.repository.ReviewRepository; import umc.spring.repository.storeRepository.StoreRepository; @@ -17,6 +21,7 @@ public class StoreQueryServiceImpl implements StoreQueryService{ private final StoreRepository storeRepository; private final ReviewRepository reviewRepository; + private final MissionRepository missionRepository; @Override public Page getReviewList(Long storeId, Integer page){ @@ -28,4 +33,19 @@ public Page getReviewList(Long storeId, Integer page){ } return storePage; } + @Override + public Page getMissionList(Long storeId, Integer page){ + Store store = storeRepository.findById(storeId).orElseThrow( + () -> new StoreHandler(ErrorStatus.STORE_NOT_FOUND) + ); + + Page missionPage = missionRepository.findAllByStore(store, PageRequest.of(page, 10)); + return missionPage; + } + + @Override + public Boolean existById(Long storeId){ + return storeRepository.existsById(storeId); + } + } diff --git a/src/main/java/umc/spring/validation/annotation/ExistStore.java b/src/main/java/umc/spring/validation/annotation/ExistStore.java new file mode 100644 index 0000000..23300e5 --- /dev/null +++ b/src/main/java/umc/spring/validation/annotation/ExistStore.java @@ -0,0 +1,18 @@ +package umc.spring.validation.annotation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import umc.spring.validation.validator.StoreExistValidator; +import umc.spring.validation.validator.StoreExistValidator; + +import java.lang.annotation.*; +@Documented +@Constraint(validatedBy = StoreExistValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistStore { + + String message() default "해당하는 가게가 존재하지 않습니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} \ No newline at end of file diff --git a/src/main/java/umc/spring/validation/validator/StoreExistValidator.java b/src/main/java/umc/spring/validation/validator/StoreExistValidator.java new file mode 100644 index 0000000..65cf57b --- /dev/null +++ b/src/main/java/umc/spring/validation/validator/StoreExistValidator.java @@ -0,0 +1,38 @@ +package umc.spring.validation.validator; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import umc.spring.repository.storeRepository.StoreRepository; +import umc.spring.service.storeService.StoreCommandService; +import umc.spring.service.storeService.StoreQueryService; +import umc.spring.validation.annotation.ExistStore; +import umc.spring.apiPayload.code.ErrorStatus; +import umc.spring.service.storeService.StoreCommandService; +import umc.spring.validation.annotation.ExistStore; + +@Component +@RequiredArgsConstructor +public class StoreExistValidator implements ConstraintValidator { + + private final StoreQueryService storeQueryService; + + @Override + public void initialize(ExistStore constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(Long storeId, ConstraintValidatorContext context) { + + boolean isValid = storeQueryService.existById(storeId); + + if (!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.STORE_NOT_FOUND.toString()).addConstraintViolation(); + } + + return isValid; + } +} diff --git a/src/main/java/umc/spring/web/controller/StoreRestController.java b/src/main/java/umc/spring/web/controller/StoreRestController.java index c47199b..5d345c9 100644 --- a/src/main/java/umc/spring/web/controller/StoreRestController.java +++ b/src/main/java/umc/spring/web/controller/StoreRestController.java @@ -9,19 +9,25 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import umc.spring.apiPayload.ApiResponse; import umc.spring.converter.MemberConverter; +import umc.spring.converter.MissionConverter; import umc.spring.converter.ReviewConverter; import umc.spring.converter.StoreConverter; import umc.spring.domain.Member; +import umc.spring.domain.Mission; import umc.spring.domain.Review; import umc.spring.domain.Store; import umc.spring.service.storeService.StoreCommandService; import umc.spring.service.storeService.StoreQueryService; +import umc.spring.validation.annotation.ExistStore; +import umc.spring.validation.annotation.ValidPage; import umc.spring.web.dto.*; @RestController +@Validated @RequestMapping("/stores") @RequiredArgsConstructor public class StoreRestController { @@ -53,4 +59,21 @@ public ApiResponse getReviewList( Page reviewList = storeQueryService.getReviewList(storeId, page); return ApiResponse.onSuccess(ReviewConverter.reviewPreviewListDto(reviewList)); } + + @GetMapping("{storeId}/missions") + @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 = "AUTH003", description = "access 토큰을 주세요", content = @Content(schema = @Schema(implementation = ApiResponse.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "AUTH004", description = "access 토큰 만료", content = @Content(schema = @Schema(implementation = ApiResponse.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "STORE4001", description = "가게가 존재하지 않습니다", content = @Content(schema = @Schema(implementation = ApiResponse.class))) + }) + @Parameters({ + @Parameter(name = "storeId", description = "가게의 아이디, path variable") + }) + public ApiResponse getMissionList( + @ExistStore @PathVariable(name = "storeId") Long storeId, @ValidPage @RequestParam(name = "page") Integer page){ + Page missionList = storeQueryService.getMissionList(storeId, page-1); + return ApiResponse.onSuccess(MissionConverter.missionPreviewListDto(missionList)); + } } diff --git a/src/main/java/umc/spring/web/dto/StoreResponseDto.java b/src/main/java/umc/spring/web/dto/StoreResponseDto.java index a8c829f..6778eb8 100644 --- a/src/main/java/umc/spring/web/dto/StoreResponseDto.java +++ b/src/main/java/umc/spring/web/dto/StoreResponseDto.java @@ -38,4 +38,25 @@ public static class ReviewPreviewDto{ String content; LocalDate createdAt; } + + @Builder + @Getter + @AllArgsConstructor + public static class MissionPreviewListDto{ + List missionList; + Integer listSize; + Integer totalPage; + Long totalElements; + Boolean isFirst; + Boolean isLast; + } + + @Builder + @Getter + @AllArgsConstructor + public static class MissionPreviewDto{ + String description; + Integer point; + LocalDate deadline; + } } From b61130228a5a007f438be02ecc31f2d9fa301086 Mon Sep 17 00:00:00 2001 From: sammool Date: Mon, 26 May 2025 02:15:38 +0900 Subject: [PATCH 12/16] =?UTF-8?q?[mission/#9]=20=EC=A7=84=ED=96=89?= =?UTF-8?q?=EC=A4=91=EC=9D=B8=20=EB=AF=B8=EC=85=98=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/MemberMissionConverter.java | 30 +++++++++++++++++++ .../repository/MemberMissionRepository.java | 5 ++++ .../MemberMissionQueryService.java | 9 ++++++ .../MemberMissionQueryServiceImpl.java | 24 +++++++++++++++ .../web/controller/MemberRestController.java | 27 ++++++++++++++++- .../umc/spring/web/dto/MemberResponseDto.java | 23 ++++++++++++++ 6 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/main/java/umc/spring/service/memberMissionService/MemberMissionQueryService.java create mode 100644 src/main/java/umc/spring/service/memberMissionService/MemberMissionQueryServiceImpl.java diff --git a/src/main/java/umc/spring/converter/MemberMissionConverter.java b/src/main/java/umc/spring/converter/MemberMissionConverter.java index c4ea8b3..c645cf6 100644 --- a/src/main/java/umc/spring/converter/MemberMissionConverter.java +++ b/src/main/java/umc/spring/converter/MemberMissionConverter.java @@ -1,11 +1,16 @@ package umc.spring.converter; +import org.springframework.data.domain.Page; +import umc.spring.domain.Mission; import umc.spring.domain.enums.MissionStatus; import umc.spring.domain.mapping.MemberMission; +import umc.spring.web.dto.MemberResponseDto; import umc.spring.web.dto.MissionRequestDto; import umc.spring.web.dto.MissionResponseDto; import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; public class MemberMissionConverter { @@ -23,4 +28,29 @@ public static MemberMission toMemberMission(MissionRequestDto.ChallengeMissionDt .status(MissionStatus.ONGOING) .build(); } + + public static MemberResponseDto.MissionPreviewDto missionPreviewDto(MemberMission memberMission){ + Mission mission = memberMission.getMission(); + + return MemberResponseDto.MissionPreviewDto.builder() + .description(mission.getDescription()) + .point(mission.getPoint()) + .deadline(mission.getDeadline()) + .status(memberMission.getStatus()) + .build(); + } + + public static MemberResponseDto.MissionPreviewListDto missionPreviewListDto(Page missionList){ + List missionPreviewList = missionList.stream() + .map(MemberMissionConverter::missionPreviewDto).collect(Collectors.toList()); + + return MemberResponseDto.MissionPreviewListDto.builder() + .isFirst(missionList.isFirst()) + .isLast(missionList.isLast()) + .totalPage(missionList.getTotalPages()) + .totalElements(missionList.getTotalElements()) + .listSize(missionPreviewList.size()) + .missionList(missionPreviewList) + .build(); + } } diff --git a/src/main/java/umc/spring/repository/MemberMissionRepository.java b/src/main/java/umc/spring/repository/MemberMissionRepository.java index 214bf54..725a720 100644 --- a/src/main/java/umc/spring/repository/MemberMissionRepository.java +++ b/src/main/java/umc/spring/repository/MemberMissionRepository.java @@ -1,8 +1,13 @@ package umc.spring.repository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; +import umc.spring.domain.Member; +import umc.spring.domain.enums.MissionStatus; import umc.spring.domain.mapping.MemberMission; public interface MemberMissionRepository extends JpaRepository { boolean existsByMemberIdAndMissionId(Long memberId, Long MissionId); + Page findByMemberAndStatus (Member member, MissionStatus status, PageRequest pageRequest); } diff --git a/src/main/java/umc/spring/service/memberMissionService/MemberMissionQueryService.java b/src/main/java/umc/spring/service/memberMissionService/MemberMissionQueryService.java new file mode 100644 index 0000000..4fd14ae --- /dev/null +++ b/src/main/java/umc/spring/service/memberMissionService/MemberMissionQueryService.java @@ -0,0 +1,9 @@ +package umc.spring.service.memberMissionService; + +import org.springframework.data.domain.Page; +import umc.spring.domain.enums.MissionStatus; +import umc.spring.domain.mapping.MemberMission; + +public interface MemberMissionQueryService { + Page getMissionPage(Long memberId, MissionStatus status, Integer page); +} diff --git a/src/main/java/umc/spring/service/memberMissionService/MemberMissionQueryServiceImpl.java b/src/main/java/umc/spring/service/memberMissionService/MemberMissionQueryServiceImpl.java new file mode 100644 index 0000000..f747c92 --- /dev/null +++ b/src/main/java/umc/spring/service/memberMissionService/MemberMissionQueryServiceImpl.java @@ -0,0 +1,24 @@ +package umc.spring.service.memberMissionService; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import umc.spring.apiPayload.code.ErrorStatus; +import umc.spring.apiPayload.exception.handler.MemberHandler; +import umc.spring.domain.Member; +import umc.spring.domain.enums.MissionStatus; +import umc.spring.domain.mapping.MemberMission; +import umc.spring.repository.MemberMissionRepository; +import umc.spring.repository.MemberRepository; + +@Service +@RequiredArgsConstructor +public class MemberMissionQueryServiceImpl implements MemberMissionQueryService{ + private final MemberRepository memberRepository; + private final MemberMissionRepository memberMissionRepository; + public Page getMissionPage(Long memberId, MissionStatus status, Integer page){ + Member member = memberRepository.findById(memberId).orElseThrow(()->new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); + return memberMissionRepository.findByMemberAndStatus(member,status, PageRequest.of(page,10)); + } +} diff --git a/src/main/java/umc/spring/web/controller/MemberRestController.java b/src/main/java/umc/spring/web/controller/MemberRestController.java index 22a4960..005b025 100644 --- a/src/main/java/umc/spring/web/controller/MemberRestController.java +++ b/src/main/java/umc/spring/web/controller/MemberRestController.java @@ -3,6 +3,7 @@ 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.enums.ParameterIn; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -14,8 +15,12 @@ import umc.spring.apiPayload.ApiResponse; import umc.spring.apiPayload.code.ErrorStatus; import umc.spring.converter.MemberConverter; +import umc.spring.converter.MemberMissionConverter; import umc.spring.domain.Member; import umc.spring.domain.Review; +import umc.spring.domain.enums.MissionStatus; +import umc.spring.domain.mapping.MemberMission; +import umc.spring.service.memberMissionService.MemberMissionQueryService; import umc.spring.service.memberService.MemberCommandService; import umc.spring.service.memberService.MemberQueryService; import umc.spring.validation.annotation.ValidPage; @@ -29,6 +34,7 @@ public class MemberRestController { private final MemberCommandService memberCommandService; private final MemberQueryService memberQueryService; + private final MemberMissionQueryService memberMissionQueryService; @PostMapping("/") public ApiResponse join(@RequestBody @Valid MemberRequestDto.JoinDto request){ @@ -49,8 +55,27 @@ public ApiResponse join(@RequestBody @Valid Mem }) public ApiResponse getReviewList(@PathVariable(name = "memberId") Long memberId, @ValidPage @RequestParam(name = "page") Integer page){ - Page reviewList = memberQueryService.getReviewList(memberId, --page); + Page reviewList = memberQueryService.getReviewList(memberId, page-1); return ApiResponse.onSuccess(MemberConverter.reviewPreviewListDto(reviewList)); } + @GetMapping("{memberId}/missions")@Operation(summary = "내가 진행중인 미션 목록 조회 API", description = "특정 유저의 진행중인 미션 목록을 조회하는 API이며, 페이징을 포함합니다. query String 으로 page 번호와 미션 status를 주세요") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "AUTH003", description = "access 토큰을 주세요", content = @Content(schema = @Schema(implementation = ApiResponse.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "AUTH004", description = "access 토큰 만료", content = @Content(schema = @Schema(implementation = ApiResponse.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "MEMBER4001", description = "사용자가 존재하지 않습니다", content = @Content(schema = @Schema(implementation = ApiResponse.class))) + }) + @Parameters({ + @Parameter(name = "memberId", description = "멤버의 아이디, path variable", in = ParameterIn.PATH), + @Parameter(name="page", description = "페이지 번호(1부터 시작)", in = ParameterIn.QUERY), + @Parameter(name="status", description = "미션 상태", in = ParameterIn.QUERY) + }) + public ApiResponse getMissionList( + @PathVariable(name = "memberId") Long memberId, @ValidPage @RequestParam(name = "page") Integer page + , @RequestParam(name = "status") MissionStatus status){ + + Page memberMissionList = memberMissionQueryService.getMissionPage(memberId, status, page-1); + return ApiResponse.onSuccess(MemberMissionConverter.missionPreviewListDto(memberMissionList)); + } } diff --git a/src/main/java/umc/spring/web/dto/MemberResponseDto.java b/src/main/java/umc/spring/web/dto/MemberResponseDto.java index 8bf5b07..c8cdae3 100644 --- a/src/main/java/umc/spring/web/dto/MemberResponseDto.java +++ b/src/main/java/umc/spring/web/dto/MemberResponseDto.java @@ -4,6 +4,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import umc.spring.domain.enums.MissionStatus; import java.time.LocalDate; import java.time.LocalDateTime; @@ -41,4 +42,26 @@ public static class ReviewPreviewDto{ LocalDate createdAt; } + @Builder + @Getter + @AllArgsConstructor + public static class MissionPreviewListDto{ + List missionList; + Integer listSize; + Integer totalPage; + Long totalElements; + Boolean isFirst; + Boolean isLast; + } + + @Builder + @Getter + @AllArgsConstructor + public static class MissionPreviewDto{ + String description; + MissionStatus status; + Integer point; + LocalDate deadline; + } + } From 5d6dd7d2d2ad0356610fa15063828ebcaa111b8d Mon Sep 17 00:00:00 2001 From: sammool Date: Mon, 26 May 2025 16:14:00 +0900 Subject: [PATCH 13/16] =?UTF-8?q?[mission/#9]=20=EC=A7=84=ED=96=89=20?= =?UTF-8?q?=EC=A4=91=EC=9D=B8=20=EB=AF=B8=EC=85=98=20=EC=A7=84=ED=96=89=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=EB=A1=9C=20=EB=B0=94=EA=BE=B8=EA=B8=B0=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring/apiPayload/code/ErrorStatus.java | 2 ++ .../handler/MemberMissionHandler.java | 11 ++++++ .../converter/MemberMissionConverter.java | 11 ++++++ .../repository/MemberMissionRepository.java | 3 ++ .../MemberMissionCommandService.java | 1 + .../MemberMissionCommandServiceImpl.java | 16 +++++++++ .../web/controller/MissionRestController.java | 34 ++++++++++++++++--- .../umc/spring/web/dto/MissionRequestDto.java | 8 +++++ .../spring/web/dto/MissionResponseDto.java | 11 ++++++ 9 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 src/main/java/umc/spring/apiPayload/exception/handler/MemberMissionHandler.java diff --git a/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java b/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java index db5953c..31591a5 100644 --- a/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java +++ b/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java @@ -22,6 +22,8 @@ public enum ErrorStatus implements BaseErrorCode { //Mission 관련 에러 MISSION_NOT_FOUND(HttpStatus.BAD_REQUEST, "MISSION4001", "해당 미션이 존재하지 않습니다"), MISSION_ALREADY_CHALLENGED(HttpStatus.BAD_REQUEST, "MISSION4002", "해당 미션은 이미 진행중인 미션입니다."), + //Member_mission관련 에러 + MEMBER_MISSION_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBERMISSION4001", "해당 유저의 해당 미션이 존재하지 않습니다."), //Store 관련 에러 STORE_NOT_FOUND(HttpStatus.BAD_REQUEST, "STORE4001", "가게가 존재하지 않습니다."), //Region 관련 에러 diff --git a/src/main/java/umc/spring/apiPayload/exception/handler/MemberMissionHandler.java b/src/main/java/umc/spring/apiPayload/exception/handler/MemberMissionHandler.java new file mode 100644 index 0000000..34b41d7 --- /dev/null +++ b/src/main/java/umc/spring/apiPayload/exception/handler/MemberMissionHandler.java @@ -0,0 +1,11 @@ +package umc.spring.apiPayload.exception.handler; + +import umc.spring.apiPayload.BaseErrorCode; +import umc.spring.apiPayload.exception.GeneralException; + +public class MemberMissionHandler extends GeneralException { + + public MemberMissionHandler(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/umc/spring/converter/MemberMissionConverter.java b/src/main/java/umc/spring/converter/MemberMissionConverter.java index c645cf6..7ba1eea 100644 --- a/src/main/java/umc/spring/converter/MemberMissionConverter.java +++ b/src/main/java/umc/spring/converter/MemberMissionConverter.java @@ -29,6 +29,7 @@ public static MemberMission toMemberMission(MissionRequestDto.ChallengeMissionDt .build(); } + public static MemberResponseDto.MissionPreviewDto missionPreviewDto(MemberMission memberMission){ Mission mission = memberMission.getMission(); @@ -53,4 +54,14 @@ public static MemberResponseDto.MissionPreviewListDto missionPreviewListDto(Page .missionList(missionPreviewList) .build(); } + + public static MissionResponseDto.CompleteMissionResultDto completeMissionDto(MemberMission memberMission){ + return MissionResponseDto.CompleteMissionResultDto.builder() + .storeName(memberMission.getMission().getStore().getName()) + .description(memberMission.getMission().getDescription()) + .completedAt(memberMission.getCompletedAt()) + .point(memberMission.getMission().getPoint()) + .status(memberMission.getStatus()) + .build(); + } } diff --git a/src/main/java/umc/spring/repository/MemberMissionRepository.java b/src/main/java/umc/spring/repository/MemberMissionRepository.java index 725a720..5bbfa8a 100644 --- a/src/main/java/umc/spring/repository/MemberMissionRepository.java +++ b/src/main/java/umc/spring/repository/MemberMissionRepository.java @@ -7,7 +7,10 @@ import umc.spring.domain.enums.MissionStatus; import umc.spring.domain.mapping.MemberMission; +import java.util.Optional; + public interface MemberMissionRepository extends JpaRepository { boolean existsByMemberIdAndMissionId(Long memberId, Long MissionId); Page findByMemberAndStatus (Member member, MissionStatus status, PageRequest pageRequest); + Optional findByMissionIdAndMemberId(Long memberId, Long missionId); } diff --git a/src/main/java/umc/spring/service/memberMissionService/MemberMissionCommandService.java b/src/main/java/umc/spring/service/memberMissionService/MemberMissionCommandService.java index 6ebf00b..12c748e 100644 --- a/src/main/java/umc/spring/service/memberMissionService/MemberMissionCommandService.java +++ b/src/main/java/umc/spring/service/memberMissionService/MemberMissionCommandService.java @@ -7,4 +7,5 @@ public interface MemberMissionCommandService { MemberMission challengeMission(MissionRequestDto.ChallengeMissionDto request); boolean existsByMemberIdAndMissionId(Long memberId, Long missionId); + MemberMission setMissionComplete(Long missionId, Long memberId); } diff --git a/src/main/java/umc/spring/service/memberMissionService/MemberMissionCommandServiceImpl.java b/src/main/java/umc/spring/service/memberMissionService/MemberMissionCommandServiceImpl.java index 87143ba..f0e059f 100644 --- a/src/main/java/umc/spring/service/memberMissionService/MemberMissionCommandServiceImpl.java +++ b/src/main/java/umc/spring/service/memberMissionService/MemberMissionCommandServiceImpl.java @@ -2,18 +2,24 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import umc.spring.apiPayload.code.ErrorStatus; import umc.spring.apiPayload.exception.handler.MemberHandler; +import umc.spring.apiPayload.exception.handler.MemberMissionHandler; import umc.spring.apiPayload.exception.handler.MissionHandler; import umc.spring.converter.MemberMissionConverter; import umc.spring.domain.Member; import umc.spring.domain.Mission; +import umc.spring.domain.enums.MissionStatus; import umc.spring.domain.mapping.MemberMission; import umc.spring.repository.MemberMissionRepository; import umc.spring.repository.MemberRepository; import umc.spring.repository.MissionRepository; import umc.spring.web.dto.MissionRequestDto; +import java.time.LocalDateTime; +import java.util.UUID; + @Service @RequiredArgsConstructor public class MemberMissionCommandServiceImpl implements MemberMissionCommandService{ @@ -40,4 +46,14 @@ public boolean existsByMemberIdAndMissionId(Long memberId, Long missionId){ return memberMissionRepository.existsByMemberIdAndMissionId(memberId, missionId); } + @Override + @Transactional + public MemberMission setMissionComplete(Long missionId, Long memberId) { + MemberMission memberMission = memberMissionRepository.findByMissionIdAndMemberId(missionId, memberId).orElseThrow(()->new MemberMissionHandler(ErrorStatus.MEMBER_MISSION_NOT_FOUND)); + memberMission.setStatus(MissionStatus.COMPLETE); + memberMission.setCompletedAt(LocalDateTime.now()); + memberMission.setCertificationNumber(UUID.randomUUID().toString()); + return memberMission; + } + } diff --git a/src/main/java/umc/spring/web/controller/MissionRestController.java b/src/main/java/umc/spring/web/controller/MissionRestController.java index e37d1f2..878ec20 100644 --- a/src/main/java/umc/spring/web/controller/MissionRestController.java +++ b/src/main/java/umc/spring/web/controller/MissionRestController.java @@ -1,18 +1,26 @@ package umc.spring.web.controller; +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.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.*; import umc.spring.apiPayload.ApiResponse; import umc.spring.converter.MemberMissionConverter; import umc.spring.converter.MissionConverter; import umc.spring.domain.Mission; +import umc.spring.domain.enums.MissionStatus; import umc.spring.domain.mapping.MemberMission; import umc.spring.service.memberMissionService.MemberMissionCommandService; import umc.spring.service.missionService.MissionCommandService; +import umc.spring.validation.annotation.ValidPage; +import umc.spring.web.dto.MemberResponseDto; import umc.spring.web.dto.MissionRequestDto; import umc.spring.web.dto.MissionResponseDto; @@ -36,4 +44,22 @@ public ApiResponse addMission(@Re MemberMission memberMission = memberMissionCommandService.challengeMission(request); return ApiResponse.onSuccess(MemberMissionConverter.toChallengeMissionResultDto(memberMission)); } + + @PatchMapping("/complete")@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 = "AUTH003", description = "access 토큰을 주세요", content = @Content(schema = @Schema(implementation = ApiResponse.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "AUTH004", description = "access 토큰 만료", content = @Content(schema = @Schema(implementation = ApiResponse.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "MISSION4001", description = "해당 미션이 존재하지 않습니다", content = @Content(schema = @Schema(implementation = ApiResponse.class))) + }) + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "미션 완료 요청 바디", + required = true, + content = @Content(schema = @Schema(implementation = MissionRequestDto.CompleteMissionDto.class)) + ) + public ApiResponse setMissionComplete(@RequestBody @Valid MissionRequestDto.CompleteMissionDto request){ + + MemberMission memberMission = memberMissionCommandService.setMissionComplete(request.getMissionId(), request.getMemberId()); + return ApiResponse.onSuccess(MemberMissionConverter.completeMissionDto(memberMission)); + } } diff --git a/src/main/java/umc/spring/web/dto/MissionRequestDto.java b/src/main/java/umc/spring/web/dto/MissionRequestDto.java index 595c0ae..ebbd0cc 100644 --- a/src/main/java/umc/spring/web/dto/MissionRequestDto.java +++ b/src/main/java/umc/spring/web/dto/MissionRequestDto.java @@ -30,4 +30,12 @@ public static class ChallengeMissionDto{ @NotNull Long memberId; } + + @Getter + public static class CompleteMissionDto{ + @NotNull + Long memberId; + @NotNull + Long missionId; + } } diff --git a/src/main/java/umc/spring/web/dto/MissionResponseDto.java b/src/main/java/umc/spring/web/dto/MissionResponseDto.java index 3cbfc9f..f7045c3 100644 --- a/src/main/java/umc/spring/web/dto/MissionResponseDto.java +++ b/src/main/java/umc/spring/web/dto/MissionResponseDto.java @@ -27,4 +27,15 @@ public static class ChallengeMissionResultDto{ MissionStatus status; LocalDateTime createdAt; } + + @AllArgsConstructor + @Getter + @Builder + public static class CompleteMissionResultDto{ + MissionStatus status; + String storeName; + String description; + Integer point; + LocalDateTime completedAt; + } } From 0f0275598a6ca26bb547b3031cfb2824c5b0e186 Mon Sep 17 00:00:00 2001 From: sammool Date: Tue, 17 Jun 2025 02:21:26 +0900 Subject: [PATCH 14/16] =?UTF-8?q?=EC=8B=A4=EC=8A=B51?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/CustomUserDetailService.java | 27 +++++++ .../config/security/SecurityConfig.java | 47 +++++++++++++ .../web/controller/MemberViewController.java | 61 ++++++++++++++++ .../umc/spring/web/dto/MemberRequestDto.java | 50 +++++++++++++ src/main/resources/application.yml | 6 ++ src/main/resources/templates/admin.html | 10 +++ src/main/resources/templates/home.html | 20 ++++++ src/main/resources/templates/login.html | 26 +++++++ src/main/resources/templates/signup.html | 70 +++++++++++++++++++ 9 files changed, 317 insertions(+) create mode 100644 src/main/java/umc/spring/config/security/CustomUserDetailService.java create mode 100644 src/main/java/umc/spring/config/security/SecurityConfig.java create mode 100644 src/main/java/umc/spring/web/controller/MemberViewController.java create mode 100644 src/main/java/umc/spring/web/dto/MemberRequestDto.java create mode 100644 src/main/resources/templates/admin.html create mode 100644 src/main/resources/templates/home.html create mode 100644 src/main/resources/templates/login.html create mode 100644 src/main/resources/templates/signup.html diff --git a/src/main/java/umc/spring/config/security/CustomUserDetailService.java b/src/main/java/umc/spring/config/security/CustomUserDetailService.java new file mode 100644 index 0000000..4905c21 --- /dev/null +++ b/src/main/java/umc/spring/config/security/CustomUserDetailService.java @@ -0,0 +1,27 @@ +package umc.spring.config.security; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.User; +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; +import umc.spring.domain.Member; +import umc.spring.repository.MemberRepository; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailService implements UserDetailsService { + private final MemberRepository memberRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{ + Member member = memberRepository.findByEmail(username).orElseThrow(() -> new UsernameNotFoundException("해당 이메일을 가진 유저가 존재하지 않습니다.")); + + return User + .withUsername(member.getName()) + .password(member.getPassword()) + .roles(member.getRole().name()) + .build(); + } +} diff --git a/src/main/java/umc/spring/config/security/SecurityConfig.java b/src/main/java/umc/spring/config/security/SecurityConfig.java new file mode 100644 index 0000000..aae9da8 --- /dev/null +++ b/src/main/java/umc/spring/config/security/SecurityConfig.java @@ -0,0 +1,47 @@ +package umc.spring.config.security; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +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.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import umc.spring.config.security.jwt.JwtAuthenticationFilter; +import umc.spring.config.security.jwt.JwtTokenProvider; + +@EnableWebSecurity +@Configuration +@RequiredArgsConstructor +public class SecurityConfig { + private final JwtTokenProvider jwtTokenProvider; + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests((requests) -> requests + .requestMatchers("/", "/home", "/signup","/members/signup", "/css/**").permitAll() + .requestMatchers("/admin/**").hasRole("ADMIN") + .anyRequest().authenticated() + ) + .formLogin((form) -> form + .loginPage("/login") + .defaultSuccessUrl("/home", true) + .permitAll() + ) + .logout((logout) -> logout + .logoutUrl("/logout") + .logoutSuccessUrl("/login?logout") + .permitAll() + ); + + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/umc/spring/web/controller/MemberViewController.java b/src/main/java/umc/spring/web/controller/MemberViewController.java new file mode 100644 index 0000000..62936c6 --- /dev/null +++ b/src/main/java/umc/spring/web/controller/MemberViewController.java @@ -0,0 +1,61 @@ +package umc.spring.web.controller; + + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import umc.spring.service.memberService.MemberCommandService; +import umc.spring.web.dto.MemberRequestDto; +; + +@Controller +@RequiredArgsConstructor + +public class MemberViewController { + + private final MemberCommandService memberCommandService; + + // thymeleaf 사용을 위해 일부가 변경되었습니다. + // 실제로는 8주차에서 작성한 컨트롤러와 동일하게 작성하시면 됩니다!! + @GetMapping("/signup") + public String signupPage(Model model) { + model.addAttribute("memberJoinDto", new MemberRequestDto.JoinDto()); + return "signup"; + } + @GetMapping("/login") + public String loginPage() { + return "login"; + } + + @PostMapping("/members/signup") + public String joinMember(@ModelAttribute("memberJoinDto") MemberRequestDto.JoinDto request, // 협업시에는 기존 RequestBody 어노테이션을 붙여주시면 됩니다! + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + // 뷰에 데이터 바인딩이 실패할 경우 signup 페이지를 유지합니다. + return "signup"; + } + + try { + memberCommandService.joinMember(request); + return "redirect:/login"; + } catch (Exception e) { + // 회원가입 과정에서 에러가 발생할 경우 에러 메시지를 보내고, signup 페이디를 유지합니다. + model.addAttribute("error", e.getMessage()); + return "signup"; + } + } + @GetMapping("/home") + public String home() { + return "home"; + } + + @GetMapping("/admin") + public String admin() { + return "admin"; + } +} diff --git a/src/main/java/umc/spring/web/dto/MemberRequestDto.java b/src/main/java/umc/spring/web/dto/MemberRequestDto.java new file mode 100644 index 0000000..c145d06 --- /dev/null +++ b/src/main/java/umc/spring/web/dto/MemberRequestDto.java @@ -0,0 +1,50 @@ +package umc.spring.web.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; +import umc.spring.domain.enums.Gender; +import umc.spring.domain.enums.Role; +import umc.spring.validation.annotation.ExistCategories; + +import java.time.LocalDate; +import java.util.List; + +public class MemberRequestDto { + @Getter + @Setter + public static class JoinDto{ + @NotBlank + String name; + @NotNull + Integer gender; + @NotNull + LocalDate birth; + @Size(min = 5, max = 12) + String address; + @Email + String email; + @NotBlank + String password; + @Size(max = 15) + String phoneNumber; + @ExistCategories + List preferCategory; + @NotNull + Role role; + } + + @Getter + @Setter + public static class LoginRequestDto{ + @NotBlank(message = "이메일은 필수입니다.") + @Email(message = "올바른 이메일 형식이어야 합니다.") + private String email; + + @NotBlank(message = "패스워드는 필수입니다.") + private String password; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9635924..f6af816 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -20,3 +20,9 @@ spring: use_sql_comments: true hbm2ddl.auto: update default_batch_fetch_size: 1000 + +jwt: + token: + secretKey: umceightfightingjwttokenauthentication + expiration: + access: 14400000 \ No newline at end of file diff --git a/src/main/resources/templates/admin.html b/src/main/resources/templates/admin.html new file mode 100644 index 0000000..c7066e5 --- /dev/null +++ b/src/main/resources/templates/admin.html @@ -0,0 +1,10 @@ +Add commentMore actions + + + Admin Page + + +

Admin Page

+

관리자만 접근할 수 있는 페이지입니다.

+ + \ No newline at end of file diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html new file mode 100644 index 0000000..8c10cb1 --- /dev/null +++ b/src/main/resources/templates/home.html @@ -0,0 +1,20 @@ + + + + Home + + +

Welcome to Home Page!

+ +

+ + + + + +
+ +
+ \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..7804a3a --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,26 @@ + + + + Login + + +

Login

+
+
+ + +
+
+ + +
+ +
+ +

사용자 이름 또는 비밀번호가 잘못되었습니다.

+

로그아웃되었습니다.

+ + +

계정이 없나요? Sign up

+ + \ No newline at end of file diff --git a/src/main/resources/templates/signup.html b/src/main/resources/templates/signup.html new file mode 100644 index 0000000..856134b --- /dev/null +++ b/src/main/resources/templates/signup.html @@ -0,0 +1,70 @@ + + + + 회원가입 + + +

회원가입

+
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + + +
+
+ +
+ + +
+ + +
+ + From 48cea2a3418093c04113744ddb367c6e2db3246c Mon Sep 17 00:00:00 2001 From: sammool Date: Tue, 17 Jun 2025 02:23:20 +0900 Subject: [PATCH 15/16] =?UTF-8?q?=EC=8B=A4=EC=8A=B52?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/umc/spring/Application.java | 24 +++--- .../spring/apiPayload/code/ErrorStatus.java | 4 +- .../apiPayload/exception/ExceptionAdvice.java | 52 ++++++------ .../java/umc/spring/config/SwaggerConfig.java | 36 ++++++++ .../spring/config/properties/Constants.java | 6 ++ .../config/properties/JwtProperties.java | 22 +++++ .../config/security/SecurityConfig.java | 22 ++--- .../security/jwt/JwtAuthenticationFilter.java | 43 ++++++++++ .../config/security/jwt/JwtTokenProvider.java | 84 +++++++++++++++++++ .../umc/spring/converter/MemberConverter.java | 27 ++++-- .../spring/converter/MemberFoodConverter.java | 18 ++++ .../umc/spring/converter/ReviewConverter.java | 2 + src/main/java/umc/spring/domain/Member.java | 21 ++++- src/main/java/umc/spring/domain/Region.java | 5 ++ .../java/umc/spring/domain/ReviewImage.java | 1 + src/main/java/umc/spring/domain/Store.java | 9 +- .../java/umc/spring/domain/enums/Gender.java | 2 +- .../java/umc/spring/domain/enums/Role.java | 5 ++ .../umc/spring/domain/mapping/MemberFood.java | 19 ++++- .../spring/domain/mapping/MemberMission.java | 1 + .../repository/FoodCategoryRepository.java | 8 ++ .../spring/repository/MemberRepository.java | 9 +- .../memberService/MemberCommandService.java | 2 + .../MemberCommandServiceImpl.java | 32 +++++++ .../memberService/MemberQueryService.java | 3 + .../memberService/MemberQueryServiceImpl.java | 15 ++++ .../missionService/MissionQueryService.java | 8 ++ .../MissionQueryServiceImpl.java | 21 +++++ .../reviewService/ReviewQueryService.java | 8 ++ .../reviewService/ReviewQueryServiceImpl.java | 20 +++++ .../web/controller/MemberRestController.java | 20 ++++- .../web/controller/ReviewRestController.java | 21 +++-- .../umc/spring/web/dto/MemberResponseDto.java | 17 ++++ 33 files changed, 508 insertions(+), 79 deletions(-) create mode 100644 src/main/java/umc/spring/config/SwaggerConfig.java create mode 100644 src/main/java/umc/spring/config/properties/Constants.java create mode 100644 src/main/java/umc/spring/config/properties/JwtProperties.java create mode 100644 src/main/java/umc/spring/config/security/jwt/JwtAuthenticationFilter.java create mode 100644 src/main/java/umc/spring/config/security/jwt/JwtTokenProvider.java create mode 100644 src/main/java/umc/spring/converter/MemberFoodConverter.java create mode 100644 src/main/java/umc/spring/domain/enums/Role.java create mode 100644 src/main/java/umc/spring/repository/FoodCategoryRepository.java create mode 100644 src/main/java/umc/spring/service/missionService/MissionQueryService.java create mode 100644 src/main/java/umc/spring/service/missionService/MissionQueryServiceImpl.java create mode 100644 src/main/java/umc/spring/service/reviewService/ReviewQueryService.java create mode 100644 src/main/java/umc/spring/service/reviewService/ReviewQueryServiceImpl.java diff --git a/src/main/java/umc/spring/Application.java b/src/main/java/umc/spring/Application.java index 9d0cd43..d947b93 100644 --- a/src/main/java/umc/spring/Application.java +++ b/src/main/java/umc/spring/Application.java @@ -6,7 +6,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; -import umc.spring.service.storeService.StoreQueryService; @SpringBootApplication @EnableJpaAuditing @@ -19,18 +18,17 @@ public static void main(String[] args) { @Bean public CommandLineRunner run(ApplicationContext context){ return args -> { - StoreQueryService storeService = context.getBean(StoreQueryService.class); - - String name = "요아정"; - Float score = 4.0f; - - System.out.println("Executing findStoresByNameAndScore with parameters:"); - System.out.println("Name: " + name); - System.out.println("Score: " + score); - - storeService.findStoreByNameAndScore(name,score) - .forEach(System.out::println); - +// MemberRepository memberRepository = context.getBean(MemberRepository.class); +// MemberMissionRepository memberMissionRepository = context.getBean(MemberMissionRepository.class); +// String regionName = "서울"; +// Long userId = 2L; +// +// System.out.println("Executing findStoresByNameAndScore with parameters:"); +// +// System.out.println(memberRepository.getMypageInfo(userId)); +// memberMissionRepository.findPossibleMissions(regionName, userId,10L,10).forEach(System.out::println); +// Integer i = memberMissionRepository.regionMissionCount(regionName, userId); +// System.out.println(i); }; } } diff --git a/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java b/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java index 31591a5..4a0f6ca 100644 --- a/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java +++ b/src/main/java/umc/spring/apiPayload/code/ErrorStatus.java @@ -30,7 +30,9 @@ public enum ErrorStatus implements BaseErrorCode { REGION_NOT_EXIST(HttpStatus.BAD_REQUEST, "REGION4001", "지역이 존재하지 않습니다."), TEMP_EXCEPTION(HttpStatus.BAD_REQUEST, "TEMP4001", "test exception"), //Page 관련 에러 - PAGE_NOT_EXIST(HttpStatus.BAD_REQUEST, "PAGE4001", "존재하지 않는 페이지입니다."); + PAGE_NOT_EXIST(HttpStatus.BAD_REQUEST, "PAGE4001", "존재하지 않는 페이지입니다."), + INVALID_TOKEN(HttpStatus.BAD_REQUEST, "TOKEN4001", "존재하지 않는 토큰입니다."), + INVALID_PASSWORD(HttpStatus.BAD_REQUEST, "TOKEN4001", "비밀번호가 일치하지 않습니다."); private final HttpStatus httpStatus; private final String code; private final String message; diff --git a/src/main/java/umc/spring/apiPayload/exception/ExceptionAdvice.java b/src/main/java/umc/spring/apiPayload/exception/ExceptionAdvice.java index 996830c..302d979 100644 --- a/src/main/java/umc/spring/apiPayload/exception/ExceptionAdvice.java +++ b/src/main/java/umc/spring/apiPayload/exception/ExceptionAdvice.java @@ -29,14 +29,14 @@ public class ExceptionAdvice extends ResponseEntityExceptionHandler { -// @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); -// } + @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, HttpStatusCode status, WebRequest request) { @@ -74,24 +74,24 @@ public ResponseEntity onThrowException(GeneralException generalException, HttpSe return handleExceptionInternal(generalException,errorReasonHttpStatus,null,request); } - @ExceptionHandler(value = ConstraintViolationException.class) - public ResponseEntity handleConstraintViolation(ConstraintViolationException ex, WebRequest request) { - String errorKey = ex.getConstraintViolations().stream() - .map(v -> v.getMessage()) - .findFirst() - .orElse("INVALID_REQUEST"); - - ErrorStatus status; - - switch (errorKey) { - case "STORE_NOT_FOUND" -> status = ErrorStatus.STORE_NOT_FOUND; - case "PAGE_NOT_EXIST" -> status = ErrorStatus.PAGE_NOT_EXIST; - default -> status = ErrorStatus._BAD_REQUEST; - } - - ApiResponse body = ApiResponse.onFailure(status.getCode(), status.getMessage(), null); - return ResponseEntity.status(status.getHttpStatus()).body(body); - } +// @ExceptionHandler(value = ConstraintViolationException.class) +// public ResponseEntity handleConstraintViolation(ConstraintViolationException ex, WebRequest request) { +// String errorKey = ex.getConstraintViolations().stream() +// .map(v -> v.getMessage()) +// .findFirst() +// .orElse("INVALID_REQUEST"); +// +// ErrorStatus status; +// +// switch (errorKey) { +// case "STORE_NOT_FOUND" -> status = ErrorStatus.STORE_NOT_FOUND; +// case "PAGE_NOT_EXIST" -> status = ErrorStatus.PAGE_NOT_EXIST; +// default -> status = ErrorStatus._BAD_REQUEST; +// } +// +// ApiResponse body = ApiResponse.onFailure(status.getCode(), status.getMessage(), null); +// return ResponseEntity.status(status.getHttpStatus()).body(body); +// } private ResponseEntity handleExceptionInternal(Exception e, ErrorReasonDTO reason, HttpHeaders headers, HttpServletRequest request) { diff --git a/src/main/java/umc/spring/config/SwaggerConfig.java b/src/main/java/umc/spring/config/SwaggerConfig.java new file mode 100644 index 0000000..1072b0a --- /dev/null +++ b/src/main/java/umc/spring/config/SwaggerConfig.java @@ -0,0 +1,36 @@ +package umc.spring.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +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; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI UMCstudyAPI(){ + Info info = new Info() + .title("UMC Server WorkBook API") + .description("UMC Server WorkBook API") + .version("1.0.0"); + String jwtSchemeName = "JWT TOKEN"; + SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName); + 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/umc/spring/config/properties/Constants.java b/src/main/java/umc/spring/config/properties/Constants.java new file mode 100644 index 0000000..e6b2850 --- /dev/null +++ b/src/main/java/umc/spring/config/properties/Constants.java @@ -0,0 +1,6 @@ +package umc.spring.config.properties; + +public final class Constants { + public static final String AUTH_HEADER = "Authorization"; + public static final String TOKEN_PREFIX = "Bearer "; +} diff --git a/src/main/java/umc/spring/config/properties/JwtProperties.java b/src/main/java/umc/spring/config/properties/JwtProperties.java new file mode 100644 index 0000000..4288385 --- /dev/null +++ b/src/main/java/umc/spring/config/properties/JwtProperties.java @@ -0,0 +1,22 @@ +package umc.spring.config.properties; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@Getter +@Setter +@ConfigurationProperties("jwt.token") +public class JwtProperties { + private String secretKey=""; + private Expiration expiration; + + @Getter + @Setter + public static class Expiration{ + private Long access; + // TODO: refreshToken + } +} \ No newline at end of file diff --git a/src/main/java/umc/spring/config/security/SecurityConfig.java b/src/main/java/umc/spring/config/security/SecurityConfig.java index aae9da8..0782b3c 100644 --- a/src/main/java/umc/spring/config/security/SecurityConfig.java +++ b/src/main/java/umc/spring/config/security/SecurityConfig.java @@ -21,21 +21,17 @@ public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http - .authorizeHttpRequests((requests) -> requests - .requestMatchers("/", "/home", "/signup","/members/signup", "/css/**").permitAll() - .requestMatchers("/admin/**").hasRole("ADMIN") - .anyRequest().authenticated() + .sessionManagement(session -> + session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) - .formLogin((form) -> form - .loginPage("/login") - .defaultSuccessUrl("/home", true) - .permitAll() + .authorizeHttpRequests( + (requests) -> requests + .requestMatchers("/", "/members/join", "/members/login", "/swagger-ui/**", "/v3/api-docs/**").permitAll() + .requestMatchers("/admin/**").hasRole("ADMIN") + .anyRequest().authenticated() ) - .logout((logout) -> logout - .logoutUrl("/logout") - .logoutSuccessUrl("/login?logout") - .permitAll() - ); + .csrf(csrf->csrf.disable()) + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); return http.build(); } diff --git a/src/main/java/umc/spring/config/security/jwt/JwtAuthenticationFilter.java b/src/main/java/umc/spring/config/security/jwt/JwtAuthenticationFilter.java new file mode 100644 index 0000000..1cd3c5c --- /dev/null +++ b/src/main/java/umc/spring/config/security/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,43 @@ +package umc.spring.config.security.jwt; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; +import umc.spring.config.properties.Constants; + +import java.io.IOException; + +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + + String token = resolveToken(request); + + if(StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) { + Authentication authentication = jwtTokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + filterChain.doFilter(request, response); + } + + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader(Constants.AUTH_HEADER); + if(StringUtils.hasText(bearerToken) && bearerToken.startsWith(Constants.TOKEN_PREFIX)) { + return bearerToken.substring(Constants.TOKEN_PREFIX.length()); + } + return null; + } +} diff --git a/src/main/java/umc/spring/config/security/jwt/JwtTokenProvider.java b/src/main/java/umc/spring/config/security/jwt/JwtTokenProvider.java new file mode 100644 index 0000000..a3a62bf --- /dev/null +++ b/src/main/java/umc/spring/config/security/jwt/JwtTokenProvider.java @@ -0,0 +1,84 @@ +package umc.spring.config.security.jwt; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.User; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import umc.spring.apiPayload.code.ErrorStatus; +import umc.spring.apiPayload.exception.handler.MemberHandler; +import umc.spring.config.properties.Constants; +import umc.spring.config.properties.JwtProperties; + +import java.security.Key; +import java.util.Date; +import java.util.Collections; + +@Component +@RequiredArgsConstructor +public class JwtTokenProvider { + + private final JwtProperties jwtProperties; + + private Key getSigningKey() { + return Keys.hmacShaKeyFor(jwtProperties.getSecretKey().getBytes()); + } + + public String generateToken(Authentication authentication) { + String email = authentication.getName(); + + return Jwts.builder() + .setSubject(email) + .claim("role", authentication.getAuthorities().iterator().next().getAuthority()) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getExpiration().getAccess())) + .signWith(getSigningKey(), SignatureAlgorithm.HS256) + .compact(); + } + + public boolean validateToken(String token) { + try { + Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token); + return true; + } catch (JwtException | IllegalArgumentException e) { + return false; + } + } + + public Authentication getAuthentication(String token) { + Claims claims = Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token) + .getBody(); + + String email = claims.getSubject(); + String role = claims.get("role", String.class); + + User principal = new User(email, "", Collections.singleton(() -> role)); + return new UsernamePasswordAuthenticationToken(principal, token, principal.getAuthorities()); + } + + public static String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader(Constants.AUTH_HEADER); + if(StringUtils.hasText(bearerToken) && bearerToken.startsWith(Constants.TOKEN_PREFIX)) { + return bearerToken.substring(Constants.TOKEN_PREFIX.length()); + } + return null; + } + + public Authentication extractAuthentication(HttpServletRequest request){ + String accessToken = resolveToken(request); + if(accessToken == null || !validateToken(accessToken)) { + throw new MemberHandler(ErrorStatus.INVALID_TOKEN); + } + return getAuthentication(accessToken); + } +} \ No newline at end of file diff --git a/src/main/java/umc/spring/converter/MemberConverter.java b/src/main/java/umc/spring/converter/MemberConverter.java index feb93c7..31e32bd 100644 --- a/src/main/java/umc/spring/converter/MemberConverter.java +++ b/src/main/java/umc/spring/converter/MemberConverter.java @@ -20,12 +20,28 @@ public static MemberResponseDto.JoinResultDTO toJoinResultDTO(Member member){ .build(); } + public static MemberResponseDto.LoginResultDto toLoginResultDto(Long memberId, String accessToken){ + return MemberResponseDto.LoginResultDto.builder() + .memberId(memberId) + .accessToken(accessToken) + .build(); + } + + public static MemberResponseDto.MemberInfoDto toMemberInfoDto(Member member){ + return MemberResponseDto.MemberInfoDto.builder() + .email(member.getEmail()) + .name(member.getName()) + .gender(member.getGender()) + .build(); + } + public static Member toMember(MemberRequestDto.JoinDto request){ - Gender gender = switch (request.getGender()){ - case 1 -> Gender.MALE; - case 2 -> Gender.FEMALE; - default -> null; - }; + Gender gender = null; + switch (request.getGender()) { + case 1: gender = Gender.MALE; break; + case 2: gender = Gender.FEMALE; break; + case 3: gender = Gender.UNKNOWN; break; + } return Member.builder() .address(request.getAddress()) .gender(gender) @@ -33,6 +49,7 @@ public static Member toMember(MemberRequestDto.JoinDto request){ .birth(request.getBirth()) .email(request.getEmail()) .phoneNumber(request.getPhoneNumber()) + .role(request.getRole()) .memberFoodList(new ArrayList<>()) .build(); } diff --git a/src/main/java/umc/spring/converter/MemberFoodConverter.java b/src/main/java/umc/spring/converter/MemberFoodConverter.java new file mode 100644 index 0000000..13ef72b --- /dev/null +++ b/src/main/java/umc/spring/converter/MemberFoodConverter.java @@ -0,0 +1,18 @@ +package umc.spring.converter; + +import umc.spring.domain.FoodCategory; +import umc.spring.domain.mapping.MemberFood; + +import java.util.List; +import java.util.stream.Collectors; + +public class MemberFoodConverter { + public static List toMemberFoodList(List foodCategoryList){ + return foodCategoryList.stream() + .map(foodCategory -> + MemberFood.builder() + .foodCategory(foodCategory) + .build() + ).collect(Collectors.toList()); + } +} diff --git a/src/main/java/umc/spring/converter/ReviewConverter.java b/src/main/java/umc/spring/converter/ReviewConverter.java index bafb31f..7ff8640 100644 --- a/src/main/java/umc/spring/converter/ReviewConverter.java +++ b/src/main/java/umc/spring/converter/ReviewConverter.java @@ -2,6 +2,7 @@ import org.springframework.data.domain.Page; import umc.spring.domain.Review; +import umc.spring.web.dto.MemberResponseDto; import umc.spring.web.dto.ReviewRequestDto; import umc.spring.web.dto.ReviewResponseDto; import umc.spring.web.dto.StoreResponseDto; @@ -49,4 +50,5 @@ public static StoreResponseDto.ReviewPreviewListDto reviewPreviewListDto(Page memberMissionList = new ArrayList<>(); + + public void encodedPassword(String password){ + this.password = password; + } + } diff --git a/src/main/java/umc/spring/domain/Region.java b/src/main/java/umc/spring/domain/Region.java index fb40e20..7535f1c 100644 --- a/src/main/java/umc/spring/domain/Region.java +++ b/src/main/java/umc/spring/domain/Region.java @@ -21,4 +21,9 @@ public class Region extends BaseEntity { @OneToMany(mappedBy = "region", cascade = CascadeType.ALL) private List storeList = new ArrayList<>(); + + public void addStore(Store store){ + storeList.add(store); + store.setRegion(this); + } } diff --git a/src/main/java/umc/spring/domain/ReviewImage.java b/src/main/java/umc/spring/domain/ReviewImage.java index 4b1529a..8037f24 100644 --- a/src/main/java/umc/spring/domain/ReviewImage.java +++ b/src/main/java/umc/spring/domain/ReviewImage.java @@ -5,6 +5,7 @@ @Entity @Getter +@Setter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor diff --git a/src/main/java/umc/spring/domain/Store.java b/src/main/java/umc/spring/domain/Store.java index 898944e..08b664f 100644 --- a/src/main/java/umc/spring/domain/Store.java +++ b/src/main/java/umc/spring/domain/Store.java @@ -2,6 +2,7 @@ import jakarta.persistence.*; import lombok.*; +import org.springframework.stereotype.Service; import umc.spring.domain.common.BaseEntity; import java.util.ArrayList; @@ -9,6 +10,7 @@ @Entity @Getter +@Setter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @@ -24,8 +26,8 @@ public class Store extends BaseEntity { @Column(nullable = false, length = 50) private String address; - @Column(nullable = false) - private Float score; + @Column(nullable = true) + private Float score = 0.0f; private String category; @@ -33,6 +35,9 @@ public class Store extends BaseEntity { @JoinColumn(name = "region_id") private Region region; + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL) + private List reviewList = new ArrayList<>(); + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL) private List missionList = new ArrayList<>(); diff --git a/src/main/java/umc/spring/domain/enums/Gender.java b/src/main/java/umc/spring/domain/enums/Gender.java index b74c14e..abdde7f 100644 --- a/src/main/java/umc/spring/domain/enums/Gender.java +++ b/src/main/java/umc/spring/domain/enums/Gender.java @@ -1,5 +1,5 @@ package umc.spring.domain.enums; public enum Gender { - MALE, FEMALE + MALE, FEMALE, UNKNOWN } diff --git a/src/main/java/umc/spring/domain/enums/Role.java b/src/main/java/umc/spring/domain/enums/Role.java new file mode 100644 index 0000000..61d3b50 --- /dev/null +++ b/src/main/java/umc/spring/domain/enums/Role.java @@ -0,0 +1,5 @@ +package umc.spring.domain.enums; + +public enum Role { + ADMIN, USER +} diff --git a/src/main/java/umc/spring/domain/mapping/MemberFood.java b/src/main/java/umc/spring/domain/mapping/MemberFood.java index 28cc582..ff38492 100644 --- a/src/main/java/umc/spring/domain/mapping/MemberFood.java +++ b/src/main/java/umc/spring/domain/mapping/MemberFood.java @@ -1,15 +1,13 @@ package umc.spring.domain.mapping; import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import umc.spring.domain.FoodCategory; import umc.spring.domain.Member; import umc.spring.domain.common.BaseEntity; @Entity @Getter +@Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class MemberFood extends BaseEntity{ @@ -24,4 +22,17 @@ public class MemberFood extends BaseEntity{ @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "category_id") private FoodCategory foodCategory; + + + //연관관계 편의 메서드 + public void setMember(Member member){ + if(this.member != null) + member.getMemberFoodList().remove(this); + this.member = member; + member.getMemberFoodList().add(this); + } + + public void setFoodCategory(FoodCategory foodCategory){ + this.foodCategory = foodCategory; + } //양방향 아니라서 한 쪽만 넣어줌 } diff --git a/src/main/java/umc/spring/domain/mapping/MemberMission.java b/src/main/java/umc/spring/domain/mapping/MemberMission.java index 948c024..e92842e 100644 --- a/src/main/java/umc/spring/domain/mapping/MemberMission.java +++ b/src/main/java/umc/spring/domain/mapping/MemberMission.java @@ -11,6 +11,7 @@ @Entity @Getter +@Setter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor diff --git a/src/main/java/umc/spring/repository/FoodCategoryRepository.java b/src/main/java/umc/spring/repository/FoodCategoryRepository.java new file mode 100644 index 0000000..1558776 --- /dev/null +++ b/src/main/java/umc/spring/repository/FoodCategoryRepository.java @@ -0,0 +1,8 @@ +package umc.spring.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import umc.spring.domain.FoodCategory; + +public interface FoodCategoryRepository extends JpaRepository { + +} diff --git a/src/main/java/umc/spring/repository/MemberRepository.java b/src/main/java/umc/spring/repository/MemberRepository.java index 21a4961..999b4c1 100644 --- a/src/main/java/umc/spring/repository/MemberRepository.java +++ b/src/main/java/umc/spring/repository/MemberRepository.java @@ -1,13 +1,10 @@ -package umc.spring.repository.memberRepository; +package umc.spring.repository; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import umc.spring.domain.Member; -import umc.spring.domain.enums.MemberStatus; -import java.util.List; +import java.util.Optional; public interface MemberRepository extends JpaRepository { - + Optional findByEmail(String email); } diff --git a/src/main/java/umc/spring/service/memberService/MemberCommandService.java b/src/main/java/umc/spring/service/memberService/MemberCommandService.java index 02a051b..f7a0ec9 100644 --- a/src/main/java/umc/spring/service/memberService/MemberCommandService.java +++ b/src/main/java/umc/spring/service/memberService/MemberCommandService.java @@ -2,7 +2,9 @@ import umc.spring.domain.Member; import umc.spring.web.dto.MemberRequestDto; +import umc.spring.web.dto.MemberResponseDto; public interface MemberCommandService { public Member joinMember(MemberRequestDto.JoinDto request); + MemberResponseDto.LoginResultDto loginMember(MemberRequestDto.LoginRequestDto request); } diff --git a/src/main/java/umc/spring/service/memberService/MemberCommandServiceImpl.java b/src/main/java/umc/spring/service/memberService/MemberCommandServiceImpl.java index 728a5f1..50c050b 100644 --- a/src/main/java/umc/spring/service/memberService/MemberCommandServiceImpl.java +++ b/src/main/java/umc/spring/service/memberService/MemberCommandServiceImpl.java @@ -1,10 +1,16 @@ package umc.spring.service.memberService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import umc.spring.apiPayload.code.ErrorStatus; import umc.spring.apiPayload.exception.handler.FoodCategoryHandler; +import umc.spring.apiPayload.exception.handler.MemberHandler; +import umc.spring.config.security.jwt.JwtTokenProvider; import umc.spring.converter.MemberConverter; import umc.spring.converter.MemberFoodConverter; import umc.spring.domain.FoodCategory; @@ -13,20 +19,26 @@ import umc.spring.repository.FoodCategoryRepository; import umc.spring.repository.MemberRepository; import umc.spring.web.dto.MemberRequestDto; +import umc.spring.web.dto.MemberResponseDto; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +@Slf4j @Service @RequiredArgsConstructor public class MemberCommandServiceImpl implements MemberCommandService { private final MemberRepository memberRepository; private final FoodCategoryRepository foodCategoryRepository; + private final PasswordEncoder passwordEncoder; + private final JwtTokenProvider jwtTokenProvider; @Override @Transactional public Member joinMember(MemberRequestDto.JoinDto request){ Member newMember = MemberConverter.toMember(request); + newMember.encodedPassword(passwordEncoder.encode(request.getPassword())); //비밀번호 암호화 후 저장 List foodCategoryList = request.getPreferCategory().stream(). map(category->{ return foodCategoryRepository.findById(category).orElseThrow(() -> new FoodCategoryHandler(ErrorStatus.FOOD_CATEGORY_NOT_FOUND)); @@ -36,7 +48,27 @@ public Member joinMember(MemberRequestDto.JoinDto request){ memberFoodList.forEach(memberFood -> { memberFood.setMember(newMember); }); + log.info("join"); return memberRepository.save(newMember); } + @Override + @Transactional + public MemberResponseDto.LoginResultDto loginMember(MemberRequestDto.LoginRequestDto request) { + Member member = memberRepository.findByEmail(request.getEmail()).orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); + if(!passwordEncoder.matches(request.getPassword(), member.getPassword())){ + throw new MemberHandler(ErrorStatus.INVALID_PASSWORD); + } + + Authentication authentication = new UsernamePasswordAuthenticationToken( + member.getEmail(), null, Collections.singleton(()->member.getRole().name()) + ); + + String accessToken = jwtTokenProvider.generateToken(authentication); + return MemberConverter.toLoginResultDto( + member.getId(), + accessToken + ); + } + } diff --git a/src/main/java/umc/spring/service/memberService/MemberQueryService.java b/src/main/java/umc/spring/service/memberService/MemberQueryService.java index aeb27ac..e24a1d4 100644 --- a/src/main/java/umc/spring/service/memberService/MemberQueryService.java +++ b/src/main/java/umc/spring/service/memberService/MemberQueryService.java @@ -1,8 +1,11 @@ package umc.spring.service.memberService; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.data.domain.Page; import umc.spring.domain.Review; +import umc.spring.web.dto.MemberResponseDto; public interface MemberQueryService { Page getReviewList(Long memberId, Integer page); + MemberResponseDto.MemberInfoDto getMemberInfo(HttpServletRequest request); } diff --git a/src/main/java/umc/spring/service/memberService/MemberQueryServiceImpl.java b/src/main/java/umc/spring/service/memberService/MemberQueryServiceImpl.java index 588b055..451a34d 100644 --- a/src/main/java/umc/spring/service/memberService/MemberQueryServiceImpl.java +++ b/src/main/java/umc/spring/service/memberService/MemberQueryServiceImpl.java @@ -1,15 +1,20 @@ package umc.spring.service.memberService; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import umc.spring.apiPayload.code.ErrorStatus; import umc.spring.apiPayload.exception.handler.MemberHandler; +import umc.spring.config.security.jwt.JwtTokenProvider; +import umc.spring.converter.MemberConverter; import umc.spring.domain.Member; import umc.spring.domain.Review; import umc.spring.repository.MemberRepository; import umc.spring.repository.ReviewRepository; +import umc.spring.web.dto.MemberResponseDto; @Service @RequiredArgsConstructor @@ -17,6 +22,7 @@ public class MemberQueryServiceImpl implements MemberQueryService { private final MemberRepository memberRepository; private final ReviewRepository reviewRepository; + private final JwtTokenProvider jwtTokenProvider; @Override public Page getReviewList(Long memberId, Integer page){ @@ -25,4 +31,13 @@ public Page getReviewList(Long memberId, Integer page){ return memberPage; } + @Override + public MemberResponseDto.MemberInfoDto getMemberInfo(HttpServletRequest request) { + Authentication authentication = jwtTokenProvider.extractAuthentication(request); + String email = authentication.getName(); + + Member member = memberRepository.findByEmail(email).orElseThrow(()->new MemberHandler(ErrorStatus.MISSION_NOT_FOUND)); + return MemberConverter.toMemberInfoDto(member); + } + } diff --git a/src/main/java/umc/spring/service/missionService/MissionQueryService.java b/src/main/java/umc/spring/service/missionService/MissionQueryService.java new file mode 100644 index 0000000..be0749e --- /dev/null +++ b/src/main/java/umc/spring/service/missionService/MissionQueryService.java @@ -0,0 +1,8 @@ +package umc.spring.service.missionService; + +import org.springframework.data.domain.Page; +import umc.spring.domain.Mission; + +public interface MissionQueryService { + +} diff --git a/src/main/java/umc/spring/service/missionService/MissionQueryServiceImpl.java b/src/main/java/umc/spring/service/missionService/MissionQueryServiceImpl.java new file mode 100644 index 0000000..d5768bd --- /dev/null +++ b/src/main/java/umc/spring/service/missionService/MissionQueryServiceImpl.java @@ -0,0 +1,21 @@ +package umc.spring.service.missionService; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import umc.spring.apiPayload.code.ErrorStatus; +import umc.spring.apiPayload.exception.handler.StoreHandler; +import umc.spring.domain.Mission; +import umc.spring.domain.Store; +import umc.spring.repository.MissionRepository; +import umc.spring.repository.storeRepository.StoreRepository; + +@Service +@RequiredArgsConstructor +public class MissionQueryServiceImpl implements MissionQueryService{ + + private final StoreRepository storeRepository; + private final MissionRepository missionRepository; + +} diff --git a/src/main/java/umc/spring/service/reviewService/ReviewQueryService.java b/src/main/java/umc/spring/service/reviewService/ReviewQueryService.java new file mode 100644 index 0000000..c904d7f --- /dev/null +++ b/src/main/java/umc/spring/service/reviewService/ReviewQueryService.java @@ -0,0 +1,8 @@ +package umc.spring.service.reviewService; + +import org.springframework.data.domain.Page; +import umc.spring.domain.Review; + +public interface ReviewQueryService { + +} diff --git a/src/main/java/umc/spring/service/reviewService/ReviewQueryServiceImpl.java b/src/main/java/umc/spring/service/reviewService/ReviewQueryServiceImpl.java new file mode 100644 index 0000000..4a63960 --- /dev/null +++ b/src/main/java/umc/spring/service/reviewService/ReviewQueryServiceImpl.java @@ -0,0 +1,20 @@ +package umc.spring.service.reviewService; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import umc.spring.domain.Review; +import umc.spring.domain.Store; +import umc.spring.repository.ReviewRepository; +import umc.spring.repository.storeRepository.StoreRepository; + +@Service +@RequiredArgsConstructor +public class ReviewQueryServiceImpl implements ReviewQueryService { + + private final ReviewRepository reviewRepository; + private final StoreRepository storeRepository; + + +} diff --git a/src/main/java/umc/spring/web/controller/MemberRestController.java b/src/main/java/umc/spring/web/controller/MemberRestController.java index 005b025..08ad585 100644 --- a/src/main/java/umc/spring/web/controller/MemberRestController.java +++ b/src/main/java/umc/spring/web/controller/MemberRestController.java @@ -7,6 +7,8 @@ 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 io.swagger.v3.oas.annotations.security.SecurityRequirement; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -36,12 +38,28 @@ public class MemberRestController { private final MemberQueryService memberQueryService; private final MemberMissionQueryService memberMissionQueryService; - @PostMapping("/") + @PostMapping("/join") + @Operation(summary = "유저 회원가입 API",description = "유저가 회원가입하는 API입니다.") public ApiResponse join(@RequestBody @Valid MemberRequestDto.JoinDto request){ Member member = memberCommandService.joinMember(request); return ApiResponse.onSuccess(MemberConverter.toJoinResultDTO(member)); } + @PostMapping("/login") + @Operation(summary = "유저 로그인 API",description = "유저가 로그인하는 API입니다.") + public ApiResponse login(@RequestBody @Valid MemberRequestDto.LoginRequestDto request) { + return ApiResponse.onSuccess(memberCommandService.loginMember(request)); + } + + @GetMapping("/info") + @Operation(summary = "유저 내 정보 조회 API - 인증 필요", + description = "유저가 내 정보를 조회하는 API입니다.", + security = { @SecurityRequirement(name = "JWT TOKEN") } + ) + public ApiResponse getMyInfo(HttpServletRequest request) { + return ApiResponse.onSuccess(memberQueryService.getMemberInfo(request)); + } + @GetMapping("{memberId}/reviews") @Operation(summary = "특정 유저의 리뷰 목록 조회 API", description = "특정 유저의 리뷰들의 목록을 조회하는 API이며, 페이징을 포함합니다. query String 으로 page 번호를 주세요") @ApiResponses({ diff --git a/src/main/java/umc/spring/web/controller/ReviewRestController.java b/src/main/java/umc/spring/web/controller/ReviewRestController.java index ce74a3d..8986603 100644 --- a/src/main/java/umc/spring/web/controller/ReviewRestController.java +++ b/src/main/java/umc/spring/web/controller/ReviewRestController.java @@ -1,15 +1,20 @@ package umc.spring.web.controller; +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 jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.*; import umc.spring.apiPayload.ApiResponse; import umc.spring.converter.ReviewConverter; import umc.spring.domain.Review; import umc.spring.service.reviewService.ReviewCommandService; +import umc.spring.service.reviewService.ReviewQueryService; import umc.spring.web.dto.ReviewRequestDto; import umc.spring.web.dto.ReviewResponseDto; @@ -18,9 +23,13 @@ @RequestMapping("/reviews") public class ReviewRestController { private final ReviewCommandService reviewCommandService; - @PostMapping("/") - public ApiResponse ReviewAdd(@RequestBody @Valid ReviewRequestDto.ReviewAddDto request){ + private final ReviewQueryService reviewQueryService; + + @PostMapping("/add") + public ApiResponse AddReview(@RequestBody @Valid ReviewRequestDto.ReviewAddDto request){ Review review = reviewCommandService.addReview(request); return ApiResponse.onSuccess(ReviewConverter.toReviewAddResultDto(review)); } + + } diff --git a/src/main/java/umc/spring/web/dto/MemberResponseDto.java b/src/main/java/umc/spring/web/dto/MemberResponseDto.java index c8cdae3..f84dfee 100644 --- a/src/main/java/umc/spring/web/dto/MemberResponseDto.java +++ b/src/main/java/umc/spring/web/dto/MemberResponseDto.java @@ -4,6 +4,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import umc.spring.domain.enums.Gender; import umc.spring.domain.enums.MissionStatus; import java.time.LocalDate; @@ -64,4 +65,20 @@ public static class MissionPreviewDto{ LocalDate deadline; } + @Builder + @Getter + @AllArgsConstructor + public static class LoginResultDto{ + Long memberId; + String accessToken; + } + @Builder + @Getter + @AllArgsConstructor + public static class MemberInfoDto{ + String name; + String email; + Gender gender; + } + } From 09b7aa9f2ac95732c5f3a6d28bef736b148c5997 Mon Sep 17 00:00:00 2001 From: sammool Date: Sun, 6 Jul 2025 23:05:31 +0900 Subject: [PATCH 16/16] ci/cd test --- .github/workflows/dev_deploy.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/workflows/dev_deploy.yml diff --git a/.github/workflows/dev_deploy.yml b/.github/workflows/dev_deploy.yml new file mode 100644 index 0000000..313a476 --- /dev/null +++ b/.github/workflows/dev_deploy.yml @@ -0,0 +1,11 @@ +name: sammool's Dev CI/CD + +on: + pull_request: + types: [closed] + workflow_dispatch: #(2).수동 실행 가능하도록 + +jobs: + build: + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'develop' \ No newline at end of file