diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ae0646a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+# IDE / Gradle / OS
+.idea/
+.gradle/
+out/
+build/
+*.iml
+.DS_Store
+
+# Spring Boot 보안 설정 파일
+src/main/resources/application.yml
+src/main/resources/application-secret.yml
+build/resources/main/application.yml
+build/resources/main/application-secret.yml
diff --git a/.gradle/8.14.3/checksums/checksums.lock b/.gradle/8.14.3/checksums/checksums.lock
new file mode 100644
index 0000000..0c99994
Binary files /dev/null and b/.gradle/8.14.3/checksums/checksums.lock differ
diff --git a/.gradle/8.14.3/executionHistory/executionHistory.bin b/.gradle/8.14.3/executionHistory/executionHistory.bin
new file mode 100644
index 0000000..f6ed596
Binary files /dev/null and b/.gradle/8.14.3/executionHistory/executionHistory.bin differ
diff --git a/.gradle/8.14.3/executionHistory/executionHistory.lock b/.gradle/8.14.3/executionHistory/executionHistory.lock
new file mode 100644
index 0000000..3f755af
Binary files /dev/null and b/.gradle/8.14.3/executionHistory/executionHistory.lock differ
diff --git a/.gradle/8.14.3/fileChanges/last-build.bin b/.gradle/8.14.3/fileChanges/last-build.bin
new file mode 100644
index 0000000..f76dd23
Binary files /dev/null and b/.gradle/8.14.3/fileChanges/last-build.bin differ
diff --git a/.gradle/8.14.3/fileHashes/fileHashes.bin b/.gradle/8.14.3/fileHashes/fileHashes.bin
new file mode 100644
index 0000000..edd5581
Binary files /dev/null and b/.gradle/8.14.3/fileHashes/fileHashes.bin differ
diff --git a/.gradle/8.14.3/fileHashes/fileHashes.lock b/.gradle/8.14.3/fileHashes/fileHashes.lock
new file mode 100644
index 0000000..7600e51
Binary files /dev/null and b/.gradle/8.14.3/fileHashes/fileHashes.lock differ
diff --git a/.gradle/8.14.3/fileHashes/resourceHashesCache.bin b/.gradle/8.14.3/fileHashes/resourceHashesCache.bin
new file mode 100644
index 0000000..d65bf2f
Binary files /dev/null and b/.gradle/8.14.3/fileHashes/resourceHashesCache.bin differ
diff --git a/.gradle/8.14.3/gc.properties b/.gradle/8.14.3/gc.properties
new file mode 100644
index 0000000..e69de29
diff --git a/.gradle/8.5/checksums/checksums.lock b/.gradle/8.5/checksums/checksums.lock
new file mode 100644
index 0000000..ddeecc9
Binary files /dev/null and b/.gradle/8.5/checksums/checksums.lock differ
diff --git a/.gradle/8.5/checksums/md5-checksums.bin b/.gradle/8.5/checksums/md5-checksums.bin
new file mode 100644
index 0000000..65f0a26
Binary files /dev/null and b/.gradle/8.5/checksums/md5-checksums.bin differ
diff --git a/.gradle/8.5/checksums/sha1-checksums.bin b/.gradle/8.5/checksums/sha1-checksums.bin
new file mode 100644
index 0000000..424e00d
Binary files /dev/null and b/.gradle/8.5/checksums/sha1-checksums.bin differ
diff --git a/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock b/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock
new file mode 100644
index 0000000..1c81e2d
Binary files /dev/null and b/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock differ
diff --git a/.gradle/8.5/dependencies-accessors/gc.properties b/.gradle/8.5/dependencies-accessors/gc.properties
new file mode 100644
index 0000000..e69de29
diff --git a/.gradle/8.5/executionHistory/executionHistory.bin b/.gradle/8.5/executionHistory/executionHistory.bin
new file mode 100644
index 0000000..81c34de
Binary files /dev/null and b/.gradle/8.5/executionHistory/executionHistory.bin differ
diff --git a/.gradle/8.5/executionHistory/executionHistory.lock b/.gradle/8.5/executionHistory/executionHistory.lock
new file mode 100644
index 0000000..7308fe6
Binary files /dev/null and b/.gradle/8.5/executionHistory/executionHistory.lock differ
diff --git a/.gradle/8.5/fileChanges/last-build.bin b/.gradle/8.5/fileChanges/last-build.bin
new file mode 100644
index 0000000..f76dd23
Binary files /dev/null and b/.gradle/8.5/fileChanges/last-build.bin differ
diff --git a/.gradle/8.5/fileHashes/fileHashes.bin b/.gradle/8.5/fileHashes/fileHashes.bin
new file mode 100644
index 0000000..0e494b9
Binary files /dev/null and b/.gradle/8.5/fileHashes/fileHashes.bin differ
diff --git a/.gradle/8.5/fileHashes/fileHashes.lock b/.gradle/8.5/fileHashes/fileHashes.lock
new file mode 100644
index 0000000..88c56af
Binary files /dev/null and b/.gradle/8.5/fileHashes/fileHashes.lock differ
diff --git a/.gradle/8.5/fileHashes/resourceHashesCache.bin b/.gradle/8.5/fileHashes/resourceHashesCache.bin
new file mode 100644
index 0000000..76aec4c
Binary files /dev/null and b/.gradle/8.5/fileHashes/resourceHashesCache.bin differ
diff --git a/.gradle/8.5/gc.properties b/.gradle/8.5/gc.properties
new file mode 100644
index 0000000..e69de29
diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock
new file mode 100644
index 0000000..dbf469f
Binary files /dev/null and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ
diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties
new file mode 100644
index 0000000..c26392e
--- /dev/null
+++ b/.gradle/buildOutputCleanup/cache.properties
@@ -0,0 +1,2 @@
+#Tue Nov 04 23:31:33 KST 2025
+gradle.version=8.5
diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin
new file mode 100644
index 0000000..50cce15
Binary files /dev/null and b/.gradle/buildOutputCleanup/outputFiles.bin differ
diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe
new file mode 100644
index 0000000..7f95735
Binary files /dev/null and b/.gradle/file-system.probe differ
diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties
new file mode 100644
index 0000000..e69de29
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..c3f502a
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# 디폴트 무시된 파일
+/shelf/
+/workspace.xml
+# 에디터 기반 HTTP 클라이언트 요청
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..abe6c2f
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000..a51e90f
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,112 @@
+
+
+
+
+ mysql.8
+ true
+ true
+ $PROJECT_DIR$/src/main/resources/application.yml
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://localhost:3306/umc9th_db?serverTimezone=Asia/Seoul&characterEncoding=utf8
+ $ProjectFileDir$
+
+
+ mysql.8
+ true
+ true
+ $PROJECT_DIR$/src/main/resources/application.yml
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://localhost:3306/umc9th_db?serverTimezone=Asia/Seoul&useUnicode=true&characterEncoding=UTF-8
+
+
+
+
+
+ $ProjectFileDir$
+
+
+ mysql_aurora.aws_wrapper
+ true
+ software.amazon.jdbc.Driver
+ jdbc:aws-wrapper:mysql://localhost:3306/umc9th_db
+
+
+
+
+
+ $ProjectFileDir$
+
+
+ mysql.8
+ true
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://localhost:3306/umc9th_db
+
+
+
+
+
+ $ProjectFileDir$
+
+
+ mysql.8
+ true
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://localhost:3306/umc9th_db
+
+
+
+
+
+ $ProjectFileDir$
+
+
+ mysql.9
+ true
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://localhost:3306/umc9th_db
+
+
+
+
+
+ $ProjectFileDir$
+
+
+ mysql.9
+ true
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://localhost:3306/umc9th_db
+
+
+
+
+
+ $ProjectFileDir$
+
+
+ mysql.9
+ true
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://localhost:3306/umc9th_db
+
+
+
+
+
+ $ProjectFileDir$
+
+
+ mysql.9
+ true
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://localhost:3306
+
+
+
+
+
+ $ProjectFileDir$
+
+
+
\ No newline at end of file
diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml
new file mode 100644
index 0000000..66ff35b
--- /dev/null
+++ b/.idea/data_source_mapping.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..ce1c62c
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..bd7f609
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..1e66c9c
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules/umc_spring_first.main.iml b/.idea/modules/umc_spring_first.main.iml
new file mode 100644
index 0000000..343ca82
--- /dev/null
+++ b/.idea/modules/umc_spring_first.main.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules/umc_spring_first.test.iml b/.idea/modules/umc_spring_first.test.iml
new file mode 100644
index 0000000..934a18b
--- /dev/null
+++ b/.idea/modules/umc_spring_first.test.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/sqlDataSources.xml b/.idea/sqlDataSources.xml
new file mode 100644
index 0000000..586f499
--- /dev/null
+++ b/.idea/sqlDataSources.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..26679f4
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/HELP.md b/HELP.md
new file mode 100644
index 0000000..3d2d617
--- /dev/null
+++ b/HELP.md
@@ -0,0 +1,28 @@
+# Getting Started
+
+### Reference Documentation
+
+For further reference, please consider the following sections:
+
+* [Official Gradle documentation](https://docs.gradle.org)
+* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/3.5.6/gradle-plugin)
+* [Create an OCI image](https://docs.spring.io/spring-boot/3.5.6/gradle-plugin/packaging-oci-image.html)
+* [Spring Data JPA](https://docs.spring.io/spring-boot/3.5.6/reference/data/sql.html#data.sql.jpa-and-spring-data)
+* [Spring Web](https://docs.spring.io/spring-boot/3.5.6/reference/web/servlet.html)
+
+### Guides
+
+The following guides illustrate how to use some features concretely:
+
+* [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/)
+* [Accessing data with MySQL](https://spring.io/guides/gs/accessing-data-mysql/)
+* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/)
+* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/)
+* [Building REST services with Spring](https://spring.io/guides/tutorials/rest/)
+
+### Additional Links
+
+These additional references should also help you:
+
+* [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle)
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..4cc0ecd
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,72 @@
+plugins {
+ id 'java'
+ id 'org.springframework.boot' version '3.3.4'
+ id 'io.spring.dependency-management' version '1.1.5'
+}
+
+group = 'com.example'
+version = '0.0.1-SNAPSHOT'
+java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }
+
+repositories { mavenCentral() }
+
+dependencies {
+ // Spring
+ implementation 'org.springframework.boot:spring-boot-starter-web'
+ implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+ runtimeOnly 'com.mysql:mysql-connector-j'
+
+ // Lombok
+ compileOnly 'org.projectlombok:lombok'
+ annotationProcessor 'org.projectlombok:lombok'
+ testCompileOnly 'org.projectlombok:lombok'
+ testAnnotationProcessor 'org.projectlombok:lombok'
+
+ // Test
+ testImplementation 'org.springframework.boot:spring-boot-starter-test'
+
+ // ===== QueryDSL (OpenFeign fork) =====
+ implementation "io.github.openfeign.querydsl:querydsl-core:7.0"
+ implementation "io.github.openfeign.querydsl:querydsl-jpa:7.0"
+ annotationProcessor "io.github.openfeign.querydsl:querydsl-apt:7.0:jpa"
+
+ // JPA/Annotation APIs for APT(메타모델 생성 시 타입 해석용)
+ annotationProcessor "jakarta.persistence:jakarta.persistence-api"
+ annotationProcessor "jakarta.annotation:jakarta.annotation-api"
+
+ implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'
+
+ // Security
+ implementation 'org.springframework.boot:spring-boot-starter-security'
+ testImplementation 'org.springframework.security:spring-security-test'
+
+ // Jwt
+ implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
+ implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
+ implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
+ implementation 'org.springframework.boot:spring-boot-configuration-processor'
+
+}
+
+// JUnit Platform
+tasks.withType(Test).configureEach { useJUnitPlatform() }
+
+// ===== QueryDSL 생성 경로 설정 =====
+// build/generated/querydsl로 생성
+def querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile
+
+// main 소스 세트에 생성 디렉터리 포함
+sourceSets {
+ main.java.srcDirs += [ querydslDir ]
+}
+
+// APT 결과물을 위 디렉터리로 보내기
+tasks.withType(JavaCompile).configureEach {
+ // Gradle 8+: generatedSourceOutputDirectory 사용
+ options.generatedSourceOutputDirectory.set(querydslDir)
+}
+
+// clean 시 생성물 삭제
+clean.doLast {
+ file(querydslDir).deleteDir()
+}
diff --git a/build/classes/java/main/com/example/umc_spring_first/UmcSpringFirstApplication.class b/build/classes/java/main/com/example/umc_spring_first/UmcSpringFirstApplication.class
new file mode 100644
index 0000000..488a36a
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/UmcSpringFirstApplication.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/config/QuerydslConfig.class b/build/classes/java/main/com/example/umc_spring_first/config/QuerydslConfig.class
new file mode 100644
index 0000000..509dd86
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/config/QuerydslConfig.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/inquiry/entity/Inquiry$InquiryBuilder.class b/build/classes/java/main/com/example/umc_spring_first/domain/inquiry/entity/Inquiry$InquiryBuilder.class
new file mode 100644
index 0000000..d8932cd
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/inquiry/entity/Inquiry$InquiryBuilder.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/inquiry/entity/Inquiry.class b/build/classes/java/main/com/example/umc_spring_first/domain/inquiry/entity/Inquiry.class
new file mode 100644
index 0000000..23c9a6a
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/inquiry/entity/Inquiry.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/inquiry/entity/QInquiry.class b/build/classes/java/main/com/example/umc_spring_first/domain/inquiry/entity/QInquiry.class
new file mode 100644
index 0000000..132444d
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/inquiry/entity/QInquiry.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/inquiry/repository/InquiryRepository.class b/build/classes/java/main/com/example/umc_spring_first/domain/inquiry/repository/InquiryRepository.class
new file mode 100644
index 0000000..8923a8d
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/inquiry/repository/InquiryRepository.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/Mission$MissionBuilder.class b/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/Mission$MissionBuilder.class
new file mode 100644
index 0000000..968ba5a
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/Mission$MissionBuilder.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/Mission.class b/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/Mission.class
new file mode 100644
index 0000000..222b1fd
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/Mission.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/QMission.class b/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/QMission.class
new file mode 100644
index 0000000..5d437f6
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/QMission.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/QUserMission.class b/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/QUserMission.class
new file mode 100644
index 0000000..2e821c5
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/QUserMission.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/UserMission$UserMissionBuilder.class b/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/UserMission$UserMissionBuilder.class
new file mode 100644
index 0000000..1f6cc5f
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/UserMission$UserMissionBuilder.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/UserMission.class b/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/UserMission.class
new file mode 100644
index 0000000..66e7110
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/mission/entity/UserMission.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/mission/enums/MissionStatus.class b/build/classes/java/main/com/example/umc_spring_first/domain/mission/enums/MissionStatus.class
new file mode 100644
index 0000000..2827aa2
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/mission/enums/MissionStatus.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/mission/repository/MissionRepository.class b/build/classes/java/main/com/example/umc_spring_first/domain/mission/repository/MissionRepository.class
new file mode 100644
index 0000000..fd19fa3
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/mission/repository/MissionRepository.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/mission/repository/UserMissionRepository.class b/build/classes/java/main/com/example/umc_spring_first/domain/mission/repository/UserMissionRepository.class
new file mode 100644
index 0000000..4a4eb03
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/mission/repository/UserMissionRepository.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/notice/entity/Notice$NoticeBuilder.class b/build/classes/java/main/com/example/umc_spring_first/domain/notice/entity/Notice$NoticeBuilder.class
new file mode 100644
index 0000000..b4fe4e2
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/notice/entity/Notice$NoticeBuilder.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/notice/entity/Notice.class b/build/classes/java/main/com/example/umc_spring_first/domain/notice/entity/Notice.class
new file mode 100644
index 0000000..6f53385
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/notice/entity/Notice.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/notice/entity/QNotice.class b/build/classes/java/main/com/example/umc_spring_first/domain/notice/entity/QNotice.class
new file mode 100644
index 0000000..ccd1ef2
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/notice/entity/QNotice.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/notice/repository/NoticeRepository.class b/build/classes/java/main/com/example/umc_spring_first/domain/notice/repository/NoticeRepository.class
new file mode 100644
index 0000000..a4f1f92
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/notice/repository/NoticeRepository.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/Prefer$PreferBuilder.class b/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/Prefer$PreferBuilder.class
new file mode 100644
index 0000000..ccc7015
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/Prefer$PreferBuilder.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/Prefer.class b/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/Prefer.class
new file mode 100644
index 0000000..210290d
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/Prefer.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/QPrefer.class b/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/QPrefer.class
new file mode 100644
index 0000000..85f9c70
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/QPrefer.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/QUserPrefer.class b/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/QUserPrefer.class
new file mode 100644
index 0000000..c1144c8
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/QUserPrefer.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/UserPrefer$UserPreferBuilder.class b/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/UserPrefer$UserPreferBuilder.class
new file mode 100644
index 0000000..d366c5f
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/UserPrefer$UserPreferBuilder.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/UserPrefer.class b/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/UserPrefer.class
new file mode 100644
index 0000000..4192dea
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/prefer/entity/UserPrefer.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/prefer/repository/PreferRepository.class b/build/classes/java/main/com/example/umc_spring_first/domain/prefer/repository/PreferRepository.class
new file mode 100644
index 0000000..0bd22fe
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/prefer/repository/PreferRepository.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/prefer/repository/UserPreferRepository.class b/build/classes/java/main/com/example/umc_spring_first/domain/prefer/repository/UserPreferRepository.class
new file mode 100644
index 0000000..8789eb5
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/prefer/repository/UserPreferRepository.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/review/controller/ReviewController.class b/build/classes/java/main/com/example/umc_spring_first/domain/review/controller/ReviewController.class
new file mode 100644
index 0000000..8aa77c5
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/review/controller/ReviewController.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/review/entity/QReview.class b/build/classes/java/main/com/example/umc_spring_first/domain/review/entity/QReview.class
new file mode 100644
index 0000000..fa86bc3
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/review/entity/QReview.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/review/entity/Review$ReviewBuilder.class b/build/classes/java/main/com/example/umc_spring_first/domain/review/entity/Review$ReviewBuilder.class
new file mode 100644
index 0000000..f169efc
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/review/entity/Review$ReviewBuilder.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/review/entity/Review.class b/build/classes/java/main/com/example/umc_spring_first/domain/review/entity/Review.class
new file mode 100644
index 0000000..3cc7ff5
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/review/entity/Review.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/review/repository/ReviewRepository.class b/build/classes/java/main/com/example/umc_spring_first/domain/review/repository/ReviewRepository.class
new file mode 100644
index 0000000..6468c14
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/review/repository/ReviewRepository.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/review/service/ReviewQueryService.class b/build/classes/java/main/com/example/umc_spring_first/domain/review/service/ReviewQueryService.class
new file mode 100644
index 0000000..4940eff
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/review/service/ReviewQueryService.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/store/entity/QStore.class b/build/classes/java/main/com/example/umc_spring_first/domain/store/entity/QStore.class
new file mode 100644
index 0000000..c9a7cfe
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/store/entity/QStore.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/store/entity/Store$StoreBuilder.class b/build/classes/java/main/com/example/umc_spring_first/domain/store/entity/Store$StoreBuilder.class
new file mode 100644
index 0000000..47b17fa
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/store/entity/Store$StoreBuilder.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/store/entity/Store.class b/build/classes/java/main/com/example/umc_spring_first/domain/store/entity/Store.class
new file mode 100644
index 0000000..3b4ab8e
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/store/entity/Store.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/store/repository/StoreRepository.class b/build/classes/java/main/com/example/umc_spring_first/domain/store/repository/StoreRepository.class
new file mode 100644
index 0000000..a5de0cb
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/store/repository/StoreRepository.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/user/entity/QUser.class b/build/classes/java/main/com/example/umc_spring_first/domain/user/entity/QUser.class
new file mode 100644
index 0000000..629b163
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/user/entity/QUser.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/user/entity/User$UserBuilder.class b/build/classes/java/main/com/example/umc_spring_first/domain/user/entity/User$UserBuilder.class
new file mode 100644
index 0000000..5c2b290
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/user/entity/User$UserBuilder.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/user/entity/User.class b/build/classes/java/main/com/example/umc_spring_first/domain/user/entity/User.class
new file mode 100644
index 0000000..2244aed
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/user/entity/User.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/domain/user/repository/UserRepository.class b/build/classes/java/main/com/example/umc_spring_first/domain/user/repository/UserRepository.class
new file mode 100644
index 0000000..a67f7f8
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/domain/user/repository/UserRepository.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/global/entity/BaseTimeEntity.class b/build/classes/java/main/com/example/umc_spring_first/global/entity/BaseTimeEntity.class
new file mode 100644
index 0000000..f9c0a04
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/global/entity/BaseTimeEntity.class differ
diff --git a/build/classes/java/main/com/example/umc_spring_first/global/entity/QBaseTimeEntity.class b/build/classes/java/main/com/example/umc_spring_first/global/entity/QBaseTimeEntity.class
new file mode 100644
index 0000000..03794e4
Binary files /dev/null and b/build/classes/java/main/com/example/umc_spring_first/global/entity/QBaseTimeEntity.class differ
diff --git a/build/generated/querydsl/com/example/umc_spring_first/domain/inquiry/entity/QInquiry.java b/build/generated/querydsl/com/example/umc_spring_first/domain/inquiry/entity/QInquiry.java
new file mode 100644
index 0000000..4007977
--- /dev/null
+++ b/build/generated/querydsl/com/example/umc_spring_first/domain/inquiry/entity/QInquiry.java
@@ -0,0 +1,54 @@
+package com.example.umc_spring_first.domain.inquiry.entity;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.dsl.StringTemplate;
+
+import com.querydsl.core.types.PathMetadata;
+import com.querydsl.core.annotations.Generated;
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.dsl.PathInits;
+
+
+/**
+ * QInquiry is a Querydsl query type for Inquiry
+ */
+@SuppressWarnings("this-escape")
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QInquiry extends EntityPathBase {
+
+ private static final long serialVersionUID = -1124463070L;
+
+ private static final PathInits INITS = PathInits.DIRECT2;
+
+ public static final QInquiry inquiry = new QInquiry("inquiry");
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ public final com.example.umc_spring_first.domain.user.entity.QUser user;
+
+ public QInquiry(String variable) {
+ this(Inquiry.class, forVariable(variable), INITS);
+ }
+
+ public QInquiry(Path extends Inquiry> path) {
+ this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS));
+ }
+
+ public QInquiry(PathMetadata metadata) {
+ this(metadata, PathInits.getFor(metadata, INITS));
+ }
+
+ public QInquiry(PathMetadata metadata, PathInits inits) {
+ this(Inquiry.class, metadata, inits);
+ }
+
+ public QInquiry(Class extends Inquiry> type, PathMetadata metadata, PathInits inits) {
+ super(type, metadata, inits);
+ this.user = inits.isInitialized("user") ? new com.example.umc_spring_first.domain.user.entity.QUser(forProperty("user")) : null;
+ }
+
+}
+
diff --git a/build/generated/querydsl/com/example/umc_spring_first/domain/mission/entity/QMission.java b/build/generated/querydsl/com/example/umc_spring_first/domain/mission/entity/QMission.java
new file mode 100644
index 0000000..af870db
--- /dev/null
+++ b/build/generated/querydsl/com/example/umc_spring_first/domain/mission/entity/QMission.java
@@ -0,0 +1,66 @@
+package com.example.umc_spring_first.domain.mission.entity;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.dsl.StringTemplate;
+
+import com.querydsl.core.types.PathMetadata;
+import com.querydsl.core.annotations.Generated;
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.dsl.PathInits;
+
+
+/**
+ * QMission is a Querydsl query type for Mission
+ */
+@SuppressWarnings("this-escape")
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QMission extends EntityPathBase {
+
+ private static final long serialVersionUID = -202577022L;
+
+ private static final PathInits INITS = PathInits.DIRECT2;
+
+ public static final QMission mission = new QMission("mission");
+
+ public final DateTimePath createAt = createDateTime("createAt", java.time.LocalDateTime.class);
+
+ public final StringPath description = createString("description");
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ public final NumberPath point = createNumber("point", Integer.class);
+
+ public final StringPath status = createString("status");
+
+ public final com.example.umc_spring_first.domain.store.entity.QStore store;
+
+ public final DateTimePath updateAt = createDateTime("updateAt", java.time.LocalDateTime.class);
+
+ public final ListPath userMissions = this.createList("userMissions", UserMission.class, QUserMission.class, PathInits.DIRECT2);
+
+ public QMission(String variable) {
+ this(Mission.class, forVariable(variable), INITS);
+ }
+
+ public QMission(Path extends Mission> 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 extends Mission> type, PathMetadata metadata, PathInits inits) {
+ super(type, metadata, inits);
+ this.store = inits.isInitialized("store") ? new com.example.umc_spring_first.domain.store.entity.QStore(forProperty("store")) : null;
+ }
+
+}
+
diff --git a/build/generated/querydsl/com/example/umc_spring_first/domain/mission/entity/QUserMission.java b/build/generated/querydsl/com/example/umc_spring_first/domain/mission/entity/QUserMission.java
new file mode 100644
index 0000000..9e36b72
--- /dev/null
+++ b/build/generated/querydsl/com/example/umc_spring_first/domain/mission/entity/QUserMission.java
@@ -0,0 +1,65 @@
+package com.example.umc_spring_first.domain.mission.entity;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.dsl.StringTemplate;
+
+import com.querydsl.core.types.PathMetadata;
+import com.querydsl.core.annotations.Generated;
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.dsl.PathInits;
+
+
+/**
+ * QUserMission is a Querydsl query type for UserMission
+ */
+@SuppressWarnings("this-escape")
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QUserMission extends EntityPathBase {
+
+ private static final long serialVersionUID = -266542537L;
+
+ private static final PathInits INITS = PathInits.DIRECT2;
+
+ public static final QUserMission userMission = new QUserMission("userMission");
+
+ public final DateTimePath createAt = createDateTime("createAt", java.time.LocalDateTime.class);
+
+ public final DateTimePath deadline = createDateTime("deadline", java.time.LocalDateTime.class);
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ public final QMission mission;
+
+ public final EnumPath status = createEnum("status", com.example.umc_spring_first.domain.mission.enums.UserMissionStatus.class);
+
+ public final DateTimePath updateAt = createDateTime("updateAt", java.time.LocalDateTime.class);
+
+ public final com.example.umc_spring_first.domain.user.entity.QUser user;
+
+ public QUserMission(String variable) {
+ this(UserMission.class, forVariable(variable), INITS);
+ }
+
+ public QUserMission(Path extends UserMission> path) {
+ this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS));
+ }
+
+ public QUserMission(PathMetadata metadata) {
+ this(metadata, PathInits.getFor(metadata, INITS));
+ }
+
+ public QUserMission(PathMetadata metadata, PathInits inits) {
+ this(UserMission.class, metadata, inits);
+ }
+
+ public QUserMission(Class extends UserMission> type, PathMetadata metadata, PathInits inits) {
+ super(type, metadata, inits);
+ this.mission = inits.isInitialized("mission") ? new QMission(forProperty("mission"), inits.get("mission")) : null;
+ this.user = inits.isInitialized("user") ? new com.example.umc_spring_first.domain.user.entity.QUser(forProperty("user")) : null;
+ }
+
+}
+
diff --git a/build/generated/querydsl/com/example/umc_spring_first/domain/notice/entity/QNotice.java b/build/generated/querydsl/com/example/umc_spring_first/domain/notice/entity/QNotice.java
new file mode 100644
index 0000000..f440528
--- /dev/null
+++ b/build/generated/querydsl/com/example/umc_spring_first/domain/notice/entity/QNotice.java
@@ -0,0 +1,54 @@
+package com.example.umc_spring_first.domain.notice.entity;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.dsl.StringTemplate;
+
+import com.querydsl.core.types.PathMetadata;
+import com.querydsl.core.annotations.Generated;
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.dsl.PathInits;
+
+
+/**
+ * QNotice is a Querydsl query type for Notice
+ */
+@SuppressWarnings("this-escape")
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QNotice extends EntityPathBase {
+
+ private static final long serialVersionUID = -1596367092L;
+
+ private static final PathInits INITS = PathInits.DIRECT2;
+
+ public static final QNotice notice = new QNotice("notice");
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ public final com.example.umc_spring_first.domain.user.entity.QUser user;
+
+ public QNotice(String variable) {
+ this(Notice.class, forVariable(variable), INITS);
+ }
+
+ public QNotice(Path extends Notice> path) {
+ this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS));
+ }
+
+ public QNotice(PathMetadata metadata) {
+ this(metadata, PathInits.getFor(metadata, INITS));
+ }
+
+ public QNotice(PathMetadata metadata, PathInits inits) {
+ this(Notice.class, metadata, inits);
+ }
+
+ public QNotice(Class extends Notice> type, PathMetadata metadata, PathInits inits) {
+ super(type, metadata, inits);
+ this.user = inits.isInitialized("user") ? new com.example.umc_spring_first.domain.user.entity.QUser(forProperty("user")) : null;
+ }
+
+}
+
diff --git a/build/generated/querydsl/com/example/umc_spring_first/domain/prefer/entity/QPrefer.java b/build/generated/querydsl/com/example/umc_spring_first/domain/prefer/entity/QPrefer.java
new file mode 100644
index 0000000..4899bdc
--- /dev/null
+++ b/build/generated/querydsl/com/example/umc_spring_first/domain/prefer/entity/QPrefer.java
@@ -0,0 +1,50 @@
+package com.example.umc_spring_first.domain.prefer.entity;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.dsl.StringTemplate;
+
+import com.querydsl.core.types.PathMetadata;
+import com.querydsl.core.annotations.Generated;
+import com.querydsl.core.types.Path;
+
+
+/**
+ * QPrefer is a Querydsl query type for Prefer
+ */
+@SuppressWarnings("this-escape")
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QPrefer extends EntityPathBase {
+
+ private static final long serialVersionUID = -1184394372L;
+
+ public static final QPrefer prefer = new QPrefer("prefer");
+
+ public final com.example.umc_spring_first.global.entity.QBaseTimeEntity _super = new com.example.umc_spring_first.global.entity.QBaseTimeEntity(this);
+
+ //inherited
+ public final DateTimePath createdAt = _super.createdAt;
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ public final StringPath menu = createString("menu");
+
+ //inherited
+ public final DateTimePath updatedAt = _super.updatedAt;
+
+ public QPrefer(String variable) {
+ super(Prefer.class, forVariable(variable));
+ }
+
+ public QPrefer(Path extends Prefer> path) {
+ super(path.getType(), path.getMetadata());
+ }
+
+ public QPrefer(PathMetadata metadata) {
+ super(Prefer.class, metadata);
+ }
+
+}
+
diff --git a/build/generated/querydsl/com/example/umc_spring_first/domain/prefer/entity/QUserPrefer.java b/build/generated/querydsl/com/example/umc_spring_first/domain/prefer/entity/QUserPrefer.java
new file mode 100644
index 0000000..7e0ecaa
--- /dev/null
+++ b/build/generated/querydsl/com/example/umc_spring_first/domain/prefer/entity/QUserPrefer.java
@@ -0,0 +1,57 @@
+package com.example.umc_spring_first.domain.prefer.entity;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.dsl.StringTemplate;
+
+import com.querydsl.core.types.PathMetadata;
+import com.querydsl.core.annotations.Generated;
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.dsl.PathInits;
+
+
+/**
+ * QUserPrefer is a Querydsl query type for UserPrefer
+ */
+@SuppressWarnings("this-escape")
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QUserPrefer extends EntityPathBase {
+
+ private static final long serialVersionUID = 2098303271L;
+
+ private static final PathInits INITS = PathInits.DIRECT2;
+
+ public static final QUserPrefer userPrefer = new QUserPrefer("userPrefer");
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ public final QPrefer prefer;
+
+ public final com.example.umc_spring_first.domain.user.entity.QUser user;
+
+ public QUserPrefer(String variable) {
+ this(UserPrefer.class, forVariable(variable), INITS);
+ }
+
+ public QUserPrefer(Path extends UserPrefer> path) {
+ this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS));
+ }
+
+ public QUserPrefer(PathMetadata metadata) {
+ this(metadata, PathInits.getFor(metadata, INITS));
+ }
+
+ public QUserPrefer(PathMetadata metadata, PathInits inits) {
+ this(UserPrefer.class, metadata, inits);
+ }
+
+ public QUserPrefer(Class extends UserPrefer> type, PathMetadata metadata, PathInits inits) {
+ super(type, metadata, inits);
+ this.prefer = inits.isInitialized("prefer") ? new QPrefer(forProperty("prefer")) : null;
+ this.user = inits.isInitialized("user") ? new com.example.umc_spring_first.domain.user.entity.QUser(forProperty("user")) : null;
+ }
+
+}
+
diff --git a/build/generated/querydsl/com/example/umc_spring_first/domain/review/entity/QReview.java b/build/generated/querydsl/com/example/umc_spring_first/domain/review/entity/QReview.java
new file mode 100644
index 0000000..c66f61b
--- /dev/null
+++ b/build/generated/querydsl/com/example/umc_spring_first/domain/review/entity/QReview.java
@@ -0,0 +1,67 @@
+package com.example.umc_spring_first.domain.review.entity;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.dsl.StringTemplate;
+
+import com.querydsl.core.types.PathMetadata;
+import com.querydsl.core.annotations.Generated;
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.dsl.PathInits;
+
+
+/**
+ * QReview is a Querydsl query type for Review
+ */
+@SuppressWarnings("this-escape")
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QReview extends EntityPathBase {
+
+ private static final long serialVersionUID = -625655796L;
+
+ private static final PathInits INITS = PathInits.DIRECT2;
+
+ public static final QReview review = new QReview("review");
+
+ public final StringPath content = createString("content");
+
+ public final DateTimePath createAt = createDateTime("createAt", java.time.LocalDateTime.class);
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ public final StringPath image = createString("image");
+
+ public final NumberPath rating = createNumber("rating", Float.class);
+
+ public final com.example.umc_spring_first.domain.store.entity.QStore store;
+
+ public final DateTimePath updateAt = createDateTime("updateAt", java.time.LocalDateTime.class);
+
+ public final com.example.umc_spring_first.domain.user.entity.QUser user;
+
+ public QReview(String variable) {
+ this(Review.class, forVariable(variable), INITS);
+ }
+
+ public QReview(Path extends Review> 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 extends Review> type, PathMetadata metadata, PathInits inits) {
+ super(type, metadata, inits);
+ this.store = inits.isInitialized("store") ? new com.example.umc_spring_first.domain.store.entity.QStore(forProperty("store")) : null;
+ this.user = inits.isInitialized("user") ? new com.example.umc_spring_first.domain.user.entity.QUser(forProperty("user")) : null;
+ }
+
+}
+
diff --git a/build/generated/querydsl/com/example/umc_spring_first/domain/store/entity/QStore.java b/build/generated/querydsl/com/example/umc_spring_first/domain/store/entity/QStore.java
new file mode 100644
index 0000000..04f01fe
--- /dev/null
+++ b/build/generated/querydsl/com/example/umc_spring_first/domain/store/entity/QStore.java
@@ -0,0 +1,57 @@
+package com.example.umc_spring_first.domain.store.entity;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.dsl.StringTemplate;
+
+import com.querydsl.core.types.PathMetadata;
+import com.querydsl.core.annotations.Generated;
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.dsl.PathInits;
+
+
+/**
+ * QStore is a Querydsl query type for Store
+ */
+@SuppressWarnings("this-escape")
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QStore extends EntityPathBase {
+
+ private static final long serialVersionUID = -379107614L;
+
+ public static final QStore store = new QStore("store");
+
+ public final StringPath address = createString("address");
+
+ public final DateTimePath createAt = createDateTime("createAt", java.time.LocalDateTime.class);
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ public final ListPath missions = this.createList("missions", com.example.umc_spring_first.domain.mission.entity.Mission.class, com.example.umc_spring_first.domain.mission.entity.QMission.class, PathInits.DIRECT2);
+
+ public final StringPath name = createString("name");
+
+ public final StringPath ownerNumber = createString("ownerNumber");
+
+ public final StringPath phone = createString("phone");
+
+ public final ListPath reviews = this.createList("reviews", com.example.umc_spring_first.domain.review.entity.Review.class, com.example.umc_spring_first.domain.review.entity.QReview.class, PathInits.DIRECT2);
+
+ public final DateTimePath updateAt = createDateTime("updateAt", java.time.LocalDateTime.class);
+
+ public QStore(String variable) {
+ super(Store.class, forVariable(variable));
+ }
+
+ public QStore(Path extends Store> path) {
+ super(path.getType(), path.getMetadata());
+ }
+
+ public QStore(PathMetadata metadata) {
+ super(Store.class, metadata);
+ }
+
+}
+
diff --git a/build/generated/querydsl/com/example/umc_spring_first/domain/user/entity/QUser.java b/build/generated/querydsl/com/example/umc_spring_first/domain/user/entity/QUser.java
new file mode 100644
index 0000000..a80207f
--- /dev/null
+++ b/build/generated/querydsl/com/example/umc_spring_first/domain/user/entity/QUser.java
@@ -0,0 +1,69 @@
+package com.example.umc_spring_first.domain.user.entity;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.dsl.StringTemplate;
+
+import com.querydsl.core.types.PathMetadata;
+import com.querydsl.core.annotations.Generated;
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.dsl.PathInits;
+
+
+/**
+ * QUser is a Querydsl query type for User
+ */
+@SuppressWarnings("this-escape")
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QUser extends EntityPathBase {
+
+ private static final long serialVersionUID = -2040355534L;
+
+ public static final QUser user = new QUser("user");
+
+ public final com.example.umc_spring_first.global.entity.QBaseTimeEntity _super = new com.example.umc_spring_first.global.entity.QBaseTimeEntity(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", com.example.umc_spring_first.domain.user.enums.Gender.class);
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ public final StringPath name = createString("name");
+
+ public final StringPath password = createString("password");
+
+ public final StringPath phone = createString("phone");
+
+ public final NumberPath point = createNumber("point", Integer.class);
+
+ public final EnumPath role = createEnum("role", com.example.umc_spring_first.global.auth.enums.Role.class);
+
+ //inherited
+ public final DateTimePath updatedAt = _super.updatedAt;
+
+ public final ListPath userFoodList = this.createList("userFoodList", com.example.umc_spring_first.domain.user.entity.mapping.UserFood.class, com.example.umc_spring_first.domain.user.entity.mapping.QUserFood.class, PathInits.DIRECT2);
+
+ public QUser(String variable) {
+ super(User.class, forVariable(variable));
+ }
+
+ public QUser(Path extends User> path) {
+ super(path.getType(), path.getMetadata());
+ }
+
+ public QUser(PathMetadata metadata) {
+ super(User.class, metadata);
+ }
+
+}
+
diff --git a/build/generated/querydsl/com/example/umc_spring_first/global/entity/QBaseTimeEntity.java b/build/generated/querydsl/com/example/umc_spring_first/global/entity/QBaseTimeEntity.java
new file mode 100644
index 0000000..2ed4ad7
--- /dev/null
+++ b/build/generated/querydsl/com/example/umc_spring_first/global/entity/QBaseTimeEntity.java
@@ -0,0 +1,42 @@
+package com.example.umc_spring_first.global.entity;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.dsl.StringTemplate;
+
+import com.querydsl.core.types.PathMetadata;
+import com.querydsl.core.annotations.Generated;
+import com.querydsl.core.types.Path;
+
+
+/**
+ * QBaseTimeEntity is a Querydsl query type for BaseTimeEntity
+ */
+@SuppressWarnings("this-escape")
+@Generated("com.querydsl.codegen.DefaultSupertypeSerializer")
+public class QBaseTimeEntity extends EntityPathBase {
+
+ private static final long serialVersionUID = 945504652L;
+
+ public static final QBaseTimeEntity baseTimeEntity = new QBaseTimeEntity("baseTimeEntity");
+
+ public final DateTimePath createdAt = createDateTime("createdAt", java.time.LocalDateTime.class);
+
+ public final DateTimePath updatedAt = createDateTime("updatedAt", java.time.LocalDateTime.class);
+
+ public QBaseTimeEntity(String variable) {
+ super(BaseTimeEntity.class, forVariable(variable));
+ }
+
+ public QBaseTimeEntity(Path extends BaseTimeEntity> path) {
+ super(path.getType(), path.getMetadata());
+ }
+
+ public QBaseTimeEntity(PathMetadata metadata) {
+ super(BaseTimeEntity.class, metadata);
+ }
+
+}
+
diff --git a/build/resolvedMainClassName b/build/resolvedMainClassName
new file mode 100644
index 0000000..dbace84
--- /dev/null
+++ b/build/resolvedMainClassName
@@ -0,0 +1 @@
+com.example.umc_spring_first.UmcSpringFirstApplication
\ No newline at end of file
diff --git a/build/tmp/compileJava/previous-compilation-data.bin b/build/tmp/compileJava/previous-compilation-data.bin
new file mode 100644
index 0000000..5d5e403
Binary files /dev/null and b/build/tmp/compileJava/previous-compilation-data.bin differ
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..d64cd49
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..1af9e09
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..1aa94a4
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,249 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..6689b85
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,92 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/mission/controller/MissionController.java b/mission/controller/MissionController.java
new file mode 100644
index 0000000..bf00b2a
--- /dev/null
+++ b/mission/controller/MissionController.java
@@ -0,0 +1,104 @@
+package com.example.umc_spring_first.domain.mission.controller;
+
+import com.example.umc_spring_first.domain.mission.dto.req.MissionReqDTO;
+import com.example.umc_spring_first.domain.mission.dto.res.MissionResDTO;
+import com.example.umc_spring_first.domain.mission.exception.code.MissionSuccessCode;
+import com.example.umc_spring_first.domain.mission.service.MissionQueryService;
+import com.example.umc_spring_first.domain.mission.service.MissionService;
+import com.example.umc_spring_first.domain.mission.service.UserMissionService;
+import com.example.umc_spring_first.global.apiPayload.ApiResponse;
+import com.example.umc_spring_first.global.apiPayload.annotation.ValidPage;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api")
+@RequiredArgsConstructor
+@Validated
+public class MissionController {
+
+ private final MissionService missionService;
+ private final UserMissionService userMissionService;
+ private final MissionQueryService missionQueryService;
+
+ // 특정 가게의 미션 목록
+ @Operation(
+ summary = "특정 가게의 미션 목록 조회",
+ description = "storeId 에 해당하는 가게의 미션을 page 단위(1페이지당 10개)로 조회합니다."
+ )
+ @ApiResponses({
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "미션 조회 성공"),
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "유효하지 않은 page 값입니다."),
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "가게 또는 미션을 찾을 수 없습니다.")
+ })
+ @GetMapping("/stores/{storeId}/missions")
+ public ApiResponse getStoreMissions(
+ @PathVariable Long storeId,
+ @RequestParam @ValidPage Integer page
+ ) {
+ return ApiResponse.onSuccess(
+ MissionSuccessCode.FOUND,
+ missionQueryService.getStoreMissions(storeId, page)
+ );
+ }
+
+ // 내가 진행중인 미션 목록
+ @Operation(
+ summary = "내가 진행중인 미션 목록 조회",
+ description = "로그인 미구현 → userId=1 로 가정하고, IN_PROGRESS 상태의 미션을 page 단위로 조회합니다."
+ )
+ @ApiResponses({
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "미션 조회 성공"),
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "유효하지 않은 page 값입니다.")
+ })
+ @GetMapping("/missions/my")
+ public ApiResponse getMyInProgressMissions(
+ @RequestParam @ValidPage Integer page
+ ) {
+ Long userId = 1L; // 로그인 미구현
+
+ return ApiResponse.onSuccess(
+ MissionSuccessCode.FOUND,
+ missionQueryService.getMyInProgressMissions(userId, page)
+ );
+ }
+
+ // 가게에 미션 추가하기 API
+ @Operation(
+ summary = "가게에 미션 추가",
+ description = "요청 바디에 storeId, point, description 을 담아 새로운 미션을 생성합니다."
+ )
+ @ApiResponses({
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "미션 생성 성공"),
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "가게를 찾을 수 없습니다.")
+ })
+ @PostMapping("/missions")
+ public ApiResponse createMission(
+ @RequestBody MissionReqDTO.CreateMissionRequest req
+ ) {
+ return ApiResponse.onSuccess(
+ MissionSuccessCode.CREATED,
+ missionService.createMission(req)
+ );
+ }
+
+ // 미션 도전하기 API
+ @Operation(
+ summary = "미션 도전하기",
+ description = "missionId 에 해당하는 미션을 UserMission에 추가하여 도전 상태로 저장합니다. (userId=1 가정)"
+ )
+ @ApiResponses({
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "미션 도전 등록 성공"),
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "미션을 찾을 수 없습니다.")
+ })
+ @PostMapping("/missions/{missionId}/challenge")
+ public ApiResponse challengeMission(
+ @PathVariable Long missionId
+ ) {
+ Long id = userMissionService.challengeMission(missionId);
+ return ApiResponse.onSuccess(MissionSuccessCode.CREATED, id);
+ }
+}
diff --git a/mission/converter/MissionConverter.java b/mission/converter/MissionConverter.java
new file mode 100644
index 0000000..9967429
--- /dev/null
+++ b/mission/converter/MissionConverter.java
@@ -0,0 +1,30 @@
+package com.example.umc_spring_first.domain.mission.converter;
+
+import com.example.umc_spring_first.domain.mission.dto.res.MissionResDTO;
+import com.example.umc_spring_first.domain.mission.entity.Mission;
+import org.springframework.data.domain.Page;
+
+public class MissionConverter {
+
+ // 미션 생성 응답
+ public static MissionResDTO.CreateMissionResponse toCreateMissionResponse(Mission mission) {
+ return MissionResDTO.CreateMissionResponse.builder()
+ .missionId(mission.getId())
+ .createdAt(mission.getCreateAt())
+ .build();
+ }
+
+ // 미션 목록 페이지 -> DTO
+ public static MissionResDTO.MissionPreviewListDTO toMissionPreviewListDTO(
+ Page page
+ ) {
+ return MissionResDTO.MissionPreviewListDTO.builder()
+ .missionList(page.getContent().stream().toList()) // Stream 사용
+ .listSize(page.getSize())
+ .totalPage(page.getTotalPages())
+ .totalElements(page.getTotalElements())
+ .isFirst(page.isFirst())
+ .isLast(page.isLast())
+ .build();
+ }
+}
diff --git a/mission/dto/req/MissionReqDTO.java b/mission/dto/req/MissionReqDTO.java
new file mode 100644
index 0000000..25c3d8d
--- /dev/null
+++ b/mission/dto/req/MissionReqDTO.java
@@ -0,0 +1,18 @@
+package com.example.umc_spring_first.domain.mission.dto.req;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+public class MissionReqDTO {
+
+ // 가게에 미션 추가하기 API용 요청 DTO
+ @Getter
+ @Builder
+ @AllArgsConstructor
+ public static class CreateMissionRequest {
+ private Long storeId; // 어떤 가게의 미션인지
+ private Integer point; // 미션 포인트
+ private String description;// 미션 설명
+ }
+}
diff --git a/mission/dto/res/MissionResDTO.java b/mission/dto/res/MissionResDTO.java
new file mode 100644
index 0000000..8088e33
--- /dev/null
+++ b/mission/dto/res/MissionResDTO.java
@@ -0,0 +1,38 @@
+package com.example.umc_spring_first.domain.mission.dto.res;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+public class MissionResDTO {
+
+ // 미션 생성 응답 DTO
+ @Getter
+ @Builder
+ @AllArgsConstructor
+ public static class CreateMissionResponse {
+ private Long missionId;
+ private LocalDateTime createdAt;
+ }
+
+ @Builder
+ public record MissionPreviewListDTO(
+ List missionList,
+ Integer listSize,
+ Integer totalPage,
+ Long totalElements,
+ Boolean isFirst,
+ Boolean isLast
+ ) {}
+
+ @Builder
+ public record MissionPreviewDTO(
+ Long missionId,
+ String storeName,
+ Integer point,
+ String description
+ ) {}
+}
diff --git a/mission/entity/Mission.java b/mission/entity/Mission.java
new file mode 100644
index 0000000..ceb05c3
--- /dev/null
+++ b/mission/entity/Mission.java
@@ -0,0 +1,29 @@
+package com.example.umc_spring_first.domain.mission.entity;
+
+import com.example.umc_spring_first.domain.store.entity.Store;
+import jakarta.persistence.*;
+import lombok.*;
+import java.time.LocalDateTime;
+import java.util.*;
+
+@Entity
+@Table(name = "mission")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
+public class Mission {
+
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "store_id", nullable = false)
+ private Store store;
+
+ private Integer point;
+ private LocalDateTime createAt;
+ private LocalDateTime updateAt;
+ private String status;
+ private String description;
+
+ @OneToMany(mappedBy = "mission", cascade = CascadeType.ALL, orphanRemoval = true)
+ private List userMissions = new ArrayList<>();
+}
diff --git a/mission/entity/UserMission.java b/mission/entity/UserMission.java
new file mode 100644
index 0000000..a116989
--- /dev/null
+++ b/mission/entity/UserMission.java
@@ -0,0 +1,32 @@
+package com.example.umc_spring_first.domain.mission.entity;
+
+import com.example.umc_spring_first.domain.mission.enums.UserMissionStatus;
+import com.example.umc_spring_first.domain.user.entity.User;
+import jakarta.persistence.*;
+import lombok.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "user_mission")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
+public class UserMission {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id", nullable = false)
+ private User user;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "mission_id", nullable = false)
+ private Mission mission;
+
+ private LocalDateTime deadline;
+ private LocalDateTime createAt;
+ private LocalDateTime updateAt;
+
+ @Enumerated(EnumType.STRING) // enum이라 추가
+ private UserMissionStatus status; // enum으로 변경
+}
diff --git a/mission/enums/MissionStatus.java b/mission/enums/MissionStatus.java
new file mode 100644
index 0000000..6702d95
--- /dev/null
+++ b/mission/enums/MissionStatus.java
@@ -0,0 +1,5 @@
+package com.example.umc_spring_first.domain.mission.enums;
+
+public enum MissionStatus {
+ ONGOING, DONE, PAUSED
+}
diff --git a/mission/enums/UserMissionStatus.java b/mission/enums/UserMissionStatus.java
new file mode 100644
index 0000000..9d05b7d
--- /dev/null
+++ b/mission/enums/UserMissionStatus.java
@@ -0,0 +1,6 @@
+package com.example.umc_spring_first.domain.mission.enums;
+
+public enum UserMissionStatus {
+ IN_PROGRESS,
+ COMPLETED
+}
diff --git a/mission/exception/MissionException.java b/mission/exception/MissionException.java
new file mode 100644
index 0000000..7477567
--- /dev/null
+++ b/mission/exception/MissionException.java
@@ -0,0 +1,10 @@
+package com.example.umc_spring_first.domain.mission.exception;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import com.example.umc_spring_first.global.apiPayload.exception.GeneralException;
+
+public class MissionException extends GeneralException {
+ public MissionException(BaseErrorCode code) {
+ super(code);
+ }
+}
diff --git a/mission/exception/code/MissionErrorCode.java b/mission/exception/code/MissionErrorCode.java
new file mode 100644
index 0000000..1a8fdc9
--- /dev/null
+++ b/mission/exception/code/MissionErrorCode.java
@@ -0,0 +1,22 @@
+package com.example.umc_spring_first.domain.mission.exception.code;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+@AllArgsConstructor
+public enum MissionErrorCode implements BaseErrorCode {
+
+ STORE_NOT_FOUND(HttpStatus.NOT_FOUND,
+ "MISSION404_1",
+ "가게를 찾을 수 없습니다."),
+ MISSION_NOT_FOUND(HttpStatus.NOT_FOUND,
+ "MISSION404_2",
+ "미션을 찾을 수 없습니다.");
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+}
diff --git a/mission/exception/code/MissionSuccessCode.java b/mission/exception/code/MissionSuccessCode.java
new file mode 100644
index 0000000..a1b62d9
--- /dev/null
+++ b/mission/exception/code/MissionSuccessCode.java
@@ -0,0 +1,27 @@
+package com.example.umc_spring_first.domain.mission.exception.code;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseSuccessCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+@AllArgsConstructor
+public enum MissionSuccessCode implements BaseSuccessCode {
+
+ CREATED(HttpStatus.CREATED,
+ "MISSION201_1",
+ "성공적으로 미션을 생성했습니다."),
+
+ CHALLENGED(HttpStatus.CREATED,
+ "MISSION201_2",
+ "성공적으로 미션 도전을 등록했습니다."),
+
+ FOUND(HttpStatus.OK,
+ "MISSION200_1",
+ "성공적으로 미션을 조회했습니다.");
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+}
diff --git a/mission/repository/MissionQueryDsl.java b/mission/repository/MissionQueryDsl.java
new file mode 100644
index 0000000..3ef6817
--- /dev/null
+++ b/mission/repository/MissionQueryDsl.java
@@ -0,0 +1,20 @@
+package com.example.umc_spring_first.domain.mission.repository;
+
+import com.example.umc_spring_first.domain.mission.dto.res.MissionResDTO;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+public interface MissionQueryDsl {
+
+ // 2번: 특정 가게의 미션 목록
+ Page searchStoreMissions(
+ Long storeId,
+ Pageable pageable
+ );
+
+ // 3번: 내가 진행중인 미션 목록
+ Page searchMyInProgressMissions(
+ Long userId,
+ Pageable pageable
+ );
+}
diff --git a/mission/repository/MissionRepository.java b/mission/repository/MissionRepository.java
new file mode 100644
index 0000000..e575e27
--- /dev/null
+++ b/mission/repository/MissionRepository.java
@@ -0,0 +1,7 @@
+package com.example.umc_spring_first.domain.mission.repository;
+
+import com.example.umc_spring_first.domain.mission.entity.Mission;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface MissionRepository extends JpaRepository, MissionQueryDsl {
+}
diff --git a/mission/repository/MissionRepositoryImpl.java b/mission/repository/MissionRepositoryImpl.java
new file mode 100644
index 0000000..fcd5501
--- /dev/null
+++ b/mission/repository/MissionRepositoryImpl.java
@@ -0,0 +1,100 @@
+package com.example.umc_spring_first.domain.mission.repository;
+
+import com.example.umc_spring_first.domain.mission.dto.res.MissionResDTO;
+import com.example.umc_spring_first.domain.mission.entity.QMission;
+import com.example.umc_spring_first.domain.mission.entity.QUserMission;
+import com.example.umc_spring_first.domain.mission.enums.UserMissionStatus;
+import com.example.umc_spring_first.domain.store.entity.QStore;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.*;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+@RequiredArgsConstructor
+public class MissionRepositoryImpl implements MissionQueryDsl {
+
+ private final JPAQueryFactory query;
+
+ // 2번: 특정 가게의 미션 목록
+ @Override
+ public Page searchStoreMissions(
+ Long storeId,
+ Pageable pageable
+ ) {
+ QMission m = QMission.mission;
+ QStore s = QStore.store;
+
+ List content = query
+ .select(
+ com.querydsl.core.types.Projections.constructor(
+ MissionResDTO.MissionPreviewDTO.class,
+ m.id,
+ s.name,
+ m.point,
+ m.description
+ )
+ )
+ .from(m)
+ .join(m.store, s)
+ .where(m.store.id.eq(storeId))
+ .orderBy(m.id.desc())
+ .offset(pageable.getOffset())
+ .limit(pageable.getPageSize())
+ .fetch();
+
+ long total = query
+ .select(m.count())
+ .from(m)
+ .where(m.store.id.eq(storeId))
+ .fetchOne();
+
+ return new PageImpl<>(content, pageable, total);
+ }
+
+ // 3번: 내가 진행중인 미션 목록
+ @Override
+ public Page searchMyInProgressMissions(
+ Long userId,
+ Pageable pageable
+ ) {
+ QUserMission um = QUserMission.userMission;
+ QMission m = QMission.mission;
+ QStore s = QStore.store;
+
+ List content = query
+ .select(
+ com.querydsl.core.types.Projections.constructor(
+ MissionResDTO.MissionPreviewDTO.class,
+ m.id,
+ s.name,
+ m.point,
+ m.description
+ )
+ )
+ .from(um)
+ .join(um.mission, m)
+ .join(m.store, s)
+ .where(
+ um.user.id.eq(userId)
+ .and(um.status.eq(UserMissionStatus.IN_PROGRESS))
+ )
+ .orderBy(um.id.desc())
+ .offset(pageable.getOffset())
+ .limit(pageable.getPageSize())
+ .fetch();
+
+ long total = query
+ .select(um.count())
+ .from(um)
+ .where(
+ um.user.id.eq(userId)
+ .and(um.status.eq(UserMissionStatus.IN_PROGRESS))
+ )
+ .fetchOne();
+
+ return new PageImpl<>(content, pageable, total);
+ }
+}
diff --git a/mission/repository/UserMissionQueryDsl.java b/mission/repository/UserMissionQueryDsl.java
new file mode 100644
index 0000000..c5aa238
--- /dev/null
+++ b/mission/repository/UserMissionQueryDsl.java
@@ -0,0 +1,15 @@
+package com.example.umc_spring_first.domain.mission.repository;
+
+import com.example.umc_spring_first.domain.mission.dto.res.MissionResDTO;
+import com.example.umc_spring_first.domain.mission.enums.UserMissionStatus;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+public interface UserMissionQueryDsl {
+
+ Page searchMyMissions(
+ Long userId,
+ UserMissionStatus status,
+ Pageable pageable
+ );
+}
diff --git a/mission/repository/UserMissionRepository.java b/mission/repository/UserMissionRepository.java
new file mode 100644
index 0000000..7a32382
--- /dev/null
+++ b/mission/repository/UserMissionRepository.java
@@ -0,0 +1,10 @@
+package com.example.umc_spring_first.domain.mission.repository;
+
+import com.example.umc_spring_first.domain.mission.entity.UserMission;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface UserMissionRepository
+ extends JpaRepository, UserMissionQueryDsl {
+
+ // 여기에는 별도 @Query 메서드 두지 말기
+}
diff --git a/mission/repository/UserMissionRepositoryImpl.java b/mission/repository/UserMissionRepositoryImpl.java
new file mode 100644
index 0000000..23887f4
--- /dev/null
+++ b/mission/repository/UserMissionRepositoryImpl.java
@@ -0,0 +1,65 @@
+package com.example.umc_spring_first.domain.mission.repository;
+
+import com.example.umc_spring_first.domain.mission.dto.res.MissionResDTO;
+import com.example.umc_spring_first.domain.mission.entity.QMission;
+import com.example.umc_spring_first.domain.mission.entity.QUserMission;
+import com.example.umc_spring_first.domain.mission.enums.UserMissionStatus;
+import com.example.umc_spring_first.domain.store.entity.QStore;
+import com.querydsl.core.types.Projections;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.*;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+@RequiredArgsConstructor
+public class UserMissionRepositoryImpl implements UserMissionQueryDsl {
+
+ private final JPAQueryFactory query;
+
+ @Override
+ public Page searchMyMissions(
+ Long userId,
+ UserMissionStatus status,
+ Pageable pageable
+ ) {
+ QUserMission um = QUserMission.userMission;
+ QMission m = QMission.mission;
+ QStore s = QStore.store;
+
+ List content = query
+ .select(Projections.constructor(
+ MissionResDTO.MissionPreviewDTO.class,
+ um.id,
+ m.point,
+ s.name,
+ m.description,
+ um.status,
+ um.deadline
+ ))
+ .from(um)
+ .join(um.mission, m)
+ .join(m.store, s)
+ .where(
+ um.user.id.eq(userId),
+ um.status.eq(status)
+ )
+ .orderBy(um.id.desc())
+ .offset(pageable.getOffset())
+ .limit(pageable.getPageSize())
+ .fetch();
+
+ Long total = query
+ .select(um.count())
+ .from(um)
+ .where(
+ um.user.id.eq(userId),
+ um.status.eq(status)
+ )
+ .fetchOne();
+
+ return new PageImpl<>(content, pageable, total == null ? 0 : total);
+ }
+}
diff --git a/mission/service/MissionQueryService.java b/mission/service/MissionQueryService.java
new file mode 100644
index 0000000..b5b4b6b
--- /dev/null
+++ b/mission/service/MissionQueryService.java
@@ -0,0 +1,43 @@
+package com.example.umc_spring_first.domain.mission.service;
+
+import com.example.umc_spring_first.domain.mission.converter.MissionConverter;
+import com.example.umc_spring_first.domain.mission.dto.res.MissionResDTO;
+import com.example.umc_spring_first.domain.mission.enums.UserMissionStatus;
+import com.example.umc_spring_first.domain.mission.repository.MissionRepository;
+import com.example.umc_spring_first.domain.mission.repository.UserMissionRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional(readOnly = true)
+@RequiredArgsConstructor
+@Service
+public class MissionQueryService {
+
+ private final MissionRepository missionRepository;
+ private final UserMissionRepository userMissionRepository;
+
+ // 2번: 특정 가게의 미션 목록
+ public MissionResDTO.MissionPreviewListDTO getStoreMissions(Long storeId, int page) {
+ Pageable pageable = PageRequest.of(page - 1, 10);
+
+ var result = missionRepository.searchStoreMissions(storeId, pageable);
+
+ return MissionConverter.toMissionPreviewListDTO(result);
+ }
+
+ // 3번: 내가 진행중인 미션 목록
+ public MissionResDTO.MissionPreviewListDTO getMyInProgressMissions(Long userId, int page) {
+ Pageable pageable = PageRequest.of(page - 1, 10);
+
+ var result = userMissionRepository.searchMyMissions(
+ userId,
+ UserMissionStatus.IN_PROGRESS,
+ pageable
+ );
+
+ return MissionConverter.toMissionPreviewListDTO(result);
+ }
+}
diff --git a/mission/service/MissionService.java b/mission/service/MissionService.java
new file mode 100644
index 0000000..abc1a87
--- /dev/null
+++ b/mission/service/MissionService.java
@@ -0,0 +1,38 @@
+package com.example.umc_spring_first.domain.mission.service;
+
+import com.example.umc_spring_first.domain.mission.converter.MissionConverter;
+import com.example.umc_spring_first.domain.mission.dto.req.MissionReqDTO;
+import com.example.umc_spring_first.domain.mission.dto.res.MissionResDTO;
+import com.example.umc_spring_first.domain.mission.entity.Mission;
+import com.example.umc_spring_first.domain.mission.repository.MissionRepository;
+import com.example.umc_spring_first.domain.store.entity.Store;
+import com.example.umc_spring_first.domain.store.repository.StoreRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class MissionService {
+
+ private final MissionRepository missionRepository;
+ private final StoreRepository storeRepository;
+
+ // 3번: 가게에 미션 추가하기 API용
+ public MissionResDTO.CreateMissionResponse createMission(MissionReqDTO.CreateMissionRequest req) {
+
+ Store store = storeRepository.findById(req.getStoreId())
+ .orElseThrow(() -> new RuntimeException("가게를 찾을 수 없습니다."));
+
+ Mission mission = Mission.builder()
+ .store(store)
+ .point(req.getPoint())
+ .description(req.getDescription())
+ .build();
+
+ missionRepository.save(mission);
+
+ return MissionConverter.toCreateMissionResponse(mission);
+ }
+}
diff --git a/mission/service/UserMissionService.java b/mission/service/UserMissionService.java
new file mode 100644
index 0000000..3f30914
--- /dev/null
+++ b/mission/service/UserMissionService.java
@@ -0,0 +1,44 @@
+package com.example.umc_spring_first.domain.mission.service;
+
+import com.example.umc_spring_first.domain.mission.entity.Mission;
+import com.example.umc_spring_first.domain.mission.entity.UserMission;
+import com.example.umc_spring_first.domain.mission.enums.UserMissionStatus;
+import com.example.umc_spring_first.domain.mission.repository.MissionRepository;
+import com.example.umc_spring_first.domain.mission.repository.UserMissionRepository;
+import com.example.umc_spring_first.domain.user.entity.User;
+import com.example.umc_spring_first.domain.user.repository.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class UserMissionService {
+
+ private final UserMissionRepository userMissionRepository;
+ private final MissionRepository missionRepository;
+ private final UserRepository userRepository;
+
+ // 4번: 가게의 미션을 도전 중인 미션에 추가 (미션 도전하기)
+ public Long challengeMission(Long missionId) {
+
+ Long userId = 1L; // 로그인 미구현 → 하드코딩
+
+ User user = userRepository.findById(userId)
+ .orElseThrow(() -> new RuntimeException("유저 없음"));
+
+ Mission mission = missionRepository.findById(missionId)
+ .orElseThrow(() -> new RuntimeException("미션 없음"));
+
+ UserMission um = UserMission.builder()
+ .mission(mission)
+ .user(user)
+ .status(UserMissionStatus.IN_PROGRESS)
+ .build();
+
+ userMissionRepository.save(um);
+
+ return um.getId();
+ }
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..17e9025
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'umc_spring_first'
diff --git a/src/main/java/com/example/umc_spring_first/UmcSpringFirstApplication.java b/src/main/java/com/example/umc_spring_first/UmcSpringFirstApplication.java
new file mode 100644
index 0000000..a005fbf
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/UmcSpringFirstApplication.java
@@ -0,0 +1,12 @@
+package com.example.umc_spring_first;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+
+@SpringBootApplication
+public class UmcSpringFirstApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(UmcSpringFirstApplication.class, args);
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/config/QuerydslConfig.java b/src/main/java/com/example/umc_spring_first/config/QuerydslConfig.java
new file mode 100644
index 0000000..51c62bc
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/config/QuerydslConfig.java
@@ -0,0 +1,19 @@
+package com.example.umc_spring_first.config;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class QuerydslConfig {
+
+ @PersistenceContext
+ private EntityManager em; // jakarta import
+
+ @Bean
+ public JPAQueryFactory jpaQueryFactory() {
+ return new JPAQueryFactory(em);
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/inquiry/entity/Inquiry.java b/src/main/java/com/example/umc_spring_first/domain/inquiry/entity/Inquiry.java
new file mode 100644
index 0000000..795308a
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/inquiry/entity/Inquiry.java
@@ -0,0 +1,17 @@
+package com.example.umc_spring_first.domain.inquiry.entity;
+
+import com.example.umc_spring_first.domain.user.entity.User;
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity
+@Table(name = "Inquiry")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
+public class Inquiry {
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id", nullable = false)
+ private User user;
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/inquiry/repository/InquiryRepository.java b/src/main/java/com/example/umc_spring_first/domain/inquiry/repository/InquiryRepository.java
new file mode 100644
index 0000000..a429aee
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/inquiry/repository/InquiryRepository.java
@@ -0,0 +1,7 @@
+package com.example.umc_spring_first.domain.inquiry.repository;
+
+import com.example.umc_spring_first.domain.inquiry.entity.Inquiry;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface InquiryRepository extends JpaRepository {
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/controller/MissionController.java b/src/main/java/com/example/umc_spring_first/domain/mission/controller/MissionController.java
new file mode 100644
index 0000000..9a04f39
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/controller/MissionController.java
@@ -0,0 +1,104 @@
+package com.example.umc_spring_first.domain.mission.controller;
+
+import com.example.umc_spring_first.domain.mission.dto.req.MissionReqDTO;
+import com.example.umc_spring_first.domain.mission.dto.res.MissionResDTO;
+import com.example.umc_spring_first.domain.mission.exception.code.MissionSuccessCode;
+import com.example.umc_spring_first.domain.mission.service.MissionQueryService;
+import com.example.umc_spring_first.domain.mission.service.MissionService;
+import com.example.umc_spring_first.domain.mission.service.UserMissionService;
+import com.example.umc_spring_first.global.apiPayload.ApiResponse;
+import com.example.umc_spring_first.global.annotation.ValidPage;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api")
+@RequiredArgsConstructor
+@Validated
+public class MissionController {
+
+ private final MissionService missionService;
+ private final UserMissionService userMissionService;
+ private final MissionQueryService missionQueryService;
+
+ // 특정 가게의 미션 목록
+ @Operation(
+ summary = "특정 가게의 미션 목록 조회",
+ description = "storeId 에 해당하는 가게의 미션을 page 단위(1페이지당 10개)로 조회합니다."
+ )
+ @ApiResponses({
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "미션 조회 성공"),
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "유효하지 않은 page 값입니다."),
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "가게 또는 미션을 찾을 수 없습니다.")
+ })
+ @GetMapping("/stores/{storeId}/missions")
+ public ApiResponse getStoreMissions(
+ @PathVariable Long storeId,
+ @RequestParam @ValidPage Integer page
+ ) {
+ return ApiResponse.onSuccess(
+ MissionSuccessCode.FOUND,
+ missionQueryService.getStoreMissions(storeId, page)
+ );
+ }
+
+ // 내가 진행중인 미션 목록
+ @Operation(
+ summary = "내가 진행중인 미션 목록 조회",
+ description = "로그인 미구현 → userId=1 로 가정하고, IN_PROGRESS 상태의 미션을 page 단위로 조회합니다."
+ )
+ @ApiResponses({
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "미션 조회 성공"),
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "유효하지 않은 page 값입니다.")
+ })
+ @GetMapping("/missions/my")
+ public ApiResponse getMyInProgressMissions(
+ @RequestParam @ValidPage Integer page
+ ) {
+ Long userId = 1L; // 로그인 미구현
+
+ return ApiResponse.onSuccess(
+ MissionSuccessCode.FOUND,
+ missionQueryService.getMyInProgressMissions(userId, page) //실제로 조회한 미션 목록 데이터
+ );
+ }
+
+ // 가게에 미션 추가하기 API
+ @Operation(
+ summary = "가게에 미션 추가",
+ description = "요청 바디에 storeId, point, description 을 담아 새로운 미션을 생성합니다."
+ )
+ @ApiResponses({
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "미션 생성 성공"),
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "가게를 찾을 수 없습니다.")
+ })
+ @PostMapping("/missions")
+ public ApiResponse createMission(
+ @RequestBody MissionReqDTO.CreateMissionRequest req
+ ) {
+ return ApiResponse.onSuccess(
+ MissionSuccessCode.CREATED,
+ missionService.createMission(req)
+ );
+ }
+
+ // 미션 도전하기 API
+ @Operation(
+ summary = "미션 도전하기",
+ description = "missionId 에 해당하는 미션을 UserMission에 추가하여 도전 상태로 저장합니다. (userId=1 가정)"
+ )
+ @ApiResponses({
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "미션 도전 등록 성공"),
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "미션을 찾을 수 없습니다.")
+ })
+ @PostMapping("/missions/{missionId}/challenge")
+ public ApiResponse challengeMission(
+ @PathVariable Long missionId
+ ) {
+ Long id = userMissionService.challengeMission(missionId);
+ return ApiResponse.onSuccess(MissionSuccessCode.CREATED, id);
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/converter/MissionConverter.java b/src/main/java/com/example/umc_spring_first/domain/mission/converter/MissionConverter.java
new file mode 100644
index 0000000..966e6c0
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/converter/MissionConverter.java
@@ -0,0 +1,91 @@
+package com.example.umc_spring_first.domain.mission.converter;
+
+import com.example.umc_spring_first.domain.mission.dto.req.MissionReqDTO;
+import com.example.umc_spring_first.domain.mission.dto.res.MissionResDTO;
+import com.example.umc_spring_first.domain.mission.entity.Mission;
+import com.example.umc_spring_first.domain.mission.entity.UserMission;
+import com.example.umc_spring_first.domain.mission.enums.UserMissionStatus;
+import com.example.umc_spring_first.domain.store.entity.Store;
+import com.example.umc_spring_first.domain.user.entity.User;
+import org.springframework.data.domain.Page;
+
+public class MissionConverter {
+
+ // DTO → Entity
+ public static Mission toMissionEntity(MissionReqDTO.CreateMissionRequest req, Store store) {
+ return Mission.builder()
+ .store(store)
+ .point(req.getPoint())
+ .description(req.getDescription())
+ .build();
+ }
+
+ public static UserMission toUserMissionEntity(User user, Mission mission) {
+ return UserMission.builder()
+ .user(user)
+ .mission(mission)
+ .status(UserMissionStatus.IN_PROGRESS)
+ .build();
+ }
+
+ // Entity → DTO
+ public static MissionResDTO.CreateMissionResponse toCreateMissionResponse(Mission mission) {
+ return MissionResDTO.CreateMissionResponse.builder()
+ .missionId(mission.getId())
+ .createdAt(mission.getCreateAt())
+ .build();
+ }
+
+ public static MissionResDTO.MissionPreviewDTO toMissionPreviewDTO(UserMission um) {
+ Mission mission = um.getMission();
+
+ return MissionResDTO.MissionPreviewDTO.builder()
+ .userMissionId(um.getId())
+ .storeName(mission.getStore().getName())
+ .description(mission.getDescription())
+ .point(mission.getPoint())
+ .status(um.getStatus())
+ .deadline(um.getDeadline())
+ .build();
+ }
+
+ // Page → DTO
+ public static MissionResDTO.MissionPreviewListDTO toMissionPreviewList(Page page) {
+ return MissionResDTO.MissionPreviewListDTO.builder()
+ .missionList(
+ page.getContent().stream()
+ .map(MissionConverter::toMissionPreviewDTO)
+ .toList()
+ )
+ .listSize(page.getSize())
+ .totalPage(page.getTotalPages())
+ .totalElements(page.getTotalElements())
+ .isFirst(page.isFirst())
+ .isLast(page.isLast())
+ .build();
+ }
+
+ // Mission 목록 조회용
+ public static MissionResDTO.MissionPreviewListDTO toStoreMissionList(Page page) {
+ return MissionResDTO.MissionPreviewListDTO.builder()
+ .missionList(
+ page.getContent().stream()
+ .map(m -> MissionResDTO.MissionPreviewDTO.builder()
+ .userMissionId(null) // store mission 목록은 UserMission 없음
+ .storeName(m.getStore().getName())
+ .description(m.getDescription())
+ .point(m.getPoint())
+ .status(null)
+ .deadline(null)
+ .build()
+ )
+ .toList()
+ )
+ .listSize(page.getSize())
+ .totalPage(page.getTotalPages())
+ .totalElements(page.getTotalElements())
+ .isFirst(page.isFirst())
+ .isLast(page.isLast())
+ .build();
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/dto/req/MissionReqDTO.java b/src/main/java/com/example/umc_spring_first/domain/mission/dto/req/MissionReqDTO.java
new file mode 100644
index 0000000..25c3d8d
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/dto/req/MissionReqDTO.java
@@ -0,0 +1,18 @@
+package com.example.umc_spring_first.domain.mission.dto.req;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+public class MissionReqDTO {
+
+ // 가게에 미션 추가하기 API용 요청 DTO
+ @Getter
+ @Builder
+ @AllArgsConstructor
+ public static class CreateMissionRequest {
+ private Long storeId; // 어떤 가게의 미션인지
+ private Integer point; // 미션 포인트
+ private String description;// 미션 설명
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/dto/res/MissionResDTO.java b/src/main/java/com/example/umc_spring_first/domain/mission/dto/res/MissionResDTO.java
new file mode 100644
index 0000000..f754a85
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/dto/res/MissionResDTO.java
@@ -0,0 +1,41 @@
+package com.example.umc_spring_first.domain.mission.dto.res;
+
+import com.example.umc_spring_first.domain.mission.enums.UserMissionStatus;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+public class MissionResDTO {
+
+ // 미션 생성 응답 DTO
+ @Getter
+ @Builder
+ @AllArgsConstructor
+ public static class CreateMissionResponse {
+ private Long missionId;
+ private LocalDateTime createdAt;
+ }
+
+ @Builder
+ public record MissionPreviewListDTO(
+ List missionList,
+ Integer listSize,
+ Integer totalPage,
+ Long totalElements,
+ Boolean isFirst,
+ Boolean isLast
+ ) {}
+
+ @Builder
+ public record MissionPreviewDTO(
+ Long userMissionId,
+ String storeName,
+ String description,
+ Integer point,
+ UserMissionStatus status,
+ LocalDateTime deadline
+ ) {}
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/entity/Mission.java b/src/main/java/com/example/umc_spring_first/domain/mission/entity/Mission.java
new file mode 100644
index 0000000..ceb05c3
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/entity/Mission.java
@@ -0,0 +1,29 @@
+package com.example.umc_spring_first.domain.mission.entity;
+
+import com.example.umc_spring_first.domain.store.entity.Store;
+import jakarta.persistence.*;
+import lombok.*;
+import java.time.LocalDateTime;
+import java.util.*;
+
+@Entity
+@Table(name = "mission")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
+public class Mission {
+
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "store_id", nullable = false)
+ private Store store;
+
+ private Integer point;
+ private LocalDateTime createAt;
+ private LocalDateTime updateAt;
+ private String status;
+ private String description;
+
+ @OneToMany(mappedBy = "mission", cascade = CascadeType.ALL, orphanRemoval = true)
+ private List userMissions = new ArrayList<>();
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/entity/UserMission.java b/src/main/java/com/example/umc_spring_first/domain/mission/entity/UserMission.java
new file mode 100644
index 0000000..a116989
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/entity/UserMission.java
@@ -0,0 +1,32 @@
+package com.example.umc_spring_first.domain.mission.entity;
+
+import com.example.umc_spring_first.domain.mission.enums.UserMissionStatus;
+import com.example.umc_spring_first.domain.user.entity.User;
+import jakarta.persistence.*;
+import lombok.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "user_mission")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
+public class UserMission {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id", nullable = false)
+ private User user;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "mission_id", nullable = false)
+ private Mission mission;
+
+ private LocalDateTime deadline;
+ private LocalDateTime createAt;
+ private LocalDateTime updateAt;
+
+ @Enumerated(EnumType.STRING) // enum이라 추가
+ private UserMissionStatus status; // enum으로 변경
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/enums/MissionStatus.java b/src/main/java/com/example/umc_spring_first/domain/mission/enums/MissionStatus.java
new file mode 100644
index 0000000..6702d95
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/enums/MissionStatus.java
@@ -0,0 +1,5 @@
+package com.example.umc_spring_first.domain.mission.enums;
+
+public enum MissionStatus {
+ ONGOING, DONE, PAUSED
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/enums/UserMissionStatus.java b/src/main/java/com/example/umc_spring_first/domain/mission/enums/UserMissionStatus.java
new file mode 100644
index 0000000..9d05b7d
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/enums/UserMissionStatus.java
@@ -0,0 +1,6 @@
+package com.example.umc_spring_first.domain.mission.enums;
+
+public enum UserMissionStatus {
+ IN_PROGRESS,
+ COMPLETED
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/exception/MissionException.java b/src/main/java/com/example/umc_spring_first/domain/mission/exception/MissionException.java
new file mode 100644
index 0000000..7477567
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/exception/MissionException.java
@@ -0,0 +1,10 @@
+package com.example.umc_spring_first.domain.mission.exception;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import com.example.umc_spring_first.global.apiPayload.exception.GeneralException;
+
+public class MissionException extends GeneralException {
+ public MissionException(BaseErrorCode code) {
+ super(code);
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/exception/code/MissionErrorCode.java b/src/main/java/com/example/umc_spring_first/domain/mission/exception/code/MissionErrorCode.java
new file mode 100644
index 0000000..1a8fdc9
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/exception/code/MissionErrorCode.java
@@ -0,0 +1,22 @@
+package com.example.umc_spring_first.domain.mission.exception.code;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+@AllArgsConstructor
+public enum MissionErrorCode implements BaseErrorCode {
+
+ STORE_NOT_FOUND(HttpStatus.NOT_FOUND,
+ "MISSION404_1",
+ "가게를 찾을 수 없습니다."),
+ MISSION_NOT_FOUND(HttpStatus.NOT_FOUND,
+ "MISSION404_2",
+ "미션을 찾을 수 없습니다.");
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/exception/code/MissionSuccessCode.java b/src/main/java/com/example/umc_spring_first/domain/mission/exception/code/MissionSuccessCode.java
new file mode 100644
index 0000000..a1b62d9
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/exception/code/MissionSuccessCode.java
@@ -0,0 +1,27 @@
+package com.example.umc_spring_first.domain.mission.exception.code;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseSuccessCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+@AllArgsConstructor
+public enum MissionSuccessCode implements BaseSuccessCode {
+
+ CREATED(HttpStatus.CREATED,
+ "MISSION201_1",
+ "성공적으로 미션을 생성했습니다."),
+
+ CHALLENGED(HttpStatus.CREATED,
+ "MISSION201_2",
+ "성공적으로 미션 도전을 등록했습니다."),
+
+ FOUND(HttpStatus.OK,
+ "MISSION200_1",
+ "성공적으로 미션을 조회했습니다.");
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/repository/MissionQueryDsl.java b/src/main/java/com/example/umc_spring_first/domain/mission/repository/MissionQueryDsl.java
new file mode 100644
index 0000000..91a1304
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/repository/MissionQueryDsl.java
@@ -0,0 +1,14 @@
+package com.example.umc_spring_first.domain.mission.repository;
+
+import com.example.umc_spring_first.domain.mission.entity.Mission;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+public interface MissionQueryDsl {
+
+ // 2번: 특정 가게의 미션 목록 (엔티티 기반) / 구현체인 impl이 뭘 구현해야하는지 명시해줌.
+ Page searchStoreMissions(
+ Long storeId,
+ Pageable pageable
+ );
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/repository/MissionRepository.java b/src/main/java/com/example/umc_spring_first/domain/mission/repository/MissionRepository.java
new file mode 100644
index 0000000..e575e27
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/repository/MissionRepository.java
@@ -0,0 +1,7 @@
+package com.example.umc_spring_first.domain.mission.repository;
+
+import com.example.umc_spring_first.domain.mission.entity.Mission;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface MissionRepository extends JpaRepository, MissionQueryDsl {
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/repository/MissionRepositoryImpl.java b/src/main/java/com/example/umc_spring_first/domain/mission/repository/MissionRepositoryImpl.java
new file mode 100644
index 0000000..a543ee7
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/repository/MissionRepositoryImpl.java
@@ -0,0 +1,43 @@
+package com.example.umc_spring_first.domain.mission.repository;
+
+import com.example.umc_spring_first.domain.mission.entity.Mission;
+import com.example.umc_spring_first.domain.mission.entity.QMission;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.*;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+@RequiredArgsConstructor
+public class MissionRepositoryImpl implements MissionQueryDsl {
+
+ private final JPAQueryFactory query;
+
+ @Override
+ public Page searchStoreMissions(Long storeId, Pageable pageable) {
+
+ QMission m = QMission.mission;
+
+ List content = query
+ .select(m)
+ .from(m)
+ .where(m.store.id.eq(storeId))
+ .orderBy(m.id.desc())
+ .offset(pageable.getOffset())
+ .limit(pageable.getPageSize())
+ .fetch();
+
+ Long total = query
+ .select(m.count())
+ .from(m)
+ .where(m.store.id.eq(storeId))
+ .fetchOne();
+
+ return new PageImpl<>(content, pageable, total == null ? 0 : total);
+ //QueryDSL을 사용할 때는 직접 페이징 쿼리를 실행하므로
+ //스프링이 자동으로 Page 객체를 만들어주지 않는다. 그래서 우리가 직접 Page 객체를 만들어줘야 한다.
+ //그 역할이 바로 PageImpl 이다.
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/repository/UserMissionQueryDsl.java b/src/main/java/com/example/umc_spring_first/domain/mission/repository/UserMissionQueryDsl.java
new file mode 100644
index 0000000..55d8902
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/repository/UserMissionQueryDsl.java
@@ -0,0 +1,15 @@
+package com.example.umc_spring_first.domain.mission.repository;
+
+import com.example.umc_spring_first.domain.mission.dto.res.MissionResDTO;
+import com.example.umc_spring_first.domain.mission.entity.UserMission;
+import com.example.umc_spring_first.domain.mission.enums.UserMissionStatus;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+public interface UserMissionQueryDsl {
+ Page searchMyMissions( //구현체인 impl이 뭘 구현해야하는지 명시해줌.
+ Long userId,
+ UserMissionStatus status,
+ Pageable pageable
+ );
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/repository/UserMissionRepository.java b/src/main/java/com/example/umc_spring_first/domain/mission/repository/UserMissionRepository.java
new file mode 100644
index 0000000..7a32382
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/repository/UserMissionRepository.java
@@ -0,0 +1,10 @@
+package com.example.umc_spring_first.domain.mission.repository;
+
+import com.example.umc_spring_first.domain.mission.entity.UserMission;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface UserMissionRepository
+ extends JpaRepository, UserMissionQueryDsl {
+
+ // 여기에는 별도 @Query 메서드 두지 말기
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/repository/UserMissionRepositoryImpl.java b/src/main/java/com/example/umc_spring_first/domain/mission/repository/UserMissionRepositoryImpl.java
new file mode 100644
index 0000000..cc7c68f
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/repository/UserMissionRepositoryImpl.java
@@ -0,0 +1,55 @@
+package com.example.umc_spring_first.domain.mission.repository;
+
+import com.example.umc_spring_first.domain.mission.entity.QUserMission;
+import com.example.umc_spring_first.domain.mission.entity.UserMission;
+import com.example.umc_spring_first.domain.mission.enums.UserMissionStatus;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.*;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+@RequiredArgsConstructor
+public class UserMissionRepositoryImpl implements UserMissionQueryDsl {
+
+ private final JPAQueryFactory query;
+
+ @Override
+ public Page searchMyMissions(
+ Long userId,
+ UserMissionStatus status,
+ Pageable pageable
+ ) {
+ QUserMission um = QUserMission.userMission;
+
+ // 1) content 생성 (엔티티 반환)
+ List content = query //usermission entity 꺼내옴
+ .select(um)
+ .from(um)
+ .where(
+ um.user.id.eq(userId),
+ um.status.eq(status)
+ )
+ .orderBy(um.id.desc())
+ .offset(pageable.getOffset())
+ .limit(pageable.getPageSize())
+ .fetch();
+
+ // 2) total count
+ Long total = query
+ .select(um.count())
+ .from(um)
+ .where(
+ um.user.id.eq(userId),
+ um.status.eq(status)
+ )
+ .fetchOne();
+
+ return new PageImpl<>(content, pageable, total == null ? 0 : total);
+ //QueryDSL을 사용할 때는 직접 페이징 쿼리를 실행하므로
+ //스프링이 자동으로 Page 객체를 만들어주지 않는다. 그래서 우리가 직접 Page 객체를 만들어줘야 한다.
+ //그 역할이 바로 PageImpl 이다.
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/service/MissionQueryService.java b/src/main/java/com/example/umc_spring_first/domain/mission/service/MissionQueryService.java
new file mode 100644
index 0000000..d9a0d0e
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/service/MissionQueryService.java
@@ -0,0 +1,46 @@
+package com.example.umc_spring_first.domain.mission.service;
+
+import com.example.umc_spring_first.domain.mission.converter.MissionConverter;
+import com.example.umc_spring_first.domain.mission.dto.res.MissionResDTO;
+import com.example.umc_spring_first.domain.mission.entity.Mission;
+import com.example.umc_spring_first.domain.mission.entity.UserMission;
+import com.example.umc_spring_first.domain.mission.enums.UserMissionStatus;
+import com.example.umc_spring_first.domain.mission.repository.MissionRepository;
+import com.example.umc_spring_first.domain.mission.repository.UserMissionRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional(readOnly = true)
+@RequiredArgsConstructor
+@Service
+public class MissionQueryService {
+
+ private final MissionRepository missionRepository;
+ private final UserMissionRepository userMissionRepository;
+
+ // 2번: 특정 가게의 미션 목록
+ public MissionResDTO.MissionPreviewListDTO getStoreMissions(Long storeId, int page) {
+ Pageable pageable = PageRequest.of(page - 1, 10);
+
+ Page result = missionRepository.searchStoreMissions(storeId, pageable);
+
+ return MissionConverter.toStoreMissionList(result);
+ }
+
+ // 3번: 내가 진행중인 미션 목록
+ public MissionResDTO.MissionPreviewListDTO getMyInProgressMissions(Long userId, int page) {
+ Pageable pageable = PageRequest.of(page - 1, 10);
+
+ Page result = userMissionRepository.searchMyMissions(
+ userId,
+ UserMissionStatus.IN_PROGRESS,
+ pageable
+ );
+
+ return MissionConverter.toMissionPreviewList(result);
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/service/MissionService.java b/src/main/java/com/example/umc_spring_first/domain/mission/service/MissionService.java
new file mode 100644
index 0000000..3d831ee
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/service/MissionService.java
@@ -0,0 +1,46 @@
+package com.example.umc_spring_first.domain.mission.service;
+
+import com.example.umc_spring_first.domain.mission.converter.MissionConverter;
+import com.example.umc_spring_first.domain.mission.dto.req.MissionReqDTO;
+import com.example.umc_spring_first.domain.mission.dto.res.MissionResDTO;
+import com.example.umc_spring_first.domain.mission.entity.Mission;
+import com.example.umc_spring_first.domain.mission.repository.MissionRepository;
+import com.example.umc_spring_first.domain.store.entity.Store;
+import com.example.umc_spring_first.domain.store.repository.StoreRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.*;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class MissionService {
+
+ private final MissionRepository missionRepository;
+ private final StoreRepository storeRepository;
+
+ public MissionResDTO.CreateMissionResponse createMission(MissionReqDTO.CreateMissionRequest req) {
+
+ Store store = storeRepository.findById(req.getStoreId())
+ .orElseThrow(() -> new RuntimeException("Store not found"));
+
+ Mission mission = MissionConverter.toMissionEntity(req, store);
+
+ missionRepository.save(mission);
+
+ return MissionConverter.toCreateMissionResponse(mission);
+ }
+
+ // 특정 가게 미션 목록
+ @Transactional(readOnly = true)
+ public MissionResDTO.MissionPreviewListDTO getStoreMissions(Long storeId, int page) {
+
+ Pageable pageable = PageRequest.of(page - 1, 10);
+
+ Page missionPage =
+ missionRepository.searchStoreMissions(storeId, pageable);
+
+ return MissionConverter.toStoreMissionList(missionPage);
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/mission/service/UserMissionService.java b/src/main/java/com/example/umc_spring_first/domain/mission/service/UserMissionService.java
new file mode 100644
index 0000000..41d8a36
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/mission/service/UserMissionService.java
@@ -0,0 +1,62 @@
+package com.example.umc_spring_first.domain.mission.service;
+
+import com.example.umc_spring_first.domain.mission.converter.MissionConverter;
+import com.example.umc_spring_first.domain.mission.dto.res.MissionResDTO;
+import com.example.umc_spring_first.domain.mission.entity.Mission;
+import com.example.umc_spring_first.domain.mission.entity.UserMission;
+import com.example.umc_spring_first.domain.mission.enums.UserMissionStatus;
+import com.example.umc_spring_first.domain.mission.repository.MissionRepository;
+import com.example.umc_spring_first.domain.mission.repository.UserMissionRepository;
+import com.example.umc_spring_first.domain.user.entity.User;
+import com.example.umc_spring_first.domain.user.repository.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.*;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class UserMissionService {
+
+ private final UserMissionRepository userMissionRepository;
+ private final MissionRepository missionRepository;
+ private final UserRepository userRepository;
+
+ // 미션 도전하기
+ public Long challengeMission(Long missionId) {
+
+ Long userId = 1L; // 로그인 미구현 → 하드코딩
+
+ User user = userRepository.findById(userId)
+ .orElseThrow(() -> new RuntimeException("유저 없음"));
+
+ Mission mission = missionRepository.findById(missionId)
+ .orElseThrow(() -> new RuntimeException("미션 없음"));
+
+ // Converter 사용해서 UserMission 엔티티 생성
+ UserMission um = MissionConverter.toUserMissionEntity(user, mission);
+
+ userMissionRepository.save(um);
+
+ // 컨트롤러에서 Long id를 반환받으므로 Long 리턴
+ return um.getId();
+ }
+
+ //내가 진행중인 미션 목록 조회
+ @Transactional(readOnly = true)
+ public MissionResDTO.MissionPreviewListDTO getMyMissions(int page) {
+
+ Long userId = 1L;
+ Pageable pageable = PageRequest.of(page - 1, 10);
+
+ Page entityPage =
+ userMissionRepository.searchMyMissions(
+ userId,
+ UserMissionStatus.IN_PROGRESS,
+ pageable
+ );
+
+ return MissionConverter.toMissionPreviewList(entityPage);
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/notice/entity/Notice.java b/src/main/java/com/example/umc_spring_first/domain/notice/entity/Notice.java
new file mode 100644
index 0000000..657680c
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/notice/entity/Notice.java
@@ -0,0 +1,19 @@
+package com.example.umc_spring_first.domain.notice.entity;
+
+import com.example.umc_spring_first.domain.user.entity.User;
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity
+@Table(name = "notice")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
+public class Notice {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id", nullable = false)
+ private User user;
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/notice/repository/NoticeRepository.java b/src/main/java/com/example/umc_spring_first/domain/notice/repository/NoticeRepository.java
new file mode 100644
index 0000000..0de090b
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/notice/repository/NoticeRepository.java
@@ -0,0 +1,7 @@
+package com.example.umc_spring_first.domain.notice.repository;
+
+import com.example.umc_spring_first.domain.notice.entity.Notice;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface NoticeRepository extends JpaRepository {
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/prefer/entity/Prefer.java b/src/main/java/com/example/umc_spring_first/domain/prefer/entity/Prefer.java
new file mode 100644
index 0000000..ddacc95
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/prefer/entity/Prefer.java
@@ -0,0 +1,19 @@
+// src/main/java/com/example/umc_spring_first/domain/prefer/entity/Prefer.java
+package com.example.umc_spring_first.domain.prefer.entity;
+
+import com.example.umc_spring_first.global.entity.BaseTimeEntity;
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity
+@Table(name = "prefer")
+@Getter @NoArgsConstructor(access = AccessLevel.PROTECTED)
+@AllArgsConstructor @Builder
+public class Prefer extends BaseTimeEntity {
+
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "menu", nullable = false, length = 100)
+ private String menu;
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/prefer/entity/UserPrefer.java b/src/main/java/com/example/umc_spring_first/domain/prefer/entity/UserPrefer.java
new file mode 100644
index 0000000..0721259
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/prefer/entity/UserPrefer.java
@@ -0,0 +1,23 @@
+package com.example.umc_spring_first.domain.prefer.entity;
+
+import com.example.umc_spring_first.domain.user.entity.User;
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity
+@Table(name = "user_prefer")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
+public class UserPrefer {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id", nullable = false)
+ private User user;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "prefer_id", nullable = false)
+ private Prefer prefer;
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/prefer/repository/PreferRepository.java b/src/main/java/com/example/umc_spring_first/domain/prefer/repository/PreferRepository.java
new file mode 100644
index 0000000..2a89e6d
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/prefer/repository/PreferRepository.java
@@ -0,0 +1,9 @@
+// PreferRepository.java
+package com.example.umc_spring_first.domain.prefer.repository;
+
+import com.example.umc_spring_first.domain.prefer.entity.Prefer;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface PreferRepository extends JpaRepository {
+ boolean existsByMenu(String menu);
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/prefer/repository/UserPreferRepository.java b/src/main/java/com/example/umc_spring_first/domain/prefer/repository/UserPreferRepository.java
new file mode 100644
index 0000000..5fbbb92
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/prefer/repository/UserPreferRepository.java
@@ -0,0 +1,9 @@
+// UserPreferRepository.java
+package com.example.umc_spring_first.domain.prefer.repository;
+
+import com.example.umc_spring_first.domain.prefer.entity.UserPrefer;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface UserPreferRepository extends JpaRepository {
+ boolean existsByUserIdAndPrefer_Id(Long userId, Long preferId);
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/review/controller/ReviewController.java b/src/main/java/com/example/umc_spring_first/domain/review/controller/ReviewController.java
new file mode 100644
index 0000000..b3f618f
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/review/controller/ReviewController.java
@@ -0,0 +1,83 @@
+package com.example.umc_spring_first.domain.review.controller;
+
+import com.example.umc_spring_first.domain.review.dto.req.ReviewReqDTO;
+import com.example.umc_spring_first.domain.review.dto.res.ReviewResDTO;
+import com.example.umc_spring_first.domain.review.exception.code.ReviewSuccessCode;
+import com.example.umc_spring_first.domain.review.service.ReviewQueryService;
+import com.example.umc_spring_first.domain.review.service.ReviewService;
+import com.example.umc_spring_first.global.apiPayload.ApiResponse;
+import com.example.umc_spring_first.global.annotation.ValidPage;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/reviews")
+@Validated // @ValidPage 동작하게 해주는 어노테이션
+public class ReviewController {
+
+ private final ReviewQueryService reviewQueryService;
+ private final ReviewService reviewService;
+
+ // 전체 리뷰 조회
+ @Operation(
+ summary = "전체 리뷰 조회",
+ description = "storeId, 별점(stars) 기준으로 필터링하여 페이지 단위(10개)로 조회합니다."
+ )
+ @ApiResponses({
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "리뷰 조회 성공"),
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "유효하지 않은 page 값입니다.")
+ })
+ @GetMapping
+ public ApiResponse getReviews(
+ @RequestParam(required = false) Long storeId,
+ @RequestParam(required = false, name = "stars") Integer starBand,
+ @RequestParam(defaultValue = "1") @ValidPage Integer page
+ ) {
+ return ApiResponse.onSuccess(
+ ReviewSuccessCode.FOUND,
+ reviewQueryService.getReviews(storeId, starBand, page)
+ );
+ }
+
+ // 내가 작성한 리뷰 조회
+ @Operation(
+ summary = "내가 작성한 리뷰 조회",
+ description = "로그인 미구현 → userId=1 로 가정하고 내가 작성한 리뷰를 페이지 단위로 조회합니다."
+ )
+ @ApiResponses({
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "리뷰 조회 성공"),
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "유효하지 않은 page 값입니다.")
+ })
+ @GetMapping("/my")
+ public ApiResponse getMyReviews(
+ @RequestParam(defaultValue = "1") @ValidPage Integer page
+ ) {
+ return ApiResponse.onSuccess(
+ ReviewSuccessCode.FOUND,
+ reviewQueryService.getMyReviews(1L, page)
+ );
+ }
+
+ // 리뷰 생성
+ @Operation(
+ summary = "리뷰 생성",
+ description = "별점(rating), 내용(content)을 입력받아 리뷰를 생성합니다."
+ )
+ @ApiResponses({
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "리뷰 생성 성공"),
+ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "요청 데이터가 유효하지 않음")
+ })
+ @PostMapping
+ public ApiResponse createReview(
+ @RequestBody ReviewReqDTO.CreateReviewRequest req
+ ) {
+ return ApiResponse.onSuccess(
+ ReviewSuccessCode.CREATED,
+ reviewService.createReview(req)
+ );
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/review/converter/ReviewConverter.java b/src/main/java/com/example/umc_spring_first/domain/review/converter/ReviewConverter.java
new file mode 100644
index 0000000..3f0ebf2
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/review/converter/ReviewConverter.java
@@ -0,0 +1,61 @@
+package com.example.umc_spring_first.domain.review.converter;
+
+import com.example.umc_spring_first.domain.review.dto.req.ReviewReqDTO;
+import com.example.umc_spring_first.domain.review.dto.res.ReviewResDTO;
+import com.example.umc_spring_first.domain.review.entity.Review;
+import com.example.umc_spring_first.domain.store.entity.Store;
+import com.example.umc_spring_first.domain.user.entity.User;
+import org.springframework.data.domain.Page;
+import java.time.LocalDateTime;
+
+public class ReviewConverter {
+
+ // DTO → Entity
+ public static Review toReview(ReviewReqDTO.CreateReviewRequest dto, User user, Store store) {
+ LocalDateTime now = LocalDateTime.now();
+ return Review.builder()
+ .user(user)
+ .store(store)
+ .content(dto.content())
+ .rating(dto.rating())
+ .createAt(now)
+ .updateAt(now)
+ .build();
+ }
+
+ // Entity → PreviewDTO
+ public static ReviewResDTO.ReviewPreviewDTO toPreviewDTO(Review r) {
+ return ReviewResDTO.ReviewPreviewDTO.builder()
+ .reviewId(r.getId())
+ .storeName(r.getStore().getName())
+ .rating(r.getRating())
+ .content(r.getContent())
+ .createdAt(r.getCreateAt())
+ .build();
+ }
+
+ // Page → PageDTO
+ public static ReviewResDTO.ReviewPreviewListDTO toPreviewListDTO(Page page) {
+ return ReviewResDTO.ReviewPreviewListDTO.builder()
+ .reviewList(
+ page.getContent().stream()
+ .map(ReviewConverter::toPreviewDTO)
+ .toList()
+ )
+ .listSize(page.getSize())
+ .totalPage(page.getTotalPages())
+ .totalElements(page.getTotalElements())
+ .isFirst(page.isFirst())
+ .isLast(page.isLast())
+ .build();
+ }
+
+ // Entity → CreateResponseDTO
+ public static ReviewResDTO.CreateReviewResponse toCreateResponse(Review review) {
+ return ReviewResDTO.CreateReviewResponse.builder()
+ .reviewId(review.getId())
+ .createdAt(review.getCreateAt())
+ .build();
+ }
+}
+
diff --git a/src/main/java/com/example/umc_spring_first/domain/review/dto/req/ReviewReqDTO.java b/src/main/java/com/example/umc_spring_first/domain/review/dto/req/ReviewReqDTO.java
new file mode 100644
index 0000000..8a4ae6c
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/review/dto/req/ReviewReqDTO.java
@@ -0,0 +1,13 @@
+package com.example.umc_spring_first.domain.review.dto.req;
+
+import lombok.Builder;
+
+public class ReviewReqDTO {
+
+ @Builder
+ public record CreateReviewRequest(
+ Long storeId,
+ Float rating,
+ String content
+ ) {}
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/review/dto/res/ReviewResDTO.java b/src/main/java/com/example/umc_spring_first/domain/review/dto/res/ReviewResDTO.java
new file mode 100644
index 0000000..ebc303d
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/review/dto/res/ReviewResDTO.java
@@ -0,0 +1,42 @@
+package com.example.umc_spring_first.domain.review.dto.res;
+
+import lombok.Builder;
+import java.time.LocalDateTime;
+import java.util.List;
+
+public class ReviewResDTO {
+
+ @Builder
+ public record ReviewPreviewDTO(
+ Long reviewId,
+ String storeName,
+ Float rating,
+ String content,
+ LocalDateTime createdAt
+ ) {}
+
+ @Builder
+ public record ReviewPreviewListDTO(
+ List reviewList,
+ Integer listSize,
+ Integer totalPage,
+ Long totalElements,
+ Boolean isFirst,
+ Boolean isLast
+ ) {}
+
+ @Builder
+ public record MyReviewResponse(
+ Long reviewId,
+ String storeName,
+ Float rating,
+ String content,
+ LocalDateTime createdAt
+ ) {}
+
+ @Builder
+ public record CreateReviewResponse(
+ Long reviewId,
+ LocalDateTime createdAt
+ ) {}
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/review/entity/Review.java b/src/main/java/com/example/umc_spring_first/domain/review/entity/Review.java
new file mode 100644
index 0000000..ffcfd20
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/review/entity/Review.java
@@ -0,0 +1,30 @@
+package com.example.umc_spring_first.domain.review.entity;
+
+import com.example.umc_spring_first.domain.store.entity.Store;
+import com.example.umc_spring_first.domain.user.entity.User;
+import jakarta.persistence.*;
+import lombok.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "review")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
+public class Review {
+
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id", nullable = false)
+ private User user;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "store_id", nullable = false)
+ private Store store;
+
+ private Float rating;
+ private String content;
+ private String image;
+ private LocalDateTime createAt;
+ private LocalDateTime updateAt;
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/review/exception/ReviewException.java b/src/main/java/com/example/umc_spring_first/domain/review/exception/ReviewException.java
new file mode 100644
index 0000000..ef34228
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/review/exception/ReviewException.java
@@ -0,0 +1,10 @@
+package com.example.umc_spring_first.domain.review.exception;
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import com.example.umc_spring_first.global.apiPayload.exception.GeneralException;
+
+public class ReviewException extends GeneralException {
+ public ReviewException(BaseErrorCode code) {
+ super(code);
+ }
+}
+
diff --git a/src/main/java/com/example/umc_spring_first/domain/review/exception/code/ReviewErrorCode.java b/src/main/java/com/example/umc_spring_first/domain/review/exception/code/ReviewErrorCode.java
new file mode 100644
index 0000000..a5163e6
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/review/exception/code/ReviewErrorCode.java
@@ -0,0 +1,21 @@
+package com.example.umc_spring_first.domain.review.exception.code;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+@AllArgsConstructor
+public enum ReviewErrorCode implements BaseErrorCode {
+
+ NOT_FOUND(HttpStatus.NOT_FOUND,
+ "REVIEW404_1",
+ "리뷰를 찾을 수 없습니다."),
+ ;
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+}
+
diff --git a/src/main/java/com/example/umc_spring_first/domain/review/exception/code/ReviewSuccessCode.java b/src/main/java/com/example/umc_spring_first/domain/review/exception/code/ReviewSuccessCode.java
new file mode 100644
index 0000000..1e69d43
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/review/exception/code/ReviewSuccessCode.java
@@ -0,0 +1,25 @@
+package com.example.umc_spring_first.domain.review.exception.code;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseSuccessCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+@AllArgsConstructor
+public enum ReviewSuccessCode implements BaseSuccessCode {
+
+ CREATED(HttpStatus.CREATED,
+ "REVIEW201_1",
+ "성공적으로 리뷰를 생성했습니다."),
+
+ FOUND(HttpStatus.OK,
+ "REVIEW200_1",
+ "성공적으로 리뷰를 조회했습니다."),
+ ;
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/review/repository/ReviewQueryDsl.java b/src/main/java/com/example/umc_spring_first/domain/review/repository/ReviewQueryDsl.java
new file mode 100644
index 0000000..85a9f0d
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/review/repository/ReviewQueryDsl.java
@@ -0,0 +1,20 @@
+package com.example.umc_spring_first.domain.review.repository;
+
+import com.example.umc_spring_first.domain.review.entity.Review;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+public interface ReviewQueryDsl {
+
+ Page searchReviews(
+ Long storeId,
+ Integer starBand,
+ Pageable pageable
+ );
+
+ Page searchMyReviews(
+ Long userId,
+ Pageable pageable
+ );
+}
+
diff --git a/src/main/java/com/example/umc_spring_first/domain/review/repository/ReviewRepository.java b/src/main/java/com/example/umc_spring_first/domain/review/repository/ReviewRepository.java
new file mode 100644
index 0000000..fca1dd8
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/review/repository/ReviewRepository.java
@@ -0,0 +1,9 @@
+package com.example.umc_spring_first.domain.review.repository;
+
+import com.example.umc_spring_first.domain.review.entity.Review;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface ReviewRepository extends JpaRepository, ReviewQueryDsl {
+
+}
+
diff --git a/src/main/java/com/example/umc_spring_first/domain/review/repository/ReviewRepositoryImpl.java b/src/main/java/com/example/umc_spring_first/domain/review/repository/ReviewRepositoryImpl.java
new file mode 100644
index 0000000..ccf8cf0
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/review/repository/ReviewRepositoryImpl.java
@@ -0,0 +1,75 @@
+package com.example.umc_spring_first.domain.review.repository;
+
+import com.example.umc_spring_first.domain.review.entity.QReview;
+import com.example.umc_spring_first.domain.review.entity.Review;
+import com.querydsl.core.BooleanBuilder;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.*;
+import org.springframework.stereotype.Repository;
+import java.util.List;
+
+@Repository
+@RequiredArgsConstructor
+public class ReviewRepositoryImpl implements ReviewQueryDsl {
+
+ private final JPAQueryFactory query;
+
+ @Override
+ public Page searchReviews(Long storeId, Integer starBand, Pageable pageable) {
+ QReview r = QReview.review;
+
+ BooleanBuilder builder = new BooleanBuilder();
+
+ if (storeId != null)
+ builder.and(r.store.id.eq(storeId));
+
+ if (starBand != null) {
+ if (starBand == 5) {
+ builder.and(r.rating.eq(5.0f));
+ } else {
+ builder.and(r.rating.between(starBand, starBand + 0.9999f));
+ }
+ }
+
+ List content = query
+ .select(r) // Entity 반환
+ .from(r)
+ .where(builder)
+ .orderBy(r.id.desc())
+ .offset(pageable.getOffset())
+ .limit(pageable.getPageSize())
+ .fetch();
+
+ Long total = query
+ .select(r.count())
+ .from(r)
+ .where(builder)
+ .fetchOne();
+
+ return new PageImpl<>(content, pageable, total == null ? 0 : total);
+ }
+
+ @Override
+ public Page searchMyReviews(Long userId, Pageable pageable) {
+ QReview r = QReview.review;
+
+ List content = query
+ .select(r) // Entity 반환
+ .from(r)
+ .where(r.user.id.eq(userId))
+ .orderBy(r.id.desc())
+ .offset(pageable.getOffset())
+ .limit(pageable.getPageSize())
+ .fetch();
+
+ Long total = query
+ .select(r.count())
+ .from(r)
+ .where(r.user.id.eq(userId))
+ .fetchOne();
+
+ return new PageImpl<>(content, pageable, total == null ? 0 : total);
+ }
+}
+
diff --git a/src/main/java/com/example/umc_spring_first/domain/review/service/ReviewQueryService.java b/src/main/java/com/example/umc_spring_first/domain/review/service/ReviewQueryService.java
new file mode 100644
index 0000000..c08807c
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/review/service/ReviewQueryService.java
@@ -0,0 +1,36 @@
+package com.example.umc_spring_first.domain.review.service;
+
+import com.example.umc_spring_first.domain.review.converter.ReviewConverter;
+import com.example.umc_spring_first.domain.review.dto.res.ReviewResDTO;
+import com.example.umc_spring_first.domain.review.entity.Review;
+import com.example.umc_spring_first.domain.review.repository.ReviewRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.*;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@Transactional(readOnly = true)
+public class ReviewQueryService {
+
+ private final ReviewRepository reviewRepository;
+
+ public ReviewResDTO.ReviewPreviewListDTO getReviews(Long storeId, Integer starBand, int page) {
+
+ Pageable pageable = PageRequest.of(page - 1, 10);
+
+ Page entityPage = reviewRepository.searchReviews(storeId, starBand, pageable);
+
+ return ReviewConverter.toPreviewListDTO(entityPage);
+ }
+
+ public ReviewResDTO.ReviewPreviewListDTO getMyReviews(Long userId, int page) {
+
+ Pageable pageable = PageRequest.of(page - 1, 10);
+
+ Page entityPage = reviewRepository.searchMyReviews(userId, pageable);
+
+ return ReviewConverter.toPreviewListDTO(entityPage);
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/review/service/ReviewService.java b/src/main/java/com/example/umc_spring_first/domain/review/service/ReviewService.java
new file mode 100644
index 0000000..57bb5f6
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/review/service/ReviewService.java
@@ -0,0 +1,41 @@
+package com.example.umc_spring_first.domain.review.service;
+
+import com.example.umc_spring_first.domain.review.converter.ReviewConverter;
+import com.example.umc_spring_first.domain.review.dto.req.ReviewReqDTO;
+import com.example.umc_spring_first.domain.review.dto.res.ReviewResDTO;
+import com.example.umc_spring_first.domain.review.entity.Review;
+import com.example.umc_spring_first.domain.review.repository.ReviewRepository;
+import com.example.umc_spring_first.domain.store.entity.Store;
+import com.example.umc_spring_first.domain.store.repository.StoreRepository;
+import com.example.umc_spring_first.domain.user.entity.User;
+import com.example.umc_spring_first.domain.user.repository.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class ReviewService {
+
+ private final ReviewRepository reviewRepository;
+ private final UserRepository userRepository;
+ private final StoreRepository storeRepository;
+
+ public ReviewResDTO.CreateReviewResponse createReview(ReviewReqDTO.CreateReviewRequest req) {
+
+ Long userId = 1L; // 로그인 미구현
+ User user = userRepository.findById(userId)
+ .orElseThrow(() -> new RuntimeException("유저 없음"));
+
+ Store store = storeRepository.findById(req.storeId())
+ .orElseThrow(() -> new RuntimeException("가게 없음"));
+
+ Review review = ReviewConverter.toReview(req, user, store);
+
+ reviewRepository.save(review);
+
+ return ReviewConverter.toCreateResponse(review);
+ }
+}
+
diff --git a/src/main/java/com/example/umc_spring_first/domain/store/controller/StoreController.java b/src/main/java/com/example/umc_spring_first/domain/store/controller/StoreController.java
new file mode 100644
index 0000000..b9ba65a
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/store/controller/StoreController.java
@@ -0,0 +1,23 @@
+package com.example.umc_spring_first.domain.store.controller;
+
+import com.example.umc_spring_first.domain.store.dto.StoreCreateRequest;
+import com.example.umc_spring_first.domain.store.service.StoreService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api")
+@RequiredArgsConstructor
+public class StoreController {
+
+ private final StoreService storeService;
+
+ @PostMapping("/stores") //특정 지역에 가게 추가하기 API
+ public ResponseEntity> addStore(
+ @RequestBody StoreCreateRequest req
+ ) {
+ Long id = storeService.addStore(req);
+ return ResponseEntity.ok(id);
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/store/dto/StoreCreateRequest.java b/src/main/java/com/example/umc_spring_first/domain/store/dto/StoreCreateRequest.java
new file mode 100644
index 0000000..c5de254
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/store/dto/StoreCreateRequest.java
@@ -0,0 +1,14 @@
+package com.example.umc_spring_first.domain.store.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class StoreCreateRequest {
+ private String name;
+ private String address;
+ private String phone;
+ private String ownerNumber;
+}
+
diff --git a/src/main/java/com/example/umc_spring_first/domain/store/entity/Store.java b/src/main/java/com/example/umc_spring_first/domain/store/entity/Store.java
new file mode 100644
index 0000000..4dcb3ea
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/store/entity/Store.java
@@ -0,0 +1,32 @@
+package com.example.umc_spring_first.domain.store.entity;
+
+import com.example.umc_spring_first.domain.mission.entity.Mission;
+import com.example.umc_spring_first.domain.review.entity.Review;
+import jakarta.persistence.*;
+import lombok.*;
+import java.time.LocalDateTime;
+import java.util.*;
+
+@Entity
+@Table(name = "store")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
+public class Store {
+
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(nullable = false, length = 50)
+ private String name;
+
+ private String address;
+ private String phone;
+ private LocalDateTime createAt;
+ private LocalDateTime updateAt;
+ private String ownerNumber;
+
+ @OneToMany(mappedBy = "store", cascade = CascadeType.ALL)
+ private List reviews = new ArrayList<>();
+
+ @OneToMany(mappedBy = "store", cascade = CascadeType.ALL)
+ private List missions = new ArrayList<>();
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/store/repository/StoreRepository.java b/src/main/java/com/example/umc_spring_first/domain/store/repository/StoreRepository.java
new file mode 100644
index 0000000..78d7ce6
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/store/repository/StoreRepository.java
@@ -0,0 +1,7 @@
+package com.example.umc_spring_first.domain.store.repository;
+
+import com.example.umc_spring_first.domain.store.entity.Store;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface StoreRepository extends JpaRepository {
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/store/service/StoreService.java b/src/main/java/com/example/umc_spring_first/domain/store/service/StoreService.java
new file mode 100644
index 0000000..e0eb168
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/store/service/StoreService.java
@@ -0,0 +1,29 @@
+package com.example.umc_spring_first.domain.store.service;
+
+import com.example.umc_spring_first.domain.store.dto.StoreCreateRequest;
+import com.example.umc_spring_first.domain.store.entity.Store;
+import com.example.umc_spring_first.domain.store.repository.StoreRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class StoreService {
+
+ private final StoreRepository storeRepository;
+
+ public Long addStore(StoreCreateRequest req) {
+
+ Store store = Store.builder()
+ .name(req.getName())
+ .address(req.getAddress())
+ .phone(req.getPhone())
+ .ownerNumber(req.getOwnerNumber())
+ .build();
+
+ storeRepository.save(store);
+ return store.getId();
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/test/controller/TestController.java b/src/main/java/com/example/umc_spring_first/domain/test/controller/TestController.java
new file mode 100644
index 0000000..a8e6a9c
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/test/controller/TestController.java
@@ -0,0 +1,44 @@
+package com.example.umc_spring_first.domain.test.controller;
+
+import com.example.umc_spring_first.domain.test.converter.TestConverter;
+import com.example.umc_spring_first.domain.test.dto.res.TestResDTO;
+import com.example.umc_spring_first.domain.test.service.query.TestQueryService;
+import com.example.umc_spring_first.global.apiPayload.ApiResponse;
+import com.example.umc_spring_first.global.apiPayload.code.GeneralSuccessCode;
+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;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/temp")
+public class TestController {
+
+ private final TestQueryService testQueryService;
+
+ @GetMapping("/test")
+ public ApiResponse test() {
+ // 응답 코드 정의
+ GeneralSuccessCode code = GeneralSuccessCode.OK;
+
+ return ApiResponse.onSuccess(
+ code,
+ TestConverter.toTestingDTO("This is Test!")
+ );
+ }
+
+ // 예외 상황
+ @GetMapping("/exception")
+ public ApiResponse exception(
+ @RequestParam Long flag
+ ) {
+
+ testQueryService.checkFlag(flag);
+
+ // 응답 코드 정의
+ GeneralSuccessCode code = GeneralSuccessCode.OK;
+ return ApiResponse.onSuccess(code, TestConverter.toExceptionDTO("This is Test!"));
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/test/converter/TestConverter.java b/src/main/java/com/example/umc_spring_first/domain/test/converter/TestConverter.java
new file mode 100644
index 0000000..9a7a165
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/test/converter/TestConverter.java
@@ -0,0 +1,24 @@
+package com.example.umc_spring_first.domain.test.converter;
+
+import com.example.umc_spring_first.domain.test.dto.res.TestResDTO;
+
+public class TestConverter {
+
+ // 객체 -> DTO
+ public static TestResDTO.Testing toTestingDTO(
+ String testing
+ ) {
+ return TestResDTO.Testing.builder()
+ .testString(testing)
+ .build();
+ }
+
+ // 객체 -> DTO
+ public static TestResDTO.Exception toExceptionDTO(
+ String testing
+ ){
+ return TestResDTO.Exception.builder()
+ .testString(testing)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/test/dto/req/TestReqDTO.java b/src/main/java/com/example/umc_spring_first/domain/test/dto/req/TestReqDTO.java
new file mode 100644
index 0000000..d27fb6f
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/test/dto/req/TestReqDTO.java
@@ -0,0 +1,4 @@
+package com.example.umc_spring_first.domain.test.dto.req;
+
+public class TestReqDTO {
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/test/dto/res/TestResDTO.java b/src/main/java/com/example/umc_spring_first/domain/test/dto/res/TestResDTO.java
new file mode 100644
index 0000000..fa55816
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/test/dto/res/TestResDTO.java
@@ -0,0 +1,20 @@
+package com.example.umc_spring_first.domain.test.dto.res;
+
+import lombok.Builder;
+import lombok.Getter;
+
+public class TestResDTO {
+
+ @Getter
+ @Builder
+ public static class Testing {
+ private String testString;
+ }
+
+ @Builder
+ @Getter
+ public static class Exception {
+ private String testString;
+ }
+}
+
diff --git a/src/main/java/com/example/umc_spring_first/domain/test/exception/TestException.java b/src/main/java/com/example/umc_spring_first/domain/test/exception/TestException.java
new file mode 100644
index 0000000..ee65a39
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/test/exception/TestException.java
@@ -0,0 +1,11 @@
+package com.example.umc_spring_first.domain.test.exception;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import com.example.umc_spring_first.global.apiPayload.exception.GeneralException;
+
+public class TestException extends GeneralException {
+ public TestException(BaseErrorCode code) {
+ super(code);
+ }
+}
+
diff --git a/src/main/java/com/example/umc_spring_first/domain/test/exception/code/TestErrorCode.java b/src/main/java/com/example/umc_spring_first/domain/test/exception/code/TestErrorCode.java
new file mode 100644
index 0000000..63d42ad
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/test/exception/code/TestErrorCode.java
@@ -0,0 +1,19 @@
+package com.example.umc_spring_first.domain.test.exception.code;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+@AllArgsConstructor
+public enum TestErrorCode implements BaseErrorCode {
+
+ // For test
+ TEST_EXCEPTION(HttpStatus.BAD_REQUEST, "TEST400_1", "이거는 테스트"),
+ ;
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/test/service/command/ReviewCommandService.java b/src/main/java/com/example/umc_spring_first/domain/test/service/command/ReviewCommandService.java
new file mode 100644
index 0000000..91cb553
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/test/service/command/ReviewCommandService.java
@@ -0,0 +1,4 @@
+package com.example.umc_spring_first.domain.test.service.command;
+
+public interface ReviewCommandService {
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/test/service/command/ReviewCommandServiceImpl.java b/src/main/java/com/example/umc_spring_first/domain/test/service/command/ReviewCommandServiceImpl.java
new file mode 100644
index 0000000..1d50b5b
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/test/service/command/ReviewCommandServiceImpl.java
@@ -0,0 +1,4 @@
+package com.example.umc_spring_first.domain.test.service.command;
+
+public class ReviewCommandServiceImpl {
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/test/service/query/TestQueryService.java b/src/main/java/com/example/umc_spring_first/domain/test/service/query/TestQueryService.java
new file mode 100644
index 0000000..476f6b5
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/test/service/query/TestQueryService.java
@@ -0,0 +1,5 @@
+package com.example.umc_spring_first.domain.test.service.query;
+
+public interface TestQueryService {
+ void checkFlag(Long flag);
+}
diff --git a/src/main/java/com/example/umc_spring_first/domain/test/service/query/TestQueryServiceImpl.java b/src/main/java/com/example/umc_spring_first/domain/test/service/query/TestQueryServiceImpl.java
new file mode 100644
index 0000000..302e200
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/test/service/query/TestQueryServiceImpl.java
@@ -0,0 +1,18 @@
+package com.example.umc_spring_first.domain.test.service.query;
+
+import com.example.umc_spring_first.domain.test.exception.TestException;
+import com.example.umc_spring_first.domain.test.exception.code.TestErrorCode;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class TestQueryServiceImpl implements TestQueryService {
+
+ @Override
+ public void checkFlag(Long flag){
+ if (flag == 1){
+ throw new TestException(TestErrorCode.TEST_EXCEPTION);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/controller/UserController.java b/src/main/java/com/example/umc_spring_first/domain/user/controller/UserController.java
new file mode 100644
index 0000000..4f46f14
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/controller/UserController.java
@@ -0,0 +1,37 @@
+package com.example.umc_spring_first.domain.user.controller;
+
+import com.example.umc_spring_first.domain.user.dto.req.UserReqDTO;
+import com.example.umc_spring_first.domain.user.dto.res.UserResDTO;
+import com.example.umc_spring_first.domain.user.exception.code.UserSuccessCode;
+import com.example.umc_spring_first.domain.user.service.command.UserCommandService;
+import com.example.umc_spring_first.domain.user.service.query.UserQueryService;
+import com.example.umc_spring_first.global.apiPayload.ApiResponse;
+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.RestController;
+
+@RestController
+@RequiredArgsConstructor
+public class UserController {
+
+ private final UserCommandService UserCommandService;
+ private final UserQueryService UserQueryService;
+
+ // 회원가입
+ @PostMapping("/sign-up")
+ public ApiResponse signUp(
+ @RequestBody @Valid UserReqDTO.JoinDTO dto
+ ){
+ return ApiResponse.onSuccess(UserSuccessCode.FOUND, UserCommandService.signup(dto));
+ }
+
+ // 로그인
+ @PostMapping("/login")
+ public ApiResponse login(
+ @RequestBody @Valid UserReqDTO.LoginDTO dto
+ ){
+ return ApiResponse.onSuccess(UserSuccessCode.FOUND, UserQueryService.login(dto));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/converter/UserConverter.java b/src/main/java/com/example/umc_spring_first/domain/user/converter/UserConverter.java
new file mode 100644
index 0000000..3b84807
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/converter/UserConverter.java
@@ -0,0 +1,33 @@
+package com.example.umc_spring_first.domain.user.converter;
+
+import com.example.umc_spring_first.domain.user.dto.req.UserReqDTO;
+import com.example.umc_spring_first.domain.user.dto.res.UserResDTO;
+import com.example.umc_spring_first.domain.user.entity.User;
+import com.example.umc_spring_first.global.auth.enums.Role;
+
+public class UserConverter {
+
+ // Entity -> DTO
+ public static UserResDTO.JoinDTO toJoinDTO(User user) {
+ return UserResDTO.JoinDTO.builder()
+ .userId(user.getId())
+ .createAt(user.getCreatedAt())
+ .build();
+ }
+
+ // DTO -> Entity
+ public static User toUser(UserReqDTO.JoinDTO dto, String password, Role role) {
+
+
+ return User.builder()
+ .name(dto.name())
+ .birth(dto.birth())
+ .address(dto.address())
+ .gender(dto.gender())
+ .email(dto.email())
+ .password(password)
+ .role(role)
+ .phone(dto.phone())
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/dto/req/UserReqDTO.java b/src/main/java/com/example/umc_spring_first/domain/user/dto/req/UserReqDTO.java
new file mode 100644
index 0000000..f2e5722
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/dto/req/UserReqDTO.java
@@ -0,0 +1,28 @@
+package com.example.umc_spring_first.domain.user.dto.req;
+
+import com.example.umc_spring_first.domain.user.enums.Gender;
+import jakarta.validation.constraints.NotBlank;
+
+import java.time.LocalDate;
+import java.util.List;
+
+public class UserReqDTO {
+ public record JoinDTO(
+ String name,
+ Gender gender,
+ LocalDate birth,
+ String address,
+ String specAddress,
+ String email,
+ String password,
+ String phone,
+ List preferCategory
+ ) {}
+
+ public record LoginDTO(
+ @NotBlank
+ String email,
+ @NotBlank
+ String password
+ ){}
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/dto/res/UserResDTO.java b/src/main/java/com/example/umc_spring_first/domain/user/dto/res/UserResDTO.java
new file mode 100644
index 0000000..abbde94
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/dto/res/UserResDTO.java
@@ -0,0 +1,19 @@
+package com.example.umc_spring_first.domain.user.dto.res;
+
+import lombok.Builder;
+
+import java.time.LocalDateTime;
+
+public class UserResDTO {
+ @Builder
+ public record JoinDTO(
+ Long userId,
+ LocalDateTime createAt
+ ){}
+
+ @Builder
+ public record LoginDTO(
+ Long userId,
+ String accessToken
+ ){}
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/entity/Food.java b/src/main/java/com/example/umc_spring_first/domain/user/entity/Food.java
new file mode 100644
index 0000000..8f75d4a
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/entity/Food.java
@@ -0,0 +1,26 @@
+package com.example.umc_spring_first.domain.user.entity;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Entity
+@Builder
+@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED)
+@AllArgsConstructor(access = lombok.AccessLevel.PRIVATE)
+@Getter
+@Table(name = "food")
+
+public class Food {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "food_id")
+ private Long id;
+
+ @Column(nullable = false, length = 50)
+ private String foodName;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/entity/Term.java b/src/main/java/com/example/umc_spring_first/domain/user/entity/Term.java
new file mode 100644
index 0000000..0e44bfc
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/entity/Term.java
@@ -0,0 +1,24 @@
+package com.example.umc_spring_first.domain.user.entity;
+
+import com.example.umc_spring_first.domain.user.enums.TermName;
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity
+@Getter
+@Builder
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Table(name = "term")
+
+public class Term {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "term_id")
+ private Long id;
+
+ @Column(name = "term_name", nullable = false)
+ @Enumerated(EnumType.STRING)
+ private TermName name;
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/entity/User.java b/src/main/java/com/example/umc_spring_first/domain/user/entity/User.java
new file mode 100644
index 0000000..2d5ff91
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/entity/User.java
@@ -0,0 +1,59 @@
+package com.example.umc_spring_first.domain.user.entity;
+
+import com.example.umc_spring_first.domain.user.entity.mapping.UserFood;
+import com.example.umc_spring_first.domain.user.enums.Gender;
+import com.example.umc_spring_first.global.entity.BaseTimeEntity;
+import com.example.umc_spring_first.global.auth.enums.Role;
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+@Builder
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Getter
+@Table(name = "user")
+public class User extends BaseTimeEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "user_id")
+ private Long id;
+
+ @Column(nullable = false, length = 10)
+ private String name;
+
+ @Enumerated(EnumType.STRING)
+ @Builder.Default
+ @Column(nullable = false)
+ private Gender gender = Gender.NONE;
+
+ @Column(nullable = false, unique = true)
+ private String email;
+
+ @Column
+ private LocalDate birth;
+
+ @Column
+ private String address;
+
+ @Column(nullable = false)
+ private String password;
+
+ @Enumerated(EnumType.STRING)
+ private Role role;
+
+ @Column(length = 20)
+ private String phone;
+
+ @Builder.Default
+ @Column(nullable = false)
+ private Integer point = 0;
+
+ @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
+ private List userFoodList = new ArrayList<>();
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/entity/mapping/UserFood.java b/src/main/java/com/example/umc_spring_first/domain/user/entity/mapping/UserFood.java
new file mode 100644
index 0000000..a99fd53
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/entity/mapping/UserFood.java
@@ -0,0 +1,27 @@
+package com.example.umc_spring_first.domain.user.entity.mapping;
+
+import com.example.umc_spring_first.domain.user.entity.Food;
+import com.example.umc_spring_first.domain.user.entity.User;
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity
+@Builder
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Getter
+@Table(name = "user_food")
+public class UserFood {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "user_food_id")
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id")
+ private User user;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "food_id")
+ private Food food;
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/entity/mapping/UserTerm.java b/src/main/java/com/example/umc_spring_first/domain/user/entity/mapping/UserTerm.java
new file mode 100644
index 0000000..5774367
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/entity/mapping/UserTerm.java
@@ -0,0 +1,29 @@
+package com.example.umc_spring_first.domain.user.entity.mapping;
+
+import com.example.umc_spring_first.domain.user.entity.Term;
+import com.example.umc_spring_first.domain.user.entity.User;
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity
+@Builder
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Getter
+@Table(name = "user_term")
+
+public class UserTerm {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "user_term_id")
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id")
+ private User user;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "term_id")
+ private Term term;
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/enums/Gender.java b/src/main/java/com/example/umc_spring_first/domain/user/enums/Gender.java
new file mode 100644
index 0000000..f96a260
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/enums/Gender.java
@@ -0,0 +1,5 @@
+package com.example.umc_spring_first.domain.user.enums;
+
+public enum Gender {
+ MALE, FEMALE, NONE
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/enums/TermName.java b/src/main/java/com/example/umc_spring_first/domain/user/enums/TermName.java
new file mode 100644
index 0000000..46f4d8d
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/enums/TermName.java
@@ -0,0 +1,9 @@
+package com.example.umc_spring_first.domain.user.enums;
+
+public enum TermName {
+ AGE, //필수
+ SERVICE, //필수
+ PRIVACY, //필수
+ LOCATION, //선택
+ MARKETING//(선택)
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/enums/UserMissionStatus.java b/src/main/java/com/example/umc_spring_first/domain/user/enums/UserMissionStatus.java
new file mode 100644
index 0000000..8dc3e9f
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/enums/UserMissionStatus.java
@@ -0,0 +1,6 @@
+package com.example.umc_spring_first.domain.user.enums;
+
+public enum UserMissionStatus {
+ IN_PROGRESS,
+ COMPLETED
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/exception/FoodException.java b/src/main/java/com/example/umc_spring_first/domain/user/exception/FoodException.java
new file mode 100644
index 0000000..504f595
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/exception/FoodException.java
@@ -0,0 +1,11 @@
+package com.example.umc_spring_first.domain.user.exception;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import com.example.umc_spring_first.global.apiPayload.exception.GeneralException;
+
+public class FoodException extends GeneralException {
+
+ public FoodException(BaseErrorCode code) {
+ super(code);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/exception/UserException.java b/src/main/java/com/example/umc_spring_first/domain/user/exception/UserException.java
new file mode 100644
index 0000000..145717c
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/exception/UserException.java
@@ -0,0 +1,10 @@
+package com.example.umc_spring_first.domain.user.exception;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import com.example.umc_spring_first.global.apiPayload.exception.GeneralException;
+
+public class UserException extends GeneralException{
+ public UserException(BaseErrorCode code) {
+ super(code);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/exception/code/FoodErrorCode.java b/src/main/java/com/example/umc_spring_first/domain/user/exception/code/FoodErrorCode.java
new file mode 100644
index 0000000..36f1239
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/exception/code/FoodErrorCode.java
@@ -0,0 +1,19 @@
+package com.example.umc_spring_first.domain.user.exception.code;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+@AllArgsConstructor
+public enum FoodErrorCode implements BaseErrorCode {
+
+ FOOD_NOT_FOUND(HttpStatus.NOT_FOUND, "FOOD404_1", "음식을 찾을 수 없습니다."),
+ INVALID_FOOD_ID(HttpStatus.BAD_REQUEST, "FOOD400_1", "유효하지 않은 음식 ID입니다."),
+ ;
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/exception/code/FoodSuccessCode.java b/src/main/java/com/example/umc_spring_first/domain/user/exception/code/FoodSuccessCode.java
new file mode 100644
index 0000000..a7a1533
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/exception/code/FoodSuccessCode.java
@@ -0,0 +1,20 @@
+package com.example.umc_spring_first.domain.user.exception.code;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseSuccessCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+@AllArgsConstructor
+public enum FoodSuccessCode implements BaseSuccessCode {
+
+ FOUND(HttpStatus.OK,
+ "MEMBER200_1",
+ "성공적으로 조회했습니다."),
+ ;
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/exception/code/UserErrorCode.java b/src/main/java/com/example/umc_spring_first/domain/user/exception/code/UserErrorCode.java
new file mode 100644
index 0000000..c2f99d2
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/exception/code/UserErrorCode.java
@@ -0,0 +1,30 @@
+package com.example.umc_spring_first.domain.user.exception.code;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+@AllArgsConstructor
+public enum UserErrorCode implements BaseErrorCode {
+
+ NOT_FOUND(HttpStatus.NOT_FOUND,
+ "MEMBER404_1",
+ "해당 사용자를 찾지 못했습니다."),
+
+ ALREADY_CHALLENGING(HttpStatus.BAD_REQUEST,
+ "MEMBER400_1",
+ "이미 진행중인 미션입니다."),
+
+
+ INVALID(HttpStatus.BAD_REQUEST,
+ "MEMBER400_2",
+ "유효하지 않은 사용자 정보입니다.")
+ ;
+
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/exception/code/UserSuccessCode.java b/src/main/java/com/example/umc_spring_first/domain/user/exception/code/UserSuccessCode.java
new file mode 100644
index 0000000..b50bd2d
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/exception/code/UserSuccessCode.java
@@ -0,0 +1,20 @@
+package com.example.umc_spring_first.domain.user.exception.code;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseSuccessCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+@AllArgsConstructor
+public enum UserSuccessCode implements BaseSuccessCode {
+
+ FOUND(HttpStatus.OK,
+ "MEMBER200_1",
+ "성공적으로 사용자를 조회했습니다."),
+ ;
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/repository/FoodRepository.java b/src/main/java/com/example/umc_spring_first/domain/user/repository/FoodRepository.java
new file mode 100644
index 0000000..f57679e
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/repository/FoodRepository.java
@@ -0,0 +1,9 @@
+package com.example.umc_spring_first.domain.user.repository;
+
+import com.example.umc_spring_first.domain.user.entity.Food;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface FoodRepository extends JpaRepository {
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/repository/UserFoodRepository.java b/src/main/java/com/example/umc_spring_first/domain/user/repository/UserFoodRepository.java
new file mode 100644
index 0000000..d63b247
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/repository/UserFoodRepository.java
@@ -0,0 +1,9 @@
+package com.example.umc_spring_first.domain.user.repository;
+
+import com.example.umc_spring_first.domain.user.entity.mapping.UserFood;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface UserFoodRepository extends JpaRepository {
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/repository/UserRepository.java b/src/main/java/com/example/umc_spring_first/domain/user/repository/UserRepository.java
new file mode 100644
index 0000000..306a882
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/repository/UserRepository.java
@@ -0,0 +1,10 @@
+package com.example.umc_spring_first.domain.user.repository;
+
+import com.example.umc_spring_first.domain.user.entity.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface UserRepository extends JpaRepository {
+ Optional findByEmail(String email);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/service/command/UserCommandService.java b/src/main/java/com/example/umc_spring_first/domain/user/service/command/UserCommandService.java
new file mode 100644
index 0000000..e84fabb
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/service/command/UserCommandService.java
@@ -0,0 +1,8 @@
+package com.example.umc_spring_first.domain.user.service.command;
+
+import com.example.umc_spring_first.domain.user.dto.req.UserReqDTO;
+import com.example.umc_spring_first.domain.user.dto.res.UserResDTO;
+
+public interface UserCommandService {
+ UserResDTO.JoinDTO signup(UserReqDTO.JoinDTO dto);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/service/command/UserCommandServiceImpl.java b/src/main/java/com/example/umc_spring_first/domain/user/service/command/UserCommandServiceImpl.java
new file mode 100644
index 0000000..dc23977
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/service/command/UserCommandServiceImpl.java
@@ -0,0 +1,72 @@
+package com.example.umc_spring_first.domain.user.service.command;
+
+import com.example.umc_spring_first.domain.user.converter.UserConverter;
+import com.example.umc_spring_first.domain.user.dto.req.UserReqDTO;
+import com.example.umc_spring_first.domain.user.dto.res.UserResDTO;
+import com.example.umc_spring_first.domain.user.entity.Food;
+import com.example.umc_spring_first.domain.user.entity.User;
+import com.example.umc_spring_first.domain.user.entity.mapping.UserFood;
+import com.example.umc_spring_first.domain.user.exception.FoodException;
+import com.example.umc_spring_first.domain.user.exception.code.FoodErrorCode;
+import com.example.umc_spring_first.domain.user.repository.FoodRepository;
+import com.example.umc_spring_first.domain.user.repository.UserFoodRepository;
+import com.example.umc_spring_first.domain.user.repository.UserRepository;
+import com.example.umc_spring_first.global.auth.enums.Role;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class UserCommandServiceImpl implements UserCommandService {
+
+ private final UserRepository userRepository;
+ private final UserFoodRepository userFoodRepository;
+ private final FoodRepository foodRepository;
+ private final PasswordEncoder passwordEncoder;
+
+ @Override
+ @Transactional
+ public UserResDTO.JoinDTO signup(UserReqDTO.JoinDTO dto) {
+
+ // 솔트된 비밀번호 생성
+ String salt = passwordEncoder.encode(dto.password());
+
+ // 사용자 생성
+ User user = UserConverter.toUser(dto, salt, Role.ROLE_USER);
+
+ // DB 저장
+ userRepository.save(user);
+
+ // 선호 음식 존재 여부 확인
+ if (dto.preferCategory() != null && dto.preferCategory().size() > 0) {
+
+ List userFoodList = new ArrayList<>();
+
+ // 선호 음식 ID 별 조회
+ for (Long id : dto.preferCategory()) {
+ // 음식 존재 여부 검증
+ Food food = foodRepository.findById(id)
+ .orElseThrow(() -> new FoodException(FoodErrorCode.FOOD_NOT_FOUND));
+
+ // UserFood 엔티티 생성
+ UserFood userFood = UserFood.builder()
+ .user(user)
+ .food(food)
+ .build();
+
+ userFoodList.add(userFood);
+ }
+
+ // DB 저장
+ userFoodRepository.saveAll(userFoodList);
+ }
+
+ // to 응답 DTO
+ return UserConverter.toJoinDTO(user);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/service/query/UserQueryService.java b/src/main/java/com/example/umc_spring_first/domain/user/service/query/UserQueryService.java
new file mode 100644
index 0000000..08f9856
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/service/query/UserQueryService.java
@@ -0,0 +1,10 @@
+package com.example.umc_spring_first.domain.user.service.query;
+
+import com.example.umc_spring_first.domain.user.dto.req.UserReqDTO;
+import com.example.umc_spring_first.domain.user.dto.res.UserResDTO;
+import jakarta.validation.Valid;
+
+public interface UserQueryService {
+
+ UserResDTO.LoginDTO login(@Valid UserReqDTO.LoginDTO dto);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/domain/user/service/query/UserQueryServiceImpl.java b/src/main/java/com/example/umc_spring_first/domain/user/service/query/UserQueryServiceImpl.java
new file mode 100644
index 0000000..63b753a
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/domain/user/service/query/UserQueryServiceImpl.java
@@ -0,0 +1,50 @@
+package com.example.umc_spring_first.domain.user.service.query;
+
+import com.example.umc_spring_first.domain.user.dto.req.UserReqDTO;
+import com.example.umc_spring_first.domain.user.dto.res.UserResDTO;
+import com.example.umc_spring_first.domain.user.entity.User;
+import com.example.umc_spring_first.domain.user.exception.UserException;
+import com.example.umc_spring_first.domain.user.exception.code.UserErrorCode;
+import com.example.umc_spring_first.domain.user.repository.UserRepository;
+import com.example.umc_spring_first.global.auth.details.CustomUserDetails;
+import com.example.umc_spring_first.global.auth.jwt.JwtUtil;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@Transactional(readOnly = true)
+public class UserQueryServiceImpl implements UserQueryService {
+
+ private final UserRepository userRepository;
+ private final JwtUtil jwtUtil;
+ private final PasswordEncoder passwordEncoder;
+
+ @Override
+ public UserResDTO.LoginDTO login(UserReqDTO.LoginDTO dto) {
+
+ // user 조회
+ User user = userRepository.findByEmail(dto.email())
+ .orElseThrow(() -> new UserException(UserErrorCode.NOT_FOUND));
+
+ // 비밀번호 검증
+ if (!passwordEncoder.matches(dto.password(), user.getPassword())) {
+ throw new UserException(UserErrorCode.INVALID);
+ }
+
+ // JWT 토큰 발급용 UserDetails
+ CustomUserDetails userDetails = new CustomUserDetails(user);
+
+ // 엑세스 토큰 발급
+ String accessToken = jwtUtil.createAccessToken(userDetails);
+
+
+ // 응답 DTO 반환
+ return UserResDTO.LoginDTO.builder()
+ .userId(user.getId())
+ .accessToken(accessToken)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/entity/BaseTimeEntity.java b/src/main/java/com/example/umc_spring_first/entity/BaseTimeEntity.java
new file mode 100644
index 0000000..5b1a427
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/entity/BaseTimeEntity.java
@@ -0,0 +1,28 @@
+package com.example.umc_spring_first.global.entity;
+
+import jakarta.persistence.Column;
+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;
+
+//BaseTimeEntity는 모든 엔티티에 공통으로 들어가는 생성시간/수정시간 필드를 모아 둔 추상 상위 클래스.
+//각 엔티티에서 extends BaseTimeEntity만 해주면 createdAt / updatedAt을 자동으로 관리.
+
+@Getter
+@MappedSuperclass // 상속 엔티티에 매핑 정보 전달
+@EntityListeners(AuditingEntityListener.class) // Auditing 이벤트 수신
+public abstract class BaseTimeEntity {
+
+ @CreatedDate
+ @Column(name = "created_at", nullable = false, updatable = false)
+ private LocalDateTime createdAt;
+
+ @LastModifiedDate
+ @Column(name = "updated_at", nullable = false)
+ private LocalDateTime updatedAt;
+}
diff --git a/src/main/java/com/example/umc_spring_first/global/annotation/PageValidator.java b/src/main/java/com/example/umc_spring_first/global/annotation/PageValidator.java
new file mode 100644
index 0000000..16cdc3b
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/annotation/PageValidator.java
@@ -0,0 +1,17 @@
+package com.example.umc_spring_first.global.annotation;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+public class PageValidator implements ConstraintValidator {
+
+ @Override
+ public boolean isValid(Integer value, ConstraintValidatorContext context) {
+ // null 이면 실패하게 할지, 통과시킬지는 취향인데
+ // 과제 조건상 page는 무조건 1 이상이어야 하니까 null도 실패 처리하자
+ if (value == null) {
+ return false;
+ }
+ return value >= 1;
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/global/annotation/ValidPage.java b/src/main/java/com/example/umc_spring_first/global/annotation/ValidPage.java
new file mode 100644
index 0000000..dafeab6
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/annotation/ValidPage.java
@@ -0,0 +1,20 @@
+// package 예시는 global.annotation 밑이라고 가정
+package com.example.umc_spring_first.global.annotation;
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+import java.lang.annotation.*;
+
+@Documented
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = PageValidator.class)
+public @interface ValidPage {
+
+ String message() default "page 값은 1 이상의 정수여야 합니다.";
+
+ Class>[] groups() default {};
+
+ Class extends Payload>[] payload() default {};
+}
diff --git a/src/main/java/com/example/umc_spring_first/global/apiPayload/ApiResponse.java b/src/main/java/com/example/umc_spring_first/global/apiPayload/ApiResponse.java
new file mode 100644
index 0000000..18e0257
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/apiPayload/ApiResponse.java
@@ -0,0 +1,35 @@
+package com.example.umc_spring_first.global.apiPayload;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import com.example.umc_spring_first.global.apiPayload.code.BaseSuccessCode;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+@JsonPropertyOrder({"isSuccess", "code", "message", "result"})
+public class ApiResponse {
+
+ @JsonProperty("isSuccess")
+ private final Boolean isSuccess;
+
+ @JsonProperty("code")
+ private final String code;
+
+ @JsonProperty("message")
+ private final String message;
+
+ @JsonProperty("result")
+ private T result;
+
+ // 성공한 경우 (result 포함)
+ public static ApiResponse onSuccess(BaseSuccessCode code, T result) {
+ return new ApiResponse<>(true, code.getCode(), code.getMessage(), result);
+ }
+ // 실패한 경우 (result 포함)
+ public static ApiResponse onFailure(BaseErrorCode code, T result) {
+ return new ApiResponse<>(false, code.getCode(), code.getMessage(), result);
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/global/apiPayload/code/BaseErrorCode.java b/src/main/java/com/example/umc_spring_first/global/apiPayload/code/BaseErrorCode.java
new file mode 100644
index 0000000..cb14347
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/apiPayload/code/BaseErrorCode.java
@@ -0,0 +1,10 @@
+package com.example.umc_spring_first.global.apiPayload.code;
+
+import org.springframework.http.HttpStatus;
+
+public interface BaseErrorCode {
+
+ HttpStatus getStatus();
+ String getCode();
+ String getMessage();
+}
diff --git a/src/main/java/com/example/umc_spring_first/global/apiPayload/code/BaseSuccessCode.java b/src/main/java/com/example/umc_spring_first/global/apiPayload/code/BaseSuccessCode.java
new file mode 100644
index 0000000..9d6ade6
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/apiPayload/code/BaseSuccessCode.java
@@ -0,0 +1,10 @@
+package com.example.umc_spring_first.global.apiPayload.code;
+
+import org.springframework.http.HttpStatus;
+
+public interface BaseSuccessCode { //BaseErrorCode와 구조를 완전히 똑같이 유지하는 게 핵심
+
+ HttpStatus getStatus();
+ String getCode();
+ String getMessage();
+}
diff --git a/src/main/java/com/example/umc_spring_first/global/apiPayload/code/GeneralErrorCode.java b/src/main/java/com/example/umc_spring_first/global/apiPayload/code/GeneralErrorCode.java
new file mode 100644
index 0000000..8bfdc58
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/apiPayload/code/GeneralErrorCode.java
@@ -0,0 +1,31 @@
+package com.example.umc_spring_first.global.apiPayload.code;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+@AllArgsConstructor
+public enum GeneralErrorCode implements BaseErrorCode{
+
+ BAD_REQUEST(HttpStatus.BAD_REQUEST,
+ "COMMON400_1",
+ "잘못된 요청입니다."),
+ UNAUTHORIZED(HttpStatus.UNAUTHORIZED,
+ "AUTH401_1",
+ "인증이 필요합니다."),
+ FORBIDDEN(HttpStatus.FORBIDDEN,
+ "AUTH403_1",
+ "요청이 거부되었습니다."),
+ NOT_FOUND(HttpStatus.NOT_FOUND,
+ "COMMON404_1",
+ "요청한 리소스를 찾을 수 없습니다."),
+ INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR,
+ "COMMON500_1",
+ "예기치 않은 서버 에러가 발생했습니다."),
+ ;
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/global/apiPayload/code/GeneralSuccessCode.java b/src/main/java/com/example/umc_spring_first/global/apiPayload/code/GeneralSuccessCode.java
new file mode 100644
index 0000000..97fed07
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/apiPayload/code/GeneralSuccessCode.java
@@ -0,0 +1,25 @@
+package com.example.umc_spring_first.global.apiPayload.code;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+@AllArgsConstructor
+public enum GeneralSuccessCode implements BaseSuccessCode {
+
+ OK(HttpStatus.OK,
+ "COMMON200",
+ "요청이 성공적으로 처리되었습니다."),
+ CREATED(HttpStatus.CREATED,
+ "COMMON201",
+ "리소스가 성공적으로 생성되었습니다."),
+ NO_CONTENT(HttpStatus.NO_CONTENT,
+ "COMMON204",
+ "요청이 성공했지만 반환할 데이터가 없습니다."),
+ ;
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+}
diff --git a/src/main/java/com/example/umc_spring_first/global/apiPayload/exception/GeneralException.java b/src/main/java/com/example/umc_spring_first/global/apiPayload/exception/GeneralException.java
new file mode 100644
index 0000000..ae1248d
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/apiPayload/exception/GeneralException.java
@@ -0,0 +1,12 @@
+package com.example.umc_spring_first.global.apiPayload.exception;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public class GeneralException extends RuntimeException {
+
+ private final BaseErrorCode code;
+}
diff --git a/src/main/java/com/example/umc_spring_first/global/apiPayload/handler/GeneralExceptionAdvice.java b/src/main/java/com/example/umc_spring_first/global/apiPayload/handler/GeneralExceptionAdvice.java
new file mode 100644
index 0000000..e5fd5c9
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/apiPayload/handler/GeneralExceptionAdvice.java
@@ -0,0 +1,42 @@
+package com.example.umc_spring_first.global.apiPayload.handler;
+
+import com.example.umc_spring_first.global.apiPayload.ApiResponse;
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import com.example.umc_spring_first.global.apiPayload.code.GeneralErrorCode;
+import com.example.umc_spring_first.global.apiPayload.exception.GeneralException;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@RestControllerAdvice
+public class GeneralExceptionAdvice {
+
+ // 애플리케이션에서 발생하는 커스텀 예외를 처리
+ @ExceptionHandler(GeneralException.class)
+ public ResponseEntity> handleException(
+ GeneralException ex
+ ) {
+
+ return ResponseEntity.status(ex.getCode().getStatus())
+ .body(ApiResponse.onFailure(
+ ex.getCode(),
+ null
+ )
+ );
+ }
+
+ // 그 외의 정의되지 않은 모든 예외 처리
+ @ExceptionHandler(Exception.class)
+ public ResponseEntity> handleException(
+ Exception ex
+ ) {
+
+ BaseErrorCode code = GeneralErrorCode.INTERNAL_SERVER_ERROR;
+ return ResponseEntity.status(code.getStatus())
+ .body(ApiResponse.onFailure(
+ code,
+ ex.getMessage()
+ )
+ );
+ }
+}
diff --git a/src/main/java/com/example/umc_spring_first/global/auth/details/CustomUserDetails.java b/src/main/java/com/example/umc_spring_first/global/auth/details/CustomUserDetails.java
new file mode 100644
index 0000000..e3a52f6
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/auth/details/CustomUserDetails.java
@@ -0,0 +1,31 @@
+package com.example.umc_spring_first.global.auth.details;
+
+import com.example.umc_spring_first.domain.user.entity.User;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.List;
+
+
+@RequiredArgsConstructor
+public class CustomUserDetails implements UserDetails {
+
+ private final User user;
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return List.of(() -> user.getRole().toString());
+ }
+
+ @Override
+ public String getPassword() {
+ return user.getPassword();
+ }
+
+ @Override
+ public String getUsername() {
+ return user.getEmail();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/global/auth/enums/Role.java b/src/main/java/com/example/umc_spring_first/global/auth/enums/Role.java
new file mode 100644
index 0000000..07b35f8
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/auth/enums/Role.java
@@ -0,0 +1,5 @@
+package com.example.umc_spring_first.global.auth.enums;
+
+public enum Role {
+ ROLE_ADMIN, ROLE_USER
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/global/auth/enums/SocialType.java b/src/main/java/com/example/umc_spring_first/global/auth/enums/SocialType.java
new file mode 100644
index 0000000..b012852
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/auth/enums/SocialType.java
@@ -0,0 +1,4 @@
+package com.example.umc_spring_first.global.auth.enums;
+
+public enum SocialType {
+}
diff --git a/src/main/java/com/example/umc_spring_first/global/auth/jwt/JwtAuthFilter.java b/src/main/java/com/example/umc_spring_first/global/auth/jwt/JwtAuthFilter.java
new file mode 100644
index 0000000..d9ccdee
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/auth/jwt/JwtAuthFilter.java
@@ -0,0 +1,72 @@
+package com.example.umc_spring_first.global.auth.jwt;
+
+import com.example.umc_spring_first.global.apiPayload.ApiResponse;
+import com.example.umc_spring_first.global.apiPayload.code.GeneralErrorCode;
+import com.example.umc_spring_first.global.auth.service.CustomUserDetailsService;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.lang.NonNull;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+
+import java.io.IOException;
+@RequiredArgsConstructor
+public class JwtAuthFilter extends OncePerRequestFilter {
+
+ private final JwtUtil jwtUtil;
+ private final CustomUserDetailsService customUserDetailsService;
+
+ @Override
+ protected void doFilterInternal(
+ @NonNull HttpServletRequest request,
+ @NonNull HttpServletResponse response,
+ @NonNull FilterChain filterChain
+ ) throws ServletException, IOException {
+
+ try {
+ // 토큰 가져오기
+ String token = request.getHeader("Authorization");
+ // token이 없거나 Bearer가 아니면 넘기기
+ if (token == null || !token.startsWith("Bearer ")) {
+ filterChain.doFilter(request, response);
+ return;
+ }
+ // Bearer이면 추출
+ token = token.replace("Bearer ", "");
+ // AccessToken 검증하기: 올바른 토큰이면
+ if (jwtUtil.isValid(token)) {
+ // 토큰에서 이메일 추출
+ String email = jwtUtil.getEmail(token);
+ // 인증 객체 생성: 이메일로 찾아온 뒤, 인증 객체 생성
+ UserDetails user = customUserDetailsService.loadUserByUsername(email);
+ Authentication auth = new UsernamePasswordAuthenticationToken(
+ user,
+ null,
+ user.getAuthorities()
+ );
+ // 인증 완료 후 SecurityContextHolder에 넣기
+ SecurityContextHolder.getContext().setAuthentication(auth);
+ }
+ filterChain.doFilter(request, response);
+ } catch (Exception e) {
+ response.setContentType("application/json;charset=UTF-8");
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+
+ ApiResponse errorResponse = ApiResponse.onFailure(
+ GeneralErrorCode.UNAUTHORIZED,
+ null
+ );
+
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.writeValue(response.getOutputStream(), errorResponse);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/global/auth/jwt/JwtUtil.java b/src/main/java/com/example/umc_spring_first/global/auth/jwt/JwtUtil.java
new file mode 100644
index 0000000..2a18a2a
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/auth/jwt/JwtUtil.java
@@ -0,0 +1,93 @@
+package com.example.umc_spring_first.global.auth.jwt;
+
+import com.example.umc_spring_first.global.auth.details.CustomUserDetails;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.JwtException;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.security.Keys;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.SecretKey;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.stream.Collectors;
+
+@Component
+public class JwtUtil {
+
+ private final SecretKey secretKey;
+ private final Duration accessExpiration;
+
+ public JwtUtil(
+ @Value("${jwt.token.secretKey}") String secret,
+ @Value("${jwt.token.expiration.access}") Long accessExpiration
+ ) {
+ this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
+ this.accessExpiration = Duration.ofMillis(accessExpiration);
+ }
+
+ // AccessToken 생성
+ public String createAccessToken(CustomUserDetails user) {
+ return createToken(user, accessExpiration);
+ }
+
+ /** 토큰에서 이메일 가져오기
+ *
+ * @param token 유저 정보를 추출할 토큰
+ * @return 유저 이메일을 토큰에서 추출합니다
+ */
+ public String getEmail(String token) {
+ try {
+ return getClaims(token).getPayload().getSubject(); // Parsing해서 Subject 가져오기
+ } catch (JwtException e) {
+ return null;
+ }
+ }
+
+ /** 토큰 유효성 확인
+ *
+ * @param token 유효한지 확인할 토큰
+ * @return True, False 반환합니다
+ */
+ public boolean isValid(String token) {
+ try {
+ getClaims(token);
+ return true;
+ } catch (JwtException e) {
+ return false;
+ }
+ }
+
+ // 토큰 생성
+ private String createToken(CustomUserDetails user, Duration expiration) {
+ Instant now = Instant.now();
+
+ // 인가 정보
+ String authorities = user.getAuthorities().stream()
+ .map(GrantedAuthority::getAuthority)
+ .collect(Collectors.joining(","));
+
+ return Jwts.builder()
+ .subject(user.getUsername()) // User 이메일을 Subject로
+ .claim("role", authorities)
+ .claim("email", user.getUsername())
+ .issuedAt(Date.from(now)) // 언제 발급한지
+ .expiration(Date.from(now.plus(expiration))) // 언제까지 유효한지
+ .signWith(secretKey) // sign할 Key
+ .compact();
+ }
+
+ // 토큰 정보 가져오기
+ private Jws getClaims(String token) throws JwtException {
+ return Jwts.parser()
+ .verifyWith(secretKey)
+ .clockSkewSeconds(60)
+ .build()
+ .parseSignedClaims(token);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/global/auth/service/CustomUserDetailsService.java b/src/main/java/com/example/umc_spring_first/global/auth/service/CustomUserDetailsService.java
new file mode 100644
index 0000000..528b87a
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/auth/service/CustomUserDetailsService.java
@@ -0,0 +1,30 @@
+package com.example.umc_spring_first.global.auth.service;
+
+import com.example.umc_spring_first.domain.user.entity.User;
+import com.example.umc_spring_first.domain.user.exception.UserException;
+import com.example.umc_spring_first.domain.user.exception.code.UserErrorCode;
+import com.example.umc_spring_first.domain.user.repository.UserRepository;
+import com.example.umc_spring_first.global.auth.details.CustomUserDetails;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class CustomUserDetailsService implements UserDetailsService {
+
+ private final UserRepository userRepository;
+
+ @Override
+ public UserDetails loadUserByUsername(
+ String username
+ ) throws UsernameNotFoundException {
+ // 검증할 Member 조회
+ User user = userRepository.findByEmail(username)
+ .orElseThrow(() -> new UserException(UserErrorCode.NOT_FOUND));
+ // CustomUserDetails 반환
+ return new CustomUserDetails(user);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/global/config/SecurityConfig.java b/src/main/java/com/example/umc_spring_first/global/config/SecurityConfig.java
new file mode 100644
index 0000000..1e91864
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/config/SecurityConfig.java
@@ -0,0 +1,65 @@
+package com.example.umc_spring_first.global.config;
+
+import com.example.umc_spring_first.global.auth.jwt.JwtAuthFilter;
+import com.example.umc_spring_first.global.auth.jwt.JwtUtil;
+import com.example.umc_spring_first.global.auth.service.CustomUserDetailsService;
+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.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+@EnableWebSecurity
+@Configuration
+@RequiredArgsConstructor
+public class SecurityConfig {
+
+ private final JwtUtil jwtUtil;
+ private final CustomUserDetailsService customUserDetailsService;
+
+ private final String[] allowUris = {
+ "/swagger-ui/**",
+ "/swagger-resources/**",
+ "/v3/api-docs/**",
+ "/sign-up",
+ "/login"
+ };
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http
+ .authorizeHttpRequests(requests -> requests
+ .requestMatchers(allowUris).permitAll()
+ .requestMatchers("/admin/**").hasRole("ADMIN")
+ .anyRequest().authenticated()
+ )
+ // 폼로그인 비활성화
+ .formLogin(AbstractHttpConfigurer::disable)
+ // JwtAuthFilter를 UsernamePasswordAuthenticationFilter 앞에 추가
+ .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class)
+ .csrf(AbstractHttpConfigurer::disable)
+ .logout(logout -> logout
+ .logoutUrl("/logout")
+ .logoutSuccessUrl("/login?logout")
+ .permitAll()
+ );
+
+ return http.build();
+ }
+
+ @Bean
+ public JwtAuthFilter jwtAuthFilter() {
+ return new JwtAuthFilter(jwtUtil, customUserDetailsService);
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/umc_spring_first/global/config/SwaggerConfig.java b/src/main/java/com/example/umc_spring_first/global/config/SwaggerConfig.java
new file mode 100644
index 0000000..10597d0
--- /dev/null
+++ b/src/main/java/com/example/umc_spring_first/global/config/SwaggerConfig.java
@@ -0,0 +1,36 @@
+package com.example.umc_spring_first.global.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import io.swagger.v3.oas.models.servers.Server;
+
+@Configuration
+public class SwaggerConfig {
+ @Bean
+ public OpenAPI swagger() {
+ Info info = new Info().title("Project").description("Project Swagger").version("0.0.1");
+
+ // JWT 토큰 헤더 방식
+ String securityScheme = "JWT TOKEN";
+ SecurityRequirement securityRequirement = new SecurityRequirement().addList(securityScheme);
+
+ Components components = new Components()
+ .addSecuritySchemes(securityScheme, new SecurityScheme()
+ .name(securityScheme)
+ .type(SecurityScheme.Type.HTTP)
+ .scheme("Bearer")
+ .bearerFormat("JWT"));
+
+ return new OpenAPI()
+ .info(info)
+ .addServersItem(new Server().url("/"))
+ .addSecurityItem(securityRequirement)
+ .components(components);
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql
new file mode 100644
index 0000000..e69de29
diff --git a/week5_mission.md b/week5_mission.md
new file mode 100644
index 0000000..156f423
--- /dev/null
+++ b/week5_mission.md
@@ -0,0 +1,78 @@
+## 1번
+```java
+public interface ReviewRepository extends JpaRepository { }
+ /*
+ Review → 이 repository가 다룰 엔티티 클래스 타입 (@Entity 붙은 클래스)
+ Long → 그 엔티티의 식별자(ID) 타입
+ 즉, “Review 엔티티를 Long 타입 PK로 CRUD 하는 repository”라는 뜻
+ */
+```
+
+## 2번
+```java
+public interface UserRepository extends JpaRepository {
+
+ @Query("""
+ select new com.example.umc_spring_first.domain.user.dto.UserProfileDto(
+ u.name, u.email, u.phone, u.point
+ )
+ from User u
+ where u.id = :userId
+ """)
+ Optional findProfileById(@Param("userId") Long userId);
+ /*
+ Optional :
+ - 행이 있으면 `UserProfileDto`가 들어있는 `Optional`을 반환
+ - 없으면 `Optional.empty()` 반환 (null 대신)
+ 즉, NullPointerException 방지
+ */
+}
+```
+
+## 3번
+```java
+public interface UserMissionRepository extends JpaRepository {
+
+ // 3번: 내가 진행/완료한 미션 목록 (정렬: m.id DESC)
+ @Query("""
+ select new com.example.umc_spring_first.domain.mission.dto.MyMissionRowDto(
+ m.point, s.name, m.description, um.status
+ )
+ from UserMission um
+ join um.mission m
+ join m.store s
+ where um.user.id = :userId
+ and (:status is null or um.status = :status)
+ order by m.id desc
+ """)
+ Page findMyMissions(
+ @Param("userId") Long userId,
+ @Param("status") String status, // 'y' / 'n' 등
+ Pageable pageable
+ );
+}
+```
+
+## 4번
+```java
+ public interface UserMissionRepository extends JpaRepository {
+
+ // 4번: 홈 화면 - 진행 가능('n') 미션 목록 (정렬: deadline ASC)
+ @Query("""
+ select new com.example.umc_spring_first.domain.mission.dto.HomeMissionRowDto(
+ m.point, m.description, um.deadline, s.name, s.address
+ )
+ from UserMission um
+ join um.mission m
+ join m.store s
+ where um.user.id = :userId
+ and um.status = :status
+ order by um.deadline asc
+ """)
+ Page findHomeMissions(
+ @Param("userId") Long userId,
+ @Param("status") String status, // 예: 'n'
+ Pageable pageable // PageRequest.of(page, size)
+ );
+}
+```
\ No newline at end of file
diff --git a/week7/global/apiPayload/ApiResponse.java b/week7/global/apiPayload/ApiResponse.java
new file mode 100644
index 0000000..ad14403
--- /dev/null
+++ b/week7/global/apiPayload/ApiResponse.java
@@ -0,0 +1,35 @@
+package com.example.umc_spring_first.week7.global.apiPayload;
+
+import com.example.umc_spring_first.week7.global.apiPayload.code.BaseErrorCode;
+import com.example.umc_spring_first.week7.global.apiPayload.code.BaseSuccessCode;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+@JsonPropertyOrder({"isSuccess", "code", "message", "result"})
+public class ApiResponse {
+
+ @JsonProperty("isSuccess")
+ private final Boolean isSuccess;
+
+ @JsonProperty("code")
+ private final String code;
+
+ @JsonProperty("message")
+ private final String message;
+
+ @JsonProperty("result")
+ private T result;
+
+ // 성공한 경우 (result 포함)
+ public static ApiResponse onSuccess(BaseSuccessCode code, T result) {
+ return new ApiResponse<>(true, code.getCode(), code.getMessage(), result);
+ }
+ // 실패한 경우 (result 포함)
+ public static ApiResponse onFailure(BaseErrorCode code, T result) {
+ return new ApiResponse<>(false, code.getCode(), code.getMessage(), result);
+ }
+}
diff --git a/week7/global/apiPayload/code/BaseErrorCode.java b/week7/global/apiPayload/code/BaseErrorCode.java
new file mode 100644
index 0000000..09b4407
--- /dev/null
+++ b/week7/global/apiPayload/code/BaseErrorCode.java
@@ -0,0 +1,10 @@
+package com.example.umc_spring_first.week7.global.apiPayload.code;
+
+import org.springframework.http.HttpStatus;
+
+public interface BaseErrorCode {
+
+ HttpStatus getStatus();
+ String getCode();
+ String getMessage();
+}
diff --git a/week7/global/apiPayload/code/BaseSuccessCode.java b/week7/global/apiPayload/code/BaseSuccessCode.java
new file mode 100644
index 0000000..83247e0
--- /dev/null
+++ b/week7/global/apiPayload/code/BaseSuccessCode.java
@@ -0,0 +1,10 @@
+package com.example.umc_spring_first.week7.global.apiPayload.code;
+
+import org.springframework.http.HttpStatus;
+
+public interface BaseSuccessCode { //BaseErrorCode와 구조를 완전히 똑같이 유지하는 게 핵심
+
+ HttpStatus getStatus();
+ String getCode();
+ String getMessage();
+}
diff --git a/week7/global/apiPayload/code/GeneralErrorCode.java b/week7/global/apiPayload/code/GeneralErrorCode.java
new file mode 100644
index 0000000..df5d344
--- /dev/null
+++ b/week7/global/apiPayload/code/GeneralErrorCode.java
@@ -0,0 +1,31 @@
+package com.example.umc_spring_first.week7.global.apiPayload.code;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+@AllArgsConstructor
+public enum GeneralErrorCode implements BaseErrorCode {
+
+ BAD_REQUEST(HttpStatus.BAD_REQUEST,
+ "COMMON400_1",
+ "잘못된 요청입니다."),
+ UNAUTHORIZED(HttpStatus.UNAUTHORIZED,
+ "AUTH401_1",
+ "인증이 필요합니다."),
+ FORBIDDEN(HttpStatus.FORBIDDEN,
+ "AUTH403_1",
+ "요청이 거부되었습니다."),
+ NOT_FOUND(HttpStatus.NOT_FOUND,
+ "COMMON404_1",
+ "요청한 리소스를 찾을 수 없습니다."),
+ INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR,
+ "COMMON500_1",
+ "예기치 않은 서버 에러가 발생했습니다."),
+ ;
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+}
\ No newline at end of file
diff --git a/week7/global/apiPayload/code/GeneralSuccessCode.java b/week7/global/apiPayload/code/GeneralSuccessCode.java
new file mode 100644
index 0000000..eb39b7c
--- /dev/null
+++ b/week7/global/apiPayload/code/GeneralSuccessCode.java
@@ -0,0 +1,25 @@
+package com.example.umc_spring_first.week7.global.apiPayload.code;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+@AllArgsConstructor
+public enum GeneralSuccessCode implements BaseSuccessCode {
+
+ OK(HttpStatus.OK,
+ "COMMON200",
+ "요청이 성공적으로 처리되었습니다."),
+ CREATED(HttpStatus.CREATED,
+ "COMMON201",
+ "리소스가 성공적으로 생성되었습니다."),
+ NO_CONTENT(HttpStatus.NO_CONTENT,
+ "COMMON204",
+ "요청이 성공했지만 반환할 데이터가 없습니다."),
+ ;
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+}
diff --git a/week7/global/apiPayload/exception/GeneralException.java b/week7/global/apiPayload/exception/GeneralException.java
new file mode 100644
index 0000000..e6adf66
--- /dev/null
+++ b/week7/global/apiPayload/exception/GeneralException.java
@@ -0,0 +1,12 @@
+package com.example.umc_spring_first.week7.global.apiPayload.exception;
+
+import com.example.umc_spring_first.week7.global.apiPayload.code.BaseErrorCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public class GeneralException extends RuntimeException {
+
+ private final BaseErrorCode code;
+}
diff --git a/week7/global/apiPayload/handler/GeneralExceptionAdvice.java b/week7/global/apiPayload/handler/GeneralExceptionAdvice.java
new file mode 100644
index 0000000..6cc6f7f
--- /dev/null
+++ b/week7/global/apiPayload/handler/GeneralExceptionAdvice.java
@@ -0,0 +1,42 @@
+package com.example.umc_spring_first.week7.global.apiPayload.handler;
+
+import com.example.umc_spring_first.week7.global.apiPayload.ApiResponse;
+import com.example.umc_spring_first.week7.global.apiPayload.code.BaseErrorCode;
+import com.example.umc_spring_first.week7.global.apiPayload.code.GeneralErrorCode;
+import com.example.umc_spring_first.week7.global.apiPayload.exception.GeneralException;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@RestControllerAdvice
+public class GeneralExceptionAdvice {
+
+ // 애플리케이션에서 발생하는 커스텀 예외를 처리
+ @ExceptionHandler(GeneralException.class)
+ public ResponseEntity> handleException(
+ GeneralException ex
+ ) {
+
+ return ResponseEntity.status(ex.getCode().getStatus())
+ .body(ApiResponse.onFailure(
+ ex.getCode(),
+ null
+ )
+ );
+ }
+
+ // 그 외의 정의되지 않은 모든 예외 처리
+ @ExceptionHandler(Exception.class)
+ public ResponseEntity> handleException(
+ Exception ex
+ ) {
+
+ BaseErrorCode code = GeneralErrorCode.INTERNAL_SERVER_ERROR;
+ return ResponseEntity.status(code.getStatus())
+ .body(ApiResponse.onFailure(
+ code,
+ ex.getMessage()
+ )
+ );
+ }
+}
diff --git a/week7/test/controller/TestController.java b/week7/test/controller/TestController.java
new file mode 100644
index 0000000..508511e
--- /dev/null
+++ b/week7/test/controller/TestController.java
@@ -0,0 +1,44 @@
+package com.example.umc_spring_first.week7.test.controller;
+
+import com.example.umc_spring_first.global.apiPayload.ApiResponse;
+import com.example.umc_spring_first.global.apiPayload.code.GeneralSuccessCode;
+import com.example.umc_spring_first.week7.test.converter.TestConverter;
+import com.example.umc_spring_first.week7.test.dto.res.TestResDTO;
+import com.example.umc_spring_first.week7.test.service.query.TestQueryService;
+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;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/temp")
+public class TestController {
+
+ private final TestQueryService testQueryService;
+
+ @GetMapping("/test")
+ public ApiResponse test() {
+ // 응답 코드 정의
+ GeneralSuccessCode code = GeneralSuccessCode.OK;
+
+ return ApiResponse.onSuccess(
+ code,
+ TestConverter.toTestingDTO("This is Test!")
+ );
+ }
+
+ // 예외 상황
+ @GetMapping("/exception")
+ public ApiResponse exception(
+ @RequestParam Long flag
+ ) {
+
+ testQueryService.checkFlag(flag);
+
+ // 응답 코드 정의
+ GeneralSuccessCode code = GeneralSuccessCode.OK;
+ return ApiResponse.onSuccess(code, TestConverter.toExceptionDTO("This is Test!"));
+ }
+}
diff --git a/week7/test/converter/TestConverter.java b/week7/test/converter/TestConverter.java
new file mode 100644
index 0000000..36cca74
--- /dev/null
+++ b/week7/test/converter/TestConverter.java
@@ -0,0 +1,24 @@
+package com.example.umc_spring_first.week7.test.converter;
+
+import com.example.umc_spring_first.week7.test.dto.res.TestResDTO;
+
+public class TestConverter {
+
+ // 객체 -> DTO
+ public static TestResDTO.Testing toTestingDTO(
+ String testing
+ ) {
+ return TestResDTO.Testing.builder()
+ .testString(testing)
+ .build();
+ }
+
+ // 객체 -> DTO
+ public static TestResDTO.Exception toExceptionDTO(
+ String testing
+ ){
+ return TestResDTO.Exception.builder()
+ .testString(testing)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/week7/test/dto/req/TestReqDTO.java b/week7/test/dto/req/TestReqDTO.java
new file mode 100644
index 0000000..d757210
--- /dev/null
+++ b/week7/test/dto/req/TestReqDTO.java
@@ -0,0 +1,4 @@
+package com.example.umc_spring_first.week7.test.dto.req;
+
+public class TestReqDTO {
+}
diff --git a/week7/test/dto/res/TestResDTO.java b/week7/test/dto/res/TestResDTO.java
new file mode 100644
index 0000000..9844cd9
--- /dev/null
+++ b/week7/test/dto/res/TestResDTO.java
@@ -0,0 +1,20 @@
+package com.example.umc_spring_first.week7.test.dto.res;
+
+import lombok.Builder;
+import lombok.Getter;
+
+public class TestResDTO {
+
+ @Getter
+ @Builder
+ public static class Testing {
+ private String testString;
+ }
+
+ @Builder
+ @Getter
+ public static class Exception {
+ private String testString;
+ }
+}
+
diff --git a/week7/test/exception/TestException.java b/week7/test/exception/TestException.java
new file mode 100644
index 0000000..6c5840b
--- /dev/null
+++ b/week7/test/exception/TestException.java
@@ -0,0 +1,11 @@
+package com.example.umc_spring_first.week7.test.exception;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import com.example.umc_spring_first.global.apiPayload.exception.GeneralException;
+
+public class TestException extends GeneralException {
+ public TestException(BaseErrorCode code) {
+ super(code);
+ }
+}
+
diff --git a/week7/test/exception/code/TestErrorCode.java b/week7/test/exception/code/TestErrorCode.java
new file mode 100644
index 0000000..f1aee2d
--- /dev/null
+++ b/week7/test/exception/code/TestErrorCode.java
@@ -0,0 +1,19 @@
+package com.example.umc_spring_first.week7.test.exception.code;
+
+import com.example.umc_spring_first.global.apiPayload.code.BaseErrorCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+@AllArgsConstructor
+public enum TestErrorCode implements BaseErrorCode {
+
+ // For test
+ TEST_EXCEPTION(HttpStatus.BAD_REQUEST, "TEST400_1", "이거는 테스트"),
+ ;
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+}
diff --git a/week7/test/service/command/ReviewCommandService.java b/week7/test/service/command/ReviewCommandService.java
new file mode 100644
index 0000000..31bfd21
--- /dev/null
+++ b/week7/test/service/command/ReviewCommandService.java
@@ -0,0 +1,4 @@
+package com.example.umc_spring_first.week7.test.service.command;
+
+public interface ReviewCommandService {
+}
diff --git a/week7/test/service/command/ReviewCommandServiceImpl.java b/week7/test/service/command/ReviewCommandServiceImpl.java
new file mode 100644
index 0000000..53c2494
--- /dev/null
+++ b/week7/test/service/command/ReviewCommandServiceImpl.java
@@ -0,0 +1,4 @@
+package com.example.umc_spring_first.week7.test.service.command;
+
+public class ReviewCommandServiceImpl {
+}
diff --git a/week7/test/service/query/TestQueryService.java b/week7/test/service/query/TestQueryService.java
new file mode 100644
index 0000000..ae90465
--- /dev/null
+++ b/week7/test/service/query/TestQueryService.java
@@ -0,0 +1,5 @@
+package com.example.umc_spring_first.week7.test.service.query;
+
+public interface TestQueryService {
+ void checkFlag(Long flag);
+}
diff --git a/week7/test/service/query/TestQueryServiceImpl.java b/week7/test/service/query/TestQueryServiceImpl.java
new file mode 100644
index 0000000..63a7a2c
--- /dev/null
+++ b/week7/test/service/query/TestQueryServiceImpl.java
@@ -0,0 +1,18 @@
+package com.example.umc_spring_first.week7.test.service.query;
+
+import com.example.umc_spring_first.week7.test.exception.TestException;
+import com.example.umc_spring_first.week7.test.exception.code.TestErrorCode;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class TestQueryServiceImpl implements TestQueryService {
+
+ @Override
+ public void checkFlag(Long flag){
+ if (flag == 1){
+ throw new TestException(TestErrorCode.TEST_EXCEPTION);
+ }
+ }
+}
\ No newline at end of file
diff --git a/week8/domain/mission/controller/MissionController.java b/week8/domain/mission/controller/MissionController.java
new file mode 100644
index 0000000..df31105
--- /dev/null
+++ b/week8/domain/mission/controller/MissionController.java
@@ -0,0 +1,34 @@
+package com.example.umc_spring_first.domain.mission.controller;
+
+import com.example.umc_spring_first.domain.mission.dto.MissionCreateRequest;
+import com.example.umc_spring_first.domain.mission.service.MissionService;
+import com.example.umc_spring_first.domain.mission.service.UserMissionService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api")
+@RequiredArgsConstructor
+public class MissionController {
+
+ private final MissionService missionService;
+ private final UserMissionService userMissionService;
+
+ @PostMapping("/stores/{storeId}/missions")
+ public ResponseEntity> addMission(
+ @PathVariable Long storeId,
+ @RequestBody MissionCreateRequest req
+ ) {
+ Long id = missionService.addMission(storeId, req);
+ return ResponseEntity.ok(id);
+ }
+
+ @PostMapping("/missions/{missionId}/challenge")
+ public ResponseEntity> challengeMission(
+ @PathVariable Long missionId
+ ) {
+ Long id = userMissionService.challengeMission(missionId);
+ return ResponseEntity.ok(id);
+ }
+}
diff --git a/week8/domain/mission/dto/HomeMissionRowDto.java b/week8/domain/mission/dto/HomeMissionRowDto.java
new file mode 100644
index 0000000..bd56cc2
--- /dev/null
+++ b/week8/domain/mission/dto/HomeMissionRowDto.java
@@ -0,0 +1,5 @@
+package com.example.umc_spring_first.domain.mission.dto;
+
+import java.time.LocalDateTime;
+public record HomeMissionRowDto(Integer point, String description,
+ LocalDateTime deadline, String name, String address) { }
diff --git a/week8/domain/mission/dto/MissionCreateRequest.java b/week8/domain/mission/dto/MissionCreateRequest.java
new file mode 100644
index 0000000..7aed670
--- /dev/null
+++ b/week8/domain/mission/dto/MissionCreateRequest.java
@@ -0,0 +1,11 @@
+package com.example.umc_spring_first.domain.mission.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class MissionCreateRequest {
+ private String description;
+ private int point;
+}
diff --git a/week8/domain/mission/dto/MyMissionRowDto.java b/week8/domain/mission/dto/MyMissionRowDto.java
new file mode 100644
index 0000000..4354d61
--- /dev/null
+++ b/week8/domain/mission/dto/MyMissionRowDto.java
@@ -0,0 +1,16 @@
+package com.example.umc_spring_first.domain.mission.dto;
+
+import com.example.umc_spring_first.domain.mission.enums.UserMissionStatus;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor
+public class MyMissionRowDto {
+ private Integer point;
+ private String storeName;
+ private String description;
+ private UserMissionStatus status; // String → Enum(UserMissionStatus)로 변경
+}
diff --git a/week8/domain/mission/entity/Mission.java b/week8/domain/mission/entity/Mission.java
new file mode 100644
index 0000000..ceb05c3
--- /dev/null
+++ b/week8/domain/mission/entity/Mission.java
@@ -0,0 +1,29 @@
+package com.example.umc_spring_first.domain.mission.entity;
+
+import com.example.umc_spring_first.domain.store.entity.Store;
+import jakarta.persistence.*;
+import lombok.*;
+import java.time.LocalDateTime;
+import java.util.*;
+
+@Entity
+@Table(name = "mission")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
+public class Mission {
+
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "store_id", nullable = false)
+ private Store store;
+
+ private Integer point;
+ private LocalDateTime createAt;
+ private LocalDateTime updateAt;
+ private String status;
+ private String description;
+
+ @OneToMany(mappedBy = "mission", cascade = CascadeType.ALL, orphanRemoval = true)
+ private List userMissions = new ArrayList<>();
+}
diff --git a/week8/domain/mission/entity/UserMission.java b/week8/domain/mission/entity/UserMission.java
new file mode 100644
index 0000000..023ffc8
--- /dev/null
+++ b/week8/domain/mission/entity/UserMission.java
@@ -0,0 +1,32 @@
+package com.example.umc_spring_first.domain.mission.entity;
+
+import com.example.umc_spring_first.domain.mission.enums.UserMissionStatus; // ← 추가
+import com.example.umc_spring_first.domain.user.entity.User;
+import jakarta.persistence.*;
+import lombok.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "user_mission")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
+public class UserMission {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id", nullable = false)
+ private User user;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "mission_id", nullable = false)
+ private Mission mission;
+
+ private LocalDateTime deadline;
+ private LocalDateTime createAt;
+ private LocalDateTime updateAt;
+
+ @Enumerated(EnumType.STRING) // enum이라 추가
+ private UserMissionStatus status; // enum으로 변경
+}
diff --git a/week8/domain/mission/enums/MissionStatus.java b/week8/domain/mission/enums/MissionStatus.java
new file mode 100644
index 0000000..6702d95
--- /dev/null
+++ b/week8/domain/mission/enums/MissionStatus.java
@@ -0,0 +1,5 @@
+package com.example.umc_spring_first.domain.mission.enums;
+
+public enum MissionStatus {
+ ONGOING, DONE, PAUSED
+}
diff --git a/week8/domain/mission/enums/UserMissionStatus.java b/week8/domain/mission/enums/UserMissionStatus.java
new file mode 100644
index 0000000..9d05b7d
--- /dev/null
+++ b/week8/domain/mission/enums/UserMissionStatus.java
@@ -0,0 +1,6 @@
+package com.example.umc_spring_first.domain.mission.enums;
+
+public enum UserMissionStatus {
+ IN_PROGRESS,
+ COMPLETED
+}
diff --git a/week8/domain/mission/repository/MissionRepository.java b/week8/domain/mission/repository/MissionRepository.java
new file mode 100644
index 0000000..202fa47
--- /dev/null
+++ b/week8/domain/mission/repository/MissionRepository.java
@@ -0,0 +1,7 @@
+package com.example.umc_spring_first.domain.mission.repository;
+
+import com.example.umc_spring_first.domain.mission.entity.Mission;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface MissionRepository extends JpaRepository {
+}
diff --git a/week8/domain/mission/repository/UserMissionRepository.java b/week8/domain/mission/repository/UserMissionRepository.java
new file mode 100644
index 0000000..11f635a
--- /dev/null
+++ b/week8/domain/mission/repository/UserMissionRepository.java
@@ -0,0 +1,48 @@
+package com.example.umc_spring_first.domain.mission.repository;
+
+import com.example.umc_spring_first.domain.mission.dto.HomeMissionRowDto;
+import com.example.umc_spring_first.domain.mission.dto.MyMissionRowDto;
+import com.example.umc_spring_first.domain.mission.entity.UserMission;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.*;
+import org.springframework.data.repository.query.Param;
+
+public interface UserMissionRepository extends JpaRepository {
+
+ // 3번: 내가 진행/완료한 미션 목록 (정렬: m.id DESC)
+ @Query("""
+ select new com.example.umc_spring_first.domain.mission.dto.MyMissionRowDto(
+ m.point, s.name, m.description, um.status
+ )
+ from UserMission um
+ join um.mission m
+ join m.store s
+ where um.user.id = :userId
+ and (:status is null or um.status = :status)
+ order by m.id desc
+ """)
+ Page findMyMissions(
+ @Param("userId") Long userId,
+ @Param("status") String status, // 'y' / 'n' 등
+ Pageable pageable
+ );
+
+ // 4번: 홈 화면 - 진행 가능('n') 미션 목록 (정렬: deadline ASC)
+ @Query("""
+ select new com.example.umc_spring_first.domain.mission.dto.HomeMissionRowDto(
+ m.point, m.description, um.deadline, s.name, s.address
+ )
+ from UserMission um
+ join um.mission m
+ join m.store s
+ where um.user.id = :userId
+ and um.status = :status
+ order by um.deadline asc
+ """)
+ Page findHomeMissions(
+ @Param("userId") Long userId,
+ @Param("status") String status, // 예: 'n'
+ Pageable pageable // PageRequest.of(page, size)
+ );
+}
\ No newline at end of file
diff --git a/week8/domain/mission/service/MissionService.java b/week8/domain/mission/service/MissionService.java
new file mode 100644
index 0000000..7c424c9
--- /dev/null
+++ b/week8/domain/mission/service/MissionService.java
@@ -0,0 +1,34 @@
+package com.example.umc_spring_first.domain.mission.service;
+
+import com.example.umc_spring_first.domain.mission.dto.MissionCreateRequest;
+import com.example.umc_spring_first.domain.mission.entity.Mission;
+import com.example.umc_spring_first.domain.mission.repository.MissionRepository;
+import com.example.umc_spring_first.domain.store.entity.Store;
+import com.example.umc_spring_first.domain.store.repository.StoreRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@Transactional
+@RequiredArgsConstructor
+public class MissionService {
+
+ private final MissionRepository missionRepository;
+ private final StoreRepository storeRepository;
+
+ public Long addMission(Long storeId, MissionCreateRequest req) {
+
+ Store store = storeRepository.findById(storeId)
+ .orElseThrow(() -> new RuntimeException("가게 없음"));
+
+ Mission mission = Mission.builder()
+ .description(req.getDescription())
+ .point(req.getPoint())
+ .store(store)
+ .build();
+
+ missionRepository.save(mission);
+ return mission.getId();
+ }
+}
diff --git a/week8/domain/mission/service/UserMissionService.java b/week8/domain/mission/service/UserMissionService.java
new file mode 100644
index 0000000..6031a77
--- /dev/null
+++ b/week8/domain/mission/service/UserMissionService.java
@@ -0,0 +1,43 @@
+package com.example.umc_spring_first.domain.mission.service;
+
+import com.example.umc_spring_first.domain.mission.entity.Mission;
+import com.example.umc_spring_first.domain.mission.entity.UserMission;
+import com.example.umc_spring_first.domain.mission.enums.UserMissionStatus;
+import com.example.umc_spring_first.domain.mission.repository.MissionRepository;
+import com.example.umc_spring_first.domain.mission.repository.UserMissionRepository;
+import com.example.umc_spring_first.domain.user.entity.User;
+import com.example.umc_spring_first.domain.user.repository.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class UserMissionService {
+
+ private final UserMissionRepository userMissionRepository;
+ private final MissionRepository missionRepository;
+ private final UserRepository userRepository;
+
+ public Long challengeMission(Long missionId) {
+
+ Long userId = 1L; // 로그인 없음 → 하드코딩
+
+ User user = userRepository.findById(userId)
+ .orElseThrow(() -> new RuntimeException("유저 없음"));
+
+ Mission mission = missionRepository.findById(missionId)
+ .orElseThrow(() -> new RuntimeException("미션 없음"));
+
+ UserMission um = UserMission.builder()
+ .mission(mission)
+ .user(user)
+ .status(UserMissionStatus.IN_PROGRESS)
+ .build();
+
+ userMissionRepository.save(um);
+
+ return um.getId();
+ }
+}
diff --git a/week8/domain/review/controller/ReviewController.java b/week8/domain/review/controller/ReviewController.java
new file mode 100644
index 0000000..4cb0961
--- /dev/null
+++ b/week8/domain/review/controller/ReviewController.java
@@ -0,0 +1,48 @@
+package com.example.umc_spring_first.domain.review.controller;
+
+import com.example.umc_spring_first.domain.review.dto.MyReviewRowDto;
+import com.example.umc_spring_first.domain.review.dto.ReviewCreateRequest;
+import com.example.umc_spring_first.domain.review.service.ReviewQueryService;
+import com.example.umc_spring_first.domain.review.service.ReviewService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/reviews")
+public class ReviewController {
+
+ private final ReviewQueryService reviewQueryService;
+ private final ReviewService reviewService;
+
+ /**
+ * 리뷰 조회 API
+ * /api/reviews?storeId=1
+ * /api/reviews?stars=4
+ * /api/reviews?storeId=1&stars=4
+ */
+ @GetMapping
+ public Page getReviews(
+ @RequestParam(required = false) Long storeId,
+ @RequestParam(required = false, name = "stars") Integer starBand,
+ @PageableDefault(size = 10) Pageable pageable
+ ) {
+ return reviewQueryService.getReviews(storeId, starBand, pageable);
+ }
+
+ /**
+ * 리뷰 생성 API
+ * POST /api/reviews
+ */
+ @PostMapping
+ public ResponseEntity createReview(
+ @RequestBody ReviewCreateRequest req
+ ) {
+ Long reviewId = reviewService.createReview(req);
+ return ResponseEntity.ok(reviewId);
+ }
+}
diff --git a/week8/domain/review/dto/MyReviewRowDto.java b/week8/domain/review/dto/MyReviewRowDto.java
new file mode 100644
index 0000000..c3794fe
--- /dev/null
+++ b/week8/domain/review/dto/MyReviewRowDto.java
@@ -0,0 +1,12 @@
+package com.example.umc_spring_first.domain.review.dto;
+
+import java.time.LocalDateTime;
+
+public record MyReviewRowDto( //조회용 DTO -> 조회 결과를 프론트에 내려줄 때 사용
+ Long reviewId,
+ Long storeId,
+ String storeName,
+ Float rating,
+ String content,
+ LocalDateTime createdAt
+) {}
diff --git a/week8/domain/review/dto/ReviewCreateRequest.java b/week8/domain/review/dto/ReviewCreateRequest.java
new file mode 100644
index 0000000..93d7145
--- /dev/null
+++ b/week8/domain/review/dto/ReviewCreateRequest.java
@@ -0,0 +1,21 @@
+package com.example.umc_spring_first.domain.review.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class ReviewCreateRequest { //리뷰 생성용 DTO -> 리뷰 생성 시 클라이언트가 보내는 데이터를 받는 DTO
+
+ // 어떤 가게에 대한 리뷰인지
+ private Long storeId;
+
+ // 별점
+ private Float rating;
+
+ // 리뷰 내용
+ private String content;
+
+ // 이미지 URL 같은 거 쓰려면 여기도 추가 가능
+ // private String image;
+}
diff --git a/week8/domain/review/entity/Review.java b/week8/domain/review/entity/Review.java
new file mode 100644
index 0000000..ffcfd20
--- /dev/null
+++ b/week8/domain/review/entity/Review.java
@@ -0,0 +1,30 @@
+package com.example.umc_spring_first.domain.review.entity;
+
+import com.example.umc_spring_first.domain.store.entity.Store;
+import com.example.umc_spring_first.domain.user.entity.User;
+import jakarta.persistence.*;
+import lombok.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "review")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
+public class Review {
+
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id", nullable = false)
+ private User user;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "store_id", nullable = false)
+ private Store store;
+
+ private Float rating;
+ private String content;
+ private String image;
+ private LocalDateTime createAt;
+ private LocalDateTime updateAt;
+}
diff --git a/week8/domain/review/repository/ReviewQueryRepository.java b/week8/domain/review/repository/ReviewQueryRepository.java
new file mode 100644
index 0000000..7a44330
--- /dev/null
+++ b/week8/domain/review/repository/ReviewQueryRepository.java
@@ -0,0 +1,9 @@
+package com.example.umc_spring_first.domain.review.repository;
+
+import com.example.umc_spring_first.domain.review.dto.MyReviewRowDto;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+public interface ReviewQueryRepository {
+ Page findReviews(Long storeId, Integer starBand, Pageable pageable);
+}
diff --git a/week8/domain/review/repository/ReviewQueryRepositoryImpl.java b/week8/domain/review/repository/ReviewQueryRepositoryImpl.java
new file mode 100644
index 0000000..f5f31c1
--- /dev/null
+++ b/week8/domain/review/repository/ReviewQueryRepositoryImpl.java
@@ -0,0 +1,71 @@
+package com.example.umc_spring_first.domain.review.repository;
+
+import com.example.umc_spring_first.domain.review.dto.MyReviewRowDto;
+import com.example.umc_spring_first.domain.review.entity.QReview;
+import com.example.umc_spring_first.domain.store.entity.QStore;
+import com.querydsl.core.BooleanBuilder;
+import com.querydsl.core.types.Projections;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.support.PageableExecutionUtils;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+@RequiredArgsConstructor
+public class ReviewQueryRepositoryImpl implements ReviewQueryRepository {
+
+ private final JPAQueryFactory query;
+
+ @Override
+ public Page findReviews(Long storeId, Integer starBand, Pageable pageable) {
+ QReview r = QReview.review;
+ QStore s = QStore.store;
+
+ BooleanBuilder where = new BooleanBuilder();
+
+ //store 필터는 r.store.id 로 통일
+ if (storeId != null) {
+ where.and(r.store.id.eq(storeId));
+ }
+
+ // 별점 밴드(정수별 1점 구간: 4 → 4.0~4.99), 5는 5.0 고정
+ if (starBand != null) {
+ if (starBand == 5) {
+ where.and(r.rating.goe(5.0f).and(r.rating.loe(5.0f)));
+ } else if (starBand >= 1 && starBand <= 4) {
+ float lower = starBand;
+ float upper = starBand + 0.9999f;
+ where.and(r.rating.goe(lower).and(r.rating.loe(upper)));
+ }
+ }
+
+ List content = query
+ .select(Projections.constructor(MyReviewRowDto.class,
+ r.id,
+ r.store.id,
+ s.name,
+ r.rating,
+ r.content,
+ r.createAt
+ ))
+ .from(r)
+ .join(r.store, s)
+ .where(where)
+ .orderBy(r.id.desc())
+ .offset(pageable.getOffset())
+ .limit(pageable.getPageSize())
+ .fetch();
+
+ // where 에 s 필드 참조가 없도록 구성했으므로 join 없이 count 가능
+ var countQuery = query
+ .select(r.id.count())
+ .from(r)
+ .where(where);
+
+ return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne);
+ }
+}
diff --git a/week8/domain/review/repository/ReviewRepository.java b/week8/domain/review/repository/ReviewRepository.java
new file mode 100644
index 0000000..2e5bdcc
--- /dev/null
+++ b/week8/domain/review/repository/ReviewRepository.java
@@ -0,0 +1,7 @@
+package com.example.umc_spring_first.domain.review.repository;
+
+import com.example.umc_spring_first.domain.review.entity.Review;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface ReviewRepository extends JpaRepository, ReviewQueryRepository {
+}
diff --git a/week8/domain/review/service/ReviewQueryService.java b/week8/domain/review/service/ReviewQueryService.java
new file mode 100644
index 0000000..88b8d5c
--- /dev/null
+++ b/week8/domain/review/service/ReviewQueryService.java
@@ -0,0 +1,21 @@
+package com.example.umc_spring_first.domain.review.service;
+
+import com.example.umc_spring_first.domain.review.dto.MyReviewRowDto;
+import com.example.umc_spring_first.domain.review.repository.ReviewRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+public class ReviewQueryService {
+
+ private final ReviewRepository reviewRepository;
+
+ @Transactional(readOnly = true)
+ public Page getReviews(Long storeId, Integer starBand, Pageable pageable) {
+ return reviewRepository.findReviews(storeId, starBand, pageable);
+ }
+}
\ No newline at end of file
diff --git a/week8/domain/review/service/ReviewService.java b/week8/domain/review/service/ReviewService.java
new file mode 100644
index 0000000..8273ab8
--- /dev/null
+++ b/week8/domain/review/service/ReviewService.java
@@ -0,0 +1,51 @@
+package com.example.umc_spring_first.domain.review.service;
+
+import com.example.umc_spring_first.domain.review.dto.ReviewCreateRequest;
+import com.example.umc_spring_first.domain.review.entity.Review;
+import com.example.umc_spring_first.domain.review.repository.ReviewRepository;
+import com.example.umc_spring_first.domain.store.entity.Store;
+import com.example.umc_spring_first.domain.store.repository.StoreRepository;
+import com.example.umc_spring_first.domain.user.entity.User;
+import com.example.umc_spring_first.domain.user.repository.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class ReviewService {
+
+ private final ReviewRepository reviewRepository;
+ private final UserRepository userRepository;
+ private final StoreRepository storeRepository;
+
+ public Long createReview(ReviewCreateRequest req) {
+
+ // 로그인 미구현 → 유저 하드코딩
+ Long userId = 1L;
+
+ User user = userRepository.findById(userId)
+ .orElseThrow(() -> new RuntimeException("유저를 찾을 수 없습니다."));
+
+ Store store = storeRepository.findById(req.getStoreId())
+ .orElseThrow(() -> new RuntimeException("가게를 찾을 수 없습니다."));
+
+ LocalDateTime now = LocalDateTime.now();
+
+ Review review = Review.builder()
+ .user(user)
+ .store(store)
+ .rating(req.getRating())
+ .content(req.getContent())
+ // .image(req.getImage()) // 필요하면 DTO에 추가해서 사용
+ .createAt(now)
+ .updateAt(now)
+ .build();
+
+ reviewRepository.save(review);
+ return review.getId();
+ }
+}
diff --git a/week8/domain/store/controller/StoreController.java b/week8/domain/store/controller/StoreController.java
new file mode 100644
index 0000000..264a2d5
--- /dev/null
+++ b/week8/domain/store/controller/StoreController.java
@@ -0,0 +1,23 @@
+package com.example.umc_spring_first.domain.store.controller;
+
+import com.example.umc_spring_first.domain.store.dto.StoreCreateRequest;
+import com.example.umc_spring_first.domain.store.service.StoreService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api")
+@RequiredArgsConstructor
+public class StoreController {
+
+ private final StoreService storeService;
+
+ @PostMapping("/stores")
+ public ResponseEntity> addStore(
+ @RequestBody StoreCreateRequest req
+ ) {
+ Long id = storeService.addStore(req);
+ return ResponseEntity.ok(id);
+ }
+}
diff --git a/week8/domain/store/dto/StoreCreateRequest.java b/week8/domain/store/dto/StoreCreateRequest.java
new file mode 100644
index 0000000..c5de254
--- /dev/null
+++ b/week8/domain/store/dto/StoreCreateRequest.java
@@ -0,0 +1,14 @@
+package com.example.umc_spring_first.domain.store.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class StoreCreateRequest {
+ private String name;
+ private String address;
+ private String phone;
+ private String ownerNumber;
+}
+
diff --git a/week8/domain/store/entity/Store.java b/week8/domain/store/entity/Store.java
new file mode 100644
index 0000000..4dcb3ea
--- /dev/null
+++ b/week8/domain/store/entity/Store.java
@@ -0,0 +1,32 @@
+package com.example.umc_spring_first.domain.store.entity;
+
+import com.example.umc_spring_first.domain.mission.entity.Mission;
+import com.example.umc_spring_first.domain.review.entity.Review;
+import jakarta.persistence.*;
+import lombok.*;
+import java.time.LocalDateTime;
+import java.util.*;
+
+@Entity
+@Table(name = "store")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
+public class Store {
+
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(nullable = false, length = 50)
+ private String name;
+
+ private String address;
+ private String phone;
+ private LocalDateTime createAt;
+ private LocalDateTime updateAt;
+ private String ownerNumber;
+
+ @OneToMany(mappedBy = "store", cascade = CascadeType.ALL)
+ private List reviews = new ArrayList<>();
+
+ @OneToMany(mappedBy = "store", cascade = CascadeType.ALL)
+ private List missions = new ArrayList<>();
+}
\ No newline at end of file
diff --git a/week8/domain/store/repository/StoreRepository.java b/week8/domain/store/repository/StoreRepository.java
new file mode 100644
index 0000000..78d7ce6
--- /dev/null
+++ b/week8/domain/store/repository/StoreRepository.java
@@ -0,0 +1,7 @@
+package com.example.umc_spring_first.domain.store.repository;
+
+import com.example.umc_spring_first.domain.store.entity.Store;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface StoreRepository extends JpaRepository {
+}
diff --git a/week8/domain/store/service/StoreService.java b/week8/domain/store/service/StoreService.java
new file mode 100644
index 0000000..e0eb168
--- /dev/null
+++ b/week8/domain/store/service/StoreService.java
@@ -0,0 +1,29 @@
+package com.example.umc_spring_first.domain.store.service;
+
+import com.example.umc_spring_first.domain.store.dto.StoreCreateRequest;
+import com.example.umc_spring_first.domain.store.entity.Store;
+import com.example.umc_spring_first.domain.store.repository.StoreRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class StoreService {
+
+ private final StoreRepository storeRepository;
+
+ public Long addStore(StoreCreateRequest req) {
+
+ Store store = Store.builder()
+ .name(req.getName())
+ .address(req.getAddress())
+ .phone(req.getPhone())
+ .ownerNumber(req.getOwnerNumber())
+ .build();
+
+ storeRepository.save(store);
+ return store.getId();
+ }
+}